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  }