mirror of
https://github.com/pgsty/minio.git
synced 2026-11-02 05:42:06 +03:00
fix: harden CORS and replication request trust
Keep pre-authentication CORS lookups resident-only so attacker-controlled path segments cannot trigger metadata I/O or grow the metadata cache. Preserve fail-closed behavior for startup, load failures, invalid metadata, and the internal namespace. Centralize replication request trust after authentication, distinguish general replication from replica-only privileges, and gate SSE-C ciphertext handling, source metadata, object-lock bypasses, event suppression, delete semantics, and replica status on the appropriate permission. Add least-privilege, multipart, PostPolicy, CORS amplification, and compatibility regressions. Signed-off-by: Feng Ruohang <rh@vonng.com>
This commit is contained in:
@@ -171,6 +171,21 @@ func (api objectAPIHandlers) NewMultipartUploadHandler(w http.ResponseWriter, r
|
||||
writeErrorResponse(ctx, w, errorCodes.ToAPIErr(s3Error), r.URL)
|
||||
return
|
||||
}
|
||||
rawReplica := hasReplicaStatus(r.Header)
|
||||
markerExact := hasReplicationMarker(r.Header)
|
||||
replicationPermitted := false
|
||||
if rawReplica || markerExact {
|
||||
replicationPermitted = replicationPermissionAllowed(ctx, r, bucket, object, policy.ReplicateObjectAction)
|
||||
}
|
||||
if rawReplica && !replicationPermitted {
|
||||
writeErrorResponse(ctx, w, errorCodes.ToAPIErr(ErrAccessDenied), r.URL)
|
||||
return
|
||||
}
|
||||
trustedReplication := markerExact && replicationPermitted
|
||||
replicaTrusted := trustedReplication && rawReplica
|
||||
if hasReplicationRequestHeaders(r.Header) {
|
||||
ctx, r = applyReplicationTrust(ctx, r, trustedReplication, replicaTrusted)
|
||||
}
|
||||
|
||||
// Check if bucket encryption is enabled
|
||||
sseConfig, _ := globalBucketSSEConfigSys.Get(bucket)
|
||||
@@ -205,7 +220,6 @@ func (api objectAPIHandlers) NewMultipartUploadHandler(w http.ResponseWriter, r
|
||||
return
|
||||
}
|
||||
|
||||
_, sourceReplReq := r.Header[xhttp.MinIOSourceReplicationRequest]
|
||||
ssecRepHeaders := []string{
|
||||
"X-Minio-Replication-Server-Side-Encryption-Seal-Algorithm",
|
||||
"X-Minio-Replication-Server-Side-Encryption-Sealed-Key",
|
||||
@@ -218,7 +232,7 @@ func (api objectAPIHandlers) NewMultipartUploadHandler(w http.ResponseWriter, r
|
||||
break
|
||||
}
|
||||
}
|
||||
if !ssecRep || !sourceReplReq {
|
||||
if !ssecRep || !replicaTrusted {
|
||||
if err = setEncryptionMetadata(r, bucket, object, encMetadata); err != nil {
|
||||
writeErrorResponse(ctx, w, toAPIError(ctx, err), r.URL)
|
||||
return
|
||||
@@ -242,24 +256,23 @@ func (api objectAPIHandlers) NewMultipartUploadHandler(w http.ResponseWriter, r
|
||||
return
|
||||
}
|
||||
}
|
||||
if r.Header.Get(xhttp.AmzBucketReplicationStatus) == replication.Replica.String() {
|
||||
if s3Err := isPutActionAllowed(ctx, getRequestAuthType(r), bucket, object, r, policy.ReplicateObjectAction); s3Err != ErrNone {
|
||||
writeErrorResponse(ctx, w, errorCodes.ToAPIErr(s3Err), r.URL)
|
||||
return
|
||||
}
|
||||
if replicaTrusted {
|
||||
if err = extractReplicationMetadataFromMime(ctx, textproto.MIMEHeader(r.Header), metadata); err != nil {
|
||||
writeErrorResponse(ctx, w, toAPIError(ctx, err), r.URL)
|
||||
return
|
||||
}
|
||||
metadata[xhttp.AmzBucketReplicationStatus] = replication.Replica.String()
|
||||
metadata[ReservedMetadataPrefixLower+ReplicaStatus] = replication.Replica.String()
|
||||
metadata[ReservedMetadataPrefixLower+ReplicaTimestamp] = UTCNow().Format(time.RFC3339Nano)
|
||||
} else {
|
||||
delete(metadata, xhttp.AmzBucketReplicationStatus)
|
||||
}
|
||||
retPerms := isPutActionAllowed(ctx, getRequestAuthType(r), bucket, object, r, policy.PutObjectRetentionAction)
|
||||
holdPerms := isPutActionAllowed(ctx, getRequestAuthType(r), bucket, object, r, policy.PutObjectLegalHoldAction)
|
||||
|
||||
getObjectInfo := objectAPI.GetObjectInfo
|
||||
|
||||
retentionMode, retentionDate, legalHold, s3Err := checkPutObjectLockAllowed(ctx, r, bucket, object, getObjectInfo, retPerms, holdPerms)
|
||||
retentionMode, retentionDate, legalHold, s3Err := checkPutObjectLockAllowed(ctx, r, bucket, object, getObjectInfo, retPerms, holdPerms, replicaTrusted)
|
||||
if s3Err == ErrNone && retentionMode.Valid() {
|
||||
metadata[strings.ToLower(xhttp.AmzObjectLockMode)] = string(retentionMode)
|
||||
metadata[strings.ToLower(xhttp.AmzObjectLockRetainUntilDate)] = amztime.ISO8601Format(retentionDate.UTC())
|
||||
@@ -408,6 +421,14 @@ func (api objectAPIHandlers) CopyObjectPartHandler(w http.ResponseWriter, r *htt
|
||||
writeErrorResponse(ctx, w, errorCodes.ToAPIErr(s3Error), r.URL)
|
||||
return
|
||||
}
|
||||
if hasReplicaStatus(r.Header) &&
|
||||
!replicationPermissionAllowed(ctx, r, dstBucket, dstObject, policy.ReplicateObjectAction) {
|
||||
writeErrorResponse(ctx, w, errorCodes.ToAPIErr(ErrAccessDenied), r.URL)
|
||||
return
|
||||
}
|
||||
if hasReplicationRequestHeaders(r.Header) {
|
||||
ctx, r = applyReplicationTrust(ctx, r, false, false)
|
||||
}
|
||||
|
||||
uploadID := r.Form.Get(xhttp.UploadID)
|
||||
partIDString := r.Form.Get(xhttp.PartNumber)
|
||||
@@ -849,6 +870,22 @@ func (api objectAPIHandlers) PutObjectPartHandler(w http.ResponseWriter, r *http
|
||||
writeErrorResponse(ctx, w, toAPIError(ctx, err), r.URL)
|
||||
return
|
||||
}
|
||||
rawReplica := hasReplicaStatus(r.Header)
|
||||
markerExact := hasReplicationMarker(r.Header)
|
||||
replicationPermitted := false
|
||||
if rawReplica || markerExact {
|
||||
replicationPermitted = replicationPermissionAllowed(ctx, r, bucket, object, policy.ReplicateObjectAction)
|
||||
}
|
||||
if rawReplica && !replicationPermitted {
|
||||
writeErrorResponse(ctx, w, errorCodes.ToAPIErr(ErrAccessDenied), r.URL)
|
||||
return
|
||||
}
|
||||
trustedReplication := markerExact && replicationPermitted
|
||||
storedReplica := mi.UserDefined[xhttp.AmzBucketReplicationStatus] == replication.Replica.String()
|
||||
replicaTrusted := trustedReplication && storedReplica
|
||||
if hasReplicationRequestHeaders(r.Header) {
|
||||
ctx, r = applyReplicationTrust(ctx, r, trustedReplication, replicaTrusted)
|
||||
}
|
||||
|
||||
// Read compression metadata preserved in the init multipart for the decision.
|
||||
_, isCompressed := mi.UserDefined[ReservedMetadataPrefix+"compression"]
|
||||
@@ -917,11 +954,9 @@ func (api objectAPIHandlers) PutObjectPartHandler(w http.ResponseWriter, r *http
|
||||
pReader.setChecksumReader(checksumReader)
|
||||
|
||||
_, isEncrypted := crypto.IsEncrypted(mi.UserDefined)
|
||||
_, replicationStatus := mi.UserDefined[xhttp.AmzBucketReplicationStatus]
|
||||
_, sourceReplReq := r.Header[xhttp.MinIOSourceReplicationRequest]
|
||||
var objectEncryptionKey crypto.ObjectKey
|
||||
if isEncrypted {
|
||||
if !crypto.SSEC.IsRequested(r.Header) && crypto.SSEC.IsEncrypted(mi.UserDefined) && !replicationStatus {
|
||||
if !crypto.SSEC.IsRequested(r.Header) && crypto.SSEC.IsEncrypted(mi.UserDefined) && !replicaTrusted {
|
||||
writeErrorResponse(ctx, w, errorCodes.ToAPIErr(ErrSSEMultipartEncrypted), r.URL)
|
||||
return
|
||||
}
|
||||
@@ -941,7 +976,7 @@ func (api objectAPIHandlers) PutObjectPartHandler(w http.ResponseWriter, r *http
|
||||
}
|
||||
}
|
||||
|
||||
if !sourceReplReq || !crypto.SSEC.IsEncrypted(mi.UserDefined) {
|
||||
if !replicaTrusted || !crypto.SSEC.IsEncrypted(mi.UserDefined) {
|
||||
// Calculating object encryption key
|
||||
key, err = decryptObjectMeta(key, bucket, object, mi.UserDefined)
|
||||
if err != nil {
|
||||
@@ -1000,7 +1035,7 @@ func (api objectAPIHandlers) PutObjectPartHandler(w http.ResponseWriter, r *http
|
||||
}
|
||||
opts.IndexCB = idxCb
|
||||
|
||||
opts.ReplicationRequest = sourceReplReq
|
||||
opts.ReplicationRequest = trustedReplication
|
||||
putObjectPart := objectAPI.PutObjectPart
|
||||
|
||||
partInfo, err := putObjectPart(ctx, bucket, object, uploadID, partID, pReader, opts)
|
||||
@@ -1075,6 +1110,20 @@ func (api objectAPIHandlers) CompleteMultipartUploadHandler(w http.ResponseWrite
|
||||
writeErrorResponse(ctx, w, errorCodes.ToAPIErr(s3Error), r.URL)
|
||||
return
|
||||
}
|
||||
rawReplica := hasReplicaStatus(r.Header)
|
||||
markerExact := hasReplicationMarker(r.Header)
|
||||
replicationPermitted := false
|
||||
if rawReplica || markerExact {
|
||||
replicationPermitted = replicationPermissionAllowed(ctx, r, bucket, object, policy.ReplicateObjectAction)
|
||||
}
|
||||
if rawReplica && !replicationPermitted {
|
||||
writeErrorResponse(ctx, w, errorCodes.ToAPIErr(ErrAccessDenied), r.URL)
|
||||
return
|
||||
}
|
||||
trustedReplication := markerExact && replicationPermitted
|
||||
if hasReplicationRequestHeaders(r.Header) {
|
||||
ctx, r = applyReplicationTrust(ctx, r, trustedReplication, trustedReplication && rawReplica)
|
||||
}
|
||||
|
||||
// Get upload id.
|
||||
uploadID, _, _, _, s3Error := getObjectResources(r.Form)
|
||||
@@ -1117,7 +1166,7 @@ func (api objectAPIHandlers) CompleteMultipartUploadHandler(w http.ResponseWrite
|
||||
return
|
||||
}
|
||||
|
||||
if _, _, _, s3Err := checkPutObjectLockAllowed(ctx, r, bucket, object, objectAPI.GetObjectInfo, ErrNone, ErrNone); s3Err != ErrNone {
|
||||
if _, _, _, s3Err := checkPutObjectLockAllowed(ctx, r, bucket, object, objectAPI.GetObjectInfo, ErrNone, ErrNone, false); s3Err != ErrNone {
|
||||
writeErrorResponse(ctx, w, errorCodes.ToAPIErr(s3Err), r.URL)
|
||||
return
|
||||
}
|
||||
@@ -1200,7 +1249,7 @@ func (api objectAPIHandlers) CompleteMultipartUploadHandler(w http.ResponseWrite
|
||||
if dsc := mustReplicate(ctx, bucket, object, objInfo.getMustReplicateOptions(replication.ObjectReplicationType, opts)); dsc.ReplicateAny() {
|
||||
scheduleReplication(ctx, objInfo, objectAPI, dsc, replication.ObjectReplicationType)
|
||||
}
|
||||
if _, ok := r.Header[xhttp.MinIOSourceReplicationRequest]; ok {
|
||||
if isTrustedReplication(ctx) {
|
||||
actualSize, _ := objInfo.GetActualSize()
|
||||
defer globalReplicationStats.Load().UpdateReplicaStat(bucket, actualSize)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user