mirror of
https://github.com/minio/minio.git
synced 2025-01-12 15:33:22 -05:00
Add admin API to edit remote bucket target credentials (#10848)
This commit is contained in:
parent
f96ed3769f
commit
3ad41fe89d
@ -128,6 +128,7 @@ func (a adminAPIHandlers) SetRemoteTargetHandler(w http.ResponseWriter, r *http.
|
|||||||
defer logger.AuditLog(w, r, "SetBucketTarget", mustGetClaimsFromToken(r))
|
defer logger.AuditLog(w, r, "SetBucketTarget", mustGetClaimsFromToken(r))
|
||||||
vars := mux.Vars(r)
|
vars := mux.Vars(r)
|
||||||
bucket := vars["bucket"]
|
bucket := vars["bucket"]
|
||||||
|
update := r.URL.Query().Get("update") == "true"
|
||||||
|
|
||||||
if !globalIsErasure {
|
if !globalIsErasure {
|
||||||
writeErrorResponseJSON(ctx, w, errorCodes.ToAPIErr(ErrNotImplemented), r.URL)
|
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)
|
writeErrorResponseJSON(ctx, w, errorCodes.ToAPIErr(ErrBucketRemoteIdenticalToSource), r.URL)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
target.SourceBucket = bucket
|
target.SourceBucket = bucket
|
||||||
target.Arn = globalBucketTargetSys.getRemoteARN(bucket, &target)
|
if !update {
|
||||||
|
target.Arn = globalBucketTargetSys.getRemoteARN(bucket, &target)
|
||||||
|
}
|
||||||
if target.Arn == "" {
|
if target.Arn == "" {
|
||||||
writeErrorResponseJSON(ctx, w, errorCodes.ToAPIErrWithErr(ErrAdminConfigBadJSON, err), r.URL)
|
writeErrorResponseJSON(ctx, w, errorCodes.ToAPIErrWithErr(ErrAdminConfigBadJSON, err), r.URL)
|
||||||
return
|
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)
|
writeErrorResponseJSON(ctx, w, toAPIError(ctx, err), r.URL)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@ -188,7 +193,6 @@ func (a adminAPIHandlers) SetRemoteTargetHandler(w http.ResponseWriter, r *http.
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
writeErrorResponse(ctx, w, toAPIError(ctx, err), r.URL, guessIsBrowserReq(r))
|
writeErrorResponse(ctx, w, toAPIError(ctx, err), r.URL, guessIsBrowserReq(r))
|
||||||
return
|
return
|
||||||
|
|
||||||
}
|
}
|
||||||
tgtBytes, err := json.Marshal(&targets)
|
tgtBytes, err := json.Marshal(&targets)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -188,7 +188,7 @@ func registerAdminRouter(router *mux.Router, enableConfigOps, enableIAMOps bool)
|
|||||||
// SetRemoteTargetHandler
|
// SetRemoteTargetHandler
|
||||||
adminRouter.Methods(http.MethodPut).Path(adminVersion+"/set-remote-target").HandlerFunc(
|
adminRouter.Methods(http.MethodPut).Path(adminVersion+"/set-remote-target").HandlerFunc(
|
||||||
httpTraceHdrs(adminAPI.SetRemoteTargetHandler)).Queries("bucket", "{bucket:.*}")
|
httpTraceHdrs(adminAPI.SetRemoteTargetHandler)).Queries("bucket", "{bucket:.*}")
|
||||||
// SetRemoteTargetHandler
|
// RemoveRemoteTargetHandler
|
||||||
adminRouter.Methods(http.MethodDelete).Path(adminVersion+"/remove-remote-target").HandlerFunc(
|
adminRouter.Methods(http.MethodDelete).Path(adminVersion+"/remove-remote-target").HandlerFunc(
|
||||||
httpTraceHdrs(adminAPI.RemoveRemoteTargetHandler)).Queries("bucket", "{bucket:.*}", "arn", "{arn:.*}")
|
httpTraceHdrs(adminAPI.RemoveRemoteTargetHandler)).Queries("bucket", "{bucket:.*}", "arn", "{arn:.*}")
|
||||||
}
|
}
|
||||||
|
@ -829,7 +829,7 @@ var errorCodes = errorCodeMap{
|
|||||||
},
|
},
|
||||||
ErrReplicationRemoteConnectionError: {
|
ErrReplicationRemoteConnectionError: {
|
||||||
Code: "XMinioAdminReplicationRemoteConnectionError",
|
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,
|
HTTPStatusCode: http.StatusNotFound,
|
||||||
},
|
},
|
||||||
ErrBucketRemoteIdenticalToSource: {
|
ErrBucketRemoteIdenticalToSource: {
|
||||||
|
@ -35,7 +35,6 @@ type BucketTargetSys struct {
|
|||||||
sync.RWMutex
|
sync.RWMutex
|
||||||
arnRemotesMap map[string]*miniogo.Core
|
arnRemotesMap map[string]*miniogo.Core
|
||||||
targetsMap map[string][]madmin.BucketTarget
|
targetsMap map[string][]madmin.BucketTarget
|
||||||
clientsCache map[string]*miniogo.Core
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// ListTargets lists bucket targets across tenant or for individual bucket, and returns
|
// 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.
|
// 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 {
|
if globalIsGateway {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
if !tgt.Type.IsValid() {
|
if !tgt.Type.IsValid() && !update {
|
||||||
return BucketRemoteArnTypeInvalid{Bucket: bucket}
|
return BucketRemoteArnTypeInvalid{Bucket: bucket}
|
||||||
}
|
}
|
||||||
clnt, err := sys.getRemoteTargetClient(tgt)
|
clnt, err := sys.getRemoteTargetClient(tgt)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return BucketRemoteTargetNotFound{Bucket: tgt.TargetBucket}
|
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 tgt.Type == madmin.ReplicationService {
|
||||||
if !globalIsErasure {
|
if !globalIsErasure {
|
||||||
return NotImplemented{}
|
return NotImplemented{}
|
||||||
@ -97,9 +103,6 @@ func (sys *BucketTargetSys) SetTarget(ctx context.Context, bucket string, tgt *m
|
|||||||
}
|
}
|
||||||
vcfg, err := clnt.GetBucketVersioning(ctx, tgt.TargetBucket)
|
vcfg, err := clnt.GetBucketVersioning(ctx, tgt.TargetBucket)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
if minio.ToErrorResponse(err).Code == "NoSuchBucket" {
|
|
||||||
return BucketRemoteTargetNotFound{Bucket: tgt.TargetBucket}
|
|
||||||
}
|
|
||||||
return BucketRemoteConnectionErr{Bucket: tgt.TargetBucket}
|
return BucketRemoteConnectionErr{Bucket: tgt.TargetBucket}
|
||||||
}
|
}
|
||||||
if vcfg.Status != string(versioning.Enabled) {
|
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 {
|
for idx, t := range tgts {
|
||||||
labels[t.Label] = struct{}{}
|
labels[t.Label] = struct{}{}
|
||||||
if t.Type == tgt.Type {
|
if t.Type == tgt.Type {
|
||||||
if t.Arn == tgt.Arn {
|
if t.Arn == tgt.Arn && !update {
|
||||||
return BucketRemoteAlreadyExists{Bucket: t.TargetBucket}
|
return BucketRemoteAlreadyExists{Bucket: t.TargetBucket}
|
||||||
}
|
}
|
||||||
if t.Label == tgt.Label {
|
if t.Label == tgt.Label && !update {
|
||||||
return BucketRemoteLabelInUse{Bucket: t.TargetBucket}
|
return BucketRemoteLabelInUse{Bucket: t.TargetBucket}
|
||||||
}
|
}
|
||||||
newtgts[idx] = *tgt
|
newtgts[idx] = *tgt
|
||||||
@ -142,18 +145,15 @@ func (sys *BucketTargetSys) SetTarget(ctx context.Context, bucket string, tgt *m
|
|||||||
}
|
}
|
||||||
newtgts[idx] = t
|
newtgts[idx] = t
|
||||||
}
|
}
|
||||||
if _, ok := labels[tgt.Label]; ok {
|
if _, ok := labels[tgt.Label]; ok && !update {
|
||||||
return BucketRemoteLabelInUse{Bucket: tgt.TargetBucket}
|
return BucketRemoteLabelInUse{Bucket: tgt.TargetBucket}
|
||||||
}
|
}
|
||||||
if !found {
|
if !found && !update {
|
||||||
newtgts = append(newtgts, *tgt)
|
newtgts = append(newtgts, *tgt)
|
||||||
}
|
}
|
||||||
|
|
||||||
sys.targetsMap[bucket] = newtgts
|
sys.targetsMap[bucket] = newtgts
|
||||||
sys.arnRemotesMap[tgt.Arn] = clnt
|
sys.arnRemotesMap[tgt.Arn] = clnt
|
||||||
if _, ok := sys.clientsCache[clnt.EndpointURL().String()]; !ok {
|
|
||||||
sys.clientsCache[clnt.EndpointURL().String()] = clnt
|
|
||||||
}
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -262,7 +262,6 @@ func NewBucketTargetSys() *BucketTargetSys {
|
|||||||
return &BucketTargetSys{
|
return &BucketTargetSys{
|
||||||
arnRemotesMap: make(map[string]*miniogo.Core),
|
arnRemotesMap: make(map[string]*miniogo.Core),
|
||||||
targetsMap: make(map[string][]madmin.BucketTarget),
|
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
|
continue
|
||||||
}
|
}
|
||||||
sys.arnRemotesMap[tgt.Arn] = tgtClient
|
sys.arnRemotesMap[tgt.Arn] = tgtClient
|
||||||
if _, ok := sys.clientsCache[tgtClient.EndpointURL().String()]; !ok {
|
|
||||||
sys.clientsCache[tgtClient.EndpointURL().String()] = tgtClient
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
sys.targetsMap[bucket] = tgts.Targets
|
sys.targetsMap[bucket] = tgts.Targets
|
||||||
}
|
}
|
||||||
@ -335,9 +331,6 @@ func (sys *BucketTargetSys) load(ctx context.Context, buckets []BucketInfo, objA
|
|||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
sys.arnRemotesMap[tgt.Arn] = tgtClient
|
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
|
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.
|
// 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) {
|
func (sys *BucketTargetSys) getRemoteTargetClient(tcfg *madmin.BucketTarget) (*miniogo.Core, error) {
|
||||||
if clnt, ok := sys.clientsCache[tcfg.Endpoint]; ok {
|
|
||||||
return clnt, nil
|
|
||||||
}
|
|
||||||
config := tcfg.Credentials
|
config := tcfg.Credentials
|
||||||
creds := credentials.NewStaticV4(config.AccessKey, config.SecretKey, "")
|
creds := credentials.NewStaticV4(config.AccessKey, config.SecretKey, "")
|
||||||
|
|
||||||
|
@ -21,6 +21,7 @@ package main
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
|
"fmt"
|
||||||
"log"
|
"log"
|
||||||
|
|
||||||
"github.com/minio/minio/pkg/auth"
|
"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}
|
target := madmin.BucketTarget{Endpoint: "site2:9000", Credentials: creds, TargetBucket: "destbucket", IsSSL: false, Type: madmin.ReplicationArn, BandwidthLimit: 2 * 1024 * 1024}
|
||||||
// Set bucket target
|
// 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)
|
log.Fatalln(err)
|
||||||
}
|
}
|
||||||
|
fmt.Println("replication target ARN is:", arn)
|
||||||
// List all bucket target(s)
|
// List all bucket target(s)
|
||||||
target, err = madmClnt.ListBucketTargets(ctx, "srcbucket", "")
|
target, err = madmClnt.ListBucketTargets(ctx, "srcbucket", "")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@ -57,6 +60,17 @@ func main() {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
log.Fatalln(err)
|
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
|
// Remove bucket target
|
||||||
arn := "arn:minio:replica::ac66b2cf-dd8f-4e7e-a882-9a64132f0d59:dest"
|
arn := "arn:minio:replica::ac66b2cf-dd8f-4e7e-a882-9a64132f0d59:dest"
|
||||||
if err := madmClnt.RemoveBucketTarget(ctx, "srcbucket", arn); err != nil {
|
if err := madmClnt.RemoveBucketTarget(ctx, "srcbucket", arn); err != nil {
|
||||||
|
@ -236,6 +236,51 @@ func (adm *AdminClient) SetRemoteTarget(ctx context.Context, bucket string, targ
|
|||||||
return arn, nil
|
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
|
// RemoveRemoteTarget removes a remote target associated with particular ARN for this bucket
|
||||||
func (adm *AdminClient) RemoveRemoteTarget(ctx context.Context, bucket, arn string) error {
|
func (adm *AdminClient) RemoveRemoteTarget(ctx context.Context, bucket, arn string) error {
|
||||||
queryValues := url.Values{}
|
queryValues := url.Values{}
|
||||||
|
Loading…
Reference in New Issue
Block a user