You can not select more than 25 topics
			Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
		
		
		
		
		
			
		
			
				
					
					
						
							346 lines
						
					
					
						
							12 KiB
						
					
					
				
			
		
		
		
			
			
			
		
		
	
	
							346 lines
						
					
					
						
							12 KiB
						
					
					
				
								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)
							 | 
						|
								}
							 |