Browse Source

refactor

pull/7479/head
chrislu 2 months ago
parent
commit
8a63906f90
  1. 125
      weed/s3api/s3_action_resolver.go
  2. 1
      weed/s3api/s3_constants/s3_action_strings.go
  3. 118
      weed/s3api/s3_granular_action_security_test.go
  4. 162
      weed/s3api/s3_iam_middleware.go
  5. 64
      weed/s3api/s3api_bucket_handlers.go
  6. 45
      weed/s3api/s3api_bucket_policy_engine.go

125
weed/s3api/s3_action_resolver.go

@ -53,6 +53,37 @@ func ResolveS3Action(r *http.Request, baseAction string, bucket string, object s
return mapBaseActionToS3Format(baseAction)
}
// bucketQueryActions maps bucket-level query parameters to their corresponding S3 actions by HTTP method
var bucketQueryActions = map[string]map[string]string{
"policy": {
http.MethodGet: s3_constants.S3_ACTION_GET_BUCKET_POLICY,
http.MethodPut: s3_constants.S3_ACTION_PUT_BUCKET_POLICY,
http.MethodDelete: s3_constants.S3_ACTION_DELETE_BUCKET_POLICY,
},
"cors": {
http.MethodGet: s3_constants.S3_ACTION_GET_BUCKET_CORS,
http.MethodPut: s3_constants.S3_ACTION_PUT_BUCKET_CORS,
http.MethodDelete: s3_constants.S3_ACTION_DELETE_BUCKET_CORS,
},
"lifecycle": {
http.MethodGet: s3_constants.S3_ACTION_GET_BUCKET_LIFECYCLE,
http.MethodPut: s3_constants.S3_ACTION_PUT_BUCKET_LIFECYCLE,
http.MethodDelete: s3_constants.S3_ACTION_DELETE_BUCKET_LIFECYCLE,
},
"versioning": {
http.MethodGet: s3_constants.S3_ACTION_GET_BUCKET_VERSIONING,
http.MethodPut: s3_constants.S3_ACTION_PUT_BUCKET_VERSIONING,
},
"notification": {
http.MethodGet: s3_constants.S3_ACTION_GET_BUCKET_NOTIFICATION,
http.MethodPut: s3_constants.S3_ACTION_PUT_BUCKET_NOTIFICATION,
},
"object-lock": {
http.MethodGet: s3_constants.S3_ACTION_GET_BUCKET_OBJECT_LOCK,
http.MethodPut: s3_constants.S3_ACTION_PUT_BUCKET_OBJECT_LOCK,
},
}
// resolveFromQueryParameters checks query parameters to determine specific S3 actions
func resolveFromQueryParameters(query url.Values, method string, hasObject bool) string {
// Multipart upload operations
@ -118,15 +149,6 @@ func resolveFromQueryParameters(query url.Values, method string, hasObject bool)
}
}
}
// Versioning operations
if query.Has("versioning") {
if method == http.MethodGet {
return s3_constants.S3_ACTION_GET_BUCKET_VERSIONING
} else if method == http.MethodPut {
return s3_constants.S3_ACTION_PUT_BUCKET_VERSIONING
}
}
if query.Has("versions") {
if method == http.MethodGet {
@ -144,75 +166,36 @@ func resolveFromQueryParameters(query url.Values, method string, hasObject bool)
}
}
// Policy operations
if query.Has("policy") {
if method == http.MethodGet {
return s3_constants.S3_ACTION_GET_BUCKET_POLICY
} else if method == http.MethodPut {
return s3_constants.S3_ACTION_PUT_BUCKET_POLICY
} else if method == http.MethodDelete {
return s3_constants.S3_ACTION_DELETE_BUCKET_POLICY
}
}
// CORS operations
if query.Has("cors") {
if method == http.MethodGet {
return s3_constants.S3_ACTION_GET_BUCKET_CORS
} else if method == http.MethodPut {
return s3_constants.S3_ACTION_PUT_BUCKET_CORS
} else if method == http.MethodDelete {
return s3_constants.S3_ACTION_DELETE_BUCKET_CORS
}
}
// Lifecycle operations
if query.Has("lifecycle") {
if method == http.MethodGet {
return s3_constants.S3_ACTION_GET_BUCKET_LIFECYCLE
} else if method == http.MethodPut {
return s3_constants.S3_ACTION_PUT_BUCKET_LIFECYCLE
} else if method == http.MethodDelete {
return s3_constants.S3_ACTION_DELETE_BUCKET_LIFECYCLE
// Check bucket-level query parameters using data-driven approach
for param, actions := range bucketQueryActions {
if query.Has(param) {
if action, ok := actions[method]; ok {
return action
}
}
}
// Location
// Location (GET only)
if query.Has("location") {
return s3_constants.S3_ACTION_GET_BUCKET_LOCATION
}
// Notification
if query.Has("notification") {
if method == http.MethodGet {
return s3_constants.S3_ACTION_GET_BUCKET_NOTIFICATION
} else if method == http.MethodPut {
return s3_constants.S3_ACTION_PUT_BUCKET_NOTIFICATION
}
}
// Object Lock operations
if query.Has("object-lock") {
if method == http.MethodGet {
return s3_constants.S3_ACTION_GET_BUCKET_OBJECT_LOCK
} else if method == http.MethodPut {
return s3_constants.S3_ACTION_PUT_BUCKET_OBJECT_LOCK
}
}
if query.Has("retention") {
if method == http.MethodGet {
return s3_constants.S3_ACTION_GET_OBJECT_RETENTION
} else if method == http.MethodPut {
return s3_constants.S3_ACTION_PUT_OBJECT_RETENTION
// Object retention and legal hold operations (object-level only)
if hasObject {
if query.Has("retention") {
if method == http.MethodGet {
return s3_constants.S3_ACTION_GET_OBJECT_RETENTION
} else if method == http.MethodPut {
return s3_constants.S3_ACTION_PUT_OBJECT_RETENTION
}
}
}
if query.Has("legal-hold") {
if method == http.MethodGet {
return s3_constants.S3_ACTION_GET_OBJECT_LEGAL_HOLD
} else if method == http.MethodPut {
return s3_constants.S3_ACTION_PUT_OBJECT_LEGAL_HOLD
if query.Has("legal-hold") {
if method == http.MethodGet {
return s3_constants.S3_ACTION_GET_OBJECT_LEGAL_HOLD
} else if method == http.MethodPut {
return s3_constants.S3_ACTION_PUT_OBJECT_LEGAL_HOLD
}
}
}
@ -236,7 +219,7 @@ func resolveObjectLevelAction(method string, baseAction string, r *http.Request)
if baseAction == s3_constants.ACTION_WRITE {
// Check for copy operation
if r.Header.Get("X-Amz-Copy-Source") != "" {
return s3_constants.S3_ACTION_PUT_OBJECT // CopyObject also requires PutObject permission
return s3_constants.S3_ACTION_COPY_OBJECT
}
return s3_constants.S3_ACTION_PUT_OBJECT
}

1
weed/s3api/s3_constants/s3_action_strings.go

@ -6,6 +6,7 @@ const (
// Object operations
S3_ACTION_GET_OBJECT = "s3:GetObject"
S3_ACTION_PUT_OBJECT = "s3:PutObject"
S3_ACTION_COPY_OBJECT = "s3:CopyObject"
S3_ACTION_DELETE_OBJECT = "s3:DeleteObject"
S3_ACTION_DELETE_OBJECT_VERSION = "s3:DeleteObjectVersion"
S3_ACTION_GET_OBJECT_VERSION = "s3:GetObjectVersion"

118
weed/s3api/s3_granular_action_security_test.go

@ -3,6 +3,7 @@ package s3api
import (
"net/http"
"net/url"
"strings"
"testing"
"github.com/gorilla/mux"
@ -311,40 +312,40 @@ func TestPolicyEnforcementScenarios(t *testing.T) {
// Previously, DeleteObject operations were mapped to s3:PutObject, preventing fine-grained policies from working
func TestDeleteObjectPolicyEnforcement(t *testing.T) {
tests := []struct {
name string
method string
bucket string
objectKey string
baseAction Action
name string
method string
bucket string
objectKey string
baseAction Action
expectedS3Action string
policyScenario string
policyScenario string
}{
{
name: "delete_object_maps_to_correct_action",
method: http.MethodDelete,
bucket: "test-bucket",
objectKey: "test-object.txt",
baseAction: s3_constants.ACTION_WRITE,
name: "delete_object_maps_to_correct_action",
method: http.MethodDelete,
bucket: "test-bucket",
objectKey: "test-object.txt",
baseAction: s3_constants.ACTION_WRITE,
expectedS3Action: "s3:DeleteObject",
policyScenario: "Policy that denies s3:DeleteObject but allows s3:PutObject should now work correctly",
policyScenario: "Policy that denies s3:DeleteObject but allows s3:PutObject should now work correctly",
},
{
name: "put_object_maps_to_correct_action",
method: http.MethodPut,
bucket: "test-bucket",
objectKey: "test-object.txt",
baseAction: s3_constants.ACTION_WRITE,
name: "put_object_maps_to_correct_action",
method: http.MethodPut,
bucket: "test-bucket",
objectKey: "test-object.txt",
baseAction: s3_constants.ACTION_WRITE,
expectedS3Action: "s3:PutObject",
policyScenario: "Policy that allows s3:PutObject but denies s3:DeleteObject should allow uploads",
policyScenario: "Policy that allows s3:PutObject but denies s3:DeleteObject should allow uploads",
},
{
name: "batch_delete_maps_to_delete_action",
method: http.MethodPost,
bucket: "test-bucket",
objectKey: "",
baseAction: s3_constants.ACTION_WRITE,
name: "batch_delete_maps_to_delete_action",
method: http.MethodPost,
bucket: "test-bucket",
objectKey: "",
baseAction: s3_constants.ACTION_WRITE,
expectedS3Action: "s3:DeleteObject",
policyScenario: "Batch delete operations should also map to s3:DeleteObject",
policyScenario: "Batch delete operations should also map to s3:DeleteObject",
},
}
@ -496,13 +497,13 @@ func TestFineGrainedPolicyExample(t *testing.T) {
// was always mapped to s3:PutObject, preventing fine-grained policies from working.
func TestCoarseActionResolution(t *testing.T) {
testCases := []struct {
name string
method string
path string
queryParams map[string]string
coarseAction Action
expectedS3Action string
policyScenario string
name string
method string
path string
queryParams map[string]string
coarseAction Action
expectedS3Action string
policyScenario string
}{
{
name: "PUT_with_ACTION_WRITE_resolves_to_PutObject",
@ -541,7 +542,7 @@ func TestCoarseActionResolution(t *testing.T) {
policyScenario: "Policy allowing s3:PutObject but denying s3:CreateMultipartUpload can now work",
},
}
for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
// Build URL with query parameters
@ -552,70 +553,43 @@ func TestCoarseActionResolution(t *testing.T) {
q.Add(k, v)
}
u.RawQuery = q.Encode()
// Create HTTP request
req, err := http.NewRequest(tc.method, u.String(), nil)
assert.NoError(t, err)
// Extract bucket and object from path for mux.Vars simulation
// Path format: /bucket/object or /bucket
pathParts := []string{}
for _, part := range []byte(u.Path) {
if part == '/' {
continue
}
pathParts = append(pathParts, string(part))
}
// Parse path to extract bucket and object
parts := strings.Split(strings.TrimPrefix(u.Path, "/"), "/")
bucket := ""
object := ""
if u.Path != "" {
parts := []string{}
current := ""
for _, ch := range u.Path {
if ch == '/' {
if current != "" {
parts = append(parts, current)
current = ""
}
} else {
current += string(ch)
}
}
if current != "" {
parts = append(parts, current)
}
if len(parts) > 0 {
bucket = parts[0]
if len(parts) > 1 {
object = "/" + parts[1]
}
}
if len(parts) > 0 {
bucket = parts[0]
}
if len(parts) > 1 {
object = "/" + strings.Join(parts[1:], "/")
}
// Simulate mux.Vars for GetBucketAndObject
req = mux.SetURLVars(req, map[string]string{
"bucket": bucket,
"object": object,
})
// Call ResolveS3Action with coarse action constant
resolvedAction := ResolveS3Action(req, string(tc.coarseAction), bucket, object)
// Verify correct S3 action is resolved
assert.Equal(t, tc.expectedS3Action, resolvedAction,
"Coarse action %s with method %s should resolve to %s",
tc.coarseAction, tc.method, tc.expectedS3Action)
t.Logf("SUCCESS: %s", tc.name)
t.Logf(" Input: %s %s + ACTION_WRITE", tc.method, tc.path)
t.Logf(" Output: %s", resolvedAction)
t.Logf(" Policy impact: %s", tc.policyScenario)
})
}
t.Log("\n=== ARCHITECTURAL LIMITATION RESOLVED ===")
t.Log("Handlers can use coarse ACTION_WRITE constant, and the context-aware")
t.Log("resolver will map it to the correct specific S3 action (PutObject,")

162
weed/s3api/s3_iam_middleware.go

@ -246,9 +246,7 @@ func buildS3ResourceArn(bucket string, objectKey string) string {
}
// Remove leading slash from object key if present
if strings.HasPrefix(objectKey, "/") {
objectKey = objectKey[1:]
}
objectKey = strings.TrimPrefix(objectKey, "/")
return "arn:aws:s3:::" + bucket + "/" + objectKey
}
@ -271,165 +269,13 @@ func determineGranularS3Action(r *http.Request, fallbackAction Action, bucket st
return mapLegacyActionToIAM(fallbackAction)
}
// Use the shared action resolver for consistent resolution
// This now handles most of the action resolution logic
// Use the shared action resolver for consistent resolution across all S3 operations
// ResolveS3Action handles all query parameters, HTTP methods, and object/bucket distinctions
if resolvedAction := ResolveS3Action(r, string(fallbackAction), bucket, objectKey); resolvedAction != "" {
return resolvedAction
}
// Legacy IAM-specific object-level handling (for backward compatibility)
if objectKey != "" && objectKey != "/" {
switch r.Method {
case "GET", "HEAD":
// Object read operations - check for specific query parameters
if _, hasAcl := query["acl"]; hasAcl {
return "s3:GetObjectAcl"
}
if _, hasTagging := query["tagging"]; hasTagging {
return "s3:GetObjectTagging"
}
if _, hasRetention := query["retention"]; hasRetention {
return "s3:GetObjectRetention"
}
if _, hasLegalHold := query["legal-hold"]; hasLegalHold {
return "s3:GetObjectLegalHold"
}
if _, hasVersions := query["versions"]; hasVersions {
return "s3:GetObjectVersion"
}
if _, hasUploadId := query["uploadId"]; hasUploadId {
return "s3:ListParts"
}
// Default object read
return "s3:GetObject"
case "PUT", "POST":
// Object write operations - check for specific query parameters
if _, hasAcl := query["acl"]; hasAcl {
return "s3:PutObjectAcl"
}
if _, hasTagging := query["tagging"]; hasTagging {
return "s3:PutObjectTagging"
}
if _, hasRetention := query["retention"]; hasRetention {
return "s3:PutObjectRetention"
}
if _, hasLegalHold := query["legal-hold"]; hasLegalHold {
return "s3:PutObjectLegalHold"
}
// Check for multipart upload operations
if _, hasUploads := query["uploads"]; hasUploads {
return "s3:CreateMultipartUpload"
}
if _, hasUploadId := query["uploadId"]; hasUploadId {
if _, hasPartNumber := query["partNumber"]; hasPartNumber {
return "s3:UploadPart"
}
return "s3:CompleteMultipartUpload" // Complete multipart upload
}
// Default object write
return "s3:PutObject"
case "DELETE":
// Object delete operations
if _, hasTagging := query["tagging"]; hasTagging {
return "s3:DeleteObjectTagging"
}
if _, hasUploadId := query["uploadId"]; hasUploadId {
return "s3:AbortMultipartUpload"
}
// Default object delete
return "s3:DeleteObject"
}
}
// Handle bucket-level operations
if bucket != "" {
switch r.Method {
case "GET", "HEAD":
// Bucket read operations - check for specific query parameters
if _, hasAcl := query["acl"]; hasAcl {
return "s3:GetBucketAcl"
}
if _, hasPolicy := query["policy"]; hasPolicy {
return "s3:GetBucketPolicy"
}
if _, hasTagging := query["tagging"]; hasTagging {
return "s3:GetBucketTagging"
}
if _, hasCors := query["cors"]; hasCors {
return "s3:GetBucketCors"
}
if _, hasVersioning := query["versioning"]; hasVersioning {
return "s3:GetBucketVersioning"
}
if _, hasNotification := query["notification"]; hasNotification {
return "s3:GetBucketNotification"
}
if _, hasObjectLock := query["object-lock"]; hasObjectLock {
return "s3:GetBucketObjectLockConfiguration"
}
if _, hasUploads := query["uploads"]; hasUploads {
return "s3:ListMultipartUploads"
}
if _, hasVersions := query["versions"]; hasVersions {
return "s3:ListBucketVersions"
}
// Default bucket read/list
return "s3:ListBucket"
case "PUT":
// Bucket write operations - check for specific query parameters
if _, hasAcl := query["acl"]; hasAcl {
return "s3:PutBucketAcl"
}
if _, hasPolicy := query["policy"]; hasPolicy {
return "s3:PutBucketPolicy"
}
if _, hasTagging := query["tagging"]; hasTagging {
return "s3:PutBucketTagging"
}
if _, hasCors := query["cors"]; hasCors {
return "s3:PutBucketCors"
}
if _, hasVersioning := query["versioning"]; hasVersioning {
return "s3:PutBucketVersioning"
}
if _, hasNotification := query["notification"]; hasNotification {
return "s3:PutBucketNotification"
}
if _, hasObjectLock := query["object-lock"]; hasObjectLock {
return "s3:PutBucketObjectLockConfiguration"
}
// Default bucket creation
return "s3:CreateBucket"
case "POST":
// Bucket POST operations - check for specific query parameters
if _, hasDelete := query["delete"]; hasDelete {
// Batch delete operation
return "s3:DeleteObject"
}
// Default bucket POST (e.g., policy form upload)
return "s3:PutObject"
case "DELETE":
// Bucket delete operations - check for specific query parameters
if _, hasPolicy := query["policy"]; hasPolicy {
return "s3:DeleteBucketPolicy"
}
if _, hasTagging := query["tagging"]; hasTagging {
return "s3:DeleteBucketTagging"
}
if _, hasCors := query["cors"]; hasCors {
return "s3:DeleteBucketCors"
}
// Default bucket delete
return "s3:DeleteBucket"
}
}
// Fallback to legacy mapping for specific known actions
// Final fallback to legacy mapping if no specific resolution found
return mapLegacyActionToIAM(fallbackAction)
}

64
weed/s3api/s3api_bucket_handlers.go

@ -597,44 +597,44 @@ func (s3a *S3ApiServer) AuthWithPublicRead(handler http.HandlerFunc, action Acti
glog.V(4).Infof("AuthWithPublicRead: bucket=%s, object=%s, authType=%v, isAnonymous=%v", bucket, object, authType, isAnonymous)
// For anonymous requests, check if bucket allows public read via ACLs or bucket policies
if isAnonymous {
// First check ACL-based public access
isPublic := s3a.isBucketPublicRead(bucket)
glog.V(4).Infof("AuthWithPublicRead: bucket=%s, isPublicACL=%v", bucket, isPublic)
if isPublic {
glog.V(3).Infof("AuthWithPublicRead: allowing anonymous access to public-read bucket %s (ACL)", bucket)
handler(w, r)
return
}
// Check bucket policy for anonymous access using the policy engine
principal := "*" // Anonymous principal
// Use context-aware policy evaluation to get the correct S3 action
allowed, evaluated, err := s3a.policyEngine.EvaluatePolicyWithContext(bucket, object, string(action), principal, r)
if err != nil {
// SECURITY: Fail-close on policy evaluation errors
// If we can't evaluate the policy, deny access rather than falling through to IAM
glog.Errorf("AuthWithPublicRead: error evaluating bucket policy for %s/%s: %v - denying access", bucket, object, err)
s3err.WriteErrorResponse(w, r, s3err.ErrAccessDenied)
return
} else if evaluated {
// A bucket policy exists and was evaluated with a matching statement
if allowed {
// Policy explicitly allows anonymous access
glog.V(3).Infof("AuthWithPublicRead: allowing anonymous access to bucket %s (bucket policy)", bucket)
// For anonymous requests, check if bucket allows public read via ACLs or bucket policies
if isAnonymous {
// First check ACL-based public access
isPublic := s3a.isBucketPublicRead(bucket)
glog.V(4).Infof("AuthWithPublicRead: bucket=%s, isPublicACL=%v", bucket, isPublic)
if isPublic {
glog.V(3).Infof("AuthWithPublicRead: allowing anonymous access to public-read bucket %s (ACL)", bucket)
handler(w, r)
return
} else {
// Policy explicitly denies anonymous access
glog.V(3).Infof("AuthWithPublicRead: bucket policy explicitly denies anonymous access to %s/%s", bucket, object)
}
// Check bucket policy for anonymous access using the policy engine
principal := "*" // Anonymous principal
// Use context-aware policy evaluation to get the correct S3 action
allowed, evaluated, err := s3a.policyEngine.EvaluatePolicyWithContext(bucket, object, string(action), principal, r)
if err != nil {
// SECURITY: Fail-close on policy evaluation errors
// If we can't evaluate the policy, deny access rather than falling through to IAM
glog.Errorf("AuthWithPublicRead: error evaluating bucket policy for %s/%s: %v - denying access", bucket, object, err)
s3err.WriteErrorResponse(w, r, s3err.ErrAccessDenied)
return
} else if evaluated {
// A bucket policy exists and was evaluated with a matching statement
if allowed {
// Policy explicitly allows anonymous access
glog.V(3).Infof("AuthWithPublicRead: allowing anonymous access to bucket %s (bucket policy)", bucket)
handler(w, r)
return
} else {
// Policy explicitly denies anonymous access
glog.V(3).Infof("AuthWithPublicRead: bucket policy explicitly denies anonymous access to %s/%s", bucket, object)
s3err.WriteErrorResponse(w, r, s3err.ErrAccessDenied)
return
}
}
// No matching policy statement - fall through to check ACLs and then IAM auth
glog.V(3).Infof("AuthWithPublicRead: no bucket policy match for %s, checking ACLs", bucket)
}
// No matching policy statement - fall through to check ACLs and then IAM auth
glog.V(3).Infof("AuthWithPublicRead: no bucket policy match for %s, checking ACLs", bucket)
}
// For all authenticated requests and anonymous requests to non-public buckets,
// use normal IAM auth to enforce policies

45
weed/s3api/s3api_bucket_policy_engine.go

@ -9,7 +9,6 @@ import (
"github.com/seaweedfs/seaweedfs/weed/iam/policy"
"github.com/seaweedfs/seaweedfs/weed/pb/filer_pb"
"github.com/seaweedfs/seaweedfs/weed/s3api/policy_engine"
"github.com/seaweedfs/seaweedfs/weed/s3api/s3_constants"
)
// BucketPolicyEngine wraps the policy_engine to provide bucket policy evaluation
@ -102,8 +101,8 @@ func (bpe *BucketPolicyEngine) EvaluatePolicy(bucket, object, action, principal
return false, false, fmt.Errorf("action cannot be empty")
}
// Convert action to S3 action format
s3Action := convertActionToS3Format(action, nil)
// Convert action to S3 action format using base mapping (no HTTP context available)
s3Action := mapBaseActionToS3Format(action)
// Build resource ARN
resource := buildResourceARN(bucket, object)
@ -147,7 +146,17 @@ func (bpe *BucketPolicyEngine) EvaluatePolicyWithContext(bucket, object, action,
}
// Convert action to S3 action format using request context
s3Action := convertActionToS3Format(action, r)
// Use ResolveS3Action for context-aware resolution, fall back to base mapping
var s3Action string
if r != nil {
if resolved := ResolveS3Action(r, action, bucket, object); resolved != "" {
s3Action = resolved
} else {
s3Action = mapBaseActionToS3Format(action)
}
} else {
s3Action = mapBaseActionToS3Format(action)
}
// Build resource ARN
resource := buildResourceARN(bucket, object)
@ -180,28 +189,6 @@ func (bpe *BucketPolicyEngine) EvaluatePolicyWithContext(bucket, object, action,
}
}
// convertActionToS3Format converts internal action strings to S3 action format
// with optional HTTP request context for fine-grained action resolution.
//
// This function now uses the shared ResolveS3Action utility for consistent
// action resolution across the bucket policy engine and IAM integration.
//
// Parameters:
// - action: The internal action constant (e.g., ACTION_WRITE, ACTION_READ)
// - r: Optional HTTP request for context-aware resolution. If nil, uses legacy mapping.
func convertActionToS3Format(action string, r *http.Request) string {
// If request context is provided, use the shared action resolver
if r != nil {
bucket, object := s3_constants.GetBucketAndObject(r)
if resolvedAction := ResolveS3Action(r, action, bucket, object); resolvedAction != "" {
return resolvedAction
}
}
// Fallback to base action mapping
return mapBaseActionToS3Format(action)
}
// NOTE: resolveS3ActionFromRequest has been replaced by the shared ResolveS3Action
// function in s3_action_resolver.go. This consolidates action resolution logic
// used by both the bucket policy engine and IAM integration.
// NOTE: The convertActionToS3Format wrapper has been removed for simplicity.
// EvaluatePolicy and EvaluatePolicyWithContext now call ResolveS3Action or
// mapBaseActionToS3Format directly, making the control flow more explicit.
Loading…
Cancel
Save