mirror of
https://github.com/pgsty/minio.git
synced 2026-08-09 15:53:28 +03:00
fix(iam): bind policy conditions to effective request values
Policy evaluation mixed server-derived identity and transport values with raw headers and query parameters. A client could therefore shadow internal condition keys, synthesize LDAP or JWT resource variables, substitute request tags for stored tags, or make a condition observe a value different from the one the handler actually used. Partition condition sources, reserve internal names, adopt exact-name lookup from silo-pkg, and bind authorization to the effective request state. Preserve compatible query forms for storage class and upload tags with explicit header precedence, while restricting signature age and existing-object tags to authenticated or server-resolved values. Tests sweep every supported key across header and query routes and exercise LDAP/OIDC variables, object-lock spelling, STS tags, metadata extraction, and end-to-end policy decisions. Co-authored-by: ChatGPT <noreply@openai.com> Co-authored-by: Claude <noreply@anthropic.com>
This commit is contained in:
@@ -27,6 +27,7 @@ import (
|
||||
"net/url"
|
||||
"os"
|
||||
"reflect"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"github.com/minio/minio/internal/config"
|
||||
@@ -194,6 +195,63 @@ func TestExtractMetadataHeaders(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestExtractMetadataFromRequestUsesHeaderPrecedence(t *testing.T) {
|
||||
query := make(url.Values)
|
||||
query.Set(strings.ToLower(xhttp.AmzStorageClass), "QUERY-CLASS")
|
||||
query.Set(strings.ToLower(xhttp.AmzObjectTagging), "source=query")
|
||||
req, err := http.NewRequest(http.MethodGet, "http://localhost/test?"+query.Encode(), nil)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
req.Header.Set(xhttp.AmzStorageClass, "HEADER-CLASS")
|
||||
req.Header.Set(xhttp.AmzObjectTagging, "source=header")
|
||||
if err = req.ParseForm(); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
metadata, err := extractMetadataFromReq(t.Context(), req)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if got := metadata[xhttp.AmzStorageClass]; got != "HEADER-CLASS" {
|
||||
t.Fatalf("storage class: expected header, got %q", got)
|
||||
}
|
||||
if got := metadata[xhttp.AmzObjectTagging]; got != "source=header" {
|
||||
t.Fatalf("tagging: expected header, got %q", got)
|
||||
}
|
||||
|
||||
// Presence, rather than a non-empty value, establishes precedence. This
|
||||
// prevents a query value from taking over when a signed header is empty.
|
||||
req.Header[xhttp.AmzObjectTagging] = []string{""}
|
||||
if got, ok := getRequestHeaderOrQueryValue(req, xhttp.AmzObjectTagging); !ok || got != "" {
|
||||
t.Fatalf("empty header did not override query: value=%q present=%v", got, ok)
|
||||
}
|
||||
}
|
||||
|
||||
func TestExtractMetadataFromRequestKeepsQueryCompatibility(t *testing.T) {
|
||||
query := make(url.Values)
|
||||
query.Set(strings.ToLower(xhttp.AmzStorageClass), "REDUCED_REDUNDANCY")
|
||||
query.Set(strings.ToLower(xhttp.AmzObjectTagging), "security=public")
|
||||
req, err := http.NewRequest(http.MethodGet, "http://localhost/test?"+query.Encode(), nil)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if err = req.ParseForm(); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
metadata, err := extractMetadataFromReq(t.Context(), req)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if got := metadata[xhttp.AmzStorageClass]; got != "REDUCED_REDUNDANCY" {
|
||||
t.Fatalf("storage class query value lost: %q", got)
|
||||
}
|
||||
if got := metadata[xhttp.AmzObjectTagging]; got != "security=public" {
|
||||
t.Fatalf("tagging query value lost: %q", got)
|
||||
}
|
||||
}
|
||||
|
||||
func TestExtractReplicationMetadataHeaders(t *testing.T) {
|
||||
header := http.Header{
|
||||
"X-Minio-Replication-Server-Side-Encryption-Sealed-Key": []string{"sealed-key"},
|
||||
|
||||
Reference in New Issue
Block a user