signature: Add legacy signature v2 support transparently. (#2811)

Add new tests as well.
This commit is contained in:
Harshavardhana 2016-09-30 14:32:13 -07:00 committed by GitHub
parent 9fb1c89f81
commit 5885ffc8ae
20 changed files with 3619 additions and 340 deletions

View File

@ -42,12 +42,24 @@ func isRequestSignatureV4(r *http.Request) bool {
return strings.HasPrefix(r.Header.Get("Authorization"), signV4Algorithm)
}
// Verify if request has AWS Signature Version '2'.
func isRequestSignatureV2(r *http.Request) bool {
return (!strings.HasPrefix(r.Header.Get("Authorization"), signV4Algorithm) &&
strings.HasPrefix(r.Header.Get("Authorization"), signV2Algorithm))
}
// Verify if request has AWS PreSign Version '4'.
func isRequestPresignedSignatureV4(r *http.Request) bool {
_, ok := r.URL.Query()["X-Amz-Credential"]
return ok
}
// Verify request has AWS PreSign Version '2'.
func isRequestPresignedSignatureV2(r *http.Request) bool {
_, ok := r.URL.Query()["AWSAccessKeyId"]
return ok
}
// Verify if request has AWS Post policy Signature Version '4'.
func isRequestPostPolicySignatureV4(r *http.Request) bool {
return strings.Contains(r.Header.Get("Content-Type"), "multipart/form-data") && r.Method == "POST"
@ -66,15 +78,21 @@ const (
authTypeUnknown authType = iota
authTypeAnonymous
authTypePresigned
authTypePresignedV2
authTypePostPolicy
authTypeStreamingSigned
authTypeSigned
authTypeSignedV2
authTypeJWT
)
// Get request authentication type.
func getRequestAuthType(r *http.Request) authType {
if isRequestSignStreamingV4(r) {
if isRequestSignatureV2(r) {
return authTypeSignedV2
} else if isRequestPresignedSignatureV2(r) {
return authTypePresignedV2
} else if isRequestSignStreamingV4(r) {
return authTypeStreamingSigned
} else if isRequestSignatureV4(r) {
return authTypeSigned
@ -104,6 +122,14 @@ func sumMD5(data []byte) []byte {
return hash.Sum(nil)
}
// Verify if request has valid AWS Signature Version '2'.
func isReqAuthenticatedV2(r *http.Request) (s3Error APIErrorCode) {
if isRequestSignatureV2(r) {
return doesSignV2Match(r)
}
return doesPresignV2SignatureMatch(r)
}
// Verify if request has valid AWS Signature Version '4'.
func isReqAuthenticated(r *http.Request, region string) (s3Error APIErrorCode) {
if r == nil {
@ -111,6 +137,7 @@ func isReqAuthenticated(r *http.Request, region string) (s3Error APIErrorCode) {
}
payload, err := ioutil.ReadAll(r.Body)
if err != nil {
errorIf(err, "Unable to read request body for signature verification")
return ErrInternalError
}
// Verify Content-Md5, if payload is set.
@ -144,7 +171,6 @@ func isReqAuthenticated(r *http.Request, region string) (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())
}
@ -152,11 +178,14 @@ func checkAuth(r *http.Request) APIErrorCode {
func checkAuthWithRegion(r *http.Request, region string) APIErrorCode {
// Validates the request for both Presigned and Signed.
aType := getRequestAuthType(r)
if aType != authTypePresigned && aType != authTypeSigned {
switch aType {
case authTypeSignedV2, authTypePresignedV2: // Signature V2.
return isReqAuthenticatedV2(r)
case authTypeSigned, authTypePresigned: // Signature V4.
return isReqAuthenticated(r, region)
}
// For all unhandled auth types return error AccessDenied.
return ErrAccessDenied
}
return isReqAuthenticated(r, region)
}
// authHandler - handles all the incoming authorization headers and validates them if possible.
@ -173,7 +202,9 @@ func setAuthHandler(h http.Handler) http.Handler {
var supportedS3AuthTypes = map[authType]struct{}{
authTypeAnonymous: {},
authTypePresigned: {},
authTypePresignedV2: {},
authTypeSigned: {},
authTypeSignedV2: {},
authTypePostPolicy: {},
authTypeStreamingSigned: {},
}

View File

@ -151,19 +151,29 @@ func TestS3SupportedAuthType(t *testing.T) {
authT: authTypeStreamingSigned,
pass: true,
},
// Test 6 - JWT is not supported s3 type.
// Test 6 - supported s3 type with signature v2.
{
authT: authTypeSignedV2,
pass: true,
},
// Test 7 - supported s3 type with presign v2.
{
authT: authTypePresignedV2,
pass: true,
},
// Test 8 - JWT is not supported s3 type.
{
authT: authTypeJWT,
pass: false,
},
// Test 7 - unknown auth header is not supported s3 type.
// Test 9 - unknown auth header is not supported s3 type.
{
authT: authTypeUnknown,
pass: false,
},
// Test 8 - some new auth type is not supported s3 type.
// Test 10 - some new auth type is not supported s3 type.
{
authT: authType(7),
authT: authType(9),
pass: false,
},
}
@ -210,6 +220,39 @@ func TestIsRequestUnsignedPayload(t *testing.T) {
}
}
func TestIsRequestPresignedSignatureV2(t *testing.T) {
testCases := []struct {
inputQueryKey string
inputQueryValue string
expectedResult bool
}{
// Test case - 1.
// Test case with query key "AWSAccessKeyId" set.
{"", "", false},
// Test case - 2.
{"AWSAccessKeyId", "", true},
// Test case - 3.
{"X-Amz-Content-Sha256", "", false},
}
for i, testCase := range testCases {
// creating an input HTTP request.
// Only the query parameters are relevant for this particular test.
inputReq, err := http.NewRequest("GET", "http://example.com", nil)
if err != nil {
t.Fatalf("Error initializing input HTTP request: %v", err)
}
q := inputReq.URL.Query()
q.Add(testCase.inputQueryKey, testCase.inputQueryValue)
inputReq.URL.RawQuery = q.Encode()
actualResult := isRequestPresignedSignatureV2(inputReq)
if testCase.expectedResult != actualResult {
t.Errorf("Test %d: Expected the result to `%v`, but instead got `%v`", i+1, testCase.expectedResult, actualResult)
}
}
}
// TestIsRequestPresignedSignatureV4 - Test validates the logic for presign signature verision v4 detection.
func TestIsRequestPresignedSignatureV4(t *testing.T) {
testCases := []struct {
@ -258,7 +301,7 @@ func mustNewRequest(method string, urlStr string, contentLength int64, body io.R
func mustNewSignedRequest(method string, urlStr string, contentLength int64, body io.ReadSeeker, t *testing.T) *http.Request {
req := mustNewRequest(method, urlStr, contentLength, body, t)
cred := serverConfig.GetCredential()
if err := signRequest(req, cred.AccessKeyID, cred.SecretAccessKey); err != nil {
if err := signRequestV4(req, cred.AccessKeyID, cred.SecretAccessKey); err != nil {
t.Fatalf("Unable to inititalized new signed http request %s", err)
}
return req

View File

@ -81,6 +81,13 @@ func (api objectAPIHandlers) ListObjectsV2Handler(w http.ResponseWriter, r *http
writeErrorResponse(w, r, s3Error, r.URL.Path)
return
}
case authTypePresignedV2, authTypeSignedV2:
// Signature V2 validation.
if s3Error := isReqAuthenticatedV2(r); s3Error != ErrNone {
errorIf(errSignatureMismatch, dumpRequest(r))
writeErrorResponse(w, r, s3Error, r.URL.Path)
return
}
case authTypeSigned, authTypePresigned:
if s3Error := isReqAuthenticated(r, serverConfig.GetRegion()); s3Error != ErrNone {
errorIf(errSignatureMismatch, dumpRequest(r))
@ -148,6 +155,13 @@ func (api objectAPIHandlers) ListObjectsV1Handler(w http.ResponseWriter, r *http
writeErrorResponse(w, r, s3Error, r.URL.Path)
return
}
case authTypePresignedV2, authTypeSignedV2:
// Signature V2 validation.
if s3Error := isReqAuthenticatedV2(r); s3Error != ErrNone {
errorIf(errSignatureMismatch, dumpRequest(r))
writeErrorResponse(w, r, s3Error, r.URL.Path)
return
}
case authTypeSigned, authTypePresigned:
if s3Error := isReqAuthenticated(r, serverConfig.GetRegion()); s3Error != ErrNone {
errorIf(errSignatureMismatch, dumpRequest(r))

View File

@ -93,6 +93,13 @@ func (api objectAPIHandlers) GetBucketLocationHandler(w http.ResponseWriter, r *
writeErrorResponse(w, r, s3Error, r.URL.Path)
return
}
case authTypePresignedV2, authTypeSignedV2:
// Signature V2 validation.
if s3Error := isReqAuthenticatedV2(r); s3Error != ErrNone {
errorIf(errSignatureMismatch, dumpRequest(r))
writeErrorResponse(w, r, s3Error, r.URL.Path)
return
}
case authTypeSigned, authTypePresigned:
if s3Error := isReqAuthenticated(r, "us-east-1"); s3Error != ErrNone {
errorIf(errSignatureMismatch, dumpRequest(r))
@ -149,6 +156,13 @@ func (api objectAPIHandlers) ListMultipartUploadsHandler(w http.ResponseWriter,
writeErrorResponse(w, r, s3Error, r.URL.Path)
return
}
case authTypePresignedV2, authTypeSignedV2:
// Signature V2 validation.
if s3Error := isReqAuthenticatedV2(r); s3Error != ErrNone {
errorIf(errSignatureMismatch, dumpRequest(r))
writeErrorResponse(w, r, s3Error, r.URL.Path)
return
}
case authTypePresigned, authTypeSigned:
if s3Error := isReqAuthenticated(r, serverConfig.GetRegion()); s3Error != ErrNone {
errorIf(errSignatureMismatch, dumpRequest(r))
@ -198,6 +212,7 @@ func (api objectAPIHandlers) ListBucketsHandler(w http.ResponseWriter, r *http.R
// List buckets does not support bucket policies, no need to enforce it.
// Proceed to validate signature.
// Validates the request for both Presigned and Signed.
if s3Error := checkAuthWithRegion(r, ""); s3Error != ErrNone {
errorIf(errSignatureMismatch, dumpRequest(r))
writeErrorResponse(w, r, s3Error, r.URL.Path)
@ -243,6 +258,13 @@ func (api objectAPIHandlers) DeleteMultipleObjectsHandler(w http.ResponseWriter,
writeErrorResponse(w, r, s3Error, r.URL.Path)
return
}
case authTypePresignedV2, authTypeSignedV2:
// Signature V2 validation.
if s3Error := isReqAuthenticatedV2(r); s3Error != ErrNone {
errorIf(errSignatureMismatch, dumpRequest(r))
writeErrorResponse(w, r, s3Error, r.URL.Path)
return
}
case authTypePresigned, authTypeSigned:
if s3Error := isReqAuthenticated(r, serverConfig.GetRegion()); s3Error != ErrNone {
errorIf(errSignatureMismatch, dumpRequest(r))
@ -490,6 +512,13 @@ func (api objectAPIHandlers) HeadBucketHandler(w http.ResponseWriter, r *http.Re
writeErrorResponse(w, r, s3Error, r.URL.Path)
return
}
case authTypePresignedV2, authTypeSignedV2:
// Signature V2 validation.
if s3Error := isReqAuthenticatedV2(r); s3Error != ErrNone {
errorIf(errSignatureMismatch, dumpRequest(r))
writeErrorResponse(w, r, s3Error, r.URL.Path)
return
}
case authTypePresigned, authTypeSigned:
if s3Error := isReqAuthenticated(r, serverConfig.GetRegion()); s3Error != ErrNone {
errorIf(errSignatureMismatch, dumpRequest(r))

View File

@ -94,7 +94,7 @@ func testGetBucketLocationHandler(obj ObjectLayer, instanceType string, t TestEr
// initialize HTTP NewRecorder, this records any mutations to response writer inside the handler.
rec := httptest.NewRecorder()
// construct HTTP request for Get bucket location.
req, err := newTestSignedRequest("GET", getBucketLocationURL("", testCase.bucketName), 0, nil, testCase.accessKey, testCase.secretKey)
req, err := newTestSignedRequestV4("GET", getBucketLocationURL("", testCase.bucketName), 0, nil, testCase.accessKey, testCase.secretKey)
if err != nil {
t.Fatalf("Test %d: %s: Failed to create HTTP request for GetBucketLocationHandler: <ERROR> %v", i+1, instanceType, err)
}
@ -187,7 +187,7 @@ func testHeadBucketHandler(obj ObjectLayer, instanceType string, t TestErrHandle
// initialize HTTP NewRecorder, this records any mutations to response writer inside the handler.
rec := httptest.NewRecorder()
// construct HTTP request for HEAD bucket.
req, err := newTestSignedRequest("HEAD", getHEADBucketURL("", testCase.bucketName), 0, nil, testCase.accessKey, testCase.secretKey)
req, err := newTestSignedRequestV4("HEAD", getHEADBucketURL("", testCase.bucketName), 0, nil, testCase.accessKey, testCase.secretKey)
if err != nil {
t.Fatalf("Test %d: %s: Failed to create HTTP request for HeadBucketHandler: <ERROR> %v", i+1, instanceType, err)
}
@ -272,7 +272,7 @@ func testListMultipartUploadsHandler(obj ObjectLayer, instanceType string, t Tes
// construct HTTP request for List multipart uploads endpoint.
u := getListMultipartUploadsURLWithParams("", testCase.bucket, testCase.prefix, testCase.keyMarker, testCase.uploadIDMarker, testCase.delimiter, testCase.maxUploads)
req, gerr := newTestSignedRequest("GET", u, 0, nil, credentials.AccessKeyID, credentials.SecretAccessKey)
req, gerr := newTestSignedRequestV4("GET", u, 0, nil, credentials.AccessKeyID, credentials.SecretAccessKey)
if gerr != nil {
t.Fatalf("Test %d: %s: Failed to create HTTP request for ListMultipartUploadsHandler: <ERROR> %v", i+1, instanceType, gerr)
}
@ -289,7 +289,7 @@ func testListMultipartUploadsHandler(obj ObjectLayer, instanceType string, t Tes
// construct HTTP request for List multipart uploads endpoint.
u := getListMultipartUploadsURLWithParams("", bucketName, "", "", "", "", "")
req, err := newTestSignedRequest("GET", u, 0, nil, "", "") // Generate an anonymous request.
req, err := newTestSignedRequestV4("GET", u, 0, nil, "", "") // Generate an anonymous request.
if err != nil {
t.Fatalf("Test %s: Failed to create HTTP request for ListMultipartUploadsHandler: <ERROR> %v", instanceType, err)
}
@ -359,7 +359,7 @@ func testListBucketsHandler(obj ObjectLayer, instanceType string, t TestErrHandl
for i, testCase := range testCases {
// initialize HTTP NewRecorder, this records any mutations to response writer inside the handler.
rec := httptest.NewRecorder()
req, lerr := newTestSignedRequest("GET", getListBucketURL(""), 0, nil, testCase.accessKey, testCase.secretKey)
req, lerr := newTestSignedRequestV4("GET", getListBucketURL(""), 0, nil, testCase.accessKey, testCase.secretKey)
if lerr != nil {
t.Fatalf("Test %d: %s: Failed to create HTTP request for ListBucketsHandler: <ERROR> %v", i+1, instanceType, lerr)
}

View File

@ -221,7 +221,7 @@ func testGetBucketNotificationHandler(obj ObjectLayer, instanceType string, t Te
rec := httptest.NewRecorder()
// Prepare notification config for one of the test cases.
req, err := newTestSignedRequest("PUT", getPutBucketNotificationURL("", randBucket),
req, err := newTestSignedRequestV4("PUT", getPutBucketNotificationURL("", randBucket),
int64(len(sampleNotificationBytes)), bytes.NewReader(sampleNotificationBytes),
credentials.AccessKeyID, credentials.SecretAccessKey)
if err != nil {
@ -251,7 +251,7 @@ func testGetBucketNotificationHandler(obj ObjectLayer, instanceType string, t Te
signatureMismatchCode := getAPIError(ErrContentSHA256Mismatch).Code
for i, test := range testCases {
testRec := httptest.NewRecorder()
testReq, tErr := newTestSignedRequest("GET", getGetBucketNotificationURL("", test.bucketName),
testReq, tErr := newTestSignedRequestV4("GET", getGetBucketNotificationURL("", test.bucketName),
int64(0), nil, credentials.AccessKeyID, credentials.SecretAccessKey)
if tErr != nil {
t.Fatalf("Test %d: %s: Failed to create HTTP testRequest for GetBucketNotification: <ERROR> %v",
@ -308,7 +308,7 @@ func testGetBucketNotificationHandler(obj ObjectLayer, instanceType string, t Te
"PutBucketNotificationHandler",
})
testRec := httptest.NewRecorder()
testReq, tErr := newTestSignedRequest("GET", getGetBucketNotificationURL("", randBucket),
testReq, tErr := newTestSignedRequestV4("GET", getGetBucketNotificationURL("", randBucket),
int64(0), nil, credentials.AccessKeyID, credentials.SecretAccessKey)
if tErr != nil {
t.Fatalf("Test %d: %s: Failed to create HTTP testRequest for GetBucketNotification: <ERROR> %v",
@ -391,7 +391,7 @@ func testPutBucketNotificationHandler(obj ObjectLayer, instanceType string, t Te
}
for i, test := range testCases {
testRec := httptest.NewRecorder()
testReq, tErr := newTestSignedRequest("PUT", getPutBucketNotificationURL("", test.bucketName),
testReq, tErr := newTestSignedRequestV4("PUT", getPutBucketNotificationURL("", test.bucketName),
int64(len(test.expectedNotificationBytes)), bytes.NewReader(test.expectedNotificationBytes),
credentials.AccessKeyID, credentials.SecretAccessKey)
if tErr != nil {
@ -416,7 +416,7 @@ func testPutBucketNotificationHandler(obj ObjectLayer, instanceType string, t Te
switch test.kind {
case CompareBytes:
testReq, tErr = newTestSignedRequest("GET", getGetBucketNotificationURL("", test.bucketName),
testReq, tErr = newTestSignedRequestV4("GET", getGetBucketNotificationURL("", test.bucketName),
int64(0), nil, credentials.AccessKeyID, credentials.SecretAccessKey)
if tErr != nil {
t.Fatalf("Test %d: %s: Failed to create HTTP testRequest for GetBucketNotification: <ERROR> %v",
@ -464,7 +464,7 @@ func testPutBucketNotificationHandler(obj ObjectLayer, instanceType string, t Te
"PutBucketNotificationHandler",
})
testRec := httptest.NewRecorder()
testReq, tErr := newTestSignedRequest("PUT", getPutBucketNotificationURL("", randBucket),
testReq, tErr := newTestSignedRequestV4("PUT", getPutBucketNotificationURL("", randBucket),
int64(len(sampleNotificationBytes)), bytes.NewReader(sampleNotificationBytes),
credentials.AccessKeyID, credentials.SecretAccessKey)
if tErr != nil {
@ -526,7 +526,7 @@ func testListenBucketNotificationHandler(obj ObjectLayer, instanceType string, t
instanceType, err)
}
testRec := httptest.NewRecorder()
testReq, tErr := newTestSignedRequest("PUT", getPutBucketNotificationURL("", randBucket),
testReq, tErr := newTestSignedRequestV4("PUT", getPutBucketNotificationURL("", randBucket),
int64(len(sampleNotificationBytes)), bytes.NewReader(sampleNotificationBytes),
credentials.AccessKeyID, credentials.SecretAccessKey)
if tErr != nil {
@ -562,7 +562,7 @@ func testListenBucketNotificationHandler(obj ObjectLayer, instanceType string, t
for i, test := range testCases {
testRec = httptest.NewRecorder()
testReq, tErr = newTestSignedRequest("GET",
testReq, tErr = newTestSignedRequestV4("GET",
getListenBucketNotificationURL("", test.bucketName, test.prefix, test.suffix, test.events),
0, nil, credentials.AccessKeyID, credentials.SecretAccessKey)
if tErr != nil {
@ -611,7 +611,7 @@ func testListenBucketNotificationHandler(obj ObjectLayer, instanceType string, t
"ListenBucketNotificationHandler",
})
testRec = httptest.NewRecorder()
testReq, tErr = newTestSignedRequest("GET",
testReq, tErr = newTestSignedRequestV4("GET",
getListenBucketNotificationURL("", randBucket, "", "*.jpg", []string{"s3:ObjectCreated:*", "s3:ObjectRemoved:*"}),
0, nil, credentials.AccessKeyID, credentials.SecretAccessKey)
if tErr != nil {
@ -670,7 +670,7 @@ func testRemoveNotificationConfig(obj ObjectLayer, instanceType string, t TestEr
}
// Set sample bucket notification on randBucket.
testRec := httptest.NewRecorder()
testReq, tErr := newTestSignedRequest("PUT", getPutBucketNotificationURL("", randBucket),
testReq, tErr := newTestSignedRequestV4("PUT", getPutBucketNotificationURL("", randBucket),
int64(len(sampleNotificationBytes)), bytes.NewReader(sampleNotificationBytes),
credentials.AccessKeyID, credentials.SecretAccessKey)
if tErr != nil {

View File

@ -132,19 +132,15 @@ func (api objectAPIHandlers) PutBucketPolicyHandler(w http.ResponseWriter, r *ht
return
}
vars := mux.Vars(r)
bucket := vars["bucket"]
switch getRequestAuthType(r) {
default:
// For all unknown auth types return error.
writeErrorResponse(w, r, ErrAccessDenied, r.URL.Path)
return
case authTypePresigned, authTypeSigned:
if s3Error := isReqAuthenticated(r, serverConfig.GetRegion()); s3Error != ErrNone {
// PutBucketPolicy does not support bucket policies, use checkAuth to validate signature.
if s3Error := checkAuth(r); s3Error != ErrNone {
errorIf(errSignatureMismatch, dumpRequest(r))
writeErrorResponse(w, r, s3Error, r.URL.Path)
return
}
}
vars := mux.Vars(r)
bucket := vars["bucket"]
// If Content-Length is unknown or zero, deny the
// request. PutBucketPolicy always needs a Content-Length if
@ -214,20 +210,15 @@ func (api objectAPIHandlers) DeleteBucketPolicyHandler(w http.ResponseWriter, r
return
}
vars := mux.Vars(r)
bucket := vars["bucket"]
switch getRequestAuthType(r) {
default:
// For all unknown auth types return error.
writeErrorResponse(w, r, ErrAccessDenied, r.URL.Path)
return
case authTypePresigned, authTypeSigned:
if s3Error := isReqAuthenticated(r, serverConfig.GetRegion()); s3Error != ErrNone {
// DeleteBucketPolicy does not support bucket policies, use checkAuth to validate signature.
if s3Error := checkAuth(r); s3Error != ErrNone {
errorIf(errSignatureMismatch, dumpRequest(r))
writeErrorResponse(w, r, s3Error, r.URL.Path)
return
}
}
vars := mux.Vars(r)
bucket := vars["bucket"]
// Delete bucket access policy.
if err := removeBucketPolicy(bucket, objAPI); err != nil {
@ -260,20 +251,15 @@ func (api objectAPIHandlers) GetBucketPolicyHandler(w http.ResponseWriter, r *ht
return
}
vars := mux.Vars(r)
bucket := vars["bucket"]
switch getRequestAuthType(r) {
default:
// For all unknown auth types return error.
writeErrorResponse(w, r, ErrAccessDenied, r.URL.Path)
return
case authTypePresigned, authTypeSigned:
if s3Error := isReqAuthenticated(r, serverConfig.GetRegion()); s3Error != ErrNone {
// GetBucketPolicy does not support bucket policies, use checkAuth to validate signature.
if s3Error := checkAuth(r); s3Error != ErrNone {
errorIf(errSignatureMismatch, dumpRequest(r))
writeErrorResponse(w, r, s3Error, r.URL.Path)
return
}
}
vars := mux.Vars(r)
bucket := vars["bucket"]
// Read bucket access policy.
policy, err := readBucketPolicy(bucket, objAPI)

View File

@ -289,18 +289,32 @@ func testPutBucketPolicyHandler(obj ObjectLayer, instanceType string, t TestErrH
// obtain the put bucket policy request body.
bucketPolicyStr := fmt.Sprintf(bucketPolicyTemplate, testCase.bucketName, testCase.bucketName)
// initialize HTTP NewRecorder, this records any mutations to response writer inside the handler.
rec := httptest.NewRecorder()
recV4 := httptest.NewRecorder()
// construct HTTP request for PUT bucket policy endpoint.
req, err := newTestSignedRequest("PUT", getPutPolicyURL("", testCase.bucketName),
reqV4, err := newTestSignedRequestV4("PUT", getPutPolicyURL("", testCase.bucketName),
int64(len(bucketPolicyStr)), bytes.NewReader([]byte(bucketPolicyStr)), testCase.accessKey, testCase.secretKey)
if err != nil {
t.Fatalf("Test %d: %s: Failed to create HTTP request for PutBucketPolicyHandler: <ERROR> %v", i+1, instanceType, err)
}
// Since `apiRouter` satisfies `http.Handler` it has a ServeHTTP to execute the logic ofthe handler.
// Call the ServeHTTP to execute the handler.
apiRouter.ServeHTTP(rec, req)
if rec.Code != testCase.expectedRespStatus {
t.Errorf("Test %d: %s: Expected the response status to be `%d`, but instead found `%d`", i+1, instanceType, testCase.expectedRespStatus, rec.Code)
apiRouter.ServeHTTP(recV4, reqV4)
if recV4.Code != testCase.expectedRespStatus {
t.Errorf("Test %d: %s: Expected the response status to be `%d`, but instead found `%d`", i+1, instanceType, testCase.expectedRespStatus, recV4.Code)
}
// initialize HTTP NewRecorder, this records any mutations to response writer inside the handler.
recV2 := httptest.NewRecorder()
// construct HTTP request for PUT bucket policy endpoint.
reqV2, err := newTestSignedRequestV2("PUT", getPutPolicyURL("", testCase.bucketName),
int64(len(bucketPolicyStr)), bytes.NewReader([]byte(bucketPolicyStr)), testCase.accessKey, testCase.secretKey)
if err != nil {
t.Fatalf("Test %d: %s: Failed to create HTTP request for PutBucketPolicyHandler: <ERROR> %v", i+1, instanceType, err)
}
// Since `apiRouter` satisfies `http.Handler` it has a ServeHTTP to execute the logic ofthe handler.
// Call the ServeHTTP to execute the handler.
apiRouter.ServeHTTP(recV2, reqV2)
if recV2.Code != testCase.expectedRespStatus {
t.Errorf("Test %d: %s: Expected the response status to be `%d`, but instead found `%d`", i+1, instanceType, testCase.expectedRespStatus, recV2.Code)
}
}
}
@ -357,18 +371,32 @@ func testGetBucketPolicyHandler(obj ObjectLayer, instanceType string, t TestErrH
// obtain the put bucket policy request body.
bucketPolicyStr := fmt.Sprintf(bucketPolicyTemplate, testPolicy.bucketName, testPolicy.bucketName)
// initialize HTTP NewRecorder, this records any mutations to response writer inside the handler.
rec := httptest.NewRecorder()
recV4 := httptest.NewRecorder()
// construct HTTP request for PUT bucket policy endpoint.
req, err := newTestSignedRequest("PUT", getPutPolicyURL("", testPolicy.bucketName),
reqV4, err := newTestSignedRequestV4("PUT", getPutPolicyURL("", testPolicy.bucketName),
int64(len(bucketPolicyStr)), bytes.NewReader([]byte(bucketPolicyStr)), testPolicy.accessKey, testPolicy.secretKey)
if err != nil {
t.Fatalf("Test %d: Failed to create HTTP request for PutBucketPolicyHandler: <ERROR> %v", i+1, err)
}
// Since `apiRouter` satisfies `http.Handler` it has a ServeHTTP to execute the logic ofthe handler.
// Call the ServeHTTP to execute the handler.
apiRouter.ServeHTTP(rec, req)
if rec.Code != testPolicy.expectedRespStatus {
t.Fatalf("Case %d: Expected the response status to be `%d`, but instead found `%d`", i+1, testPolicy.expectedRespStatus, rec.Code)
apiRouter.ServeHTTP(recV4, reqV4)
if recV4.Code != testPolicy.expectedRespStatus {
t.Fatalf("Case %d: Expected the response status to be `%d`, but instead found `%d`", i+1, testPolicy.expectedRespStatus, recV4.Code)
}
// initialize HTTP NewRecorder, this records any mutations to response writer inside the handler.
recV2 := httptest.NewRecorder()
// construct HTTP request for PUT bucket policy endpoint.
reqV2, err := newTestSignedRequestV2("PUT", getPutPolicyURL("", testPolicy.bucketName),
int64(len(bucketPolicyStr)), bytes.NewReader([]byte(bucketPolicyStr)), testPolicy.accessKey, testPolicy.secretKey)
if err != nil {
t.Fatalf("Test %d: Failed to create HTTP request for PutBucketPolicyHandler: <ERROR> %v", i+1, err)
}
// Since `apiRouter` satisfies `http.Handler` it has a ServeHTTP to execute the logic ofthe handler.
// Call the ServeHTTP to execute the handler.
apiRouter.ServeHTTP(recV2, reqV2)
if recV2.Code != testPolicy.expectedRespStatus {
t.Fatalf("Case %d: Expected the response status to be `%d`, but instead found `%d`", i+1, testPolicy.expectedRespStatus, recV2.Code)
}
}
@ -388,9 +416,9 @@ func testGetBucketPolicyHandler(obj ObjectLayer, instanceType string, t TestErrH
// expected bucket policy json string.
expectedBucketPolicyStr := fmt.Sprintf(testCase.expectedBucketPolicy, testCase.bucketName, testCase.bucketName)
// initialize HTTP NewRecorder, this records any mutations to response writer inside the handler.
rec := httptest.NewRecorder()
recV4 := httptest.NewRecorder()
// construct HTTP request for PUT bucket policy endpoint.
req, err := newTestSignedRequest("GET", getGetPolicyURL("", testCase.bucketName),
reqV4, err := newTestSignedRequestV4("GET", getGetPolicyURL("", testCase.bucketName),
0, nil, testCase.accessKey, testCase.secretKey)
if err != nil {
@ -398,13 +426,37 @@ func testGetBucketPolicyHandler(obj ObjectLayer, instanceType string, t TestErrH
}
// Since `apiRouter` satisfies `http.Handler` it has a ServeHTTP to execute the logic of the handler.
// Call the ServeHTTP to execute the handler, GetBucketPolicyHandler handles the request.
apiRouter.ServeHTTP(rec, req)
apiRouter.ServeHTTP(recV4, reqV4)
// Assert the response code with the expected status.
if rec.Code != testCase.expectedRespStatus {
t.Fatalf("Case %d: Expected the response status to be `%d`, but instead found `%d`", i+1, testCase.expectedRespStatus, rec.Code)
if recV4.Code != testCase.expectedRespStatus {
t.Fatalf("Case %d: Expected the response status to be `%d`, but instead found `%d`", i+1, testCase.expectedRespStatus, recV4.Code)
}
// read the response body.
bucketPolicyReadBuf, err := ioutil.ReadAll(rec.Body)
bucketPolicyReadBuf, err := ioutil.ReadAll(recV4.Body)
if err != nil {
t.Fatalf("Test %d: %s: Failed parsing response body: <ERROR> %v", i+1, instanceType, err)
}
// Verify whether the bucket policy fetched is same as the one inserted.
if expectedBucketPolicyStr != string(bucketPolicyReadBuf) {
t.Errorf("Test %d: %s: Bucket policy differs from expected value.", i+1, instanceType)
}
// initialize HTTP NewRecorder, this records any mutations to response writer inside the handler.
recV2 := httptest.NewRecorder()
// construct HTTP request for PUT bucket policy endpoint.
reqV2, err := newTestSignedRequestV2("GET", getGetPolicyURL("", testCase.bucketName),
0, nil, testCase.accessKey, testCase.secretKey)
if err != nil {
t.Fatalf("Test %d: Failed to create HTTP request for GetBucketPolicyHandler: <ERROR> %v", i+1, err)
}
// Since `apiRouter` satisfies `http.Handler` it has a ServeHTTP to execute the logic of the handler.
// Call the ServeHTTP to execute the handler, GetBucketPolicyHandler handles the request.
apiRouter.ServeHTTP(recV2, reqV2)
// Assert the response code with the expected status.
if recV2.Code != testCase.expectedRespStatus {
t.Fatalf("Case %d: Expected the response status to be `%d`, but instead found `%d`", i+1, testCase.expectedRespStatus, recV2.Code)
}
// read the response body.
bucketPolicyReadBuf, err = ioutil.ReadAll(recV2.Body)
if err != nil {
t.Fatalf("Test %d: %s: Failed parsing response body: <ERROR> %v", i+1, instanceType, err)
}
@ -502,20 +554,21 @@ func testDeleteBucketPolicyHandler(obj ObjectLayer, instanceType string, t TestE
// obtain the put bucket policy request body.
bucketPolicyStr := fmt.Sprintf(bucketPolicyTemplate, testPolicy.bucketName, testPolicy.bucketName)
// initialize HTTP NewRecorder, this records any mutations to response writer inside the handler.
rec := httptest.NewRecorder()
recV4 := httptest.NewRecorder()
// construct HTTP request for PUT bucket policy endpoint.
req, err := newTestSignedRequest("PUT", getPutPolicyURL("", testPolicy.bucketName),
reqV4, err := newTestSignedRequestV4("PUT", getPutPolicyURL("", testPolicy.bucketName),
int64(len(bucketPolicyStr)), bytes.NewReader([]byte(bucketPolicyStr)), testPolicy.accessKey, testPolicy.secretKey)
if err != nil {
t.Fatalf("Test %d: Failed to create HTTP request for PutBucketPolicyHandler: <ERROR> %v", i+1, err)
}
// Since `apiRouter` satisfies `http.Handler` it has a ServeHTTP to execute the logic of the handler.
// Call the ServeHTTP to execute the handler.
apiRouter.ServeHTTP(rec, req)
if rec.Code != testPolicy.expectedRespStatus {
t.Fatalf("Case %d: Expected the response status to be `%d`, but instead found `%d`", i+1, testPolicy.expectedRespStatus, rec.Code)
apiRouter.ServeHTTP(recV4, reqV4)
if recV4.Code != testPolicy.expectedRespStatus {
t.Fatalf("Case %d: Expected the response status to be `%d`, but instead found `%d`", i+1, testPolicy.expectedRespStatus, recV4.Code)
}
}
// testcases with input and expected output for DeleteBucketPolicyHandler.
testCases := []struct {
bucketName string
@ -529,21 +582,58 @@ func testDeleteBucketPolicyHandler(obj ObjectLayer, instanceType string, t TestE
// Iterating over the cases and deleting the bucket policy and then asserting response.
for i, testCase := range testCases {
// initialize HTTP NewRecorder, this records any mutations to response writer inside the handler.
rec := httptest.NewRecorder()
recV4 := httptest.NewRecorder()
// construct HTTP request for Delete bucket policy endpoint.
req, err := newTestSignedRequest("DELETE", getDeletePolicyURL("", testCase.bucketName),
reqV4, err := newTestSignedRequestV4("DELETE", getDeletePolicyURL("", testCase.bucketName),
0, nil, testCase.accessKey, testCase.secretKey)
if err != nil {
t.Fatalf("Test %d: Failed to create HTTP request for GetBucketPolicyHandler: <ERROR> %v", i+1, err)
}
// Since `apiRouter` satisfies `http.Handler` it has a ServeHTTP to execute the logic of the handler.
// Call the ServeHTTP to execute the handler, DeleteBucketPolicyHandler handles the request.
apiRouter.ServeHTTP(rec, req)
apiRouter.ServeHTTP(recV4, reqV4)
// Assert the response code with the expected status.
if rec.Code != testCase.expectedRespStatus {
t.Fatalf("Case %d: Expected the response status to be `%d`, but instead found `%d`", i+1, testCase.expectedRespStatus, rec.Code)
if recV4.Code != testCase.expectedRespStatus {
t.Fatalf("Case %d: Expected the response status to be `%d`, but instead found `%d`", i+1, testCase.expectedRespStatus, recV4.Code)
}
}
// Iterating over the cases and writing the bucket policy.
// its required to write the policies first before running tests on GetBucketPolicy.
for i, testPolicy := range putTestPolicies {
// obtain the put bucket policy request body.
bucketPolicyStr := fmt.Sprintf(bucketPolicyTemplate, testPolicy.bucketName, testPolicy.bucketName)
// initialize HTTP NewRecorder, this records any mutations to response writer inside the handler.
recV2 := httptest.NewRecorder()
// construct HTTP request for PUT bucket policy endpoint.
reqV2, err := newTestSignedRequestV2("PUT", getPutPolicyURL("", testPolicy.bucketName),
int64(len(bucketPolicyStr)), bytes.NewReader([]byte(bucketPolicyStr)), testPolicy.accessKey, testPolicy.secretKey)
if err != nil {
t.Fatalf("Test %d: Failed to create HTTP request for PutBucketPolicyHandler: <ERROR> %v", i+1, err)
}
// Since `apiRouter` satisfies `http.Handler` it has a ServeHTTP to execute the logic of the handler.
// Call the ServeHTTP to execute the handler.
apiRouter.ServeHTTP(recV2, reqV2)
if recV2.Code != testPolicy.expectedRespStatus {
t.Fatalf("Case %d: Expected the response status to be `%d`, but instead found `%d`", i+1, testPolicy.expectedRespStatus, recV2.Code)
}
}
for i, testCase := range testCases {
// initialize HTTP NewRecorder, this records any mutations to response writer inside the handler.
recV2 := httptest.NewRecorder()
// construct HTTP request for Delete bucket policy endpoint.
reqV2, err := newTestSignedRequestV2("DELETE", getDeletePolicyURL("", testCase.bucketName),
0, nil, testCase.accessKey, testCase.secretKey)
if err != nil {
t.Fatalf("Test %d: Failed to create HTTP request for GetBucketPolicyHandler: <ERROR> %v", i+1, err)
}
// Since `apiRouter` satisfies `http.Handler` it has a ServeHTTP to execute the logic of the handler.
// Call the ServeHTTP to execute the handler, DeleteBucketPolicyHandler handles the request.
apiRouter.ServeHTTP(recV2, reqV2)
// Assert the response code with the expected status.
if recV2.Code != testCase.expectedRespStatus {
t.Fatalf("Case %d: Expected the response status to be `%d`, but instead found `%d`", i+1, testCase.expectedRespStatus, recV2.Code)
}
}
}

View File

@ -61,7 +61,7 @@ func generateSystemLockResponse() (SystemLockState, error) {
defer nsMutex.lockMapMutex.Unlock()
if nsMutex.debugLockMap == nil {
return SystemLockState{}, LockInfoNil{}
return SystemLockState{}, errLockNotInitialized
}
lockState := SystemLockState{}

View File

@ -17,6 +17,7 @@
package cmd
import (
"errors"
"fmt"
"time"
)
@ -58,14 +59,6 @@ func newDebugLockInfoPerVolumePath() *debugLockInfoPerVolumePath {
}
}
// LockInfoNil - Returned if the lock info map is not initialized.
type LockInfoNil struct {
}
func (l LockInfoNil) Error() string {
return fmt.Sprintf("Debug Lock Map not initialized:\n1. Enable Lock Debugging using right ENV settings \n2. Make sure initNSLock() is called.")
}
// LockInfoOriginNotFound - While changing the state of the lock info its important that the entry for
// lock at a given origin exists, if not `LockInfoOriginNotFound` is returned.
type LockInfoOriginNotFound struct {
@ -114,13 +107,15 @@ func (l LockInfoStateNotBlocked) Error() string {
return fmt.Sprintf("Lock state should be \"Blocked\" for <volume> %s, <path> %s, <operationID> %s.", l.volume, l.path, l.operationID)
}
var errLockNotInitialized = errors.New("Debug Lock Map not initialized:\n1. Enable Lock Debugging using right ENV settings \n2. Make sure initNSLock() is called.")
// change the state of the lock from Blocked to Running.
func (n *nsLockMap) statusBlockedToRunning(param nsParam, lockOrigin, operationID string, readLock bool) error {
// This operation is not executed under the scope nsLockMap.mutex.Lock(), lock has to be explicitly held here.
n.lockMapMutex.Lock()
defer n.lockMapMutex.Unlock()
if n.debugLockMap == nil {
return LockInfoNil{}
return errLockNotInitialized
}
// new state info to be set for the lock.
newLockInfo := debugLockInfo{
@ -140,7 +135,7 @@ func (n *nsLockMap) statusBlockedToRunning(param nsParam, lockOrigin, operationI
if debugLockMap, ok := n.debugLockMap[param]; ok {
// ``*debugLockInfoPerVolumePath` entry containing lock info for `param <volume, path>` is `nil`.
if debugLockMap == nil {
return LockInfoNil{}
return errLockNotInitialized
}
} else {
// The lock state info foe given <volume, path> pair should already exist.
@ -186,7 +181,7 @@ func (n *nsLockMap) statusBlockedToRunning(param nsParam, lockOrigin, operationI
// change the state of the lock from Ready to Blocked.
func (n *nsLockMap) statusNoneToBlocked(param nsParam, lockOrigin, operationID string, readLock bool) error {
if n.debugLockMap == nil {
return LockInfoNil{}
return errLockNotInitialized
}
newLockInfo := debugLockInfo{
@ -230,7 +225,7 @@ func (n *nsLockMap) statusNoneToBlocked(param nsParam, lockOrigin, operationID s
// deleteLockInfoEntry - Deletes the lock state information for given <volume, path> pair. Called when nsLk.ref count is 0.
func (n *nsLockMap) deleteLockInfoEntryForVolumePath(param nsParam) error {
if n.debugLockMap == nil {
return LockInfoNil{}
return errLockNotInitialized
}
// delete the lock info for the given operation.
if _, found := n.debugLockMap[param]; found {
@ -246,7 +241,7 @@ func (n *nsLockMap) deleteLockInfoEntryForVolumePath(param nsParam) error {
// called when the nsLk ref count for the given <volume, path> pair is not 0.
func (n *nsLockMap) deleteLockInfoEntryForOps(param nsParam, operationID string) error {
if n.debugLockMap == nil {
return LockInfoNil{}
return errLockNotInitialized
}
// delete the lock info for the given operation.
if infoMap, found := n.debugLockMap[param]; found {

View File

@ -327,7 +327,7 @@ func TestNsLockMapStatusBlockedToRunning(t *testing.T) {
actualErr := nsMutex.statusBlockedToRunning(param, testCases[0].lockOrigin,
testCases[0].opsID, testCases[0].readLock)
expectedNilErr := LockInfoNil{}
expectedNilErr := errLockNotInitialized
if actualErr != expectedNilErr {
t.Fatalf("Errors mismatch: Expected \"%s\", got \"%s\"", expectedNilErr, actualErr)
}
@ -338,12 +338,12 @@ func TestNsLockMapStatusBlockedToRunning(t *testing.T) {
lockMap: make(map[nsParam]*nsLock),
}
// Entry for <volume, path> pair is set to nil.
// Should fail with `LockInfoNil{}`.
// Should fail with `errLockNotInitialized`.
nsMutex.debugLockMap[param] = nil
actualErr = nsMutex.statusBlockedToRunning(param, testCases[0].lockOrigin,
testCases[0].opsID, testCases[0].readLock)
expectedNilErr = LockInfoNil{}
expectedNilErr = errLockNotInitialized
if actualErr != expectedNilErr {
t.Fatalf("Errors mismatch: Expected \"%s\", got \"%s\"", expectedNilErr, actualErr)
}
@ -524,7 +524,7 @@ func TestNsLockMapStatusNoneToBlocked(t *testing.T) {
actualErr := nsMutex.statusBlockedToRunning(param, testCases[0].lockOrigin,
testCases[0].opsID, testCases[0].readLock)
expectedNilErr := LockInfoNil{}
expectedNilErr := errLockNotInitialized
if actualErr != expectedNilErr {
t.Fatalf("Errors mismatch: Expected \"%s\", got \"%s\"", expectedNilErr, actualErr)
}
@ -569,7 +569,7 @@ func TestNsLockMapDeleteLockInfoEntryForOps(t *testing.T) {
actualErr := nsMutex.deleteLockInfoEntryForOps(param, testCases[0].opsID)
expectedNilErr := LockInfoNil{}
expectedNilErr := errLockNotInitialized
if actualErr != expectedNilErr {
t.Fatalf("Errors mismatch: Expected \"%s\", got \"%s\"", expectedNilErr, actualErr)
}
@ -667,7 +667,7 @@ func TestNsLockMapDeleteLockInfoEntryForVolumePath(t *testing.T) {
actualErr := nsMutex.deleteLockInfoEntryForVolumePath(param)
expectedNilErr := LockInfoNil{}
expectedNilErr := errLockNotInitialized
if actualErr != expectedNilErr {
t.Fatalf("Errors mismatch: Expected \"%s\", got \"%s\"", expectedNilErr, actualErr)
}

View File

@ -110,6 +110,13 @@ func (api objectAPIHandlers) GetObjectHandler(w http.ResponseWriter, r *http.Req
writeErrorResponse(w, r, s3Error, r.URL.Path)
return
}
case authTypePresignedV2, authTypeSignedV2:
// Signature V2 validation.
if s3Error := isReqAuthenticatedV2(r); s3Error != ErrNone {
errorIf(errSignatureMismatch, dumpRequest(r))
writeErrorResponse(w, r, s3Error, r.URL.Path)
return
}
case authTypePresigned, authTypeSigned:
if s3Error := isReqAuthenticated(r, serverConfig.GetRegion()); s3Error != ErrNone {
errorIf(errSignatureMismatch, dumpRequest(r))
@ -222,6 +229,13 @@ func (api objectAPIHandlers) HeadObjectHandler(w http.ResponseWriter, r *http.Re
writeErrorResponse(w, r, s3Error, r.URL.Path)
return
}
case authTypePresignedV2, authTypeSignedV2:
// Signature V2 validation.
if s3Error := isReqAuthenticatedV2(r); s3Error != ErrNone {
errorIf(errSignatureMismatch, dumpRequest(r))
writeErrorResponse(w, r, s3Error, r.URL.Path)
return
}
case authTypePresigned, authTypeSigned:
if s3Error := isReqAuthenticated(r, serverConfig.GetRegion()); s3Error != ErrNone {
errorIf(errSignatureMismatch, dumpRequest(r))
@ -279,6 +293,13 @@ func (api objectAPIHandlers) CopyObjectHandler(w http.ResponseWriter, r *http.Re
writeErrorResponse(w, r, s3Error, r.URL.Path)
return
}
case authTypePresignedV2, authTypeSignedV2:
// Signature V2 validation.
if s3Error := isReqAuthenticatedV2(r); s3Error != ErrNone {
errorIf(errSignatureMismatch, dumpRequest(r))
writeErrorResponse(w, r, s3Error, r.URL.Path)
return
}
case authTypePresigned, authTypeSigned:
if s3Error := isReqAuthenticated(r, serverConfig.GetRegion()); s3Error != ErrNone {
errorIf(errSignatureMismatch, dumpRequest(r))
@ -399,12 +420,12 @@ func (api objectAPIHandlers) PutObjectHandler(w http.ResponseWriter, r *http.Req
return
}
// If the matching failed, it means that the X-Amz-Copy-Source was
// wrong, fail right here.
// X-Amz-Copy-Source shouldn't be set for this call.
if _, ok := r.Header["X-Amz-Copy-Source"]; ok {
writeErrorResponse(w, r, ErrInvalidCopySource, r.URL.Path)
return
}
vars := mux.Vars(r)
bucket := vars["bucket"]
object := vars["object"]
@ -416,6 +437,7 @@ func (api objectAPIHandlers) PutObjectHandler(w http.ResponseWriter, r *http.Req
writeErrorResponse(w, r, ErrInvalidDigest, r.URL.Path)
return
}
/// if Content-Length is unknown/missing, deny the request
size := r.ContentLength
rAuthType := getRequestAuthType(r)
@ -432,6 +454,7 @@ func (api objectAPIHandlers) PutObjectHandler(w http.ResponseWriter, r *http.Req
writeErrorResponse(w, r, ErrMissingContentLength, r.URL.Path)
return
}
/// maximum Upload size for objects in a single operation
if isMaxObjectSize(size) {
writeErrorResponse(w, r, ErrEntityTooLarge, r.URL.Path)
@ -466,6 +489,14 @@ func (api objectAPIHandlers) PutObjectHandler(w http.ResponseWriter, r *http.Req
return
}
objInfo, err = objectAPI.PutObject(bucket, object, size, reader, metadata)
case authTypeSignedV2, authTypePresignedV2:
s3Error := isReqAuthenticatedV2(r)
if s3Error != ErrNone {
errorIf(errSignatureMismatch, dumpRequest(r))
writeErrorResponse(w, r, s3Error, r.URL.Path)
return
}
objInfo, err = objectAPI.PutObject(bucket, object, size, r.Body, metadata)
case authTypePresigned, authTypeSigned:
// Initialize signature verifier.
reader := newSignVerify(r)
@ -517,6 +548,13 @@ func (api objectAPIHandlers) NewMultipartUploadHandler(w http.ResponseWriter, r
writeErrorResponse(w, r, s3Error, r.URL.Path)
return
}
case authTypePresignedV2, authTypeSignedV2:
// Signature V2 validation.
if s3Error := isReqAuthenticatedV2(r); s3Error != ErrNone {
errorIf(errSignatureMismatch, dumpRequest(r))
writeErrorResponse(w, r, s3Error, r.URL.Path)
return
}
case authTypePresigned, authTypeSigned:
if s3Error := isReqAuthenticated(r, serverConfig.GetRegion()); s3Error != ErrNone {
errorIf(errSignatureMismatch, dumpRequest(r))
@ -626,6 +664,14 @@ func (api objectAPIHandlers) PutObjectPartHandler(w http.ResponseWriter, r *http
return
}
partMD5, err = objectAPI.PutObjectPart(bucket, object, uploadID, partID, size, reader, incomingMD5)
case authTypeSignedV2, authTypePresignedV2:
s3Error := isReqAuthenticatedV2(r)
if s3Error != ErrNone {
errorIf(errSignatureMismatch, dumpRequest(r))
writeErrorResponse(w, r, s3Error, r.URL.Path)
return
}
partMD5, err = objectAPI.PutObjectPart(bucket, object, uploadID, partID, size, r.Body, incomingMD5)
case authTypePresigned, authTypeSigned:
// Initialize signature verifier.
reader := newSignVerify(r)
@ -666,6 +712,13 @@ func (api objectAPIHandlers) AbortMultipartUploadHandler(w http.ResponseWriter,
writeErrorResponse(w, r, s3Error, r.URL.Path)
return
}
case authTypePresignedV2, authTypeSignedV2:
// Signature V2 validation.
if s3Error := isReqAuthenticatedV2(r); s3Error != ErrNone {
errorIf(errSignatureMismatch, dumpRequest(r))
writeErrorResponse(w, r, s3Error, r.URL.Path)
return
}
case authTypePresigned, authTypeSigned:
if s3Error := isReqAuthenticated(r, serverConfig.GetRegion()); s3Error != ErrNone {
errorIf(errSignatureMismatch, dumpRequest(r))
@ -706,6 +759,13 @@ func (api objectAPIHandlers) ListObjectPartsHandler(w http.ResponseWriter, r *ht
writeErrorResponse(w, r, s3Error, r.URL.Path)
return
}
case authTypePresignedV2, authTypeSignedV2:
// Signature V2 validation.
if s3Error := isReqAuthenticatedV2(r); s3Error != ErrNone {
errorIf(errSignatureMismatch, dumpRequest(r))
writeErrorResponse(w, r, s3Error, r.URL.Path)
return
}
case authTypePresigned, authTypeSigned:
if s3Error := isReqAuthenticated(r, serverConfig.GetRegion()); s3Error != ErrNone {
errorIf(errSignatureMismatch, dumpRequest(r))
@ -764,6 +824,13 @@ func (api objectAPIHandlers) CompleteMultipartUploadHandler(w http.ResponseWrite
writeErrorResponse(w, r, s3Error, r.URL.Path)
return
}
case authTypePresignedV2, authTypeSignedV2:
// Signature V2 validation.
if s3Error := isReqAuthenticatedV2(r); s3Error != ErrNone {
errorIf(errSignatureMismatch, dumpRequest(r))
writeErrorResponse(w, r, s3Error, r.URL.Path)
return
}
case authTypePresigned, authTypeSigned:
if s3Error := isReqAuthenticated(r, serverConfig.GetRegion()); s3Error != ErrNone {
errorIf(errSignatureMismatch, dumpRequest(r))
@ -873,6 +940,13 @@ func (api objectAPIHandlers) DeleteObjectHandler(w http.ResponseWriter, r *http.
writeErrorResponse(w, r, s3Error, r.URL.Path)
return
}
case authTypePresignedV2, authTypeSignedV2:
// Signature V2 validation.
if s3Error := isReqAuthenticatedV2(r); s3Error != ErrNone {
errorIf(errSignatureMismatch, dumpRequest(r))
writeErrorResponse(w, r, s3Error, r.URL.Path)
return
}
case authTypeSigned, authTypePresigned:
if s3Error := isReqAuthenticated(r, serverConfig.GetRegion()); s3Error != ErrNone {
errorIf(errSignatureMismatch, dumpRequest(r))

View File

@ -128,7 +128,7 @@ func testAPIGetOjectHandler(obj ObjectLayer, instanceType, bucketName string, ap
// initialize HTTP NewRecorder, this records any mutations to response writer inside the handler.
rec := httptest.NewRecorder()
// construct HTTP request for Get Object end point.
req, err := newTestSignedRequest("GET", getGetObjectURL("", testCase.bucketName, testCase.objectName),
req, err := newTestSignedRequestV4("GET", getGetObjectURL("", testCase.bucketName, testCase.objectName),
0, nil, credentials.AccessKeyID, credentials.SecretAccessKey)
if err != nil {
@ -342,7 +342,7 @@ func testAPIPutObjectHandler(obj ObjectLayer, instanceType, bucketName string, a
// initialize HTTP NewRecorder, this records any mutations to response writer inside the handler.
rec := httptest.NewRecorder()
// construct HTTP request for Get Object end point.
req, err := newTestSignedRequest("PUT", getPutObjectURL("", testCase.bucketName, testCase.objectName),
req, err := newTestSignedRequestV4("PUT", getPutObjectURL("", testCase.bucketName, testCase.objectName),
int64(testCase.dataLen), bytes.NewReader(testCase.data), credentials.AccessKeyID, credentials.SecretAccessKey)
if err != nil {
t.Fatalf("Test %d: Failed to create HTTP request for Put Object: <ERROR> %v", i+1, err)
@ -484,7 +484,7 @@ func testAPICopyObjectHandler(obj ObjectLayer, instanceType, bucketName string,
// initialize HTTP NewRecorder, this records any mutations to response writer inside the handler.
rec := httptest.NewRecorder()
// construct HTTP request for copy object.
req, err := newTestSignedRequest("PUT", getCopyObjectURL("", testCase.bucketName, testCase.newObjectName),
req, err := newTestSignedRequestV4("PUT", getCopyObjectURL("", testCase.bucketName, testCase.newObjectName),
0, nil, credentials.AccessKeyID, credentials.SecretAccessKey)
if err != nil {
@ -529,7 +529,7 @@ func testAPINewMultipartHandler(obj ObjectLayer, instanceType, bucketName string
objectName := "test-object-new-multipart"
rec := httptest.NewRecorder()
// construct HTTP request for copy object.
req, err := newTestSignedRequest("POST", getNewMultipartURL("", bucketName, objectName), 0, nil, credentials.AccessKeyID, credentials.SecretAccessKey)
req, err := newTestSignedRequestV4("POST", getNewMultipartURL("", bucketName, objectName), 0, nil, credentials.AccessKeyID, credentials.SecretAccessKey)
if err != nil {
t.Fatalf("Failed to create HTTP request for copy Object: <ERROR> %v", err)
@ -581,7 +581,7 @@ func testAPINewMultipartHandlerParallel(obj ObjectLayer, instanceType, bucketNam
defer wg.Done()
rec := httptest.NewRecorder()
// construct HTTP request for copy object.
req, err := newTestSignedRequest("POST", getNewMultipartURL("", bucketName, objectName), 0, nil, credentials.AccessKeyID, credentials.SecretAccessKey)
req, err := newTestSignedRequestV4("POST", getNewMultipartURL("", bucketName, objectName), 0, nil, credentials.AccessKeyID, credentials.SecretAccessKey)
if err != nil {
t.Fatalf("Failed to create HTTP request for copy Object: <ERROR> %v", err)
@ -828,7 +828,7 @@ func testAPICompleteMultipartHandler(obj ObjectLayer, instanceType, bucketName s
t.Fatalf("Error XML encoding of parts: <ERROR> %s.", err)
}
// Indicating that all parts are uploaded and initiating completeMultipartUpload.
req, err = newTestSignedRequest("POST", getCompleteMultipartUploadURL("", bucketName, objectName, testCase.uploadID),
req, err = newTestSignedRequestV4("POST", getCompleteMultipartUploadURL("", bucketName, objectName, testCase.uploadID),
int64(len(completeBytes)), bytes.NewReader(completeBytes), credentials.AccessKeyID, credentials.SecretAccessKey)
if err != nil {
t.Fatalf("Failed to create HTTP request for copy Object: <ERROR> %v", err)
@ -930,7 +930,7 @@ func testAPIDeleteOjectHandler(obj ObjectLayer, instanceType, bucketName string,
// initialize HTTP NewRecorder, this records any mutations to response writer inside the handler.
rec := httptest.NewRecorder()
// construct HTTP request for Get Object end point.
req, err := newTestSignedRequest("DELETE", getDeleteObjectURL("", testCase.bucketName, testCase.objectName),
req, err := newTestSignedRequestV4("DELETE", getDeleteObjectURL("", testCase.bucketName, testCase.objectName),
0, nil, credentials.AccessKeyID, credentials.SecretAccessKey)
if err != nil {

File diff suppressed because it is too large Load Diff

2453
cmd/server_v2_test.go Normal file

File diff suppressed because it is too large Load Diff

56
cmd/signature-v2-utils.go Normal file
View File

@ -0,0 +1,56 @@
/*
* Minio Cloud Storage, (C) 2016 Minio, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package cmd
import (
"bytes"
"net/url"
"sort"
"strings"
)
// Replaces any occurring '/' in string, into its encoded representation.
func percentEncodeSlash(s string) string {
return strings.Replace(s, "/", "%2F", -1)
}
// queryEncode - encodes query values in their URL encoded form. In
// addition to the percent encoding performed by getURLEncodedName() used
// here, it also percent encodes '/' (forward slash)
func queryEncode(v url.Values) string {
if v == nil {
return ""
}
var buf bytes.Buffer
keys := make([]string, 0, len(v))
for k := range v {
keys = append(keys, k)
}
sort.Strings(keys)
for _, k := range keys {
vs := v[k]
prefix := percentEncodeSlash(getURLEncodedName(k)) + "="
for _, v := range vs {
if buf.Len() > 0 {
buf.WriteByte('&')
}
buf.WriteString(prefix)
buf.WriteString(percentEncodeSlash(getURLEncodedName(v)))
}
}
return buf.String()
}

303
cmd/signature-v2.go Normal file
View File

@ -0,0 +1,303 @@
/*
* Minio Cloud Storage, (C) 2016 Minio, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package cmd
import (
"bytes"
"crypto/hmac"
"crypto/sha1"
"encoding/base64"
"fmt"
"net/http"
"net/url"
"sort"
"strconv"
"strings"
"time"
)
// Signature and API related constants.
const (
signV2Algorithm = "AWS"
)
// TODO add post policy signature.
// doesPresignV2SignatureMatch - Verify query headers with presigned signature
// - http://docs.aws.amazon.com/AmazonS3/latest/dev/RESTAuthentication.html#RESTAuthenticationQueryStringAuth
// returns ErrNone if matches. S3 errors otherwise.
func doesPresignV2SignatureMatch(r *http.Request) APIErrorCode {
// Access credentials.
cred := serverConfig.GetCredential()
// Copy request
req := *r
// Validate if we do have query params.
if req.URL.Query().Encode() == "" {
return ErrInvalidQueryParams
}
// Validate if access key id same.
if req.URL.Query().Get("AWSAccessKeyId") != cred.AccessKeyID {
return ErrInvalidAccessKeyID
}
// Parse expires param into its native form.
expired, err := strconv.ParseInt(req.URL.Query().Get("Expires"), 10, 64)
if err != nil {
errorIf(err, "Unable to parse expires query param")
return ErrMalformedExpires
}
// Validate if the request has already expired.
if expired < time.Now().UTC().Unix() {
return ErrExpiredPresignRequest
}
// Get presigned string to sign.
stringToSign := preStringifyHTTPReq(req)
hm := hmac.New(sha1.New, []byte(cred.SecretAccessKey))
hm.Write([]byte(stringToSign))
// Calculate signature and validate.
signature := base64.StdEncoding.EncodeToString(hm.Sum(nil))
if req.URL.Query().Get("Signature") != signature {
return ErrSignatureDoesNotMatch
}
// Success.
return ErrNone
}
// Authorization = "AWS" + " " + AWSAccessKeyId + ":" + Signature;
// Signature = Base64( HMAC-SHA1( YourSecretAccessKeyID, UTF-8-Encoding-Of( StringToSign ) ) );
//
// StringToSign = HTTP-Verb + "\n" +
// Content-Md5 + "\n" +
// Content-Type + "\n" +
// Date + "\n" +
// CanonicalizedProtocolHeaders +
// CanonicalizedResource;
//
// CanonicalizedResource = [ "/" + Bucket ] +
// <HTTP-Request-URI, from the protocol name up to the query string> +
// [ subresource, if present. For example "?acl", "?location", "?logging", or "?torrent"];
//
// CanonicalizedProtocolHeaders = <described below>
// doesSignV2Match - Verify authorization header with calculated header in accordance with
// - http://docs.aws.amazon.com/AmazonS3/latest/dev/auth-request-sig-v2.html
// returns true if matches, false otherwise. if error is not nil then it is always false
func doesSignV2Match(r *http.Request) APIErrorCode {
// Access credentials.
cred := serverConfig.GetCredential()
// Copy request.
req := *r
// Save authorization header.
v2Auth := req.Header.Get("Authorization")
if v2Auth == "" {
return ErrAuthHeaderEmpty
}
// Add date if not present.
if date := req.Header.Get("Date"); date == "" {
if date = req.Header.Get("X-Amz-Date"); date == "" {
return ErrMissingDateHeader
}
}
// Calculate HMAC for secretAccessKey.
stringToSign := stringifyHTTPReq(req)
hm := hmac.New(sha1.New, []byte(cred.SecretAccessKey))
hm.Write([]byte(stringToSign))
// Prepare auth header.
authHeader := new(bytes.Buffer)
authHeader.WriteString(fmt.Sprintf("%s %s:", signV2Algorithm, cred.AccessKeyID))
encoder := base64.NewEncoder(base64.StdEncoding, authHeader)
encoder.Write(hm.Sum(nil))
encoder.Close()
// Verify if signature match.
if authHeader.String() != v2Auth {
return ErrSignatureDoesNotMatch
}
return ErrNone
}
// From the Amazon docs:
//
// StringToSign = HTTP-Verb + "\n" +
// Content-Md5 + "\n" +
// Content-Type + "\n" +
// Expires + "\n" +
// CanonicalizedProtocolHeaders +
// CanonicalizedResource;
func preStringifyHTTPReq(req http.Request) string {
buf := new(bytes.Buffer)
// Write standard headers.
writePreSignV2Headers(buf, req)
// Write canonicalized protocol headers if any.
writeCanonicalizedHeaders(buf, req)
// Write canonicalized Query resources if any.
isPreSign := true
writeCanonicalizedResource(buf, req, isPreSign)
return buf.String()
}
// writePreSignV2Headers - write preSign v2 required headers.
func writePreSignV2Headers(buf *bytes.Buffer, req http.Request) {
buf.WriteString(req.Method + "\n")
buf.WriteString(req.Header.Get("Content-Md5") + "\n")
buf.WriteString(req.Header.Get("Content-Type") + "\n")
buf.WriteString(req.Header.Get("Expires") + "\n")
}
// From the Amazon docs:
//
// StringToSign = HTTP-Verb + "\n" +
// Content-Md5 + "\n" +
// Content-Type + "\n" +
// Date + "\n" +
// CanonicalizedProtocolHeaders +
// CanonicalizedResource;
func stringifyHTTPReq(req http.Request) string {
buf := new(bytes.Buffer)
// Write standard headers.
writeSignV2Headers(buf, req)
// Write canonicalized protocol headers if any.
writeCanonicalizedHeaders(buf, req)
// Write canonicalized Query resources if any.
isPreSign := false
writeCanonicalizedResource(buf, req, isPreSign)
return buf.String()
}
// writeSignV2Headers - write signV2 required headers.
func writeSignV2Headers(buf *bytes.Buffer, req http.Request) {
buf.WriteString(req.Method + "\n")
buf.WriteString(req.Header.Get("Content-Md5") + "\n")
buf.WriteString(req.Header.Get("Content-Type") + "\n")
buf.WriteString(req.Header.Get("Date") + "\n")
}
// writeCanonicalizedHeaders - write canonicalized headers.
func writeCanonicalizedHeaders(buf *bytes.Buffer, req http.Request) {
var protoHeaders []string
vals := make(map[string][]string)
for k, vv := range req.Header {
// All the AMZ headers should be lowercase
lk := strings.ToLower(k)
if strings.HasPrefix(lk, "x-amz") {
protoHeaders = append(protoHeaders, lk)
vals[lk] = vv
}
}
sort.Strings(protoHeaders)
for _, k := range protoHeaders {
buf.WriteString(k)
buf.WriteByte(':')
for idx, v := range vals[k] {
if idx > 0 {
buf.WriteByte(',')
}
if strings.Contains(v, "\n") {
// TODO: "Unfold" long headers that
// span multiple lines (as allowed by
// RFC 2616, section 4.2) by replacing
// the folding white-space (including
// new-line) by a single space.
buf.WriteString(v)
} else {
buf.WriteString(v)
}
}
buf.WriteByte('\n')
}
}
// The following list is already sorted and should always be, otherwise we could
// have signature-related issues
var resourceList = []string{
"acl",
"delete",
"location",
"logging",
"notification",
"partNumber",
"policy",
"requestPayment",
"torrent",
"uploadId",
"uploads",
"versionId",
"versioning",
"versions",
"website",
}
// From the Amazon docs:
//
// CanonicalizedResource = [ "/" + Bucket ] +
// <HTTP-Request-URI, from the protocol name up to the query string> +
// [ sub-resource, if present. For example "?acl", "?location", "?logging", or "?torrent"];
func writeCanonicalizedResource(buf *bytes.Buffer, req http.Request, isPreSign bool) {
// Save request URL.
requestURL := req.URL
// Get encoded URL path.
path := getURLEncodedName(requestURL.Path)
if isPreSign {
// Get encoded URL path.
if len(requestURL.Query()) > 0 {
// Keep the usual queries unescaped for string to sign.
query, _ := url.QueryUnescape(queryEncode(requestURL.Query()))
path = path + "?" + query
}
buf.WriteString(path)
return
}
buf.WriteString(path)
if requestURL.RawQuery != "" {
var n int
vals, _ := url.ParseQuery(requestURL.RawQuery)
// Verify if any sub resource queries are present, if yes
// canonicallize them.
for _, resource := range resourceList {
if vv, ok := vals[resource]; ok && len(vv) > 0 {
n++
// First element
switch n {
case 1:
buf.WriteByte('?')
// The rest
default:
buf.WriteByte('&')
}
buf.WriteString(resource)
// Request parameters
if len(vv[0]) > 0 {
buf.WriteByte('=')
buf.WriteString(strings.Replace(url.QueryEscape(vv[0]), "+", "%20", -1))
}
}
}
}
}

159
cmd/signature-v2_test.go Normal file
View File

@ -0,0 +1,159 @@
package cmd
import (
"fmt"
"net/http"
"net/url"
"sort"
"testing"
"time"
)
// Tests for 'func TestResourceListSorting(t *testing.T)'.
func TestResourceListSorting(t *testing.T) {
sortedResourceList := make([]string, len(resourceList))
copy(sortedResourceList, resourceList)
sort.Strings(sortedResourceList)
for i := 0; i < len(resourceList); i++ {
if resourceList[i] != sortedResourceList[i] {
t.Errorf("Expected resourceList[%d] = \"%s\", resourceList is not correctly sorted.", i, sortedResourceList[i])
break
}
}
}
// Tests validate the query encoding.
func TestQueryEncode(t *testing.T) {
testCases := []struct {
// Input.
input url.Values
// Expected result.
result string
}{
// % should be encoded as %25
{url.Values{
"key": []string{"thisisthe%url"},
}, "key=thisisthe%25url"},
// UTF-8 encoding.
{url.Values{
"key": []string{"本語"},
}, "key=%E6%9C%AC%E8%AA%9E"},
// UTF-8 encoding with ASCII.
{url.Values{
"key": []string{"本語.1"},
}, "key=%E6%9C%AC%E8%AA%9E.1"},
// Unusual ASCII characters.
{url.Values{
"key": []string{">123"},
}, "key=%3E123"},
// Fragment path characters.
{url.Values{
"key": []string{"myurl#link"},
}, "key=myurl%23link"},
// Space should be set to %20 not '+'.
{url.Values{
"key": []string{"space in url"},
}, "key=space%20in%20url"},
// '+' shouldn't be treated as space.
{url.Values{
"key": []string{"url+path"},
}, "key=url%2Bpath"},
// '/' shouldn't be treated as '/' should be percent coded.
{url.Values{
"key": []string{"url/+path"},
}, "key=url%2F%2Bpath"},
// Values is empty and empty string.
{nil, ""},
}
// Tests generated values from url encoded name.
for i, testCase := range testCases {
result := queryEncode(testCase.input)
if testCase.result != result {
t.Errorf("Test %d: Expected queryEncoded result to be \"%s\", but found it to be \"%s\" instead", i+1, testCase.result, result)
}
}
}
func TestDoesPresignedV2SignatureMatch(t *testing.T) {
root, err := newTestConfig("us-east-1")
if err != nil {
t.Fatal("Unable to initialize test config.")
}
defer removeAll(root)
now := time.Now().UTC()
testCases := []struct {
queryParams map[string]string
headers map[string]string
expected APIErrorCode
}{
// (0) Should error without a set URL query.
{
expected: ErrInvalidQueryParams,
},
// (1) Should error on an invalid access key.
{
queryParams: map[string]string{
"Expires": "60",
"Signature": "badsignature",
"AWSAccessKeyId": "Z7IXGOO6BZ0REAN1Q26I",
},
expected: ErrInvalidAccessKeyID,
},
// (2) Should error with malformed expires.
{
queryParams: map[string]string{
"Expires": "60s",
"Signature": "badsignature",
"AWSAccessKeyId": serverConfig.GetCredential().AccessKeyID,
},
expected: ErrMalformedExpires,
},
// (3) Should give an expired request if it has expired.
{
queryParams: map[string]string{
"Expires": "60",
"Signature": "badsignature",
"AWSAccessKeyId": serverConfig.GetCredential().AccessKeyID,
},
expected: ErrExpiredPresignRequest,
},
// (4) Should error when the signature does not match.
{
queryParams: map[string]string{
"Expires": fmt.Sprintf("%d", now.Unix()+60),
"Signature": "badsignature",
"AWSAccessKeyId": serverConfig.GetCredential().AccessKeyID,
},
expected: ErrSignatureDoesNotMatch,
},
}
// Run each test case individually.
for i, testCase := range testCases {
// Turn the map[string]string into map[string][]string, because Go.
query := url.Values{}
for key, value := range testCase.queryParams {
query.Set(key, value)
}
// Create a request to use.
req, e := http.NewRequest(http.MethodGet, "http://host/a/b?"+query.Encode(), nil)
if e != nil {
t.Errorf("(%d) failed to create http.Request, got %v", i, e)
}
// Do the same for the headers.
for key, value := range testCase.headers {
req.Header.Set(key, value)
}
// Check if it matches!
err := doesPresignV2SignatureMatch(req)
if err != testCase.expected {
t.Errorf("(%d) expected to get %s, instead got %s", i, niceError(testCase.expected), niceError(err))
}
}
}

View File

@ -22,7 +22,8 @@ import (
"time"
)
// TestSkipContentSha256Cksum - Test validate the logic which decides whether to skip checksum validation based on the request header.
// TestSkipContentSha256Cksum - Test validate the logic which decides whether
// to skip checksum validation based on the request header.
func TestSkipContentSha256Cksum(t *testing.T) {
testCases := []struct {
inputHeaderKey string
@ -124,7 +125,7 @@ func TestGetURLEncodedName(t *testing.T) {
for i, testCase := range testCases {
result := getURLEncodedName(testCase.inputStr)
if testCase.result != result {
t.Errorf("Test %d: Expected queryEncode result to be \"%s\", but found it to be \"%s\" instead", i+1, testCase.result, result)
t.Errorf("Test %d: Expected URLEncoded result to be \"%s\", but found it to be \"%s\" instead", i+1, testCase.result, result)
}
}
}

View File

@ -18,6 +18,8 @@ package cmd
import (
"bytes"
"crypto/hmac"
"crypto/sha1"
"encoding/base64"
"encoding/hex"
"encoding/json"
@ -31,14 +33,12 @@ import (
"net/url"
"os"
"reflect"
"regexp"
"sort"
"strconv"
"strings"
"sync"
"testing"
"time"
"unicode/utf8"
router "github.com/gorilla/mux"
)
@ -476,8 +476,76 @@ func newTestStreamingSignedRequest(method, urlStr string, contentLength, chunkSi
return req, nil
}
// preSignV2 - presign the request in following style.
// https://${S3_BUCKET}.s3.amazonaws.com/${S3_OBJECT}?AWSAccessKeyId=${S3_ACCESS_KEY}&Expires=${TIMESTAMP}&Signature=${SIGNATURE}.
func preSignV2(req *http.Request, accessKeyID, secretAccessKey string, expires int64) error {
// Presign is not needed for anonymous credentials.
if accessKeyID == "" || secretAccessKey == "" {
return errors.New("Presign cannot be generated without access and secret keys")
}
d := time.Now().UTC()
// Find epoch expires when the request will expire.
epochExpires := d.Unix() + expires
// Add expires header if not present.
if expiresStr := req.Header.Get("Expires"); expiresStr == "" {
req.Header.Set("Expires", strconv.FormatInt(epochExpires, 10))
}
// Get presigned string to sign.
stringToSign := preStringifyHTTPReq(*req)
hm := hmac.New(sha1.New, []byte(secretAccessKey))
hm.Write([]byte(stringToSign))
// Calculate signature.
signature := base64.StdEncoding.EncodeToString(hm.Sum(nil))
query := req.URL.Query()
// Handle specially for Google Cloud Storage.
query.Set("AWSAccessKeyId", accessKeyID)
// Fill in Expires for presigned query.
query.Set("Expires", strconv.FormatInt(epochExpires, 10))
// Encode query and save.
req.URL.RawQuery = queryEncode(query)
// Save signature finally.
req.URL.RawQuery += "&Signature=" + getURLEncodedName(signature)
// Success.
return nil
}
// Sign given request using Signature V2.
func signRequestV2(req *http.Request, accessKey, secretKey string) error {
// Initial time.
d := time.Now().UTC()
// Add date if not present.
if date := req.Header.Get("Date"); date == "" {
req.Header.Set("Date", d.Format(http.TimeFormat))
}
// Calculate HMAC for secretAccessKey.
stringToSign := stringifyHTTPReq(*req)
hm := hmac.New(sha1.New, []byte(secretKey))
hm.Write([]byte(stringToSign))
// Prepare auth header.
authHeader := new(bytes.Buffer)
authHeader.WriteString(fmt.Sprintf("%s %s:", signV2Algorithm, accessKey))
encoder := base64.NewEncoder(base64.StdEncoding, authHeader)
encoder.Write(hm.Sum(nil))
encoder.Close()
// Set Authorization header.
req.Header.Set("Authorization", authHeader.String())
return nil
}
// Sign given request using Signature V4.
func signRequest(req *http.Request, accessKey, secretKey string) error {
func signRequestV4(req *http.Request, accessKey, secretKey string) error {
// Get hashed payload.
hashedPayload := req.Header.Get("x-amz-content-sha256")
if hashedPayload == "" {
@ -611,9 +679,9 @@ func newTestRequest(method, urlStr string, contentLength int64, body io.ReadSeek
case body == nil:
hashedPayload = hex.EncodeToString(sum256([]byte{}))
default:
payloadBytes, e := ioutil.ReadAll(body)
if e != nil {
return nil, e
payloadBytes, err := ioutil.ReadAll(body)
if err != nil {
return nil, err
}
hashedPayload = hex.EncodeToString(sum256(payloadBytes))
md5Base64 := base64.StdEncoding.EncodeToString(sumMD5(payloadBytes))
@ -635,8 +703,50 @@ func newTestRequest(method, urlStr string, contentLength int64, body io.ReadSeek
return req, nil
}
func newTestSignedRequestV2ContentType(method, urlStr string, contentLength int64, body io.ReadSeeker, accessKey, secretKey, contentType string) (*http.Request, error) {
req, err := newTestRequest(method, urlStr, contentLength, body)
if err != nil {
return nil, err
}
req.Header.Del("x-amz-content-sha256")
req.Header.Set("Content-Type", contentType)
// Anonymous request return quickly.
if accessKey == "" || secretKey == "" {
return req, nil
}
err = signRequestV2(req, accessKey, secretKey)
if err != nil {
return nil, err
}
return req, nil
}
// Returns new HTTP request object signed with signature v2.
func newTestSignedRequestV2(method, urlStr string, contentLength int64, body io.ReadSeeker, accessKey, secretKey string) (*http.Request, error) {
req, err := newTestRequest(method, urlStr, contentLength, body)
if err != nil {
return nil, err
}
req.Header.Del("x-amz-content-sha256")
// Anonymous request return quickly.
if accessKey == "" || secretKey == "" {
return req, nil
}
err = signRequestV2(req, accessKey, secretKey)
if err != nil {
return nil, err
}
return req, nil
}
// Returns new HTTP request object signed with signature v4.
func newTestSignedRequest(method, urlStr string, contentLength int64, body io.ReadSeeker, accessKey, secretKey string) (*http.Request, error) {
func newTestSignedRequestV4(method, urlStr string, contentLength int64, body io.ReadSeeker, accessKey, secretKey string) (*http.Request, error) {
req, err := newTestRequest(method, urlStr, contentLength, body)
if err != nil {
return nil, err
@ -647,7 +757,7 @@ func newTestSignedRequest(method, urlStr string, contentLength int64, body io.Re
return req, nil
}
err = signRequest(req, accessKey, secretKey)
err = signRequestV4(req, accessKey, secretKey)
if err != nil {
return nil, err
}
@ -842,71 +952,6 @@ func (t *EOFWriter) Write(p []byte) (n int, err error) {
return
}
// queryEncode - encodes query values in their URL encoded form.
func queryEncode(v url.Values) string {
if v == nil {
return ""
}
var buf bytes.Buffer
keys := make([]string, 0, len(v))
for k := range v {
keys = append(keys, k)
}
sort.Strings(keys)
for _, k := range keys {
vs := v[k]
prefix := urlEncodePath(k) + "="
for _, v := range vs {
if buf.Len() > 0 {
buf.WriteByte('&')
}
buf.WriteString(prefix)
buf.WriteString(urlEncodePath(v))
}
}
return buf.String()
}
// urlEncodePath encode the strings from UTF-8 byte representations to HTML hex escape sequences
//
// This is necessary since regular url.Parse() and url.Encode() functions do not support UTF-8
// non english characters cannot be parsed due to the nature in which url.Encode() is written
//
// This function on the other hand is a direct replacement for url.Encode() technique to support
// pretty much every UTF-8 character.
func urlEncodePath(pathName string) string {
// if object matches reserved string, no need to encode them
reservedNames := regexp.MustCompile("^[a-zA-Z0-9-_.~/]+$")
if reservedNames.MatchString(pathName) {
return pathName
}
var encodedPathname string
for _, s := range pathName {
if 'A' <= s && s <= 'Z' || 'a' <= s && s <= 'z' || '0' <= s && s <= '9' { // §2.3 Unreserved characters (mark)
encodedPathname = encodedPathname + string(s)
continue
}
switch s {
case '-', '_', '.', '~', '/': // §2.3 Unreserved characters (mark)
encodedPathname = encodedPathname + string(s)
continue
default:
len := utf8.RuneLen(s)
if len < 0 {
// if utf8 cannot convert return the same string as is
return pathName
}
u := make([]byte, len)
utf8.EncodeRune(u, s)
for _, r := range u {
hex := hex.EncodeToString([]byte{r})
encodedPathname = encodedPathname + "%" + strings.ToUpper(hex)
}
}
}
return encodedPathname
}
// construct URL for http requests for bucket operations.
func makeTestTargetURL(endPoint, bucketName, objectName string, queryValues url.Values) string {
urlStr := endPoint + "/"
@ -914,7 +959,7 @@ func makeTestTargetURL(endPoint, bucketName, objectName string, queryValues url.
urlStr = urlStr + bucketName + "/"
}
if objectName != "" {
urlStr = urlStr + urlEncodePath(objectName)
urlStr = urlStr + getURLEncodedName(objectName)
}
if len(queryValues) > 0 {
urlStr = urlStr + "?" + queryEncode(queryValues)