aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorchrislu <chris.lu@gmail.com>2025-12-08 23:58:13 -0800
committerchrislu <chris.lu@gmail.com>2025-12-09 00:01:31 -0800
commitc6721bb18d8f70ec9e86b6aa043b488d2d2f0239 (patch)
treedf61724296cab7caa59d2b2627956bc02a839b04
parentd5f21fd8ba6ee20c2504455093a9ceeaa178b826 (diff)
downloadseaweedfs-c6721bb18d8f70ec9e86b6aa043b488d2d2f0239.tar.xz
seaweedfs-c6721bb18d8f70ec9e86b6aa043b488d2d2f0239.zip
s3: add s3:ExistingObjectTag condition support in policy engine
Add support for s3:ExistingObjectTag/<tag-key> condition keys in bucket policies, allowing access control based on object tags. Changes: - Add ObjectEntry field to PolicyEvaluationArgs (entry.Extended metadata) - Update EvaluateConditions to handle s3:ExistingObjectTag/<key> format - Extract tag value from entry metadata using X-Amz-Tagging-<key> prefix This enables policies like: { "Condition": { "StringEquals": { "s3:ExistingObjectTag/status": ["public"] } } } Fixes: https://github.com/seaweedfs/seaweedfs/issues/7447
-rw-r--r--weed/s3api/policy_engine/conditions.go56
-rw-r--r--weed/s3api/policy_engine/engine.go2
-rw-r--r--weed/s3api/policy_engine/types.go5
3 files changed, 54 insertions, 9 deletions
diff --git a/weed/s3api/policy_engine/conditions.go b/weed/s3api/policy_engine/conditions.go
index fc8005fd0..feb582e89 100644
--- a/weed/s3api/policy_engine/conditions.go
+++ b/weed/s3api/policy_engine/conditions.go
@@ -705,8 +705,15 @@ func GetConditionEvaluator(operator string) (ConditionEvaluator, error) {
}
}
+// ExistingObjectTagPrefix is the prefix for object tag condition keys
+const ExistingObjectTagPrefix = "s3:ExistingObjectTag/"
+
+// ObjectTagMetadataPrefix is the prefix used to store tags in entry.Extended
+const ObjectTagMetadataPrefix = "X-Amz-Tagging-"
+
// EvaluateConditions evaluates all conditions in a policy statement
-func EvaluateConditions(conditions PolicyConditions, contextValues map[string][]string) bool {
+// objectEntry is the object's metadata from entry.Extended (can be nil)
+func EvaluateConditions(conditions PolicyConditions, contextValues map[string][]string, objectEntry map[string][]byte) bool {
if len(conditions) == 0 {
return true // No conditions means always true
}
@@ -719,9 +726,27 @@ func EvaluateConditions(conditions PolicyConditions, contextValues map[string][]
}
for key, value := range conditionMap {
- contextVals, exists := contextValues[key]
- if !exists {
- contextVals = []string{}
+ var contextVals []string
+
+ // Handle s3:ExistingObjectTag/<tag-key> condition keys
+ // These refer to tags that already exist on the object
+ if strings.HasPrefix(key, ExistingObjectTagPrefix) {
+ // Extract tag value from entry.Extended using the tag prefix
+ tagKey := key[len(ExistingObjectTagPrefix):]
+ metadataKey := ObjectTagMetadataPrefix + tagKey
+ if objectEntry != nil {
+ if tagValue, exists := objectEntry[metadataKey]; exists {
+ contextVals = []string{string(tagValue)}
+ }
+ }
+ // If tag doesn't exist, contextVals remains empty
+ } else {
+ // Regular condition key lookup
+ var exists bool
+ contextVals, exists = contextValues[key]
+ if !exists {
+ contextVals = []string{}
+ }
}
if !conditionEvaluator.Evaluate(value.Strings(), contextVals) {
@@ -734,7 +759,8 @@ func EvaluateConditions(conditions PolicyConditions, contextValues map[string][]
}
// EvaluateConditionsLegacy evaluates conditions using the old interface{} format for backward compatibility
-func EvaluateConditionsLegacy(conditions map[string]interface{}, contextValues map[string][]string) bool {
+// objectEntry is the object's metadata from entry.Extended (can be nil)
+func EvaluateConditionsLegacy(conditions map[string]interface{}, contextValues map[string][]string, objectEntry map[string][]byte) bool {
if len(conditions) == 0 {
return true // No conditions means always true
}
@@ -753,9 +779,23 @@ func EvaluateConditionsLegacy(conditions map[string]interface{}, contextValues m
}
for key, value := range conditionMapTyped {
- contextVals, exists := contextValues[key]
- if !exists {
- contextVals = []string{}
+ var contextVals []string
+
+ // Handle s3:ExistingObjectTag/<tag-key> condition keys
+ if strings.HasPrefix(key, ExistingObjectTagPrefix) {
+ tagKey := key[len(ExistingObjectTagPrefix):]
+ metadataKey := ObjectTagMetadataPrefix + tagKey
+ if objectEntry != nil {
+ if tagValue, exists := objectEntry[metadataKey]; exists {
+ contextVals = []string{string(tagValue)}
+ }
+ }
+ } else {
+ var exists bool
+ contextVals, exists = contextValues[key]
+ if !exists {
+ contextVals = []string{}
+ }
}
if !conditionEvaluator.Evaluate(value, contextVals) {
diff --git a/weed/s3api/policy_engine/engine.go b/weed/s3api/policy_engine/engine.go
index 01af3c240..57a13881c 100644
--- a/weed/s3api/policy_engine/engine.go
+++ b/weed/s3api/policy_engine/engine.go
@@ -154,7 +154,7 @@ func (engine *PolicyEngine) evaluateStatement(stmt *CompiledStatement, args *Pol
// Check conditions
if len(stmt.Statement.Condition) > 0 {
- if !EvaluateConditions(stmt.Statement.Condition, args.Conditions) {
+ if !EvaluateConditions(stmt.Statement.Condition, args.Conditions, args.ObjectEntry) {
return false
}
}
diff --git a/weed/s3api/policy_engine/types.go b/weed/s3api/policy_engine/types.go
index d68b1f297..a8f822fb8 100644
--- a/weed/s3api/policy_engine/types.go
+++ b/weed/s3api/policy_engine/types.go
@@ -106,6 +106,11 @@ type PolicyEvaluationArgs struct {
Resource string
Principal string
Conditions map[string][]string
+ // ObjectEntry is the object's metadata from entry.Extended.
+ // Used for evaluating conditions like s3:ExistingObjectTag/<tag-key>.
+ // Tags are stored as "X-Amz-Tagging-<key>" -> value.
+ // Can be nil for bucket-level operations or when object doesn't exist.
+ ObjectEntry map[string][]byte
}
// PolicyCache for caching compiled policies