Invalidate console session when minio user doesn't exists (#332)
This commit is contained in:
@@ -101,8 +101,6 @@ Additionally, you can create policies to limit the privileges for `console` user
|
||||
To run the server:
|
||||
|
||||
```
|
||||
export CONSOLE_HMAC_JWT_SECRET=YOURJWTSIGNINGSECRET
|
||||
|
||||
#required to encrypt jwet payload
|
||||
export CONSOLE_PBKDF_PASSPHRASE=SECRET
|
||||
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
|
||||
`Console` will authenticate against `Kubernetes`using bearer tokens via HTTP `Authorization` header. The user will provide this token once
|
||||
in the login form, Console will validate it against Kubernetes (list apis) and if valid will generate and return a new Console sessions
|
||||
with encrypted claims (the user Service account token will be inside the JWT in the data field)
|
||||
with encrypted claims (the user Service account token will be inside the session encrypted token
|
||||
|
||||
# Kubernetes
|
||||
|
||||
|
||||
@@ -23,10 +23,9 @@ import (
|
||||
"github.com/minio/minio/pkg/env"
|
||||
)
|
||||
|
||||
// ConsoleSTSAndJWTDurationSeconds returns the default session duration for the STS requested tokens and the generated JWTs.
|
||||
// Ideally both values should match so jwt and Minio sts sessions expires at the same time.
|
||||
func GetConsoleSTSAndJWTDurationInSeconds() int {
|
||||
duration, err := strconv.Atoi(env.Get(ConsoleSTSAndJWTDurationSeconds, "3600"))
|
||||
// ConsoleSTSDurationSeconds returns the default session duration for the STS requested tokens.
|
||||
func GetConsoleSTSDurationInSeconds() int {
|
||||
duration, err := strconv.Atoi(env.Get(ConsoleSTSDurationSeconds, "3600"))
|
||||
if err != nil {
|
||||
duration = 3600
|
||||
}
|
||||
|
||||
@@ -17,7 +17,7 @@
|
||||
package token
|
||||
|
||||
const (
|
||||
ConsoleSTSAndJWTDurationSeconds = "CONSOLE_STS_AND_JWT_DURATION_SECONDS"
|
||||
ConsolePBKDFPassphrase = "CONSOLE_PBKDF_PASSPHRASE"
|
||||
ConsolePBKDFSalt = "CONSOLE_PBKDF_SALT"
|
||||
ConsoleSTSDurationSeconds = "CONSOLE_STS_DURATION_SECONDS"
|
||||
ConsolePBKDFPassphrase = "CONSOLE_PBKDF_PASSPHRASE"
|
||||
ConsolePBKDFSalt = "CONSOLE_PBKDF_SALT"
|
||||
)
|
||||
|
||||
@@ -60,17 +60,17 @@ func TestJWTAuthenticate(t *testing.T) {
|
||||
funcAssert.Equal(claims.SecretAccessKey, creds.SecretAccessKey)
|
||||
funcAssert.Equal(claims.SessionToken, creds.SessionToken)
|
||||
}
|
||||
// Test-2 : SessionTokenAuthenticate() return an error because of a tampered jwt
|
||||
// Test-2 : SessionTokenAuthenticate() return an error because of a tampered token
|
||||
if _, err := SessionTokenAuthenticate(badToken); err != nil {
|
||||
funcAssert.Equal("session token internal data is malformed", err.Error())
|
||||
}
|
||||
// Test-3 : SessionTokenAuthenticate() return an error because of an empty jwt
|
||||
// Test-3 : SessionTokenAuthenticate() return an error because of an empty token
|
||||
if _, err := SessionTokenAuthenticate(""); err != nil {
|
||||
funcAssert.Equal("session token missing", err.Error())
|
||||
}
|
||||
}
|
||||
|
||||
func TestIsJWTValid(t *testing.T) {
|
||||
func TestSessionTokenValid(t *testing.T) {
|
||||
funcAssert := assert.New(t)
|
||||
// Test-1 : SessionTokenAuthenticate() provided token is valid
|
||||
funcAssert.Equal(true, IsSessionTokenValid(goodToken))
|
||||
|
||||
@@ -646,7 +646,6 @@ func getTenantCreatedResponse(session *models.Principal, params admin_api.Create
|
||||
},
|
||||
Immutable: &imm,
|
||||
Data: map[string][]byte{
|
||||
"CONSOLE_HMAC_JWT_SECRET": []byte(RandomCharString(16)),
|
||||
"CONSOLE_PBKDF_PASSPHRASE": []byte(RandomCharString(16)),
|
||||
"CONSOLE_PBKDF_SALT": []byte(RandomCharString(8)),
|
||||
"CONSOLE_ACCESS_KEY": []byte(consoleAccess),
|
||||
|
||||
@@ -260,7 +260,7 @@ func newConsoleCredentials(accessKey, secretKey, location string) (*credentials.
|
||||
AccessKey: accessKey,
|
||||
SecretKey: secretKey,
|
||||
Location: location,
|
||||
DurationSeconds: xjwt.GetConsoleSTSAndJWTDurationInSeconds(),
|
||||
DurationSeconds: xjwt.GetConsoleSTSDurationInSeconds(),
|
||||
}
|
||||
stsClient := PrepareSTSClient(false)
|
||||
stsAssumeRole := &credentials.STSAssumeRole{
|
||||
@@ -274,23 +274,14 @@ func newConsoleCredentials(accessKey, secretKey, location string) (*credentials.
|
||||
}
|
||||
}
|
||||
|
||||
// GetClaimsFromJWT decrypt and returns the claims associated to a provided jwt
|
||||
func GetClaimsFromJWT(jwt string) (*auth.DecryptedClaims, error) {
|
||||
claims, err := auth.SessionTokenAuthenticate(jwt)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return claims, nil
|
||||
}
|
||||
|
||||
// getConsoleCredentialsFromSession returns the *consoleCredentials.Login associated to the
|
||||
// provided jwt, this is useful for running the Expire() or IsExpired() operations
|
||||
// provided session token, this is useful for running the Expire() or IsExpired() operations
|
||||
func getConsoleCredentialsFromSession(claims *models.Principal) *credentials.Credentials {
|
||||
return credentials.NewStaticV4(claims.AccessKeyID, claims.SecretAccessKey, claims.SessionToken)
|
||||
}
|
||||
|
||||
// newMinioClient creates a new MinIO client based on the consoleCredentials extracted
|
||||
// from the provided jwt
|
||||
// from the provided session token
|
||||
func newMinioClient(claims *models.Principal) (*minio.Client, error) {
|
||||
creds := getConsoleCredentialsFromSession(claims)
|
||||
stsClient := PrepareSTSClient(false)
|
||||
|
||||
@@ -61,7 +61,7 @@ func configureAPI(api *operations.ConsoleAPI) http.Handler {
|
||||
// Applies when the "x-token" header is set
|
||||
|
||||
api.KeyAuth = func(token string, scopes []string) (*models.Principal, error) {
|
||||
// we are validating the jwt by decrypting the claims inside, if the operation succed that means the jwt
|
||||
// we are validating the session token by decrypting the claims inside, if the operation succeed that means the jwt
|
||||
// was generated and signed by us in the first place
|
||||
claims, err := auth.SessionTokenAuthenticate(token)
|
||||
if err != nil {
|
||||
|
||||
@@ -87,6 +87,10 @@ func prepareError(err ...error) *models.Error {
|
||||
errorCode = 401
|
||||
errorMessage = errorGenericInvalidSession.Error()
|
||||
}
|
||||
if madmin.ToErrorResponse(err[0]).Code == "InvalidAccessKeyId" {
|
||||
errorCode = 401
|
||||
errorMessage = errorGenericInvalidSession.Error()
|
||||
}
|
||||
// console invalid session error
|
||||
if madmin.ToErrorResponse(err[0]).Code == "XMinioAdminNoSuchUser" {
|
||||
errorCode = 401
|
||||
|
||||
@@ -72,12 +72,12 @@ func login(credentials ConsoleCredentials, actions []string) (*string, error) {
|
||||
return nil, err
|
||||
}
|
||||
// if we made it here, the consoleCredentials work, generate a jwt with claims
|
||||
jwt, err := auth.NewEncryptedTokenForClient(&tokens, actions)
|
||||
token, err := auth.NewEncryptedTokenForClient(&tokens, actions)
|
||||
if err != nil {
|
||||
log.Println("error authenticating user", err)
|
||||
return nil, errInvalidCredentials
|
||||
}
|
||||
return &jwt, nil
|
||||
return &token, nil
|
||||
}
|
||||
|
||||
func getConfiguredRegionForLogin(client MinioAdmin) (string, error) {
|
||||
@@ -224,19 +224,19 @@ func getLoginOauth2AuthResponse(lr *models.LoginOauth2AuthRequest) (*models.Logi
|
||||
return nil, prepareError(err)
|
||||
}
|
||||
actions := acl.GetActionsStringFromPolicy(policy)
|
||||
// User was created correctly, create a new session/JWT
|
||||
// User was created correctly, create a new session
|
||||
creds, err := newConsoleCredentials(accessKey, secretKey, location)
|
||||
if err != nil {
|
||||
return nil, prepareError(err)
|
||||
}
|
||||
credentials := consoleCredentials{consoleCredentials: creds}
|
||||
jwt, err := login(credentials, actions)
|
||||
token, err := login(credentials, actions)
|
||||
if err != nil {
|
||||
return nil, prepareError(errInvalidCredentials, nil, err)
|
||||
}
|
||||
// serialize output
|
||||
loginResponse := &models.LoginResponse{
|
||||
SessionID: *jwt,
|
||||
SessionID: *token,
|
||||
}
|
||||
return loginResponse, nil
|
||||
}
|
||||
@@ -251,13 +251,13 @@ func getLoginOperatorResponse(lmr *models.LoginOperatorRequest) (*models.LoginRe
|
||||
}
|
||||
credentials := consoleCredentials{consoleCredentials: creds}
|
||||
var actions []string
|
||||
jwt, err := login(credentials, actions)
|
||||
token, err := login(credentials, actions)
|
||||
if err != nil {
|
||||
return nil, prepareError(errInvalidCredentials, nil, err)
|
||||
}
|
||||
// serialize output
|
||||
loginResponse := &models.LoginResponse{
|
||||
SessionID: *jwt,
|
||||
SessionID: *token,
|
||||
}
|
||||
return loginResponse, nil
|
||||
}
|
||||
|
||||
@@ -52,8 +52,8 @@ func TestLogin(t *testing.T) {
|
||||
SignerType: 0,
|
||||
}, nil
|
||||
}
|
||||
jwt, err := login(consoleCredentials, []string{""})
|
||||
funcAssert.NotEmpty(jwt, "JWT was returned empty")
|
||||
token, err := login(consoleCredentials, []string{""})
|
||||
funcAssert.NotEmpty(token, "Token was returned empty")
|
||||
funcAssert.Nil(err, "error creating a session")
|
||||
|
||||
// Test Case 2: Invalid credentials
|
||||
|
||||
@@ -35,7 +35,7 @@ func registerSessionHandlers(api *operations.ConsoleAPI) {
|
||||
})
|
||||
}
|
||||
|
||||
// getSessionResponse parse the jwt of the current session and returns a list of allowed actions to render in the UI
|
||||
// getSessionResponse parse the token of the current session and returns a list of allowed actions to render in the UI
|
||||
func getSessionResponse(session *models.Principal) (*models.SessionResponse, *models.Error) {
|
||||
// serialize output
|
||||
if session == nil {
|
||||
|
||||
Reference in New Issue
Block a user