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