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:
+42
-49
@@ -387,11 +387,7 @@ func (api objectAPIHandlers) getObjectHandler(ctx context.Context, objectAPI Obj
|
||||
return true
|
||||
}
|
||||
|
||||
if oi.UserTags != "" {
|
||||
r.Header.Set(xhttp.AmzObjectTagging, oi.UserTags)
|
||||
}
|
||||
|
||||
if s3Error := authorizeRequest(ctx, r, policy.GetObjectAction); s3Error != ErrNone {
|
||||
if s3Error := authorizeRequestWithExistingTags(ctx, r, policy.GetObjectAction, oi.UserTags); s3Error != ErrNone {
|
||||
writeErrorResponse(ctx, w, errorCodes.ToAPIErr(s3Error), r.URL)
|
||||
return true
|
||||
}
|
||||
@@ -429,15 +425,17 @@ func (api objectAPIHandlers) getObjectHandler(ctx context.Context, objectAPI Obj
|
||||
}
|
||||
}
|
||||
if reader == nil || !proxy.Proxy {
|
||||
// The conditional callback has already written 304/412. Do not
|
||||
// authorize again without the stored tags or write a second response.
|
||||
if isErrPreconditionFailed(err) {
|
||||
return
|
||||
}
|
||||
// validate if the request indeed was authorized, if it wasn't we need to return "ErrAccessDenied"
|
||||
// instead of any namespace related error.
|
||||
if s3Error := authorizeRequest(ctx, r, policy.GetObjectAction); s3Error != ErrNone {
|
||||
writeErrorResponse(ctx, w, errorCodes.ToAPIErr(s3Error), r.URL)
|
||||
return
|
||||
}
|
||||
if isErrPreconditionFailed(err) {
|
||||
return
|
||||
}
|
||||
if proxy.Err != nil {
|
||||
writeErrorResponse(ctx, w, toAPIError(ctx, proxy.Err), r.URL)
|
||||
return
|
||||
@@ -839,12 +837,7 @@ func (api objectAPIHandlers) headObjectHandler(ctx context.Context, objectAPI Ob
|
||||
}
|
||||
}
|
||||
|
||||
if objInfo.UserTags != "" {
|
||||
// Set this such that authorization policies can be applied on the object tags.
|
||||
r.Header.Set(xhttp.AmzObjectTagging, objInfo.UserTags)
|
||||
}
|
||||
|
||||
if s3Error := authorizeRequest(ctx, r, policy.GetObjectAction); s3Error != ErrNone {
|
||||
if s3Error := authorizeRequestWithExistingTags(ctx, r, policy.GetObjectAction, objInfo.UserTags); s3Error != ErrNone {
|
||||
writeErrorResponseHeadersOnly(w, errorCodes.ToAPIErr(s3Error))
|
||||
return
|
||||
}
|
||||
@@ -1055,10 +1048,7 @@ func getCpObjMetadataFromHeader(ctx context.Context, r *http.Request, userMeta m
|
||||
// Storage class is special, it can be replaced regardless of the
|
||||
// metadata directive, if set should be preserved and replaced
|
||||
// to the destination metadata.
|
||||
sc := r.Header.Get(xhttp.AmzStorageClass)
|
||||
if sc == "" {
|
||||
sc = r.Form.Get(xhttp.AmzStorageClass)
|
||||
}
|
||||
sc, _ := getRequestHeaderOrQueryValue(r, xhttp.AmzStorageClass)
|
||||
|
||||
// if x-amz-metadata-directive says REPLACE then
|
||||
// we extract metadata from the input headers.
|
||||
@@ -1256,9 +1246,10 @@ func (api objectAPIHandlers) CopyObjectHandler(w http.ResponseWriter, r *http.Re
|
||||
return
|
||||
}
|
||||
|
||||
// Validate storage class metadata if present
|
||||
dstSc := r.Header.Get(xhttp.AmzStorageClass)
|
||||
if dstSc != "" && !storageclass.IsValid(dstSc) {
|
||||
// Validate the storage class header if present. Query values retain the
|
||||
// existing compatibility path, including its historical validation behavior.
|
||||
dstSc, _ := getRequestHeaderOrQueryValue(r, xhttp.AmzStorageClass)
|
||||
if headerStorageClass := r.Header.Get(xhttp.AmzStorageClass); headerStorageClass != "" && !storageclass.IsValid(headerStorageClass) {
|
||||
writeErrorResponse(ctx, w, errorCodes.ToAPIErr(ErrInvalidStorageClass), r.URL)
|
||||
return
|
||||
}
|
||||
@@ -1857,7 +1848,8 @@ func (api objectAPIHandlers) PutObjectHandler(w http.ResponseWriter, r *http.Req
|
||||
return
|
||||
}
|
||||
|
||||
// Validate storage class metadata if present
|
||||
// Validate the storage class header if present. Query values retain the
|
||||
// existing compatibility path, including its historical validation behavior.
|
||||
if sc := r.Header.Get(xhttp.AmzStorageClass); sc != "" {
|
||||
if !storageclass.IsValid(sc) {
|
||||
writeErrorResponse(ctx, w, errorCodes.ToAPIErr(ErrInvalidStorageClass), r.URL)
|
||||
@@ -1906,13 +1898,11 @@ func (api objectAPIHandlers) PutObjectHandler(w http.ResponseWriter, r *http.Req
|
||||
return
|
||||
}
|
||||
|
||||
if objTags := r.Header.Get(xhttp.AmzObjectTagging); objTags != "" {
|
||||
if objTags := metadata[xhttp.AmzObjectTagging]; objTags != "" {
|
||||
if _, err := tags.ParseObjectTags(objTags); err != nil {
|
||||
writeErrorResponse(ctx, w, toAPIError(ctx, err), r.URL)
|
||||
return
|
||||
}
|
||||
|
||||
metadata[xhttp.AmzObjectTagging] = objTags
|
||||
}
|
||||
|
||||
var (
|
||||
@@ -1924,7 +1914,12 @@ func (api objectAPIHandlers) PutObjectHandler(w http.ResponseWriter, r *http.Req
|
||||
)
|
||||
|
||||
// Check if put is allowed
|
||||
if s3Err = isPutActionAllowed(ctx, rAuthType, bucket, object, r, policy.PutObjectAction); s3Err != ErrNone {
|
||||
requestTags, hasRequestTags := metadata[xhttp.AmzObjectTagging]
|
||||
var requestTagsPtr *string
|
||||
if hasRequestTags {
|
||||
requestTagsPtr = &requestTags
|
||||
}
|
||||
if s3Err = isPutActionAllowedWithRequestTags(ctx, rAuthType, bucket, object, r, policy.PutObjectAction, requestTagsPtr); s3Err != ErrNone {
|
||||
writeErrorResponse(ctx, w, errorCodes.ToAPIErr(s3Err), r.URL)
|
||||
return
|
||||
}
|
||||
@@ -2271,13 +2266,12 @@ func (api objectAPIHandlers) PutObjectExtractHandler(w http.ResponseWriter, r *h
|
||||
return
|
||||
}
|
||||
|
||||
// Validate storage class metadata if present
|
||||
sc := r.Header.Get(xhttp.AmzStorageClass)
|
||||
if sc != "" {
|
||||
if !storageclass.IsValid(sc) {
|
||||
writeErrorResponse(ctx, w, errorCodes.ToAPIErr(ErrInvalidStorageClass), r.URL)
|
||||
return
|
||||
}
|
||||
// Validate the storage class header if present. PutObjectExtract now also
|
||||
// consumes the compatible query value so policy and operation stay aligned.
|
||||
sc, _ := getRequestHeaderOrQueryValue(r, xhttp.AmzStorageClass)
|
||||
if headerStorageClass := r.Header.Get(xhttp.AmzStorageClass); headerStorageClass != "" && !storageclass.IsValid(headerStorageClass) {
|
||||
writeErrorResponse(ctx, w, errorCodes.ToAPIErr(ErrInvalidStorageClass), r.URL)
|
||||
return
|
||||
}
|
||||
|
||||
clientETag, err := etag.FromContentMD5(r.Header)
|
||||
@@ -3205,12 +3199,7 @@ func (api objectAPIHandlers) GetObjectTaggingHandler(w http.ResponseWriter, r *h
|
||||
}
|
||||
}
|
||||
|
||||
// Set this such that authorization policies can be applied on the object tags.
|
||||
if tags := ot.String(); tags != "" {
|
||||
r.Header.Set(xhttp.AmzObjectTagging, tags)
|
||||
}
|
||||
|
||||
if s3Error := authorizeRequest(ctx, r, policy.GetObjectTaggingAction); s3Error != ErrNone {
|
||||
if s3Error := authorizeRequestWithExistingTags(ctx, r, policy.GetObjectTaggingAction, ot.String()); s3Error != ErrNone {
|
||||
writeErrorResponse(ctx, w, errorCodes.ToAPIErr(s3Error), r.URL)
|
||||
return
|
||||
}
|
||||
@@ -3264,12 +3253,14 @@ func (api objectAPIHandlers) PutObjectTaggingHandler(w http.ResponseWriter, r *h
|
||||
writeErrorResponse(ctx, w, toAPIError(ctx, err), r.URL)
|
||||
return
|
||||
}
|
||||
tagsStr := tags.String()
|
||||
|
||||
// Set this such that authorization policies can be applied on the object tags.
|
||||
r.Header.Set(xhttp.AmzObjectTagging, tags.String())
|
||||
r.Header.Set(xhttp.AmzObjectTagging, tagsStr)
|
||||
|
||||
// Allow putObjectTagging if policy action is set
|
||||
if s3Error := checkRequestAuthType(ctx, r, policy.PutObjectTaggingAction, bucket, object); s3Error != ErrNone {
|
||||
logger.GetReqInfo(ctx).BucketName = bucket
|
||||
logger.GetReqInfo(ctx).ObjectName = object
|
||||
if s3Error := authenticateRequest(ctx, r, policy.PutObjectTaggingAction); s3Error != ErrNone {
|
||||
writeErrorResponse(ctx, w, errorCodes.ToAPIErr(s3Error), r.URL)
|
||||
return
|
||||
}
|
||||
@@ -3281,6 +3272,14 @@ func (api objectAPIHandlers) PutObjectTaggingHandler(w http.ResponseWriter, r *h
|
||||
}
|
||||
|
||||
objInfo, err := objAPI.GetObjectInfo(ctx, bucket, object, opts)
|
||||
existingTags := ""
|
||||
if err == nil {
|
||||
existingTags = objInfo.UserTags
|
||||
}
|
||||
if s3Error := authorizeRequestWithExistingTags(ctx, r, policy.PutObjectTaggingAction, existingTags); s3Error != ErrNone {
|
||||
writeErrorResponse(ctx, w, errorCodes.ToAPIErr(s3Error), r.URL)
|
||||
return
|
||||
}
|
||||
if err != nil {
|
||||
// if object is not found locally, but exists on peer site - proxy
|
||||
// the tagging request to peer site. The response to client will
|
||||
@@ -3314,7 +3313,6 @@ func (api objectAPIHandlers) PutObjectTaggingHandler(w http.ResponseWriter, r *h
|
||||
writeErrorResponse(ctx, w, toAPIError(ctx, err), r.URL)
|
||||
return
|
||||
}
|
||||
tagsStr := tags.String()
|
||||
|
||||
dsc := mustReplicate(ctx, bucket, object, getMustReplicateOptions(objInfo.UserDefined, tagsStr, objInfo.ReplicationStatus, replication.MetadataReplicationType, opts))
|
||||
if dsc.ReplicateAny() {
|
||||
@@ -3413,13 +3411,8 @@ func (api objectAPIHandlers) DeleteObjectTaggingHandler(w http.ResponseWriter, r
|
||||
return
|
||||
}
|
||||
|
||||
if userTags := oi.UserTags; userTags != "" {
|
||||
// Set this such that authorization policies can be applied on the object tags.
|
||||
r.Header.Set(xhttp.AmzObjectTagging, oi.UserTags)
|
||||
}
|
||||
|
||||
// Allow deleteObjectTagging if policy action is set
|
||||
if s3Error := checkRequestAuthType(ctx, r, policy.DeleteObjectTaggingAction, bucket, object); s3Error != ErrNone {
|
||||
if s3Error := checkRequestAuthTypeWithExistingTags(ctx, r, policy.DeleteObjectTaggingAction, bucket, object, oi.UserTags); s3Error != ErrNone {
|
||||
writeErrorResponse(ctx, w, errorCodes.ToAPIErr(s3Error), r.URL)
|
||||
return
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user