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" ) // GetBucketEncryptionHandler handles GET bucket encryption requests func (s3a *S3ApiServer) GetBucketEncryptionHandler(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 } } // PutBucketEncryptionHandler handles PUT bucket encryption requests func (s3a *S3ApiServer) PutBucketEncryptionHandler(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) } // DeleteBucketEncryptionHandler handles DELETE bucket encryption requests func (s3a *S3ApiServer) DeleteBucketEncryptionHandler(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) }