Parse and return proper errors with x-amz-security-token (#6766)

This PR also simplifies the token and access key validation
across our signature handling.
This commit is contained in:
Harshavardhana 2018-11-07 06:40:03 -08:00 committed by Nitish Tiwari
parent 88959ce600
commit bf414068a3
9 changed files with 103 additions and 148 deletions

View File

@ -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: {

View File

@ -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, ""),

View File

@ -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.

View File

@ -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 {

View File

@ -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
}

View File

@ -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.

View File

@ -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.

View File

@ -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 {

View File

@ -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)