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