mirror of
https://github.com/seaweedfs/seaweedfs.git
synced 2026-10-25 14:30:43 +01:00
* s3api: support s3:RequestObjectTagKeys in bucket policies Condition operators accept the ForAllValues: and ForAnyValue: qualifiers, and s3:RequestObjectTagKeys holds the keys of the tags taken from X-Amz-Tagging wherever s3:RequestObjectTag/<key> is set. CopyObject no longer stores an empty-key tag for an empty segment in X-Amz-Tagging, so it keeps the key set a policy has checked. Fixes #7679 * s3: parse tagging header like the tag handlers, not url.ParseQuery url.ParseQuery stops at 10,000 parameters, so an X-Amz-Tagging header padded with empty & segments made requestObjectTags return nil: the copy handler still stored the denied tag while every s3:RequestObjectTag* condition saw nothing. Scan non-empty segments the way parseTagsHeader does so the policy sees the same tags the object gets. * test: gofmt s3tables test files after #11997 The format check fails on these files on master as merged, so every PR's S3 Tables Format Check goes red until they are re-formatted. Generated with [Devin](https://devin.ai) Co-Authored-By: Devin <158243242+devin-ai-integration[bot]@users.noreply.github.com> --------- Co-authored-by: user.email <chrislusf@users.noreply.github.com> Co-authored-by: Devin <158243242+devin-ai-integration[bot]@users.noreply.github.com>
103 lines
4.4 KiB
Go
103 lines
4.4 KiB
Go
package policy_engine
|
|
|
|
import (
|
|
"testing"
|
|
)
|
|
|
|
func TestConditionSetOperators(t *testing.T) {
|
|
allowed := []string{"department", "project"}
|
|
tests := []struct {
|
|
name string
|
|
operator string
|
|
conditionValue interface{}
|
|
contextValues []string
|
|
expected bool
|
|
}{
|
|
{"ForAllValues all match", "ForAllValues:StringEquals", allowed, []string{"department", "project"}, true},
|
|
{"ForAllValues subset matches", "ForAllValues:StringEquals", allowed, []string{"project"}, true},
|
|
{"ForAllValues one outsider fails", "ForAllValues:StringEquals", allowed, []string{"department", "owner"}, false},
|
|
{"ForAllValues empty set matches", "ForAllValues:StringEquals", allowed, []string{}, true},
|
|
{"ForAllValues nil set matches", "ForAllValues:StringEquals", allowed, nil, true},
|
|
{"ForAllValues is case sensitive", "ForAllValues:StringEquals", allowed, []string{"Department"}, false},
|
|
{"ForAllValues StringLike all match", "ForAllValues:StringLike", "proj-*", []string{"proj-a", "proj-b"}, true},
|
|
{"ForAllValues StringLike one outsider fails", "ForAllValues:StringLike", "proj-*", []string{"proj-a", "team"}, false},
|
|
{"ForAllValues StringNotEquals none listed", "ForAllValues:StringNotEquals", allowed, []string{"owner", "team"}, true},
|
|
{"ForAllValues StringNotEquals one listed fails", "ForAllValues:StringNotEquals", allowed, []string{"owner", "project"}, false},
|
|
|
|
{"ForAnyValue one match", "ForAnyValue:StringEquals", allowed, []string{"owner", "project"}, true},
|
|
{"ForAnyValue no match", "ForAnyValue:StringEquals", allowed, []string{"owner", "team"}, false},
|
|
{"ForAnyValue empty set fails", "ForAnyValue:StringEquals", allowed, []string{}, false},
|
|
{"ForAnyValue nil set fails", "ForAnyValue:StringEquals", allowed, nil, false},
|
|
{"ForAnyValue StringLike one match", "ForAnyValue:StringLike", []string{"secret*"}, []string{"owner", "secret-level"}, true},
|
|
{"ForAnyValue StringLike no match", "ForAnyValue:StringLike", []string{"secret*"}, []string{"owner"}, false},
|
|
{"ForAnyValue StringNotEquals one unlisted", "ForAnyValue:StringNotEquals", allowed, []string{"department", "owner"}, true},
|
|
{"ForAnyValue StringNotEquals all listed fails", "ForAnyValue:StringNotEquals", allowed, []string{"department", "project"}, false},
|
|
{"ForAnyValue StringEqualsIgnoreCase", "ForAnyValue:StringEqualsIgnoreCase", allowed, []string{"PROJECT"}, true},
|
|
}
|
|
|
|
for _, tt := range tests {
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
evaluator, err := GetConditionEvaluator(tt.operator)
|
|
if err != nil {
|
|
t.Fatalf("Failed to get condition evaluator: %v", err)
|
|
}
|
|
if got := evaluator.Evaluate(tt.conditionValue, tt.contextValues); got != tt.expected {
|
|
t.Errorf("Expected %v, got %v", tt.expected, got)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestGetConditionEvaluatorRejectsUnknownSetOperators(t *testing.T) {
|
|
for _, op := range []string{
|
|
"ForAllValues:StringBogus",
|
|
"ForAnyValue:",
|
|
"ForEachValue:StringEquals",
|
|
"ForAllValues:ForAnyValue:StringEquals",
|
|
} {
|
|
if _, err := GetConditionEvaluator(op); err == nil {
|
|
t.Errorf("GetConditionEvaluator accepted %q; expected error", op)
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestValidateBucketPolicyAcceptsSetOperators(t *testing.T) {
|
|
policy := `{
|
|
"Version": "2012-10-17",
|
|
"Statement": [{
|
|
"Effect": "Deny",
|
|
"Principal": "*",
|
|
"Action": "s3:PutObject",
|
|
"Resource": "arn:aws:s3:::test-bucket/*",
|
|
"Condition": {
|
|
"ForAllValues:StringNotEquals": {"s3:RequestObjectTagKeys": ["department", "project"]},
|
|
"ForAnyValue:StringLike": {"s3:RequestObjectTagKeys": ["secret*"]}
|
|
}
|
|
}]
|
|
}`
|
|
engine := NewPolicyEngine()
|
|
if err := engine.SetBucketPolicy("test-bucket", policy); err != nil {
|
|
t.Fatalf("SetBucketPolicy rejected set operators: %v", err)
|
|
}
|
|
}
|
|
|
|
func TestOriginalGrantsSupplementOnlyForAnyValue(t *testing.T) {
|
|
const key = "s3:x-amz-grant-read"
|
|
contextValues := map[string][]string{key: {"id=canonical"}}
|
|
originalGrants := map[string][]string{key: {"uri=original"}}
|
|
|
|
anyValue := PolicyConditions{
|
|
"ForAnyValue:StringEquals": {key: NewStringOrStringSlice("uri=original")},
|
|
}
|
|
if !evaluateConditions(anyValue, contextValues, nil, nil, originalGrants) {
|
|
t.Error("ForAnyValue should see the original grant values")
|
|
}
|
|
|
|
allValues := PolicyConditions{
|
|
"ForAllValues:StringEquals": {key: NewStringOrStringSlice("id=canonical")},
|
|
}
|
|
if !evaluateConditions(allValues, contextValues, nil, nil, originalGrants) {
|
|
t.Error("ForAllValues should be evaluated on the canonical values only")
|
|
}
|
|
}
|