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

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