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  }