Files
minio/cmd/replication-trust.go
T
Feng Ruohang 9b11dc9469 build: converge the final 20260903 dependency graph
Adopt silo-pkg v3.13.2, the 20260903 mcli release, and the latest validated SILO Console commit. Move maintained source imports to the pgsty/silo-pkg module path, refresh the resulting dependency closure and credits, and keep only documented legacy minio/pkg transitive residue.

Signed-off-by: Feng Ruohang <rh@vonng.com>
2026-09-03 20:54:40 +08:00

148 lines
5.4 KiB
Go

// Copyright (c) 2015-2026 MinIO, Inc.
// Copyright (c) 2026 PGSTY
//
// This file is part of MinIO Object Storage stack
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
package cmd
import (
"context"
"net/http"
objectreplication "github.com/minio/minio/internal/bucket/replication"
xhttp "github.com/minio/minio/internal/http"
"github.com/minio/minio/internal/logger"
"github.com/pgsty/silo-pkg/v3/policy"
)
type (
replicationTrustKey struct{}
replicaTrustKey struct{}
)
// hasReplicationMarker reports whether the internal replication marker has
// its one accepted wire value. Header presence alone is never a trust signal.
func hasReplicationMarker(h http.Header) bool {
values, ok := h[http.CanonicalHeaderKey(xhttp.MinIOSourceReplicationRequest)]
return ok && len(values) == 1 && values[0] == "true"
}
func hasReplicationMarkerHeader(h http.Header) bool {
_, ok := h[http.CanonicalHeaderKey(xhttp.MinIOSourceReplicationRequest)]
return ok
}
func hasReplicaStatus(h http.Header) bool {
return h.Get(xhttp.AmzBucketReplicationStatus) == objectreplication.Replica.String()
}
func withReplicationTrust(ctx context.Context, trusted, replicaTrusted bool) context.Context {
ctx = context.WithValue(ctx, replicationTrustKey{}, trusted)
return context.WithValue(ctx, replicaTrustKey{}, trusted && replicaTrusted)
}
func isTrustedReplication(ctx context.Context) bool {
trusted, _ := ctx.Value(replicationTrustKey{}).(bool)
return trusted
}
func isReplicaTrusted(ctx context.Context) bool {
trusted, _ := ctx.Value(replicaTrustKey{}).(bool)
return trusted
}
// replicationPermissionAllowed must be called only after the request's
// existing authentication/signature path has succeeded and populated ReqInfo.
// Replication peers are authenticated principals; an anonymous bucket-policy
// grant must not turn client-controlled internal headers into trusted state.
func replicationPermissionAllowed(ctx context.Context, r *http.Request, bucket, object string, action policy.Action) bool {
reqInfo := logger.GetReqInfo(ctx)
if reqInfo == nil || reqInfo.Cred.AccessKey == "" {
return false
}
reqInfo.BucketName = bucket
reqInfo.ObjectName = object
return authorizeRequest(ctx, r, action) == ErrNone
}
// evaluateReplicationTrust decides whether a request may carry replication
// semantics for the given action. A request that declares itself a replica
// without holding the replication permission is rejected. trusted reports that
// the exact marker came from a permitted principal; replica additionally
// requires the request to declare REPLICA status.
func evaluateReplicationTrust(ctx context.Context, r *http.Request, bucket, object string, action policy.Action) (trusted, replica bool, s3Err APIErrorCode) {
rawReplica := hasReplicaStatus(r.Header)
markerExact := hasReplicationMarker(r.Header)
permitted := false
if rawReplica || markerExact {
permitted = replicationPermissionAllowed(ctx, r, bucket, object, action)
}
if rawReplica && !permitted {
return false, false, ErrAccessDenied
}
trusted = markerExact && permitted
return trusted, trusted && rawReplica, ErrNone
}
// replicationRequestHeaders are internal request controls. They are removed
// only after signature verification when a request has not earned replication
// trust. Public S3/SSE/checksum headers, proxy loop guards, and replication
// validity/readiness probes are intentionally not listed here.
var replicationRequestHeaders = []string{
xhttp.MinIOSourceReplicationRequest,
xhttp.MinIOSourceETag,
xhttp.MinIOSourceMTime,
xhttp.MinIOSourceDeleteMarker,
xhttp.MinIOSourceDeleteMarkerDelete,
xhttp.MinIOSourceTaggingTimestamp,
xhttp.MinIOSourceObjectRetentionTimestamp,
xhttp.MinIOSourceObjectLegalHoldTimestamp,
"X-Minio-Replication-Server-Side-Encryption-Sealed-Key",
"X-Minio-Replication-Server-Side-Encryption-Seal-Algorithm",
"X-Minio-Replication-Server-Side-Encryption-Iv",
"X-Minio-Replication-Encrypted-Multipart",
xhttp.MinIOReplicationActualObjectSize,
ReplicationSsecChecksumHeader,
xhttp.AmzBucketReplicationStatus,
}
func stripReplicationRequestHeaders(h http.Header) {
for _, name := range replicationRequestHeaders {
h.Del(name)
}
}
func hasReplicationRequestHeaders(h http.Header) bool {
for _, name := range replicationRequestHeaders {
if _, ok := h[http.CanonicalHeaderKey(name)]; ok {
return true
}
}
return false
}
func cloneRequestWithoutReplicationHeaders(ctx context.Context, r *http.Request) *http.Request {
clone := r.Clone(ctx)
stripReplicationRequestHeaders(clone.Header)
// A streaming body reader built from the original request fills r.Trailer
// as the body is consumed; the checksum reader must observe that same map.
clone.Trailer = r.Trailer
return clone
}
// applyReplicationTrust binds the handler context to the effective request.
// The context marker is the authorization source of truth; header removal is
// defense in depth for option builders and future call sites.
func applyReplicationTrust(ctx context.Context, r *http.Request, trusted, replicaTrusted bool) (context.Context, *http.Request) {
ctx = withReplicationTrust(ctx, trusted, replicaTrusted)
if trusted {
return ctx, r.WithContext(ctx)
}
return ctx, cloneRequestWithoutReplicationHeaders(ctx, r)
}