From b2a0e5754bc1738ef106d5277e4eb7b31b8be3bd Mon Sep 17 00:00:00 2001 From: Anis Elleuch Date: Sun, 4 Dec 2016 21:23:19 +0100 Subject: [PATCH] bucket-handlers: More tests for post form handler (#3392) --- cmd/post-policy_test.go | 173 ++++++++++++++++++++++++++++++++-------- 1 file changed, 139 insertions(+), 34 deletions(-) diff --git a/cmd/post-policy_test.go b/cmd/post-policy_test.go index a7c734adc..456302b62 100644 --- a/cmd/post-policy_test.go +++ b/cmd/post-policy_test.go @@ -42,7 +42,7 @@ func newPostPolicyBytesV4WithContentRange(credential, bucketName, objectKey stri // Add the bucket condition, only accept buckets equal to the one passed. bucketConditionStr := fmt.Sprintf(`["eq", "$bucket", "%s"]`, bucketName) // Add the key condition, only accept keys equal to the one passed. - keyConditionStr := fmt.Sprintf(`["eq", "$key", "%s"]`, objectKey) + keyConditionStr := fmt.Sprintf(`["eq", "$key", "%s/upload.txt"]`, objectKey) // Add content length condition, only accept content sizes of a given length. contentLengthCondStr := `["content-length-range", 1024, 1048576]` // Add the algorithm condition, only accept AWS SignV4 Sha256. @@ -71,7 +71,7 @@ func newPostPolicyBytesV4(credential, bucketName, objectKey string, expiration t // Add the bucket condition, only accept buckets equal to the one passed. bucketConditionStr := fmt.Sprintf(`["eq", "$bucket", "%s"]`, bucketName) // Add the key condition, only accept keys equal to the one passed. - keyConditionStr := fmt.Sprintf(`["eq", "$key", "%s"]`, objectKey) + keyConditionStr := fmt.Sprintf(`["eq", "$key", "%s/upload.txt"]`, objectKey) // Add the algorithm condition, only accept AWS SignV4 Sha256. algorithmConditionStr := `["eq", "$x-amz-algorithm", "AWS4-HMAC-SHA256"]` // Add the date condition, only accept the current date. @@ -96,7 +96,7 @@ func newPostPolicyBytesV2(bucketName, objectKey string, expiration time.Time) [] // Add the bucket condition, only accept buckets equal to the one passed. bucketConditionStr := fmt.Sprintf(`["eq", "$bucket", "%s"]`, bucketName) // Add the key condition, only accept keys equal to the one passed. - keyConditionStr := fmt.Sprintf(`["eq", "$key", "%s"]`, objectKey) + keyConditionStr := fmt.Sprintf(`["starts-with", "$key", "%s/upload.txt"]`, objectKey) // Combine all conditions into one string. conditionStr := fmt.Sprintf(`"conditions":[%s, %s]`, bucketConditionStr, keyConditionStr) @@ -108,13 +108,13 @@ func newPostPolicyBytesV2(bucketName, objectKey string, expiration time.Time) [] return []byte(retStr) } -// Wrapper for calling TestPostPolicyHandlerHandler tests for both XL multiple disks and single node setup. -func TestPostPolicyHandler(t *testing.T) { - ExecObjectLayerTest(t, testPostPolicyHandler) +// Wrapper for calling TestPostPolicyBucketHandler tests for both XL multiple disks and single node setup. +func TestPostPolicyBucketHandler(t *testing.T) { + ExecObjectLayerTest(t, testPostPolicyBucketHandler) } -// testPostPolicyHandler - Tests validate post policy handler uploading objects. -func testPostPolicyHandler(obj ObjectLayer, instanceType string, t TestErrHandler) { +// testPostPolicyBucketHandler - Tests validate post policy handler uploading objects. +func testPostPolicyBucketHandler(obj ObjectLayer, instanceType string, t TestErrHandler) { root, err := newTestConfig("us-east-1") if err != nil { t.Fatalf("Initializing config.json failed") @@ -133,17 +133,11 @@ func testPostPolicyHandler(obj ObjectLayer, instanceType string, t TestErrHandle // Register the API end points with XL/FS object layer. apiRouter := initTestAPIEndPoints(obj, []string{"PostPolicy"}) - // initialize the server and obtain the credentials and root. - // credentials are necessary to sign the HTTP request. - rootPath, err := newTestConfig("us-east-1") - if err != nil { - t.Fatalf("Init Test config failed") - } - // remove the root directory after the test ends. - defer removeAll(rootPath) - credentials := serverConfig.GetCredential() + curTime := time.Now().UTC() + curTimePlus5Min := curTime.Add(time.Minute * 5) + // bucketnames[0]. // objectNames[0]. // uploadIds [0]. @@ -237,6 +231,102 @@ func testPostPolicyHandler(obj ObjectLayer, instanceType string, t TestErrHandle } } + // Test cases for signature-V4. + testCasesV4BadData := []struct { + objectName string + data []byte + expectedRespStatus int + accessKey string + secretKey string + dates []interface{} + policy string + corruptedBase64 bool + corruptedMultipart bool + }{ + // Success case. + { + objectName: "test", + data: []byte("Hello, World"), + expectedRespStatus: http.StatusNoContent, + accessKey: credentials.AccessKeyID, + secretKey: credentials.SecretAccessKey, + dates: []interface{}{curTimePlus5Min.Format(expirationDateFormat), curTime.Format(iso8601DateFormat), curTime.Format(yyyymmdd)}, + policy: `{"expiration": "%s","conditions":[["eq", "$bucket", "` + bucketName + `"], ["starts-with", "$key", "test/"], ["eq", "$x-amz-algorithm", "AWS4-HMAC-SHA256"], ["eq", "$x-amz-date", "%s"], ["eq", "$x-amz-credential", "` + credentials.AccessKeyID + `/%s/us-east-1/s3/aws4_request"]]}`, + }, + // Corrupted Base 64 result + { + objectName: "test", + data: []byte("Hello, World"), + expectedRespStatus: http.StatusBadRequest, + accessKey: credentials.AccessKeyID, + secretKey: credentials.SecretAccessKey, + dates: []interface{}{curTimePlus5Min.Format(expirationDateFormat), curTime.Format(iso8601DateFormat), curTime.Format(yyyymmdd)}, + policy: `{"expiration": "%s","conditions":[["eq", "$bucket", "` + bucketName + `"], ["starts-with", "$key", "test/"], ["eq", "$x-amz-algorithm", "AWS4-HMAC-SHA256"], ["eq", "$x-amz-date", "%s"], ["eq", "$x-amz-credential", "` + credentials.AccessKeyID + `/%s/us-east-1/s3/aws4_request"]]}`, + corruptedBase64: true, + }, + // Corrupted Multipart body + { + objectName: "test", + data: []byte("Hello, World"), + expectedRespStatus: http.StatusBadRequest, + accessKey: credentials.AccessKeyID, + secretKey: credentials.SecretAccessKey, + dates: []interface{}{curTimePlus5Min.Format(expirationDateFormat), curTime.Format(iso8601DateFormat), curTime.Format(yyyymmdd)}, + policy: `{"expiration": "%s","conditions":[["eq", "$bucket", "` + bucketName + `"], ["starts-with", "$key", "test/"], ["eq", "$x-amz-algorithm", "AWS4-HMAC-SHA256"], ["eq", "$x-amz-date", "%s"], ["eq", "$x-amz-credential", "` + credentials.AccessKeyID + `/%s/us-east-1/s3/aws4_request"]]}`, + corruptedMultipart: true, + }, + + // Bad case invalid request. + { + objectName: "test", + data: []byte("Hello, World"), + expectedRespStatus: http.StatusBadRequest, + accessKey: "", + secretKey: "", + dates: []interface{}{}, + policy: ``, + }, + // Expired document + { + objectName: "test", + data: []byte("Hello, World"), + expectedRespStatus: http.StatusBadRequest, + accessKey: credentials.AccessKeyID, + secretKey: credentials.SecretAccessKey, + dates: []interface{}{curTime.Add(-1 * time.Minute * 5).Format(expirationDateFormat), curTime.Format(iso8601DateFormat), curTime.Format(yyyymmdd)}, + policy: `{"expiration": "%s","conditions":[["eq", "$bucket", "` + bucketName + `"], ["starts-with", "$key", "test/"], ["eq", "$x-amz-algorithm", "AWS4-HMAC-SHA256"], ["eq", "$x-amz-date", "%s"], ["eq", "$x-amz-credential", "` + credentials.AccessKeyID + `/%s/us-east-1/s3/aws4_request"]]}`, + }, + // Corrupted policy document + { + objectName: "test", + data: []byte("Hello, World"), + expectedRespStatus: http.StatusBadRequest, + accessKey: credentials.AccessKeyID, + secretKey: credentials.SecretAccessKey, + dates: []interface{}{curTimePlus5Min.Format(expirationDateFormat), curTime.Format(iso8601DateFormat), curTime.Format(yyyymmdd)}, + policy: `{"3/aws4_request"]]}`, + }, + } + + for i, testCase := range testCasesV4BadData { + // initialize HTTP NewRecorder, this records any mutations to response writer inside the handler. + rec := httptest.NewRecorder() + + // policy := buildGenericPolicy(curTime, testCase.accessKey, bucketName, testCase.objectName, false) + testCase.policy = fmt.Sprintf(testCase.policy, testCase.dates...) + req, perr := newPostRequestV4Generic("", bucketName, testCase.objectName, testCase.data, testCase.accessKey, + testCase.secretKey, curTime, []byte(testCase.policy), testCase.corruptedBase64, testCase.corruptedMultipart) + if perr != nil { + t.Fatalf("Test %d: %s: Failed to create HTTP request for PostPolicyHandler: %v", i+1, instanceType, perr) + } + // 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) + } + } + testCases2 := []struct { objectName string data []byte @@ -314,7 +404,7 @@ func newPostRequestV2(endPoint, bucketName, objectName string, accessKey, secret formData := map[string]string{ "AWSAccessKeyId": accessKey, "bucket": bucketName, - "key": objectName, + "key": objectName + "/${filename}", "policy": encodedPolicy, "signature": signature, } @@ -328,7 +418,7 @@ func newPostRequestV2(endPoint, bucketName, objectName string, accessKey, secret w.WriteField(k, v) } // Set the File formData - writer, err := w.CreateFormFile("file", "s3verify/post/object") + writer, err := w.CreateFormFile("file", "upload.txt") if err != nil { // return nil, err return nil, err @@ -350,27 +440,36 @@ func newPostRequestV2(endPoint, bucketName, objectName string, accessKey, secret return req, nil } -func newPostRequestV4Generic(endPoint, bucketName, objectName string, objData []byte, accessKey, secretKey string, contentLengthRange bool) (*http.Request, error) { - // Keep time. - t := time.Now().UTC() +func buildGenericPolicy(t time.Time, accessKey, bucketName, objectName string, contentLengthRange bool) []byte { // Expire the request five minutes from now. expirationTime := t.Add(time.Minute * 5) - // Get the user credential. + credStr := getCredential(accessKey, serverConfig.GetRegion(), t) // Create a new post policy. policy := newPostPolicyBytesV4(credStr, bucketName, objectName, expirationTime) if contentLengthRange { policy = newPostPolicyBytesV4WithContentRange(credStr, bucketName, objectName, expirationTime) } + return policy +} + +func newPostRequestV4Generic(endPoint, bucketName, objectName string, objData []byte, accessKey, secretKey string, t time.Time, policy []byte, corruptedB64 bool, corruptedMultipart bool) (*http.Request, error) { + // Get the user credential. + credStr := getCredential(accessKey, serverConfig.GetRegion(), t) + // Only need the encoding. encodedPolicy := base64.StdEncoding.EncodeToString(policy) + if corruptedB64 { + encodedPolicy = "%!~&" + encodedPolicy + } + // Presign with V4 signature based on the policy. signature := postPresignSignatureV4(encodedPolicy, t, secretKey, serverConfig.GetRegion()) formData := map[string]string{ "bucket": bucketName, - "key": objectName, + "key": objectName + "/${filename}", "x-amz-credential": credStr, "policy": encodedPolicy, "x-amz-signature": signature, @@ -386,15 +485,17 @@ func newPostRequestV4Generic(endPoint, bucketName, objectName string, objData [] for k, v := range formData { w.WriteField(k, v) } - // Set the File formData - writer, err := w.CreateFormFile("file", "s3verify/post/object") - if err != nil { - // return nil, err - return nil, err + // Set the File formData but don't if we want send an incomplete multipart request + if !corruptedMultipart { + writer, err := w.CreateFormFile("file", "upload.txt") + if err != nil { + // return nil, err + return nil, err + } + writer.Write(objData) + // Close before creating the new request. + w.Close() } - writer.Write(objData) - // Close before creating the new request. - w.Close() // Set the body equal to the created policy. reader := bytes.NewReader(buf.Bytes()) @@ -410,9 +511,13 @@ func newPostRequestV4Generic(endPoint, bucketName, objectName string, objData [] } func newPostRequestV4WithContentLength(endPoint, bucketName, objectName string, objData []byte, accessKey, secretKey string) (*http.Request, error) { - return newPostRequestV4Generic(endPoint, bucketName, objectName, objData, accessKey, secretKey, true) + t := time.Now().UTC() + policy := buildGenericPolicy(t, accessKey, bucketName, objectName, true) + return newPostRequestV4Generic(endPoint, bucketName, objectName, objData, accessKey, secretKey, t, policy, false, false) } func newPostRequestV4(endPoint, bucketName, objectName string, objData []byte, accessKey, secretKey string) (*http.Request, error) { - return newPostRequestV4Generic(endPoint, bucketName, objectName, objData, accessKey, secretKey, false) + t := time.Now().UTC() + policy := buildGenericPolicy(t, accessKey, bucketName, objectName, false) + return newPostRequestV4Generic(endPoint, bucketName, objectName, objData, accessKey, secretKey, t, policy, false, false) }