From f6ac7e047eccaf4a6e69a1a239c1a2f76ae3a877 Mon Sep 17 00:00:00 2001 From: Lenin Alevski Date: Mon, 19 Oct 2020 15:32:21 -0700 Subject: [PATCH] Invalidate console session when minio user doesn't exists (#332) --- README.md | 2 -- docs/console_operator_mode.md | 2 +- pkg/auth/token/config.go | 7 +++---- pkg/auth/token/const.go | 6 +++--- pkg/auth/token_test.go | 6 +++--- restapi/admin_tenants.go | 1 - restapi/client.go | 15 +++------------ restapi/configure_console.go | 2 +- restapi/error.go | 4 ++++ restapi/user_login.go | 14 +++++++------- restapi/user_login_test.go | 4 ++-- restapi/user_session.go | 2 +- 12 files changed, 28 insertions(+), 37 deletions(-) diff --git a/README.md b/README.md index f6f7db8ce..fb410d27f 100644 --- a/README.md +++ b/README.md @@ -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 diff --git a/docs/console_operator_mode.md b/docs/console_operator_mode.md index 3a8828f20..056e7f2e5 100644 --- a/docs/console_operator_mode.md +++ b/docs/console_operator_mode.md @@ -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 diff --git a/pkg/auth/token/config.go b/pkg/auth/token/config.go index 54ea48b80..93c969b2a 100644 --- a/pkg/auth/token/config.go +++ b/pkg/auth/token/config.go @@ -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 } diff --git a/pkg/auth/token/const.go b/pkg/auth/token/const.go index a3dab1272..33c3fa286 100644 --- a/pkg/auth/token/const.go +++ b/pkg/auth/token/const.go @@ -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" ) diff --git a/pkg/auth/token_test.go b/pkg/auth/token_test.go index 377d90b24..f085e6f58 100644 --- a/pkg/auth/token_test.go +++ b/pkg/auth/token_test.go @@ -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)) diff --git a/restapi/admin_tenants.go b/restapi/admin_tenants.go index fab9a413d..1dfabd391 100644 --- a/restapi/admin_tenants.go +++ b/restapi/admin_tenants.go @@ -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), diff --git a/restapi/client.go b/restapi/client.go index 962da45b7..7a8175187 100644 --- a/restapi/client.go +++ b/restapi/client.go @@ -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) diff --git a/restapi/configure_console.go b/restapi/configure_console.go index 8784cef8a..ec7c52586 100644 --- a/restapi/configure_console.go +++ b/restapi/configure_console.go @@ -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 { diff --git a/restapi/error.go b/restapi/error.go index 4bcdde366..cef865059 100644 --- a/restapi/error.go +++ b/restapi/error.go @@ -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 diff --git a/restapi/user_login.go b/restapi/user_login.go index 047799e5a..486d5884a 100644 --- a/restapi/user_login.go +++ b/restapi/user_login.go @@ -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 } diff --git a/restapi/user_login_test.go b/restapi/user_login_test.go index 7200ec3a2..7d87c07b1 100644 --- a/restapi/user_login_test.go +++ b/restapi/user_login_test.go @@ -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 diff --git a/restapi/user_session.go b/restapi/user_session.go index 40ddba40c..c61eb64b2 100644 --- a/restapi/user_session.go +++ b/restapi/user_session.go @@ -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 {