forked from hashicorp/consul
-
Notifications
You must be signed in to change notification settings - Fork 0
/
acl.go
392 lines (342 loc) · 11.4 KB
/
acl.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
package state
import (
"fmt"
"github.com/hashicorp/consul/agent/structs"
"github.com/hashicorp/go-memdb"
)
// aclsTableSchema returns a new table schema used for storing ACL tokens.
func aclsTableSchema() *memdb.TableSchema {
return &memdb.TableSchema{
Name: "acls",
Indexes: map[string]*memdb.IndexSchema{
"id": &memdb.IndexSchema{
Name: "id",
AllowMissing: false,
Unique: true,
Indexer: &memdb.StringFieldIndex{
Field: "ID",
Lowercase: false,
},
},
},
}
}
// aclsBootstrapTableSchema returns a new schema used for tracking the ACL
// bootstrap status for a cluster. This is designed to have only a single
// row, so it has a somewhat unusual no-op indexer.
func aclsBootstrapTableSchema() *memdb.TableSchema {
return &memdb.TableSchema{
Name: "acls-bootstrap",
Indexes: map[string]*memdb.IndexSchema{
"id": &memdb.IndexSchema{
Name: "id",
AllowMissing: true,
Unique: true,
Indexer: &memdb.ConditionalIndex{
Conditional: func(obj interface{}) (bool, error) { return true, nil },
},
},
},
}
}
// ACLs is used to pull all the ACLs from the snapshot.
func (s *Snapshot) ACLs() (memdb.ResultIterator, error) {
iter, err := s.tx.Get("acls", "id")
if err != nil {
return nil, err
}
return iter, nil
}
// ACL is used when restoring from a snapshot. For general inserts, use ACLSet.
func (s *Restore) ACL(acl *structs.ACL) error {
if err := s.tx.Insert("acls", acl); err != nil {
return fmt.Errorf("failed restoring acl: %s", err)
}
if err := indexUpdateMaxTxn(s.tx, acl.ModifyIndex, "acls"); err != nil {
return fmt.Errorf("failed updating index: %s", err)
}
return nil
}
// ACLBootstrap is used to pull the ACL bootstrap info from the snapshot. This
// might return nil, in which case nothing should be saved to the snapshot.
func (s *Snapshot) ACLBootstrap() (*structs.ACLBootstrap, error) {
existing, err := s.tx.First("acls-bootstrap", "id")
if err != nil {
return nil, fmt.Errorf("failed acl bootstrap lookup: %s", err)
}
if existing != nil {
return existing.(*structs.ACLBootstrap), nil
}
return nil, nil
}
// ACLBootstrap is used to restore the ACL bootstrap info from the snapshot.
func (s *Restore) ACLBootstrap(bs *structs.ACLBootstrap) error {
if err := s.tx.Insert("acls-bootstrap", bs); err != nil {
return fmt.Errorf("failed updating acl bootstrap: %v", err)
}
return nil
}
// ACLBootstrapInit is used to perform a scan for existing tokens which will
// decide whether bootstrapping is allowed for a cluster. This is initiated by
// the leader when it steps up, if necessary. This is because the state store
// snapshots would become incompatible with older agents if we added this on
// the fly, so we rely on the leader to determine a safe time to add this so
// we can start tracking whether bootstrap is enabled. This will return an
// error if bootstrap is already initialized.
//
// This returns a boolean indicating if ACL boostrapping is enabled.
func (s *Store) ACLBootstrapInit(idx uint64) (bool, error) {
tx := s.db.Txn(true)
defer tx.Abort()
// Don't allow this to happen more than once.
existing, err := tx.First("acls-bootstrap", "id")
if err != nil {
return false, fmt.Errorf("failed acl bootstrap lookup: %s", err)
}
if existing != nil {
return false, fmt.Errorf("acl bootstrap init already done")
}
// See if there are any management tokens, which means we shouldn't
// allow bootstrapping.
foundMgmt, err := s.aclHasManagementTokensTxn(tx)
if err != nil {
return false, fmt.Errorf("failed checking for management tokens: %v", err)
}
allowBootstrap := !foundMgmt
// Create a new bootstrap record.
bs := structs.ACLBootstrap{
AllowBootstrap: allowBootstrap,
RaftIndex: structs.RaftIndex{
CreateIndex: idx,
ModifyIndex: idx,
},
}
if err := tx.Insert("acls-bootstrap", &bs); err != nil {
return false, fmt.Errorf("failed creating acl bootstrap: %v", err)
}
tx.Commit()
return allowBootstrap, nil
}
// ACLBootstrap is used to perform a one-time ACL bootstrap operation on a
// cluster to get the first management token.
func (s *Store) ACLBootstrap(idx uint64, acl *structs.ACL) error {
tx := s.db.Txn(true)
defer tx.Abort()
// We must have initialized before this will ever be possible.
existing, err := tx.First("acls-bootstrap", "id")
if err != nil {
return fmt.Errorf("failed acl bootstrap lookup: %s", err)
}
if existing == nil {
return structs.ACLBootstrapNotInitializedErr
}
// See if this cluster has already been bootstrapped.
bs := *existing.(*structs.ACLBootstrap)
if !bs.AllowBootstrap {
return structs.ACLBootstrapNotAllowedErr
}
// This should not be required since we keep the boolean above in sync
// with any new management tokens that are added, but since this is such
// a critical thing for correct operation we perform a sanity check.
foundMgmt, err := s.aclHasManagementTokensTxn(tx)
if err != nil {
return fmt.Errorf("failed checking for management tokens: %v", err)
}
if foundMgmt {
return fmt.Errorf("internal error: acl bootstrap enabled but existing management tokens were found")
}
// Bootstrap and then make sure we disable bootstrapping forever. The
// set will also disable this as a side effect but we want to be super
// explicit here.
if err := s.aclSetTxn(tx, idx, acl); err != nil {
return fmt.Errorf("failed inserting bootstrap token: %v", err)
}
if disabled, err := s.aclDisableBootstrapTxn(tx, idx); err != nil || !disabled {
return fmt.Errorf("failed to disable acl bootstrap (disabled=%v): %v", disabled, err)
}
tx.Commit()
return nil
}
// aclDisableBootstrapTxn will disable ACL bootstrapping if the bootstrap init
// has been completed and bootstrap is currently enabled. This will return true
// if bootstrap is disabled.
func (s *Store) aclDisableBootstrapTxn(tx *memdb.Txn, idx uint64) (bool, error) {
// If the init hasn't been done then we aren't tracking this yet, so we
// can bail out. When the init is done for the first time it will scan
// for management tokens to set the initial state correctly.
existing, err := tx.First("acls-bootstrap", "id")
if err != nil {
return false, fmt.Errorf("failed acl bootstrap lookup: %s", err)
}
if existing == nil {
// Not yet init-ed, nothing to do.
return false, nil
}
// See if bootstrap is already disabled, which is the common case, so we
// can avoid a spurious write. We do a copy here in case we need to write
// down below, though.
bs := *existing.(*structs.ACLBootstrap)
if !bs.AllowBootstrap {
return true, nil
}
// Need to disable bootstrap!
bs.AllowBootstrap = false
bs.ModifyIndex = idx
if err := tx.Insert("acls-bootstrap", &bs); err != nil {
return false, fmt.Errorf("failed updating acl bootstrap: %v", err)
}
return true, nil
}
// aclHasManagementTokensTxn returns true if any management tokens are present
// in the state store.
func (s *Store) aclHasManagementTokensTxn(tx *memdb.Txn) (bool, error) {
iter, err := tx.Get("acls", "id")
if err != nil {
return false, fmt.Errorf("failed acl lookup: %s", err)
}
for acl := iter.Next(); acl != nil; acl = iter.Next() {
if acl.(*structs.ACL).Type == structs.ACLTypeManagement {
return true, nil
}
}
return false, nil
}
// ACLGetBootstrap returns the ACL bootstrap status for the cluster, which might
// be nil if it hasn't yet been initialized.
func (s *Store) ACLGetBootstrap() (*structs.ACLBootstrap, error) {
tx := s.db.Txn(false)
defer tx.Abort()
existing, err := tx.First("acls-bootstrap", "id")
if err != nil {
return nil, fmt.Errorf("failed acl bootstrap lookup: %s", err)
}
if existing != nil {
return existing.(*structs.ACLBootstrap), nil
}
return nil, nil
}
// ACLSet is used to insert an ACL rule into the state store.
func (s *Store) ACLSet(idx uint64, acl *structs.ACL) error {
tx := s.db.Txn(true)
defer tx.Abort()
// Call set on the ACL
if err := s.aclSetTxn(tx, idx, acl); err != nil {
return err
}
tx.Commit()
return nil
}
// aclSetTxn is the inner method used to insert an ACL rule with the
// proper indexes into the state store.
func (s *Store) aclSetTxn(tx *memdb.Txn, idx uint64, acl *structs.ACL) error {
// Check that the ID is set
if acl.ID == "" {
return ErrMissingACLID
}
// Check for an existing ACL
existing, err := tx.First("acls", "id", acl.ID)
if err != nil {
return fmt.Errorf("failed acl lookup: %s", err)
}
// Set the indexes
if existing != nil {
acl.CreateIndex = existing.(*structs.ACL).CreateIndex
acl.ModifyIndex = idx
} else {
acl.CreateIndex = idx
acl.ModifyIndex = idx
}
// Insert the ACL
if err := tx.Insert("acls", acl); err != nil {
return fmt.Errorf("failed inserting acl: %s", err)
}
if err := tx.Insert("index", &IndexEntry{"acls", idx}); err != nil {
return fmt.Errorf("failed updating index: %s", err)
}
// If this is a management token, make sure bootstrapping gets disabled.
if acl.Type == structs.ACLTypeManagement {
if _, err := s.aclDisableBootstrapTxn(tx, idx); err != nil {
return fmt.Errorf("failed disabling acl bootstrapping: %v", err)
}
}
return nil
}
// ACLGet is used to look up an existing ACL by ID.
func (s *Store) ACLGet(ws memdb.WatchSet, aclID string) (uint64, *structs.ACL, error) {
tx := s.db.Txn(false)
defer tx.Abort()
// Get the table index.
idx := maxIndexTxn(tx, "acls")
// Query for the existing ACL
watchCh, acl, err := tx.FirstWatch("acls", "id", aclID)
if err != nil {
return 0, nil, fmt.Errorf("failed acl lookup: %s", err)
}
ws.Add(watchCh)
if acl != nil {
return idx, acl.(*structs.ACL), nil
}
return idx, nil, nil
}
// ACLList is used to list out all of the ACLs in the state store.
func (s *Store) ACLList(ws memdb.WatchSet) (uint64, structs.ACLs, error) {
tx := s.db.Txn(false)
defer tx.Abort()
// Get the table index.
idx := maxIndexTxn(tx, "acls")
// Return the ACLs.
acls, err := s.aclListTxn(tx, ws)
if err != nil {
return 0, nil, fmt.Errorf("failed acl lookup: %s", err)
}
return idx, acls, nil
}
// aclListTxn is used to list out all of the ACLs in the state store. This is a
// function vs. a method so it can be called from the snapshotter.
func (s *Store) aclListTxn(tx *memdb.Txn, ws memdb.WatchSet) (structs.ACLs, error) {
// Query all of the ACLs in the state store
iter, err := tx.Get("acls", "id")
if err != nil {
return nil, fmt.Errorf("failed acl lookup: %s", err)
}
ws.Add(iter.WatchCh())
// Go over all of the ACLs and build the response
var result structs.ACLs
for acl := iter.Next(); acl != nil; acl = iter.Next() {
a := acl.(*structs.ACL)
result = append(result, a)
}
return result, nil
}
// ACLDelete is used to remove an existing ACL from the state store. If
// the ACL does not exist this is a no-op and no error is returned.
func (s *Store) ACLDelete(idx uint64, aclID string) error {
tx := s.db.Txn(true)
defer tx.Abort()
// Call the ACL delete
if err := s.aclDeleteTxn(tx, idx, aclID); err != nil {
return err
}
tx.Commit()
return nil
}
// aclDeleteTxn is used to delete an ACL from the state store within
// an existing transaction.
func (s *Store) aclDeleteTxn(tx *memdb.Txn, idx uint64, aclID string) error {
// Look up the existing ACL
acl, err := tx.First("acls", "id", aclID)
if err != nil {
return fmt.Errorf("failed acl lookup: %s", err)
}
if acl == nil {
return nil
}
// Delete the ACL from the state store and update indexes
if err := tx.Delete("acls", acl); err != nil {
return fmt.Errorf("failed deleting acl: %s", err)
}
if err := tx.Insert("index", &IndexEntry{"acls", idx}); err != nil {
return fmt.Errorf("failed updating index: %s", err)
}
return nil
}