1// 2// MinIO Object Storage (c) 2021 MinIO, Inc. 3// 4// Licensed under the Apache License, Version 2.0 (the "License"); 5// you may not use this file except in compliance with the License. 6// You may obtain a copy of the License at 7// 8// http://www.apache.org/licenses/LICENSE-2.0 9// 10// Unless required by applicable law or agreed to in writing, software 11// distributed under the License is distributed on an "AS IS" BASIS, 12// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13// See the License for the specific language governing permissions and 14// limitations under the License. 15// 16 17package madmin 18 19import ( 20 "context" 21 "encoding/json" 22 "io/ioutil" 23 "net/http" 24 "net/url" 25) 26 27// PeerSite - represents a cluster/site to be added to the set of replicated 28// sites. 29type PeerSite struct { 30 Name string `json:"name"` 31 Endpoint string `json:"endpoints"` 32 AccessKey string `json:"accessKey"` 33 SecretKey string `json:"secretKey"` 34} 35 36// Meaningful values for ReplicateAddStatus.Status 37const ( 38 ReplicateAddStatusSuccess = "Requested sites were configured for replication successfully." 39 ReplicateAddStatusPartial = "Some sites could not be configured for replication." 40) 41 42// ReplicateAddStatus - returns status of add request. 43type ReplicateAddStatus struct { 44 Success bool `json:"success"` 45 Status string `json:"status"` 46 ErrDetail string `json:"errorDetail,omitempty"` 47 InitialSyncErrorMessage string `json:"initialSyncErrorMessage,omitempty"` 48} 49 50// SiteReplicationAdd - sends the SR add API call. 51func (adm *AdminClient) SiteReplicationAdd(ctx context.Context, sites []PeerSite) (ReplicateAddStatus, error) { 52 sitesBytes, err := json.Marshal(sites) 53 if err != nil { 54 return ReplicateAddStatus{}, nil 55 } 56 encBytes, err := EncryptData(adm.getSecretKey(), sitesBytes) 57 if err != nil { 58 return ReplicateAddStatus{}, err 59 } 60 61 reqData := requestData{ 62 relPath: adminAPIPrefix + "/site-replication/add", 63 content: encBytes, 64 } 65 66 resp, err := adm.executeMethod(ctx, http.MethodPut, reqData) 67 defer closeResponse(resp) 68 if err != nil { 69 return ReplicateAddStatus{}, err 70 } 71 72 if resp.StatusCode != http.StatusOK { 73 return ReplicateAddStatus{}, httpRespToErrorResponse(resp) 74 } 75 76 b, err := ioutil.ReadAll(resp.Body) 77 if err != nil { 78 return ReplicateAddStatus{}, err 79 } 80 81 var res ReplicateAddStatus 82 if err = json.Unmarshal(b, &res); err != nil { 83 return ReplicateAddStatus{}, err 84 } 85 86 return res, nil 87} 88 89// SiteReplicationInfo - contains cluster replication information. 90type SiteReplicationInfo struct { 91 Enabled bool `json:"enabled"` 92 Name string `json:"name,omitempty"` 93 Sites []PeerInfo `json:"sites,omitempty"` 94 ServiceAccountAccessKey string `json:"serviceAccountAccessKey,omitempty"` 95} 96 97// SiteReplicationInfo - returns cluster replication information. 98func (adm *AdminClient) SiteReplicationInfo(ctx context.Context) (info SiteReplicationInfo, err error) { 99 reqData := requestData{ 100 relPath: adminAPIPrefix + "/site-replication/info", 101 } 102 103 resp, err := adm.executeMethod(ctx, http.MethodGet, reqData) 104 defer closeResponse(resp) 105 if err != nil { 106 return info, err 107 } 108 109 if resp.StatusCode != http.StatusOK { 110 return info, httpRespToErrorResponse(resp) 111 } 112 113 b, err := ioutil.ReadAll(resp.Body) 114 if err != nil { 115 return info, err 116 } 117 118 err = json.Unmarshal(b, &info) 119 return info, err 120} 121 122// SRInternalJoinReq - arg body for SRInternalJoin 123type SRInternalJoinReq struct { 124 SvcAcctAccessKey string `json:"svcAcctAccessKey"` 125 SvcAcctSecretKey string `json:"svcAcctSecretKey"` 126 SvcAcctParent string `json:"svcAcctParent"` 127 Peers map[string]PeerInfo `json:"peers"` 128} 129 130// PeerInfo - contains some properties of a cluster peer. 131type PeerInfo struct { 132 Endpoint string `json:"endpoint"` 133 Name string `json:"name"` 134 // Deployment ID is useful as it is immutable - though endpoint may 135 // change. 136 DeploymentID string `json:"deploymentID"` 137} 138 139// SRInternalJoin - used only by minio server to send SR join requests to peer 140// servers. 141func (adm *AdminClient) SRInternalJoin(ctx context.Context, r SRInternalJoinReq) error { 142 b, err := json.Marshal(r) 143 if err != nil { 144 return err 145 } 146 encBuf, err := EncryptData(adm.getSecretKey(), b) 147 if err != nil { 148 return err 149 } 150 151 reqData := requestData{ 152 relPath: adminAPIPrefix + "/site-replication/peer/join", 153 content: encBuf, 154 } 155 156 resp, err := adm.executeMethod(ctx, http.MethodPut, reqData) 157 defer closeResponse(resp) 158 if err != nil { 159 return err 160 } 161 162 if resp.StatusCode != http.StatusOK { 163 return httpRespToErrorResponse(resp) 164 } 165 166 return nil 167} 168 169// BktOp represents the bucket operation being requested. 170type BktOp string 171 172// BktOp value constants. 173const ( 174 // make bucket and enable versioning 175 MakeWithVersioningBktOp BktOp = "make-with-versioning" 176 // add replication configuration 177 ConfigureReplBktOp BktOp = "configure-replication" 178 // delete bucket (forceDelete = off) 179 DeleteBucketBktOp BktOp = "delete-bucket" 180 // delete bucket (forceDelete = on) 181 ForceDeleteBucketBktOp BktOp = "force-delete-bucket" 182) 183 184// SRInternalBucketOps - tells peers to create bucket and setup replication. 185func (adm *AdminClient) SRInternalBucketOps(ctx context.Context, bucket string, op BktOp, opts map[string]string) error { 186 v := url.Values{} 187 v.Add("bucket", bucket) 188 v.Add("operation", string(op)) 189 190 // For make-bucket, bucket options may be sent via `opts` 191 if op == MakeWithVersioningBktOp { 192 for k, val := range opts { 193 v.Add(k, val) 194 } 195 } 196 reqData := requestData{ 197 queryValues: v, 198 relPath: adminAPIPrefix + "/site-replication/peer/bucket-ops", 199 } 200 201 resp, err := adm.executeMethod(ctx, http.MethodPut, reqData) 202 defer closeResponse(resp) 203 if err != nil { 204 return err 205 } 206 207 if resp.StatusCode != http.StatusOK { 208 return httpRespToErrorResponse(resp) 209 } 210 211 return nil 212} 213 214// SRIAMItem.Type constants. 215const ( 216 SRIAMItemPolicy = "policy" 217 SRIAMItemSvcAcc = "service-account" 218 SRIAMItemSTSAcc = "sts-account" 219 SRIAMItemPolicyMapping = "policy-mapping" 220) 221 222// SRSvcAccCreate - create operation 223type SRSvcAccCreate struct { 224 Parent string `json:"parent"` 225 AccessKey string `json:"accessKey"` 226 SecretKey string `json:"secretKey"` 227 Groups []string `json:"groups"` 228 Claims map[string]interface{} `json:"claims"` 229 SessionPolicy json.RawMessage `json:"sessionPolicy"` 230 Status string `json:"status"` 231} 232 233// SRSvcAccUpdate - update operation 234type SRSvcAccUpdate struct { 235 AccessKey string `json:"accessKey"` 236 SecretKey string `json:"secretKey"` 237 Status string `json:"status"` 238 SessionPolicy json.RawMessage `json:"sessionPolicy"` 239} 240 241// SRSvcAccDelete - delete operation 242type SRSvcAccDelete struct { 243 AccessKey string `json:"accessKey"` 244} 245 246// SRSvcAccChange - sum-type to represent an svc account change. 247type SRSvcAccChange struct { 248 Create *SRSvcAccCreate `json:"crSvcAccCreate"` 249 Update *SRSvcAccUpdate `json:"crSvcAccUpdate"` 250 Delete *SRSvcAccDelete `json:"crSvcAccDelete"` 251} 252 253// SRPolicyMapping - represents mapping of a policy to a user or group. 254type SRPolicyMapping struct { 255 UserOrGroup string `json:"userOrGroup"` 256 IsGroup bool `json:"isGroup"` 257 Policy string `json:"policy"` 258} 259 260// SRSTSCredential - represents an STS credential to be replicated. 261type SRSTSCredential struct { 262 AccessKey string `json:"accessKey"` 263 SecretKey string `json:"secretKey"` 264 SessionToken string `json:"sessionToken"` 265} 266 267// SRIAMItem - represents an IAM object that will be copied to a peer. 268type SRIAMItem struct { 269 Type string `json:"type"` 270 271 // Name and Policy below are used when Type == SRIAMItemPolicy 272 Name string `json:"name"` 273 Policy json.RawMessage `json:"policy"` 274 275 // Used when Type == SRIAMItemPolicyMapping 276 PolicyMapping *SRPolicyMapping `json:"policyMapping"` 277 278 // Used when Type == SRIAMItemSvcAcc 279 SvcAccChange *SRSvcAccChange `json:"serviceAccountChange"` 280 281 // Used when Type = SRIAMItemSTSAcc 282 STSCredential *SRSTSCredential `json:"stsCredential"` 283} 284 285// SRInternalReplicateIAMItem - copies an IAM object to a peer cluster. 286func (adm *AdminClient) SRInternalReplicateIAMItem(ctx context.Context, item SRIAMItem) error { 287 b, err := json.Marshal(item) 288 if err != nil { 289 return err 290 } 291 reqData := requestData{ 292 relPath: adminAPIPrefix + "/site-replication/peer/iam-item", 293 content: b, 294 } 295 296 resp, err := adm.executeMethod(ctx, http.MethodPut, reqData) 297 defer closeResponse(resp) 298 if err != nil { 299 return err 300 } 301 302 if resp.StatusCode != http.StatusOK { 303 return httpRespToErrorResponse(resp) 304 } 305 306 return nil 307} 308 309// SRBucketMeta.Type constants 310const ( 311 SRBucketMetaTypePolicy = "policy" 312 SRBucketMetaTypeTags = "tags" 313 SRBucketMetaTypeObjectLockConfig = "object-lock-config" 314 SRBucketMetaTypeSSEConfig = "sse-config" 315) 316 317// SRBucketMeta - represents a bucket metadata change that will be copied to a 318// peer. 319type SRBucketMeta struct { 320 Type string `json:"type"` 321 Bucket string `json:"bucket"` 322 Policy json.RawMessage `json:"policy,omitempty"` 323 324 // Since tags does not have a json representation, we use its xml byte 325 // representation directly. 326 Tags *string `json:"tags,omitempty"` 327 328 // Since object lock does not have a json representation, we use its xml 329 // byte representation. 330 ObjectLockConfig *string `json:"objectLockConfig,omitempty"` 331 332 // Since SSE config does not have a json representation, we use its xml 333 // byte respresentation. 334 SSEConfig *string `json:"sseConfig,omitempty"` 335} 336 337// SRInternalReplicateBucketMeta - copies a bucket metadata change to a peer 338// cluster. 339func (adm *AdminClient) SRInternalReplicateBucketMeta(ctx context.Context, item SRBucketMeta) error { 340 b, err := json.Marshal(item) 341 if err != nil { 342 return err 343 } 344 reqData := requestData{ 345 relPath: adminAPIPrefix + "/site-replication/peer/bucket-meta", 346 content: b, 347 } 348 349 resp, err := adm.executeMethod(ctx, http.MethodPut, reqData) 350 defer closeResponse(resp) 351 if err != nil { 352 return err 353 } 354 355 if resp.StatusCode != http.StatusOK { 356 return httpRespToErrorResponse(resp) 357 } 358 359 return nil 360} 361 362// IDPSettings contains key IDentity Provider settings to validate that all 363// peers have the same configuration. 364type IDPSettings struct { 365 IsLDAPEnabled bool 366 LDAPUserDNSearchBase string 367 LDAPUserDNSearchFilter string 368 LDAPGroupSearchBase string 369 LDAPGroupSearchFilter string 370} 371 372// SRInternalGetIDPSettings - fetches IDP settings from the server. 373func (adm *AdminClient) SRInternalGetIDPSettings(ctx context.Context) (info IDPSettings, err error) { 374 reqData := requestData{ 375 relPath: adminAPIPrefix + "/site-replication/peer/idp-settings", 376 } 377 378 resp, err := adm.executeMethod(ctx, http.MethodGet, reqData) 379 defer closeResponse(resp) 380 if err != nil { 381 return info, err 382 } 383 384 if resp.StatusCode != http.StatusOK { 385 return info, httpRespToErrorResponse(resp) 386 } 387 388 b, err := ioutil.ReadAll(resp.Body) 389 if err != nil { 390 return info, err 391 } 392 393 err = json.Unmarshal(b, &info) 394 return info, err 395} 396