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