| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291 |
- package s3api
- import (
- "bytes"
- "crypto/rand"
- "fmt"
- "io"
- "net/http"
- "github.com/seaweedfs/seaweedfs/weed/glog"
- "github.com/seaweedfs/seaweedfs/weed/pb/filer_pb"
- "github.com/seaweedfs/seaweedfs/weed/s3api/s3_constants"
- )
- // rotateSSECKey handles SSE-C key rotation for same-object copies
- func (s3a *S3ApiServer) rotateSSECKey(entry *filer_pb.Entry, r *http.Request) ([]*filer_pb.FileChunk, error) {
- // Parse source and destination SSE-C keys
- sourceKey, err := ParseSSECCopySourceHeaders(r)
- if err != nil {
- return nil, fmt.Errorf("parse SSE-C copy source headers: %w", err)
- }
- destKey, err := ParseSSECHeaders(r)
- if err != nil {
- return nil, fmt.Errorf("parse SSE-C destination headers: %w", err)
- }
- // Validate that we have both keys
- if sourceKey == nil {
- return nil, fmt.Errorf("source SSE-C key required for key rotation")
- }
- if destKey == nil {
- return nil, fmt.Errorf("destination SSE-C key required for key rotation")
- }
- // Check if keys are actually different
- if sourceKey.KeyMD5 == destKey.KeyMD5 {
- glog.V(2).Infof("SSE-C key rotation: keys are identical, using direct copy")
- return entry.GetChunks(), nil
- }
- glog.V(2).Infof("SSE-C key rotation: rotating from key %s to key %s",
- sourceKey.KeyMD5[:8], destKey.KeyMD5[:8])
- // For SSE-C key rotation, we need to re-encrypt all chunks
- // This cannot be a metadata-only operation because the encryption key changes
- return s3a.rotateSSECChunks(entry, sourceKey, destKey)
- }
- // rotateSSEKMSKey handles SSE-KMS key rotation for same-object copies
- func (s3a *S3ApiServer) rotateSSEKMSKey(entry *filer_pb.Entry, r *http.Request) ([]*filer_pb.FileChunk, error) {
- // Get source and destination key IDs
- srcKeyID, srcEncrypted := GetSourceSSEKMSInfo(entry.Extended)
- if !srcEncrypted {
- return nil, fmt.Errorf("source object is not SSE-KMS encrypted")
- }
- dstKeyID := r.Header.Get(s3_constants.AmzServerSideEncryptionAwsKmsKeyId)
- if dstKeyID == "" {
- // Use default key if not specified
- dstKeyID = "default"
- }
- // Check if keys are actually different
- if srcKeyID == dstKeyID {
- glog.V(2).Infof("SSE-KMS key rotation: keys are identical, using direct copy")
- return entry.GetChunks(), nil
- }
- glog.V(2).Infof("SSE-KMS key rotation: rotating from key %s to key %s", srcKeyID, dstKeyID)
- // For SSE-KMS, we can potentially do metadata-only rotation
- // if the KMS service supports key aliasing and the data encryption key can be re-wrapped
- if s3a.canDoMetadataOnlyKMSRotation(srcKeyID, dstKeyID) {
- return s3a.rotateSSEKMSMetadataOnly(entry, srcKeyID, dstKeyID)
- }
- // Fallback to full re-encryption
- return s3a.rotateSSEKMSChunks(entry, srcKeyID, dstKeyID, r)
- }
- // canDoMetadataOnlyKMSRotation determines if KMS key rotation can be done metadata-only
- func (s3a *S3ApiServer) canDoMetadataOnlyKMSRotation(srcKeyID, dstKeyID string) bool {
- // For now, we'll be conservative and always re-encrypt
- // In a full implementation, this would check if:
- // 1. Both keys are in the same KMS instance
- // 2. The KMS supports key re-wrapping
- // 3. The user has permissions for both keys
- return false
- }
- // rotateSSEKMSMetadataOnly performs metadata-only SSE-KMS key rotation
- func (s3a *S3ApiServer) rotateSSEKMSMetadataOnly(entry *filer_pb.Entry, srcKeyID, dstKeyID string) ([]*filer_pb.FileChunk, error) {
- // This would re-wrap the data encryption key with the new KMS key
- // For now, return an error since we don't support this yet
- return nil, fmt.Errorf("metadata-only KMS key rotation not yet implemented")
- }
- // rotateSSECChunks re-encrypts all chunks with new SSE-C key
- func (s3a *S3ApiServer) rotateSSECChunks(entry *filer_pb.Entry, sourceKey, destKey *SSECustomerKey) ([]*filer_pb.FileChunk, error) {
- // Get IV from entry metadata
- iv, err := GetIVFromMetadata(entry.Extended)
- if err != nil {
- return nil, fmt.Errorf("get IV from metadata: %w", err)
- }
- var rotatedChunks []*filer_pb.FileChunk
- for _, chunk := range entry.GetChunks() {
- rotatedChunk, err := s3a.rotateSSECChunk(chunk, sourceKey, destKey, iv)
- if err != nil {
- return nil, fmt.Errorf("rotate SSE-C chunk: %w", err)
- }
- rotatedChunks = append(rotatedChunks, rotatedChunk)
- }
- // Generate new IV for the destination and store it in entry metadata
- newIV := make([]byte, s3_constants.AESBlockSize)
- if _, err := io.ReadFull(rand.Reader, newIV); err != nil {
- return nil, fmt.Errorf("generate new IV: %w", err)
- }
- // Update entry metadata with new IV and SSE-C headers
- if entry.Extended == nil {
- entry.Extended = make(map[string][]byte)
- }
- StoreIVInMetadata(entry.Extended, newIV)
- entry.Extended[s3_constants.AmzServerSideEncryptionCustomerAlgorithm] = []byte("AES256")
- entry.Extended[s3_constants.AmzServerSideEncryptionCustomerKeyMD5] = []byte(destKey.KeyMD5)
- return rotatedChunks, nil
- }
- // rotateSSEKMSChunks re-encrypts all chunks with new SSE-KMS key
- func (s3a *S3ApiServer) rotateSSEKMSChunks(entry *filer_pb.Entry, srcKeyID, dstKeyID string, r *http.Request) ([]*filer_pb.FileChunk, error) {
- var rotatedChunks []*filer_pb.FileChunk
- // Parse encryption context and bucket key settings
- _, encryptionContext, bucketKeyEnabled, err := ParseSSEKMSCopyHeaders(r)
- if err != nil {
- return nil, fmt.Errorf("parse SSE-KMS copy headers: %w", err)
- }
- for _, chunk := range entry.GetChunks() {
- rotatedChunk, err := s3a.rotateSSEKMSChunk(chunk, srcKeyID, dstKeyID, encryptionContext, bucketKeyEnabled)
- if err != nil {
- return nil, fmt.Errorf("rotate SSE-KMS chunk: %w", err)
- }
- rotatedChunks = append(rotatedChunks, rotatedChunk)
- }
- return rotatedChunks, nil
- }
- // rotateSSECChunk rotates a single SSE-C encrypted chunk
- func (s3a *S3ApiServer) rotateSSECChunk(chunk *filer_pb.FileChunk, sourceKey, destKey *SSECustomerKey, iv []byte) (*filer_pb.FileChunk, error) {
- // Create new chunk with same properties
- newChunk := &filer_pb.FileChunk{
- Offset: chunk.Offset,
- Size: chunk.Size,
- ModifiedTsNs: chunk.ModifiedTsNs,
- ETag: chunk.ETag,
- }
- // Assign new volume for the rotated chunk
- assignResult, err := s3a.assignNewVolume("")
- if err != nil {
- return nil, fmt.Errorf("assign new volume: %w", err)
- }
- // Set file ID on new chunk
- if err := s3a.setChunkFileId(newChunk, assignResult); err != nil {
- return nil, err
- }
- // Get source chunk data
- srcUrl, err := s3a.lookupVolumeUrl(chunk.GetFileIdString())
- if err != nil {
- return nil, fmt.Errorf("lookup source volume: %w", err)
- }
- // Download encrypted data
- encryptedData, err := s3a.downloadChunkData(srcUrl, 0, int64(chunk.Size))
- if err != nil {
- return nil, fmt.Errorf("download chunk data: %w", err)
- }
- // Decrypt with source key using provided IV
- decryptedReader, err := CreateSSECDecryptedReader(bytes.NewReader(encryptedData), sourceKey, iv)
- if err != nil {
- return nil, fmt.Errorf("create decrypted reader: %w", err)
- }
- decryptedData, err := io.ReadAll(decryptedReader)
- if err != nil {
- return nil, fmt.Errorf("decrypt data: %w", err)
- }
- // Re-encrypt with destination key
- encryptedReader, _, err := CreateSSECEncryptedReader(bytes.NewReader(decryptedData), destKey)
- if err != nil {
- return nil, fmt.Errorf("create encrypted reader: %w", err)
- }
- // Note: IV will be handled at the entry level by the calling function
- reencryptedData, err := io.ReadAll(encryptedReader)
- if err != nil {
- return nil, fmt.Errorf("re-encrypt data: %w", err)
- }
- // Update chunk size to include new IV
- newChunk.Size = uint64(len(reencryptedData))
- // Upload re-encrypted data
- if err := s3a.uploadChunkData(reencryptedData, assignResult); err != nil {
- return nil, fmt.Errorf("upload re-encrypted data: %w", err)
- }
- return newChunk, nil
- }
- // rotateSSEKMSChunk rotates a single SSE-KMS encrypted chunk
- func (s3a *S3ApiServer) rotateSSEKMSChunk(chunk *filer_pb.FileChunk, srcKeyID, dstKeyID string, encryptionContext map[string]string, bucketKeyEnabled bool) (*filer_pb.FileChunk, error) {
- // Create new chunk with same properties
- newChunk := &filer_pb.FileChunk{
- Offset: chunk.Offset,
- Size: chunk.Size,
- ModifiedTsNs: chunk.ModifiedTsNs,
- ETag: chunk.ETag,
- }
- // Assign new volume for the rotated chunk
- assignResult, err := s3a.assignNewVolume("")
- if err != nil {
- return nil, fmt.Errorf("assign new volume: %w", err)
- }
- // Set file ID on new chunk
- if err := s3a.setChunkFileId(newChunk, assignResult); err != nil {
- return nil, err
- }
- // Get source chunk data
- srcUrl, err := s3a.lookupVolumeUrl(chunk.GetFileIdString())
- if err != nil {
- return nil, fmt.Errorf("lookup source volume: %w", err)
- }
- // Download data (this would be encrypted with the old KMS key)
- chunkData, err := s3a.downloadChunkData(srcUrl, 0, int64(chunk.Size))
- if err != nil {
- return nil, fmt.Errorf("download chunk data: %w", err)
- }
- // For now, we'll just re-upload the data as-is
- // In a full implementation, this would:
- // 1. Decrypt with old KMS key
- // 2. Re-encrypt with new KMS key
- // 3. Update metadata accordingly
- // Upload data with new key (placeholder implementation)
- if err := s3a.uploadChunkData(chunkData, assignResult); err != nil {
- return nil, fmt.Errorf("upload rotated data: %w", err)
- }
- return newChunk, nil
- }
- // IsSameObjectCopy determines if this is a same-object copy operation
- func IsSameObjectCopy(r *http.Request, srcBucket, srcObject, dstBucket, dstObject string) bool {
- return srcBucket == dstBucket && srcObject == dstObject
- }
- // NeedsKeyRotation determines if the copy operation requires key rotation
- func NeedsKeyRotation(entry *filer_pb.Entry, r *http.Request) bool {
- // Check for SSE-C key rotation
- if IsSSECEncrypted(entry.Extended) && IsSSECRequest(r) {
- return true // Assume different keys for safety
- }
- // Check for SSE-KMS key rotation
- if IsSSEKMSEncrypted(entry.Extended) && IsSSEKMSRequest(r) {
- srcKeyID, _ := GetSourceSSEKMSInfo(entry.Extended)
- dstKeyID := r.Header.Get(s3_constants.AmzServerSideEncryptionAwsKmsKeyId)
- return srcKeyID != dstKeyID
- }
- return false
- }
|