github.com/anth0d/nomad@v0.0.0-20221214183521-ae3a0a2cad06/nomad/state/state_store_acl_binding_rule.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 ) 10 11 // UpsertACLBindingRules is used to insert a number of ACL binding rules into 12 // the state store. It uses a single write transaction for efficiency, however, 13 // any error means no entries will be committed. 14 func (s *StateStore) UpsertACLBindingRules( 15 index uint64, bindingRules []*structs.ACLBindingRule, allowMissingAuthMethod bool) error { 16 17 // Grab a write transaction. 18 txn := s.db.WriteTxnMsgT(structs.ACLBindingRulesUpsertRequestType, index) 19 defer txn.Abort() 20 21 // updated tracks whether any inserts have been made. This allows us to 22 // skip updating the index table if we do not need to. 23 var updated bool 24 25 // Iterate the array of rules. In the event of a single error, all inserts 26 // fail via the txn.Abort() defer. 27 for _, rule := range bindingRules { 28 29 bindingRuleInserted, err := s.upsertACLBindingRuleTxn(index, txn, rule, allowMissingAuthMethod) 30 if err != nil { 31 return err 32 } 33 34 // Ensure we track whether any inserts have been made. 35 updated = updated || bindingRuleInserted 36 } 37 38 // If we did not perform any inserts, exit early. 39 if !updated { 40 return nil 41 } 42 43 // Perform the index table update to mark the new insert. 44 if err := txn.Insert(tableIndex, &IndexEntry{TableACLBindingRules, index}); err != nil { 45 return fmt.Errorf("index update failed: %v", err) 46 } 47 48 return txn.Commit() 49 } 50 51 // upsertACLBindingRuleTxn inserts a single ACL binding rule into the state 52 // store using the provided write transaction. It is the responsibility of the 53 // caller to update the index table. 54 func (s *StateStore) upsertACLBindingRuleTxn( 55 index uint64, txn *txn, rule *structs.ACLBindingRule, allowMissingAuthMethod bool) (bool, error) { 56 57 // Ensure the rule hash is not zero to provide defense in depth. This 58 // should be done outside the state store, so we do not spend time here and 59 // thus Raft, when it can be avoided. 60 if len(rule.Hash) == 0 { 61 rule.SetHash() 62 } 63 64 // This validation also happens within the RPC handler, but Raft latency 65 // could mean that by the time the state call is invoked, another Raft 66 // update has the auth method detailed in binding rule. Therefore, check 67 // again while in our write txn. 68 if !allowMissingAuthMethod { 69 method, err := s.GetACLAuthMethodByName(nil, rule.AuthMethod) 70 if err != nil { 71 return false, fmt.Errorf("ACL auth method lookup failed: %v", err) 72 } 73 if method == nil { 74 return false, fmt.Errorf("ACL binding rule insert failed: ACL auth method not found") 75 } 76 } 77 78 // This validation also happens within the RPC handler, but Raft latency 79 // could mean that by the time the state call is invoked, another Raft 80 // update has already written a method with the same name. We therefore 81 // need to check we are not trying to create a rule with an existing ID. 82 existingRaw, err := txn.First(TableACLBindingRules, indexID, rule.ID) 83 if err != nil { 84 return false, fmt.Errorf("ACL binding rule lookup failed: %v", err) 85 } 86 87 var existing *structs.ACLBindingRule 88 if existingRaw != nil { 89 existing = existingRaw.(*structs.ACLBindingRule) 90 } 91 92 // Depending on whether this is an initial create, or an update, we need to 93 // check and set certain parameters. The most important is to ensure any 94 // create index is carried over. 95 if existing != nil { 96 97 // If the rule already exists, check whether the update contains any 98 // difference. If it doesn't, we can avoid a state update as well as 99 // updates to any blocking queries. 100 if existing.Equal(rule) { 101 return false, nil 102 } 103 104 rule.CreateIndex = existing.CreateIndex 105 rule.ModifyIndex = index 106 rule.CreateTime = existing.CreateTime 107 } else { 108 rule.CreateIndex = index 109 rule.ModifyIndex = index 110 } 111 112 // Insert the auth method into the table. 113 if err := txn.Insert(TableACLBindingRules, rule); err != nil { 114 return false, fmt.Errorf("ACL binding rule insert failed: %v", err) 115 } 116 return true, nil 117 } 118 119 // DeleteACLBindingRules is responsible for batch deleting ACL binding rules. 120 // It uses a single write transaction for efficiency, however, any error means 121 // no entries will be committed. An error is produced if a rule is not found 122 // within state which has been passed within the array. 123 func (s *StateStore) DeleteACLBindingRules(index uint64, bindingRuleIDs []string) error { 124 txn := s.db.WriteTxnMsgT(structs.ACLBindingRulesDeleteRequestType, index) 125 defer txn.Abort() 126 127 for _, ruleID := range bindingRuleIDs { 128 if err := s.deleteACLBindingRuleTxn(txn, ruleID); err != nil { 129 return err 130 } 131 } 132 133 // Update the index table to indicate an update has occurred. 134 if err := txn.Insert(tableIndex, &IndexEntry{TableACLBindingRules, index}); err != nil { 135 return fmt.Errorf("index update failed: %v", err) 136 } 137 138 return txn.Commit() 139 } 140 141 // deleteACLBindingRuleTxn deletes a single ACL binding rule from the state 142 // store using the provided write transaction. It is the responsibility of the 143 // caller to update the index table. 144 func (s *StateStore) deleteACLBindingRuleTxn(txn *txn, ruleID string) error { 145 existing, err := txn.First(TableACLBindingRules, indexID, ruleID) 146 if err != nil { 147 return fmt.Errorf("ACL binding rule lookup failed: %v", err) 148 } 149 if existing == nil { 150 return errors.New("ACL binding rule not found") 151 } 152 153 // Delete the existing entry from the table. 154 if err := txn.Delete(TableACLBindingRules, existing); err != nil { 155 return fmt.Errorf("ACL binding rule deletion failed: %v", err) 156 } 157 return nil 158 } 159 160 // GetACLBindingRules returns an iterator that contains all ACL binding rules 161 // stored within state. 162 func (s *StateStore) GetACLBindingRules(ws memdb.WatchSet) (memdb.ResultIterator, error) { 163 txn := s.db.ReadTxn() 164 165 // Walk the entire table to get all ACL binding rules. 166 iter, err := txn.Get(TableACLBindingRules, indexID) 167 if err != nil { 168 return nil, fmt.Errorf("ACL binding rules lookup failed: %v", err) 169 } 170 ws.Add(iter.WatchCh()) 171 172 return iter, nil 173 } 174 175 // GetACLBindingRule returns a single ACL binding rule specified by the input 176 // ID. The binding rule object will be nil, if no matching entry was found; it 177 // is the responsibility of the caller to check for this. 178 func (s *StateStore) GetACLBindingRule(ws memdb.WatchSet, ruleID string) (*structs.ACLBindingRule, error) { 179 txn := s.db.ReadTxn() 180 181 // Perform the ACL binding rule lookup using the ID. 182 watchCh, existing, err := txn.FirstWatch(TableACLBindingRules, indexID, ruleID) 183 if err != nil { 184 return nil, fmt.Errorf("ACL binding rule lookup failed: %v", err) 185 } 186 ws.Add(watchCh) 187 188 if existing != nil { 189 return existing.(*structs.ACLBindingRule), nil 190 } 191 return nil, nil 192 } 193 194 // GetACLBindingRulesByAuthMethod returns an iterator with all binding rules 195 // associated with the named authentication method. 196 func (s *StateStore) GetACLBindingRulesByAuthMethod( 197 ws memdb.WatchSet, authMethod string) (memdb.ResultIterator, error) { 198 199 txn := s.db.ReadTxn() 200 201 iter, err := txn.Get(TableACLBindingRules, indexAuthMethod, authMethod) 202 if err != nil { 203 return nil, fmt.Errorf("ACL binding rule lookup failed: %v", err) 204 } 205 ws.Add(iter.WatchCh()) 206 207 return iter, nil 208 }