From 2e81f27d277fe98ba5234b731b6c6c7a22cc4427 Mon Sep 17 00:00:00 2001 From: Harshavardhana Date: Wed, 17 Oct 2018 16:23:09 -0700 Subject: [PATCH] Allow all browser calls to honor multi-users (#6645) - GetAuth - SetAuth - GenerateAuth Disallow changing bucket level metadata or creating/deleting buckets. --- cmd/auth-handler.go | 2 +- cmd/jwt.go | 5 -- cmd/typed-errors.go | 3 + cmd/web-handlers.go | 161 ++++++++++++++++++++++++++++++--------- cmd/web-handlers_test.go | 2 +- 5 files changed, 129 insertions(+), 44 deletions(-) diff --git a/cmd/auth-handler.go b/cmd/auth-handler.go index e34e0b896..030781003 100644 --- a/cmd/auth-handler.go +++ b/cmd/auth-handler.go @@ -358,7 +358,7 @@ func (a authHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) { return } else if aType == authTypeJWT { // Validate Authorization header if its valid for JWT request. - if !isHTTPRequestValid(r) { + if _, _, authErr := webRequestAuthenticate(r); authErr != nil { w.WriteHeader(http.StatusUnauthorized) return } diff --git a/cmd/jwt.go b/cmd/jwt.go index 4922741cd..555600f3b 100644 --- a/cmd/jwt.go +++ b/cmd/jwt.go @@ -205,11 +205,6 @@ func webTokenAuthenticate(token string) (jwtgo.StandardClaims, bool, error) { return claims, owner, nil } -func isHTTPRequestValid(req *http.Request) bool { - _, _, err := webRequestAuthenticate(req) - return err == nil -} - // Check if the request is authenticated. // Returns nil if the request is authenticated. errNoAuthToken if token missing. // Returns errAuthentication for all other errors. diff --git a/cmd/typed-errors.go b/cmd/typed-errors.go index e1f9368dd..366141463 100644 --- a/cmd/typed-errors.go +++ b/cmd/typed-errors.go @@ -82,3 +82,6 @@ var errNoSuchUser = errors.New("Specified user does not exist") // error returned in IAM subsystem when policy doesn't exist. var errNoSuchPolicy = errors.New("Specified canned policy does not exist") + +// error returned when access is denied. +var errAccessDenied = errors.New("Do not have enough permissions to access this resource") diff --git a/cmd/web-handlers.go b/cmd/web-handlers.go index 0851b9f7e..6b54670a0 100644 --- a/cmd/web-handlers.go +++ b/cmd/web-handlers.go @@ -74,8 +74,9 @@ type ServerInfoRep struct { // ServerInfo - get server info. func (web *webAPIHandlers) ServerInfo(r *http.Request, args *WebGenericArgs, reply *ServerInfoRep) error { - if !isHTTPRequestValid(r) { - return toJSONError(errAuthentication) + _, owner, authErr := webRequestAuthenticate(r) + if authErr != nil { + return toJSONError(authErr) } host, err := os.Hostname() if err != nil { @@ -96,6 +97,14 @@ func (web *webAPIHandlers) ServerInfo(r *http.Request, args *WebGenericArgs, rep reply.MinioVersion = Version reply.MinioGlobalInfo = getGlobalInfo() + // If ENV creds are not set and incoming user is not owner + // disable changing credentials. + // TODO: fix this in future and allow changing user credentials. + v, ok := reply.MinioGlobalInfo["isEnvCreds"].(bool) + if ok && !v { + reply.MinioGlobalInfo["isEnvCreds"] = !owner + } + reply.MinioMemory = mem reply.MinioPlatform = platform reply.MinioRuntime = goruntime @@ -115,8 +124,9 @@ func (web *webAPIHandlers) StorageInfo(r *http.Request, args *AuthArgs, reply *S if objectAPI == nil { return toJSONError(errServerNotInitialized) } - if !isHTTPRequestValid(r) { - return toJSONError(errAuthentication) + _, _, authErr := webRequestAuthenticate(r) + if authErr != nil { + return toJSONError(authErr) } reply.StorageInfo = objectAPI.StorageInfo(context.Background()) reply.UIVersion = browser.UIVersion @@ -134,8 +144,13 @@ func (web *webAPIHandlers) MakeBucket(r *http.Request, args *MakeBucketArgs, rep if objectAPI == nil { return toJSONError(errServerNotInitialized) } - if !isHTTPRequestValid(r) { - return toJSONError(errAuthentication) + _, owner, authErr := webRequestAuthenticate(r) + if authErr != nil { + return toJSONError(authErr) + } + // TODO: Allow MakeBucket in future. + if !owner { + return toJSONError(errAccessDenied) } // Check if bucket is a reserved bucket name or invalid. @@ -182,8 +197,13 @@ func (web *webAPIHandlers) DeleteBucket(r *http.Request, args *RemoveBucketArgs, if objectAPI == nil { return toJSONError(errServerNotInitialized) } - if !isHTTPRequestValid(r) { - return toJSONError(errAuthentication) + _, owner, authErr := webRequestAuthenticate(r) + if authErr != nil { + return toJSONError(authErr) + } + // TODO: Allow DeleteBucket in future. + if !owner { + return toJSONError(errAccessDenied) } ctx := context.Background() @@ -338,7 +358,7 @@ func (web *webAPIHandlers) ListObjects(r *http.Request, args *ListObjectsArgs, r if !readable { // Error out if anonymous user (non-owner) has no access to download or upload objects if !writable { - return errAuthentication + return errAccessDenied } // return empty object list if access is write only return nil @@ -372,7 +392,7 @@ func (web *webAPIHandlers) ListObjects(r *http.Request, args *ListObjectsArgs, r if !readable { // Error out if anonymous user (non-owner) has no access to download or upload objects if !writable { - return errAuthentication + return errAccessDenied } // return empty object list if access is write only return nil @@ -435,8 +455,10 @@ func (web *webAPIHandlers) RemoveObject(r *http.Request, args *RemoveObjectArgs, if web.CacheAPI() != nil { listObjects = web.CacheAPI().ListObjects } - if !isHTTPRequestValid(r) { - return toJSONError(errAuthentication) + + claims, owner, authErr := webRequestAuthenticate(r) + if authErr != nil { + return toJSONError(authErr) } if args.BucketName == "" || len(args.Objects) == 0 { @@ -455,12 +477,34 @@ next: } } + if !globalIAMSys.IsAllowed(iampolicy.Args{ + AccountName: claims.Subject, + Action: iampolicy.Action(policy.DeleteObjectAction), + BucketName: args.BucketName, + ConditionValues: getConditionValues(r, ""), + IsOwner: owner, + ObjectName: objectName, + }) { + return toJSONError(errAccessDenied) + } + if err = deleteObject(nil, objectAPI, web.CacheAPI(), args.BucketName, objectName, r); err != nil { break next } continue } + if !globalIAMSys.IsAllowed(iampolicy.Args{ + AccountName: claims.Subject, + Action: iampolicy.Action(policy.DeleteObjectAction), + BucketName: args.BucketName, + ConditionValues: getConditionValues(r, ""), + IsOwner: owner, + ObjectName: objectName, + }) { + return toJSONError(errAccessDenied) + } + // For directories, list the contents recursively and remove. marker := "" for { @@ -523,8 +567,12 @@ type GenerateAuthReply struct { } func (web webAPIHandlers) GenerateAuth(r *http.Request, args *WebGenericArgs, reply *GenerateAuthReply) error { - if !isHTTPRequestValid(r) { - return toJSONError(errAuthentication) + _, owner, authErr := webRequestAuthenticate(r) + if authErr != nil { + return toJSONError(authErr) + } + if !owner { + return toJSONError(errAccessDenied) } cred, err := auth.GetNewCredentials() if err != nil { @@ -551,12 +599,14 @@ type SetAuthReply struct { // SetAuth - Set accessKey and secretKey credentials. func (web *webAPIHandlers) SetAuth(r *http.Request, args *SetAuthArgs, reply *SetAuthReply) error { - if !isHTTPRequestValid(r) { - return toJSONError(errAuthentication) + _, owner, authErr := webRequestAuthenticate(r) + if authErr != nil { + return toJSONError(authErr) } // If creds are set through ENV disallow changing credentials. - if globalIsEnvCreds || globalWORMEnabled { + // TODO: Multi-user credentials also cannot be changed from browser. + if globalIsEnvCreds || globalWORMEnabled || !owner { return toJSONError(errChangeCredNotAllowed) } @@ -588,7 +638,10 @@ func (web *webAPIHandlers) SetAuth(r *http.Request, args *SetAuthArgs, reply *Se reply.PeerErrMsgs[host.String()] = err.Error() } } else { - reply.Token = newAuthToken() + reply.Token, err = authenticateWeb(creds.AccessKey, creds.SecretKey) + if err != nil { + return toJSONError(err) + } reply.UIVersion = browser.UIVersion } @@ -604,8 +657,12 @@ type GetAuthReply struct { // GetAuth - return accessKey and secretKey credentials. func (web *webAPIHandlers) GetAuth(r *http.Request, args *WebGenericArgs, reply *GetAuthReply) error { - if !isHTTPRequestValid(r) { - return toJSONError(errAuthentication) + _, owner, authErr := webRequestAuthenticate(r) + if authErr != nil { + return toJSONError(authErr) + } + if !owner { + return toJSONError(errAccessDenied) } creds := globalServerConfig.GetCredential() reply.AccessKey = creds.AccessKey @@ -622,11 +679,19 @@ type URLTokenReply struct { // CreateURLToken creates a URL token (short-lived) for GET requests. func (web *webAPIHandlers) CreateURLToken(r *http.Request, args *WebGenericArgs, reply *URLTokenReply) error { - if !isHTTPRequestValid(r) { - return toJSONError(errAuthentication) + claims, owner, authErr := webRequestAuthenticate(r) + if authErr != nil { + return toJSONError(authErr) } creds := globalServerConfig.GetCredential() + if !owner { + var ok bool + creds, ok = globalIAMSys.GetUser(claims.Subject) + if !ok { + return toJSONError(errInvalidAccessKeyID) + } + } token, err := authenticateURL(creds.AccessKey, creds.SecretKey) if err != nil { @@ -1211,8 +1276,12 @@ func (web *webAPIHandlers) GetBucketPolicy(r *http.Request, args *GetBucketPolic return toJSONError(errServerNotInitialized) } - if !isHTTPRequestValid(r) { - return toJSONError(errAuthentication) + _, owner, authErr := webRequestAuthenticate(r) + if authErr != nil { + return toJSONError(authErr) + } + if !owner { + return toJSONError(errAccessDenied) } bucketPolicy, err := objectAPI.GetBucketPolicy(context.Background(), args.BucketName) @@ -1260,8 +1329,12 @@ func (web *webAPIHandlers) ListAllBucketPolicies(r *http.Request, args *ListAllB return toJSONError(errServerNotInitialized) } - if !isHTTPRequestValid(r) { - return toJSONError(errAuthentication) + _, owner, authErr := webRequestAuthenticate(r) + if authErr != nil { + return toJSONError(authErr) + } + if !owner { + return toJSONError(errAccessDenied) } bucketPolicy, err := objectAPI.GetBucketPolicy(context.Background(), args.BucketName) @@ -1307,8 +1380,12 @@ func (web *webAPIHandlers) SetBucketPolicy(r *http.Request, args *SetBucketPolic return toJSONError(errServerNotInitialized) } - if !isHTTPRequestValid(r) { - return toJSONError(errAuthentication) + _, owner, authErr := webRequestAuthenticate(r) + if authErr != nil { + return toJSONError(authErr) + } + if !owner { + return toJSONError(errAccessDenied) } policyType := miniogopolicy.BucketPolicy(args.Policy) @@ -1385,27 +1462,37 @@ type PresignedGetRep struct { // PresignedGET - returns presigned-Get url. func (web *webAPIHandlers) PresignedGet(r *http.Request, args *PresignedGetArgs, reply *PresignedGetRep) error { - if !isHTTPRequestValid(r) { - return toJSONError(errAuthentication) + claims, owner, authErr := webRequestAuthenticate(r) + if authErr != nil { + return toJSONError(authErr) + } + var creds auth.Credentials + if !owner { + var ok bool + creds, ok = globalIAMSys.GetUser(claims.Subject) + if !ok { + return toJSONError(errInvalidAccessKeyID) + } + } else { + creds = globalServerConfig.GetCredential() } + region := globalServerConfig.GetRegion() if args.BucketName == "" || args.ObjectName == "" { return &json2.Error{ Message: "Bucket and Object are mandatory arguments.", } } + reply.UIVersion = browser.UIVersion - reply.URL = presignedGet(args.HostName, args.BucketName, args.ObjectName, args.Expiry) + reply.URL = presignedGet(args.HostName, args.BucketName, args.ObjectName, args.Expiry, creds, region) return nil } // Returns presigned url for GET method. -func presignedGet(host, bucket, object string, expiry int64) string { - cred := globalServerConfig.GetCredential() - region := globalServerConfig.GetRegion() - - accessKey := cred.AccessKey - secretKey := cred.SecretKey +func presignedGet(host, bucket, object string, expiry int64, creds auth.Credentials, region string) string { + accessKey := creds.AccessKey + secretKey := creds.SecretKey date := UTCNow() dateStr := date.Format(iso8601Format) diff --git a/cmd/web-handlers_test.go b/cmd/web-handlers_test.go index c9bc657a4..e91705c3a 100644 --- a/cmd/web-handlers_test.go +++ b/cmd/web-handlers_test.go @@ -343,7 +343,7 @@ func testDeleteBucketWebHandler(obj ObjectLayer, instanceType string, t TestErrH {"minio", false, token, "specified bucket minio does not exist"}, {bucketName, false, token, ""}, {bucketName, true, token, "Bucket not empty"}, - {bucketName, false, "", "Authentication failed"}, + {bucketName, false, "", "JWT token missing"}, } for _, test := range testCases {