fix(s3): enforce tag count and object lock permissions on proxy reads

Skip x-amz-tagging-count and X-Amz-Tagging in setObjectHeaders UserDefined iteration and proxyHeadToRepTarget metadata copies so opts.TaggingCount/opts.Tagging remains the sole gate without emitting duplicate headers. Make FilterObjectLockMetadata case-insensitive so canonical-case X-Amz-Object-Lock-* headers from peer HEAD responses are stripped when s3:GetObjectRetention or s3:GetObjectLegalHold is denied while preserved for authorized callers.

Signed-off-by: Feng Ruohang <rh@vonng.com>
This commit is contained in:
Feng Ruohang
2026-11-12 10:18:54 +08:00
parent 265c31b714
commit ea37a7e8b0
5 changed files with 204 additions and 26 deletions
+28 -26
View File
@@ -466,29 +466,34 @@ func ParseObjectLockRetentionHeaders(h http.Header, allowPastRetainDate bool) (r
return rmode, RetentionDate{retDate}, nil
}
func lookupMetaFold(meta map[string]string, key string) (string, bool) {
if v, ok := meta[strings.ToLower(key)]; ok {
return v, true
}
if v, ok := meta[key]; ok {
return v, true
}
for k, v := range meta {
if strings.EqualFold(k, key) {
return v, true
}
}
return "", false
}
// GetObjectRetentionMeta constructs ObjectRetention from metadata
func GetObjectRetentionMeta(meta map[string]string) ObjectRetention {
var mode RetMode
var retainTill RetentionDate
var modeStr, tillStr string
ok := false
modeStr, ok = meta[strings.ToLower(AmzObjectLockMode)]
if !ok {
modeStr, ok = meta[AmzObjectLockMode]
}
modeStr, ok := lookupMetaFold(meta, AmzObjectLockMode)
if ok {
mode = parseRetMode(modeStr)
} else {
return ObjectRetention{}
}
tillStr, ok = meta[strings.ToLower(AmzObjectLockRetainUntilDate)]
if !ok {
tillStr, ok = meta[AmzObjectLockRetainUntilDate]
}
if ok {
if tillStr, ok := lookupMetaFold(meta, AmzObjectLockRetainUntilDate); ok {
if t, e := amztime.ISO8601Parse(tillStr); e == nil {
retainTill = RetentionDate{t.UTC()}
}
@@ -498,11 +503,7 @@ func GetObjectRetentionMeta(meta map[string]string) ObjectRetention {
// GetObjectLegalHoldMeta constructs ObjectLegalHold from metadata
func GetObjectLegalHoldMeta(meta map[string]string) ObjectLegalHold {
holdStr, ok := meta[strings.ToLower(AmzObjectLockLegalHold)]
if !ok {
holdStr, ok = meta[AmzObjectLockLegalHold]
}
if ok {
if holdStr, ok := lookupMetaFold(meta, AmzObjectLockLegalHold); ok {
return ObjectLegalHold{XMLNS: "http://s3.amazonaws.com/doc/2006-03-01/", Status: parseLegalHoldStatus(holdStr)}
}
return ObjectLegalHold{}
@@ -592,16 +593,17 @@ func FilterObjectLockMetadata(metadata map[string]string, filterRetention, filte
dst := metadata
var copied bool
delKey := func(key string) {
key = strings.ToLower(key)
if _, ok := metadata[key]; !ok {
return
for k := range metadata {
if !strings.EqualFold(k, key) {
continue
}
if !copied {
dst = make(map[string]string, len(metadata))
maps.Copy(dst, metadata)
copied = true
}
delete(dst, k)
}
if !copied {
dst = make(map[string]string, len(metadata))
maps.Copy(dst, metadata)
copied = true
}
delete(dst, key)
}
legalHold := GetObjectLegalHoldMeta(metadata)
if !legalHold.Status.Valid() || filterLegalHold {
+22
View File
@@ -608,6 +608,28 @@ func TestFilterObjectLockMetadata(t *testing.T) {
"x-amz-object-lock-retain-until-date": "2020-02-01",
},
},
{
metadata: map[string]string{
"X-Amz-Object-Lock-Legal-Hold": "ON",
"X-Amz-Object-Lock-Mode": "GOVERNANCE",
"X-Amz-Object-Lock-Retain-Until-Date": "2026-12-31T00:00:00Z",
},
expected: map[string]string{},
filterRetention: true,
filterLegalHold: true,
},
{
metadata: map[string]string{
"X-Amz-Object-Lock-Legal-Hold": "ON",
"X-Amz-Object-Lock-Mode": "GOVERNANCE",
"X-Amz-Object-Lock-Retain-Until-Date": "2026-12-31T00:00:00Z",
},
expected: map[string]string{
"X-Amz-Object-Lock-Legal-Hold": "ON",
"X-Amz-Object-Lock-Mode": "GOVERNANCE",
"X-Amz-Object-Lock-Retain-Until-Date": "2026-12-31T00:00:00Z",
},
},
}
for i, tt := range tests {