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