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.
		
		
		
		
		
			
		
			
				
					
					
						
							328 lines
						
					
					
						
							10 KiB
						
					
					
				
			
		
		
		
			
			
			
		
		
	
	
							328 lines
						
					
					
						
							10 KiB
						
					
					
				| package s3api | |
| 
 | |
| import ( | |
| 	"context" | |
| 	"encoding/json" | |
| 	"fmt" | |
| 	"io" | |
| 	"net/http" | |
| 	"strings" | |
| 
 | |
| 	"github.com/seaweedfs/seaweedfs/weed/glog" | |
| 	"github.com/seaweedfs/seaweedfs/weed/iam/policy" | |
| 	"github.com/seaweedfs/seaweedfs/weed/pb/filer_pb" | |
| 	"github.com/seaweedfs/seaweedfs/weed/s3api/s3_constants" | |
| 	"github.com/seaweedfs/seaweedfs/weed/s3api/s3err" | |
| ) | |
| 
 | |
| // Bucket policy metadata key for storing policies in filer | |
| const BUCKET_POLICY_METADATA_KEY = "s3-bucket-policy" | |
| 
 | |
| // GetBucketPolicyHandler handles GET bucket?policy requests | |
| func (s3a *S3ApiServer) GetBucketPolicyHandler(w http.ResponseWriter, r *http.Request) { | |
| 	bucket, _ := s3_constants.GetBucketAndObject(r) | |
| 
 | |
| 	glog.V(3).Infof("GetBucketPolicyHandler: bucket=%s", bucket) | |
| 
 | |
| 	// Get bucket policy from filer metadata | |
| 	policyDocument, err := s3a.getBucketPolicy(bucket) | |
| 	if err != nil { | |
| 		if strings.Contains(err.Error(), "not found") { | |
| 			s3err.WriteErrorResponse(w, r, s3err.ErrNoSuchBucketPolicy) | |
| 		} else { | |
| 			glog.Errorf("Failed to get bucket policy for %s: %v", bucket, err) | |
| 			s3err.WriteErrorResponse(w, r, s3err.ErrInternalError) | |
| 		} | |
| 		return | |
| 	} | |
| 
 | |
| 	// Return policy as JSON | |
| 	w.Header().Set("Content-Type", "application/json") | |
| 	w.WriteHeader(http.StatusOK) | |
| 
 | |
| 	if err := json.NewEncoder(w).Encode(policyDocument); err != nil { | |
| 		glog.Errorf("Failed to encode bucket policy response: %v", err) | |
| 	} | |
| } | |
| 
 | |
| // PutBucketPolicyHandler handles PUT bucket?policy requests | |
| func (s3a *S3ApiServer) PutBucketPolicyHandler(w http.ResponseWriter, r *http.Request) { | |
| 	bucket, _ := s3_constants.GetBucketAndObject(r) | |
| 
 | |
| 	glog.V(3).Infof("PutBucketPolicyHandler: bucket=%s", bucket) | |
| 
 | |
| 	// Read policy document from request body | |
| 	body, err := io.ReadAll(r.Body) | |
| 	if err != nil { | |
| 		glog.Errorf("Failed to read bucket policy request body: %v", err) | |
| 		s3err.WriteErrorResponse(w, r, s3err.ErrInvalidPolicyDocument) | |
| 		return | |
| 	} | |
| 	defer r.Body.Close() | |
| 
 | |
| 	// Parse and validate policy document | |
| 	var policyDoc policy.PolicyDocument | |
| 	if err := json.Unmarshal(body, &policyDoc); err != nil { | |
| 		glog.Errorf("Failed to parse bucket policy JSON: %v", err) | |
| 		s3err.WriteErrorResponse(w, r, s3err.ErrMalformedPolicy) | |
| 		return | |
| 	} | |
| 
 | |
| 	// Validate policy document structure | |
| 	if err := policy.ValidatePolicyDocument(&policyDoc); err != nil { | |
| 		glog.Errorf("Invalid bucket policy document: %v", err) | |
| 		s3err.WriteErrorResponse(w, r, s3err.ErrInvalidPolicyDocument) | |
| 		return | |
| 	} | |
| 
 | |
| 	// Additional bucket policy specific validation | |
| 	if err := s3a.validateBucketPolicy(&policyDoc, bucket); err != nil { | |
| 		glog.Errorf("Bucket policy validation failed: %v", err) | |
| 		s3err.WriteErrorResponse(w, r, s3err.ErrInvalidPolicyDocument) | |
| 		return | |
| 	} | |
| 
 | |
| 	// Store bucket policy | |
| 	if err := s3a.setBucketPolicy(bucket, &policyDoc); err != nil { | |
| 		glog.Errorf("Failed to store bucket policy for %s: %v", bucket, err) | |
| 		s3err.WriteErrorResponse(w, r, s3err.ErrInternalError) | |
| 		return | |
| 	} | |
| 
 | |
| 	// Update IAM integration with new bucket policy | |
| 	if s3a.iam.iamIntegration != nil { | |
| 		if err := s3a.updateBucketPolicyInIAM(bucket, &policyDoc); err != nil { | |
| 			glog.Errorf("Failed to update IAM with bucket policy: %v", err) | |
| 			// Don't fail the request, but log the warning | |
| 		} | |
| 	} | |
| 
 | |
| 	w.WriteHeader(http.StatusNoContent) | |
| } | |
| 
 | |
