Add support for {jwt:sub} substitutions for policies (#8393)

Fixes #8345
master
Harshavardhana 5 years ago committed by GitHub
parent f2cc97a44c
commit 5afb1b6747
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
  1. 8
      cmd/auth-handler.go
  2. 2
      cmd/bucket-handlers.go
  3. 36
      cmd/jwt.go
  4. 6
      cmd/object-handlers.go
  5. 9
      cmd/policy.go
  6. 40
      cmd/web-handlers.go
  7. 23
      pkg/policy/condition/key.go

@ -334,7 +334,7 @@ func checkRequestAuthTypeToAccessKey(ctx context.Context, r *http.Request, actio
AccountName: cred.AccessKey, AccountName: cred.AccessKey,
Action: action, Action: action,
BucketName: bucketName, BucketName: bucketName,
ConditionValues: getConditionValues(r, locationConstraint, ""), ConditionValues: getConditionValues(r, locationConstraint, "", nil),
IsOwner: false, IsOwner: false,
ObjectName: objectName, ObjectName: objectName,
}) { }) {
@ -348,7 +348,7 @@ func checkRequestAuthTypeToAccessKey(ctx context.Context, r *http.Request, actio
AccountName: cred.AccessKey, AccountName: cred.AccessKey,
Action: iampolicy.Action(action), Action: iampolicy.Action(action),
BucketName: bucketName, BucketName: bucketName,
ConditionValues: getConditionValues(r, "", cred.AccessKey), ConditionValues: getConditionValues(r, "", cred.AccessKey, claims),
ObjectName: objectName, ObjectName: objectName,
IsOwner: owner, IsOwner: owner,
Claims: claims, Claims: claims,
@ -502,7 +502,7 @@ func isPutAllowed(atype authType, bucketName, objectName string, r *http.Request
AccountName: cred.AccessKey, AccountName: cred.AccessKey,
Action: policy.PutObjectAction, Action: policy.PutObjectAction,
BucketName: bucketName, BucketName: bucketName,
ConditionValues: getConditionValues(r, "", ""), ConditionValues: getConditionValues(r, "", "", nil),
IsOwner: false, IsOwner: false,
ObjectName: objectName, ObjectName: objectName,
}) { }) {
@ -515,7 +515,7 @@ func isPutAllowed(atype authType, bucketName, objectName string, r *http.Request
AccountName: cred.AccessKey, AccountName: cred.AccessKey,
Action: policy.PutObjectAction, Action: policy.PutObjectAction,
BucketName: bucketName, BucketName: bucketName,
ConditionValues: getConditionValues(r, "", cred.AccessKey), ConditionValues: getConditionValues(r, "", cred.AccessKey, claims),
ObjectName: objectName, ObjectName: objectName,
IsOwner: owner, IsOwner: owner,
Claims: claims, Claims: claims,

@ -296,7 +296,7 @@ func (api objectAPIHandlers) ListBucketsHandler(w http.ResponseWriter, r *http.R
AccountName: accessKey, AccountName: accessKey,
Action: iampolicy.ListBucketAction, Action: iampolicy.ListBucketAction,
BucketName: bucketInfo.Name, BucketName: bucketInfo.Name,
ConditionValues: getConditionValues(r, "", accessKey), ConditionValues: getConditionValues(r, "", accessKey, claims),
IsOwner: owner, IsOwner: owner,
ObjectName: "", ObjectName: "",
Claims: claims, Claims: claims,

@ -161,44 +161,58 @@ func isAuthTokenValid(token string) bool {
return err == nil return err == nil
} }
func webTokenAuthenticate(token string) (jwtgo.StandardClaims, bool, error) { func webTokenAuthenticate(token string) (standardClaims, bool, error) {
var claims = jwtgo.StandardClaims{} var claims = jwtgo.StandardClaims{}
if token == "" { if token == "" {
return claims, false, errNoAuthToken return standardClaims{claims}, false, errNoAuthToken
} }
jwtToken, err := parseJWTWithClaims(token, &claims) jwtToken, err := parseJWTWithClaims(token, &claims)
if err != nil { if err != nil {
return claims, false, err return standardClaims{claims}, false, err
} }
if !jwtToken.Valid { if !jwtToken.Valid {
return claims, false, errAuthentication return standardClaims{claims}, false, errAuthentication
} }
owner := claims.Subject == globalServerConfig.GetCredential().AccessKey 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. // Check if the request is authenticated.
// Returns nil if the request is authenticated. errNoAuthToken if token missing. // Returns nil if the request is authenticated. errNoAuthToken if token missing.
// Returns errAuthentication for all other errors. // 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{} var claims = jwtgo.StandardClaims{}
tokStr, err := jwtreq.AuthorizationHeaderExtractor.ExtractToken(req) tokStr, err := jwtreq.AuthorizationHeaderExtractor.ExtractToken(req)
if err != nil { if err != nil {
if err == jwtreq.ErrNoTokenInRequest { 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) jwtToken, err := parseJWTWithClaims(tokStr, &claims)
if err != nil { if err != nil {
return claims, false, err return standardClaims{claims}, false, err
} }
if !jwtToken.Valid { if !jwtToken.Valid {
return claims, false, errAuthentication return standardClaims{claims}, false, errAuthentication
} }
owner := claims.Subject == globalServerConfig.GetCredential().AccessKey owner := claims.Subject == globalServerConfig.GetCredential().AccessKey
return claims, owner, nil return standardClaims{claims}, owner, nil
} }
func newAuthToken() string { func newAuthToken() string {

@ -134,7 +134,7 @@ func (api objectAPIHandlers) SelectObjectContentHandler(w http.ResponseWriter, r
if globalPolicySys.IsAllowed(policy.Args{ if globalPolicySys.IsAllowed(policy.Args{
Action: policy.ListBucketAction, Action: policy.ListBucketAction,
BucketName: bucket, BucketName: bucket,
ConditionValues: getConditionValues(r, "", ""), ConditionValues: getConditionValues(r, "", "", nil),
IsOwner: false, IsOwner: false,
}) { }) {
_, err = getObjectInfo(ctx, bucket, object, opts) _, 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{ if globalPolicySys.IsAllowed(policy.Args{
Action: policy.ListBucketAction, Action: policy.ListBucketAction,
BucketName: bucket, BucketName: bucket,
ConditionValues: getConditionValues(r, "", ""), ConditionValues: getConditionValues(r, "", "", nil),
IsOwner: false, IsOwner: false,
}) { }) {
getObjectInfo := objectAPI.GetObjectInfo getObjectInfo := objectAPI.GetObjectInfo
@ -466,7 +466,7 @@ func (api objectAPIHandlers) HeadObjectHandler(w http.ResponseWriter, r *http.Re
if globalPolicySys.IsAllowed(policy.Args{ if globalPolicySys.IsAllowed(policy.Args{
Action: policy.ListBucketAction, Action: policy.ListBucketAction,
BucketName: bucket, BucketName: bucket,
ConditionValues: getConditionValues(r, "", ""), ConditionValues: getConditionValues(r, "", "", nil),
IsOwner: false, IsOwner: false,
}) { }) {
_, err = getObjectInfo(ctx, bucket, object, opts) _, err = getObjectInfo(ctx, bucket, object, opts)

@ -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() currTime := UTCNow()
principalType := func() string { principalType := func() string {
if username != "" { if username != "" {
@ -207,6 +207,13 @@ func getConditionValues(request *http.Request, locationConstraint string, userna
args["LocationConstraint"] = []string{locationConstraint} args["LocationConstraint"] = []string{locationConstraint}
} }
// JWT specific values
for k, v := range claims {
vStr, ok := v.(string)
if ok {
args[k] = []string{vStr}
}
}
return args return args
} }

@ -160,7 +160,7 @@ func (web *webAPIHandlers) MakeBucket(r *http.Request, args *MakeBucketArgs, rep
AccountName: claims.Subject, AccountName: claims.Subject,
Action: iampolicy.CreateBucketAction, Action: iampolicy.CreateBucketAction,
BucketName: args.BucketName, BucketName: args.BucketName,
ConditionValues: getConditionValues(r, "", claims.Subject), ConditionValues: getConditionValues(r, "", claims.Subject, claims.Map()),
IsOwner: owner, IsOwner: owner,
}) { }) {
return toJSONError(ctx, errAccessDenied) return toJSONError(ctx, errAccessDenied)
@ -221,7 +221,7 @@ func (web *webAPIHandlers) DeleteBucket(r *http.Request, args *RemoveBucketArgs,
AccountName: claims.Subject, AccountName: claims.Subject,
Action: iampolicy.DeleteBucketAction, Action: iampolicy.DeleteBucketAction,
BucketName: args.BucketName, BucketName: args.BucketName,
ConditionValues: getConditionValues(r, "", claims.Subject), ConditionValues: getConditionValues(r, "", claims.Subject, claims.Map()),
IsOwner: owner, IsOwner: owner,
}) { }) {
return toJSONError(ctx, errAccessDenied) return toJSONError(ctx, errAccessDenied)
@ -325,7 +325,7 @@ func (web *webAPIHandlers) ListBuckets(r *http.Request, args *WebGenericArgs, re
AccountName: claims.Subject, AccountName: claims.Subject,
Action: iampolicy.ListBucketAction, Action: iampolicy.ListBucketAction,
BucketName: dnsRecord.Key, BucketName: dnsRecord.Key,
ConditionValues: getConditionValues(r, "", claims.Subject), ConditionValues: getConditionValues(r, "", claims.Subject, claims.Map()),
IsOwner: owner, IsOwner: owner,
ObjectName: "", ObjectName: "",
}) { }) {
@ -347,7 +347,7 @@ func (web *webAPIHandlers) ListBuckets(r *http.Request, args *WebGenericArgs, re
AccountName: claims.Subject, AccountName: claims.Subject,
Action: iampolicy.ListBucketAction, Action: iampolicy.ListBucketAction,
BucketName: bucket.Name, BucketName: bucket.Name,
ConditionValues: getConditionValues(r, "", claims.Subject), ConditionValues: getConditionValues(r, "", claims.Subject, claims.Map()),
IsOwner: owner, IsOwner: owner,
ObjectName: "", ObjectName: "",
}) { }) {
@ -459,7 +459,7 @@ func (web *webAPIHandlers) ListObjects(r *http.Request, args *ListObjectsArgs, r
readable := globalPolicySys.IsAllowed(policy.Args{ readable := globalPolicySys.IsAllowed(policy.Args{
Action: policy.ListBucketAction, Action: policy.ListBucketAction,
BucketName: args.BucketName, BucketName: args.BucketName,
ConditionValues: getConditionValues(r, "", ""), ConditionValues: getConditionValues(r, "", "", nil),
IsOwner: false, IsOwner: false,
}) })
@ -467,7 +467,7 @@ func (web *webAPIHandlers) ListObjects(r *http.Request, args *ListObjectsArgs, r
writable := globalPolicySys.IsAllowed(policy.Args{ writable := globalPolicySys.IsAllowed(policy.Args{
Action: policy.PutObjectAction, Action: policy.PutObjectAction,
BucketName: args.BucketName, BucketName: args.BucketName,
ConditionValues: getConditionValues(r, "", ""), ConditionValues: getConditionValues(r, "", "", nil),
IsOwner: false, IsOwner: false,
ObjectName: args.Prefix + SlashSeparator, ObjectName: args.Prefix + SlashSeparator,
}) })
@ -498,7 +498,7 @@ func (web *webAPIHandlers) ListObjects(r *http.Request, args *ListObjectsArgs, r
AccountName: claims.Subject, AccountName: claims.Subject,
Action: iampolicy.ListBucketAction, Action: iampolicy.ListBucketAction,
BucketName: args.BucketName, BucketName: args.BucketName,
ConditionValues: getConditionValues(r, "", claims.Subject), ConditionValues: getConditionValues(r, "", claims.Subject, claims.Map()),
IsOwner: owner, IsOwner: owner,
}) })
@ -506,7 +506,7 @@ func (web *webAPIHandlers) ListObjects(r *http.Request, args *ListObjectsArgs, r
AccountName: claims.Subject, AccountName: claims.Subject,
Action: iampolicy.PutObjectAction, Action: iampolicy.PutObjectAction,
BucketName: args.BucketName, BucketName: args.BucketName,
ConditionValues: getConditionValues(r, "", claims.Subject), ConditionValues: getConditionValues(r, "", claims.Subject, claims.Map()),
IsOwner: owner, IsOwner: owner,
ObjectName: args.Prefix + SlashSeparator, ObjectName: args.Prefix + SlashSeparator,
}) })
@ -598,7 +598,7 @@ func (web *webAPIHandlers) RemoveObject(r *http.Request, args *RemoveObjectArgs,
if !globalPolicySys.IsAllowed(policy.Args{ if !globalPolicySys.IsAllowed(policy.Args{
Action: policy.DeleteObjectAction, Action: policy.DeleteObjectAction,
BucketName: args.BucketName, BucketName: args.BucketName,
ConditionValues: getConditionValues(r, "", ""), ConditionValues: getConditionValues(r, "", "", nil),
IsOwner: false, IsOwner: false,
ObjectName: object, ObjectName: object,
}) { }) {
@ -672,7 +672,7 @@ next:
AccountName: claims.Subject, AccountName: claims.Subject,
Action: iampolicy.DeleteObjectAction, Action: iampolicy.DeleteObjectAction,
BucketName: args.BucketName, BucketName: args.BucketName,
ConditionValues: getConditionValues(r, "", claims.Subject), ConditionValues: getConditionValues(r, "", claims.Subject, claims.Map()),
IsOwner: owner, IsOwner: owner,
ObjectName: objectName, ObjectName: objectName,
}) { }) {
@ -690,7 +690,7 @@ next:
AccountName: claims.Subject, AccountName: claims.Subject,
Action: iampolicy.DeleteObjectAction, Action: iampolicy.DeleteObjectAction,
BucketName: args.BucketName, BucketName: args.BucketName,
ConditionValues: getConditionValues(r, "", claims.Subject), ConditionValues: getConditionValues(r, "", claims.Subject, claims.Map()),
IsOwner: owner, IsOwner: owner,
ObjectName: objectName, ObjectName: objectName,
}) { }) {
@ -930,7 +930,7 @@ func (web *webAPIHandlers) Upload(w http.ResponseWriter, r *http.Request) {
if !globalPolicySys.IsAllowed(policy.Args{ if !globalPolicySys.IsAllowed(policy.Args{
Action: policy.PutObjectAction, Action: policy.PutObjectAction,
BucketName: bucket, BucketName: bucket,
ConditionValues: getConditionValues(r, "", ""), ConditionValues: getConditionValues(r, "", "", nil),
IsOwner: false, IsOwner: false,
ObjectName: object, ObjectName: object,
}) { }) {
@ -949,7 +949,7 @@ func (web *webAPIHandlers) Upload(w http.ResponseWriter, r *http.Request) {
AccountName: claims.Subject, AccountName: claims.Subject,
Action: iampolicy.PutObjectAction, Action: iampolicy.PutObjectAction,
BucketName: bucket, BucketName: bucket,
ConditionValues: getConditionValues(r, "", claims.Subject), ConditionValues: getConditionValues(r, "", claims.Subject, claims.Map()),
IsOwner: owner, IsOwner: owner,
ObjectName: object, ObjectName: object,
}) { }) {
@ -1110,7 +1110,7 @@ func (web *webAPIHandlers) Download(w http.ResponseWriter, r *http.Request) {
if !globalPolicySys.IsAllowed(policy.Args{ if !globalPolicySys.IsAllowed(policy.Args{
Action: policy.GetObjectAction, Action: policy.GetObjectAction,
BucketName: bucket, BucketName: bucket,
ConditionValues: getConditionValues(r, "", ""), ConditionValues: getConditionValues(r, "", "", nil),
IsOwner: false, IsOwner: false,
ObjectName: object, ObjectName: object,
}) { }) {
@ -1129,7 +1129,7 @@ func (web *webAPIHandlers) Download(w http.ResponseWriter, r *http.Request) {
AccountName: claims.Subject, AccountName: claims.Subject,
Action: iampolicy.GetObjectAction, Action: iampolicy.GetObjectAction,
BucketName: bucket, BucketName: bucket,
ConditionValues: getConditionValues(r, "", claims.Subject), ConditionValues: getConditionValues(r, "", claims.Subject, claims.Map()),
IsOwner: owner, IsOwner: owner,
ObjectName: object, ObjectName: object,
}) { }) {
@ -1259,7 +1259,7 @@ func (web *webAPIHandlers) DownloadZip(w http.ResponseWriter, r *http.Request) {
if !globalPolicySys.IsAllowed(policy.Args{ if !globalPolicySys.IsAllowed(policy.Args{
Action: policy.GetObjectAction, Action: policy.GetObjectAction,
BucketName: args.BucketName, BucketName: args.BucketName,
ConditionValues: getConditionValues(r, "", ""), ConditionValues: getConditionValues(r, "", "", nil),
IsOwner: false, IsOwner: false,
ObjectName: pathJoin(args.Prefix, object), ObjectName: pathJoin(args.Prefix, object),
}) { }) {
@ -1280,7 +1280,7 @@ func (web *webAPIHandlers) DownloadZip(w http.ResponseWriter, r *http.Request) {
AccountName: claims.Subject, AccountName: claims.Subject,
Action: iampolicy.GetObjectAction, Action: iampolicy.GetObjectAction,
BucketName: args.BucketName, BucketName: args.BucketName,
ConditionValues: getConditionValues(r, "", claims.Subject), ConditionValues: getConditionValues(r, "", claims.Subject, claims.Map()),
IsOwner: owner, IsOwner: owner,
ObjectName: pathJoin(args.Prefix, object), ObjectName: pathJoin(args.Prefix, object),
}) { }) {
@ -1426,7 +1426,7 @@ func (web *webAPIHandlers) GetBucketPolicy(r *http.Request, args *GetBucketPolic
AccountName: claims.Subject, AccountName: claims.Subject,
Action: iampolicy.GetBucketPolicyAction, Action: iampolicy.GetBucketPolicyAction,
BucketName: args.BucketName, BucketName: args.BucketName,
ConditionValues: getConditionValues(r, "", claims.Subject), ConditionValues: getConditionValues(r, "", claims.Subject, claims.Map()),
IsOwner: owner, IsOwner: owner,
}) { }) {
return toJSONError(ctx, errAccessDenied) return toJSONError(ctx, errAccessDenied)
@ -1523,7 +1523,7 @@ func (web *webAPIHandlers) ListAllBucketPolicies(r *http.Request, args *ListAllB
AccountName: claims.Subject, AccountName: claims.Subject,
Action: iampolicy.GetBucketPolicyAction, Action: iampolicy.GetBucketPolicyAction,
BucketName: args.BucketName, BucketName: args.BucketName,
ConditionValues: getConditionValues(r, "", claims.Subject), ConditionValues: getConditionValues(r, "", claims.Subject, claims.Map()),
IsOwner: owner, IsOwner: owner,
}) { }) {
return toJSONError(ctx, errAccessDenied) return toJSONError(ctx, errAccessDenied)
@ -1613,7 +1613,7 @@ func (web *webAPIHandlers) SetBucketPolicy(r *http.Request, args *SetBucketPolic
AccountName: claims.Subject, AccountName: claims.Subject,
Action: iampolicy.PutBucketPolicyAction, Action: iampolicy.PutBucketPolicyAction,
BucketName: args.BucketName, BucketName: args.BucketName,
ConditionValues: getConditionValues(r, "", claims.Subject), ConditionValues: getConditionValues(r, "", claims.Subject, claims.Map()),
IsOwner: owner, IsOwner: owner,
}) { }) {
return toJSONError(ctx, errAccessDenied) return toJSONError(ctx, errAccessDenied)

@ -85,6 +85,18 @@ const (
// AWSUsername - user friendly name, in MinIO this value is same as your user Access Key. // AWSUsername - user friendly name, in MinIO this value is same as your user Access Key.
AWSUsername Key = "aws:username" 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. // AllSupportedKeys - is list of all all supported keys.
@ -107,6 +119,10 @@ var AllSupportedKeys = []Key{
AWSPrincipalType, AWSPrincipalType,
AWSUserID, AWSUserID,
AWSUsername, AWSUsername,
JWTSub,
JWTIss,
JWTAud,
JWTJti,
// Add new supported condition keys. // Add new supported condition keys.
} }
@ -121,6 +137,10 @@ var CommonKeys = []Key{
AWSPrincipalType, AWSPrincipalType,
AWSUserID, AWSUserID,
AWSUsername, AWSUsername,
JWTSub,
JWTIss,
JWTAud,
JWTJti,
} }
func substFuncFromValues(values map[string][]string) func(string) string { func substFuncFromValues(values map[string][]string) func(string) string {
@ -166,8 +186,9 @@ func (key Key) Name() string {
if strings.HasPrefix(keyString, "aws:") { if strings.HasPrefix(keyString, "aws:") {
return strings.TrimPrefix(keyString, "aws:") return strings.TrimPrefix(keyString, "aws:")
} else if strings.HasPrefix(keyString, "jwt:") {
return strings.TrimPrefix(keyString, "jwt:")
} }
return strings.TrimPrefix(keyString, "s3:") return strings.TrimPrefix(keyString, "s3:")
} }

Loading…
Cancel
Save