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