1package api
2
3import (
4	"fmt"
5	"io"
6	"io/ioutil"
7	"time"
8)
9
10const (
11	// ACLClientType is the client type token
12	ACLClientType = "client"
13
14	// ACLManagementType is the management type token
15	ACLManagementType = "management"
16)
17
18type ACLTokenPolicyLink struct {
19	ID   string
20	Name string
21}
22
23// ACLToken represents an ACL Token
24type ACLToken struct {
25	CreateIndex uint64
26	ModifyIndex uint64
27	AccessorID  string
28	SecretID    string
29	Description string
30	Policies    []*ACLTokenPolicyLink
31	Local       bool
32	CreateTime  time.Time `json:",omitempty"`
33	Hash        []byte    `json:",omitempty"`
34
35	// DEPRECATED (ACL-Legacy-Compat)
36	// Rules will only be present for legacy tokens returned via the new APIs
37	Rules string `json:",omitempty"`
38}
39
40type ACLTokenListEntry struct {
41	CreateIndex uint64
42	ModifyIndex uint64
43	AccessorID  string
44	Description string
45	Policies    []*ACLTokenPolicyLink
46	Local       bool
47	CreateTime  time.Time
48	Hash        []byte
49	Legacy      bool
50}
51
52// ACLEntry is used to represent a legacy ACL token
53// The legacy tokens are deprecated.
54type ACLEntry struct {
55	CreateIndex uint64
56	ModifyIndex uint64
57	ID          string
58	Name        string
59	Type        string
60	Rules       string
61}
62
63// ACLReplicationStatus is used to represent the status of ACL replication.
64type ACLReplicationStatus struct {
65	Enabled              bool
66	Running              bool
67	SourceDatacenter     string
68	ReplicationType      string
69	ReplicatedIndex      uint64
70	ReplicatedTokenIndex uint64
71	LastSuccess          time.Time
72	LastError            time.Time
73}
74
75// ACLPolicy represents an ACL Policy.
76type ACLPolicy struct {
77	ID          string
78	Name        string
79	Description string
80	Rules       string
81	Datacenters []string
82	Hash        []byte
83	CreateIndex uint64
84	ModifyIndex uint64
85}
86
87type ACLPolicyListEntry struct {
88	ID          string
89	Name        string
90	Description string
91	Datacenters []string
92	Hash        []byte
93	CreateIndex uint64
94	ModifyIndex uint64
95}
96
97// ACL can be used to query the ACL endpoints
98type ACL struct {
99	c *Client
100}
101
102// ACL returns a handle to the ACL endpoints
103func (c *Client) ACL() *ACL {
104	return &ACL{c}
105}
106
107// Bootstrap is used to perform a one-time ACL bootstrap operation on a cluster
108// to get the first management token.
109func (a *ACL) Bootstrap() (*ACLToken, *WriteMeta, error) {
110	r := a.c.newRequest("PUT", "/v1/acl/bootstrap")
111	rtt, resp, err := requireOK(a.c.doRequest(r))
112	if err != nil {
113		return nil, nil, err
114	}
115	defer resp.Body.Close()
116
117	wm := &WriteMeta{RequestTime: rtt}
118	var out ACLToken
119	if err := decodeBody(resp, &out); err != nil {
120		return nil, nil, err
121	}
122	return &out, wm, nil
123}
124
125// Create is used to generate a new token with the given parameters
126//
127// Deprecated: Use TokenCreate instead.
128func (a *ACL) Create(acl *ACLEntry, q *WriteOptions) (string, *WriteMeta, error) {
129	r := a.c.newRequest("PUT", "/v1/acl/create")
130	r.setWriteOptions(q)
131	r.obj = acl
132	rtt, resp, err := requireOK(a.c.doRequest(r))
133	if err != nil {
134		return "", nil, err
135	}
136	defer resp.Body.Close()
137
138	wm := &WriteMeta{RequestTime: rtt}
139	var out struct{ ID string }
140	if err := decodeBody(resp, &out); err != nil {
141		return "", nil, err
142	}
143	return out.ID, wm, nil
144}
145
146// Update is used to update the rules of an existing token
147//
148// Deprecated: Use TokenUpdate instead.
149func (a *ACL) Update(acl *ACLEntry, q *WriteOptions) (*WriteMeta, error) {
150	r := a.c.newRequest("PUT", "/v1/acl/update")
151	r.setWriteOptions(q)
152	r.obj = acl
153	rtt, resp, err := requireOK(a.c.doRequest(r))
154	if err != nil {
155		return nil, err
156	}
157	defer resp.Body.Close()
158
159	wm := &WriteMeta{RequestTime: rtt}
160	return wm, nil
161}
162
163// Destroy is used to destroy a given ACL token ID
164//
165// Deprecated: Use TokenDelete instead.
166func (a *ACL) Destroy(id string, q *WriteOptions) (*WriteMeta, error) {
167	r := a.c.newRequest("PUT", "/v1/acl/destroy/"+id)
168	r.setWriteOptions(q)
169	rtt, resp, err := requireOK(a.c.doRequest(r))
170	if err != nil {
171		return nil, err
172	}
173	resp.Body.Close()
174
175	wm := &WriteMeta{RequestTime: rtt}
176	return wm, nil
177}
178
179// Clone is used to return a new token cloned from an existing one
180//
181// Deprecated: Use TokenClone instead.
182func (a *ACL) Clone(id string, q *WriteOptions) (string, *WriteMeta, error) {
183	r := a.c.newRequest("PUT", "/v1/acl/clone/"+id)
184	r.setWriteOptions(q)
185	rtt, resp, err := requireOK(a.c.doRequest(r))
186	if err != nil {
187		return "", nil, err
188	}
189	defer resp.Body.Close()
190
191	wm := &WriteMeta{RequestTime: rtt}
192	var out struct{ ID string }
193	if err := decodeBody(resp, &out); err != nil {
194		return "", nil, err
195	}
196	return out.ID, wm, nil
197}
198
199// Info is used to query for information about an ACL token
200//
201// Deprecated: Use TokenRead instead.
202func (a *ACL) Info(id string, q *QueryOptions) (*ACLEntry, *QueryMeta, error) {
203	r := a.c.newRequest("GET", "/v1/acl/info/"+id)
204	r.setQueryOptions(q)
205	rtt, resp, err := requireOK(a.c.doRequest(r))
206	if err != nil {
207		return nil, nil, err
208	}
209	defer resp.Body.Close()
210
211	qm := &QueryMeta{}
212	parseQueryMeta(resp, qm)
213	qm.RequestTime = rtt
214
215	var entries []*ACLEntry
216	if err := decodeBody(resp, &entries); err != nil {
217		return nil, nil, err
218	}
219	if len(entries) > 0 {
220		return entries[0], qm, nil
221	}
222	return nil, qm, nil
223}
224
225// List is used to get all the ACL tokens
226//
227// Deprecated: Use TokenList instead.
228func (a *ACL) List(q *QueryOptions) ([]*ACLEntry, *QueryMeta, error) {
229	r := a.c.newRequest("GET", "/v1/acl/list")
230	r.setQueryOptions(q)
231	rtt, resp, err := requireOK(a.c.doRequest(r))
232	if err != nil {
233		return nil, nil, err
234	}
235	defer resp.Body.Close()
236
237	qm := &QueryMeta{}
238	parseQueryMeta(resp, qm)
239	qm.RequestTime = rtt
240
241	var entries []*ACLEntry
242	if err := decodeBody(resp, &entries); err != nil {
243		return nil, nil, err
244	}
245	return entries, qm, nil
246}
247
248// Replication returns the status of the ACL replication process in the datacenter
249func (a *ACL) Replication(q *QueryOptions) (*ACLReplicationStatus, *QueryMeta, error) {
250	r := a.c.newRequest("GET", "/v1/acl/replication")
251	r.setQueryOptions(q)
252	rtt, resp, err := requireOK(a.c.doRequest(r))
253	if err != nil {
254		return nil, nil, err
255	}
256	defer resp.Body.Close()
257
258	qm := &QueryMeta{}
259	parseQueryMeta(resp, qm)
260	qm.RequestTime = rtt
261
262	var entries *ACLReplicationStatus
263	if err := decodeBody(resp, &entries); err != nil {
264		return nil, nil, err
265	}
266	return entries, qm, nil
267}
268
269// TokenCreate creates a new ACL token. It requires that the AccessorID and SecretID fields
270// of the ACLToken structure to be empty as these will be filled in by Consul.
271func (a *ACL) TokenCreate(token *ACLToken, q *WriteOptions) (*ACLToken, *WriteMeta, error) {
272	if token.AccessorID != "" {
273		return nil, nil, fmt.Errorf("Cannot specify an AccessorID in Token Creation")
274	}
275
276	if token.SecretID != "" {
277		return nil, nil, fmt.Errorf("Cannot specify a SecretID in Token Creation")
278	}
279
280	r := a.c.newRequest("PUT", "/v1/acl/token")
281	r.setWriteOptions(q)
282	r.obj = token
283	rtt, resp, err := requireOK(a.c.doRequest(r))
284	if err != nil {
285		return nil, nil, err
286	}
287	defer resp.Body.Close()
288
289	wm := &WriteMeta{RequestTime: rtt}
290	var out ACLToken
291	if err := decodeBody(resp, &out); err != nil {
292		return nil, nil, err
293	}
294
295	return &out, wm, nil
296}
297
298// TokenUpdate updates a token in place without modifying its AccessorID or SecretID. A valid
299// AccessorID must be set in the ACLToken structure passed to this function but the SecretID may
300// be omitted and will be filled in by Consul with its existing value.
301func (a *ACL) TokenUpdate(token *ACLToken, q *WriteOptions) (*ACLToken, *WriteMeta, error) {
302	if token.AccessorID == "" {
303		return nil, nil, fmt.Errorf("Must specify an AccessorID for Token Updating")
304	}
305	r := a.c.newRequest("PUT", "/v1/acl/token/"+token.AccessorID)
306	r.setWriteOptions(q)
307	r.obj = token
308	rtt, resp, err := requireOK(a.c.doRequest(r))
309	if err != nil {
310		return nil, nil, err
311	}
312	defer resp.Body.Close()
313
314	wm := &WriteMeta{RequestTime: rtt}
315	var out ACLToken
316	if err := decodeBody(resp, &out); err != nil {
317		return nil, nil, err
318	}
319
320	return &out, wm, nil
321}
322
323// TokenClone will create a new token with the same policies and locality as the original
324// token but will have its own auto-generated AccessorID and SecretID as well having the
325// description passed to this function. The tokenID parameter must be a valid Accessor ID
326// of an existing token.
327func (a *ACL) TokenClone(tokenID string, description string, q *WriteOptions) (*ACLToken, *WriteMeta, error) {
328	if tokenID == "" {
329		return nil, nil, fmt.Errorf("Must specify a tokenID for Token Cloning")
330	}
331
332	r := a.c.newRequest("PUT", "/v1/acl/token/"+tokenID+"/clone")
333	r.setWriteOptions(q)
334	r.obj = struct{ Description string }{description}
335	rtt, resp, err := requireOK(a.c.doRequest(r))
336	if err != nil {
337		return nil, nil, err
338	}
339	defer resp.Body.Close()
340
341	wm := &WriteMeta{RequestTime: rtt}
342	var out ACLToken
343	if err := decodeBody(resp, &out); err != nil {
344		return nil, nil, err
345	}
346
347	return &out, wm, nil
348}
349
350// TokenDelete removes a single ACL token. The tokenID parameter must be a valid
351// Accessor ID of an existing token.
352func (a *ACL) TokenDelete(tokenID string, q *WriteOptions) (*WriteMeta, error) {
353	r := a.c.newRequest("DELETE", "/v1/acl/token/"+tokenID)
354	r.setWriteOptions(q)
355	rtt, resp, err := requireOK(a.c.doRequest(r))
356	if err != nil {
357		return nil, err
358	}
359	resp.Body.Close()
360
361	wm := &WriteMeta{RequestTime: rtt}
362	return wm, nil
363}
364
365// TokenRead retrieves the full token details. The tokenID parameter must be a valid
366// Accessor ID of an existing token.
367func (a *ACL) TokenRead(tokenID string, q *QueryOptions) (*ACLToken, *QueryMeta, error) {
368	r := a.c.newRequest("GET", "/v1/acl/token/"+tokenID)
369	r.setQueryOptions(q)
370	rtt, resp, err := requireOK(a.c.doRequest(r))
371	if err != nil {
372		return nil, nil, err
373	}
374	defer resp.Body.Close()
375
376	qm := &QueryMeta{}
377	parseQueryMeta(resp, qm)
378	qm.RequestTime = rtt
379
380	var out ACLToken
381	if err := decodeBody(resp, &out); err != nil {
382		return nil, nil, err
383	}
384
385	return &out, qm, nil
386}
387
388// TokenReadSelf retrieves the full token details of the token currently
389// assigned to the API Client. In this manner its possible to read a token
390// by its Secret ID.
391func (a *ACL) TokenReadSelf(q *QueryOptions) (*ACLToken, *QueryMeta, error) {
392	r := a.c.newRequest("GET", "/v1/acl/token/self")
393	r.setQueryOptions(q)
394	rtt, resp, err := requireOK(a.c.doRequest(r))
395	if err != nil {
396		return nil, nil, err
397	}
398	defer resp.Body.Close()
399
400	qm := &QueryMeta{}
401	parseQueryMeta(resp, qm)
402	qm.RequestTime = rtt
403
404	var out ACLToken
405	if err := decodeBody(resp, &out); err != nil {
406		return nil, nil, err
407	}
408
409	return &out, qm, nil
410}
411
412// TokenList lists all tokens. The listing does not contain any SecretIDs as those
413// may only be retrieved by a call to TokenRead.
414func (a *ACL) TokenList(q *QueryOptions) ([]*ACLTokenListEntry, *QueryMeta, error) {
415	r := a.c.newRequest("GET", "/v1/acl/tokens")
416	r.setQueryOptions(q)
417	rtt, resp, err := requireOK(a.c.doRequest(r))
418	if err != nil {
419		return nil, nil, err
420	}
421	defer resp.Body.Close()
422
423	qm := &QueryMeta{}
424	parseQueryMeta(resp, qm)
425	qm.RequestTime = rtt
426
427	var entries []*ACLTokenListEntry
428	if err := decodeBody(resp, &entries); err != nil {
429		return nil, nil, err
430	}
431	return entries, qm, nil
432}
433
434// PolicyCreate will create a new policy. It is not allowed for the policy parameters
435// ID field to be set as this will be generated by Consul while processing the request.
436func (a *ACL) PolicyCreate(policy *ACLPolicy, q *WriteOptions) (*ACLPolicy, *WriteMeta, error) {
437	if policy.ID != "" {
438		return nil, nil, fmt.Errorf("Cannot specify an ID in Policy Creation")
439	}
440
441	r := a.c.newRequest("PUT", "/v1/acl/policy")
442	r.setWriteOptions(q)
443	r.obj = policy
444	rtt, resp, err := requireOK(a.c.doRequest(r))
445	if err != nil {
446		return nil, nil, err
447	}
448	defer resp.Body.Close()
449
450	wm := &WriteMeta{RequestTime: rtt}
451	var out ACLPolicy
452	if err := decodeBody(resp, &out); err != nil {
453		return nil, nil, err
454	}
455
456	return &out, wm, nil
457}
458
459// PolicyUpdate updates a policy. The ID field of the policy parameter must be set to an
460// existing policy ID
461func (a *ACL) PolicyUpdate(policy *ACLPolicy, q *WriteOptions) (*ACLPolicy, *WriteMeta, error) {
462	if policy.ID == "" {
463		return nil, nil, fmt.Errorf("Must specify an ID in Policy Creation")
464	}
465
466	r := a.c.newRequest("PUT", "/v1/acl/policy/"+policy.ID)
467	r.setWriteOptions(q)
468	r.obj = policy
469	rtt, resp, err := requireOK(a.c.doRequest(r))
470	if err != nil {
471		return nil, nil, err
472	}
473	defer resp.Body.Close()
474
475	wm := &WriteMeta{RequestTime: rtt}
476	var out ACLPolicy
477	if err := decodeBody(resp, &out); err != nil {
478		return nil, nil, err
479	}
480
481	return &out, wm, nil
482}
483
484// PolicyDelete deletes a policy given its ID.
485func (a *ACL) PolicyDelete(policyID string, q *WriteOptions) (*WriteMeta, error) {
486	r := a.c.newRequest("DELETE", "/v1/acl/policy/"+policyID)
487	r.setWriteOptions(q)
488	rtt, resp, err := requireOK(a.c.doRequest(r))
489	if err != nil {
490		return nil, err
491	}
492	resp.Body.Close()
493
494	wm := &WriteMeta{RequestTime: rtt}
495	return wm, nil
496}
497
498// PolicyRead retrieves the policy details including the rule set.
499func (a *ACL) PolicyRead(policyID string, q *QueryOptions) (*ACLPolicy, *QueryMeta, error) {
500	r := a.c.newRequest("GET", "/v1/acl/policy/"+policyID)
501	r.setQueryOptions(q)
502	rtt, resp, err := requireOK(a.c.doRequest(r))
503	if err != nil {
504		return nil, nil, err
505	}
506	defer resp.Body.Close()
507
508	qm := &QueryMeta{}
509	parseQueryMeta(resp, qm)
510	qm.RequestTime = rtt
511
512	var out ACLPolicy
513	if err := decodeBody(resp, &out); err != nil {
514		return nil, nil, err
515	}
516
517	return &out, qm, nil
518}
519
520// PolicyList retrieves a listing of all policies. The listing does not include the
521// rules for any policy as those should be retrieved by subsequent calls to PolicyRead.
522func (a *ACL) PolicyList(q *QueryOptions) ([]*ACLPolicyListEntry, *QueryMeta, error) {
523	r := a.c.newRequest("GET", "/v1/acl/policies")
524	r.setQueryOptions(q)
525	rtt, resp, err := requireOK(a.c.doRequest(r))
526	if err != nil {
527		return nil, nil, err
528	}
529	defer resp.Body.Close()
530
531	qm := &QueryMeta{}
532	parseQueryMeta(resp, qm)
533	qm.RequestTime = rtt
534
535	var entries []*ACLPolicyListEntry
536	if err := decodeBody(resp, &entries); err != nil {
537		return nil, nil, err
538	}
539	return entries, qm, nil
540}
541
542// RulesTranslate translates the legacy rule syntax into the current syntax.
543//
544// Deprecated: Support for the legacy syntax translation will be removed
545// when legacy ACL support is removed.
546func (a *ACL) RulesTranslate(rules io.Reader) (string, error) {
547	r := a.c.newRequest("POST", "/v1/acl/rules/translate")
548	r.body = rules
549	rtt, resp, err := requireOK(a.c.doRequest(r))
550	if err != nil {
551		return "", err
552	}
553	defer resp.Body.Close()
554	qm := &QueryMeta{}
555	parseQueryMeta(resp, qm)
556	qm.RequestTime = rtt
557
558	ruleBytes, err := ioutil.ReadAll(resp.Body)
559	if err != nil {
560		return "", fmt.Errorf("Failed to read translated rule body: %v", err)
561	}
562
563	return string(ruleBytes), nil
564}
565
566// RulesTranslateToken translates the rules associated with the legacy syntax
567// into the current syntax and returns the results.
568//
569// Deprecated: Support for the legacy syntax translation will be removed
570// when legacy ACL support is removed.
571func (a *ACL) RulesTranslateToken(tokenID string) (string, error) {
572	r := a.c.newRequest("GET", "/v1/acl/rules/translate/"+tokenID)
573	rtt, resp, err := requireOK(a.c.doRequest(r))
574	if err != nil {
575		return "", err
576	}
577	defer resp.Body.Close()
578	qm := &QueryMeta{}
579	parseQueryMeta(resp, qm)
580	qm.RequestTime = rtt
581
582	ruleBytes, err := ioutil.ReadAll(resp.Body)
583	if err != nil {
584		return "", fmt.Errorf("Failed to read translated rule body: %v", err)
585	}
586
587	return string(ruleBytes), nil
588}
589