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 }