signature: Region changes should be handled just like AWS. (#2805)

- PutBucket happens with 'us-east-1'.
- ListBuckets happens with any region.
- GetBucketLocation happens with 'us-east-1' and location is returned.
master
Harshavardhana 8 years ago committed by GitHub
parent 5fdd768903
commit 64083b9227
  1. 2
      cmd/api-datatypes.go
  2. 17
      cmd/auth-handler.go
  3. 2
      cmd/auth-handler_test.go
  4. 4
      cmd/bucket-handlers-listobjects.go
  5. 13
      cmd/bucket-handlers.go
  6. 6
      cmd/bucket-policy-handlers.go
  7. 44
      cmd/handler-utils.go
  8. 16
      cmd/object-handlers.go
  9. 39
      cmd/signature-v4.go
  10. 42
      cmd/signature-v4_test.go
  11. 9
      cmd/signature-verify-reader.go

@ -28,7 +28,7 @@ type ObjectIdentifier struct {
// createBucketConfiguration container for bucket configuration request from client.
// Used for parsing the location from the request body for MakeBucketbucket.
type createBucketLocationConfiguration struct {
XMLName xml.Name `xml:"http://s3.amazonaws.com/doc/2006-03-01/ CreateBucketConfiguration" json:"-"`
XMLName xml.Name `xml:"CreateBucketConfiguration" json:"-"`
Location string `xml:"LocationConstraint"`
}

@ -105,7 +105,7 @@ func sumMD5(data []byte) []byte {
}
// Verify if request has valid AWS Signature Version '4'.
func isReqAuthenticated(r *http.Request) (s3Error APIErrorCode) {
func isReqAuthenticated(r *http.Request, region string) (s3Error APIErrorCode) {
if r == nil {
return ErrInternalError
}
@ -121,7 +121,6 @@ func isReqAuthenticated(r *http.Request) (s3Error APIErrorCode) {
}
// Populate back the payload.
r.Body = ioutil.NopCloser(bytes.NewReader(payload))
validateRegion := true // Validate region.
var sha256sum string
// Skips calculating sha256 on the payload on server,
// if client requested for it.
@ -131,9 +130,9 @@ func isReqAuthenticated(r *http.Request) (s3Error APIErrorCode) {
sha256sum = hex.EncodeToString(sum256(payload))
}
if isRequestSignatureV4(r) {
return doesSignatureMatch(sha256sum, r, validateRegion)
return doesSignatureMatch(sha256sum, r, region)
} else if isRequestPresignedSignatureV4(r) {
return doesPresignedSignatureMatch(sha256sum, r, validateRegion)
return doesPresignedSignatureMatch(sha256sum, r, region)
}
return ErrAccessDenied
}
@ -145,13 +144,19 @@ func isReqAuthenticated(r *http.Request) (s3Error APIErrorCode) {
// request headers and body are used to calculate the signature validating
// the client signature present in request.
func checkAuth(r *http.Request) APIErrorCode {
// Validates the request for both Presigned and Signed
return checkAuthWithRegion(r, serverConfig.GetRegion())
}
// checkAuthWithRegion - similar to checkAuth but takes a custom region.
func checkAuthWithRegion(r *http.Request, region string) APIErrorCode {
// Validates the request for both Presigned and Signed.
aType := getRequestAuthType(r)
if aType != authTypePresigned && aType != authTypeSigned {
// For all unhandled auth types return error AccessDenied.
return ErrAccessDenied
}
// Validates the request for both Presigned and Signed.
return isReqAuthenticated(r)
return isReqAuthenticated(r, region)
}
// authHandler - handles all the incoming authorization headers and validates them if possible.

@ -294,7 +294,7 @@ func TestIsReqAuthenticated(t *testing.T) {
if testCase.s3Error == ErrBadDigest {
testCase.req.Header.Set("Content-Md5", "garbage")
}
if s3Error := isReqAuthenticated(testCase.req); s3Error != testCase.s3Error {
if s3Error := isReqAuthenticated(testCase.req, serverConfig.GetRegion()); s3Error != testCase.s3Error {
t.Fatalf("Unexpected s3error returned wanted %d, got %d", testCase.s3Error, s3Error)
}
}

@ -82,7 +82,7 @@ func (api objectAPIHandlers) ListObjectsV2Handler(w http.ResponseWriter, r *http
return
}
case authTypeSigned, authTypePresigned:
if s3Error := isReqAuthenticated(r); s3Error != ErrNone {
if s3Error := isReqAuthenticated(r, serverConfig.GetRegion()); s3Error != ErrNone {
errorIf(errSignatureMismatch, dumpRequest(r))
writeErrorResponse(w, r, s3Error, r.URL.Path)
return
@ -149,7 +149,7 @@ func (api objectAPIHandlers) ListObjectsV1Handler(w http.ResponseWriter, r *http
return
}
case authTypeSigned, authTypePresigned:
if s3Error := isReqAuthenticated(r); s3Error != ErrNone {
if s3Error := isReqAuthenticated(r, serverConfig.GetRegion()); s3Error != ErrNone {
errorIf(errSignatureMismatch, dumpRequest(r))
writeErrorResponse(w, r, s3Error, r.URL.Path)
return

@ -94,7 +94,7 @@ func (api objectAPIHandlers) GetBucketLocationHandler(w http.ResponseWriter, r *
return
}
case authTypeSigned, authTypePresigned:
if s3Error := isReqAuthenticated(r); s3Error != ErrNone {
if s3Error := isReqAuthenticated(r, "us-east-1"); s3Error != ErrNone {
errorIf(errSignatureMismatch, dumpRequest(r))
writeErrorResponse(w, r, s3Error, r.URL.Path)
return
@ -150,7 +150,7 @@ func (api objectAPIHandlers) ListMultipartUploadsHandler(w http.ResponseWriter,
return
}
case authTypePresigned, authTypeSigned:
if s3Error := isReqAuthenticated(r); s3Error != ErrNone {
if s3Error := isReqAuthenticated(r, serverConfig.GetRegion()); s3Error != ErrNone {
errorIf(errSignatureMismatch, dumpRequest(r))
writeErrorResponse(w, r, s3Error, r.URL.Path)
return
@ -197,7 +197,8 @@ func (api objectAPIHandlers) ListBucketsHandler(w http.ResponseWriter, r *http.R
}
// List buckets does not support bucket policies, no need to enforce it.
if s3Error := checkAuth(r); s3Error != ErrNone {
// Proceed to validate signature.
if s3Error := checkAuthWithRegion(r, ""); s3Error != ErrNone {
errorIf(errSignatureMismatch, dumpRequest(r))
writeErrorResponse(w, r, s3Error, r.URL.Path)
return
@ -243,7 +244,7 @@ func (api objectAPIHandlers) DeleteMultipleObjectsHandler(w http.ResponseWriter,
return
}
case authTypePresigned, authTypeSigned:
if s3Error := isReqAuthenticated(r); s3Error != ErrNone {
if s3Error := isReqAuthenticated(r, serverConfig.GetRegion()); s3Error != ErrNone {
errorIf(errSignatureMismatch, dumpRequest(r))
writeErrorResponse(w, r, s3Error, r.URL.Path)
return
@ -357,7 +358,7 @@ func (api objectAPIHandlers) PutBucketHandler(w http.ResponseWriter, r *http.Req
}
// PutBucket does not support policies, use checkAuth to validate signature.
if s3Error := checkAuth(r); s3Error != ErrNone {
if s3Error := checkAuthWithRegion(r, "us-east-1"); s3Error != ErrNone {
errorIf(errSignatureMismatch, dumpRequest(r))
writeErrorResponse(w, r, s3Error, r.URL.Path)
return
@ -490,7 +491,7 @@ func (api objectAPIHandlers) HeadBucketHandler(w http.ResponseWriter, r *http.Re
return
}
case authTypePresigned, authTypeSigned:
if s3Error := isReqAuthenticated(r); s3Error != ErrNone {
if s3Error := isReqAuthenticated(r, serverConfig.GetRegion()); s3Error != ErrNone {
errorIf(errSignatureMismatch, dumpRequest(r))
writeErrorResponse(w, r, s3Error, r.URL.Path)
return

@ -140,7 +140,7 @@ func (api objectAPIHandlers) PutBucketPolicyHandler(w http.ResponseWriter, r *ht
writeErrorResponse(w, r, ErrAccessDenied, r.URL.Path)
return
case authTypePresigned, authTypeSigned:
if s3Error := isReqAuthenticated(r); s3Error != ErrNone {
if s3Error := isReqAuthenticated(r, serverConfig.GetRegion()); s3Error != ErrNone {
writeErrorResponse(w, r, s3Error, r.URL.Path)
return
}
@ -223,7 +223,7 @@ func (api objectAPIHandlers) DeleteBucketPolicyHandler(w http.ResponseWriter, r
writeErrorResponse(w, r, ErrAccessDenied, r.URL.Path)
return
case authTypePresigned, authTypeSigned:
if s3Error := isReqAuthenticated(r); s3Error != ErrNone {
if s3Error := isReqAuthenticated(r, serverConfig.GetRegion()); s3Error != ErrNone {
writeErrorResponse(w, r, s3Error, r.URL.Path)
return
}
@ -269,7 +269,7 @@ func (api objectAPIHandlers) GetBucketPolicyHandler(w http.ResponseWriter, r *ht
writeErrorResponse(w, r, ErrAccessDenied, r.URL.Path)
return
case authTypePresigned, authTypeSigned:
if s3Error := isReqAuthenticated(r); s3Error != ErrNone {
if s3Error := isReqAuthenticated(r, serverConfig.GetRegion()); s3Error != ErrNone {
writeErrorResponse(w, r, s3Error, r.URL.Path)
return
}

@ -33,34 +33,28 @@ func isValidLocationConstraint(r *http.Request) (s3Error APIErrorCode) {
// If the request has no body with content-length set to 0,
// we do not have to validate location constraint. Bucket will
// be created at default region.
if r.ContentLength == 0 {
return ErrNone
}
locationConstraint := createBucketLocationConfiguration{}
if err := xmlDecoder(r.Body, &locationConstraint, r.ContentLength); err != nil {
if err == io.EOF && r.ContentLength == -1 {
// EOF is a valid condition here when ContentLength is -1.
return ErrNone
err := xmlDecoder(r.Body, &locationConstraint, r.ContentLength)
if err == nil || err == io.EOF {
// Successfully decoded, proceed to verify the region.
// Once region has been obtained we proceed to verify it.
incomingRegion := locationConstraint.Location
if incomingRegion == "" {
// Location constraint is empty for region "us-east-1",
// in accordance with protocol.
incomingRegion = "us-east-1"
}
errorIf(err, "Unable to xml decode location constraint")
// Treat all other failures as XML parsing errors.
return ErrMalformedXML
} // Successfully decoded, proceed to verify the region.
// Once region has been obtained we proceed to verify it.
incomingRegion := locationConstraint.Location
if incomingRegion == "" {
// Location constraint is empty for region "us-east-1",
// in accordance with protocol.
incomingRegion = "us-east-1"
}
// Return errInvalidRegion if location constraint does not match
// with configured region.
s3Error = ErrNone
if serverRegion != incomingRegion {
s3Error = ErrInvalidRegion
// Return errInvalidRegion if location constraint does not match
// with configured region.
s3Error = ErrNone
if serverRegion != incomingRegion {
s3Error = ErrInvalidRegion
}
return s3Error
}
return s3Error
errorIf(err, "Unable to xml decode location constraint")
// Treat all other failures as XML parsing errors.
return ErrMalformedXML
}
// Supported headers that needs to be extracted.

@ -111,7 +111,7 @@ func (api objectAPIHandlers) GetObjectHandler(w http.ResponseWriter, r *http.Req
return
}
case authTypePresigned, authTypeSigned:
if s3Error := isReqAuthenticated(r); s3Error != ErrNone {
if s3Error := isReqAuthenticated(r, serverConfig.GetRegion()); s3Error != ErrNone {
errorIf(errSignatureMismatch, dumpRequest(r))
writeErrorResponse(w, r, s3Error, r.URL.Path)
return
@ -223,7 +223,7 @@ func (api objectAPIHandlers) HeadObjectHandler(w http.ResponseWriter, r *http.Re
return
}
case authTypePresigned, authTypeSigned:
if s3Error := isReqAuthenticated(r); s3Error != ErrNone {
if s3Error := isReqAuthenticated(r, serverConfig.GetRegion()); s3Error != ErrNone {
errorIf(errSignatureMismatch, dumpRequest(r))
writeErrorResponse(w, r, s3Error, r.URL.Path)
return
@ -280,7 +280,7 @@ func (api objectAPIHandlers) CopyObjectHandler(w http.ResponseWriter, r *http.Re
return
}
case authTypePresigned, authTypeSigned:
if s3Error := isReqAuthenticated(r); s3Error != ErrNone {
if s3Error := isReqAuthenticated(r, serverConfig.GetRegion()); s3Error != ErrNone {
errorIf(errSignatureMismatch, dumpRequest(r))
writeErrorResponse(w, r, s3Error, r.URL.Path)
return
@ -518,7 +518,7 @@ func (api objectAPIHandlers) NewMultipartUploadHandler(w http.ResponseWriter, r
return
}
case authTypePresigned, authTypeSigned:
if s3Error := isReqAuthenticated(r); s3Error != ErrNone {
if s3Error := isReqAuthenticated(r, serverConfig.GetRegion()); s3Error != ErrNone {
errorIf(errSignatureMismatch, dumpRequest(r))
writeErrorResponse(w, r, s3Error, r.URL.Path)
return
@ -667,7 +667,7 @@ func (api objectAPIHandlers) AbortMultipartUploadHandler(w http.ResponseWriter,
return
}
case authTypePresigned, authTypeSigned:
if s3Error := isReqAuthenticated(r); s3Error != ErrNone {
if s3Error := isReqAuthenticated(r, serverConfig.GetRegion()); s3Error != ErrNone {
errorIf(errSignatureMismatch, dumpRequest(r))
writeErrorResponse(w, r, s3Error, r.URL.Path)
return
@ -707,7 +707,7 @@ func (api objectAPIHandlers) ListObjectPartsHandler(w http.ResponseWriter, r *ht
return
}
case authTypePresigned, authTypeSigned:
if s3Error := isReqAuthenticated(r); s3Error != ErrNone {
if s3Error := isReqAuthenticated(r, serverConfig.GetRegion()); s3Error != ErrNone {
errorIf(errSignatureMismatch, dumpRequest(r))
writeErrorResponse(w, r, s3Error, r.URL.Path)
return
@ -765,7 +765,7 @@ func (api objectAPIHandlers) CompleteMultipartUploadHandler(w http.ResponseWrite
return
}
case authTypePresigned, authTypeSigned:
if s3Error := isReqAuthenticated(r); s3Error != ErrNone {
if s3Error := isReqAuthenticated(r, serverConfig.GetRegion()); s3Error != ErrNone {
errorIf(errSignatureMismatch, dumpRequest(r))
writeErrorResponse(w, r, s3Error, r.URL.Path)
return
@ -874,7 +874,7 @@ func (api objectAPIHandlers) DeleteObjectHandler(w http.ResponseWriter, r *http.
return
}
case authTypeSigned, authTypePresigned:
if s3Error := isReqAuthenticated(r); s3Error != ErrNone {
if s3Error := isReqAuthenticated(r, serverConfig.GetRegion()); s3Error != ErrNone {
errorIf(errSignatureMismatch, dumpRequest(r))
writeErrorResponse(w, r, s3Error, r.URL.Path)
return

@ -194,13 +194,10 @@ func doesPolicySignatureMatch(formValues map[string]string) APIErrorCode {
// doesPresignedSignatureMatch - Verify query headers with presigned signature
// - http://docs.aws.amazon.com/AmazonS3/latest/API/sigv4-query-string-auth.html
// returns true if matches, false otherwise. if error is not nil then it is always false
func doesPresignedSignatureMatch(hashedPayload string, r *http.Request, validateRegion bool) APIErrorCode {
func doesPresignedSignatureMatch(hashedPayload string, r *http.Request, region string) APIErrorCode {
// Access credentials.
cred := serverConfig.GetCredential()
// Server region.
region := serverConfig.GetRegion()
// Copy request
req := *r
@ -223,15 +220,13 @@ func doesPresignedSignatureMatch(hashedPayload string, r *http.Request, validate
// Verify if region is valid.
sRegion := pSignValues.Credential.scope.region
// Should validate region, only if region is set. Some operations
// do not need region validated for example GetBucketLocation.
if validateRegion {
if !isValidRegion(sRegion, region) {
return ErrInvalidRegion
}
} else {
// Should validate region, only if region is set.
if region == "" {
region = sRegion
}
if !isValidRegion(sRegion, region) {
return ErrInvalidRegion
}
// Extract all the signed headers along with its values.
extractedSignedHeaders, errCode := extractSignedHeaders(pSignValues.SignedHeaders, req.Header)
@ -322,13 +317,10 @@ func doesPresignedSignatureMatch(hashedPayload string, r *http.Request, validate
// doesSignatureMatch - Verify authorization header with calculated header in accordance with
// - http://docs.aws.amazon.com/AmazonS3/latest/API/sig-v4-authenticating-requests.html
// returns true if matches, false otherwise. if error is not nil then it is always false
func doesSignatureMatch(hashedPayload string, r *http.Request, validateRegion bool) APIErrorCode {
func doesSignatureMatch(hashedPayload string, r *http.Request, region string) APIErrorCode {
// Access credentials.
cred := serverConfig.GetCredential()
// Server region.
region := serverConfig.GetRegion()
// Copy request.
req := *r
@ -372,14 +364,17 @@ func doesSignatureMatch(hashedPayload string, r *http.Request, validateRegion bo
// Verify if region is valid.
sRegion := signV4Values.Credential.scope.region
// Should validate region, only if region is set. Some operations
// do not need region validated for example GetBucketLocation.
if validateRegion {
if !isValidRegion(sRegion, region) {
return ErrInvalidRegion
}
// Region is set to be empty, we use whatever was sent by the
// request and proceed further. This is a work-around to address
// an important problem for ListBuckets() getting signed with
// different regions.
if region == "" {
region = sRegion
}
// Should validate region, only if region is set.
if !isValidRegion(sRegion, region) {
return ErrInvalidRegion
}
region = sRegion
// Extract date, if not present throw error.
var date string

@ -106,15 +106,15 @@ func TestDoesPresignedSignatureMatch(t *testing.T) {
credentialTemplate := "%s/%s/%s/s3/aws4_request"
testCases := []struct {
queryParams map[string]string
headers map[string]string
verifyRegion bool
expected APIErrorCode
queryParams map[string]string
headers map[string]string
region string
expected APIErrorCode
}{
// (0) Should error without a set URL query.
{
verifyRegion: false,
expected: ErrInvalidQueryParams,
region: "us-east-1",
expected: ErrInvalidQueryParams,
},
// (1) Should error on an invalid access key.
{
@ -126,8 +126,8 @@ func TestDoesPresignedSignatureMatch(t *testing.T) {
"X-Amz-SignedHeaders": "host;x-amz-content-sha256;x-amz-date",
"X-Amz-Credential": fmt.Sprintf(credentialTemplate, "Z7IXGOO6BZ0REAN1Q26I", now.Format(yyyymmdd), "us-west-1"),
},
verifyRegion: false,
expected: ErrInvalidAccessKeyID,
region: "us-west-1",
expected: ErrInvalidAccessKeyID,
},
// (2) Should error when the payload sha256 doesn't match.
{
@ -140,8 +140,8 @@ func TestDoesPresignedSignatureMatch(t *testing.T) {
"X-Amz-Credential": fmt.Sprintf(credentialTemplate, serverConfig.GetCredential().AccessKeyID, now.Format(yyyymmdd), "us-west-1"),
"X-Amz-Content-Sha256": "ThisIsNotThePayloadHash",
},
verifyRegion: false,
expected: ErrContentSHA256Mismatch,
region: "us-west-1",
expected: ErrContentSHA256Mismatch,
},
// (3) Should fail with an invalid region.
{
@ -154,8 +154,8 @@ func TestDoesPresignedSignatureMatch(t *testing.T) {
"X-Amz-Credential": fmt.Sprintf(credentialTemplate, serverConfig.GetCredential().AccessKeyID, now.Format(yyyymmdd), "us-west-1"),
"X-Amz-Content-Sha256": payload,
},
verifyRegion: true,
expected: ErrInvalidRegion,
region: "us-east-1",
expected: ErrInvalidRegion,
},
// (4) Should NOT fail with an invalid region if it doesn't verify it.
{
@ -168,8 +168,8 @@ func TestDoesPresignedSignatureMatch(t *testing.T) {
"X-Amz-Credential": fmt.Sprintf(credentialTemplate, serverConfig.GetCredential().AccessKeyID, now.Format(yyyymmdd), "us-west-1"),
"X-Amz-Content-Sha256": payload,
},
verifyRegion: false,
expected: ErrUnsignedHeaders,
region: "us-west-1",
expected: ErrUnsignedHeaders,
},
// (5) Should fail to extract headers if the host header is not signed.
{
@ -182,8 +182,8 @@ func TestDoesPresignedSignatureMatch(t *testing.T) {
"X-Amz-Credential": fmt.Sprintf(credentialTemplate, serverConfig.GetCredential().AccessKeyID, now.Format(yyyymmdd), serverConfig.GetRegion()),
"X-Amz-Content-Sha256": payload,
},
verifyRegion: true,
expected: ErrUnsignedHeaders,
region: serverConfig.GetRegion(),
expected: ErrUnsignedHeaders,
},
// (6) Should give an expired request if it has expired.
{
@ -200,8 +200,8 @@ func TestDoesPresignedSignatureMatch(t *testing.T) {
"X-Amz-Date": now.AddDate(0, 0, -2).Format(iso8601Format),
"X-Amz-Content-Sha256": payload,
},
verifyRegion: false,
expected: ErrExpiredPresignRequest,
region: serverConfig.GetRegion(),
expected: ErrExpiredPresignRequest,
},
// (7) Should error if the signature is incorrect.
{
@ -218,8 +218,8 @@ func TestDoesPresignedSignatureMatch(t *testing.T) {
"X-Amz-Date": now.Format(iso8601Format),
"X-Amz-Content-Sha256": payload,
},
verifyRegion: false,
expected: ErrSignatureDoesNotMatch,
region: serverConfig.GetRegion(),
expected: ErrSignatureDoesNotMatch,
},
}
@ -243,7 +243,7 @@ func TestDoesPresignedSignatureMatch(t *testing.T) {
}
// Check if it matches!
err := doesPresignedSignatureMatch(payload, req, testCase.verifyRegion)
err := doesPresignedSignatureMatch(payload, req, testCase.region)
if err != testCase.expected {
t.Errorf("(%d) expected to get %s, instead got %s", i, niceError(testCase.expected), niceError(err))
}

@ -19,10 +19,11 @@ package cmd
import (
"encoding/hex"
"fmt"
"github.com/minio/sha256-simd"
"hash"
"io"
"net/http"
"github.com/minio/sha256-simd"
)
// signVerifyReader represents an io.Reader compatible interface which
@ -49,7 +50,7 @@ func isSignVerify(reader io.Reader) bool {
// Verify - verifies signature and returns error upon signature mismatch.
func (v *signVerifyReader) Verify() error {
validateRegion := true // Defaults to validating region.
region := serverConfig.GetRegion()
shaPayloadHex := hex.EncodeToString(v.HashWriter.Sum(nil))
if skipContentSha256Cksum(v.Request) {
// Sets 'UNSIGNED-PAYLOAD' if client requested to not calculated sha256.
@ -58,9 +59,9 @@ func (v *signVerifyReader) Verify() error {
// Signature verification block.
var s3Error APIErrorCode
if isRequestSignatureV4(v.Request) {
s3Error = doesSignatureMatch(shaPayloadHex, v.Request, validateRegion)
s3Error = doesSignatureMatch(shaPayloadHex, v.Request, region)
} else if isRequestPresignedSignatureV4(v.Request) {
s3Error = doesPresignedSignatureMatch(shaPayloadHex, v.Request, validateRegion)
s3Error = doesPresignedSignatureMatch(shaPayloadHex, v.Request, region)
} else {
// Couldn't figure out the request type, set the error as AccessDenied.
s3Error = ErrAccessDenied

Loading…
Cancel
Save