github.imxd.top/hashicorp/consul@v1.4.5/agent/consul/state/intention.go (about) 1 package state 2 3 import ( 4 "fmt" 5 "sort" 6 7 "github.com/hashicorp/consul/agent/structs" 8 "github.com/hashicorp/go-memdb" 9 ) 10 11 const ( 12 intentionsTableName = "connect-intentions" 13 ) 14 15 // intentionsTableSchema returns a new table schema used for storing 16 // intentions for Connect. 17 func intentionsTableSchema() *memdb.TableSchema { 18 return &memdb.TableSchema{ 19 Name: intentionsTableName, 20 Indexes: map[string]*memdb.IndexSchema{ 21 "id": &memdb.IndexSchema{ 22 Name: "id", 23 AllowMissing: false, 24 Unique: true, 25 Indexer: &memdb.UUIDFieldIndex{ 26 Field: "ID", 27 }, 28 }, 29 "destination": &memdb.IndexSchema{ 30 Name: "destination", 31 AllowMissing: true, 32 // This index is not unique since we need uniqueness across the whole 33 // 4-tuple. 34 Unique: false, 35 Indexer: &memdb.CompoundIndex{ 36 Indexes: []memdb.Indexer{ 37 &memdb.StringFieldIndex{ 38 Field: "DestinationNS", 39 Lowercase: true, 40 }, 41 &memdb.StringFieldIndex{ 42 Field: "DestinationName", 43 Lowercase: true, 44 }, 45 }, 46 }, 47 }, 48 "source": &memdb.IndexSchema{ 49 Name: "source", 50 AllowMissing: true, 51 // This index is not unique since we need uniqueness across the whole 52 // 4-tuple. 53 Unique: false, 54 Indexer: &memdb.CompoundIndex{ 55 Indexes: []memdb.Indexer{ 56 &memdb.StringFieldIndex{ 57 Field: "SourceNS", 58 Lowercase: true, 59 }, 60 &memdb.StringFieldIndex{ 61 Field: "SourceName", 62 Lowercase: true, 63 }, 64 }, 65 }, 66 }, 67 "source_destination": &memdb.IndexSchema{ 68 Name: "source_destination", 69 AllowMissing: true, 70 Unique: true, 71 Indexer: &memdb.CompoundIndex{ 72 Indexes: []memdb.Indexer{ 73 &memdb.StringFieldIndex{ 74 Field: "SourceNS", 75 Lowercase: true, 76 }, 77 &memdb.StringFieldIndex{ 78 Field: "SourceName", 79 Lowercase: true, 80 }, 81 &memdb.StringFieldIndex{ 82 Field: "DestinationNS", 83 Lowercase: true, 84 }, 85 &memdb.StringFieldIndex{ 86 Field: "DestinationName", 87 Lowercase: true, 88 }, 89 }, 90 }, 91 }, 92 }, 93 } 94 } 95 96 func init() { 97 registerSchema(intentionsTableSchema) 98 } 99 100 // Intentions is used to pull all the intentions from the snapshot. 101 func (s *Snapshot) Intentions() (structs.Intentions, error) { 102 ixns, err := s.tx.Get(intentionsTableName, "id") 103 if err != nil { 104 return nil, err 105 } 106 107 var ret structs.Intentions 108 for wrapped := ixns.Next(); wrapped != nil; wrapped = ixns.Next() { 109 ret = append(ret, wrapped.(*structs.Intention)) 110 } 111 112 return ret, nil 113 } 114 115 // Intention is used when restoring from a snapshot. 116 func (s *Restore) Intention(ixn *structs.Intention) error { 117 // Insert the intention 118 if err := s.tx.Insert(intentionsTableName, ixn); err != nil { 119 return fmt.Errorf("failed restoring intention: %s", err) 120 } 121 if err := indexUpdateMaxTxn(s.tx, ixn.ModifyIndex, intentionsTableName); err != nil { 122 return fmt.Errorf("failed updating index: %s", err) 123 } 124 125 return nil 126 } 127 128 // Intentions returns the list of all intentions. 129 func (s *Store) Intentions(ws memdb.WatchSet) (uint64, structs.Intentions, error) { 130 tx := s.db.Txn(false) 131 defer tx.Abort() 132 133 // Get the index 134 idx := maxIndexTxn(tx, intentionsTableName) 135 if idx < 1 { 136 idx = 1 137 } 138 139 // Get all intentions 140 iter, err := tx.Get(intentionsTableName, "id") 141 if err != nil { 142 return 0, nil, fmt.Errorf("failed intention lookup: %s", err) 143 } 144 ws.Add(iter.WatchCh()) 145 146 var results structs.Intentions 147 for ixn := iter.Next(); ixn != nil; ixn = iter.Next() { 148 results = append(results, ixn.(*structs.Intention)) 149 } 150 151 // Sort by precedence just because that's nicer and probably what most clients 152 // want for presentation. 153 sort.Sort(structs.IntentionPrecedenceSorter(results)) 154 155 return idx, results, nil 156 } 157 158 // IntentionSet creates or updates an intention. 159 func (s *Store) IntentionSet(idx uint64, ixn *structs.Intention) error { 160 tx := s.db.Txn(true) 161 defer tx.Abort() 162 163 if err := s.intentionSetTxn(tx, idx, ixn); err != nil { 164 return err 165 } 166 167 tx.Commit() 168 return nil 169 } 170 171 // intentionSetTxn is the inner method used to insert an intention with 172 // the proper indexes into the state store. 173 func (s *Store) intentionSetTxn(tx *memdb.Txn, idx uint64, ixn *structs.Intention) error { 174 // ID is required 175 if ixn.ID == "" { 176 return ErrMissingIntentionID 177 } 178 179 // Ensure Precedence is populated correctly on "write" 180 ixn.UpdatePrecedence() 181 182 // Check for an existing intention 183 existing, err := tx.First(intentionsTableName, "id", ixn.ID) 184 if err != nil { 185 return fmt.Errorf("failed intention lookup: %s", err) 186 } 187 if existing != nil { 188 oldIxn := existing.(*structs.Intention) 189 ixn.CreateIndex = oldIxn.CreateIndex 190 ixn.CreatedAt = oldIxn.CreatedAt 191 } else { 192 ixn.CreateIndex = idx 193 } 194 ixn.ModifyIndex = idx 195 196 // Check for duplicates on the 4-tuple. 197 duplicate, err := tx.First(intentionsTableName, "source_destination", 198 ixn.SourceNS, ixn.SourceName, ixn.DestinationNS, ixn.DestinationName) 199 if err != nil { 200 return fmt.Errorf("failed intention lookup: %s", err) 201 } 202 if duplicate != nil { 203 dupIxn := duplicate.(*structs.Intention) 204 // Same ID is OK - this is an update 205 if dupIxn.ID != ixn.ID { 206 return fmt.Errorf("duplicate intention found: %s", dupIxn.String()) 207 } 208 } 209 210 // We always force meta to be non-nil so that we its an empty map. 211 // This makes it easy for API responses to not nil-check this everywhere. 212 if ixn.Meta == nil { 213 ixn.Meta = make(map[string]string) 214 } 215 216 // Insert 217 if err := tx.Insert(intentionsTableName, ixn); err != nil { 218 return err 219 } 220 if err := tx.Insert("index", &IndexEntry{intentionsTableName, idx}); err != nil { 221 return fmt.Errorf("failed updating index: %s", err) 222 } 223 224 return nil 225 } 226 227 // IntentionGet returns the given intention by ID. 228 func (s *Store) IntentionGet(ws memdb.WatchSet, id string) (uint64, *structs.Intention, error) { 229 tx := s.db.Txn(false) 230 defer tx.Abort() 231 232 // Get the table index. 233 idx := maxIndexTxn(tx, intentionsTableName) 234 if idx < 1 { 235 idx = 1 236 } 237 238 // Look up by its ID. 239 watchCh, intention, err := tx.FirstWatch(intentionsTableName, "id", id) 240 if err != nil { 241 return 0, nil, fmt.Errorf("failed intention lookup: %s", err) 242 } 243 ws.Add(watchCh) 244 245 // Convert the interface{} if it is non-nil 246 var result *structs.Intention 247 if intention != nil { 248 result = intention.(*structs.Intention) 249 } 250 251 return idx, result, nil 252 } 253 254 // IntentionDelete deletes the given intention by ID. 255 func (s *Store) IntentionDelete(idx uint64, id string) error { 256 tx := s.db.Txn(true) 257 defer tx.Abort() 258 259 if err := s.intentionDeleteTxn(tx, idx, id); err != nil { 260 return fmt.Errorf("failed intention delete: %s", err) 261 } 262 263 tx.Commit() 264 return nil 265 } 266 267 // intentionDeleteTxn is the inner method used to delete a intention 268 // with the proper indexes into the state store. 269 func (s *Store) intentionDeleteTxn(tx *memdb.Txn, idx uint64, queryID string) error { 270 // Pull the query. 271 wrapped, err := tx.First(intentionsTableName, "id", queryID) 272 if err != nil { 273 return fmt.Errorf("failed intention lookup: %s", err) 274 } 275 if wrapped == nil { 276 return nil 277 } 278 279 // Delete the query and update the index. 280 if err := tx.Delete(intentionsTableName, wrapped); err != nil { 281 return fmt.Errorf("failed intention delete: %s", err) 282 } 283 if err := tx.Insert("index", &IndexEntry{intentionsTableName, idx}); err != nil { 284 return fmt.Errorf("failed updating index: %s", err) 285 } 286 287 return nil 288 } 289 290 // IntentionMatch returns the list of intentions that match the namespace and 291 // name for either a source or destination. This applies the resolution rules 292 // so wildcards will match any value. 293 // 294 // The returned value is the list of intentions in the same order as the 295 // entries in args. The intentions themselves are sorted based on the 296 // intention precedence rules. i.e. result[0][0] is the highest precedent 297 // rule to match for the first entry. 298 func (s *Store) IntentionMatch(ws memdb.WatchSet, args *structs.IntentionQueryMatch) (uint64, []structs.Intentions, error) { 299 tx := s.db.Txn(false) 300 defer tx.Abort() 301 302 // Get the table index. 303 idx := maxIndexTxn(tx, intentionsTableName) 304 if idx < 1 { 305 idx = 1 306 } 307 308 // Make all the calls and accumulate the results 309 results := make([]structs.Intentions, len(args.Entries)) 310 for i, entry := range args.Entries { 311 // Each search entry may require multiple queries to memdb, so this 312 // returns the arguments for each necessary Get. Note on performance: 313 // this is not the most optimal set of queries since we repeat some 314 // many times (such as */*). We can work on improving that in the 315 // future, the test cases shouldn't have to change for that. 316 getParams, err := s.intentionMatchGetParams(entry) 317 if err != nil { 318 return 0, nil, err 319 } 320 321 // Perform each call and accumulate the result. 322 var ixns structs.Intentions 323 for _, params := range getParams { 324 iter, err := tx.Get(intentionsTableName, string(args.Type), params...) 325 if err != nil { 326 return 0, nil, fmt.Errorf("failed intention lookup: %s", err) 327 } 328 329 ws.Add(iter.WatchCh()) 330 331 for ixn := iter.Next(); ixn != nil; ixn = iter.Next() { 332 ixns = append(ixns, ixn.(*structs.Intention)) 333 } 334 } 335 336 // Sort the results by precedence 337 sort.Sort(structs.IntentionPrecedenceSorter(ixns)) 338 339 // Store the result 340 results[i] = ixns 341 } 342 343 return idx, results, nil 344 } 345 346 // intentionMatchGetParams returns the tx.Get parameters to find all the 347 // intentions for a certain entry. 348 func (s *Store) intentionMatchGetParams(entry structs.IntentionMatchEntry) ([][]interface{}, error) { 349 // We always query for "*/*" so include that. If the namespace is a 350 // wildcard, then we're actually done. 351 result := make([][]interface{}, 0, 3) 352 result = append(result, []interface{}{"*", "*"}) 353 if entry.Namespace == structs.IntentionWildcard { 354 return result, nil 355 } 356 357 // Search for NS/* intentions. If we have a wildcard name, then we're done. 358 result = append(result, []interface{}{entry.Namespace, "*"}) 359 if entry.Name == structs.IntentionWildcard { 360 return result, nil 361 } 362 363 // Search for the exact NS/N value. 364 result = append(result, []interface{}{entry.Namespace, entry.Name}) 365 return result, nil 366 }