aboutsummaryrefslogtreecommitdiff
path: root/weed/s3api/s3_sse_http_test.go
diff options
context:
space:
mode:
Diffstat (limited to 'weed/s3api/s3_sse_http_test.go')
-rw-r--r--weed/s3api/s3_sse_http_test.go401
1 files changed, 401 insertions, 0 deletions
diff --git a/weed/s3api/s3_sse_http_test.go b/weed/s3api/s3_sse_http_test.go
new file mode 100644
index 000000000..95f141ca7
--- /dev/null
+++ b/weed/s3api/s3_sse_http_test.go
@@ -0,0 +1,401 @@
+package s3api
+
+import (
+ "bytes"
+ "net/http"
+ "net/http/httptest"
+ "testing"
+
+ "github.com/seaweedfs/seaweedfs/weed/s3api/s3_constants"
+)
+
+// TestPutObjectWithSSEC tests PUT object with SSE-C through HTTP handler
+func TestPutObjectWithSSEC(t *testing.T) {
+ keyPair := GenerateTestSSECKey(1)
+ testData := "Hello, SSE-C PUT object!"
+
+ // Create HTTP request
+ req := CreateTestHTTPRequest("PUT", "/test-bucket/test-object", []byte(testData))
+ SetupTestSSECHeaders(req, keyPair)
+ SetupTestMuxVars(req, map[string]string{
+ "bucket": "test-bucket",
+ "object": "test-object",
+ })
+
+ // Create response recorder
+ w := CreateTestHTTPResponse()
+
+ // Test header validation
+ err := ValidateSSECHeaders(req)
+ if err != nil {
+ t.Fatalf("Header validation failed: %v", err)
+ }
+
+ // Parse SSE-C headers
+ customerKey, err := ParseSSECHeaders(req)
+ if err != nil {
+ t.Fatalf("Failed to parse SSE-C headers: %v", err)
+ }
+
+ if customerKey == nil {
+ t.Fatal("Expected customer key, got nil")
+ }
+
+ // Verify parsed key matches input
+ if !bytes.Equal(customerKey.Key, keyPair.Key) {
+ t.Error("Parsed key doesn't match input key")
+ }
+
+ if customerKey.KeyMD5 != keyPair.KeyMD5 {
+ t.Errorf("Parsed key MD5 doesn't match: expected %s, got %s", keyPair.KeyMD5, customerKey.KeyMD5)
+ }
+
+ // Simulate setting response headers
+ w.Header().Set(s3_constants.AmzServerSideEncryptionCustomerAlgorithm, "AES256")
+ w.Header().Set(s3_constants.AmzServerSideEncryptionCustomerKeyMD5, keyPair.KeyMD5)
+
+ // Verify response headers
+ AssertSSECHeaders(t, w, keyPair)
+}
+
+// TestGetObjectWithSSEC tests GET object with SSE-C through HTTP handler
+func TestGetObjectWithSSEC(t *testing.T) {
+ keyPair := GenerateTestSSECKey(1)
+
+ // Create HTTP request for GET
+ req := CreateTestHTTPRequest("GET", "/test-bucket/test-object", nil)
+ SetupTestSSECHeaders(req, keyPair)
+ SetupTestMuxVars(req, map[string]string{
+ "bucket": "test-bucket",
+ "object": "test-object",
+ })
+
+ // Create response recorder
+ w := CreateTestHTTPResponse()
+
+ // Test that SSE-C is detected for GET requests
+ if !IsSSECRequest(req) {
+ t.Error("Should detect SSE-C request for GET with SSE-C headers")
+ }
+
+ // Validate headers
+ err := ValidateSSECHeaders(req)
+ if err != nil {
+ t.Fatalf("Header validation failed: %v", err)
+ }
+
+ // Simulate response with SSE-C headers
+ w.Header().Set(s3_constants.AmzServerSideEncryptionCustomerAlgorithm, "AES256")
+ w.Header().Set(s3_constants.AmzServerSideEncryptionCustomerKeyMD5, keyPair.KeyMD5)
+ w.WriteHeader(http.StatusOK)
+
+ // Verify response
+ if w.Code != http.StatusOK {
+ t.Errorf("Expected status 200, got %d", w.Code)
+ }
+
+ AssertSSECHeaders(t, w, keyPair)
+}
+
+// TestPutObjectWithSSEKMS tests PUT object with SSE-KMS through HTTP handler
+func TestPutObjectWithSSEKMS(t *testing.T) {
+ kmsKey := SetupTestKMS(t)
+ defer kmsKey.Cleanup()
+
+ testData := "Hello, SSE-KMS PUT object!"
+
+ // Create HTTP request
+ req := CreateTestHTTPRequest("PUT", "/test-bucket/test-object", []byte(testData))
+ SetupTestSSEKMSHeaders(req, kmsKey.KeyID)
+ SetupTestMuxVars(req, map[string]string{
+ "bucket": "test-bucket",
+ "object": "test-object",
+ })
+
+ // Create response recorder
+ w := CreateTestHTTPResponse()
+
+ // Test that SSE-KMS is detected
+ if !IsSSEKMSRequest(req) {
+ t.Error("Should detect SSE-KMS request")
+ }
+
+ // Parse SSE-KMS headers
+ sseKmsKey, err := ParseSSEKMSHeaders(req)
+ if err != nil {
+ t.Fatalf("Failed to parse SSE-KMS headers: %v", err)
+ }
+
+ if sseKmsKey == nil {
+ t.Fatal("Expected SSE-KMS key, got nil")
+ }
+
+ if sseKmsKey.KeyID != kmsKey.KeyID {
+ t.Errorf("Parsed key ID doesn't match: expected %s, got %s", kmsKey.KeyID, sseKmsKey.KeyID)
+ }
+
+ // Simulate setting response headers
+ w.Header().Set(s3_constants.AmzServerSideEncryption, "aws:kms")
+ w.Header().Set(s3_constants.AmzServerSideEncryptionAwsKmsKeyId, kmsKey.KeyID)
+
+ // Verify response headers
+ AssertSSEKMSHeaders(t, w, kmsKey.KeyID)
+}
+
+// TestGetObjectWithSSEKMS tests GET object with SSE-KMS through HTTP handler
+func TestGetObjectWithSSEKMS(t *testing.T) {
+ kmsKey := SetupTestKMS(t)
+ defer kmsKey.Cleanup()
+
+ // Create HTTP request for GET (no SSE headers needed for GET)
+ req := CreateTestHTTPRequest("GET", "/test-bucket/test-object", nil)
+ SetupTestMuxVars(req, map[string]string{
+ "bucket": "test-bucket",
+ "object": "test-object",
+ })
+
+ // Create response recorder
+ w := CreateTestHTTPResponse()
+
+ // Simulate response with SSE-KMS headers (would come from stored metadata)
+ w.Header().Set(s3_constants.AmzServerSideEncryption, "aws:kms")
+ w.Header().Set(s3_constants.AmzServerSideEncryptionAwsKmsKeyId, kmsKey.KeyID)
+ w.WriteHeader(http.StatusOK)
+
+ // Verify response
+ if w.Code != http.StatusOK {
+ t.Errorf("Expected status 200, got %d", w.Code)
+ }
+
+ AssertSSEKMSHeaders(t, w, kmsKey.KeyID)
+}
+
+// TestSSECRangeRequestSupport tests that range requests are now supported for SSE-C
+func TestSSECRangeRequestSupport(t *testing.T) {
+ keyPair := GenerateTestSSECKey(1)
+
+ // Create HTTP request with Range header
+ req := CreateTestHTTPRequest("GET", "/test-bucket/test-object", nil)
+ req.Header.Set("Range", "bytes=0-100")
+ SetupTestSSECHeaders(req, keyPair)
+ SetupTestMuxVars(req, map[string]string{
+ "bucket": "test-bucket",
+ "object": "test-object",
+ })
+
+ // Create a mock proxy response with SSE-C headers
+ proxyResponse := httptest.NewRecorder()
+ proxyResponse.Header().Set(s3_constants.AmzServerSideEncryptionCustomerAlgorithm, "AES256")
+ proxyResponse.Header().Set(s3_constants.AmzServerSideEncryptionCustomerKeyMD5, keyPair.KeyMD5)
+ proxyResponse.Header().Set("Content-Length", "1000")
+
+ // Test the detection logic - these should all still work
+
+ // Should detect as SSE-C request
+ if !IsSSECRequest(req) {
+ t.Error("Should detect SSE-C request")
+ }
+
+ // Should detect range request
+ if req.Header.Get("Range") == "" {
+ t.Error("Range header should be present")
+ }
+
+ // The combination should now be allowed and handled by the filer layer
+ // Range requests with SSE-C are now supported since IV is stored in metadata
+}
+
+// TestSSEHeaderConflicts tests conflicting SSE headers
+func TestSSEHeaderConflicts(t *testing.T) {
+ testCases := []struct {
+ name string
+ setupFn func(*http.Request)
+ valid bool
+ }{
+ {
+ name: "SSE-C and SSE-KMS conflict",
+ setupFn: func(req *http.Request) {
+ keyPair := GenerateTestSSECKey(1)
+ SetupTestSSECHeaders(req, keyPair)
+ SetupTestSSEKMSHeaders(req, "test-key-id")
+ },
+ valid: false,
+ },
+ {
+ name: "Valid SSE-C only",
+ setupFn: func(req *http.Request) {
+ keyPair := GenerateTestSSECKey(1)
+ SetupTestSSECHeaders(req, keyPair)
+ },
+ valid: true,
+ },
+ {
+ name: "Valid SSE-KMS only",
+ setupFn: func(req *http.Request) {
+ SetupTestSSEKMSHeaders(req, "test-key-id")
+ },
+ valid: true,
+ },
+ {
+ name: "No SSE headers",
+ setupFn: func(req *http.Request) {
+ // No SSE headers
+ },
+ valid: true,
+ },
+ }
+
+ for _, tc := range testCases {
+ t.Run(tc.name, func(t *testing.T) {
+ req := CreateTestHTTPRequest("PUT", "/test-bucket/test-object", []byte("test"))
+ tc.setupFn(req)
+
+ ssecDetected := IsSSECRequest(req)
+ sseKmsDetected := IsSSEKMSRequest(req)
+
+ // Both shouldn't be detected simultaneously
+ if ssecDetected && sseKmsDetected {
+ t.Error("Both SSE-C and SSE-KMS should not be detected simultaneously")
+ }
+
+ // Test validation if SSE-C is detected
+ if ssecDetected {
+ err := ValidateSSECHeaders(req)
+ if tc.valid && err != nil {
+ t.Errorf("Expected valid SSE-C headers, got error: %v", err)
+ }
+ if !tc.valid && err == nil && tc.name == "SSE-C and SSE-KMS conflict" {
+ // This specific test case should probably be handled at a higher level
+ t.Log("Conflict detection should be handled by higher-level validation")
+ }
+ }
+ })
+ }
+}
+
+// TestSSECopySourceHeaders tests copy operations with SSE headers
+func TestSSECopySourceHeaders(t *testing.T) {
+ sourceKey := GenerateTestSSECKey(1)
+ destKey := GenerateTestSSECKey(2)
+
+ // Create copy request with both source and destination SSE-C headers
+ req := CreateTestHTTPRequest("PUT", "/dest-bucket/dest-object", nil)
+
+ // Set copy source headers
+ SetupTestSSECCopyHeaders(req, sourceKey)
+
+ // Set destination headers
+ SetupTestSSECHeaders(req, destKey)
+
+ // Set copy source
+ req.Header.Set("X-Amz-Copy-Source", "/source-bucket/source-object")
+
+ SetupTestMuxVars(req, map[string]string{
+ "bucket": "dest-bucket",
+ "object": "dest-object",
+ })
+
+ // Parse copy source headers
+ copySourceKey, err := ParseSSECCopySourceHeaders(req)
+ if err != nil {
+ t.Fatalf("Failed to parse copy source headers: %v", err)
+ }
+
+ if copySourceKey == nil {
+ t.Fatal("Expected copy source key, got nil")
+ }
+
+ if !bytes.Equal(copySourceKey.Key, sourceKey.Key) {
+ t.Error("Copy source key doesn't match")
+ }
+
+ // Parse destination headers
+ destCustomerKey, err := ParseSSECHeaders(req)
+ if err != nil {
+ t.Fatalf("Failed to parse destination headers: %v", err)
+ }
+
+ if destCustomerKey == nil {
+ t.Fatal("Expected destination key, got nil")
+ }
+
+ if !bytes.Equal(destCustomerKey.Key, destKey.Key) {
+ t.Error("Destination key doesn't match")
+ }
+}
+
+// TestSSERequestValidation tests comprehensive request validation
+func TestSSERequestValidation(t *testing.T) {
+ testCases := []struct {
+ name string
+ method string
+ setupFn func(*http.Request)
+ expectError bool
+ errorType string
+ }{
+ {
+ name: "Valid PUT with SSE-C",
+ method: "PUT",
+ setupFn: func(req *http.Request) {
+ keyPair := GenerateTestSSECKey(1)
+ SetupTestSSECHeaders(req, keyPair)
+ },
+ expectError: false,
+ },
+ {
+ name: "Valid GET with SSE-C",
+ method: "GET",
+ setupFn: func(req *http.Request) {
+ keyPair := GenerateTestSSECKey(1)
+ SetupTestSSECHeaders(req, keyPair)
+ },
+ expectError: false,
+ },
+ {
+ name: "Invalid SSE-C key format",
+ method: "PUT",
+ setupFn: func(req *http.Request) {
+ req.Header.Set(s3_constants.AmzServerSideEncryptionCustomerAlgorithm, "AES256")
+ req.Header.Set(s3_constants.AmzServerSideEncryptionCustomerKey, "invalid-key")
+ req.Header.Set(s3_constants.AmzServerSideEncryptionCustomerKeyMD5, "invalid-md5")
+ },
+ expectError: true,
+ errorType: "InvalidRequest",
+ },
+ {
+ name: "Missing SSE-C key MD5",
+ method: "PUT",
+ setupFn: func(req *http.Request) {
+ keyPair := GenerateTestSSECKey(1)
+ req.Header.Set(s3_constants.AmzServerSideEncryptionCustomerAlgorithm, "AES256")
+ req.Header.Set(s3_constants.AmzServerSideEncryptionCustomerKey, keyPair.KeyB64)
+ // Missing MD5
+ },
+ expectError: true,
+ errorType: "InvalidRequest",
+ },
+ }
+
+ for _, tc := range testCases {
+ t.Run(tc.name, func(t *testing.T) {
+ req := CreateTestHTTPRequest(tc.method, "/test-bucket/test-object", []byte("test data"))
+ tc.setupFn(req)
+
+ SetupTestMuxVars(req, map[string]string{
+ "bucket": "test-bucket",
+ "object": "test-object",
+ })
+
+ // Test header validation
+ if IsSSECRequest(req) {
+ err := ValidateSSECHeaders(req)
+ if tc.expectError && err == nil {
+ t.Errorf("Expected error for %s, but got none", tc.name)
+ }
+ if !tc.expectError && err != nil {
+ t.Errorf("Expected no error for %s, but got: %v", tc.name, err)
+ }
+ }
+ })
+ }
+}