mirror of
https://github.com/seaweedfs/seaweedfs.git
synced 2026-10-24 23:10:41 +02:00
s3api: support s3:RequestObjectTagKeys in bucket policies (#11993)
* 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>
This commit is contained in:
7 files changed
+353
-15
No files matched your search
@@ -2,9 +2,11 @@ package policy_engine
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"maps"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"regexp"
|
||||
"slices"
|
||||
"strings"
|
||||
"sync"
|
||||
"sync/atomic"
|
||||
@@ -441,8 +443,11 @@ func (engine *PolicyEngine) ExtractConditionValuesFromRequest(r *http.Request) m
|
||||
// Note: s3:ExistingObjectTag/<key> conditions are evaluated using objectEntry
|
||||
// passed to EvaluatePolicy, not extracted from the request.
|
||||
|
||||
for key, value := range requestObjectTags(r) {
|
||||
values[RequestObjectTagPrefix+key] = []string{value}
|
||||
if tags := requestObjectTags(r); len(tags) > 0 {
|
||||
for key, value := range tags {
|
||||
values[RequestObjectTagPrefix+key] = []string{value}
|
||||
}
|
||||
values[RequestObjectTagKeys] = slices.Sorted(maps.Keys(tags))
|
||||
}
|
||||
|
||||
// S3 bucket-level conditions
|
||||
@@ -514,16 +519,24 @@ func requestObjectTags(r *http.Request) map[string]string {
|
||||
if !storesRequestObjectTags(r) {
|
||||
return nil
|
||||
}
|
||||
// A malformed header is rejected by the handler with InvalidTag.
|
||||
parsed, err := url.ParseQuery(tagging)
|
||||
if err != nil {
|
||||
return nil
|
||||
}
|
||||
// putToFiler rejects a repeated key, but CopyObject stores its last
|
||||
// value, so that is the one the policy has to see.
|
||||
tags := make(map[string]string, len(parsed))
|
||||
for key, vals := range parsed {
|
||||
tags[key] = vals[len(vals)-1]
|
||||
// Parse the same non-empty segments the tag handlers store: url.ParseQuery
|
||||
// caps at 10,000 parameters, so a header padded with "&" segments would
|
||||
// hide stored tag keys from every s3:RequestObjectTag* condition. A
|
||||
// malformed pair is rejected by the handler with InvalidTag.
|
||||
tags := make(map[string]string)
|
||||
for _, segment := range strings.Split(tagging, "&") {
|
||||
if segment == "" {
|
||||
continue
|
||||
}
|
||||
rawKey, rawValue, _ := strings.Cut(segment, "=")
|
||||
key, errKey := url.QueryUnescape(rawKey)
|
||||
value, errValue := url.QueryUnescape(rawValue)
|
||||
if errKey != nil || errValue != nil {
|
||||
return nil
|
||||
}
|
||||
// putToFiler rejects a repeated key, but CopyObject stores its last
|
||||
// value, so that is the one the policy has to see.
|
||||
tags[key] = value
|
||||
}
|
||||
return tags
|
||||
}
|
||||
|
||||
Reference in new issue
Block a user