You can not select more than 25 topics
			Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
		
		
		
		
		
			
		
			
				
					
					
						
							401 lines
						
					
					
						
							11 KiB
						
					
					
				
			
		
		
		
			
			
			
		
		
	
	
							401 lines
						
					
					
						
							11 KiB
						
					
					
				
								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)
							 | 
						|
												}
							 | 
						|
											}
							 | 
						|
										})
							 | 
						|
									}
							 | 
						|
								}
							 |