| // DeleteBucketPolicyHandler handles DELETE bucket?policy requests | |
| func (s3a *S3ApiServer) DeleteBucketPolicyHandler(w http.ResponseWriter, r *http.Request) { | |
| 	bucket, _ := s3_constants.GetBucketAndObject(r) | |
| 
 | |
| 	glog.V(3).Infof("DeleteBucketPolicyHandler: bucket=%s", bucket) | |
| 
 | |
| 	// Check if bucket policy exists | |
| 	if _, err := s3a.getBucketPolicy(bucket); err != nil { | |
| 		if strings.Contains(err.Error(), "not found") { | |
| 			s3err.WriteErrorResponse(w, r, s3err.ErrNoSuchBucketPolicy) | |
| 		} else { | |
| 			s3err.WriteErrorResponse(w, r, s3err.ErrInternalError) | |
| 		} | |
| 		return | |
| 	} | |
| 
 | |
| 	// Delete bucket policy | |
| 	if err := s3a.deleteBucketPolicy(bucket); err != nil { | |
| 		glog.Errorf("Failed to delete bucket policy for %s: %v", bucket, err) | |
| 		s3err.WriteErrorResponse(w, r, s3err.ErrInternalError) | |
| 		return | |
| 	} | |
| 
 | |
| 	// Update IAM integration to remove bucket policy | |
| 	if s3a.iam.iamIntegration != nil { | |
| 		if err := s3a.removeBucketPolicyFromIAM(bucket); err != nil { | |
| 			glog.Errorf("Failed to remove bucket policy from IAM: %v", err) | |
| 			// Don't fail the request, but log the warning | |
| 		} | |
| 	} | |
| 
 | |
| 	w.WriteHeader(http.StatusNoContent) | |
| } | |
| 
 | |
| // Helper functions for bucket policy storage and retrieval | |
|  | |
| // getBucketPolicy retrieves a bucket policy from filer metadata | |
| func (s3a *S3ApiServer) getBucketPolicy(bucket string) (*policy.PolicyDocument, error) { | |
| 
 | |
| 	var policyDoc policy.PolicyDocument | |
| 	err := s3a.WithFilerClient(false, func(client filer_pb.SeaweedFilerClient) error { | |
| 		resp, err := client.LookupDirectoryEntry(context.Background(), &filer_pb.LookupDirectoryEntryRequest{ | |
| 			Directory: s3a.option.BucketsPath, | |
| 			Name:      bucket, | |
| 		}) | |
| 		if err != nil { | |
| 			return fmt.Errorf("bucket not found: %v", err) | |
| 		} | |
| 
 | |
| 		if resp.Entry == nil { | |
| 			return fmt.Errorf("bucket policy not found: no entry") | |
| 		} | |
| 
 | |
| 		policyJSON, exists := resp.Entry.Extended[BUCKET_POLICY_METADATA_KEY] | |
| 		if !exists || len(policyJSON) == 0 { | |
| 			return fmt.Errorf("bucket policy not found: no policy metadata") | |
| 		} | |
| 
 | |
| 		if err := json.Unmarshal(policyJSON, &policyDoc); err != nil { | |
| 			return fmt.Errorf("failed to parse stored bucket policy: %v", err) | |
| 		} | |
| 
 | |
| 		return nil | |
| 	}) | |
| 
 | |
| 	if err != nil { | |
| 		return nil, err | |
| 	} | |
| 
 | |
| 	return &policyDoc, nil | |
| } | |
| 
 | |
