package s3api import ( "encoding/xml" "errors" "fmt" "io" "net/http" "strings" "github.com/seaweedfs/seaweedfs/weed/glog" "github.com/seaweedfs/seaweedfs/weed/pb/filer_pb" "github.com/seaweedfs/seaweedfs/weed/pb/s3_pb" "github.com/seaweedfs/seaweedfs/weed/s3api/s3_constants" "github.com/seaweedfs/seaweedfs/weed/s3api/s3err" ) // ErrNoEncryptionConfig is returned when a bucket has no encryption configuration var ErrNoEncryptionConfig = errors.New("no encryption configuration found") // 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"` } // 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, ErrNoEncryptionConfig } 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 { // If the bucket directory is not found (e.g., during bucket recreation after // a partial delete), treat it as having no encryption configured rather than // failing the upload with an internal error. if errors.Is(err, filer_pb.ErrNotFound) || strings.Contains(err.Error(), "bucket directory not found") { glog.Warningf("getEncryptionConfiguration: bucket metadata not found for %s, treating as no encryption: %v", bucket, err) return nil, s3err.ErrNoSuchBucketEncryptionConfiguration } 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 // Note: UpdateBucketEncryption -> UpdateBucketMetadata -> setBucketMetadata // already invalidates the cache synchronously after successful update 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 } 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 // Note: ClearBucketEncryption -> UpdateBucketMetadata -> setBucketMetadata // already invalidates the cache synchronously after successful update err = s3a.ClearBucketEncryption(bucket) if err != nil { glog.Errorf("removeEncryptionConfiguration: failed to remove encryption config for bucket %s: %v", bucket, err) return s3err.ErrInternalError } 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 { glog.V(4).Infof("IsDefaultEncryptionEnabled: failed to get encryption config for bucket %s: %v", bucket, err) return false } if 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 { glog.V(4).Infof("GetDefaultEncryptionHeaders: failed to get encryption config for bucket %s: %v", bucket, err) return nil } if 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 }