mirror of
https://github.com/minio/minio.git
synced 2024-12-24 22:25:54 -05:00
signature-v2 fix. (#2918)
- Return errors similar to V4 Sign processsing. - Return ErrMissing fields when Auth Header fields are missing. - Return InvalidAccessID when accessID doesn't match. * tests: Adding V2 signature tests for bucket handler API's.
This commit is contained in:
parent
0aabc1d8d9
commit
17e49a9ed2
@ -44,34 +44,39 @@ func testGetBucketLocationHandler(obj ObjectLayer, instanceType, bucketName stri
|
|||||||
errorResponse APIErrorResponse
|
errorResponse APIErrorResponse
|
||||||
shouldPass bool
|
shouldPass bool
|
||||||
}{
|
}{
|
||||||
|
// Test case - 1.
|
||||||
// Tests for authenticated request and proper response.
|
// Tests for authenticated request and proper response.
|
||||||
{
|
{
|
||||||
bucketName,
|
bucketName: bucketName,
|
||||||
credentials.AccessKeyID,
|
accessKey: credentials.AccessKeyID,
|
||||||
credentials.SecretAccessKey,
|
secretKey: credentials.SecretAccessKey,
|
||||||
http.StatusOK,
|
expectedRespStatus: http.StatusOK,
|
||||||
[]byte(`<?xml version="1.0" encoding="UTF-8"?>
|
locationResponse: []byte(`<?xml version="1.0" encoding="UTF-8"?>
|
||||||
<LocationConstraint xmlns="http://s3.amazonaws.com/doc/2006-03-01/"></LocationConstraint>`),
|
<LocationConstraint xmlns="http://s3.amazonaws.com/doc/2006-03-01/"></LocationConstraint>`),
|
||||||
APIErrorResponse{},
|
errorResponse: APIErrorResponse{},
|
||||||
true,
|
shouldPass: true,
|
||||||
},
|
},
|
||||||
// Tests for anonymous requests.
|
// Test case - 2.
|
||||||
|
// Tests for signature mismatch error.
|
||||||
{
|
{
|
||||||
bucketName,
|
bucketName: bucketName,
|
||||||
"",
|
accessKey: "abcd",
|
||||||
"",
|
secretKey: "abcd",
|
||||||
http.StatusForbidden,
|
expectedRespStatus: http.StatusForbidden,
|
||||||
[]byte(""),
|
locationResponse: []byte(""),
|
||||||
APIErrorResponse{
|
errorResponse: APIErrorResponse{
|
||||||
Resource: "/" + bucketName + "/",
|
Resource: "/" + bucketName + "/",
|
||||||
Code: "AccessDenied",
|
Code: "InvalidAccessKeyID",
|
||||||
Message: "Access Denied.",
|
Message: "The access key ID you provided does not exist in our records.",
|
||||||
},
|
},
|
||||||
false,
|
shouldPass: false,
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
for i, testCase := range testCases {
|
for i, testCase := range testCases {
|
||||||
|
if i != 1 {
|
||||||
|
continue
|
||||||
|
}
|
||||||
// initialize httptest Recorder, this records any mutations to response writer inside the handler.
|
// initialize httptest Recorder, this records any mutations to response writer inside the handler.
|
||||||
rec := httptest.NewRecorder()
|
rec := httptest.NewRecorder()
|
||||||
// construct HTTP request for Get bucket location.
|
// construct HTTP request for Get bucket location.
|
||||||
@ -79,7 +84,7 @@ func testGetBucketLocationHandler(obj ObjectLayer, instanceType, bucketName stri
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("Test %d: %s: Failed to create HTTP request for GetBucketLocationHandler: <ERROR> %v", i+1, instanceType, err)
|
t.Fatalf("Test %d: %s: Failed to create HTTP request for GetBucketLocationHandler: <ERROR> %v", i+1, instanceType, err)
|
||||||
}
|
}
|
||||||
// Since `apiRouter` satisfies `http.Handler` it has a ServeHTTP to execute the logic ofthe handler.
|
// Since `apiRouter` satisfies `http.Handler` it has a ServeHTTP to execute the logic of the handler.
|
||||||
// Call the ServeHTTP to execute the handler.
|
// Call the ServeHTTP to execute the handler.
|
||||||
apiRouter.ServeHTTP(rec, req)
|
apiRouter.ServeHTTP(rec, req)
|
||||||
if rec.Code != testCase.expectedRespStatus {
|
if rec.Code != testCase.expectedRespStatus {
|
||||||
@ -102,6 +107,38 @@ func testGetBucketLocationHandler(obj ObjectLayer, instanceType, bucketName stri
|
|||||||
if errorResponse.Code != testCase.errorResponse.Code {
|
if errorResponse.Code != testCase.errorResponse.Code {
|
||||||
t.Errorf("Test %d: %s: Expected the error code to be `%s`, but instead found `%s`", i+1, instanceType, testCase.errorResponse.Code, errorResponse.Code)
|
t.Errorf("Test %d: %s: Expected the error code to be `%s`, but instead found `%s`", i+1, instanceType, testCase.errorResponse.Code, errorResponse.Code)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Verify response of the V2 signed HTTP request.
|
||||||
|
// 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", getBucketLocationURL("", testCase.bucketName), 0, nil, 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 of the 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)
|
||||||
|
}
|
||||||
|
|
||||||
|
errorResponse = APIErrorResponse{}
|
||||||
|
err = xml.Unmarshal(recV2.Body.Bytes(), &errorResponse)
|
||||||
|
if err != nil && !testCase.shouldPass {
|
||||||
|
t.Fatalf("Test %d: %s: Unable to marshal response body %s", i+1, instanceType, string(recV2.Body.Bytes()))
|
||||||
|
}
|
||||||
|
if errorResponse.Resource != testCase.errorResponse.Resource {
|
||||||
|
t.Errorf("Test %d: %s: Expected the error resource to be `%s`, but instead found `%s`", i+1, instanceType, testCase.errorResponse.Resource, errorResponse.Resource)
|
||||||
|
}
|
||||||
|
if errorResponse.Message != testCase.errorResponse.Message {
|
||||||
|
t.Errorf("Test %d: %s: Expected the error message to be `%s`, but instead found `%s`", i+1, instanceType, testCase.errorResponse.Message, errorResponse.Message)
|
||||||
|
}
|
||||||
|
if errorResponse.Code != testCase.errorResponse.Code {
|
||||||
|
t.Errorf("Test %d: %s: Expected the error code to be `%s`, but instead found `%s`", i+1, instanceType, testCase.errorResponse.Code, errorResponse.Code)
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Test for Anonymous/unsigned http request.
|
// Test for Anonymous/unsigned http request.
|
||||||
@ -149,6 +186,7 @@ func testHeadBucketHandler(obj ObjectLayer, instanceType, bucketName string, api
|
|||||||
// expected Response.
|
// expected Response.
|
||||||
expectedRespStatus int
|
expectedRespStatus int
|
||||||
}{
|
}{
|
||||||
|
// Test case - 1.
|
||||||
// Bucket exists.
|
// Bucket exists.
|
||||||
{
|
{
|
||||||
bucketName: bucketName,
|
bucketName: bucketName,
|
||||||
@ -156,6 +194,7 @@ func testHeadBucketHandler(obj ObjectLayer, instanceType, bucketName string, api
|
|||||||
secretKey: credentials.SecretAccessKey,
|
secretKey: credentials.SecretAccessKey,
|
||||||
expectedRespStatus: http.StatusOK,
|
expectedRespStatus: http.StatusOK,
|
||||||
},
|
},
|
||||||
|
// Test case - 2.
|
||||||
// Non-existent bucket name.
|
// Non-existent bucket name.
|
||||||
{
|
{
|
||||||
bucketName: "2333",
|
bucketName: "2333",
|
||||||
@ -163,11 +202,13 @@ func testHeadBucketHandler(obj ObjectLayer, instanceType, bucketName string, api
|
|||||||
secretKey: credentials.SecretAccessKey,
|
secretKey: credentials.SecretAccessKey,
|
||||||
expectedRespStatus: http.StatusNotFound,
|
expectedRespStatus: http.StatusNotFound,
|
||||||
},
|
},
|
||||||
// Un-authenticated request.
|
// Test case - 3.
|
||||||
|
// Testing for signature mismatch error.
|
||||||
|
// setting invalid acess and secret key.
|
||||||
{
|
{
|
||||||
bucketName: bucketName,
|
bucketName: bucketName,
|
||||||
accessKey: "",
|
accessKey: "abcd",
|
||||||
secretKey: "",
|
secretKey: "abcd",
|
||||||
expectedRespStatus: http.StatusForbidden,
|
expectedRespStatus: http.StatusForbidden,
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
@ -180,12 +221,29 @@ func testHeadBucketHandler(obj ObjectLayer, instanceType, bucketName string, api
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("Test %d: %s: Failed to create HTTP request for HeadBucketHandler: <ERROR> %v", i+1, instanceType, err)
|
t.Fatalf("Test %d: %s: Failed to create HTTP request for HeadBucketHandler: <ERROR> %v", i+1, instanceType, err)
|
||||||
}
|
}
|
||||||
// Since `apiRouter` satisfies `http.Handler` it has a ServeHTTP to execute the logic ofthe handler.
|
// Since `apiRouter` satisfies `http.Handler` it has a ServeHTTP to execute the logic of the handler.
|
||||||
// Call the ServeHTTP to execute the handler.
|
// Call the ServeHTTP to execute the handler.
|
||||||
apiRouter.ServeHTTP(rec, req)
|
apiRouter.ServeHTTP(rec, req)
|
||||||
if rec.Code != testCase.expectedRespStatus {
|
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)
|
t.Errorf("Test %d: %s: Expected the response status to be `%d`, but instead found `%d`", i+1, instanceType, testCase.expectedRespStatus, rec.Code)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Verify response the V2 signed HTTP request.
|
||||||
|
// 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("HEAD", getHEADBucketURL("", testCase.bucketName), 0, nil, 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 of the 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)
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Test for Anonymous/unsigned http request.
|
// Test for Anonymous/unsigned http request.
|
||||||
@ -237,25 +295,138 @@ func testListMultipartUploadsHandler(obj ObjectLayer, instanceType, bucketName s
|
|||||||
uploadIDMarker string
|
uploadIDMarker string
|
||||||
delimiter string
|
delimiter string
|
||||||
maxUploads string
|
maxUploads string
|
||||||
|
accessKey string
|
||||||
|
secretKey string
|
||||||
expectedRespStatus int
|
expectedRespStatus int
|
||||||
shouldPass bool
|
shouldPass bool
|
||||||
}{
|
}{
|
||||||
// 1 - invalid bucket name.
|
// Test case - 1.
|
||||||
{".test", "", "", "", "", "0", http.StatusBadRequest, false},
|
// Setting invalid bucket name.
|
||||||
// 2 - bucket not found.
|
{
|
||||||
{"volatile-bucket-1", "", "", "", "", "0", http.StatusNotFound, false},
|
bucket: ".test",
|
||||||
// 3 - invalid delimiter.
|
prefix: "",
|
||||||
{bucketName, "", "", "", "-", "0", http.StatusNotImplemented, false},
|
keyMarker: "",
|
||||||
// 4 - invalid prefix and marker combination.
|
uploadIDMarker: "",
|
||||||
{bucketName, "asia", "europe-object", "", "", "0", http.StatusNotImplemented, false},
|
delimiter: "",
|
||||||
// 5 - invalid upload id and marker combination.
|
maxUploads: "0",
|
||||||
{bucketName, "asia", "asia/europe/", "abc", "", "0", http.StatusNotImplemented, false},
|
accessKey: credentials.AccessKeyID,
|
||||||
// 6 - invalid max uploads.
|
secretKey: credentials.SecretAccessKey,
|
||||||
{bucketName, "", "", "", "", "-1", http.StatusBadRequest, false},
|
expectedRespStatus: http.StatusBadRequest,
|
||||||
// 7 - good case delimiter.
|
shouldPass: false,
|
||||||
{bucketName, "", "", "", "/", "100", http.StatusOK, true},
|
},
|
||||||
// 8 - good case without delimiter.
|
// Test case - 2.
|
||||||
{bucketName, "", "", "", "", "100", http.StatusOK, true},
|
// Setting a non-existent bucket.
|
||||||
|
{
|
||||||
|
bucket: "volatile-bucket-1",
|
||||||
|
prefix: "",
|
||||||
|
keyMarker: "",
|
||||||
|
uploadIDMarker: "",
|
||||||
|
delimiter: "",
|
||||||
|
maxUploads: "0",
|
||||||
|
accessKey: credentials.AccessKeyID,
|
||||||
|
secretKey: credentials.SecretAccessKey,
|
||||||
|
expectedRespStatus: http.StatusNotFound,
|
||||||
|
shouldPass: false,
|
||||||
|
},
|
||||||
|
// Test case -3.
|
||||||
|
// Setting invalid delimiter, expecting the HTTP response status to be http.StatusNotImplemented.
|
||||||
|
{
|
||||||
|
bucket: bucketName,
|
||||||
|
prefix: "",
|
||||||
|
keyMarker: "",
|
||||||
|
uploadIDMarker: "",
|
||||||
|
delimiter: "-",
|
||||||
|
maxUploads: "0",
|
||||||
|
accessKey: credentials.AccessKeyID,
|
||||||
|
secretKey: credentials.SecretAccessKey,
|
||||||
|
expectedRespStatus: http.StatusNotImplemented,
|
||||||
|
shouldPass: false,
|
||||||
|
},
|
||||||
|
// Test case - 4.
|
||||||
|
// Setting Invalid prefix and marker combination.
|
||||||
|
{
|
||||||
|
bucket: bucketName,
|
||||||
|
prefix: "asia",
|
||||||
|
keyMarker: "europe-object",
|
||||||
|
uploadIDMarker: "",
|
||||||
|
delimiter: "",
|
||||||
|
maxUploads: "0",
|
||||||
|
accessKey: credentials.AccessKeyID,
|
||||||
|
secretKey: credentials.SecretAccessKey,
|
||||||
|
expectedRespStatus: http.StatusNotImplemented,
|
||||||
|
shouldPass: false,
|
||||||
|
},
|
||||||
|
// Test case - 5.
|
||||||
|
// Invalid upload id and marker combination.
|
||||||
|
{
|
||||||
|
bucket: bucketName,
|
||||||
|
prefix: "asia",
|
||||||
|
keyMarker: "asia/europe/",
|
||||||
|
uploadIDMarker: "abc",
|
||||||
|
delimiter: "",
|
||||||
|
maxUploads: "0",
|
||||||
|
accessKey: credentials.AccessKeyID,
|
||||||
|
secretKey: credentials.SecretAccessKey,
|
||||||
|
expectedRespStatus: http.StatusNotImplemented,
|
||||||
|
shouldPass: false,
|
||||||
|
},
|
||||||
|
// Test case - 6.
|
||||||
|
// Setting a negative value to max-uploads paramater, should result in http.StatusBadRequest.
|
||||||
|
{
|
||||||
|
bucket: bucketName,
|
||||||
|
prefix: "",
|
||||||
|
keyMarker: "",
|
||||||
|
uploadIDMarker: "",
|
||||||
|
delimiter: "",
|
||||||
|
maxUploads: "-1",
|
||||||
|
accessKey: credentials.AccessKeyID,
|
||||||
|
secretKey: credentials.SecretAccessKey,
|
||||||
|
expectedRespStatus: http.StatusBadRequest,
|
||||||
|
shouldPass: false,
|
||||||
|
},
|
||||||
|
// Test case - 7.
|
||||||
|
// Case with right set of parameters,
|
||||||
|
// should result in success 200OK.
|
||||||
|
{
|
||||||
|
bucket: bucketName,
|
||||||
|
prefix: "",
|
||||||
|
keyMarker: "",
|
||||||
|
uploadIDMarker: "",
|
||||||
|
delimiter: "/",
|
||||||
|
maxUploads: "100",
|
||||||
|
accessKey: credentials.AccessKeyID,
|
||||||
|
secretKey: credentials.SecretAccessKey,
|
||||||
|
expectedRespStatus: http.StatusOK,
|
||||||
|
shouldPass: true,
|
||||||
|
},
|
||||||
|
// Test case - 8.
|
||||||
|
// Good case without delimiter.
|
||||||
|
{
|
||||||
|
bucket: bucketName,
|
||||||
|
prefix: "",
|
||||||
|
keyMarker: "",
|
||||||
|
uploadIDMarker: "",
|
||||||
|
delimiter: "",
|
||||||
|
maxUploads: "100",
|
||||||
|
accessKey: credentials.AccessKeyID,
|
||||||
|
secretKey: credentials.SecretAccessKey,
|
||||||
|
expectedRespStatus: http.StatusOK,
|
||||||
|
shouldPass: true,
|
||||||
|
},
|
||||||
|
// Test case - 9.
|
||||||
|
// Setting Invalid AccessKey and SecretKey to induce and verify Signature Mismatch error.
|
||||||
|
{
|
||||||
|
bucket: bucketName,
|
||||||
|
prefix: "",
|
||||||
|
keyMarker: "",
|
||||||
|
uploadIDMarker: "",
|
||||||
|
delimiter: "",
|
||||||
|
maxUploads: "100",
|
||||||
|
accessKey: "abcd",
|
||||||
|
secretKey: "abcd",
|
||||||
|
expectedRespStatus: http.StatusForbidden,
|
||||||
|
shouldPass: true,
|
||||||
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
for i, testCase := range testCases {
|
for i, testCase := range testCases {
|
||||||
@ -264,16 +435,34 @@ func testListMultipartUploadsHandler(obj ObjectLayer, instanceType, bucketName s
|
|||||||
|
|
||||||
// construct HTTP request for List multipart uploads endpoint.
|
// construct HTTP request for List multipart uploads endpoint.
|
||||||
u := getListMultipartUploadsURLWithParams("", testCase.bucket, testCase.prefix, testCase.keyMarker, testCase.uploadIDMarker, testCase.delimiter, testCase.maxUploads)
|
u := getListMultipartUploadsURLWithParams("", testCase.bucket, testCase.prefix, testCase.keyMarker, testCase.uploadIDMarker, testCase.delimiter, testCase.maxUploads)
|
||||||
req, gerr := newTestSignedRequestV4("GET", u, 0, nil, credentials.AccessKeyID, credentials.SecretAccessKey)
|
req, gerr := newTestSignedRequestV4("GET", u, 0, nil, testCase.accessKey, testCase.secretKey)
|
||||||
if gerr != nil {
|
if gerr != nil {
|
||||||
t.Fatalf("Test %d: %s: Failed to create HTTP request for ListMultipartUploadsHandler: <ERROR> %v", i+1, instanceType, gerr)
|
t.Fatalf("Test %d: %s: Failed to create HTTP request for ListMultipartUploadsHandler: <ERROR> %v", i+1, instanceType, gerr)
|
||||||
}
|
}
|
||||||
// Since `apiRouter` satisfies `http.Handler` it has a ServeHTTP to execute the logic ofthe handler.
|
// Since `apiRouter` satisfies `http.Handler` it has a ServeHTTP to execute the logic of the handler.
|
||||||
// Call the ServeHTTP to execute the handler.
|
// Call the ServeHTTP to execute the handler.
|
||||||
apiRouter.ServeHTTP(rec, req)
|
apiRouter.ServeHTTP(rec, req)
|
||||||
if rec.Code != testCase.expectedRespStatus {
|
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)
|
t.Errorf("Test %d: %s: Expected the response status to be `%d`, but instead found `%d`", i+1, instanceType, testCase.expectedRespStatus, rec.Code)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Verify response the V2 signed HTTP request.
|
||||||
|
// initialize HTTP NewRecorder, this records any mutations to response writer inside the handler.
|
||||||
|
recV2 := httptest.NewRecorder()
|
||||||
|
// construct HTTP request for PUT bucket policy endpoint.
|
||||||
|
|
||||||
|
// verify response for V2 signed HTTP request.
|
||||||
|
reqV2, err := newTestSignedRequestV2("GET", u, 0, nil, 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 of the 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)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// initialize HTTP NewRecorder, this records any mutations to response writer inside the handler.
|
// initialize HTTP NewRecorder, this records any mutations to response writer inside the handler.
|
||||||
@ -285,7 +474,7 @@ func testListMultipartUploadsHandler(obj ObjectLayer, instanceType, bucketName s
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("Test %s: Failed to create HTTP request for ListMultipartUploadsHandler: <ERROR> %v", instanceType, err)
|
t.Fatalf("Test %s: Failed to create HTTP request for ListMultipartUploadsHandler: <ERROR> %v", instanceType, err)
|
||||||
}
|
}
|
||||||
// Since `apiRouter` satisfies `http.Handler` it has a ServeHTTP to execute the logic ofthe handler.
|
// Since `apiRouter` satisfies `http.Handler` it has a ServeHTTP to execute the logic of the handler.
|
||||||
// Call the ServeHTTP to execute the handler.
|
// Call the ServeHTTP to execute the handler.
|
||||||
apiRouter.ServeHTTP(rec, req)
|
apiRouter.ServeHTTP(rec, req)
|
||||||
if rec.Code != http.StatusForbidden {
|
if rec.Code != http.StatusForbidden {
|
||||||
@ -340,6 +529,7 @@ func testListBucketsHandler(obj ObjectLayer, instanceType, bucketName string, ap
|
|||||||
secretKey string
|
secretKey string
|
||||||
expectedRespStatus int
|
expectedRespStatus int
|
||||||
}{
|
}{
|
||||||
|
// Test case - 1.
|
||||||
// Validate a good case request succeeds.
|
// Validate a good case request succeeds.
|
||||||
{
|
{
|
||||||
bucketName: bucketName,
|
bucketName: bucketName,
|
||||||
@ -347,11 +537,12 @@ func testListBucketsHandler(obj ObjectLayer, instanceType, bucketName string, ap
|
|||||||
secretKey: credentials.SecretAccessKey,
|
secretKey: credentials.SecretAccessKey,
|
||||||
expectedRespStatus: http.StatusOK,
|
expectedRespStatus: http.StatusOK,
|
||||||
},
|
},
|
||||||
// Validate a bad case request fails with http.StatusForbidden.
|
// Test case - 2.
|
||||||
|
// Test case with invalid accessKey to produce and validate Signature MisMatch error.
|
||||||
{
|
{
|
||||||
bucketName: bucketName,
|
bucketName: bucketName,
|
||||||
accessKey: "",
|
accessKey: "abcd",
|
||||||
secretKey: "",
|
secretKey: "abcd",
|
||||||
expectedRespStatus: http.StatusForbidden,
|
expectedRespStatus: http.StatusForbidden,
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
@ -363,12 +554,30 @@ func testListBucketsHandler(obj ObjectLayer, instanceType, bucketName string, ap
|
|||||||
if lerr != nil {
|
if lerr != nil {
|
||||||
t.Fatalf("Test %d: %s: Failed to create HTTP request for ListBucketsHandler: <ERROR> %v", i+1, instanceType, lerr)
|
t.Fatalf("Test %d: %s: Failed to create HTTP request for ListBucketsHandler: <ERROR> %v", i+1, instanceType, lerr)
|
||||||
}
|
}
|
||||||
// Since `apiRouter` satisfies `http.Handler` it has a ServeHTTP to execute the logic ofthe handler.
|
// Since `apiRouter` satisfies `http.Handler` it has a ServeHTTP to execute the logic of the handler.
|
||||||
// Call the ServeHTTP to execute the handler.
|
// Call the ServeHTTP to execute the handler.
|
||||||
apiRouter.ServeHTTP(rec, req)
|
apiRouter.ServeHTTP(rec, req)
|
||||||
if rec.Code != testCase.expectedRespStatus {
|
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)
|
t.Errorf("Test %d: %s: Expected the response status to be `%d`, but instead found `%d`", i+1, instanceType, testCase.expectedRespStatus, rec.Code)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Verify response of the V2 signed HTTP request.
|
||||||
|
// initialize HTTP NewRecorder, this records any mutations to response writer inside the handler.
|
||||||
|
recV2 := httptest.NewRecorder()
|
||||||
|
// construct HTTP request for PUT bucket policy endpoint.
|
||||||
|
|
||||||
|
// verify response for V2 signed HTTP request.
|
||||||
|
reqV2, err := newTestSignedRequestV2("GET", getListBucketURL(""), 0, nil, 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 of the 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)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Test for Anonymous/unsigned http request.
|
// Test for Anonymous/unsigned http request.
|
||||||
|
@ -140,11 +140,44 @@ func doesPresignV2SignatureMatch(r *http.Request) APIErrorCode {
|
|||||||
// doesSignV2Match - Verify authorization header with calculated header in accordance with
|
// doesSignV2Match - Verify authorization header with calculated header in accordance with
|
||||||
// - http://docs.aws.amazon.com/AmazonS3/latest/dev/auth-request-sig-v2.html
|
// - 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
|
// returns true if matches, false otherwise. if error is not nil then it is always false
|
||||||
func doesSignV2Match(r *http.Request) APIErrorCode {
|
|
||||||
gotAuth := r.Header.Get("Authorization")
|
func validateV2AuthHeader(v2Auth string) APIErrorCode {
|
||||||
if gotAuth == "" {
|
if v2Auth == "" {
|
||||||
return ErrAuthHeaderEmpty
|
return ErrAuthHeaderEmpty
|
||||||
}
|
}
|
||||||
|
// Verify if the header algorithm is supported or not.
|
||||||
|
if !strings.HasPrefix(v2Auth, signV2Algorithm) {
|
||||||
|
return ErrSignatureVersionNotSupported
|
||||||
|
}
|
||||||
|
|
||||||
|
// below is V2 Signed Auth header format, splitting on `space` (after the `AWS` string).
|
||||||
|
// Authorization = "AWS" + " " + AWSAccessKeyId + ":" + Signature
|
||||||
|
authFields := strings.Split(v2Auth, " ")
|
||||||
|
if len(authFields) != 2 {
|
||||||
|
return ErrMissingFields
|
||||||
|
}
|
||||||
|
|
||||||
|
// Then will be splitting on ":", this will seprate `AWSAccessKeyId` and `Signature` string.
|
||||||
|
keySignFields := strings.Split(strings.TrimSpace(authFields[1]), ":")
|
||||||
|
if len(keySignFields) != 2 {
|
||||||
|
return ErrMissingFields
|
||||||
|
}
|
||||||
|
|
||||||
|
// Access credentials.
|
||||||
|
cred := serverConfig.GetCredential()
|
||||||
|
if keySignFields[0] != cred.AccessKeyID {
|
||||||
|
return ErrInvalidAccessKeyID
|
||||||
|
}
|
||||||
|
|
||||||
|
return ErrNone
|
||||||
|
}
|
||||||
|
|
||||||
|
func doesSignV2Match(r *http.Request) APIErrorCode {
|
||||||
|
v2Auth := r.Header.Get("Authorization")
|
||||||
|
|
||||||
|
if apiError := validateV2AuthHeader(v2Auth); apiError != ErrNone {
|
||||||
|
return apiError
|
||||||
|
}
|
||||||
|
|
||||||
// url.RawPath will be valid if path has any encoded characters, if not it will
|
// url.RawPath will be valid if path has any encoded characters, if not it will
|
||||||
// be empty - in which case we need to consider url.Path (bug in net/http?)
|
// be empty - in which case we need to consider url.Path (bug in net/http?)
|
||||||
@ -158,7 +191,7 @@ func doesSignV2Match(r *http.Request) APIErrorCode {
|
|||||||
}
|
}
|
||||||
|
|
||||||
expectedAuth := signatureV2(r.Method, encodedResource, encodedQuery, r.Header)
|
expectedAuth := signatureV2(r.Method, encodedResource, encodedQuery, r.Header)
|
||||||
if gotAuth != expectedAuth {
|
if v2Auth != expectedAuth {
|
||||||
return ErrSignatureDoesNotMatch
|
return ErrSignatureDoesNotMatch
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -104,3 +104,79 @@ func TestDoesPresignedV2SignatureMatch(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// TestValidateV2AuthHeader - Tests validate the logic of V2 Authorization header validator.
|
||||||
|
func TestValidateV2AuthHeader(t *testing.T) {
|
||||||
|
// Initialize server config.
|
||||||
|
if err := initConfig(); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Save config.
|
||||||
|
if err := serverConfig.Save(); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
accessID := serverConfig.GetCredential().AccessKeyID
|
||||||
|
|
||||||
|
testCases := []struct {
|
||||||
|
authString string
|
||||||
|
expectedError APIErrorCode
|
||||||
|
}{
|
||||||
|
// Test case - 1.
|
||||||
|
// Case with empty V2AuthString.
|
||||||
|
{
|
||||||
|
|
||||||
|
authString: "",
|
||||||
|
expectedError: ErrAuthHeaderEmpty,
|
||||||
|
},
|
||||||
|
// Test case - 2.
|
||||||
|
// Test case with `signV2Algorithm` ("AWS") not being the prefix.
|
||||||
|
{
|
||||||
|
|
||||||
|
authString: "NoV2Prefix",
|
||||||
|
expectedError: ErrSignatureVersionNotSupported,
|
||||||
|
},
|
||||||
|
// Test case - 3.
|
||||||
|
// Test case with missing parts in the Auth string.
|
||||||
|
// below is the correct format of V2 Authorization header.
|
||||||
|
// Authorization = "AWS" + " " + AWSAccessKeyId + ":" + Signature
|
||||||
|
{
|
||||||
|
|
||||||
|
authString: signV2Algorithm,
|
||||||
|
expectedError: ErrMissingFields,
|
||||||
|
},
|
||||||
|
// Test case - 4.
|
||||||
|
// Test case with signature part missing.
|
||||||
|
{
|
||||||
|
|
||||||
|
authString: fmt.Sprintf("%s %s", signV2Algorithm, accessID),
|
||||||
|
expectedError: ErrMissingFields,
|
||||||
|
},
|
||||||
|
// Test case - 5.
|
||||||
|
// Test case with wrong accessID.
|
||||||
|
{
|
||||||
|
|
||||||
|
authString: fmt.Sprintf("%s %s:%s", signV2Algorithm, "InvalidAccessID", "signature"),
|
||||||
|
expectedError: ErrInvalidAccessKeyID,
|
||||||
|
},
|
||||||
|
// Test case - 6.
|
||||||
|
// Case with right accessID and format.
|
||||||
|
{
|
||||||
|
|
||||||
|
authString: fmt.Sprintf("%s %s:%s", signV2Algorithm, accessID, "signature"),
|
||||||
|
expectedError: ErrNone,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for i, testCase := range testCases {
|
||||||
|
t.Run(fmt.Sprintf("Case %d AuthStr \"%s\".", i+1, testCase.authString), func(t *testing.T) {
|
||||||
|
|
||||||
|
actualErrCode := validateV2AuthHeader(testCase.authString)
|
||||||
|
|
||||||
|
if testCase.expectedError != actualErrCode {
|
||||||
|
t.Errorf("Expected the error code to be %v, got %v.", testCase.expectedError, actualErrCode)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user