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