s3api_bucket_policy_handlers.go 10 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328
  1. package s3api
  2. import (
  3. "context"
  4. "encoding/json"
  5. "fmt"
  6. "io"
  7. "net/http"
  8. "strings"
  9. "github.com/seaweedfs/seaweedfs/weed/glog"
  10. "github.com/seaweedfs/seaweedfs/weed/iam/policy"
  11. "github.com/seaweedfs/seaweedfs/weed/pb/filer_pb"
  12. "github.com/seaweedfs/seaweedfs/weed/s3api/s3_constants"
  13. "github.com/seaweedfs/seaweedfs/weed/s3api/s3err"
  14. )
  15. // Bucket policy metadata key for storing policies in filer
  16. const BUCKET_POLICY_METADATA_KEY = "s3-bucket-policy"
  17. // GetBucketPolicyHandler handles GET bucket?policy requests
  18. func (s3a *S3ApiServer) GetBucketPolicyHandler(w http.ResponseWriter, r *http.Request) {
  19. bucket, _ := s3_constants.GetBucketAndObject(r)
  20. glog.V(3).Infof("GetBucketPolicyHandler: bucket=%s", bucket)
  21. // Get bucket policy from filer metadata
  22. policyDocument, err := s3a.getBucketPolicy(bucket)
  23. if err != nil {
  24. if strings.Contains(err.Error(), "not found") {
  25. s3err.WriteErrorResponse(w, r, s3err.ErrNoSuchBucketPolicy)
  26. } else {
  27. glog.Errorf("Failed to get bucket policy for %s: %v", bucket, err)
  28. s3err.WriteErrorResponse(w, r, s3err.ErrInternalError)
  29. }
  30. return
  31. }
  32. // Return policy as JSON
  33. w.Header().Set("Content-Type", "application/json")
  34. w.WriteHeader(http.StatusOK)
  35. if err := json.NewEncoder(w).Encode(policyDocument); err != nil {
  36. glog.Errorf("Failed to encode bucket policy response: %v", err)
  37. }
  38. }
  39. // PutBucketPolicyHandler handles PUT bucket?policy requests
  40. func (s3a *S3ApiServer) PutBucketPolicyHandler(w http.ResponseWriter, r *http.Request) {
  41. bucket, _ := s3_constants.GetBucketAndObject(r)
  42. glog.V(3).Infof("PutBucketPolicyHandler: bucket=%s", bucket)
  43. // Read policy document from request body
  44. body, err := io.ReadAll(r.Body)
  45. if err != nil {
  46. glog.Errorf("Failed to read bucket policy request body: %v", err)
  47. s3err.WriteErrorResponse(w, r, s3err.ErrInvalidPolicyDocument)
  48. return
  49. }
  50. defer r.Body.Close()
  51. // Parse and validate policy document
  52. var policyDoc policy.PolicyDocument
  53. if err := json.Unmarshal(body, &policyDoc); err != nil {
  54. glog.Errorf("Failed to parse bucket policy JSON: %v", err)
  55. s3err.WriteErrorResponse(w, r, s3err.ErrMalformedPolicy)
  56. return
  57. }
  58. // Validate policy document structure
  59. if err := policy.ValidatePolicyDocument(&policyDoc); err != nil {
  60. glog.Errorf("Invalid bucket policy document: %v", err)
  61. s3err.WriteErrorResponse(w, r, s3err.ErrInvalidPolicyDocument)
  62. return
  63. }
  64. // Additional bucket policy specific validation
  65. if err := s3a.validateBucketPolicy(&policyDoc, bucket); err != nil {
  66. glog.Errorf("Bucket policy validation failed: %v", err)
  67. s3err.WriteErrorResponse(w, r, s3err.ErrInvalidPolicyDocument)
  68. return
  69. }
  70. // Store bucket policy
  71. if err := s3a.setBucketPolicy(bucket, &policyDoc); err != nil {
  72. glog.Errorf("Failed to store bucket policy for %s: %v", bucket, err)
  73. s3err.WriteErrorResponse(w, r, s3err.ErrInternalError)
  74. return
  75. }
  76. // Update IAM integration with new bucket policy
  77. if s3a.iam.iamIntegration != nil {
  78. if err := s3a.updateBucketPolicyInIAM(bucket, &policyDoc); err != nil {
  79. glog.Errorf("Failed to update IAM with bucket policy: %v", err)
  80. // Don't fail the request, but log the warning
  81. }
  82. }
  83. w.WriteHeader(http.StatusNoContent)
  84. }
  85. // DeleteBucketPolicyHandler handles DELETE bucket?policy requests
  86. func (s3a *S3ApiServer) DeleteBucketPolicyHandler(w http.ResponseWriter, r *http.Request) {
  87. bucket, _ := s3_constants.GetBucketAndObject(r)
  88. glog.V(3).Infof("DeleteBucketPolicyHandler: bucket=%s", bucket)
  89. // Check if bucket policy exists
  90. if _, err := s3a.getBucketPolicy(bucket); err != nil {
  91. if strings.Contains(err.Error(), "not found") {
  92. s3err.WriteErrorResponse(w, r, s3err.ErrNoSuchBucketPolicy)
  93. } else {
  94. s3err.WriteErrorResponse(w, r, s3err.ErrInternalError)
  95. }
  96. return
  97. }
  98. // Delete bucket policy
  99. if err := s3a.deleteBucketPolicy(bucket); err != nil {
  100. glog.Errorf("Failed to delete bucket policy for %s: %v", bucket, err)
  101. s3err.WriteErrorResponse(w, r, s3err.ErrInternalError)
  102. return
  103. }
  104. // Update IAM integration to remove bucket policy
  105. if s3a.iam.iamIntegration != nil {
  106. if err := s3a.removeBucketPolicyFromIAM(bucket); err != nil {
  107. glog.Errorf("Failed to remove bucket policy from IAM: %v", err)
  108. // Don't fail the request, but log the warning
  109. }
  110. }
  111. w.WriteHeader(http.StatusNoContent)
  112. }
  113. // Helper functions for bucket policy storage and retrieval
  114. // getBucketPolicy retrieves a bucket policy from filer metadata
  115. func (s3a *S3ApiServer) getBucketPolicy(bucket string) (*policy.PolicyDocument, error) {
  116. var policyDoc policy.PolicyDocument
  117. err := s3a.WithFilerClient(false, func(client filer_pb.SeaweedFilerClient) error {
  118. resp, err := client.LookupDirectoryEntry(context.Background(), &filer_pb.LookupDirectoryEntryRequest{
  119. Directory: s3a.option.BucketsPath,
  120. Name: bucket,
  121. })
  122. if err != nil {
  123. return fmt.Errorf("bucket not found: %v", err)
  124. }
  125. if resp.Entry == nil {
  126. return fmt.Errorf("bucket policy not found: no entry")
  127. }
  128. policyJSON, exists := resp.Entry.Extended[BUCKET_POLICY_METADATA_KEY]
  129. if !exists || len(policyJSON) == 0 {
  130. return fmt.Errorf("bucket policy not found: no policy metadata")
  131. }
  132. if err := json.Unmarshal(policyJSON, &policyDoc); err != nil {
  133. return fmt.Errorf("failed to parse stored bucket policy: %v", err)
  134. }
  135. return nil
  136. })
  137. if err != nil {
  138. return nil, err
  139. }
  140. return &policyDoc, nil
  141. }
  142. // setBucketPolicy stores a bucket policy in filer metadata
  143. func (s3a *S3ApiServer) setBucketPolicy(bucket string, policyDoc *policy.PolicyDocument) error {
  144. // Serialize policy to JSON
  145. policyJSON, err := json.Marshal(policyDoc)
  146. if err != nil {
  147. return fmt.Errorf("failed to serialize policy: %v", err)
  148. }
  149. return s3a.WithFilerClient(false, func(client filer_pb.SeaweedFilerClient) error {
  150. // First, get the current entry to preserve other attributes
  151. resp, err := client.LookupDirectoryEntry(context.Background(), &filer_pb.LookupDirectoryEntryRequest{
  152. Directory: s3a.option.BucketsPath,
  153. Name: bucket,
  154. })
  155. if err != nil {
  156. return fmt.Errorf("bucket not found: %v", err)
  157. }
  158. entry := resp.Entry
  159. if entry.Extended == nil {
  160. entry.Extended = make(map[string][]byte)
  161. }
  162. // Set the bucket policy metadata
  163. entry.Extended[BUCKET_POLICY_METADATA_KEY] = policyJSON
  164. // Update the entry with new metadata
  165. _, err = client.UpdateEntry(context.Background(), &filer_pb.UpdateEntryRequest{
  166. Directory: s3a.option.BucketsPath,
  167. Entry: entry,
  168. })
  169. return err
  170. })
  171. }
  172. // deleteBucketPolicy removes a bucket policy from filer metadata
  173. func (s3a *S3ApiServer) deleteBucketPolicy(bucket string) error {
  174. return s3a.WithFilerClient(false, func(client filer_pb.SeaweedFilerClient) error {
  175. // Get the current entry
  176. resp, err := client.LookupDirectoryEntry(context.Background(), &filer_pb.LookupDirectoryEntryRequest{
  177. Directory: s3a.option.BucketsPath,
  178. Name: bucket,
  179. })
  180. if err != nil {
  181. return fmt.Errorf("bucket not found: %v", err)
  182. }
  183. entry := resp.Entry
  184. if entry.Extended == nil {
  185. return nil // No policy to delete
  186. }
  187. // Remove the bucket policy metadata
  188. delete(entry.Extended, BUCKET_POLICY_METADATA_KEY)
  189. // Update the entry
  190. _, err = client.UpdateEntry(context.Background(), &filer_pb.UpdateEntryRequest{
  191. Directory: s3a.option.BucketsPath,
  192. Entry: entry,
  193. })
  194. return err
  195. })
  196. }
  197. // validateBucketPolicy performs bucket-specific policy validation
  198. func (s3a *S3ApiServer) validateBucketPolicy(policyDoc *policy.PolicyDocument, bucket string) error {
  199. if policyDoc.Version != "2012-10-17" {
  200. return fmt.Errorf("unsupported policy version: %s (must be 2012-10-17)", policyDoc.Version)
  201. }
  202. if len(policyDoc.Statement) == 0 {
  203. return fmt.Errorf("policy document must contain at least one statement")
  204. }
  205. for i, statement := range policyDoc.Statement {
  206. // Bucket policies must have Principal
  207. if statement.Principal == nil {
  208. return fmt.Errorf("statement %d: bucket policies must specify a Principal", i)
  209. }
  210. // Validate resources refer to this bucket
  211. for _, resource := range statement.Resource {
  212. if !s3a.validateResourceForBucket(resource, bucket) {
  213. return fmt.Errorf("statement %d: resource %s does not match bucket %s", i, resource, bucket)
  214. }
  215. }
  216. // Validate actions are S3 actions
  217. for _, action := range statement.Action {
  218. if !strings.HasPrefix(action, "s3:") {
  219. return fmt.Errorf("statement %d: bucket policies only support S3 actions, got %s", i, action)
  220. }
  221. }
  222. }
  223. return nil
  224. }
  225. // validateResourceForBucket checks if a resource ARN is valid for the given bucket
  226. func (s3a *S3ApiServer) validateResourceForBucket(resource, bucket string) bool {
  227. // Expected formats:
  228. // arn:seaweed:s3:::bucket-name
  229. // arn:seaweed:s3:::bucket-name/*
  230. // arn:seaweed:s3:::bucket-name/path/to/object
  231. expectedBucketArn := fmt.Sprintf("arn:seaweed:s3:::%s", bucket)
  232. expectedBucketWildcard := fmt.Sprintf("arn:seaweed:s3:::%s/*", bucket)
  233. expectedBucketPath := fmt.Sprintf("arn:seaweed:s3:::%s/", bucket)
  234. return resource == expectedBucketArn ||
  235. resource == expectedBucketWildcard ||
  236. strings.HasPrefix(resource, expectedBucketPath)
  237. }
  238. // IAM integration functions
  239. // updateBucketPolicyInIAM updates the IAM system with the new bucket policy
  240. func (s3a *S3ApiServer) updateBucketPolicyInIAM(bucket string, policyDoc *policy.PolicyDocument) error {
  241. // This would integrate with our advanced IAM system
  242. // For now, we'll just log that the policy was updated
  243. glog.V(2).Infof("Updated bucket policy for %s in IAM system", bucket)
  244. // TODO: Integrate with IAM manager to store resource-based policies
  245. // s3a.iam.iamIntegration.iamManager.SetBucketPolicy(bucket, policyDoc)
  246. return nil
  247. }
  248. // removeBucketPolicyFromIAM removes the bucket policy from the IAM system
  249. func (s3a *S3ApiServer) removeBucketPolicyFromIAM(bucket string) error {
  250. // This would remove the bucket policy from our advanced IAM system
  251. glog.V(2).Infof("Removed bucket policy for %s from IAM system", bucket)
  252. // TODO: Integrate with IAM manager to remove resource-based policies
  253. // s3a.iam.iamIntegration.iamManager.RemoveBucketPolicy(bucket)
  254. return nil
  255. }
  256. // GetPublicAccessBlockHandler Retrieves the PublicAccessBlock configuration for an S3 bucket
  257. // https://docs.aws.amazon.com/AmazonS3/latest/API/API_GetPublicAccessBlock.html
  258. func (s3a *S3ApiServer) GetPublicAccessBlockHandler(w http.ResponseWriter, r *http.Request) {
  259. s3err.WriteErrorResponse(w, r, s3err.ErrNotImplemented)
  260. }
  261. func (s3a *S3ApiServer) PutPublicAccessBlockHandler(w http.ResponseWriter, r *http.Request) {
  262. s3err.WriteErrorResponse(w, r, s3err.ErrNotImplemented)
  263. }
  264. func (s3a *S3ApiServer) DeletePublicAccessBlockHandler(w http.ResponseWriter, r *http.Request) {
  265. s3err.WriteErrorResponse(w, r, s3err.ErrNotImplemented)
  266. }