From b21835f195afd642461141359f135f981399cd85 Mon Sep 17 00:00:00 2001 From: Harshavardhana Date: Fri, 29 Nov 2019 05:27:54 -0800 Subject: [PATCH] Honor DurationSeconds properly for WebIdentity (#8581) Also cleanup code to add various constants for verbatim strings across the code base. Fixes #8482 --- cmd/config/identity/openid/jwt.go | 72 ++++++++++---------- cmd/config/identity/openid/jwt_test.go | 38 ++++++++++- cmd/config/identity/openid/validators.go | 3 +- cmd/sts-handlers.go | 84 ++++++++++++++---------- docs/sts/assume-role.md | 4 +- docs/sts/client-grants.md | 4 +- docs/sts/ldap.md | 4 +- docs/sts/web-identity.md | 4 +- pkg/auth/credentials.go | 38 +++++++---- pkg/auth/credentials_test.go | 38 ++++++++++- 10 files changed, 194 insertions(+), 95 deletions(-) diff --git a/cmd/config/identity/openid/jwt.go b/cmd/config/identity/openid/jwt.go index c11fcdfe2..d478ab1b5 100644 --- a/cmd/config/identity/openid/jwt.go +++ b/cmd/config/identity/openid/jwt.go @@ -28,6 +28,7 @@ import ( jwtgo "github.com/dgrijalva/jwt-go" "github.com/minio/minio/cmd/config" + "github.com/minio/minio/pkg/auth" "github.com/minio/minio/pkg/env" xnet "github.com/minio/minio/pkg/net" ) @@ -107,36 +108,19 @@ type JWT struct { 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. func GetDefaultExpiration(dsecs string) (time.Duration, error) { defaultExpiryDuration := time.Duration(60) * time.Minute // Defaults to 1hr. if dsecs != "" { expirySecs, err := strconv.ParseInt(dsecs, 10, 64) if err != nil { - return 0, ErrInvalidDuration + return 0, auth.ErrInvalidDuration } // The duration, in seconds, of the role session. // The value can range from 900 seconds (15 minutes) // to 12 hours. if expirySecs < 900 || expirySecs > 43200 { - return 0, ErrInvalidDuration + return 0, auth.ErrInvalidDuration } defaultExpiryDuration = time.Duration(expirySecs) * time.Second @@ -144,6 +128,39 @@ func GetDefaultExpiration(dsecs string) (time.Duration, error) { 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. func (p *JWT) Validate(token, dsecs string) (map[string]interface{}, error) { jp := new(jwtgo.Parser) @@ -173,25 +190,10 @@ func (p *JWT) Validate(token, dsecs string) (map[string]interface{}, error) { return nil, ErrTokenExpired } - expAt, err := expToInt64(claims["exp"]) - if err != nil { + if err = updateClaimsExpiry(dsecs, claims); err != nil { 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 } diff --git a/cmd/config/identity/openid/jwt_test.go b/cmd/config/identity/openid/jwt_test.go index 67a825363..1a08fe042 100644 --- a/cmd/config/identity/openid/jwt_test.go +++ b/cmd/config/identity/openid/jwt_test.go @@ -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"); * you may not use this file except in compliance with the License. @@ -26,6 +26,42 @@ import ( 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) { const jsonkey = `{"keys": [ diff --git a/cmd/config/identity/openid/validators.go b/cmd/config/identity/openid/validators.go index 57a9911d0..2a0682c95 100644 --- a/cmd/config/identity/openid/validators.go +++ b/cmd/config/identity/openid/validators.go @@ -38,8 +38,7 @@ type Validator interface { // ErrTokenExpired - error token expired var ( - ErrTokenExpired = errors.New("token expired") - ErrInvalidDuration = errors.New("duration higher than token expiry") + ErrTokenExpired = errors.New("token expired") ) // Validators - holds list of providers indexed by provider id. diff --git a/cmd/sts-handlers.go b/cmd/sts-handlers.go index 35879b414..1134efd96 100644 --- a/cmd/sts-handlers.go +++ b/cmd/sts-handlers.go @@ -36,7 +36,15 @@ import ( const ( // 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 clientGrants = "AssumeRoleWithClientGrants" @@ -46,6 +54,10 @@ const ( stsRequestBodyLimit = 10 * (1 << 20) // 10 MiB + // JWT claim keys + expClaim = "exp" + subClaim = "sub" + // LDAP claim keys ldapUser = "ldapUser" ldapGroups = "ldapGroups" @@ -71,30 +83,30 @@ func registerSTSRouter(router *mux.Router) { }).HandlerFunc(httpTraceAll(sts.AssumeRole)) // 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)) noQueries := len(r.URL.Query()) == 0 return ctypeOk && noQueries }).HandlerFunc(httpTraceAll(sts.AssumeRoleWithJWT)) // AssumeRoleWithClientGrants - stsRouter.Methods("POST").HandlerFunc(httpTraceAll(sts.AssumeRoleWithClientGrants)). - Queries("Action", clientGrants). - Queries("Version", stsAPIVersion). - Queries("Token", "{Token:.*}") + stsRouter.Methods(http.MethodPost).HandlerFunc(httpTraceAll(sts.AssumeRoleWithClientGrants)). + Queries(stsAction, clientGrants). + Queries(stsVersion, stsAPIVersion). + Queries(stsToken, "{Token:.*}") // AssumeRoleWithWebIdentity - stsRouter.Methods("POST").HandlerFunc(httpTraceAll(sts.AssumeRoleWithWebIdentity)). - Queries("Action", webIdentity). - Queries("Version", stsAPIVersion). - Queries("WebIdentityToken", "{Token:.*}") + stsRouter.Methods(http.MethodPost).HandlerFunc(httpTraceAll(sts.AssumeRoleWithWebIdentity)). + Queries(stsAction, webIdentity). + Queries(stsVersion, stsAPIVersion). + Queries(stsWebIdentityToken, "{Token:.*}") // AssumeRoleWithLDAPIdentity - stsRouter.Methods("POST").HandlerFunc(httpTraceAll(sts.AssumeRoleWithLDAPIdentity)). - Queries("Action", ldapIdentity). - Queries("Version", stsAPIVersion). - Queries("LDAPUsername", "{LDAPUsername:.*}"). - Queries("LDAPPassword", "{LDAPPassword:.*}") + stsRouter.Methods(http.MethodPost).HandlerFunc(httpTraceAll(sts.AssumeRoleWithLDAPIdentity)). + Queries(stsAction, ldapIdentity). + Queries(stsVersion, stsAPIVersion). + Queries(stsLDAPUsername, "{LDAPUsername:.*}"). + Queries(stsLDAPPassword, "{LDAPPassword:.*}") } 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 } - if r.Form.Get("Version") != stsAPIVersion { - writeSTSErrorResponse(ctx, w, ErrSTSMissingParameter, fmt.Errorf("Invalid STS API version %s, expecting %s", 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(stsVersion), stsAPIVersion)) return } - action := r.Form.Get("Action") + action := r.Form.Get(stsAction) switch action { case assumeRole: default: @@ -157,7 +169,7 @@ func (sts *stsAPIHandlers) AssumeRole(w http.ResponseWriter, r *http.Request) { ctx = newContext(r, w, action) 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 // The plain text that you use for both inline and managed session // policies shouldn't exceed 2048 characters. @@ -182,7 +194,7 @@ func (sts *stsAPIHandlers) AssumeRole(w http.ResponseWriter, r *http.Request) { var err error 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 { writeSTSErrorResponse(ctx, w, ErrSTSInvalidParameterValue, err) return @@ -248,12 +260,12 @@ func (sts *stsAPIHandlers) AssumeRoleWithJWT(w http.ResponseWriter, r *http.Requ 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)) return } - action := r.Form.Get("Action") + action := r.Form.Get(stsAction) switch action { case clientGrants, webIdentity: default: @@ -275,12 +287,12 @@ func (sts *stsAPIHandlers) AssumeRoleWithJWT(w http.ResponseWriter, r *http.Requ return } - token := r.Form.Get("Token") + token := r.Form.Get(stsToken) 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 { switch err { case openid.ErrTokenExpired: @@ -291,7 +303,7 @@ func (sts *stsAPIHandlers) AssumeRoleWithJWT(w http.ResponseWriter, r *http.Requ writeSTSErrorResponse(ctx, w, ErrSTSWebIdentityExpiredToken, err) } return - case openid.ErrInvalidDuration: + case auth.ErrInvalidDuration: writeSTSErrorResponse(ctx, w, ErrSTSInvalidParameterValue, err) return } @@ -299,7 +311,7 @@ func (sts *stsAPIHandlers) AssumeRoleWithJWT(w http.ResponseWriter, r *http.Requ return } - sessionPolicyStr := r.Form.Get("Policy") + sessionPolicyStr := r.Form.Get(stsPolicy) // https://docs.aws.amazon.com/STS/latest/APIReference/API_AssumeRoleWithWebIdentity.html // The plain text that you use for both inline and managed session // policies shouldn't exceed 2048 characters. @@ -343,7 +355,7 @@ func (sts *stsAPIHandlers) AssumeRoleWithJWT(w http.ResponseWriter, r *http.Requ } var subFromToken string - if v, ok := m["sub"]; ok { + if v, ok := m[subClaim]; ok { subFromToken, _ = v.(string) } @@ -415,12 +427,12 @@ func (sts *stsAPIHandlers) AssumeRoleWithLDAPIdentity(w http.ResponseWriter, r * 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)) return } - action := r.Form.Get("Action") + action := r.Form.Get(stsAction) switch action { case ldapIdentity: default: @@ -431,15 +443,15 @@ func (sts *stsAPIHandlers) AssumeRoleWithLDAPIdentity(w http.ResponseWriter, r * ctx = newContext(r, w, action) defer logger.AuditLog(w, r, action, nil) - ldapUsername := r.Form.Get("LDAPUsername") - ldapPassword := r.Form.Get("LDAPPassword") + ldapUsername := r.Form.Get(stsLDAPUsername) + ldapPassword := r.Form.Get(stsLDAPPassword) if ldapUsername == "" || ldapPassword == "" { writeSTSErrorResponse(ctx, w, ErrSTSMissingParameter, fmt.Errorf("LDAPUsername and LDAPPassword cannot be empty")) return } - sessionPolicyStr := r.Form.Get("Policy") + sessionPolicyStr := r.Form.Get(stsPolicy) // https://docs.aws.amazon.com/STS/latest/APIReference/API_AssumeRole.html // The plain text that you use for both inline and managed session // policies shouldn't exceed 2048 characters. @@ -516,9 +528,9 @@ func (sts *stsAPIHandlers) AssumeRoleWithLDAPIdentity(w http.ResponseWriter, r * } expiryDur := globalLDAPConfig.GetExpiryDuration() m := map[string]interface{}{ - "exp": UTCNow().Add(expiryDur).Unix(), - "ldapUser": ldapUsername, - "ldapGroups": groups, + expClaim: UTCNow().Add(expiryDur).Unix(), + ldapUser: ldapUsername, + ldapGroups: groups, } if len(sessionPolicyStr) > 0 { diff --git a/docs/sts/assume-role.md b/docs/sts/assume-role.md index 0a3f0f51a..0e24aa0d9 100644 --- a/docs/sts/assume-role.md +++ b/docs/sts/assume-role.md @@ -10,7 +10,7 @@ - [Policy](#policy) - [Response Elements](#response-elements) - [Errors](#errors) -- [Sample Request](#sample-request) +- [Sample `POST` Request](#sample-post-request) - [Sample Response](#sample-response) - [Testing](#testing) @@ -60,7 +60,7 @@ XML response for this API is similar to [AWS STS AssumeRole](https://docs.aws.am ### 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 ``` diff --git a/docs/sts/client-grants.md b/docs/sts/client-grants.md index 3cfa4b6ae..cddb66cd1 100644 --- a/docs/sts/client-grants.md +++ b/docs/sts/client-grants.md @@ -10,7 +10,7 @@ - [Policy](#policy) - [Response Elements](#response-elements) - [Errors](#errors) -- [Sample Request](#sample-request) +- [Sample `POST` Request](#sample-post-request) - [Sample Response](#sample-response) - [Testing](#testing) @@ -64,7 +64,7 @@ XML response for this API is similar to [AWS STS AssumeRoleWithWebIdentity](http ### 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 ``` diff --git a/docs/sts/ldap.md b/docs/sts/ldap.md index f28a3aa0e..94ace6037 100644 --- a/docs/sts/ldap.md +++ b/docs/sts/ldap.md @@ -14,7 +14,7 @@ - [Policy](#policy) - [Response Elements](#response-elements) - [Errors](#errors) -- [Sample Request](#sample-request) +- [Sample `POST` Request](#sample-post-request) - [Sample Response](#sample-response) - [Testing](#testing) @@ -189,7 +189,7 @@ XML response for this API is similar to [AWS STS AssumeRoleWithWebIdentity](http ### 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 ``` diff --git a/docs/sts/web-identity.md b/docs/sts/web-identity.md index 9e451e8e1..e06f04f99 100644 --- a/docs/sts/web-identity.md +++ b/docs/sts/web-identity.md @@ -10,7 +10,7 @@ - [Policy](#policy) - [Response Elements](#response-elements) - [Errors](#errors) -- [Sample Request](#sample-request) +- [Sample `POST` Request](#sample-post-request) - [Sample Response](#sample-response) - [Testing](#testing) - [Authorization Flow](#authorization-flow) @@ -64,7 +64,7 @@ XML response for this API is similar to [AWS STS AssumeRoleWithWebIdentity](http ### 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 ``` diff --git a/pkg/auth/credentials.go b/pkg/auth/credentials.go index 231475232..06b2c8754 100644 --- a/pkg/auth/credentials.go +++ b/pkg/auth/credentials.go @@ -23,6 +23,7 @@ import ( "encoding/json" "errors" "fmt" + "strconv" "strings" "time" @@ -136,25 +137,37 @@ func (cred Credentials) Equal(ccred Credentials) bool { 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) { + case string: + expAt, err = strconv.ParseInt(exp, 10, 64) case float64: - expAt = int64(exp) + expAt, err = int64(exp), nil 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: expAt, err = exp.Int64() - if err != nil { - return 0, err - } case time.Duration: - return time.Now().UTC().Add(exp).Unix(), nil + expAt, err = time.Now().UTC().Add(exp).Unix(), nil case nil: - return 0, nil + expAt, err = 0, nil 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. @@ -185,10 +198,11 @@ func GetNewCredentialsWithMetadata(m map[string]interface{}, tokenSecret string) if err != nil { 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" - expiry, err := expToInt64(m["exp"]) + expiry, err := ExpToInt64(m["exp"]) if err != nil { return cred, err } diff --git a/pkg/auth/credentials_test.go b/pkg/auth/credentials_test.go index fd2c0f91b..4577bef96 100644 --- a/pkg/auth/credentials_test.go +++ b/pkg/auth/credentials_test.go @@ -16,7 +16,43 @@ 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) { testCases := []struct {