Honor DurationSeconds properly for WebIdentity (#8581)

Also cleanup code to add various constants for
verbatim strings across the code base.

Fixes #8482
master
Harshavardhana 5 years ago committed by Nitish Tiwari
parent c7844fb1fb
commit b21835f195
  1. 72
      cmd/config/identity/openid/jwt.go
  2. 38
      cmd/config/identity/openid/jwt_test.go
  3. 3
      cmd/config/identity/openid/validators.go
  4. 84
      cmd/sts-handlers.go
  5. 4
      docs/sts/assume-role.md
  6. 4
      docs/sts/client-grants.md
  7. 4
      docs/sts/ldap.md
  8. 4
      docs/sts/web-identity.md
  9. 38
      pkg/auth/credentials.go
  10. 38
      pkg/auth/credentials_test.go

@ -28,6 +28,7 @@ import (
jwtgo "github.com/dgrijalva/jwt-go" jwtgo "github.com/dgrijalva/jwt-go"
"github.com/minio/minio/cmd/config" "github.com/minio/minio/cmd/config"
"github.com/minio/minio/pkg/auth"
"github.com/minio/minio/pkg/env" "github.com/minio/minio/pkg/env"
xnet "github.com/minio/minio/pkg/net" xnet "github.com/minio/minio/pkg/net"
) )
@ -107,36 +108,19 @@ type JWT struct {
Config Config
} }
func expToInt64(expI interface{}) (expAt int64, err error) {
switch exp := expI.(type) {
case float64:
expAt = int64(exp)
case int64:
expAt = exp
case json.Number:
expAt, err = exp.Int64()
if err != nil {
return 0, err
}
default:
return 0, ErrInvalidDuration
}
return expAt, nil
}
// GetDefaultExpiration - returns the expiration seconds expected. // GetDefaultExpiration - returns the expiration seconds expected.
func GetDefaultExpiration(dsecs string) (time.Duration, error) { func GetDefaultExpiration(dsecs string) (time.Duration, error) {
defaultExpiryDuration := time.Duration(60) * time.Minute // Defaults to 1hr. defaultExpiryDuration := time.Duration(60) * time.Minute // Defaults to 1hr.
if dsecs != "" { if dsecs != "" {
expirySecs, err := strconv.ParseInt(dsecs, 10, 64) expirySecs, err := strconv.ParseInt(dsecs, 10, 64)
if err != nil { if err != nil {
return 0, ErrInvalidDuration return 0, auth.ErrInvalidDuration
} }
// The duration, in seconds, of the role session. // The duration, in seconds, of the role session.
// The value can range from 900 seconds (15 minutes) // The value can range from 900 seconds (15 minutes)
// to 12 hours. // to 12 hours.
if expirySecs < 900 || expirySecs > 43200 { if expirySecs < 900 || expirySecs > 43200 {
return 0, ErrInvalidDuration return 0, auth.ErrInvalidDuration
} }
defaultExpiryDuration = time.Duration(expirySecs) * time.Second defaultExpiryDuration = time.Duration(expirySecs) * time.Second
@ -144,6 +128,39 @@ func GetDefaultExpiration(dsecs string) (time.Duration, error) {
return defaultExpiryDuration, nil return defaultExpiryDuration, nil
} }
func updateClaimsExpiry(dsecs string, claims map[string]interface{}) error {
expStr := claims["exp"]
if expStr == "" {
return ErrTokenExpired
}
// No custom duration requested, the claims can be used as is.
if dsecs == "" {
return nil
}
expAt, err := auth.ExpToInt64(expStr)
if err != nil {
return err
}
defaultExpiryDuration, err := GetDefaultExpiration(dsecs)
if err != nil {
return err
}
// Verify if JWT expiry is lesser than default expiry duration,
// if that is the case then set the default expiration to be
// from the JWT expiry claim.
if time.Unix(expAt, 0).UTC().Sub(time.Now().UTC()) < defaultExpiryDuration {
defaultExpiryDuration = time.Unix(expAt, 0).UTC().Sub(time.Now().UTC())
} // else honor the specified expiry duration.
expiry := time.Now().UTC().Add(defaultExpiryDuration).Unix()
claims["exp"] = strconv.FormatInt(expiry, 10) // update with new expiry.
return nil
}
// Validate - validates the access token. // Validate - validates the access token.
func (p *JWT) Validate(token, dsecs string) (map[string]interface{}, error) { func (p *JWT) Validate(token, dsecs string) (map[string]interface{}, error) {
jp := new(jwtgo.Parser) jp := new(jwtgo.Parser)
@ -173,25 +190,10 @@ func (p *JWT) Validate(token, dsecs string) (map[string]interface{}, error) {
return nil, ErrTokenExpired return nil, ErrTokenExpired
} }
expAt, err := expToInt64(claims["exp"]) if err = updateClaimsExpiry(dsecs, claims); err != nil {
if err != nil {
return nil, err return nil, err
} }
defaultExpiryDuration, err := GetDefaultExpiration(dsecs)
if err != nil {
return nil, err
}
if time.Unix(expAt, 0).UTC().Sub(time.Now().UTC()) < defaultExpiryDuration {
defaultExpiryDuration = time.Unix(expAt, 0).UTC().Sub(time.Now().UTC())
}
expiry := time.Now().UTC().Add(defaultExpiryDuration).Unix()
if expAt < expiry {
claims["exp"] = strconv.FormatInt(expAt, 64)
}
return claims, nil return claims, nil
} }

@ -1,5 +1,5 @@
/* /*
* MinIO Cloud Storage, (C) 2018 MinIO, Inc. * MinIO Cloud Storage, (C) 2018-2019 MinIO, Inc.
* *
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
@ -26,6 +26,42 @@ import (
xnet "github.com/minio/minio/pkg/net" xnet "github.com/minio/minio/pkg/net"
) )
func TestUpdateClaimsExpiry(t *testing.T) {
testCases := []struct {
exp interface{}
dsecs string
expectedFailure bool
}{
{"", "", true},
{"-1", "0", true},
{"-1", "900", true},
{"1574812326", "900", false},
{1574812326, "900", false},
{int64(1574812326), "900", false},
{int(1574812326), "900", false},
{uint(1574812326), "900", false},
{uint64(1574812326), "900", false},
{json.Number("1574812326"), "900", false},
{1574812326.000, "900", false},
{time.Duration(3) * time.Minute, "900", false},
}
for _, testCase := range testCases {
testCase := testCase
t.Run("", func(t *testing.T) {
claims := map[string]interface{}{}
claims["exp"] = testCase.exp
err := updateClaimsExpiry(testCase.dsecs, claims)
if err != nil && !testCase.expectedFailure {
t.Errorf("Expected success, got failure %s", err)
}
if err == nil && testCase.expectedFailure {
t.Error("Expected failure, got success")
}
})
}
}
func TestJWT(t *testing.T) { func TestJWT(t *testing.T) {
const jsonkey = `{"keys": const jsonkey = `{"keys":
[ [

@ -38,8 +38,7 @@ type Validator interface {
// ErrTokenExpired - error token expired // ErrTokenExpired - error token expired
var ( var (
ErrTokenExpired = errors.New("token expired") ErrTokenExpired = errors.New("token expired")
ErrInvalidDuration = errors.New("duration higher than token expiry")
) )
// Validators - holds list of providers indexed by provider id. // Validators - holds list of providers indexed by provider id.

@ -36,7 +36,15 @@ import (
const ( const (
// STS API version. // STS API version.
stsAPIVersion = "2011-06-15" stsAPIVersion = "2011-06-15"
stsVersion = "Version"
stsAction = "Action"
stsPolicy = "Policy"
stsToken = "Token"
stsWebIdentityToken = "WebIdentityToken"
stsDurationSeconds = "DurationSeconds"
stsLDAPUsername = "LDAPUsername"
stsLDAPPassword = "LDAPPassword"
// STS API action constants // STS API action constants
clientGrants = "AssumeRoleWithClientGrants" clientGrants = "AssumeRoleWithClientGrants"
@ -46,6 +54,10 @@ const (
stsRequestBodyLimit = 10 * (1 << 20) // 10 MiB stsRequestBodyLimit = 10 * (1 << 20) // 10 MiB
// JWT claim keys
expClaim = "exp"
subClaim = "sub"
// LDAP claim keys // LDAP claim keys
ldapUser = "ldapUser" ldapUser = "ldapUser"
ldapGroups = "ldapGroups" ldapGroups = "ldapGroups"
@ -71,30 +83,30 @@ func registerSTSRouter(router *mux.Router) {
}).HandlerFunc(httpTraceAll(sts.AssumeRole)) }).HandlerFunc(httpTraceAll(sts.AssumeRole))
// Assume roles with JWT handler, handles both ClientGrants and WebIdentity. // Assume roles with JWT handler, handles both ClientGrants and WebIdentity.
stsRouter.Methods("POST").MatcherFunc(func(r *http.Request, rm *mux.RouteMatch) bool { stsRouter.Methods(http.MethodPost).MatcherFunc(func(r *http.Request, rm *mux.RouteMatch) bool {
ctypeOk := wildcard.MatchSimple("application/x-www-form-urlencoded*", r.Header.Get(xhttp.ContentType)) ctypeOk := wildcard.MatchSimple("application/x-www-form-urlencoded*", r.Header.Get(xhttp.ContentType))
noQueries := len(r.URL.Query()) == 0 noQueries := len(r.URL.Query()) == 0
return ctypeOk && noQueries return ctypeOk && noQueries
}).HandlerFunc(httpTraceAll(sts.AssumeRoleWithJWT)) }).HandlerFunc(httpTraceAll(sts.AssumeRoleWithJWT))
// AssumeRoleWithClientGrants // AssumeRoleWithClientGrants
stsRouter.Methods("POST").HandlerFunc(httpTraceAll(sts.AssumeRoleWithClientGrants)). stsRouter.Methods(http.MethodPost).HandlerFunc(httpTraceAll(sts.AssumeRoleWithClientGrants)).
Queries("Action", clientGrants). Queries(stsAction, clientGrants).
Queries("Version", stsAPIVersion). Queries(stsVersion, stsAPIVersion).
Queries("Token", "{Token:.*}") Queries(stsToken, "{Token:.*}")
// AssumeRoleWithWebIdentity // AssumeRoleWithWebIdentity
stsRouter.Methods("POST").HandlerFunc(httpTraceAll(sts.AssumeRoleWithWebIdentity)). stsRouter.Methods(http.MethodPost).HandlerFunc(httpTraceAll(sts.AssumeRoleWithWebIdentity)).
Queries("Action", webIdentity). Queries(stsAction, webIdentity).
Queries("Version", stsAPIVersion). Queries(stsVersion, stsAPIVersion).
Queries("WebIdentityToken", "{Token:.*}") Queries(stsWebIdentityToken, "{Token:.*}")
// AssumeRoleWithLDAPIdentity // AssumeRoleWithLDAPIdentity
stsRouter.Methods("POST").HandlerFunc(httpTraceAll(sts.AssumeRoleWithLDAPIdentity)). stsRouter.Methods(http.MethodPost).HandlerFunc(httpTraceAll(sts.AssumeRoleWithLDAPIdentity)).
Queries("Action", ldapIdentity). Queries(stsAction, ldapIdentity).
Queries("Version", stsAPIVersion). Queries(stsVersion, stsAPIVersion).
Queries("LDAPUsername", "{LDAPUsername:.*}"). Queries(stsLDAPUsername, "{LDAPUsername:.*}").
Queries("LDAPPassword", "{LDAPPassword:.*}") Queries(stsLDAPPassword, "{LDAPPassword:.*}")
} }
func checkAssumeRoleAuth(ctx context.Context, r *http.Request) (user auth.Credentials, stsErr STSErrorCode) { func checkAssumeRoleAuth(ctx context.Context, r *http.Request) (user auth.Credentials, stsErr STSErrorCode) {
@ -141,12 +153,12 @@ func (sts *stsAPIHandlers) AssumeRole(w http.ResponseWriter, r *http.Request) {
return return
} }
if r.Form.Get("Version") != stsAPIVersion { if r.Form.Get(stsVersion) != stsAPIVersion {
writeSTSErrorResponse(ctx, w, ErrSTSMissingParameter, fmt.Errorf("Invalid STS API version %s, expecting %s", r.Form.Get("Version"), stsAPIVersion)) writeSTSErrorResponse(ctx, w, ErrSTSMissingParameter, fmt.Errorf("Invalid STS API version %s, expecting %s", r.Form.Get(stsVersion), stsAPIVersion))
return return
} }
action := r.Form.Get("Action") action := r.Form.Get(stsAction)
switch action { switch action {
case assumeRole: case assumeRole:
default: default:
@ -157,7 +169,7 @@ func (sts *stsAPIHandlers) AssumeRole(w http.ResponseWriter, r *http.Request) {
ctx = newContext(r, w, action) ctx = newContext(r, w, action)
defer logger.AuditLog(w, r, action, nil) defer logger.AuditLog(w, r, action, nil)
sessionPolicyStr := r.Form.Get("Policy") sessionPolicyStr := r.Form.Get(stsPolicy)
// https://docs.aws.amazon.com/STS/latest/APIReference/API_AssumeRole.html // https://docs.aws.amazon.com/STS/latest/APIReference/API_AssumeRole.html
// The plain text that you use for both inline and managed session // The plain text that you use for both inline and managed session
// policies shouldn't exceed 2048 characters. // policies shouldn't exceed 2048 characters.
@ -182,7 +194,7 @@ func (sts *stsAPIHandlers) AssumeRole(w http.ResponseWriter, r *http.Request) {
var err error var err error
m := make(map[string]interface{}) m := make(map[string]interface{})
m["exp"], err = openid.GetDefaultExpiration(r.Form.Get("DurationSeconds")) m[expClaim], err = openid.GetDefaultExpiration(r.Form.Get(stsDurationSeconds))
if err != nil { if err != nil {
writeSTSErrorResponse(ctx, w, ErrSTSInvalidParameterValue, err) writeSTSErrorResponse(ctx, w, ErrSTSInvalidParameterValue, err)
return return
@ -248,12 +260,12 @@ func (sts *stsAPIHandlers) AssumeRoleWithJWT(w http.ResponseWriter, r *http.Requ
return return
} }
if r.Form.Get("Version") != stsAPIVersion { if r.Form.Get(stsVersion) != stsAPIVersion {
writeSTSErrorResponse(ctx, w, ErrSTSMissingParameter, fmt.Errorf("Invalid STS API version %s, expecting %s", r.Form.Get("Version"), stsAPIVersion)) writeSTSErrorResponse(ctx, w, ErrSTSMissingParameter, fmt.Errorf("Invalid STS API version %s, expecting %s", r.Form.Get("Version"), stsAPIVersion))
return return
} }
action := r.Form.Get("Action") action := r.Form.Get(stsAction)
switch action { switch action {
case clientGrants, webIdentity: case clientGrants, webIdentity:
default: default:
@ -275,12 +287,12 @@ func (sts *stsAPIHandlers) AssumeRoleWithJWT(w http.ResponseWriter, r *http.Requ
return return
} }
token := r.Form.Get("Token") token := r.Form.Get(stsToken)
if token == "" { if token == "" {
token = r.Form.Get("WebIdentityToken") token = r.Form.Get(stsWebIdentityToken)
} }
m, err := v.Validate(token, r.Form.Get("DurationSeconds")) m, err := v.Validate(token, r.Form.Get(stsDurationSeconds))
if err != nil { if err != nil {
switch err { switch err {
case openid.ErrTokenExpired: case openid.ErrTokenExpired:
@ -291,7 +303,7 @@ func (sts *stsAPIHandlers) AssumeRoleWithJWT(w http.ResponseWriter, r *http.Requ
writeSTSErrorResponse(ctx, w, ErrSTSWebIdentityExpiredToken, err) writeSTSErrorResponse(ctx, w, ErrSTSWebIdentityExpiredToken, err)
} }
return return
case openid.ErrInvalidDuration: case auth.ErrInvalidDuration:
writeSTSErrorResponse(ctx, w, ErrSTSInvalidParameterValue, err) writeSTSErrorResponse(ctx, w, ErrSTSInvalidParameterValue, err)
return return
} }
@ -299,7 +311,7 @@ func (sts *stsAPIHandlers) AssumeRoleWithJWT(w http.ResponseWriter, r *http.Requ
return return
} }
sessionPolicyStr := r.Form.Get("Policy") sessionPolicyStr := r.Form.Get(stsPolicy)
// https://docs.aws.amazon.com/STS/latest/APIReference/API_AssumeRoleWithWebIdentity.html // https://docs.aws.amazon.com/STS/latest/APIReference/API_AssumeRoleWithWebIdentity.html
// The plain text that you use for both inline and managed session // The plain text that you use for both inline and managed session
// policies shouldn't exceed 2048 characters. // policies shouldn't exceed 2048 characters.
@ -343,7 +355,7 @@ func (sts *stsAPIHandlers) AssumeRoleWithJWT(w http.ResponseWriter, r *http.Requ
} }
var subFromToken string var subFromToken string
if v, ok := m["sub"]; ok { if v, ok := m[subClaim]; ok {
subFromToken, _ = v.(string) subFromToken, _ = v.(string)
} }
@ -415,12 +427,12 @@ func (sts *stsAPIHandlers) AssumeRoleWithLDAPIdentity(w http.ResponseWriter, r *
return return
} }
if r.Form.Get("Version") != stsAPIVersion { if r.Form.Get(stsVersion) != stsAPIVersion {
writeSTSErrorResponse(ctx, w, ErrSTSMissingParameter, fmt.Errorf("Invalid STS API version %s, expecting %s", r.Form.Get("Version"), stsAPIVersion)) writeSTSErrorResponse(ctx, w, ErrSTSMissingParameter, fmt.Errorf("Invalid STS API version %s, expecting %s", r.Form.Get("Version"), stsAPIVersion))
return return
} }
action := r.Form.Get("Action") action := r.Form.Get(stsAction)
switch action { switch action {
case ldapIdentity: case ldapIdentity:
default: default:
@ -431,15 +443,15 @@ func (sts *stsAPIHandlers) AssumeRoleWithLDAPIdentity(w http.ResponseWriter, r *
ctx = newContext(r, w, action) ctx = newContext(r, w, action)
defer logger.AuditLog(w, r, action, nil) defer logger.AuditLog(w, r, action, nil)
ldapUsername := r.Form.Get("LDAPUsername") ldapUsername := r.Form.Get(stsLDAPUsername)
ldapPassword := r.Form.Get("LDAPPassword") ldapPassword := r.Form.Get(stsLDAPPassword)
if ldapUsername == "" || ldapPassword == "" { if ldapUsername == "" || ldapPassword == "" {
writeSTSErrorResponse(ctx, w, ErrSTSMissingParameter, fmt.Errorf("LDAPUsername and LDAPPassword cannot be empty")) writeSTSErrorResponse(ctx, w, ErrSTSMissingParameter, fmt.Errorf("LDAPUsername and LDAPPassword cannot be empty"))
return return
} }
sessionPolicyStr := r.Form.Get("Policy") sessionPolicyStr := r.Form.Get(stsPolicy)
// https://docs.aws.amazon.com/STS/latest/APIReference/API_AssumeRole.html // https://docs.aws.amazon.com/STS/latest/APIReference/API_AssumeRole.html
// The plain text that you use for both inline and managed session // The plain text that you use for both inline and managed session
// policies shouldn't exceed 2048 characters. // policies shouldn't exceed 2048 characters.
@ -516,9 +528,9 @@ func (sts *stsAPIHandlers) AssumeRoleWithLDAPIdentity(w http.ResponseWriter, r *
} }
expiryDur := globalLDAPConfig.GetExpiryDuration() expiryDur := globalLDAPConfig.GetExpiryDuration()
m := map[string]interface{}{ m := map[string]interface{}{
"exp": UTCNow().Add(expiryDur).Unix(), expClaim: UTCNow().Add(expiryDur).Unix(),
"ldapUser": ldapUsername, ldapUser: ldapUsername,
"ldapGroups": groups, ldapGroups: groups,
} }
if len(sessionPolicyStr) > 0 { if len(sessionPolicyStr) > 0 {

@ -10,7 +10,7 @@
- [Policy](#policy) - [Policy](#policy)
- [Response Elements](#response-elements) - [Response Elements](#response-elements)
- [Errors](#errors) - [Errors](#errors)
- [Sample Request](#sample-request) - [Sample `POST` Request](#sample-post-request)
- [Sample Response](#sample-response) - [Sample Response](#sample-response)
- [Testing](#testing) - [Testing](#testing)
@ -60,7 +60,7 @@ XML response for this API is similar to [AWS STS AssumeRole](https://docs.aws.am
### Errors ### Errors
XML error response for this API is similar to [AWS STS AssumeRole](https://docs.aws.amazon.com/STS/latest/APIReference/API_AssumeRole.html#API_AssumeRole_Errors) XML error response for this API is similar to [AWS STS AssumeRole](https://docs.aws.amazon.com/STS/latest/APIReference/API_AssumeRole.html#API_AssumeRole_Errors)
## Sample Request ## Sample `POST` Request
``` ```
http://minio:9000/?Action=AssumeRole&DurationSeconds=3600&Version=2011-06-15&Policy={"Version":"2012-10-17","Statement":[{"Sid":"Stmt1","Effect":"Allow","Action":"s3:*","Resource":"arn:aws:s3:::*"}]}&AUTHPARAMS http://minio:9000/?Action=AssumeRole&DurationSeconds=3600&Version=2011-06-15&Policy={"Version":"2012-10-17","Statement":[{"Sid":"Stmt1","Effect":"Allow","Action":"s3:*","Resource":"arn:aws:s3:::*"}]}&AUTHPARAMS
``` ```

@ -10,7 +10,7 @@
- [Policy](#policy) - [Policy](#policy)
- [Response Elements](#response-elements) - [Response Elements](#response-elements)
- [Errors](#errors) - [Errors](#errors)
- [Sample Request](#sample-request) - [Sample `POST` Request](#sample-post-request)
- [Sample Response](#sample-response) - [Sample Response](#sample-response)
- [Testing](#testing) - [Testing](#testing)
@ -64,7 +64,7 @@ XML response for this API is similar to [AWS STS AssumeRoleWithWebIdentity](http
### Errors ### Errors
XML error response for this API is similar to [AWS STS AssumeRoleWithWebIdentity](https://docs.aws.amazon.com/STS/latest/APIReference/API_AssumeRoleWithWebIdentity.html#API_AssumeRoleWithWebIdentity_Errors) XML error response for this API is similar to [AWS STS AssumeRoleWithWebIdentity](https://docs.aws.amazon.com/STS/latest/APIReference/API_AssumeRoleWithWebIdentity.html#API_AssumeRoleWithWebIdentity_Errors)
## Sample Request ## Sample `POST` Request
``` ```
http://minio.cluster:9000?Action=AssumeRoleWithClientGrants&DurationSeconds=3600&Token=eyJ4NXQiOiJOVEF4Wm1NeE5ETXlaRGczTVRVMVpHTTBNekV6T0RKaFpXSTRORE5sWkRVMU9HRmtOakZpTVEiLCJraWQiOiJOVEF4Wm1NeE5ETXlaRGczTVRVMVpHTTBNekV6T0RKaFpXSTRORE5sWkRVMU9HRmtOakZpTVEiLCJhbGciOiJSUzI1NiJ9.eyJhdWQiOiJQb0VnWFA2dVZPNDVJc0VOUm5nRFhqNUF1NVlhIiwiYXpwIjoiUG9FZ1hQNnVWTzQ1SXNFTlJuZ0RYajVBdTVZYSIsImlzcyI6Imh0dHBzOlwvXC9sb2NhbGhvc3Q6OTQ0M1wvb2F1dGgyXC90b2tlbiIsImV4cCI6MTU0MTgwOTU4MiwiaWF0IjoxNTQxODA1OTgyLCJqdGkiOiI2Y2YyMGIwZS1lNGZmLTQzZmQtYTdiYS1kYTc3YTE3YzM2MzYifQ.Jm29jPliRvrK6Os34nSK3rhzIYLFjE__zdVGNng3uGKXGKzP3We_i6NPnhA0szJXMOKglXzUF1UgSz8MctbaxFS8XDusQPVe4LkB_45hwBm6TmBxzui911nt-1RbBLN_jZIlvl2lPrbTUH5hSn9kEkph6seWanTNQpz9tNEoVa6R_OX3kpJqxe8tLQUWw453A1JTwFNhdHa6-f1K8_Q_eEZ_4gOYINQ9t_fhTibdbkXZkJQFLop-Jwoybi9s4nwQU_dATocgcufq5eCeNItQeleT-23lGxIz0X7CiJrJynYLdd-ER0F77SumqEb5iCxhxuf4H7dovwd1kAmyKzLxpw&Version=2011-06-15 http://minio.cluster:9000?Action=AssumeRoleWithClientGrants&DurationSeconds=3600&Token=eyJ4NXQiOiJOVEF4Wm1NeE5ETXlaRGczTVRVMVpHTTBNekV6T0RKaFpXSTRORE5sWkRVMU9HRmtOakZpTVEiLCJraWQiOiJOVEF4Wm1NeE5ETXlaRGczTVRVMVpHTTBNekV6T0RKaFpXSTRORE5sWkRVMU9HRmtOakZpTVEiLCJhbGciOiJSUzI1NiJ9.eyJhdWQiOiJQb0VnWFA2dVZPNDVJc0VOUm5nRFhqNUF1NVlhIiwiYXpwIjoiUG9FZ1hQNnVWTzQ1SXNFTlJuZ0RYajVBdTVZYSIsImlzcyI6Imh0dHBzOlwvXC9sb2NhbGhvc3Q6OTQ0M1wvb2F1dGgyXC90b2tlbiIsImV4cCI6MTU0MTgwOTU4MiwiaWF0IjoxNTQxODA1OTgyLCJqdGkiOiI2Y2YyMGIwZS1lNGZmLTQzZmQtYTdiYS1kYTc3YTE3YzM2MzYifQ.Jm29jPliRvrK6Os34nSK3rhzIYLFjE__zdVGNng3uGKXGKzP3We_i6NPnhA0szJXMOKglXzUF1UgSz8MctbaxFS8XDusQPVe4LkB_45hwBm6TmBxzui911nt-1RbBLN_jZIlvl2lPrbTUH5hSn9kEkph6seWanTNQpz9tNEoVa6R_OX3kpJqxe8tLQUWw453A1JTwFNhdHa6-f1K8_Q_eEZ_4gOYINQ9t_fhTibdbkXZkJQFLop-Jwoybi9s4nwQU_dATocgcufq5eCeNItQeleT-23lGxIz0X7CiJrJynYLdd-ER0F77SumqEb5iCxhxuf4H7dovwd1kAmyKzLxpw&Version=2011-06-15
``` ```

@ -14,7 +14,7 @@
- [Policy](#policy) - [Policy](#policy)
- [Response Elements](#response-elements) - [Response Elements](#response-elements)
- [Errors](#errors) - [Errors](#errors)
- [Sample Request](#sample-request) - [Sample `POST` Request](#sample-post-request)
- [Sample Response](#sample-response) - [Sample Response](#sample-response)
- [Testing](#testing) - [Testing](#testing)
@ -189,7 +189,7 @@ XML response for this API is similar to [AWS STS AssumeRoleWithWebIdentity](http
### Errors ### Errors
XML error response for this API is similar to [AWS STS AssumeRoleWithWebIdentity](https://docs.aws.amazon.com/STS/latest/APIReference/API_AssumeRoleWithWebIdentity.html#API_AssumeRoleWithWebIdentity_Errors) XML error response for this API is similar to [AWS STS AssumeRoleWithWebIdentity](https://docs.aws.amazon.com/STS/latest/APIReference/API_AssumeRoleWithWebIdentity.html#API_AssumeRoleWithWebIdentity_Errors)
## Sample Request ## Sample `POST` Request
``` ```
http://minio.cluster:9000?Action=AssumeRoleWithLDAPIdentity&LDAPUsername=foouser&LDAPPassword=foouserpassword&Version=2011-06-15 http://minio.cluster:9000?Action=AssumeRoleWithLDAPIdentity&LDAPUsername=foouser&LDAPPassword=foouserpassword&Version=2011-06-15
``` ```

@ -10,7 +10,7 @@
- [Policy](#policy) - [Policy](#policy)
- [Response Elements](#response-elements) - [Response Elements](#response-elements)
- [Errors](#errors) - [Errors](#errors)
- [Sample Request](#sample-request) - [Sample `POST` Request](#sample-post-request)
- [Sample Response](#sample-response) - [Sample Response](#sample-response)
- [Testing](#testing) - [Testing](#testing)
- [Authorization Flow](#authorization-flow) - [Authorization Flow](#authorization-flow)
@ -64,7 +64,7 @@ XML response for this API is similar to [AWS STS AssumeRoleWithWebIdentity](http
### Errors ### Errors
XML error response for this API is similar to [AWS STS AssumeRoleWithWebIdentity](https://docs.aws.amazon.com/STS/latest/APIReference/API_AssumeRoleWithWebIdentity.html#API_AssumeRoleWithWebIdentity_Errors) XML error response for this API is similar to [AWS STS AssumeRoleWithWebIdentity](https://docs.aws.amazon.com/STS/latest/APIReference/API_AssumeRoleWithWebIdentity.html#API_AssumeRoleWithWebIdentity_Errors)
## Sample Request ## Sample `POST` Request
``` ```
http://minio.cluster:9000?Action=AssumeRoleWithWebIdentity&DurationSeconds=3600&WebIdentityToken=eyJ4NXQiOiJOVEF4Wm1NeE5ETXlaRGczTVRVMVpHTTBNekV6T0RKaFpXSTRORE5sWkRVMU9HRmtOakZpTVEiLCJraWQiOiJOVEF4Wm1NeE5ETXlaRGczTVRVMVpHTTBNekV6T0RKaFpXSTRORE5sWkRVMU9HRmtOakZpTVEiLCJhbGciOiJSUzI1NiJ9.eyJhdWQiOiJQb0VnWFA2dVZPNDVJc0VOUm5nRFhqNUF1NVlhIiwiYXpwIjoiUG9FZ1hQNnVWTzQ1SXNFTlJuZ0RYajVBdTVZYSIsImlzcyI6Imh0dHBzOlwvXC9sb2NhbGhvc3Q6OTQ0M1wvb2F1dGgyXC90b2tlbiIsImV4cCI6MTU0MTgwOTU4MiwiaWF0IjoxNTQxODA1OTgyLCJqdGkiOiI2Y2YyMGIwZS1lNGZmLTQzZmQtYTdiYS1kYTc3YTE3YzM2MzYifQ.Jm29jPliRvrK6Os34nSK3rhzIYLFjE__zdVGNng3uGKXGKzP3We_i6NPnhA0szJXMOKglXzUF1UgSz8MctbaxFS8XDusQPVe4LkB_45hwBm6TmBxzui911nt-1RbBLN_jZIlvl2lPrbTUH5hSn9kEkph6seWanTNQpz9tNEoVa6R_OX3kpJqxe8tLQUWw453A1JTwFNhdHa6-f1K8_Q_eEZ_4gOYINQ9t_fhTibdbkXZkJQFLop-Jwoybi9s4nwQU_dATocgcufq5eCeNItQeleT-23lGxIz0X7CiJrJynYLdd-ER0F77SumqEb5iCxhxuf4H7dovwd1kAmyKzLxpw&Version=2011-06-15 http://minio.cluster:9000?Action=AssumeRoleWithWebIdentity&DurationSeconds=3600&WebIdentityToken=eyJ4NXQiOiJOVEF4Wm1NeE5ETXlaRGczTVRVMVpHTTBNekV6T0RKaFpXSTRORE5sWkRVMU9HRmtOakZpTVEiLCJraWQiOiJOVEF4Wm1NeE5ETXlaRGczTVRVMVpHTTBNekV6T0RKaFpXSTRORE5sWkRVMU9HRmtOakZpTVEiLCJhbGciOiJSUzI1NiJ9.eyJhdWQiOiJQb0VnWFA2dVZPNDVJc0VOUm5nRFhqNUF1NVlhIiwiYXpwIjoiUG9FZ1hQNnVWTzQ1SXNFTlJuZ0RYajVBdTVZYSIsImlzcyI6Imh0dHBzOlwvXC9sb2NhbGhvc3Q6OTQ0M1wvb2F1dGgyXC90b2tlbiIsImV4cCI6MTU0MTgwOTU4MiwiaWF0IjoxNTQxODA1OTgyLCJqdGkiOiI2Y2YyMGIwZS1lNGZmLTQzZmQtYTdiYS1kYTc3YTE3YzM2MzYifQ.Jm29jPliRvrK6Os34nSK3rhzIYLFjE__zdVGNng3uGKXGKzP3We_i6NPnhA0szJXMOKglXzUF1UgSz8MctbaxFS8XDusQPVe4LkB_45hwBm6TmBxzui911nt-1RbBLN_jZIlvl2lPrbTUH5hSn9kEkph6seWanTNQpz9tNEoVa6R_OX3kpJqxe8tLQUWw453A1JTwFNhdHa6-f1K8_Q_eEZ_4gOYINQ9t_fhTibdbkXZkJQFLop-Jwoybi9s4nwQU_dATocgcufq5eCeNItQeleT-23lGxIz0X7CiJrJynYLdd-ER0F77SumqEb5iCxhxuf4H7dovwd1kAmyKzLxpw&Version=2011-06-15
``` ```

@ -23,6 +23,7 @@ import (
"encoding/json" "encoding/json"
"errors" "errors"
"fmt" "fmt"
"strconv"
"strings" "strings"
"time" "time"
@ -136,25 +137,37 @@ func (cred Credentials) Equal(ccred Credentials) bool {
var timeSentinel = time.Unix(0, 0).UTC() var timeSentinel = time.Unix(0, 0).UTC()
func expToInt64(expI interface{}) (expAt int64, err error) { // ErrInvalidDuration invalid token expiry
var ErrInvalidDuration = errors.New("invalid token expiry")
// ExpToInt64 - convert input interface value to int64.
func ExpToInt64(expI interface{}) (expAt int64, err error) {
switch exp := expI.(type) { switch exp := expI.(type) {
case string:
expAt, err = strconv.ParseInt(exp, 10, 64)
case float64: case float64:
expAt = int64(exp) expAt, err = int64(exp), nil
case int64: case int64:
expAt = exp expAt, err = exp, nil
case int:
expAt, err = int64(exp), nil
case uint64:
expAt, err = int64(exp), nil
case uint:
expAt, err = int64(exp), nil
case json.Number: case json.Number:
expAt, err = exp.Int64() expAt, err = exp.Int64()
if err != nil {
return 0, err
}
case time.Duration: case time.Duration:
return time.Now().UTC().Add(exp).Unix(), nil expAt, err = time.Now().UTC().Add(exp).Unix(), nil
case nil: case nil:
return 0, nil expAt, err = 0, nil
default: default:
return 0, errors.New("invalid expiry value") expAt, err = 0, ErrInvalidDuration
}
if expAt < 0 {
return 0, ErrInvalidDuration
} }
return expAt, nil return expAt, err
} }
// GetNewCredentialsWithMetadata generates and returns new credential with expiry. // GetNewCredentialsWithMetadata generates and returns new credential with expiry.
@ -185,10 +198,11 @@ func GetNewCredentialsWithMetadata(m map[string]interface{}, tokenSecret string)
if err != nil { if err != nil {
return cred, err return cred, err
} }
cred.SecretKey = strings.Replace(string([]byte(base64.StdEncoding.EncodeToString(keyBytes))[:secretKeyMaxLen]), "/", "+", -1) cred.SecretKey = strings.Replace(string([]byte(base64.StdEncoding.EncodeToString(keyBytes))[:secretKeyMaxLen]),
"/", "+", -1)
cred.Status = "on" cred.Status = "on"
expiry, err := expToInt64(m["exp"]) expiry, err := ExpToInt64(m["exp"])
if err != nil { if err != nil {
return cred, err return cred, err
} }

@ -16,7 +16,43 @@
package auth package auth
import "testing" import (
"encoding/json"
"testing"
"time"
)
func TestExpToInt64(t *testing.T) {
testCases := []struct {
exp interface{}
expectedFailure bool
}{
{"", true},
{"-1", true},
{"1574812326", false},
{1574812326, false},
{int64(1574812326), false},
{int(1574812326), false},
{uint(1574812326), false},
{uint64(1574812326), false},
{json.Number("1574812326"), false},
{1574812326.000, false},
{time.Duration(3) * time.Minute, false},
}
for _, testCase := range testCases {
testCase := testCase
t.Run("", func(t *testing.T) {
_, err := ExpToInt64(testCase.exp)
if err != nil && !testCase.expectedFailure {
t.Errorf("Expected success but got failure %s", err)
}
if err == nil && testCase.expectedFailure {
t.Error("Expected failure but got success")
}
})
}
}
func TestIsAccessKeyValid(t *testing.T) { func TestIsAccessKeyValid(t *testing.T) {
testCases := []struct { testCases := []struct {

Loading…
Cancel
Save