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 }