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.
 
 
 
 
 
 

399 lines
10 KiB

package s3api
import (
"bytes"
"encoding/json"
"io"
"strings"
"testing"
"github.com/seaweedfs/seaweedfs/weed/kms"
"github.com/seaweedfs/seaweedfs/weed/s3api/s3err"
)
func TestSSEKMSEncryptionDecryption(t *testing.T) {
kmsKey := SetupTestKMS(t)
defer kmsKey.Cleanup()
// Test data
testData := "Hello, SSE-KMS world! This is a test of envelope encryption."
testReader := strings.NewReader(testData)
// Create encryption context
encryptionContext := BuildEncryptionContext("test-bucket", "test-object", false)
// Encrypt the data
encryptedReader, sseKey, err := CreateSSEKMSEncryptedReader(testReader, kmsKey.KeyID, encryptionContext)
if err != nil {
t.Fatalf("Failed to create encrypted reader: %v", err)
}
// Verify SSE key metadata
if sseKey.KeyID != kmsKey.KeyID {
t.Errorf("Expected key ID %s, got %s", kmsKey.KeyID, sseKey.KeyID)
}
if len(sseKey.EncryptedDataKey) == 0 {
t.Error("Encrypted data key should not be empty")
}
if sseKey.EncryptionContext == nil {
t.Error("Encryption context should not be nil")
}
// Read the encrypted data
encryptedData, err := io.ReadAll(encryptedReader)
if err != nil {
t.Fatalf("Failed to read encrypted data: %v", err)
}
// Verify the encrypted data is different from original
if string(encryptedData) == testData {
t.Error("Encrypted data should be different from original data")
}
// The encrypted data should be same size as original (IV is stored in metadata, not in stream)
if len(encryptedData) != len(testData) {
t.Errorf("Encrypted data should be same size as original: expected %d, got %d", len(testData), len(encryptedData))
}
// Decrypt the data
decryptedReader, err := CreateSSEKMSDecryptedReader(bytes.NewReader(encryptedData), sseKey)
if err != nil {
t.Fatalf("Failed to create decrypted reader: %v", err)
}
// Read the decrypted data
decryptedData, err := io.ReadAll(decryptedReader)
if err != nil {
t.Fatalf("Failed to read decrypted data: %v", err)
}
// Verify the decrypted data matches the original
if string(decryptedData) != testData {
t.Errorf("Decrypted data does not match original.\nExpected: %s\nGot: %s", testData, string(decryptedData))
}
}
func TestSSEKMSKeyValidation(t *testing.T) {
tests := []struct {
name string
keyID string
wantValid bool
}{
{
name: "Valid UUID key ID",
keyID: "12345678-1234-1234-1234-123456789012",
wantValid: true,
},
{
name: "Valid alias",
keyID: "alias/my-test-key",
wantValid: true,
},
{
name: "Valid ARN",
keyID: "arn:aws:kms:us-east-1:123456789012:key/12345678-1234-1234-1234-123456789012",
wantValid: true,
},
{
name: "Valid alias ARN",
keyID: "arn:aws:kms:us-east-1:123456789012:alias/my-test-key",
wantValid: true,
},
{
name: "Valid test key format",
keyID: "invalid-key-format",
wantValid: true, // Now valid - following Minio's permissive approach
},
{
name: "Valid short key",
keyID: "12345678-1234",
wantValid: true, // Now valid - following Minio's permissive approach
},
{
name: "Invalid - leading space",
keyID: " leading-space",
wantValid: false,
},
{
name: "Invalid - trailing space",
keyID: "trailing-space ",
wantValid: false,
},
{
name: "Invalid - empty",
keyID: "",
wantValid: false,
},
{
name: "Invalid - internal spaces",
keyID: "invalid key id",
wantValid: false,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
valid := isValidKMSKeyID(tt.keyID)
if valid != tt.wantValid {
t.Errorf("isValidKMSKeyID(%s) = %v, want %v", tt.keyID, valid, tt.wantValid)
}
})
}
}
func TestSSEKMSMetadataSerialization(t *testing.T) {
// Create test SSE key
sseKey := &SSEKMSKey{
KeyID: "test-key-id",
EncryptedDataKey: []byte("encrypted-data-key"),
EncryptionContext: map[string]string{
"aws:s3:arn": "arn:aws:s3:::test-bucket/test-object",
},
BucketKeyEnabled: true,
}
// Serialize metadata
serialized, err := SerializeSSEKMSMetadata(sseKey)
if err != nil {
t.Fatalf("Failed to serialize SSE-KMS metadata: %v", err)
}
// Verify it's valid JSON
var jsonData map[string]interface{}
if err := json.Unmarshal(serialized, &jsonData); err != nil {
t.Fatalf("Serialized data is not valid JSON: %v", err)
}
// Deserialize metadata
deserializedKey, err := DeserializeSSEKMSMetadata(serialized)
if err != nil {
t.Fatalf("Failed to deserialize SSE-KMS metadata: %v", err)
}
// Verify the deserialized data matches original
if deserializedKey.KeyID != sseKey.KeyID {
t.Errorf("KeyID mismatch: expected %s, got %s", sseKey.KeyID, deserializedKey.KeyID)
}
if !bytes.Equal(deserializedKey.EncryptedDataKey, sseKey.EncryptedDataKey) {
t.Error("EncryptedDataKey mismatch")
}
if len(deserializedKey.EncryptionContext) != len(sseKey.EncryptionContext) {
t.Error("EncryptionContext length mismatch")
}
for k, v := range sseKey.EncryptionContext {
if deserializedKey.EncryptionContext[k] != v {
t.Errorf("EncryptionContext mismatch for key %s: expected %s, got %s", k, v, deserializedKey.EncryptionContext[k])
}
}
if deserializedKey.BucketKeyEnabled != sseKey.BucketKeyEnabled {
t.Errorf("BucketKeyEnabled mismatch: expected %v, got %v", sseKey.BucketKeyEnabled, deserializedKey.BucketKeyEnabled)
}
}
func TestBuildEncryptionContext(t *testing.T) {
tests := []struct {
name string
bucket string
object string
useBucketKey bool
expectedARN string
}{
{
name: "Object-level encryption",
bucket: "test-bucket",
object: "test-object",
useBucketKey: false,
expectedARN: "arn:aws:s3:::test-bucket/test-object",
},
{
name: "Bucket-level encryption",
bucket: "test-bucket",
object: "test-object",
useBucketKey: true,
expectedARN: "arn:aws:s3:::test-bucket",
},
{
name: "Nested object path",
bucket: "my-bucket",
object: "folder/subfolder/file.txt",
useBucketKey: false,
expectedARN: "arn:aws:s3:::my-bucket/folder/subfolder/file.txt",
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
context := BuildEncryptionContext(tt.bucket, tt.object, tt.useBucketKey)
if context == nil {
t.Fatal("Encryption context should not be nil")
}
arn, exists := context[kms.EncryptionContextS3ARN]
if !exists {
t.Error("Encryption context should contain S3 ARN")
}
if arn != tt.expectedARN {
t.Errorf("Expected ARN %s, got %s", tt.expectedARN, arn)
}
})
}
}
func TestKMSErrorMapping(t *testing.T) {
tests := []struct {
name string
kmsError *kms.KMSError
expectedErr string
}{
{
name: "Key not found",
kmsError: &kms.KMSError{
Code: kms.ErrCodeNotFoundException,
Message: "Key not found",
},
expectedErr: "KMSKeyNotFoundException",
},
{
name: "Access denied",
kmsError: &kms.KMSError{
Code: kms.ErrCodeAccessDenied,
Message: "Access denied",
},
expectedErr: "KMSAccessDeniedException",
},
{
name: "Key unavailable",
kmsError: &kms.KMSError{
Code: kms.ErrCodeKeyUnavailable,
Message: "Key is disabled",
},
expectedErr: "KMSKeyDisabledException",
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
errorCode := MapKMSErrorToS3Error(tt.kmsError)
// Get the actual error description
apiError := s3err.GetAPIError(errorCode)
if apiError.Code != tt.expectedErr {
t.Errorf("Expected error code %s, got %s", tt.expectedErr, apiError.Code)
}
})
}
}
// TestLargeDataEncryption tests encryption/decryption of larger data streams
func TestSSEKMSLargeDataEncryption(t *testing.T) {
kmsKey := SetupTestKMS(t)
defer kmsKey.Cleanup()
// Create a larger test dataset (1MB)
testData := strings.Repeat("This is a test of SSE-KMS with larger data streams. ", 20000)
testReader := strings.NewReader(testData)
// Create encryption context
encryptionContext := BuildEncryptionContext("large-bucket", "large-object", false)
// Encrypt the data
encryptedReader, sseKey, err := CreateSSEKMSEncryptedReader(testReader, kmsKey.KeyID, encryptionContext)
if err != nil {
t.Fatalf("Failed to create encrypted reader: %v", err)
}
// Read the encrypted data
encryptedData, err := io.ReadAll(encryptedReader)
if err != nil {
t.Fatalf("Failed to read encrypted data: %v", err)
}
// Decrypt the data
decryptedReader, err := CreateSSEKMSDecryptedReader(bytes.NewReader(encryptedData), sseKey)
if err != nil {
t.Fatalf("Failed to create decrypted reader: %v", err)
}
// Read the decrypted data
decryptedData, err := io.ReadAll(decryptedReader)
if err != nil {
t.Fatalf("Failed to read decrypted data: %v", err)
}
// Verify the decrypted data matches the original
if string(decryptedData) != testData {
t.Errorf("Decrypted data length: %d, original data length: %d", len(decryptedData), len(testData))
t.Error("Decrypted large data does not match original")
}
t.Logf("Successfully encrypted/decrypted %d bytes of data", len(testData))
}
// TestValidateSSEKMSKey tests the ValidateSSEKMSKey function, which correctly handles empty key IDs
func TestValidateSSEKMSKey(t *testing.T) {
tests := []struct {
name string
sseKey *SSEKMSKey
wantErr bool
}{
{
name: "nil SSE-KMS key",
sseKey: nil,
wantErr: true,
},
{
name: "empty key ID (valid - represents default KMS key)",
sseKey: &SSEKMSKey{
KeyID: "",
EncryptionContext: map[string]string{"test": "value"},
BucketKeyEnabled: false,
},
wantErr: false,
},
{
name: "valid UUID key ID",
sseKey: &SSEKMSKey{
KeyID: "12345678-1234-1234-1234-123456789012",
EncryptionContext: map[string]string{"test": "value"},
BucketKeyEnabled: true,
},
wantErr: false,
},
{
name: "valid alias",
sseKey: &SSEKMSKey{
KeyID: "alias/my-test-key",
EncryptionContext: map[string]string{},
BucketKeyEnabled: false,
},
wantErr: false,
},
{
name: "valid flexible key ID format",
sseKey: &SSEKMSKey{
KeyID: "invalid-format",
EncryptionContext: map[string]string{},
BucketKeyEnabled: false,
},
wantErr: false, // Now valid - following Minio's permissive approach
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
err := ValidateSSEKMSKey(tt.sseKey)
if (err != nil) != tt.wantErr {
t.Errorf("ValidateSSEKMSKey() error = %v, wantErr %v", err, tt.wantErr)
}
})
}
}