| // setBucketPolicy stores a bucket policy in filer metadata | |
| func (s3a *S3ApiServer) setBucketPolicy(bucket string, policyDoc *policy.PolicyDocument) error { | |
| 	// Serialize policy to JSON | |
| 	policyJSON, err := json.Marshal(policyDoc) | |
| 	if err != nil { | |
| 		return fmt.Errorf("failed to serialize policy: %v", err) | |
| 	} | |
| 
 | |
| 	return s3a.WithFilerClient(false, func(client filer_pb.SeaweedFilerClient) error { | |
| 		// First, get the current entry to preserve other attributes | |
| 		resp, err := client.LookupDirectoryEntry(context.Background(), &filer_pb.LookupDirectoryEntryRequest{ | |
| 			Directory: s3a.option.BucketsPath, | |
| 			Name:      bucket, | |
| 		}) | |
| 		if err != nil { | |
| 			return fmt.Errorf("bucket not found: %v", err) | |
| 		} | |
| 
 | |
| 		entry := resp.Entry | |
| 		if entry.Extended == nil { | |
| 			entry.Extended = make(map[string][]byte) | |
| 		} | |
| 
 | |
| 		// Set the bucket policy metadata | |
| 		entry.Extended[BUCKET_POLICY_METADATA_KEY] = policyJSON | |
| 
 | |
| 		// Update the entry with new metadata | |
| 		_, err = client.UpdateEntry(context.Background(), &filer_pb.UpdateEntryRequest{ | |
| 			Directory: s3a.option.BucketsPath, | |
| 			Entry:     entry, | |
| 		}) | |
| 
 | |
| 		return err | |
| 	}) | |
| } | |
| 
 | |
| // deleteBucketPolicy removes a bucket policy from filer metadata | |
| func (s3a *S3ApiServer) deleteBucketPolicy(bucket string) error { | |
| 	return s3a.WithFilerClient(false, func(client filer_pb.SeaweedFilerClient) error { | |
| 		// Get the current entry | |
| 		resp, err := client.LookupDirectoryEntry(context.Background(), &filer_pb.LookupDirectoryEntryRequest{ | |
| 			Directory: s3a.option.BucketsPath, | |
| 			Name:      bucket, | |
| 		}) | |
| 		if err != nil { | |
| 			return fmt.Errorf("bucket not found: %v", err) | |
| 		} | |
| 
 | |
| 		entry := resp.Entry | |
| 		if entry.Extended == nil { | |
| 			return nil // No policy to delete | |
| 		} | |
| 
 | |
| 		// Remove the bucket policy metadata | |
| 		delete(entry.Extended, BUCKET_POLICY_METADATA_KEY) | |
| 
 | |
| 		// Update the entry | |
| 		_, err = client.UpdateEntry(context.Background(), &filer_pb.UpdateEntryRequest{ | |
| 			Directory: s3a.option.BucketsPath, | |
| 			Entry:     entry, | |
| 		}) | |
| 
 | |
| 		return err | |
| 	}) | |
| } | |
| 
 | |
