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) | |
| }
 |