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) } } }) } }