fix(auth): align signed request and policy condition semantics

Signed-off-by: Feng Ruohang <rh@vonng.com>
This commit is contained in:
Feng Ruohang
2026-09-11 16:24:42 +08:00
parent f760046c44
commit 87d8b5967f
8 changed files with 412 additions and 47 deletions
+12 -12
View File
@@ -211,7 +211,16 @@ func extractSignedHeaders(signedHeaders []string, r *http.Request) (http.Header,
// `host` will not be found in the headers, can be found in r.Host.
// but its always necessary that the list of signed headers containing host in it.
val, ok := reqHeaders[http.CanonicalHeaderKey(header)]
if !ok {
if ok {
// Canonicalization comma-joins repeated header fields, so a signature
// over the single value "/src/a,tail" also verifies a request carrying
// ["/src/a", "tail"]. The copy handlers read only Header.Get, so that
// rewrite would copy a different source than the one signed. Reject a
// repeated x-amz-copy-source; a single value may still contain commas.
if len(val) > 1 && strings.EqualFold(header, strings.ToLower(xhttp.AmzCopySource)) {
return nil, ErrInvalidCopySource
}
} else {
// try to set headers from Query String
val, ok = reqQueries[header]
}
@@ -274,9 +283,8 @@ func signV4TrimAll(input string) string {
// object the signing key can reach.
//
// Only headers actually sent by the client are inspected. Server-synthesized
// x-amz-* headers (e.g. x-amz-tagging derived from a request body, or the
// post-verification x-amz-signature-age scratch header) are set after signature
// verification and therefore never reach this walk.
// x-amz-* headers (e.g. x-amz-tagging derived from a request body) are set
// after signature verification and therefore never reach this walk.
func checkUnsignedHeaders(signedHeadersMap http.Header, r *http.Request) APIErrorCode {
// check headers that arrived on the request
for k := range r.Header {
@@ -294,14 +302,6 @@ func checkUnsignedHeaders(signedHeadersMap http.Header, r *http.Request) APIErro
if strings.EqualFold(k, xhttp.AmzContentSha256) {
continue
}
// X-Amz-Signature-Age is an internal scratch header written by the
// presigned verifier itself, after this check, purely so bucket-policy
// evaluation can expose s3:signatureAge. It is never sent or signed by a
// client, and exempting it keeps signature verification idempotent when
// the same request is verified more than once.
if strings.EqualFold(k, xhttp.AmzSignatureAge) {
continue
}
// The header must be a member of the signed-headers list. Testing
// membership (not value equality) is essential: an unsigned header whose
// first value is empty would otherwise compare equal to the empty string