diff --git a/cmd/admin-bucket-handlers.go b/cmd/admin-bucket-handlers.go index 1d7117193..e16eb751e 100644 --- a/cmd/admin-bucket-handlers.go +++ b/cmd/admin-bucket-handlers.go @@ -128,6 +128,7 @@ func (a adminAPIHandlers) SetRemoteTargetHandler(w http.ResponseWriter, r *http. defer logger.AuditLog(w, r, "SetBucketTarget", mustGetClaimsFromToken(r)) vars := mux.Vars(r) bucket := vars["bucket"] + update := r.URL.Query().Get("update") == "true" if !globalIsErasure { writeErrorResponseJSON(ctx, w, errorCodes.ToAPIErr(ErrNotImplemented), r.URL) @@ -174,13 +175,17 @@ func (a adminAPIHandlers) SetRemoteTargetHandler(w http.ResponseWriter, r *http. writeErrorResponseJSON(ctx, w, errorCodes.ToAPIErr(ErrBucketRemoteIdenticalToSource), r.URL) return } + target.SourceBucket = bucket - target.Arn = globalBucketTargetSys.getRemoteARN(bucket, &target) + if !update { + target.Arn = globalBucketTargetSys.getRemoteARN(bucket, &target) + } if target.Arn == "" { writeErrorResponseJSON(ctx, w, errorCodes.ToAPIErrWithErr(ErrAdminConfigBadJSON, err), r.URL) return } - if err = globalBucketTargetSys.SetTarget(ctx, bucket, &target); err != nil { + + if err = globalBucketTargetSys.SetTarget(ctx, bucket, &target, update); err != nil { writeErrorResponseJSON(ctx, w, toAPIError(ctx, err), r.URL) return } @@ -188,7 +193,6 @@ func (a adminAPIHandlers) SetRemoteTargetHandler(w http.ResponseWriter, r *http. if err != nil { writeErrorResponse(ctx, w, toAPIError(ctx, err), r.URL, guessIsBrowserReq(r)) return - } tgtBytes, err := json.Marshal(&targets) if err != nil { diff --git a/cmd/admin-router.go b/cmd/admin-router.go index 1383cc13f..2051512c4 100644 --- a/cmd/admin-router.go +++ b/cmd/admin-router.go @@ -188,7 +188,7 @@ func registerAdminRouter(router *mux.Router, enableConfigOps, enableIAMOps bool) // SetRemoteTargetHandler adminRouter.Methods(http.MethodPut).Path(adminVersion+"/set-remote-target").HandlerFunc( httpTraceHdrs(adminAPI.SetRemoteTargetHandler)).Queries("bucket", "{bucket:.*}") - // SetRemoteTargetHandler + // RemoveRemoteTargetHandler adminRouter.Methods(http.MethodDelete).Path(adminVersion+"/remove-remote-target").HandlerFunc( httpTraceHdrs(adminAPI.RemoveRemoteTargetHandler)).Queries("bucket", "{bucket:.*}", "arn", "{arn:.*}") } diff --git a/cmd/api-errors.go b/cmd/api-errors.go index 9914c6e64..4ce7ab530 100644 --- a/cmd/api-errors.go +++ b/cmd/api-errors.go @@ -829,7 +829,7 @@ var errorCodes = errorCodeMap{ }, ErrReplicationRemoteConnectionError: { Code: "XMinioAdminReplicationRemoteConnectionError", - Description: "Remote service endpoint or target bucket not available", + Description: "Remote service connection error - please check remote service credentials and target bucket", HTTPStatusCode: http.StatusNotFound, }, ErrBucketRemoteIdenticalToSource: { diff --git a/cmd/bucket-targets.go b/cmd/bucket-targets.go index ddd1b0f41..9adc88d8a 100644 --- a/cmd/bucket-targets.go +++ b/cmd/bucket-targets.go @@ -35,7 +35,6 @@ type BucketTargetSys struct { sync.RWMutex arnRemotesMap map[string]*miniogo.Core targetsMap map[string][]madmin.BucketTarget - clientsCache map[string]*miniogo.Core } // ListTargets lists bucket targets across tenant or for individual bucket, and returns @@ -77,17 +76,24 @@ func (sys *BucketTargetSys) ListBucketTargets(ctx context.Context, bucket string } // SetTarget - sets a new minio-go client target for this bucket. -func (sys *BucketTargetSys) SetTarget(ctx context.Context, bucket string, tgt *madmin.BucketTarget) error { +func (sys *BucketTargetSys) SetTarget(ctx context.Context, bucket string, tgt *madmin.BucketTarget, update bool) error { if globalIsGateway { return nil } - if !tgt.Type.IsValid() { + if !tgt.Type.IsValid() && !update { return BucketRemoteArnTypeInvalid{Bucket: bucket} } clnt, err := sys.getRemoteTargetClient(tgt) if err != nil { return BucketRemoteTargetNotFound{Bucket: tgt.TargetBucket} } + // validate if target credentials are ok + if _, err = clnt.BucketExists(ctx, tgt.TargetBucket); err != nil { + if minio.ToErrorResponse(err).Code == "NoSuchBucket" { + return BucketRemoteTargetNotFound{Bucket: tgt.TargetBucket} + } + return BucketRemoteConnectionErr{Bucket: tgt.TargetBucket} + } if tgt.Type == madmin.ReplicationService { if !globalIsErasure { return NotImplemented{} @@ -97,9 +103,6 @@ func (sys *BucketTargetSys) SetTarget(ctx context.Context, bucket string, tgt *m } vcfg, err := clnt.GetBucketVersioning(ctx, tgt.TargetBucket) if err != nil { - if minio.ToErrorResponse(err).Code == "NoSuchBucket" { - return BucketRemoteTargetNotFound{Bucket: tgt.TargetBucket} - } return BucketRemoteConnectionErr{Bucket: tgt.TargetBucket} } if vcfg.Status != string(versioning.Enabled) { @@ -130,10 +133,10 @@ func (sys *BucketTargetSys) SetTarget(ctx context.Context, bucket string, tgt *m for idx, t := range tgts { labels[t.Label] = struct{}{} if t.Type == tgt.Type { - if t.Arn == tgt.Arn { + if t.Arn == tgt.Arn && !update { return BucketRemoteAlreadyExists{Bucket: t.TargetBucket} } - if t.Label == tgt.Label { + if t.Label == tgt.Label && !update { return BucketRemoteLabelInUse{Bucket: t.TargetBucket} } newtgts[idx] = *tgt @@ -142,18 +145,15 @@ func (sys *BucketTargetSys) SetTarget(ctx context.Context, bucket string, tgt *m } newtgts[idx] = t } - if _, ok := labels[tgt.Label]; ok { + if _, ok := labels[tgt.Label]; ok && !update { return BucketRemoteLabelInUse{Bucket: tgt.TargetBucket} } - if !found { + if !found && !update { newtgts = append(newtgts, *tgt) } sys.targetsMap[bucket] = newtgts sys.arnRemotesMap[tgt.Arn] = clnt - if _, ok := sys.clientsCache[clnt.EndpointURL().String()]; !ok { - sys.clientsCache[clnt.EndpointURL().String()] = clnt - } return nil } @@ -262,7 +262,6 @@ func NewBucketTargetSys() *BucketTargetSys { return &BucketTargetSys{ arnRemotesMap: make(map[string]*miniogo.Core), targetsMap: make(map[string][]madmin.BucketTarget), - clientsCache: make(map[string]*miniogo.Core), } } @@ -309,9 +308,6 @@ func (sys *BucketTargetSys) UpdateAllTargets(bucket string, tgts *madmin.BucketT continue } sys.arnRemotesMap[tgt.Arn] = tgtClient - if _, ok := sys.clientsCache[tgtClient.EndpointURL().String()]; !ok { - sys.clientsCache[tgtClient.EndpointURL().String()] = tgtClient - } } sys.targetsMap[bucket] = tgts.Targets } @@ -335,9 +331,6 @@ func (sys *BucketTargetSys) load(ctx context.Context, buckets []BucketInfo, objA continue } sys.arnRemotesMap[tgt.Arn] = tgtClient - if _, ok := sys.clientsCache[tgtClient.EndpointURL().String()]; !ok { - sys.clientsCache[tgtClient.EndpointURL().String()] = tgtClient - } } sys.targetsMap[bucket.Name] = cfg.Targets } @@ -349,9 +342,6 @@ var getRemoteTargetInstanceTransportOnce sync.Once // Returns a minio-go Client configured to access remote host described in replication target config. func (sys *BucketTargetSys) getRemoteTargetClient(tcfg *madmin.BucketTarget) (*miniogo.Core, error) { - if clnt, ok := sys.clientsCache[tcfg.Endpoint]; ok { - return clnt, nil - } config := tcfg.Credentials creds := credentials.NewStaticV4(config.AccessKey, config.SecretKey, "") diff --git a/pkg/madmin/examples/bucket-target.go b/pkg/madmin/examples/bucket-target.go index eac3602be..74a935e63 100644 --- a/pkg/madmin/examples/bucket-target.go +++ b/pkg/madmin/examples/bucket-target.go @@ -21,6 +21,7 @@ package main import ( "context" + "fmt" "log" "github.com/minio/minio/pkg/auth" @@ -44,9 +45,11 @@ func main() { } target := madmin.BucketTarget{Endpoint: "site2:9000", Credentials: creds, TargetBucket: "destbucket", IsSSL: false, Type: madmin.ReplicationArn, BandwidthLimit: 2 * 1024 * 1024} // Set bucket target - if err := madmClnt.SetBucketTarget(ctx, "srcbucket", &target); err != nil { + arn, err := madmClnt.SetBucketTarget(ctx, "srcbucket", &target) + if err != nil { log.Fatalln(err) } + fmt.Println("replication target ARN is:", arn) // List all bucket target(s) target, err = madmClnt.ListBucketTargets(ctx, "srcbucket", "") if err != nil { @@ -57,6 +60,17 @@ func main() { if err != nil { log.Fatalln(err) } + // update credentials for target + creds, err := auth.CreateCredentials("access-key2", "secret-key2") + if err != nil { + log.Fatalln(err) + } + target := madmin.BucketTarget{Endpoint: "site2:9000", Credentials: creds, SourceBucket: "srcbucket", TargetBucket: "destbucket", IsSSL: false, Arn: "arn:minio:ilm:us-east-1:3cbe15b8-82b9-44bc-a737-db9051ab359a:srcbucket"} + // update credentials on bucket target + if _, err := madmClnt.UpdateBucketTarget(ctx, &target); err != nil { + log.Fatalln(err) + } + // Remove bucket target arn := "arn:minio:replica::ac66b2cf-dd8f-4e7e-a882-9a64132f0d59:dest" if err := madmClnt.RemoveBucketTarget(ctx, "srcbucket", arn); err != nil { diff --git a/pkg/madmin/remote-target-commands.go b/pkg/madmin/remote-target-commands.go index 8e6adb47b..c881cbf65 100644 --- a/pkg/madmin/remote-target-commands.go +++ b/pkg/madmin/remote-target-commands.go @@ -236,6 +236,51 @@ func (adm *AdminClient) SetRemoteTarget(ctx context.Context, bucket string, targ return arn, nil } +// UpdateRemoteTarget updates credentials for a remote bucket target +func (adm *AdminClient) UpdateRemoteTarget(ctx context.Context, target *BucketTarget) (string, error) { + if target == nil { + return "", fmt.Errorf("target cannot be nil") + } + data, err := json.Marshal(target) + if err != nil { + return "", err + } + encData, err := EncryptData(adm.getSecretKey(), data) + if err != nil { + return "", err + } + queryValues := url.Values{} + queryValues.Set("bucket", target.SourceBucket) + queryValues.Set("update", "true") + + reqData := requestData{ + relPath: adminAPIPrefix + "/set-remote-target", + queryValues: queryValues, + content: encData, + } + + // Execute PUT on /minio/admin/v3/set-remote-target to set a target for this bucket of specific arn type. + resp, err := adm.executeMethod(ctx, http.MethodPut, reqData) + + defer closeResponse(resp) + if err != nil { + return "", err + } + + if resp.StatusCode != http.StatusOK { + return "", httpRespToErrorResponse(resp) + } + b, err := ioutil.ReadAll(resp.Body) + if err != nil { + return "", err + } + var arn string + if err = json.Unmarshal(b, &arn); err != nil { + return "", err + } + return arn, nil +} + // RemoveRemoteTarget removes a remote target associated with particular ARN for this bucket func (adm *AdminClient) RemoveRemoteTarget(ctx context.Context, bucket, arn string) error { queryValues := url.Values{}