Invalidate console session when minio user doesn't exists (#332)

This commit is contained in:
Lenin Alevski
2020-10-19 15:32:21 -07:00
committed by GitHub
parent e1fdf3fb28
commit f6ac7e047e
12 changed files with 28 additions and 37 deletions

View File

@@ -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

View File

@@ -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

View File

@@ -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
}

View File

@@ -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"
)

View File

@@ -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))

View File

@@ -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),

View File

@@ -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)

View File

@@ -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 {

View File

@@ -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

View File

@@ -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
}

View File

@@ -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

View File

@@ -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 {