diff --git a/cmd/api-errors.go b/cmd/api-errors.go index 5b86933f7..85bb51f40 100644 --- a/cmd/api-errors.go +++ b/cmd/api-errors.go @@ -150,6 +150,9 @@ const ( ErrKMSNotConfigured ErrKMSAuthFailure + ErrNoAccessKey + ErrInvalidToken + // Bucket notification related errors. ErrEventNotification ErrARNNotification @@ -806,6 +809,16 @@ var errorCodeResponse = map[APIErrorCode]APIError{ Description: "Server side encryption specified but KMS authorization failed", HTTPStatusCode: http.StatusBadRequest, }, + ErrNoAccessKey: { + Code: "AccessDenied", + Description: "No AWSAccessKey was presented", + HTTPStatusCode: http.StatusForbidden, + }, + ErrInvalidToken: { + Code: "InvalidTokenId", + Description: "The security token included in the request is invalid", + HTTPStatusCode: http.StatusForbidden, + }, /// S3 extensions. ErrContentSHA256Mismatch: { diff --git a/cmd/auth-handler.go b/cmd/auth-handler.go index 030781003..4440231a2 100644 --- a/cmd/auth-handler.go +++ b/cmd/auth-handler.go @@ -22,6 +22,7 @@ import ( "encoding/base64" "encoding/hex" "errors" + "fmt" "io" "io/ioutil" "net/http" @@ -29,6 +30,7 @@ import ( jwtgo "github.com/dgrijalva/jwt-go" "github.com/minio/minio/cmd/logger" + "github.com/minio/minio/pkg/auth" "github.com/minio/minio/pkg/hash" "github.com/minio/minio/pkg/iam/policy" "github.com/minio/minio/pkg/policy" @@ -154,12 +156,41 @@ func getSessionToken(r *http.Request) (token string) { } // Fetch claims in the security token returned by the client and validate the token. -func getClaimsFromToken(r *http.Request) (map[string]interface{}, APIErrorCode) { +func getClaimsFromToken(r *http.Request, cred auth.Credentials) (map[string]interface{}, APIErrorCode) { + stsTokenCallback := func(jwtToken *jwtgo.Token) (interface{}, error) { + if _, ok := jwtToken.Method.(*jwtgo.SigningMethodHMAC); !ok { + return nil, fmt.Errorf("Unexpected signing method: %v", jwtToken.Header["alg"]) + } + if err := jwtToken.Claims.Valid(); err != nil { + return nil, errAuthentication + } + if claims, ok := jwtToken.Claims.(jwtgo.MapClaims); ok { + if _, ok = claims["accessKey"].(string); !ok { + return nil, errInvalidAccessKeyID + } + // JWT token for x-amz-security-token is signed with admin + // secret key, temporary credentials become invalid if + // server admin credentials change. This is done to ensure + // that clients cannot decode the token using the temp + // secret keys and generate an entirely new claim by essentially + // hijacking the policies. We need to make sure that this is + // based an admin credential such that token cannot be decoded + // on the client side and is treated like an opaque value. + return []byte(globalServerConfig.GetCredential().SecretKey), nil + } + return nil, errAuthentication + } claims := make(map[string]interface{}) token := getSessionToken(r) if token == "" { return nil, ErrNone } + if token != "" && cred.AccessKey == "" { + return nil, ErrNoAccessKey + } + if token != cred.SessionToken { + return nil, ErrInvalidToken + } p := &jwtgo.Parser{} jtoken, err := p.ParseWithClaims(token, jwtgo.MapClaims(claims), stsTokenCallback) if err != nil { @@ -177,7 +208,7 @@ func getClaimsFromToken(r *http.Request) (map[string]interface{}, APIErrorCode) // for authenticated requests validates IAM policies. // returns APIErrorCode if any to be replied to the client. func checkRequestAuthType(ctx context.Context, r *http.Request, action policy.Action, bucketName, objectName string) (s3Err APIErrorCode) { - var accessKey string + var cred auth.Credentials var owner bool switch getRequestAuthType(r) { case authTypeUnknown, authTypeStreamingSigned: @@ -186,7 +217,7 @@ func checkRequestAuthType(ctx context.Context, r *http.Request, action policy.Ac if s3Err = isReqAuthenticatedV2(r); s3Err != ErrNone { return s3Err } - accessKey, owner, s3Err = getReqAccessKeyV2(r) + cred, owner, s3Err = getReqAccessKeyV2(r) case authTypeSigned, authTypePresigned: region := globalServerConfig.GetRegion() switch action { @@ -196,7 +227,7 @@ func checkRequestAuthType(ctx context.Context, r *http.Request, action policy.Ac if s3Err = isReqAuthenticated(r, region); s3Err != ErrNone { return s3Err } - accessKey, owner, s3Err = getReqAccessKeyV4(r, region) + cred, owner, s3Err = getReqAccessKeyV4(r, region) } if s3Err != ErrNone { return s3Err @@ -225,14 +256,14 @@ func checkRequestAuthType(ctx context.Context, r *http.Request, action policy.Ac r.Body = ioutil.NopCloser(bytes.NewReader(payload)) } - claims, s3Err := getClaimsFromToken(r) + claims, s3Err := getClaimsFromToken(r, cred) if s3Err != ErrNone { return s3Err } - if accessKey == "" { + if cred.AccessKey == "" { if globalPolicySys.IsAllowed(policy.Args{ - AccountName: accessKey, + AccountName: cred.AccessKey, Action: action, BucketName: bucketName, ConditionValues: getConditionValues(r, locationConstraint), @@ -245,7 +276,7 @@ func checkRequestAuthType(ctx context.Context, r *http.Request, action policy.Ac } if globalIAMSys.IsAllowed(iampolicy.Args{ - AccountName: accessKey, + AccountName: cred.AccessKey, Action: iampolicy.Action(action), BucketName: bucketName, ConditionValues: getConditionValues(r, ""), @@ -372,29 +403,29 @@ func (a authHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) { // call verifies bucket policies and IAM policies, supports multi user // checks etc. func isPutAllowed(atype authType, bucketName, objectName string, r *http.Request) (s3Err APIErrorCode) { - var accessKey string + var cred auth.Credentials var owner bool switch atype { case authTypeUnknown: return ErrAccessDenied case authTypeSignedV2, authTypePresignedV2: - accessKey, owner, s3Err = getReqAccessKeyV2(r) + cred, owner, s3Err = getReqAccessKeyV2(r) case authTypeStreamingSigned, authTypePresigned, authTypeSigned: region := globalServerConfig.GetRegion() - accessKey, owner, s3Err = getReqAccessKeyV4(r, region) + cred, owner, s3Err = getReqAccessKeyV4(r, region) } if s3Err != ErrNone { return s3Err } - claims, s3Err := getClaimsFromToken(r) + claims, s3Err := getClaimsFromToken(r, cred) if s3Err != ErrNone { return s3Err } - if accessKey == "" { + if cred.AccessKey == "" { if globalPolicySys.IsAllowed(policy.Args{ - AccountName: accessKey, + AccountName: cred.AccessKey, Action: policy.PutObjectAction, BucketName: bucketName, ConditionValues: getConditionValues(r, ""), @@ -407,7 +438,7 @@ func isPutAllowed(atype authType, bucketName, objectName string, r *http.Request } if globalIAMSys.IsAllowed(iampolicy.Args{ - AccountName: accessKey, + AccountName: cred.AccessKey, Action: policy.PutObjectAction, BucketName: bucketName, ConditionValues: getConditionValues(r, ""), diff --git a/cmd/handler-utils.go b/cmd/handler-utils.go index f25076c32..955035d71 100644 --- a/cmd/handler-utils.go +++ b/cmd/handler-utils.go @@ -178,11 +178,11 @@ func getRedirectPostRawQuery(objInfo ObjectInfo) string { // Returns access key in the request Authorization header. func getReqAccessKey(r *http.Request, region string) (accessKey string) { - accessKey, _, _ = getReqAccessKeyV4(r, region) - if accessKey == "" { - accessKey, _, _ = getReqAccessKeyV2(r) + cred, _, _ := getReqAccessKeyV4(r, region) + if cred.AccessKey == "" { + cred, _, _ = getReqAccessKeyV2(r) } - return accessKey + return cred.AccessKey } // Extract request params to be sent with event notifiation. diff --git a/cmd/jwt.go b/cmd/jwt.go index 555600f3b..d16dd461c 100644 --- a/cmd/jwt.go +++ b/cmd/jwt.go @@ -110,34 +110,6 @@ func authenticateURL(accessKey, secretKey string) (string, error) { return authenticateJWTUsers(accessKey, secretKey, defaultURLJWTExpiry) } -func stsTokenCallback(jwtToken *jwtgo.Token) (interface{}, error) { - if _, ok := jwtToken.Method.(*jwtgo.SigningMethodHMAC); !ok { - return nil, fmt.Errorf("Unexpected signing method: %v", jwtToken.Header["alg"]) - } - - if err := jwtToken.Claims.Valid(); err != nil { - return nil, errAuthentication - } - if claims, ok := jwtToken.Claims.(jwtgo.MapClaims); ok { - accessKey, ok := claims["accessKey"].(string) - if !ok { - return nil, errInvalidAccessKeyID - } - if accessKey == globalServerConfig.GetCredential().AccessKey { - return []byte(globalServerConfig.GetCredential().SecretKey), nil - } - if globalIAMSys == nil { - return nil, errInvalidAccessKeyID - } - _, ok = globalIAMSys.GetUser(accessKey) - if !ok { - return nil, errInvalidAccessKeyID - } - return []byte(globalServerConfig.GetCredential().SecretKey), nil - } - return nil, errAuthentication -} - // Callback function used for parsing func webTokenCallback(jwtToken *jwtgo.Token) (interface{}, error) { if _, ok := jwtToken.Method.(*jwtgo.SigningMethodHMAC); !ok { diff --git a/cmd/signature-v2.go b/cmd/signature-v2.go index d1ce20897..39ebb6ba1 100644 --- a/cmd/signature-v2.go +++ b/cmd/signature-v2.go @@ -69,15 +69,9 @@ var resourceList = []string{ func doesPolicySignatureV2Match(formValues http.Header) APIErrorCode { cred := globalServerConfig.GetCredential() accessKey := formValues.Get("AWSAccessKeyId") - if accessKey != cred.AccessKey { - if globalIAMSys == nil { - return ErrInvalidAccessKeyID - } - var ok bool - cred, ok = globalIAMSys.GetUser(accessKey) - if !ok { - return ErrInvalidAccessKeyID - } + cred, _, s3Err := checkKeyValid(accessKey) + if s3Err != ErrNone { + return s3Err } policy := formValues.Get("Policy") signature := formValues.Get("Signature") @@ -153,16 +147,9 @@ func doesPresignV2SignatureMatch(r *http.Request) APIErrorCode { return ErrInvalidQueryParams } - // Validate if access key id same. - if accessKey != cred.AccessKey { - if globalIAMSys == nil { - return ErrInvalidAccessKeyID - } - var ok bool - cred, ok = globalIAMSys.GetUser(accessKey) - if !ok { - return ErrInvalidAccessKeyID - } + cred, _, s3Err := checkKeyValid(accessKey) + if s3Err != ErrNone { + return s3Err } // Make sure the request has not expired. @@ -189,27 +176,25 @@ func doesPresignV2SignatureMatch(r *http.Request) APIErrorCode { return ErrNone } -func getReqAccessKeyV2(r *http.Request) (string, bool, APIErrorCode) { +func getReqAccessKeyV2(r *http.Request) (auth.Credentials, bool, APIErrorCode) { if accessKey := r.URL.Query().Get("AWSAccessKeyId"); accessKey != "" { - owner, s3Err := checkKeyValid(accessKey) - return accessKey, owner, s3Err + return checkKeyValid(accessKey) } // below is V2 Signed Auth header format, splitting on `space` (after the `AWS` string). // Authorization = "AWS" + " " + AWSAccessKeyId + ":" + Signature authFields := strings.Split(r.Header.Get("Authorization"), " ") if len(authFields) != 2 { - return "", false, ErrMissingFields + return auth.Credentials{}, false, ErrMissingFields } // Then will be splitting on ":", this will seprate `AWSAccessKeyId` and `Signature` string. keySignFields := strings.Split(strings.TrimSpace(authFields[1]), ":") if len(keySignFields) != 2 { - return "", false, ErrMissingFields + return auth.Credentials{}, false, ErrMissingFields } - owner, s3Err := checkKeyValid(keySignFields[0]) - return keySignFields[0], owner, s3Err + return checkKeyValid(keySignFields[0]) } // Authorization = "AWS" + " " + AWSAccessKeyId + ":" + Signature; @@ -244,24 +229,11 @@ func validateV2AuthHeader(r *http.Request) (auth.Credentials, APIErrorCode) { return cred, ErrSignatureVersionNotSupported } - accessKey, owner, apiErr := getReqAccessKeyV2(r) + cred, _, apiErr := getReqAccessKeyV2(r) if apiErr != ErrNone { return cred, apiErr } - cred = globalServerConfig.GetCredential() - // Access credentials. - if !owner { - if globalIAMSys == nil { - return cred, ErrInvalidAccessKeyID - } - var ok bool - cred, ok = globalIAMSys.GetUser(accessKey) - if !ok { - return cred, ErrInvalidAccessKeyID - } - } - return cred, ErrNone } diff --git a/cmd/signature-v4-parser.go b/cmd/signature-v4-parser.go index a94609c3b..3f4326181 100644 --- a/cmd/signature-v4-parser.go +++ b/cmd/signature-v4-parser.go @@ -47,22 +47,21 @@ func (c credentialHeader) getScope() string { }, "/") } -func getReqAccessKeyV4(r *http.Request, region string) (string, bool, APIErrorCode) { +func getReqAccessKeyV4(r *http.Request, region string) (auth.Credentials, bool, APIErrorCode) { ch, err := parseCredentialHeader("Credential="+r.URL.Query().Get("X-Amz-Credential"), region) if err != ErrNone { // Strip off the Algorithm prefix. v4Auth := strings.TrimPrefix(r.Header.Get("Authorization"), signV4Algorithm) authFields := strings.Split(strings.TrimSpace(v4Auth), ",") if len(authFields) != 3 { - return "", false, ErrMissingFields + return auth.Credentials{}, false, ErrMissingFields } ch, err = parseCredentialHeader(authFields[0], region) if err != ErrNone { - return "", false, err + return auth.Credentials{}, false, err } } - owner, s3Err := checkKeyValid(ch.accessKey) - return ch.accessKey, owner, s3Err + return checkKeyValid(ch.accessKey) } // parse credentialHeader string into its structured form. diff --git a/cmd/signature-v4-utils.go b/cmd/signature-v4-utils.go index 0ffeb8723..a042fbd10 100644 --- a/cmd/signature-v4-utils.go +++ b/cmd/signature-v4-utils.go @@ -22,6 +22,7 @@ import ( "strconv" "strings" + "github.com/minio/minio/pkg/auth" "github.com/minio/sha256-simd" ) @@ -102,19 +103,21 @@ func isValidRegion(reqRegion string, confRegion string) bool { // check if the access key is valid and recognized, additionally // also returns if the access key is owner/admin. -func checkKeyValid(accessKey string) (bool, APIErrorCode) { +func checkKeyValid(accessKey string) (auth.Credentials, bool, APIErrorCode) { var owner = true - if globalServerConfig.GetCredential().AccessKey != accessKey { + var cred = globalServerConfig.GetCredential() + if cred.AccessKey != accessKey { if globalIAMSys == nil { - return false, ErrInvalidAccessKeyID + return cred, false, ErrInvalidAccessKeyID } // Check if the access key is part of users credentials. - if _, ok := globalIAMSys.GetUser(accessKey); !ok { - return false, ErrInvalidAccessKeyID + var ok bool + if cred, ok = globalIAMSys.GetUser(accessKey); !ok { + return cred, false, ErrInvalidAccessKeyID } owner = false } - return owner, ErrNone + return cred, owner, ErrNone } // sumHMAC calculate hmac between two input byte array. diff --git a/cmd/signature-v4.go b/cmd/signature-v4.go index b92b77812..f393c5706 100644 --- a/cmd/signature-v4.go +++ b/cmd/signature-v4.go @@ -161,9 +161,6 @@ func compareSignatureV4(sig1, sig2 string) bool { // - http://docs.aws.amazon.com/AmazonS3/latest/API/sigv4-HTTPPOSTConstructPolicy.html // returns ErrNone if the signature matches. func doesPolicySignatureV4Match(formValues http.Header) APIErrorCode { - // Access credentials. - cred := globalServerConfig.GetCredential() - // Server region. region := globalServerConfig.GetRegion() @@ -173,16 +170,9 @@ func doesPolicySignatureV4Match(formValues http.Header) APIErrorCode { return ErrMissingFields } - // Verify if the access key id matches. - if credHeader.accessKey != cred.AccessKey { - if globalIAMSys == nil { - return ErrInvalidAccessKeyID - } - var ok bool - cred, ok = globalIAMSys.GetUser(credHeader.accessKey) - if !ok { - return ErrInvalidAccessKeyID - } + cred, _, s3Err := checkKeyValid(credHeader.accessKey) + if s3Err != ErrNone { + return s3Err } // Get signing key. @@ -204,9 +194,6 @@ func doesPolicySignatureV4Match(formValues http.Header) APIErrorCode { // - http://docs.aws.amazon.com/AmazonS3/latest/API/sigv4-query-string-auth.html // returns ErrNone if the signature matches. func doesPresignedSignatureMatch(hashedPayload string, r *http.Request, region string) APIErrorCode { - // Access credentials. - cred := globalServerConfig.GetCredential() - // Copy request req := *r @@ -216,16 +203,9 @@ func doesPresignedSignatureMatch(hashedPayload string, r *http.Request, region s return err } - // Verify if the access key id matches. - if pSignValues.Credential.accessKey != cred.AccessKey { - if globalIAMSys == nil { - return ErrInvalidAccessKeyID - } - var ok bool - cred, ok = globalIAMSys.GetUser(pSignValues.Credential.accessKey) - if !ok { - return ErrInvalidAccessKeyID - } + cred, _, s3Err := checkKeyValid(pSignValues.Credential.accessKey) + if s3Err != ErrNone { + return s3Err } // Extract all the signed headers along with its values. @@ -233,6 +213,7 @@ func doesPresignedSignatureMatch(hashedPayload string, r *http.Request, region s if errCode != ErrNone { return errCode } + // Construct new query. query := make(url.Values) if req.URL.Query().Get("X-Amz-Content-Sha256") != "" { @@ -326,9 +307,6 @@ func doesPresignedSignatureMatch(hashedPayload string, r *http.Request, region s // - http://docs.aws.amazon.com/AmazonS3/latest/API/sig-v4-authenticating-requests.html // returns ErrNone if signature matches. func doesSignatureMatch(hashedPayload string, r *http.Request, region string) APIErrorCode { - // Access credentials. - cred := globalServerConfig.GetCredential() - // Copy request. req := *r @@ -347,16 +325,9 @@ func doesSignatureMatch(hashedPayload string, r *http.Request, region string) AP return errCode } - // Verify if the access key id matches. - if signV4Values.Credential.accessKey != cred.AccessKey { - if globalIAMSys == nil { - return ErrInvalidAccessKeyID - } - var ok bool - cred, ok = globalIAMSys.GetUser(signV4Values.Credential.accessKey) - if !ok { - return ErrInvalidAccessKeyID - } + cred, _, s3Err := checkKeyValid(signV4Values.Credential.accessKey) + if s3Err != ErrNone { + return s3Err } // Extract date, if not present throw error. @@ -366,6 +337,7 @@ func doesSignatureMatch(hashedPayload string, r *http.Request, region string) AP return ErrMissingDateHeader } } + // Parse date header. t, e := time.Parse(iso8601Format, date) if e != nil { diff --git a/cmd/streaming-signature-v4.go b/cmd/streaming-signature-v4.go index 1e2a26db1..84b76d45e 100644 --- a/cmd/streaming-signature-v4.go +++ b/cmd/streaming-signature-v4.go @@ -91,17 +91,9 @@ func calculateSeedSignature(r *http.Request) (cred auth.Credentials, signature s return cred, "", "", time.Time{}, errCode } - cred = globalServerConfig.GetCredential() - // Verify if the access key id matches. - if signV4Values.Credential.accessKey != cred.AccessKey { - if globalIAMSys == nil { - return cred, "", "", time.Time{}, ErrInvalidAccessKeyID - } - var ok bool - cred, ok = globalIAMSys.GetUser(signV4Values.Credential.accessKey) - if !ok { - return cred, "", "", time.Time{}, ErrInvalidAccessKeyID - } + cred, _, errCode = checkKeyValid(signV4Values.Credential.accessKey) + if errCode != ErrNone { + return cred, "", "", time.Time{}, errCode } // Verify if region is valid. @@ -114,6 +106,7 @@ func calculateSeedSignature(r *http.Request) (cred auth.Credentials, signature s return cred, "", "", time.Time{}, ErrMissingDateHeader } } + // Parse date header. var err error date, err = time.Parse(iso8601Format, dateStr)