From 5afb1b6747b713152d046faedfb37b50e95f8c87 Mon Sep 17 00:00:00 2001 From: Harshavardhana Date: Wed, 16 Oct 2019 08:59:59 -0700 Subject: [PATCH] Add support for {jwt:sub} substitutions for policies (#8393) Fixes #8345 --- cmd/auth-handler.go | 8 ++++---- cmd/bucket-handlers.go | 2 +- cmd/jwt.go | 36 +++++++++++++++++++++++---------- cmd/object-handlers.go | 6 +++--- cmd/policy.go | 9 ++++++++- cmd/web-handlers.go | 40 ++++++++++++++++++------------------- pkg/policy/condition/key.go | 23 ++++++++++++++++++++- 7 files changed, 83 insertions(+), 41 deletions(-) diff --git a/cmd/auth-handler.go b/cmd/auth-handler.go index 17cef185c..15a266f21 100644 --- a/cmd/auth-handler.go +++ b/cmd/auth-handler.go @@ -334,7 +334,7 @@ func checkRequestAuthTypeToAccessKey(ctx context.Context, r *http.Request, actio AccountName: cred.AccessKey, Action: action, BucketName: bucketName, - ConditionValues: getConditionValues(r, locationConstraint, ""), + ConditionValues: getConditionValues(r, locationConstraint, "", nil), IsOwner: false, ObjectName: objectName, }) { @@ -348,7 +348,7 @@ func checkRequestAuthTypeToAccessKey(ctx context.Context, r *http.Request, actio AccountName: cred.AccessKey, Action: iampolicy.Action(action), BucketName: bucketName, - ConditionValues: getConditionValues(r, "", cred.AccessKey), + ConditionValues: getConditionValues(r, "", cred.AccessKey, claims), ObjectName: objectName, IsOwner: owner, Claims: claims, @@ -502,7 +502,7 @@ func isPutAllowed(atype authType, bucketName, objectName string, r *http.Request AccountName: cred.AccessKey, Action: policy.PutObjectAction, BucketName: bucketName, - ConditionValues: getConditionValues(r, "", ""), + ConditionValues: getConditionValues(r, "", "", nil), IsOwner: false, ObjectName: objectName, }) { @@ -515,7 +515,7 @@ func isPutAllowed(atype authType, bucketName, objectName string, r *http.Request AccountName: cred.AccessKey, Action: policy.PutObjectAction, BucketName: bucketName, - ConditionValues: getConditionValues(r, "", cred.AccessKey), + ConditionValues: getConditionValues(r, "", cred.AccessKey, claims), ObjectName: objectName, IsOwner: owner, Claims: claims, diff --git a/cmd/bucket-handlers.go b/cmd/bucket-handlers.go index da30a941f..fdc3a7231 100644 --- a/cmd/bucket-handlers.go +++ b/cmd/bucket-handlers.go @@ -296,7 +296,7 @@ func (api objectAPIHandlers) ListBucketsHandler(w http.ResponseWriter, r *http.R AccountName: accessKey, Action: iampolicy.ListBucketAction, BucketName: bucketInfo.Name, - ConditionValues: getConditionValues(r, "", accessKey), + ConditionValues: getConditionValues(r, "", accessKey, claims), IsOwner: owner, ObjectName: "", Claims: claims, diff --git a/cmd/jwt.go b/cmd/jwt.go index 47b7deaef..0c43b5006 100644 --- a/cmd/jwt.go +++ b/cmd/jwt.go @@ -161,44 +161,58 @@ func isAuthTokenValid(token string) bool { return err == nil } -func webTokenAuthenticate(token string) (jwtgo.StandardClaims, bool, error) { +func webTokenAuthenticate(token string) (standardClaims, bool, error) { var claims = jwtgo.StandardClaims{} if token == "" { - return claims, false, errNoAuthToken + return standardClaims{claims}, false, errNoAuthToken } jwtToken, err := parseJWTWithClaims(token, &claims) if err != nil { - return claims, false, err + return standardClaims{claims}, false, err } if !jwtToken.Valid { - return claims, false, errAuthentication + return standardClaims{claims}, false, errAuthentication } owner := claims.Subject == globalServerConfig.GetCredential().AccessKey - return claims, owner, nil + return standardClaims{claims}, owner, nil +} + +// jwt standardClaims +type standardClaims struct { + jwtgo.StandardClaims +} + +func (s standardClaims) Map() map[string]interface{} { + m := make(map[string]interface{}) + m["sub"] = s.Subject + m["iss"] = s.Issuer + m["aud"] = s.Audience + m["jti"] = s.Id + return m } // Check if the request is authenticated. // Returns nil if the request is authenticated. errNoAuthToken if token missing. // Returns errAuthentication for all other errors. -func webRequestAuthenticate(req *http.Request) (jwtgo.StandardClaims, bool, error) { +func webRequestAuthenticate(req *http.Request) (standardClaims, bool, error) { var claims = jwtgo.StandardClaims{} tokStr, err := jwtreq.AuthorizationHeaderExtractor.ExtractToken(req) if err != nil { if err == jwtreq.ErrNoTokenInRequest { - return claims, false, errNoAuthToken + return standardClaims{claims}, false, errNoAuthToken } - return claims, false, err + return standardClaims{claims}, false, err } jwtToken, err := parseJWTWithClaims(tokStr, &claims) if err != nil { - return claims, false, err + return standardClaims{claims}, false, err } if !jwtToken.Valid { - return claims, false, errAuthentication + return standardClaims{claims}, false, errAuthentication } owner := claims.Subject == globalServerConfig.GetCredential().AccessKey - return claims, owner, nil + return standardClaims{claims}, owner, nil } func newAuthToken() string { diff --git a/cmd/object-handlers.go b/cmd/object-handlers.go index 7c05b3e73..8c035b329 100644 --- a/cmd/object-handlers.go +++ b/cmd/object-handlers.go @@ -134,7 +134,7 @@ func (api objectAPIHandlers) SelectObjectContentHandler(w http.ResponseWriter, r if globalPolicySys.IsAllowed(policy.Args{ Action: policy.ListBucketAction, BucketName: bucket, - ConditionValues: getConditionValues(r, "", ""), + ConditionValues: getConditionValues(r, "", "", nil), IsOwner: false, }) { _, err = getObjectInfo(ctx, bucket, object, opts) @@ -293,7 +293,7 @@ func (api objectAPIHandlers) GetObjectHandler(w http.ResponseWriter, r *http.Req if globalPolicySys.IsAllowed(policy.Args{ Action: policy.ListBucketAction, BucketName: bucket, - ConditionValues: getConditionValues(r, "", ""), + ConditionValues: getConditionValues(r, "", "", nil), IsOwner: false, }) { getObjectInfo := objectAPI.GetObjectInfo @@ -466,7 +466,7 @@ func (api objectAPIHandlers) HeadObjectHandler(w http.ResponseWriter, r *http.Re if globalPolicySys.IsAllowed(policy.Args{ Action: policy.ListBucketAction, BucketName: bucket, - ConditionValues: getConditionValues(r, "", ""), + ConditionValues: getConditionValues(r, "", "", nil), IsOwner: false, }) { _, err = getObjectInfo(ctx, bucket, object, opts) diff --git a/cmd/policy.go b/cmd/policy.go index be30955b8..7e1e5ba7c 100644 --- a/cmd/policy.go +++ b/cmd/policy.go @@ -167,7 +167,7 @@ func NewPolicySys() *PolicySys { } } -func getConditionValues(request *http.Request, locationConstraint string, username string) map[string][]string { +func getConditionValues(request *http.Request, locationConstraint string, username string, claims map[string]interface{}) map[string][]string { currTime := UTCNow() principalType := func() string { if username != "" { @@ -207,6 +207,13 @@ func getConditionValues(request *http.Request, locationConstraint string, userna args["LocationConstraint"] = []string{locationConstraint} } + // JWT specific values + for k, v := range claims { + vStr, ok := v.(string) + if ok { + args[k] = []string{vStr} + } + } return args } diff --git a/cmd/web-handlers.go b/cmd/web-handlers.go index 023e26a17..b4fca4c74 100644 --- a/cmd/web-handlers.go +++ b/cmd/web-handlers.go @@ -160,7 +160,7 @@ func (web *webAPIHandlers) MakeBucket(r *http.Request, args *MakeBucketArgs, rep AccountName: claims.Subject, Action: iampolicy.CreateBucketAction, BucketName: args.BucketName, - ConditionValues: getConditionValues(r, "", claims.Subject), + ConditionValues: getConditionValues(r, "", claims.Subject, claims.Map()), IsOwner: owner, }) { return toJSONError(ctx, errAccessDenied) @@ -221,7 +221,7 @@ func (web *webAPIHandlers) DeleteBucket(r *http.Request, args *RemoveBucketArgs, AccountName: claims.Subject, Action: iampolicy.DeleteBucketAction, BucketName: args.BucketName, - ConditionValues: getConditionValues(r, "", claims.Subject), + ConditionValues: getConditionValues(r, "", claims.Subject, claims.Map()), IsOwner: owner, }) { return toJSONError(ctx, errAccessDenied) @@ -325,7 +325,7 @@ func (web *webAPIHandlers) ListBuckets(r *http.Request, args *WebGenericArgs, re AccountName: claims.Subject, Action: iampolicy.ListBucketAction, BucketName: dnsRecord.Key, - ConditionValues: getConditionValues(r, "", claims.Subject), + ConditionValues: getConditionValues(r, "", claims.Subject, claims.Map()), IsOwner: owner, ObjectName: "", }) { @@ -347,7 +347,7 @@ func (web *webAPIHandlers) ListBuckets(r *http.Request, args *WebGenericArgs, re AccountName: claims.Subject, Action: iampolicy.ListBucketAction, BucketName: bucket.Name, - ConditionValues: getConditionValues(r, "", claims.Subject), + ConditionValues: getConditionValues(r, "", claims.Subject, claims.Map()), IsOwner: owner, ObjectName: "", }) { @@ -459,7 +459,7 @@ func (web *webAPIHandlers) ListObjects(r *http.Request, args *ListObjectsArgs, r readable := globalPolicySys.IsAllowed(policy.Args{ Action: policy.ListBucketAction, BucketName: args.BucketName, - ConditionValues: getConditionValues(r, "", ""), + ConditionValues: getConditionValues(r, "", "", nil), IsOwner: false, }) @@ -467,7 +467,7 @@ func (web *webAPIHandlers) ListObjects(r *http.Request, args *ListObjectsArgs, r writable := globalPolicySys.IsAllowed(policy.Args{ Action: policy.PutObjectAction, BucketName: args.BucketName, - ConditionValues: getConditionValues(r, "", ""), + ConditionValues: getConditionValues(r, "", "", nil), IsOwner: false, ObjectName: args.Prefix + SlashSeparator, }) @@ -498,7 +498,7 @@ func (web *webAPIHandlers) ListObjects(r *http.Request, args *ListObjectsArgs, r AccountName: claims.Subject, Action: iampolicy.ListBucketAction, BucketName: args.BucketName, - ConditionValues: getConditionValues(r, "", claims.Subject), + ConditionValues: getConditionValues(r, "", claims.Subject, claims.Map()), IsOwner: owner, }) @@ -506,7 +506,7 @@ func (web *webAPIHandlers) ListObjects(r *http.Request, args *ListObjectsArgs, r AccountName: claims.Subject, Action: iampolicy.PutObjectAction, BucketName: args.BucketName, - ConditionValues: getConditionValues(r, "", claims.Subject), + ConditionValues: getConditionValues(r, "", claims.Subject, claims.Map()), IsOwner: owner, ObjectName: args.Prefix + SlashSeparator, }) @@ -598,7 +598,7 @@ func (web *webAPIHandlers) RemoveObject(r *http.Request, args *RemoveObjectArgs, if !globalPolicySys.IsAllowed(policy.Args{ Action: policy.DeleteObjectAction, BucketName: args.BucketName, - ConditionValues: getConditionValues(r, "", ""), + ConditionValues: getConditionValues(r, "", "", nil), IsOwner: false, ObjectName: object, }) { @@ -672,7 +672,7 @@ next: AccountName: claims.Subject, Action: iampolicy.DeleteObjectAction, BucketName: args.BucketName, - ConditionValues: getConditionValues(r, "", claims.Subject), + ConditionValues: getConditionValues(r, "", claims.Subject, claims.Map()), IsOwner: owner, ObjectName: objectName, }) { @@ -690,7 +690,7 @@ next: AccountName: claims.Subject, Action: iampolicy.DeleteObjectAction, BucketName: args.BucketName, - ConditionValues: getConditionValues(r, "", claims.Subject), + ConditionValues: getConditionValues(r, "", claims.Subject, claims.Map()), IsOwner: owner, ObjectName: objectName, }) { @@ -930,7 +930,7 @@ func (web *webAPIHandlers) Upload(w http.ResponseWriter, r *http.Request) { if !globalPolicySys.IsAllowed(policy.Args{ Action: policy.PutObjectAction, BucketName: bucket, - ConditionValues: getConditionValues(r, "", ""), + ConditionValues: getConditionValues(r, "", "", nil), IsOwner: false, ObjectName: object, }) { @@ -949,7 +949,7 @@ func (web *webAPIHandlers) Upload(w http.ResponseWriter, r *http.Request) { AccountName: claims.Subject, Action: iampolicy.PutObjectAction, BucketName: bucket, - ConditionValues: getConditionValues(r, "", claims.Subject), + ConditionValues: getConditionValues(r, "", claims.Subject, claims.Map()), IsOwner: owner, ObjectName: object, }) { @@ -1110,7 +1110,7 @@ func (web *webAPIHandlers) Download(w http.ResponseWriter, r *http.Request) { if !globalPolicySys.IsAllowed(policy.Args{ Action: policy.GetObjectAction, BucketName: bucket, - ConditionValues: getConditionValues(r, "", ""), + ConditionValues: getConditionValues(r, "", "", nil), IsOwner: false, ObjectName: object, }) { @@ -1129,7 +1129,7 @@ func (web *webAPIHandlers) Download(w http.ResponseWriter, r *http.Request) { AccountName: claims.Subject, Action: iampolicy.GetObjectAction, BucketName: bucket, - ConditionValues: getConditionValues(r, "", claims.Subject), + ConditionValues: getConditionValues(r, "", claims.Subject, claims.Map()), IsOwner: owner, ObjectName: object, }) { @@ -1259,7 +1259,7 @@ func (web *webAPIHandlers) DownloadZip(w http.ResponseWriter, r *http.Request) { if !globalPolicySys.IsAllowed(policy.Args{ Action: policy.GetObjectAction, BucketName: args.BucketName, - ConditionValues: getConditionValues(r, "", ""), + ConditionValues: getConditionValues(r, "", "", nil), IsOwner: false, ObjectName: pathJoin(args.Prefix, object), }) { @@ -1280,7 +1280,7 @@ func (web *webAPIHandlers) DownloadZip(w http.ResponseWriter, r *http.Request) { AccountName: claims.Subject, Action: iampolicy.GetObjectAction, BucketName: args.BucketName, - ConditionValues: getConditionValues(r, "", claims.Subject), + ConditionValues: getConditionValues(r, "", claims.Subject, claims.Map()), IsOwner: owner, ObjectName: pathJoin(args.Prefix, object), }) { @@ -1426,7 +1426,7 @@ func (web *webAPIHandlers) GetBucketPolicy(r *http.Request, args *GetBucketPolic AccountName: claims.Subject, Action: iampolicy.GetBucketPolicyAction, BucketName: args.BucketName, - ConditionValues: getConditionValues(r, "", claims.Subject), + ConditionValues: getConditionValues(r, "", claims.Subject, claims.Map()), IsOwner: owner, }) { return toJSONError(ctx, errAccessDenied) @@ -1523,7 +1523,7 @@ func (web *webAPIHandlers) ListAllBucketPolicies(r *http.Request, args *ListAllB AccountName: claims.Subject, Action: iampolicy.GetBucketPolicyAction, BucketName: args.BucketName, - ConditionValues: getConditionValues(r, "", claims.Subject), + ConditionValues: getConditionValues(r, "", claims.Subject, claims.Map()), IsOwner: owner, }) { return toJSONError(ctx, errAccessDenied) @@ -1613,7 +1613,7 @@ func (web *webAPIHandlers) SetBucketPolicy(r *http.Request, args *SetBucketPolic AccountName: claims.Subject, Action: iampolicy.PutBucketPolicyAction, BucketName: args.BucketName, - ConditionValues: getConditionValues(r, "", claims.Subject), + ConditionValues: getConditionValues(r, "", claims.Subject, claims.Map()), IsOwner: owner, }) { return toJSONError(ctx, errAccessDenied) diff --git a/pkg/policy/condition/key.go b/pkg/policy/condition/key.go index ca82156f1..aeda42cb0 100644 --- a/pkg/policy/condition/key.go +++ b/pkg/policy/condition/key.go @@ -85,6 +85,18 @@ const ( // AWSUsername - user friendly name, in MinIO this value is same as your user Access Key. AWSUsername Key = "aws:username" + + // JWTSub - JWT subject claim substitution. + JWTSub Key = "jwt:sub" + + // JWTIss issuer claim substitution. + JWTIss Key = "jwt:iss" + + // JWTAud audience claim substitution. + JWTAud Key = "jwt:aud" + + // JWTJti JWT unique identifier claim substitution. + JWTJti Key = "jwt:jti" ) // AllSupportedKeys - is list of all all supported keys. @@ -107,6 +119,10 @@ var AllSupportedKeys = []Key{ AWSPrincipalType, AWSUserID, AWSUsername, + JWTSub, + JWTIss, + JWTAud, + JWTJti, // Add new supported condition keys. } @@ -121,6 +137,10 @@ var CommonKeys = []Key{ AWSPrincipalType, AWSUserID, AWSUsername, + JWTSub, + JWTIss, + JWTAud, + JWTJti, } func substFuncFromValues(values map[string][]string) func(string) string { @@ -166,8 +186,9 @@ func (key Key) Name() string { if strings.HasPrefix(keyString, "aws:") { return strings.TrimPrefix(keyString, "aws:") + } else if strings.HasPrefix(keyString, "jwt:") { + return strings.TrimPrefix(keyString, "jwt:") } - return strings.TrimPrefix(keyString, "s3:") }