s3api_key_rotation.go 9.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291
  1. package s3api
  2. import (
  3. "bytes"
  4. "crypto/rand"
  5. "fmt"
  6. "io"
  7. "net/http"
  8. "github.com/seaweedfs/seaweedfs/weed/glog"
  9. "github.com/seaweedfs/seaweedfs/weed/pb/filer_pb"
  10. "github.com/seaweedfs/seaweedfs/weed/s3api/s3_constants"
  11. )
  12. // rotateSSECKey handles SSE-C key rotation for same-object copies
  13. func (s3a *S3ApiServer) rotateSSECKey(entry *filer_pb.Entry, r *http.Request) ([]*filer_pb.FileChunk, error) {
  14. // Parse source and destination SSE-C keys
  15. sourceKey, err := ParseSSECCopySourceHeaders(r)
  16. if err != nil {
  17. return nil, fmt.Errorf("parse SSE-C copy source headers: %w", err)
  18. }
  19. destKey, err := ParseSSECHeaders(r)
  20. if err != nil {
  21. return nil, fmt.Errorf("parse SSE-C destination headers: %w", err)
  22. }
  23. // Validate that we have both keys
  24. if sourceKey == nil {
  25. return nil, fmt.Errorf("source SSE-C key required for key rotation")
  26. }
  27. if destKey == nil {
  28. return nil, fmt.Errorf("destination SSE-C key required for key rotation")
  29. }
  30. // Check if keys are actually different
  31. if sourceKey.KeyMD5 == destKey.KeyMD5 {
  32. glog.V(2).Infof("SSE-C key rotation: keys are identical, using direct copy")
  33. return entry.GetChunks(), nil
  34. }
  35. glog.V(2).Infof("SSE-C key rotation: rotating from key %s to key %s",
  36. sourceKey.KeyMD5[:8], destKey.KeyMD5[:8])
  37. // For SSE-C key rotation, we need to re-encrypt all chunks
  38. // This cannot be a metadata-only operation because the encryption key changes
  39. return s3a.rotateSSECChunks(entry, sourceKey, destKey)
  40. }
  41. // rotateSSEKMSKey handles SSE-KMS key rotation for same-object copies
  42. func (s3a *S3ApiServer) rotateSSEKMSKey(entry *filer_pb.Entry, r *http.Request) ([]*filer_pb.FileChunk, error) {
  43. // Get source and destination key IDs
  44. srcKeyID, srcEncrypted := GetSourceSSEKMSInfo(entry.Extended)
  45. if !srcEncrypted {
  46. return nil, fmt.Errorf("source object is not SSE-KMS encrypted")
  47. }
  48. dstKeyID := r.Header.Get(s3_constants.AmzServerSideEncryptionAwsKmsKeyId)
  49. if dstKeyID == "" {
  50. // Use default key if not specified
  51. dstKeyID = "default"
  52. }
  53. // Check if keys are actually different
  54. if srcKeyID == dstKeyID {
  55. glog.V(2).Infof("SSE-KMS key rotation: keys are identical, using direct copy")
  56. return entry.GetChunks(), nil
  57. }
  58. glog.V(2).Infof("SSE-KMS key rotation: rotating from key %s to key %s", srcKeyID, dstKeyID)
  59. // For SSE-KMS, we can potentially do metadata-only rotation
  60. // if the KMS service supports key aliasing and the data encryption key can be re-wrapped
  61. if s3a.canDoMetadataOnlyKMSRotation(srcKeyID, dstKeyID) {
  62. return s3a.rotateSSEKMSMetadataOnly(entry, srcKeyID, dstKeyID)
  63. }
  64. // Fallback to full re-encryption
  65. return s3a.rotateSSEKMSChunks(entry, srcKeyID, dstKeyID, r)
  66. }
  67. // canDoMetadataOnlyKMSRotation determines if KMS key rotation can be done metadata-only
  68. func (s3a *S3ApiServer) canDoMetadataOnlyKMSRotation(srcKeyID, dstKeyID string) bool {
  69. // For now, we'll be conservative and always re-encrypt
  70. // In a full implementation, this would check if:
  71. // 1. Both keys are in the same KMS instance
  72. // 2. The KMS supports key re-wrapping
  73. // 3. The user has permissions for both keys
  74. return false
  75. }
  76. // rotateSSEKMSMetadataOnly performs metadata-only SSE-KMS key rotation
  77. func (s3a *S3ApiServer) rotateSSEKMSMetadataOnly(entry *filer_pb.Entry, srcKeyID, dstKeyID string) ([]*filer_pb.FileChunk, error) {
  78. // This would re-wrap the data encryption key with the new KMS key
  79. // For now, return an error since we don't support this yet
  80. return nil, fmt.Errorf("metadata-only KMS key rotation not yet implemented")
  81. }
  82. // rotateSSECChunks re-encrypts all chunks with new SSE-C key
  83. func (s3a *S3ApiServer) rotateSSECChunks(entry *filer_pb.Entry, sourceKey, destKey *SSECustomerKey) ([]*filer_pb.FileChunk, error) {
  84. // Get IV from entry metadata
  85. iv, err := GetIVFromMetadata(entry.Extended)
  86. if err != nil {
  87. return nil, fmt.Errorf("get IV from metadata: %w", err)
  88. }
  89. var rotatedChunks []*filer_pb.FileChunk
  90. for _, chunk := range entry.GetChunks() {
  91. rotatedChunk, err := s3a.rotateSSECChunk(chunk, sourceKey, destKey, iv)
  92. if err != nil {
  93. return nil, fmt.Errorf("rotate SSE-C chunk: %w", err)
  94. }
  95. rotatedChunks = append(rotatedChunks, rotatedChunk)
  96. }
  97. // Generate new IV for the destination and store it in entry metadata
  98. newIV := make([]byte, s3_constants.AESBlockSize)
  99. if _, err := io.ReadFull(rand.Reader, newIV); err != nil {
  100. return nil, fmt.Errorf("generate new IV: %w", err)
  101. }
  102. // Update entry metadata with new IV and SSE-C headers
  103. if entry.Extended == nil {
  104. entry.Extended = make(map[string][]byte)
  105. }
  106. StoreIVInMetadata(entry.Extended, newIV)
  107. entry.Extended[s3_constants.AmzServerSideEncryptionCustomerAlgorithm] = []byte("AES256")
  108. entry.Extended[s3_constants.AmzServerSideEncryptionCustomerKeyMD5] = []byte(destKey.KeyMD5)
  109. return rotatedChunks, nil
  110. }
  111. // rotateSSEKMSChunks re-encrypts all chunks with new SSE-KMS key
  112. func (s3a *S3ApiServer) rotateSSEKMSChunks(entry *filer_pb.Entry, srcKeyID, dstKeyID string, r *http.Request) ([]*filer_pb.FileChunk, error) {
  113. var rotatedChunks []*filer_pb.FileChunk
  114. // Parse encryption context and bucket key settings
  115. _, encryptionContext, bucketKeyEnabled, err := ParseSSEKMSCopyHeaders(r)
  116. if err != nil {
  117. return nil, fmt.Errorf("parse SSE-KMS copy headers: %w", err)
  118. }
  119. for _, chunk := range entry.GetChunks() {
  120. rotatedChunk, err := s3a.rotateSSEKMSChunk(chunk, srcKeyID, dstKeyID, encryptionContext, bucketKeyEnabled)
  121. if err != nil {
  122. return nil, fmt.Errorf("rotate SSE-KMS chunk: %w", err)
  123. }
  124. rotatedChunks = append(rotatedChunks, rotatedChunk)
  125. }
  126. return rotatedChunks, nil
  127. }
  128. // rotateSSECChunk rotates a single SSE-C encrypted chunk
  129. func (s3a *S3ApiServer) rotateSSECChunk(chunk *filer_pb.FileChunk, sourceKey, destKey *SSECustomerKey, iv []byte) (*filer_pb.FileChunk, error) {
  130. // Create new chunk with same properties
  131. newChunk := &filer_pb.FileChunk{
  132. Offset: chunk.Offset,
  133. Size: chunk.Size,
  134. ModifiedTsNs: chunk.ModifiedTsNs,
  135. ETag: chunk.ETag,
  136. }
  137. // Assign new volume for the rotated chunk
  138. assignResult, err := s3a.assignNewVolume("")
  139. if err != nil {
  140. return nil, fmt.Errorf("assign new volume: %w", err)
  141. }
  142. // Set file ID on new chunk
  143. if err := s3a.setChunkFileId(newChunk, assignResult); err != nil {
  144. return nil, err
  145. }
  146. // Get source chunk data
  147. srcUrl, err := s3a.lookupVolumeUrl(chunk.GetFileIdString())
  148. if err != nil {
  149. return nil, fmt.Errorf("lookup source volume: %w", err)
  150. }
  151. // Download encrypted data
  152. encryptedData, err := s3a.downloadChunkData(srcUrl, 0, int64(chunk.Size))
  153. if err != nil {
  154. return nil, fmt.Errorf("download chunk data: %w", err)
  155. }
  156. // Decrypt with source key using provided IV
  157. decryptedReader, err := CreateSSECDecryptedReader(bytes.NewReader(encryptedData), sourceKey, iv)
  158. if err != nil {
  159. return nil, fmt.Errorf("create decrypted reader: %w", err)
  160. }
  161. decryptedData, err := io.ReadAll(decryptedReader)
  162. if err != nil {
  163. return nil, fmt.Errorf("decrypt data: %w", err)
  164. }
  165. // Re-encrypt with destination key
  166. encryptedReader, _, err := CreateSSECEncryptedReader(bytes.NewReader(decryptedData), destKey)
  167. if err != nil {
  168. return nil, fmt.Errorf("create encrypted reader: %w", err)
  169. }
  170. // Note: IV will be handled at the entry level by the calling function
  171. reencryptedData, err := io.ReadAll(encryptedReader)
  172. if err != nil {
  173. return nil, fmt.Errorf("re-encrypt data: %w", err)
  174. }
  175. // Update chunk size to include new IV
  176. newChunk.Size = uint64(len(reencryptedData))
  177. // Upload re-encrypted data
  178. if err := s3a.uploadChunkData(reencryptedData, assignResult); err != nil {
  179. return nil, fmt.Errorf("upload re-encrypted data: %w", err)
  180. }
  181. return newChunk, nil
  182. }
  183. // rotateSSEKMSChunk rotates a single SSE-KMS encrypted chunk
  184. func (s3a *S3ApiServer) rotateSSEKMSChunk(chunk *filer_pb.FileChunk, srcKeyID, dstKeyID string, encryptionContext map[string]string, bucketKeyEnabled bool) (*filer_pb.FileChunk, error) {
  185. // Create new chunk with same properties
  186. newChunk := &filer_pb.FileChunk{
  187. Offset: chunk.Offset,
  188. Size: chunk.Size,
  189. ModifiedTsNs: chunk.ModifiedTsNs,
  190. ETag: chunk.ETag,
  191. }
  192. // Assign new volume for the rotated chunk
  193. assignResult, err := s3a.assignNewVolume("")
  194. if err != nil {
  195. return nil, fmt.Errorf("assign new volume: %w", err)
  196. }
  197. // Set file ID on new chunk
  198. if err := s3a.setChunkFileId(newChunk, assignResult); err != nil {
  199. return nil, err
  200. }
  201. // Get source chunk data
  202. srcUrl, err := s3a.lookupVolumeUrl(chunk.GetFileIdString())
  203. if err != nil {
  204. return nil, fmt.Errorf("lookup source volume: %w", err)
  205. }
  206. // Download data (this would be encrypted with the old KMS key)
  207. chunkData, err := s3a.downloadChunkData(srcUrl, 0, int64(chunk.Size))
  208. if err != nil {
  209. return nil, fmt.Errorf("download chunk data: %w", err)
  210. }
  211. // For now, we'll just re-upload the data as-is
  212. // In a full implementation, this would:
  213. // 1. Decrypt with old KMS key
  214. // 2. Re-encrypt with new KMS key
  215. // 3. Update metadata accordingly
  216. // Upload data with new key (placeholder implementation)
  217. if err := s3a.uploadChunkData(chunkData, assignResult); err != nil {
  218. return nil, fmt.Errorf("upload rotated data: %w", err)
  219. }
  220. return newChunk, nil
  221. }
  222. // IsSameObjectCopy determines if this is a same-object copy operation
  223. func IsSameObjectCopy(r *http.Request, srcBucket, srcObject, dstBucket, dstObject string) bool {
  224. return srcBucket == dstBucket && srcObject == dstObject
  225. }
  226. // NeedsKeyRotation determines if the copy operation requires key rotation
  227. func NeedsKeyRotation(entry *filer_pb.Entry, r *http.Request) bool {
  228. // Check for SSE-C key rotation
  229. if IsSSECEncrypted(entry.Extended) && IsSSECRequest(r) {
  230. return true // Assume different keys for safety
  231. }
  232. // Check for SSE-KMS key rotation
  233. if IsSSEKMSEncrypted(entry.Extended) && IsSSEKMSRequest(r) {
  234. srcKeyID, _ := GetSourceSSEKMSInfo(entry.Extended)
  235. dstKeyID := r.Header.Get(s3_constants.AmzServerSideEncryptionAwsKmsKeyId)
  236. return srcKeyID != dstKeyID
  237. }
  238. return false
  239. }