| // validateBucketPolicy performs bucket-specific policy validation | |
| func (s3a *S3ApiServer) validateBucketPolicy(policyDoc *policy.PolicyDocument, bucket string) error { | |
| 	if policyDoc.Version != "2012-10-17" { | |
| 		return fmt.Errorf("unsupported policy version: %s (must be 2012-10-17)", policyDoc.Version) | |
| 	} | |
| 
 | |
| 	if len(policyDoc.Statement) == 0 { | |
| 		return fmt.Errorf("policy document must contain at least one statement") | |
| 	} | |
| 
 | |
| 	for i, statement := range policyDoc.Statement { | |
| 		// Bucket policies must have Principal | |
| 		if statement.Principal == nil { | |
| 			return fmt.Errorf("statement %d: bucket policies must specify a Principal", i) | |
| 		} | |
| 
 | |
| 		// Validate resources refer to this bucket | |
| 		for _, resource := range statement.Resource { | |
| 			if !s3a.validateResourceForBucket(resource, bucket) { | |
| 				return fmt.Errorf("statement %d: resource %s does not match bucket %s", i, resource, bucket) | |
| 			} | |
| 		} | |
| 
 | |
| 		// Validate actions are S3 actions | |
| 		for _, action := range statement.Action { | |
| 			if !strings.HasPrefix(action, "s3:") { | |
| 				return fmt.Errorf("statement %d: bucket policies only support S3 actions, got %s", i, action) | |
| 			} | |
| 		} | |
| 	} | |
| 
 | |
| 	return nil | |
| } | |
| 
 | |
| // validateResourceForBucket checks if a resource ARN is valid for the given bucket | |
| func (s3a *S3ApiServer) validateResourceForBucket(resource, bucket string) bool { | |
| 	// Expected formats: | |
| 	// arn:seaweed:s3:::bucket-name | |
| 	// arn:seaweed:s3:::bucket-name/* | |
| 	// arn:seaweed:s3:::bucket-name/path/to/object | |
|  | |
| 	expectedBucketArn := fmt.Sprintf("arn:seaweed:s3:::%s", bucket) | |
| 	expectedBucketWildcard := fmt.Sprintf("arn:seaweed:s3:::%s/*", bucket) | |
| 	expectedBucketPath := fmt.Sprintf("arn:seaweed:s3:::%s/", bucket) | |
| 
 | |
| 	return resource == expectedBucketArn || | |
| 		resource == expectedBucketWildcard || | |
| 		strings.HasPrefix(resource, expectedBucketPath) | |
| } | |
| 
 | |
| // IAM integration functions | |
|  | |
| // updateBucketPolicyInIAM updates the IAM system with the new bucket policy | |
| func (s3a *S3ApiServer) updateBucketPolicyInIAM(bucket string, policyDoc *policy.PolicyDocument) error { | |
| 	// This would integrate with our advanced IAM system | |
| 	// For now, we'll just log that the policy was updated | |
| 	glog.V(2).Infof("Updated bucket policy for %s in IAM system", bucket) | |
| 
 | |
| 	// TODO: Integrate with IAM manager to store resource-based policies | |
| 	// s3a.iam.iamIntegration.iamManager.SetBucketPolicy(bucket, policyDoc) | |
|  | |
| 	return nil | |
| } | |
| 
 | |
| // removeBucketPolicyFromIAM removes the bucket policy from the IAM system | |
| func (s3a *S3ApiServer) removeBucketPolicyFromIAM(bucket string) error { | |
| 	// This would remove the bucket policy from our advanced IAM system | |
| 	glog.V(2).Infof("Removed bucket policy for %s from IAM system", bucket) | |
| 
 | |
| 	// TODO: Integrate with IAM manager to remove resource-based policies | |
| 	// s3a.iam.iamIntegration.iamManager.RemoveBucketPolicy(bucket) | |
|  | |
| 	return nil | |
| } | |
| 
 | |
| // GetPublicAccessBlockHandler Retrieves the PublicAccessBlock configuration for an S3 bucket | |
| // https://docs.aws.amazon.com/AmazonS3/latest/API/API_GetPublicAccessBlock.html | |
| func (s3a *S3ApiServer) GetPublicAccessBlockHandler(w http.ResponseWriter, r *http.Request) { | |
| 	s3err.WriteErrorResponse(w, r, s3err.ErrNotImplemented) | |
| } | |
| 
 | |
| func (s3a *S3ApiServer) PutPublicAccessBlockHandler(w http.ResponseWriter, r *http.Request) { | |
| 	s3err.WriteErrorResponse(w, r, s3err.ErrNotImplemented) | |
| } | |
| 
 | |
| func (s3a *S3ApiServer) DeletePublicAccessBlockHandler(w http.ResponseWriter, r *http.Request) { | |
| 	s3err.WriteErrorResponse(w, r, s3err.ErrNotImplemented) | |
| }
 |