diff options
Diffstat (limited to 'weed/s3api/s3_bucket_encryption.go')
| -rw-r--r-- | weed/s3api/s3_bucket_encryption.go | 346 |
1 files changed, 346 insertions, 0 deletions
diff --git a/weed/s3api/s3_bucket_encryption.go b/weed/s3api/s3_bucket_encryption.go new file mode 100644 index 000000000..6ca05749f --- /dev/null +++ b/weed/s3api/s3_bucket_encryption.go @@ -0,0 +1,346 @@ +package s3api + +import ( + "encoding/xml" + "fmt" + "io" + "net/http" + + "github.com/seaweedfs/seaweedfs/weed/glog" + "github.com/seaweedfs/seaweedfs/weed/pb/s3_pb" + "github.com/seaweedfs/seaweedfs/weed/s3api/s3_constants" + "github.com/seaweedfs/seaweedfs/weed/s3api/s3err" +) + +// ServerSideEncryptionConfiguration represents the bucket encryption configuration +type ServerSideEncryptionConfiguration struct { + XMLName xml.Name `xml:"ServerSideEncryptionConfiguration"` + Rules []ServerSideEncryptionRule `xml:"Rule"` +} + +// ServerSideEncryptionRule represents a single encryption rule +type ServerSideEncryptionRule struct { + ApplyServerSideEncryptionByDefault ApplyServerSideEncryptionByDefault `xml:"ApplyServerSideEncryptionByDefault"` + BucketKeyEnabled *bool `xml:"BucketKeyEnabled,omitempty"` +} + +// ApplyServerSideEncryptionByDefault specifies the default encryption settings +type ApplyServerSideEncryptionByDefault struct { + SSEAlgorithm string `xml:"SSEAlgorithm"` + KMSMasterKeyID string `xml:"KMSMasterKeyID,omitempty"` +} + +// encryptionConfigToProto converts EncryptionConfiguration to protobuf format +func encryptionConfigToProto(config *s3_pb.EncryptionConfiguration) *s3_pb.EncryptionConfiguration { + if config == nil { + return nil + } + return &s3_pb.EncryptionConfiguration{ + SseAlgorithm: config.SseAlgorithm, + KmsKeyId: config.KmsKeyId, + BucketKeyEnabled: config.BucketKeyEnabled, + } +} + +// encryptionConfigFromXML converts XML ServerSideEncryptionConfiguration to protobuf +func encryptionConfigFromXML(xmlConfig *ServerSideEncryptionConfiguration) *s3_pb.EncryptionConfiguration { + if xmlConfig == nil || len(xmlConfig.Rules) == 0 { + return nil + } + + rule := xmlConfig.Rules[0] // AWS S3 supports only one rule + return &s3_pb.EncryptionConfiguration{ + SseAlgorithm: rule.ApplyServerSideEncryptionByDefault.SSEAlgorithm, + KmsKeyId: rule.ApplyServerSideEncryptionByDefault.KMSMasterKeyID, + BucketKeyEnabled: rule.BucketKeyEnabled != nil && *rule.BucketKeyEnabled, + } +} + +// encryptionConfigToXML converts protobuf EncryptionConfiguration to XML +func encryptionConfigToXML(config *s3_pb.EncryptionConfiguration) *ServerSideEncryptionConfiguration { + if config == nil { + return nil + } + + return &ServerSideEncryptionConfiguration{ + Rules: []ServerSideEncryptionRule{ + { + ApplyServerSideEncryptionByDefault: ApplyServerSideEncryptionByDefault{ + SSEAlgorithm: config.SseAlgorithm, + KMSMasterKeyID: config.KmsKeyId, + }, + BucketKeyEnabled: &config.BucketKeyEnabled, + }, + }, + } +} + +// Default encryption algorithms +const ( + EncryptionTypeAES256 = "AES256" + EncryptionTypeKMS = "aws:kms" +) + +// GetBucketEncryption handles GET bucket encryption requests +func (s3a *S3ApiServer) GetBucketEncryption(w http.ResponseWriter, r *http.Request) { + bucket, _ := s3_constants.GetBucketAndObject(r) + + // Load bucket encryption configuration + config, errCode := s3a.getEncryptionConfiguration(bucket) + if errCode != s3err.ErrNone { + if errCode == s3err.ErrNoSuchBucketEncryptionConfiguration { + s3err.WriteErrorResponse(w, r, s3err.ErrNoSuchBucketEncryptionConfiguration) + return + } + s3err.WriteErrorResponse(w, r, errCode) + return + } + + // Convert protobuf config to S3 XML response + response := encryptionConfigToXML(config) + if response == nil { + s3err.WriteErrorResponse(w, r, s3err.ErrNoSuchBucketEncryptionConfiguration) + return + } + + w.Header().Set("Content-Type", "application/xml") + if err := xml.NewEncoder(w).Encode(response); err != nil { + glog.Errorf("Failed to encode bucket encryption response: %v", err) + s3err.WriteErrorResponse(w, r, s3err.ErrInternalError) + return + } +} + +// PutBucketEncryption handles PUT bucket encryption requests +func (s3a *S3ApiServer) PutBucketEncryption(w http.ResponseWriter, r *http.Request) { + bucket, _ := s3_constants.GetBucketAndObject(r) + + // Read and parse the request body + body, err := io.ReadAll(r.Body) + if err != nil { + glog.Errorf("Failed to read request body: %v", err) + s3err.WriteErrorResponse(w, r, s3err.ErrInvalidRequest) + return + } + defer r.Body.Close() + + var xmlConfig ServerSideEncryptionConfiguration + if err := xml.Unmarshal(body, &xmlConfig); err != nil { + glog.Errorf("Failed to parse bucket encryption configuration: %v", err) + s3err.WriteErrorResponse(w, r, s3err.ErrMalformedXML) + return + } + + // Validate the configuration + if len(xmlConfig.Rules) == 0 { + s3err.WriteErrorResponse(w, r, s3err.ErrMalformedXML) + return + } + + rule := xmlConfig.Rules[0] // AWS S3 supports only one rule + + // Validate SSE algorithm + if rule.ApplyServerSideEncryptionByDefault.SSEAlgorithm != EncryptionTypeAES256 && + rule.ApplyServerSideEncryptionByDefault.SSEAlgorithm != EncryptionTypeKMS { + s3err.WriteErrorResponse(w, r, s3err.ErrInvalidEncryptionAlgorithm) + return + } + + // For aws:kms, validate KMS key if provided + if rule.ApplyServerSideEncryptionByDefault.SSEAlgorithm == EncryptionTypeKMS { + keyID := rule.ApplyServerSideEncryptionByDefault.KMSMasterKeyID + if keyID != "" && !isValidKMSKeyID(keyID) { + s3err.WriteErrorResponse(w, r, s3err.ErrKMSKeyNotFound) + return + } + } + + // Convert XML to protobuf configuration + encryptionConfig := encryptionConfigFromXML(&xmlConfig) + + // Update the bucket configuration + errCode := s3a.updateEncryptionConfiguration(bucket, encryptionConfig) + if errCode != s3err.ErrNone { + s3err.WriteErrorResponse(w, r, errCode) + return + } + + w.WriteHeader(http.StatusOK) +} + +// DeleteBucketEncryption handles DELETE bucket encryption requests +func (s3a *S3ApiServer) DeleteBucketEncryption(w http.ResponseWriter, r *http.Request) { + bucket, _ := s3_constants.GetBucketAndObject(r) + + errCode := s3a.removeEncryptionConfiguration(bucket) + if errCode != s3err.ErrNone { + s3err.WriteErrorResponse(w, r, errCode) + return + } + + w.WriteHeader(http.StatusNoContent) +} + +// GetBucketEncryptionConfig retrieves the bucket encryption configuration for internal use +func (s3a *S3ApiServer) GetBucketEncryptionConfig(bucket string) (*s3_pb.EncryptionConfiguration, error) { + config, errCode := s3a.getEncryptionConfiguration(bucket) + if errCode != s3err.ErrNone { + if errCode == s3err.ErrNoSuchBucketEncryptionConfiguration { + return nil, fmt.Errorf("no encryption configuration found") + } + return nil, fmt.Errorf("failed to get encryption configuration") + } + return config, nil +} + +// Internal methods following the bucket configuration pattern + +// getEncryptionConfiguration retrieves encryption configuration with caching +func (s3a *S3ApiServer) getEncryptionConfiguration(bucket string) (*s3_pb.EncryptionConfiguration, s3err.ErrorCode) { + // Get metadata using structured API + metadata, err := s3a.GetBucketMetadata(bucket) + if err != nil { + glog.Errorf("getEncryptionConfiguration: failed to get bucket metadata for bucket %s: %v", bucket, err) + return nil, s3err.ErrInternalError + } + + if metadata.Encryption == nil { + return nil, s3err.ErrNoSuchBucketEncryptionConfiguration + } + + return metadata.Encryption, s3err.ErrNone +} + +// updateEncryptionConfiguration updates the encryption configuration for a bucket +func (s3a *S3ApiServer) updateEncryptionConfiguration(bucket string, encryptionConfig *s3_pb.EncryptionConfiguration) s3err.ErrorCode { + // Update using structured API + err := s3a.UpdateBucketEncryption(bucket, encryptionConfig) + if err != nil { + glog.Errorf("updateEncryptionConfiguration: failed to update encryption config for bucket %s: %v", bucket, err) + return s3err.ErrInternalError + } + + // Cache will be updated automatically via metadata subscription + return s3err.ErrNone +} + +// removeEncryptionConfiguration removes the encryption configuration for a bucket +func (s3a *S3ApiServer) removeEncryptionConfiguration(bucket string) s3err.ErrorCode { + // Check if encryption configuration exists + metadata, err := s3a.GetBucketMetadata(bucket) + if err != nil { + glog.Errorf("removeEncryptionConfiguration: failed to get bucket metadata for bucket %s: %v", bucket, err) + return s3err.ErrInternalError + } + + if metadata.Encryption == nil { + return s3err.ErrNoSuchBucketEncryptionConfiguration + } + + // Update using structured API + err = s3a.ClearBucketEncryption(bucket) + if err != nil { + glog.Errorf("removeEncryptionConfiguration: failed to remove encryption config for bucket %s: %v", bucket, err) + return s3err.ErrInternalError + } + + // Cache will be updated automatically via metadata subscription + return s3err.ErrNone +} + +// IsDefaultEncryptionEnabled checks if default encryption is enabled for a bucket +func (s3a *S3ApiServer) IsDefaultEncryptionEnabled(bucket string) bool { + config, err := s3a.GetBucketEncryptionConfig(bucket) + if err != nil || config == nil { + return false + } + return config.SseAlgorithm != "" +} + +// GetDefaultEncryptionHeaders returns the default encryption headers for a bucket +func (s3a *S3ApiServer) GetDefaultEncryptionHeaders(bucket string) map[string]string { + config, err := s3a.GetBucketEncryptionConfig(bucket) + if err != nil || config == nil { + return nil + } + + headers := make(map[string]string) + headers[s3_constants.AmzServerSideEncryption] = config.SseAlgorithm + + if config.SseAlgorithm == EncryptionTypeKMS && config.KmsKeyId != "" { + headers[s3_constants.AmzServerSideEncryptionAwsKmsKeyId] = config.KmsKeyId + } + + if config.BucketKeyEnabled { + headers[s3_constants.AmzServerSideEncryptionBucketKeyEnabled] = "true" + } + + return headers +} + +// IsDefaultEncryptionEnabled checks if default encryption is enabled for a configuration +func IsDefaultEncryptionEnabled(config *s3_pb.EncryptionConfiguration) bool { + return config != nil && config.SseAlgorithm != "" +} + +// GetDefaultEncryptionHeaders generates default encryption headers from configuration +func GetDefaultEncryptionHeaders(config *s3_pb.EncryptionConfiguration) map[string]string { + if config == nil || config.SseAlgorithm == "" { + return nil + } + + headers := make(map[string]string) + headers[s3_constants.AmzServerSideEncryption] = config.SseAlgorithm + + if config.SseAlgorithm == "aws:kms" && config.KmsKeyId != "" { + headers[s3_constants.AmzServerSideEncryptionAwsKmsKeyId] = config.KmsKeyId + } + + return headers +} + +// encryptionConfigFromXMLBytes parses XML bytes to encryption configuration +func encryptionConfigFromXMLBytes(xmlBytes []byte) (*s3_pb.EncryptionConfiguration, error) { + var xmlConfig ServerSideEncryptionConfiguration + if err := xml.Unmarshal(xmlBytes, &xmlConfig); err != nil { + return nil, err + } + + // Validate namespace - should be empty or the standard AWS namespace + if xmlConfig.XMLName.Space != "" && xmlConfig.XMLName.Space != "http://s3.amazonaws.com/doc/2006-03-01/" { + return nil, fmt.Errorf("invalid XML namespace: %s", xmlConfig.XMLName.Space) + } + + // Validate the configuration + if len(xmlConfig.Rules) == 0 { + return nil, fmt.Errorf("encryption configuration must have at least one rule") + } + + rule := xmlConfig.Rules[0] + if rule.ApplyServerSideEncryptionByDefault.SSEAlgorithm == "" { + return nil, fmt.Errorf("encryption algorithm is required") + } + + // Validate algorithm + validAlgorithms := map[string]bool{ + "AES256": true, + "aws:kms": true, + } + + if !validAlgorithms[rule.ApplyServerSideEncryptionByDefault.SSEAlgorithm] { + return nil, fmt.Errorf("unsupported encryption algorithm: %s", rule.ApplyServerSideEncryptionByDefault.SSEAlgorithm) + } + + config := encryptionConfigFromXML(&xmlConfig) + return config, nil +} + +// encryptionConfigToXMLBytes converts encryption configuration to XML bytes +func encryptionConfigToXMLBytes(config *s3_pb.EncryptionConfiguration) ([]byte, error) { + if config == nil { + return nil, fmt.Errorf("encryption configuration is nil") + } + + xmlConfig := encryptionConfigToXML(config) + return xml.Marshal(xmlConfig) +} |
