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.
296 lines
8.3 KiB
296 lines
8.3 KiB
package s3api
|
|
|
|
import (
|
|
"fmt"
|
|
"net/http"
|
|
|
|
"github.com/seaweedfs/seaweedfs/weed/s3api/s3_constants"
|
|
"github.com/seaweedfs/seaweedfs/weed/s3api/s3err"
|
|
)
|
|
|
|
// CopyValidationError represents validation errors during copy operations
|
|
type CopyValidationError struct {
|
|
Code s3err.ErrorCode
|
|
Message string
|
|
}
|
|
|
|
func (e *CopyValidationError) Error() string {
|
|
return e.Message
|
|
}
|
|
|
|
// ValidateCopyEncryption performs comprehensive validation of copy encryption parameters
|
|
func ValidateCopyEncryption(srcMetadata map[string][]byte, headers http.Header) error {
|
|
// Validate SSE-C copy requirements
|
|
if err := validateSSECCopyRequirements(srcMetadata, headers); err != nil {
|
|
return err
|
|
}
|
|
|
|
// Validate SSE-KMS copy requirements
|
|
if err := validateSSEKMSCopyRequirements(srcMetadata, headers); err != nil {
|
|
return err
|
|
}
|
|
|
|
// Validate incompatible encryption combinations
|
|
if err := validateEncryptionCompatibility(headers); err != nil {
|
|
return err
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
// validateSSECCopyRequirements validates SSE-C copy header requirements
|
|
func validateSSECCopyRequirements(srcMetadata map[string][]byte, headers http.Header) error {
|
|
srcIsSSEC := IsSSECEncrypted(srcMetadata)
|
|
hasCopyHeaders := hasSSECCopyHeaders(headers)
|
|
hasSSECHeaders := hasSSECHeaders(headers)
|
|
|
|
// If source is SSE-C encrypted, copy headers are required
|
|
if srcIsSSEC && !hasCopyHeaders {
|
|
return &CopyValidationError{
|
|
Code: s3err.ErrInvalidRequest,
|
|
Message: "SSE-C encrypted source requires copy source encryption headers",
|
|
}
|
|
}
|
|
|
|
// If copy headers are provided, source must be SSE-C encrypted
|
|
if hasCopyHeaders && !srcIsSSEC {
|
|
return &CopyValidationError{
|
|
Code: s3err.ErrInvalidRequest,
|
|
Message: "SSE-C copy headers provided but source is not SSE-C encrypted",
|
|
}
|
|
}
|
|
|
|
// Validate copy header completeness
|
|
if hasCopyHeaders {
|
|
if err := validateSSECCopyHeaderCompleteness(headers); err != nil {
|
|
return err
|
|
}
|
|
}
|
|
|
|
// Validate destination SSE-C headers if present
|
|
if hasSSECHeaders {
|
|
if err := validateSSECHeaderCompleteness(headers); err != nil {
|
|
return err
|
|
}
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
// validateSSEKMSCopyRequirements validates SSE-KMS copy requirements
|
|
func validateSSEKMSCopyRequirements(srcMetadata map[string][]byte, headers http.Header) error {
|
|
dstIsSSEKMS := IsSSEKMSRequest(&http.Request{Header: headers})
|
|
|
|
// Validate KMS key ID format if provided
|
|
if dstIsSSEKMS {
|
|
keyID := headers.Get(s3_constants.AmzServerSideEncryptionAwsKmsKeyId)
|
|
if keyID != "" && !isValidKMSKeyID(keyID) {
|
|
return &CopyValidationError{
|
|
Code: s3err.ErrKMSKeyNotFound,
|
|
Message: fmt.Sprintf("Invalid KMS key ID format: %s", keyID),
|
|
}
|
|
}
|
|
}
|
|
|
|
// Validate encryption context format if provided
|
|
if contextHeader := headers.Get(s3_constants.AmzServerSideEncryptionContext); contextHeader != "" {
|
|
if !dstIsSSEKMS {
|
|
return &CopyValidationError{
|
|
Code: s3err.ErrInvalidRequest,
|
|
Message: "Encryption context can only be used with SSE-KMS",
|
|
}
|
|
}
|
|
|
|
// Validate base64 encoding and JSON format
|
|
if err := validateEncryptionContext(contextHeader); err != nil {
|
|
return &CopyValidationError{
|
|
Code: s3err.ErrInvalidRequest,
|
|
Message: fmt.Sprintf("Invalid encryption context: %v", err),
|
|
}
|
|
}
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
// validateEncryptionCompatibility validates that encryption methods are not conflicting
|
|
func validateEncryptionCompatibility(headers http.Header) error {
|
|
hasSSEC := hasSSECHeaders(headers)
|
|
hasSSEKMS := headers.Get(s3_constants.AmzServerSideEncryption) == "aws:kms"
|
|
hasSSES3 := headers.Get(s3_constants.AmzServerSideEncryption) == "AES256"
|
|
|
|
// Count how many encryption methods are specified
|
|
encryptionCount := 0
|
|
if hasSSEC {
|
|
encryptionCount++
|
|
}
|
|
if hasSSEKMS {
|
|
encryptionCount++
|
|
}
|
|
if hasSSES3 {
|
|
encryptionCount++
|
|
}
|
|
|
|
// Only one encryption method should be specified
|
|
if encryptionCount > 1 {
|
|
return &CopyValidationError{
|
|
Code: s3err.ErrInvalidRequest,
|
|
Message: "Multiple encryption methods specified - only one is allowed",
|
|
}
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
// validateSSECCopyHeaderCompleteness validates that all required SSE-C copy headers are present
|
|
func validateSSECCopyHeaderCompleteness(headers http.Header) error {
|
|
algorithm := headers.Get(s3_constants.AmzCopySourceServerSideEncryptionCustomerAlgorithm)
|
|
key := headers.Get(s3_constants.AmzCopySourceServerSideEncryptionCustomerKey)
|
|
keyMD5 := headers.Get(s3_constants.AmzCopySourceServerSideEncryptionCustomerKeyMD5)
|
|
|
|
if algorithm == "" {
|
|
return &CopyValidationError{
|
|
Code: s3err.ErrInvalidRequest,
|
|
Message: "SSE-C copy customer algorithm header is required",
|
|
}
|
|
}
|
|
|
|
if key == "" {
|
|
return &CopyValidationError{
|
|
Code: s3err.ErrInvalidRequest,
|
|
Message: "SSE-C copy customer key header is required",
|
|
}
|
|
}
|
|
|
|
if keyMD5 == "" {
|
|
return &CopyValidationError{
|
|
Code: s3err.ErrInvalidRequest,
|
|
Message: "SSE-C copy customer key MD5 header is required",
|
|
}
|
|
}
|
|
|
|
// Validate algorithm
|
|
if algorithm != "AES256" {
|
|
return &CopyValidationError{
|
|
Code: s3err.ErrInvalidRequest,
|
|
Message: fmt.Sprintf("Unsupported SSE-C algorithm: %s", algorithm),
|
|
}
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
// validateSSECHeaderCompleteness validates that all required SSE-C headers are present
|
|
func validateSSECHeaderCompleteness(headers http.Header) error {
|
|
algorithm := headers.Get(s3_constants.AmzServerSideEncryptionCustomerAlgorithm)
|
|
key := headers.Get(s3_constants.AmzServerSideEncryptionCustomerKey)
|
|
keyMD5 := headers.Get(s3_constants.AmzServerSideEncryptionCustomerKeyMD5)
|
|
|
|
if algorithm == "" {
|
|
return &CopyValidationError{
|
|
Code: s3err.ErrInvalidRequest,
|
|
Message: "SSE-C customer algorithm header is required",
|
|
}
|
|
}
|
|
|
|
if key == "" {
|
|
return &CopyValidationError{
|
|
Code: s3err.ErrInvalidRequest,
|
|
Message: "SSE-C customer key header is required",
|
|
}
|
|
}
|
|
|
|
if keyMD5 == "" {
|
|
return &CopyValidationError{
|
|
Code: s3err.ErrInvalidRequest,
|
|
Message: "SSE-C customer key MD5 header is required",
|
|
}
|
|
}
|
|
|
|
// Validate algorithm
|
|
if algorithm != "AES256" {
|
|
return &CopyValidationError{
|
|
Code: s3err.ErrInvalidRequest,
|
|
Message: fmt.Sprintf("Unsupported SSE-C algorithm: %s", algorithm),
|
|
}
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
// Helper functions for header detection
|
|
func hasSSECCopyHeaders(headers http.Header) bool {
|
|
return headers.Get(s3_constants.AmzCopySourceServerSideEncryptionCustomerAlgorithm) != "" ||
|
|
headers.Get(s3_constants.AmzCopySourceServerSideEncryptionCustomerKey) != "" ||
|
|
headers.Get(s3_constants.AmzCopySourceServerSideEncryptionCustomerKeyMD5) != ""
|
|
}
|
|
|
|
func hasSSECHeaders(headers http.Header) bool {
|
|
return headers.Get(s3_constants.AmzServerSideEncryptionCustomerAlgorithm) != "" ||
|
|
headers.Get(s3_constants.AmzServerSideEncryptionCustomerKey) != "" ||
|
|
headers.Get(s3_constants.AmzServerSideEncryptionCustomerKeyMD5) != ""
|
|
}
|
|
|
|
// validateEncryptionContext validates the encryption context header format
|
|
func validateEncryptionContext(contextHeader string) error {
|
|
// This would validate base64 encoding and JSON format
|
|
// Implementation would decode base64 and parse JSON
|
|
// For now, just check it's not empty
|
|
if contextHeader == "" {
|
|
return fmt.Errorf("encryption context cannot be empty")
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// ValidateCopySource validates the copy source path and permissions
|
|
func ValidateCopySource(copySource string, srcBucket, srcObject string) error {
|
|
if copySource == "" {
|
|
return &CopyValidationError{
|
|
Code: s3err.ErrInvalidCopySource,
|
|
Message: "Copy source header is required",
|
|
}
|
|
}
|
|
|
|
if srcBucket == "" {
|
|
return &CopyValidationError{
|
|
Code: s3err.ErrInvalidCopySource,
|
|
Message: "Source bucket cannot be empty",
|
|
}
|
|
}
|
|
|
|
if srcObject == "" {
|
|
return &CopyValidationError{
|
|
Code: s3err.ErrInvalidCopySource,
|
|
Message: "Source object cannot be empty",
|
|
}
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
// ValidateCopyDestination validates the copy destination
|
|
func ValidateCopyDestination(dstBucket, dstObject string) error {
|
|
if dstBucket == "" {
|
|
return &CopyValidationError{
|
|
Code: s3err.ErrInvalidRequest,
|
|
Message: "Destination bucket cannot be empty",
|
|
}
|
|
}
|
|
|
|
if dstObject == "" {
|
|
return &CopyValidationError{
|
|
Code: s3err.ErrInvalidRequest,
|
|
Message: "Destination object cannot be empty",
|
|
}
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
// MapCopyValidationError maps validation errors to appropriate S3 error codes
|
|
func MapCopyValidationError(err error) s3err.ErrorCode {
|
|
if validationErr, ok := err.(*CopyValidationError); ok {
|
|
return validationErr.Code
|
|
}
|
|
return s3err.ErrInvalidRequest
|
|
}
|