aboutsummaryrefslogtreecommitdiff
path: root/weed/s3api/s3api_bucket_policy_handlers.go
diff options
context:
space:
mode:
Diffstat (limited to 'weed/s3api/s3api_bucket_policy_handlers.go')
-rw-r--r--weed/s3api/s3api_bucket_policy_handlers.go328
1 files changed, 328 insertions, 0 deletions
diff --git a/weed/s3api/s3api_bucket_policy_handlers.go b/weed/s3api/s3api_bucket_policy_handlers.go
new file mode 100644
index 000000000..e079eb53e
--- /dev/null
+++ b/weed/s3api/s3api_bucket_policy_handlers.go
@@ -0,0 +1,328 @@
+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)
+}