github.com/hernad/nomad@v1.6.112/nomad/state/state_store_acl_sso.go (about)

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