diff --git a/cmd/bucket-replication.go b/cmd/bucket-replication.go index 0af7a9832..479a5efed 100644 --- a/cmd/bucket-replication.go +++ b/cmd/bucket-replication.go @@ -26,6 +26,7 @@ import ( "math" "math/rand" "net/http" + "net/url" "path" "reflect" "strings" @@ -138,6 +139,13 @@ func validateReplicationDestination(ctx context.Context, bucket string, rCfg *re // validate replication ARN against target endpoint c := globalBucketTargetSys.GetRemoteTargetClient(ctx, arnStr) if c != nil { + if err := checkRemoteEndpoint(ctx, c.EndpointURL()); err != nil { + switch err.(type) { + case BucketRemoteIdenticalToSource: + return true, errorCodes.ToAPIErrWithErr(ErrBucketRemoteIdenticalToSource, fmt.Errorf("remote target endpoint %s is self referential", c.EndpointURL().String())) + default: + } + } if c.EndpointURL().String() == clnt.EndpointURL().String() { selfTarget, _ := isLocalHost(clnt.EndpointURL().Hostname(), clnt.EndpointURL().Port(), globalMinioPort) if !sameTarget { @@ -154,6 +162,45 @@ func validateReplicationDestination(ctx context.Context, bucket string, rCfg *re return sameTarget, toAPIError(ctx, nil) } +// performs a http request to remote endpoint to check if deployment id of remote endpoint is same as +// local cluster deployment id. This is to prevent replication to self, especially in case of a loadbalancer +// in front of MinIO. +func checkRemoteEndpoint(ctx context.Context, epURL *url.URL) error { + reqURL := &url.URL{ + Scheme: epURL.Scheme, + Host: epURL.Host, + Path: healthCheckReadinessPath, + } + + req, err := http.NewRequestWithContext(ctx, http.MethodGet, reqURL.String(), nil) + if err != nil { + return err + } + + client := &http.Client{ + Transport: NewHTTPTransport(), + Timeout: 10 * time.Second, + } + + resp, err := client.Do(req) + if err != nil { + return err + } + if err == nil { + // Drain the connection. + xhttp.DrainBody(resp.Body) + } + if resp != nil { + amzid := resp.Header.Get(xhttp.AmzRequestHostID) + if _, ok := globalNodeNamesHex[amzid]; ok { + return BucketRemoteIdenticalToSource{ + Endpoint: epURL.String(), + } + } + } + return nil +} + type mustReplicateOptions struct { meta map[string]string status replication.StatusType diff --git a/cmd/globals.go b/cmd/globals.go index 1d933e01d..5e36f7065 100644 --- a/cmd/globals.go +++ b/cmd/globals.go @@ -232,6 +232,7 @@ var ( // The name of this local node, fetched from arguments globalLocalNodeName string globalLocalNodeNameHex string + globalNodeNamesHex map[string]struct{} // The global subnet config globalSubnetConfig subnet.Config diff --git a/cmd/object-api-errors.go b/cmd/object-api-errors.go index 97708e4f7..e456c239d 100644 --- a/cmd/object-api-errors.go +++ b/cmd/object-api-errors.go @@ -437,6 +437,16 @@ func (e RemoteTargetConnectionErr) Error() string { return fmt.Sprintf("Remote service endpoint %s not available\n\t%s", e.Endpoint, e.Err.Error()) } +// BucketRemoteIdenticalToSource remote already exists for this target type. +type BucketRemoteIdenticalToSource struct { + GenericError + Endpoint string +} + +func (e BucketRemoteIdenticalToSource) Error() string { + return fmt.Sprintf("Remote service endpoint %s is self referential to current cluster", e.Endpoint) +} + // BucketRemoteAlreadyExists remote already exists for this target type. type BucketRemoteAlreadyExists GenericError diff --git a/cmd/server-main.go b/cmd/server-main.go index 260a1a674..0c1f84c23 100644 --- a/cmd/server-main.go +++ b/cmd/server-main.go @@ -247,9 +247,9 @@ func serverHandleCmdArgs(ctx *cli.Context) { logger.FatalIf(err, "Invalid command line arguments") globalLocalNodeName = GetLocalPeer(globalEndpoints, globalMinioHost, globalMinioPort) - nodeNameSum := sha256.Sum256([]byte(globalLocalNodeNameHex)) + nodeNameSum := sha256.Sum256([]byte(globalLocalNodeName)) globalLocalNodeNameHex = hex.EncodeToString(nodeNameSum[:]) - + globalNodeNamesHex = make(map[string]struct{}) globalRemoteEndpoints = make(map[string]Endpoint) for _, z := range globalEndpoints { for _, ep := range z.Endpoints { @@ -258,6 +258,9 @@ func serverHandleCmdArgs(ctx *cli.Context) { } else { globalRemoteEndpoints[ep.Host] = ep } + nodeNameSum := sha256.Sum256([]byte(ep.Host)) + globalNodeNamesHex[hex.EncodeToString(nodeNameSum[:])] = struct{}{} + } }