github.com/anth0d/nomad@v0.0.0-20221214183521-ae3a0a2cad06/nomad/state/state_store_acl.go (about) 1 package state 2 3 import ( 4 "errors" 5 "fmt" 6 7 "github.com/hashicorp/go-memdb" 8 "github.com/hashicorp/nomad/nomad/structs" 9 "golang.org/x/exp/slices" 10 ) 11 12 // ACLTokensByExpired returns an array accessor IDs of expired ACL tokens. 13 // Their expiration is determined against the passed time.Time value. 14 // 15 // The function handles global and local tokens independently as determined by 16 // the global boolean argument. The number of returned IDs can be limited by 17 // the max integer, which is useful to limit the number of tokens we attempt to 18 // delete in a single transaction. 19 func (s *StateStore) ACLTokensByExpired(global bool) (memdb.ResultIterator, error) { 20 tnx := s.db.ReadTxn() 21 22 iter, err := tnx.Get("acl_token", expiresIndexName(global)) 23 if err != nil { 24 return nil, fmt.Errorf("failed acl token listing: %v", err) 25 } 26 return iter, nil 27 } 28 29 // expiresIndexName is a helper function to identify the correct ACL token 30 // table expiry index to use. 31 func expiresIndexName(global bool) string { 32 if global { 33 return indexExpiresGlobal 34 } 35 return indexExpiresLocal 36 } 37 38 // UpsertACLRoles is used to insert a number of ACL roles into the state store. 39 // It uses a single write transaction for efficiency, however, any error means 40 // no entries will be committed. 41 func (s *StateStore) UpsertACLRoles( 42 msgType structs.MessageType, index uint64, roles []*structs.ACLRole, allowMissingPolicies bool) error { 43 44 // Grab a write transaction. 45 txn := s.db.WriteTxnMsgT(msgType, index) 46 defer txn.Abort() 47 48 // updated tracks whether any inserts have been made. This allows us to 49 // skip updating the index table if we do not need to. 50 var updated bool 51 52 // Iterate the array of roles. In the event of a single error, all inserts 53 // fail via the txn.Abort() defer. 54 for _, role := range roles { 55 56 roleUpdated, err := s.upsertACLRoleTxn(index, txn, role, allowMissingPolicies) 57 if err != nil { 58 return err 59 } 60 61 // Ensure we track whether any inserts have been made. 62 updated = updated || roleUpdated 63 } 64 65 // If we did not perform any inserts, exit early. 66 if !updated { 67 return nil 68 } 69 70 // Perform the index table update to mark the new insert. 71 if err := txn.Insert(tableIndex, &IndexEntry{TableACLRoles, index}); err != nil { 72 return fmt.Errorf("index update failed: %v", err) 73 } 74 75 return txn.Commit() 76 } 77 78 // upsertACLRoleTxn inserts a single ACL role into the state store using the 79 // provided write transaction. It is the responsibility of the caller to update 80 // the index table. 81 func (s *StateStore) upsertACLRoleTxn( 82 index uint64, txn *txn, role *structs.ACLRole, allowMissingPolicies bool) (bool, error) { 83 84 // Ensure the role hash is not zero to provide defense in depth. This 85 // should be done outside the state store, so we do not spend time here 86 // and thus Raft, when it, can be avoided. 87 if len(role.Hash) == 0 { 88 role.SetHash() 89 } 90 91 // This validation also happens within the RPC handler, but Raft latency 92 // could mean that by the time the state call is invoked, another Raft 93 // update has deleted policies detailed in role. Therefore, check again 94 // while in our write txn. 95 if !allowMissingPolicies { 96 if err := s.validateACLRolePolicyLinksTxn(txn, role); err != nil { 97 return false, err 98 } 99 } 100 101 // This validation also happens within the RPC handler, but Raft latency 102 // could mean that by the time the state call is invoked, another Raft 103 // update has already written a role with the same name. We therefore need 104 // to check we are not trying to create a role with an existing name. 105 existingRaw, err := txn.First(TableACLRoles, indexName, role.Name) 106 if err != nil { 107 return false, fmt.Errorf("ACL role lookup failed: %v", err) 108 } 109 110 // Track our type asserted role, so we only need to do this once. 111 var existing *structs.ACLRole 112 113 // If we did not find an ACL Role within state with the same name, we need 114 // to check using the ID index as the operator might be performing an 115 // update on the role name. 116 // 117 // If we found an entry using the name index, we need to check that the ID 118 // matches the object within the request. 119 if existingRaw == nil { 120 existingRaw, err = txn.First(TableACLRoles, indexID, role.ID) 121 if err != nil { 122 return false, fmt.Errorf("ACL role lookup failed: %v", err) 123 } 124 if existingRaw != nil { 125 existing = existingRaw.(*structs.ACLRole) 126 } 127 } else { 128 existing = existingRaw.(*structs.ACLRole) 129 if existing.ID != role.ID { 130 return false, fmt.Errorf("ACL role with name %s already exists", role.Name) 131 } 132 } 133 134 // Depending on whether this is an initial create, or an update, we need to 135 // check and set certain parameters. The most important is to ensure any 136 // create index is carried over. 137 if existing != nil { 138 139 // If the role already exists, check whether the update contains any 140 // difference. If it doesn't, we can avoid a state update as wel as 141 // updates to any blocking queries. 142 if existing.Equal(role) { 143 return false, nil 144 } 145 146 role.CreateIndex = existing.CreateIndex 147 role.ModifyIndex = index 148 } else { 149 role.CreateIndex = index 150 role.ModifyIndex = index 151 } 152 153 // Insert the role into the table. 154 if err := txn.Insert(TableACLRoles, role); err != nil { 155 return false, fmt.Errorf("ACL role insert failed: %v", err) 156 } 157 return true, nil 158 } 159 160 // validateACLRolePolicyLinksTxn is the same as ValidateACLRolePolicyLinks but 161 // allows callers to pass their own transaction. 162 func (s *StateStore) validateACLRolePolicyLinksTxn(txn *txn, role *structs.ACLRole) error { 163 for _, policyLink := range role.Policies { 164 _, existing, err := txn.FirstWatch("acl_policy", indexID, policyLink.Name) 165 if err != nil { 166 return fmt.Errorf("ACL policy lookup failed: %v", err) 167 } 168 if existing == nil { 169 return errors.New("ACL policy not found") 170 } 171 } 172 return nil 173 } 174 175 // DeleteACLRolesByID is responsible for batch deleting ACL roles based on 176 // their ID. It uses a single write transaction for efficiency, however, any 177 // error means no entries will be committed. An error is produced if a role is 178 // not found within state which has been passed within the array. 179 func (s *StateStore) DeleteACLRolesByID( 180 msgType structs.MessageType, index uint64, roleIDs []string) error { 181 182 txn := s.db.WriteTxnMsgT(msgType, index) 183 defer txn.Abort() 184 185 for _, roleID := range roleIDs { 186 if err := s.deleteACLRoleByIDTxn(txn, roleID); err != nil { 187 return err 188 } 189 } 190 191 // Update the index table to indicate an update has occurred. 192 if err := txn.Insert(tableIndex, &IndexEntry{TableACLRoles, index}); err != nil { 193 return fmt.Errorf("index update failed: %v", err) 194 } 195 196 return txn.Commit() 197 } 198 199 // deleteACLRoleByIDTxn deletes a single ACL role from the state store using the 200 // provided write transaction. It is the responsibility of the caller to update 201 // the index table. 202 func (s *StateStore) deleteACLRoleByIDTxn(txn *txn, roleID string) error { 203 204 existing, err := txn.First(TableACLRoles, indexID, roleID) 205 if err != nil { 206 return fmt.Errorf("ACL role lookup failed: %v", err) 207 } 208 if existing == nil { 209 return errors.New("ACL role not found") 210 } 211 212 // Delete the existing entry from the table. 213 if err := txn.Delete(TableACLRoles, existing); err != nil { 214 return fmt.Errorf("ACL role deletion failed: %v", err) 215 } 216 return nil 217 } 218 219 // GetACLRoles returns an iterator that contains all ACL roles stored within 220 // state. 221 func (s *StateStore) GetACLRoles(ws memdb.WatchSet) (memdb.ResultIterator, error) { 222 txn := s.db.ReadTxn() 223 224 // Walk the entire table to get all ACL roles. 225 iter, err := txn.Get(TableACLRoles, indexID) 226 if err != nil { 227 return nil, fmt.Errorf("ACL role lookup failed: %v", err) 228 } 229 ws.Add(iter.WatchCh()) 230 231 return iter, nil 232 } 233 234 // GetACLRoleByID returns a single ACL role specified by the input ID. The role 235 // object will be nil, if no matching entry was found; it is the responsibility 236 // of the caller to check for this. 237 func (s *StateStore) GetACLRoleByID(ws memdb.WatchSet, roleID string) (*structs.ACLRole, error) { 238 txn := s.db.ReadTxn() 239 return s.getACLRoleByIDTxn(txn, ws, roleID) 240 } 241 242 // getACLRoleByIDTxn allows callers to pass a read transaction in order to read 243 // a single ACL role specified by the input ID. The role object will be nil, if 244 // no matching entry was found; it is the responsibility of the caller to check 245 // for this. 246 func (s *StateStore) getACLRoleByIDTxn(txn ReadTxn, ws memdb.WatchSet, roleID string) (*structs.ACLRole, error) { 247 248 // Perform the ACL role lookup using the "id" index. 249 watchCh, existing, err := txn.FirstWatch(TableACLRoles, indexID, roleID) 250 if err != nil { 251 return nil, fmt.Errorf("ACL role lookup failed: %v", err) 252 } 253 ws.Add(watchCh) 254 255 if existing != nil { 256 return existing.(*structs.ACLRole), nil 257 } 258 return nil, nil 259 } 260 261 // GetACLRoleByName returns a single ACL role specified by the input name. The 262 // role object will be nil, if no matching entry was found; it is the 263 // responsibility of the caller to check for this. 264 func (s *StateStore) GetACLRoleByName(ws memdb.WatchSet, roleName string) (*structs.ACLRole, error) { 265 txn := s.db.ReadTxn() 266 267 // Perform the ACL role lookup using the "name" index. 268 watchCh, existing, err := txn.FirstWatch(TableACLRoles, indexName, roleName) 269 if err != nil { 270 return nil, fmt.Errorf("ACL role lookup failed: %v", err) 271 } 272 ws.Add(watchCh) 273 274 if existing != nil { 275 return existing.(*structs.ACLRole), nil 276 } 277 return nil, nil 278 } 279 280 // GetACLRoleByIDPrefix is used to lookup ACL policies using a prefix to match 281 // on the ID. 282 func (s *StateStore) GetACLRoleByIDPrefix(ws memdb.WatchSet, idPrefix string) (memdb.ResultIterator, error) { 283 txn := s.db.ReadTxn() 284 285 iter, err := txn.Get(TableACLRoles, indexID+"_prefix", idPrefix) 286 if err != nil { 287 return nil, fmt.Errorf("ACL role lookup failed: %v", err) 288 } 289 ws.Add(iter.WatchCh()) 290 291 return iter, nil 292 } 293 294 // fixTokenRoleLinks is a state helper that ensures the returned ACL token has 295 // an accurate representation of ACL role links. The role links could have 296 // become stale when a linked role was deleted or renamed. This will correct 297 // them and generates a newly allocated token only when fixes are needed. If 298 // the role links are still accurate, we just return the original token. 299 func (s *StateStore) fixTokenRoleLinks(txn ReadTxn, original *structs.ACLToken) (*structs.ACLToken, error) { 300 301 // Track whether we have made an initial copy to ensure we are not 302 // operating on the token directly from state. 303 copied := false 304 305 token := original 306 307 // copyTokenFn is a helper function which copies the ACL token along with 308 // a certain number of ACL role links. 309 copyTokenFn := func(t *structs.ACLToken, numLinks int) *structs.ACLToken { 310 clone := t.Copy() 311 clone.Roles = slices.Clone(t.Roles[:numLinks]) 312 return clone 313 } 314 315 for linkIndex, link := range original.Roles { 316 317 // This should never happen, but guard against it anyway, so we log an 318 // error rather than panic. 319 if link.ID == "" { 320 return nil, errors.New("detected corrupted token within the state store: missing role link ID") 321 } 322 323 role, err := s.getACLRoleByIDTxn(txn, nil, link.ID) 324 if err != nil { 325 return nil, err 326 } 327 328 if role == nil { 329 if !copied { 330 // clone the token as we cannot touch the original 331 token = copyTokenFn(original, linkIndex) 332 copied = true 333 } 334 // if already owned then we just don't append it. 335 } else if role.Name != link.Name { 336 if !copied { 337 token = copyTokenFn(original, linkIndex) 338 copied = true 339 } 340 341 // append the corrected policy 342 token.Roles = append(token.Roles, &structs.ACLTokenRoleLink{ID: link.ID, Name: role.Name}) 343 344 } else if copied { 345 token.Roles = append(token.Roles, link) 346 } 347 } 348 349 return token, nil 350 }