Files
seaweedfs/weed/s3api/policy_engine/request_grant_conditions.go
T
zhao-ycandChris Lu 483dd4b12e s3api: persist ACLs on PutObject uploads (#11592)
* s3api: persist ACLs on PutObject uploads

Signed-off-by: zhaoyuchen <43179751+zhao-yc@users.noreply.github.com>

* s3api: fix PutObject ACL edge cases found in review

- Only enforce BucketOwnerEnforced when explicitly configured; buckets
  without a stored ownership control keep accepting upload ACLs
- Ignore ACL query parameters on SigV2 requests, which do not sign them
- Mirror signed-query ACL values into headers after authentication so
  grant parsing and resolveFileMode agree on presigned uploads
- Validate only caller-supplied grantees against the account registry;
  default grants now work for accounts outside the local registry
- Reject unknown grantee keys and accept comma-separated grantee lists
  without spaces in ParseCustomAclHeader
- Guard against identities without an account

* s3api: harden upload ACL parsing and authorization

Signed-off-by: zhaoyuchen <43179751+zhao-yc@users.noreply.github.com>

* s3api: evaluate upload ACL grantees individually in policies

A comma-joined grant header or a signed query parameter reached policy
conditions as one value, so a deny on a later grantee did not fire. Split
grant headers into per-grantee values for policy evaluation and share the
grantee pair parser with ParseCustomAclHeader.

* s3api: keep raw grant header values visible to policy conditions

Exact-match conditions written against the signed header value stopped
matching once grantees were split for evaluation. Preserve the original
wire values alongside the per-grantee values so deny policies fire on
either granularity.

* s3api: evaluate upload ACL grants as one canonical list in policies

Conditions on s3:x-amz-grant-* now see a single comma-separated canonical
grant list identical for a single line, repeated header lines, or a signed
query parameter. This keeps StringEquals allows and exact-list or
allowlist (StringNotEquals) denies accurate regardless of wire encoding.

* s3api: preserve upload ACL denies and align policy checks

Signed-off-by: zhaoyuchen <43179751+zhao-yc@users.noreply.github.com>

* s3api: retain upload owner grants and literal policy values

Signed-off-by: zhaoyuchen <43179751+zhao-yc@users.noreply.github.com>

---------

Signed-off-by: zhaoyuchen <43179751+zhao-yc@users.noreply.github.com>
Co-authored-by: Chris Lu <chris.lu@gmail.com>
2026-10-05 09:13:19 +08:00

47 lines
1.6 KiB
Go

package policy_engine
import (
"context"
"net/http"
)
// originalGrantConditionsKey prevents clients from forging original grant values through request headers.
type originalGrantConditionsKey struct{}
// isGrantConditionKey restricts original-value checks to the five upload grant condition keys.
func isGrantConditionKey(key string) bool {
switch key {
case "s3:x-amz-grant-read", "s3:x-amz-grant-write", "s3:x-amz-grant-read-acp", "s3:x-amz-grant-write-acp", "s3:x-amz-grant-full-control":
return true
}
return false
}
// cloneGrantConditions isolates inputs and results so later mutations cannot change the original complete grant representation.
func cloneGrantConditions(values map[string][]string) map[string][]string {
if len(values) == 0 {
return nil
}
cloned := make(map[string][]string, len(values))
for key, grants := range values {
if isGrantConditionKey(key) {
cloned[key] = append([]string(nil), grants...)
}
}
return cloned
}
// WithOriginalGrantConditions saves the complete list before upload normalization to supplement explicit deny checks only.
func WithOriginalGrantConditions(r *http.Request, values map[string][]string) *http.Request {
return r.WithContext(context.WithValue(r.Context(), originalGrantConditionsKey{}, cloneGrantConditions(values)))
}
// OriginalGrantConditionsFromRequest reads the internal snapshot; other operations have no such context.
func OriginalGrantConditionsFromRequest(r *http.Request) map[string][]string {
if r == nil {
return nil
}
values, _ := r.Context().Value(originalGrantConditionsKey{}).(map[string][]string)
return cloneGrantConditions(values)
}