1package state
2
3import (
4	"errors"
5	"fmt"
6	"github.com/hashicorp/consul/agent/connect"
7	"sort"
8
9	"github.com/hashicorp/go-memdb"
10
11	"github.com/hashicorp/consul/acl"
12	"github.com/hashicorp/consul/agent/structs"
13)
14
15const tableConnectIntentions = "connect-intentions"
16
17// intentionsTableSchema returns a new table schema used for storing
18// intentions for Connect.
19func intentionsTableSchema() *memdb.TableSchema {
20	return &memdb.TableSchema{
21		Name: tableConnectIntentions,
22		Indexes: map[string]*memdb.IndexSchema{
23			indexID: {
24				Name:         indexID,
25				AllowMissing: false,
26				Unique:       true,
27				Indexer: &memdb.UUIDFieldIndex{
28					Field: "ID",
29				},
30			},
31			"destination": {
32				Name:         "destination",
33				AllowMissing: true,
34				// This index is not unique since we need uniqueness across the whole
35				// 4-tuple.
36				Unique: false,
37				Indexer: &memdb.CompoundIndex{
38					Indexes: []memdb.Indexer{
39						&memdb.StringFieldIndex{
40							Field:     "DestinationNS",
41							Lowercase: true,
42						},
43						&memdb.StringFieldIndex{
44							Field:     "DestinationName",
45							Lowercase: true,
46						},
47					},
48				},
49			},
50			"source": {
51				Name:         "source",
52				AllowMissing: true,
53				// This index is not unique since we need uniqueness across the whole
54				// 4-tuple.
55				Unique: false,
56				Indexer: &memdb.CompoundIndex{
57					Indexes: []memdb.Indexer{
58						&memdb.StringFieldIndex{
59							Field:     "SourceNS",
60							Lowercase: true,
61						},
62						&memdb.StringFieldIndex{
63							Field:     "SourceName",
64							Lowercase: true,
65						},
66					},
67				},
68			},
69			"source_destination": {
70				Name:         "source_destination",
71				AllowMissing: true,
72				Unique:       true,
73				Indexer: &memdb.CompoundIndex{
74					Indexes: []memdb.Indexer{
75						&memdb.StringFieldIndex{
76							Field:     "SourceNS",
77							Lowercase: true,
78						},
79						&memdb.StringFieldIndex{
80							Field:     "SourceName",
81							Lowercase: true,
82						},
83						&memdb.StringFieldIndex{
84							Field:     "DestinationNS",
85							Lowercase: true,
86						},
87						&memdb.StringFieldIndex{
88							Field:     "DestinationName",
89							Lowercase: true,
90						},
91					},
92				},
93			},
94		},
95	}
96}
97
98// LegacyIntentions is used to pull all the intentions from the snapshot.
99//
100// Deprecated: service-intentions config entries are handled as config entries
101// in the snapshot.
102func (s *Snapshot) LegacyIntentions() (structs.Intentions, error) {
103	ixns, err := s.tx.Get(tableConnectIntentions, "id")
104	if err != nil {
105		return nil, err
106	}
107
108	var ret structs.Intentions
109	for wrapped := ixns.Next(); wrapped != nil; wrapped = ixns.Next() {
110		ret = append(ret, wrapped.(*structs.Intention))
111	}
112
113	return ret, nil
114}
115
116// LegacyIntention is used when restoring from a snapshot.
117//
118// Deprecated: service-intentions config entries are handled as config entries
119// in the snapshot.
120func (s *Restore) LegacyIntention(ixn *structs.Intention) error {
121	// Insert the intention
122	if err := s.tx.Insert(tableConnectIntentions, ixn); err != nil {
123		return fmt.Errorf("failed restoring intention: %s", err)
124	}
125	if err := indexUpdateMaxTxn(s.tx, ixn.ModifyIndex, tableConnectIntentions); err != nil {
126		return fmt.Errorf("failed updating index: %s", err)
127	}
128
129	return nil
130}
131
132// AreIntentionsInConfigEntries determines which table is the canonical store
133// for intentions data.
134func (s *Store) AreIntentionsInConfigEntries() (bool, error) {
135	tx := s.db.Txn(false)
136	defer tx.Abort()
137	return areIntentionsInConfigEntries(tx, nil)
138}
139
140func areIntentionsInConfigEntries(tx ReadTxn, ws memdb.WatchSet) (bool, error) {
141	_, entry, err := systemMetadataGetTxn(tx, ws, structs.SystemMetadataIntentionFormatKey)
142	if err != nil {
143		return false, fmt.Errorf("failed system metadatalookup: %s", err)
144	}
145	if entry == nil {
146		return false, nil
147	}
148	return entry.Value == structs.SystemMetadataIntentionFormatConfigValue, nil
149}
150
151// LegacyIntentions is like Intentions() but only returns legacy intentions.
152// This is exposed for migration purposes.
153func (s *Store) LegacyIntentions(ws memdb.WatchSet, entMeta *structs.EnterpriseMeta) (uint64, structs.Intentions, error) {
154	tx := s.db.Txn(false)
155	defer tx.Abort()
156
157	idx, results, _, err := legacyIntentionsListTxn(tx, ws, entMeta)
158	return idx, results, err
159}
160
161// Intentions returns the list of all intentions. The boolean response value is true if it came from config entries.
162func (s *Store) Intentions(ws memdb.WatchSet, entMeta *structs.EnterpriseMeta) (uint64, structs.Intentions, bool, error) {
163	tx := s.db.Txn(false)
164	defer tx.Abort()
165
166	usingConfigEntries, err := areIntentionsInConfigEntries(tx, ws)
167	if err != nil {
168		return 0, nil, false, err
169	}
170	if !usingConfigEntries {
171		return legacyIntentionsListTxn(tx, ws, entMeta)
172	}
173	return configIntentionsListTxn(tx, ws, entMeta)
174}
175
176func legacyIntentionsListTxn(tx ReadTxn, ws memdb.WatchSet, entMeta *structs.EnterpriseMeta) (uint64, structs.Intentions, bool, error) {
177	// Get the index
178	idx := maxIndexTxn(tx, tableConnectIntentions)
179	if idx < 1 {
180		idx = 1
181	}
182
183	iter, err := intentionListTxn(tx, entMeta)
184	if err != nil {
185		return 0, nil, false, fmt.Errorf("failed intention lookup: %s", err)
186	}
187
188	ws.Add(iter.WatchCh())
189
190	var results structs.Intentions
191	for ixn := iter.Next(); ixn != nil; ixn = iter.Next() {
192		results = append(results, ixn.(*structs.Intention))
193	}
194
195	// Sort by precedence just because that's nicer and probably what most clients
196	// want for presentation.
197	sort.Sort(structs.IntentionPrecedenceSorter(results))
198
199	return idx, results, false, nil
200}
201
202var ErrLegacyIntentionsAreDisabled = errors.New("Legacy intention modifications are disabled after the config entry migration.")
203
204func (s *Store) IntentionMutation(idx uint64, op structs.IntentionOp, mut *structs.IntentionMutation) error {
205	tx := s.db.WriteTxn(idx)
206	defer tx.Abort()
207
208	usingConfigEntries, err := areIntentionsInConfigEntries(tx, nil)
209	if err != nil {
210		return err
211	}
212	if !usingConfigEntries {
213		return errors.New("state: IntentionMutation() is not allowed when intentions are not stored in config entries")
214	}
215
216	switch op {
217	case structs.IntentionOpCreate:
218		if err := s.intentionMutationLegacyCreate(tx, idx, mut.Destination, mut.Value); err != nil {
219			return err
220		}
221	case structs.IntentionOpUpdate:
222		if err := s.intentionMutationLegacyUpdate(tx, idx, mut.ID, mut.Value); err != nil {
223			return err
224		}
225	case structs.IntentionOpDelete:
226		if mut.ID == "" {
227			if err := s.intentionMutationDelete(tx, idx, mut.Destination, mut.Source); err != nil {
228				return err
229			}
230		} else {
231			if err := s.intentionMutationLegacyDelete(tx, idx, mut.ID); err != nil {
232				return err
233			}
234		}
235	case structs.IntentionOpUpsert:
236		if err := s.intentionMutationUpsert(tx, idx, mut.Destination, mut.Source, mut.Value); err != nil {
237			return err
238		}
239	case structs.IntentionOpDeleteAll:
240		// This is an internal operation initiated by the leader and is not
241		// exposed for general RPC use.
242		return fmt.Errorf("Invalid Intention mutation operation '%s'", op)
243	default:
244		return fmt.Errorf("Invalid Intention mutation operation '%s'", op)
245	}
246
247	return tx.Commit()
248}
249
250func (s *Store) intentionMutationLegacyCreate(
251	tx WriteTxn,
252	idx uint64,
253	dest structs.ServiceName,
254	value *structs.SourceIntention,
255) error {
256	_, configEntry, err := configEntryTxn(tx, nil, structs.ServiceIntentions, dest.Name, &dest.EnterpriseMeta)
257	if err != nil {
258		return fmt.Errorf("service-intentions config entry lookup failed: %v", err)
259	}
260
261	var upsertEntry *structs.ServiceIntentionsConfigEntry
262	if configEntry == nil {
263		upsertEntry = &structs.ServiceIntentionsConfigEntry{
264			Kind:           structs.ServiceIntentions,
265			Name:           dest.Name,
266			EnterpriseMeta: dest.EnterpriseMeta,
267			Sources:        []*structs.SourceIntention{value},
268		}
269	} else {
270		prevEntry := configEntry.(*structs.ServiceIntentionsConfigEntry)
271
272		if err := checkLegacyIntentionApplyAllowed(prevEntry); err != nil {
273			return err
274		}
275
276		upsertEntry = prevEntry.Clone()
277		upsertEntry.Sources = append(upsertEntry.Sources, value)
278	}
279
280	if err := upsertEntry.LegacyNormalize(); err != nil {
281		return err
282	}
283	if err := upsertEntry.LegacyValidate(); err != nil {
284		return err
285	}
286
287	if err := ensureConfigEntryTxn(tx, idx, upsertEntry); err != nil {
288		return err
289	}
290
291	return nil
292}
293
294func (s *Store) intentionMutationLegacyUpdate(
295	tx WriteTxn,
296	idx uint64,
297	legacyID string,
298	value *structs.SourceIntention,
299) error {
300	// This variant is just for legacy UUID-based intentions.
301
302	_, prevEntry, ixn, err := s.IntentionGet(nil, legacyID)
303	if err != nil {
304		return fmt.Errorf("Intention lookup failed: %v", err)
305	}
306	if ixn == nil || prevEntry == nil {
307		return fmt.Errorf("Cannot modify non-existent intention: '%s'", legacyID)
308	}
309
310	if err := checkLegacyIntentionApplyAllowed(prevEntry); err != nil {
311		return err
312	}
313
314	upsertEntry := prevEntry.Clone()
315
316	foundMatch := upsertEntry.UpdateSourceByLegacyID(
317		legacyID,
318		value,
319	)
320	if !foundMatch {
321		return fmt.Errorf("Cannot modify non-existent intention: '%s'", legacyID)
322	}
323
324	if err := upsertEntry.LegacyNormalize(); err != nil {
325		return err
326	}
327	if err := upsertEntry.LegacyValidate(); err != nil {
328		return err
329	}
330
331	if err := ensureConfigEntryTxn(tx, idx, upsertEntry); err != nil {
332		return err
333	}
334
335	return nil
336}
337
338func (s *Store) intentionMutationDelete(
339	tx WriteTxn,
340	idx uint64,
341	dest structs.ServiceName,
342	src structs.ServiceName,
343) error {
344	_, configEntry, err := configEntryTxn(tx, nil, structs.ServiceIntentions, dest.Name, &dest.EnterpriseMeta)
345	if err != nil {
346		return fmt.Errorf("service-intentions config entry lookup failed: %v", err)
347	}
348	if configEntry == nil {
349		return nil
350	}
351
352	prevEntry := configEntry.(*structs.ServiceIntentionsConfigEntry)
353	upsertEntry := prevEntry.Clone()
354
355	deleted := upsertEntry.DeleteSourceByName(src)
356	if !deleted {
357		return nil
358	}
359
360	if upsertEntry == nil || len(upsertEntry.Sources) == 0 {
361		return deleteConfigEntryTxn(
362			tx,
363			idx,
364			structs.ServiceIntentions,
365			dest.Name,
366			&dest.EnterpriseMeta,
367		)
368	}
369
370	if err := upsertEntry.Normalize(); err != nil {
371		return err
372	}
373	if err := upsertEntry.Validate(); err != nil {
374		return err
375	}
376
377	if err := ensureConfigEntryTxn(tx, idx, upsertEntry); err != nil {
378		return err
379	}
380
381	return nil
382}
383
384func (s *Store) intentionMutationLegacyDelete(
385	tx WriteTxn,
386	idx uint64,
387	legacyID string,
388) error {
389	_, prevEntry, ixn, err := s.IntentionGet(nil, legacyID)
390	if err != nil {
391		return fmt.Errorf("Intention lookup failed: %v", err)
392	}
393	if ixn == nil || prevEntry == nil {
394		return fmt.Errorf("Cannot delete non-existent intention: '%s'", legacyID)
395	}
396
397	if err := checkLegacyIntentionApplyAllowed(prevEntry); err != nil {
398		return err
399	}
400
401	upsertEntry := prevEntry.Clone()
402
403	deleted := upsertEntry.DeleteSourceByLegacyID(legacyID)
404	if !deleted {
405		return fmt.Errorf("Cannot delete non-existent intention: '%s'", legacyID)
406	}
407
408	if upsertEntry == nil || len(upsertEntry.Sources) == 0 {
409		return deleteConfigEntryTxn(
410			tx,
411			idx,
412			structs.ServiceIntentions,
413			prevEntry.Name,
414			&prevEntry.EnterpriseMeta,
415		)
416	}
417
418	if err := upsertEntry.LegacyNormalize(); err != nil {
419		return err
420	}
421	if err := upsertEntry.LegacyValidate(); err != nil {
422		return err
423	}
424
425	if err := ensureConfigEntryTxn(tx, idx, upsertEntry); err != nil {
426		return err
427	}
428
429	return nil
430}
431
432func (s *Store) intentionMutationUpsert(
433	tx WriteTxn,
434	idx uint64,
435	dest structs.ServiceName,
436	src structs.ServiceName,
437	value *structs.SourceIntention,
438) error {
439	// This variant is just for config-entry based intentions.
440
441	_, configEntry, err := configEntryTxn(tx, nil, structs.ServiceIntentions, dest.Name, &dest.EnterpriseMeta)
442	if err != nil {
443		return fmt.Errorf("service-intentions config entry lookup failed: %v", err)
444	}
445
446	var prevEntry *structs.ServiceIntentionsConfigEntry
447	if configEntry != nil {
448		prevEntry = configEntry.(*structs.ServiceIntentionsConfigEntry)
449	}
450
451	var upsertEntry *structs.ServiceIntentionsConfigEntry
452
453	if prevEntry == nil {
454		upsertEntry = &structs.ServiceIntentionsConfigEntry{
455			Kind:           structs.ServiceIntentions,
456			Name:           dest.Name,
457			EnterpriseMeta: dest.EnterpriseMeta,
458			Sources:        []*structs.SourceIntention{value},
459		}
460	} else {
461		upsertEntry = prevEntry.Clone()
462
463		upsertEntry.UpsertSourceByName(src, value)
464	}
465
466	if err := upsertEntry.Normalize(); err != nil {
467		return err
468	}
469	if err := upsertEntry.Validate(); err != nil {
470		return err
471	}
472
473	if err := ensureConfigEntryTxn(tx, idx, upsertEntry); err != nil {
474		return err
475	}
476
477	return nil
478}
479
480func checkLegacyIntentionApplyAllowed(prevEntry *structs.ServiceIntentionsConfigEntry) error {
481	if prevEntry == nil {
482		return nil
483	}
484	if prevEntry.LegacyIDFieldsAreAllSet() {
485		return nil
486	}
487
488	sn := prevEntry.DestinationServiceName()
489	return fmt.Errorf("cannot use legacy intention API to edit intentions with a destination of %q after editing them via a service-intentions config entry", sn.String())
490}
491
492// LegacyIntentionSet creates or updates an intention.
493//
494// Deprecated: Edit service-intentions config entries directly.
495func (s *Store) LegacyIntentionSet(idx uint64, ixn *structs.Intention) error {
496	tx := s.db.WriteTxn(idx)
497	defer tx.Abort()
498
499	usingConfigEntries, err := areIntentionsInConfigEntries(tx, nil)
500	if err != nil {
501		return err
502	}
503	if usingConfigEntries {
504		return ErrLegacyIntentionsAreDisabled
505	}
506
507	if err := legacyIntentionSetTxn(tx, idx, ixn); err != nil {
508		return err
509	}
510
511	return tx.Commit()
512}
513
514// legacyIntentionSetTxn is the inner method used to insert an intention with
515// the proper indexes into the state store.
516func legacyIntentionSetTxn(tx WriteTxn, idx uint64, ixn *structs.Intention) error {
517	// ID is required
518	if ixn.ID == "" {
519		return ErrMissingIntentionID
520	}
521
522	// Ensure Precedence is populated correctly on "write"
523	//nolint:staticcheck
524	ixn.UpdatePrecedence()
525
526	// Check for an existing intention
527	existing, err := tx.First(tableConnectIntentions, "id", ixn.ID)
528	if err != nil {
529		return fmt.Errorf("failed intention lookup: %s", err)
530	}
531	if existing != nil {
532		oldIxn := existing.(*structs.Intention)
533		ixn.CreateIndex = oldIxn.CreateIndex
534		ixn.CreatedAt = oldIxn.CreatedAt
535	} else {
536		ixn.CreateIndex = idx
537	}
538	ixn.ModifyIndex = idx
539
540	// Check for duplicates on the 4-tuple.
541	duplicate, err := tx.First(tableConnectIntentions, "source_destination",
542		ixn.SourceNS, ixn.SourceName, ixn.DestinationNS, ixn.DestinationName)
543	if err != nil {
544		return fmt.Errorf("failed intention lookup: %s", err)
545	}
546	if duplicate != nil {
547		dupIxn := duplicate.(*structs.Intention)
548		// Same ID is OK - this is an update
549		if dupIxn.ID != ixn.ID {
550			return fmt.Errorf("duplicate intention found: %s", dupIxn.String())
551		}
552	}
553
554	// We always force meta to be non-nil so that we its an empty map.
555	// This makes it easy for API responses to not nil-check this everywhere.
556	if ixn.Meta == nil {
557		ixn.Meta = make(map[string]string)
558	}
559
560	// Insert
561	if err := tx.Insert(tableConnectIntentions, ixn); err != nil {
562		return err
563	}
564	if err := tx.Insert(tableIndex, &IndexEntry{tableConnectIntentions, idx}); err != nil {
565		return fmt.Errorf("failed updating index: %s", err)
566	}
567
568	return nil
569}
570
571// IntentionGet returns the given intention by ID.
572func (s *Store) IntentionGet(ws memdb.WatchSet, id string) (uint64, *structs.ServiceIntentionsConfigEntry, *structs.Intention, error) {
573	tx := s.db.Txn(false)
574	defer tx.Abort()
575
576	usingConfigEntries, err := areIntentionsInConfigEntries(tx, ws)
577	if err != nil {
578		return 0, nil, nil, err
579	}
580	if !usingConfigEntries {
581		idx, ixn, err := legacyIntentionGetTxn(tx, ws, id)
582		return idx, nil, ixn, err
583	}
584	return configIntentionGetTxn(tx, ws, id)
585}
586
587func legacyIntentionGetTxn(tx ReadTxn, ws memdb.WatchSet, id string) (uint64, *structs.Intention, error) {
588	// Get the table index.
589	idx := maxIndexTxn(tx, tableConnectIntentions)
590	if idx < 1 {
591		idx = 1
592	}
593
594	// Look up by its ID.
595	watchCh, intention, err := tx.FirstWatch(tableConnectIntentions, "id", id)
596	if err != nil {
597		return 0, nil, fmt.Errorf("failed intention lookup: %s", err)
598	}
599	ws.Add(watchCh)
600
601	// Convert the interface{} if it is non-nil
602	var result *structs.Intention
603	if intention != nil {
604		result = intention.(*structs.Intention)
605	}
606
607	return idx, result, nil
608}
609
610// IntentionGetExact returns the given intention by it's full unique name.
611func (s *Store) IntentionGetExact(ws memdb.WatchSet, args *structs.IntentionQueryExact) (uint64, *structs.ServiceIntentionsConfigEntry, *structs.Intention, error) {
612	tx := s.db.Txn(false)
613	defer tx.Abort()
614
615	usingConfigEntries, err := areIntentionsInConfigEntries(tx, ws)
616	if err != nil {
617		return 0, nil, nil, err
618	}
619	if !usingConfigEntries {
620		idx, ixn, err := s.legacyIntentionGetExactTxn(tx, ws, args)
621		return idx, nil, ixn, err
622	}
623	return s.configIntentionGetExactTxn(tx, ws, args)
624}
625
626func (s *Store) legacyIntentionGetExactTxn(tx ReadTxn, ws memdb.WatchSet, args *structs.IntentionQueryExact) (uint64, *structs.Intention, error) {
627	if err := args.Validate(); err != nil {
628		return 0, nil, err
629	}
630
631	// Get the table index.
632	idx := maxIndexTxn(tx, tableConnectIntentions)
633	if idx < 1 {
634		idx = 1
635	}
636
637	// Look up by its full name.
638	watchCh, intention, err := tx.FirstWatch(tableConnectIntentions, "source_destination",
639		args.SourceNS, args.SourceName, args.DestinationNS, args.DestinationName)
640	if err != nil {
641		return 0, nil, fmt.Errorf("failed intention lookup: %s", err)
642	}
643	ws.Add(watchCh)
644
645	// Convert the interface{} if it is non-nil
646	var result *structs.Intention
647	if intention != nil {
648		result = intention.(*structs.Intention)
649	}
650
651	return idx, result, nil
652}
653
654// LegacyIntentionDelete deletes the given intention by ID.
655//
656// Deprecated: Edit service-intentions config entries directly.
657func (s *Store) LegacyIntentionDelete(idx uint64, id string) error {
658	tx := s.db.WriteTxn(idx)
659	defer tx.Abort()
660
661	usingConfigEntries, err := areIntentionsInConfigEntries(tx, nil)
662	if err != nil {
663		return err
664	}
665	if usingConfigEntries {
666		return ErrLegacyIntentionsAreDisabled
667	}
668
669	if err := legacyIntentionDeleteTxn(tx, idx, id); err != nil {
670		return fmt.Errorf("failed intention delete: %s", err)
671	}
672
673	return tx.Commit()
674}
675
676// legacyIntentionDeleteTxn is the inner method used to delete a legacy intention
677// with the proper indexes into the state store.
678func legacyIntentionDeleteTxn(tx WriteTxn, idx uint64, queryID string) error {
679	// Pull the query.
680	wrapped, err := tx.First(tableConnectIntentions, "id", queryID)
681	if err != nil {
682		return fmt.Errorf("failed intention lookup: %s", err)
683	}
684	if wrapped == nil {
685		return nil
686	}
687
688	// Delete the query and update the index.
689	if err := tx.Delete(tableConnectIntentions, wrapped); err != nil {
690		return fmt.Errorf("failed intention delete: %s", err)
691	}
692	if err := tx.Insert(tableIndex, &IndexEntry{tableConnectIntentions, idx}); err != nil {
693		return fmt.Errorf("failed updating index: %s", err)
694	}
695
696	return nil
697}
698
699// LegacyIntentionDeleteAll deletes all legacy intentions. This is part of the
700// config entry migration code.
701func (s *Store) LegacyIntentionDeleteAll(idx uint64) error {
702	tx := s.db.WriteTxn(idx)
703	defer tx.Abort()
704
705	// Delete the table and update the index.
706	if _, err := tx.DeleteAll(tableConnectIntentions, "id"); err != nil {
707		return fmt.Errorf("failed intention delete-all: %s", err)
708	}
709	if err := tx.Insert(tableIndex, &IndexEntry{tableConnectIntentions, idx}); err != nil {
710		return fmt.Errorf("failed updating index: %s", err)
711	}
712	// Also bump the index for the config entry table so that
713	// secondaries can correctly know when they've replicated all of the service-intentions
714	// config entries that USED to exist in the old intentions table.
715	if err := tx.Insert(tableIndex, &IndexEntry{tableConfigEntries, idx}); err != nil {
716		return fmt.Errorf("failed updating index: %s", err)
717	}
718
719	// Also set a system metadata flag indicating the transition has occurred.
720	metadataEntry := &structs.SystemMetadataEntry{
721		Key:   structs.SystemMetadataIntentionFormatKey,
722		Value: structs.SystemMetadataIntentionFormatConfigValue,
723		RaftIndex: structs.RaftIndex{
724			CreateIndex: idx,
725			ModifyIndex: idx,
726		},
727	}
728	if err := systemMetadataSetTxn(tx, idx, metadataEntry); err != nil {
729		return fmt.Errorf("failed updating system metadata key %q: %s", metadataEntry.Key, err)
730	}
731
732	return tx.Commit()
733}
734
735// IntentionDecision returns whether a connection should be allowed to a source or destination given a set of intentions.
736//
737// allowPermissions determines whether the presence of L7 permissions leads to a DENY decision.
738// This should be false when evaluating a connection between a source and destination, but not the request that will be sent.
739func (s *Store) IntentionDecision(
740	target, targetNS string, intentions structs.Intentions, matchType structs.IntentionMatchType,
741	defaultDecision acl.EnforcementDecision, allowPermissions bool,
742) (structs.IntentionDecisionSummary, error) {
743
744	// Figure out which source matches this request.
745	var ixnMatch *structs.Intention
746	for _, ixn := range intentions {
747		if _, ok := connect.AuthorizeIntentionTarget(target, targetNS, ixn, matchType); ok {
748			ixnMatch = ixn
749			break
750		}
751	}
752
753	resp := structs.IntentionDecisionSummary{
754		DefaultAllow: defaultDecision == acl.Allow,
755	}
756	if ixnMatch == nil {
757		// No intention found, fall back to default
758		resp.Allowed = resp.DefaultAllow
759		return resp, nil
760	}
761
762	// Intention found, combine action + permissions
763	resp.Allowed = ixnMatch.Action == structs.IntentionActionAllow
764	if len(ixnMatch.Permissions) > 0 {
765		// If any permissions are present, fall back to allowPermissions.
766		// We are not evaluating requests so we cannot know whether the L7 permission requirements will be met.
767		resp.Allowed = allowPermissions
768		resp.HasPermissions = true
769	}
770	resp.ExternalSource = ixnMatch.Meta[structs.MetaExternalSource]
771
772	// Intentions with wildcard namespaces but specific names are not allowed (*/web -> */api)
773	// So we don't check namespaces to see if there's an exact intention
774	if ixnMatch.SourceName != structs.WildcardSpecifier && ixnMatch.DestinationName != structs.WildcardSpecifier {
775		resp.HasExact = true
776	}
777
778	return resp, nil
779}
780
781// IntentionMatch returns the list of intentions that match the namespace and
782// name for either a source or destination. This applies the resolution rules
783// so wildcards will match any value.
784//
785// The returned value is the list of intentions in the same order as the
786// entries in args. The intentions themselves are sorted based on the
787// intention precedence rules. i.e. result[0][0] is the highest precedent
788// rule to match for the first entry.
789func (s *Store) IntentionMatch(ws memdb.WatchSet, args *structs.IntentionQueryMatch) (uint64, []structs.Intentions, error) {
790	tx := s.db.Txn(false)
791	defer tx.Abort()
792
793	usingConfigEntries, err := areIntentionsInConfigEntries(tx, ws)
794	if err != nil {
795		return 0, nil, err
796	}
797	if !usingConfigEntries {
798		return s.legacyIntentionMatchTxn(tx, ws, args)
799	}
800	return s.configIntentionMatchTxn(tx, ws, args)
801}
802
803func (s *Store) legacyIntentionMatchTxn(tx ReadTxn, ws memdb.WatchSet, args *structs.IntentionQueryMatch) (uint64, []structs.Intentions, error) {
804	// Get the table index.
805	idx := maxIndexTxn(tx, tableConnectIntentions)
806	if idx < 1 {
807		idx = 1
808	}
809
810	// Make all the calls and accumulate the results
811	results := make([]structs.Intentions, len(args.Entries))
812	for i, entry := range args.Entries {
813		ixns, err := intentionMatchOneTxn(tx, ws, entry, args.Type)
814		if err != nil {
815			return 0, nil, err
816		}
817
818		// Sort the results by precedence
819		sort.Sort(structs.IntentionPrecedenceSorter(ixns))
820
821		// Store the result
822		results[i] = ixns
823	}
824
825	return idx, results, nil
826}
827
828// IntentionMatchOne returns the list of intentions that match the namespace and
829// name for a single source or destination. This applies the resolution rules
830// so wildcards will match any value.
831//
832// The returned intentions are sorted based on the intention precedence rules.
833// i.e. result[0] is the highest precedent rule to match
834func (s *Store) IntentionMatchOne(
835	ws memdb.WatchSet,
836	entry structs.IntentionMatchEntry,
837	matchType structs.IntentionMatchType,
838) (uint64, structs.Intentions, error) {
839	tx := s.db.Txn(false)
840	defer tx.Abort()
841
842	return compatIntentionMatchOneTxn(tx, ws, entry, matchType)
843}
844
845func compatIntentionMatchOneTxn(
846	tx ReadTxn,
847	ws memdb.WatchSet,
848	entry structs.IntentionMatchEntry,
849	matchType structs.IntentionMatchType,
850) (uint64, structs.Intentions, error) {
851
852	usingConfigEntries, err := areIntentionsInConfigEntries(tx, ws)
853	if err != nil {
854		return 0, nil, err
855	}
856	if !usingConfigEntries {
857		return legacyIntentionMatchOneTxn(tx, ws, entry, matchType)
858	}
859	return configIntentionMatchOneTxn(tx, ws, entry, matchType)
860}
861
862func legacyIntentionMatchOneTxn(
863	tx ReadTxn,
864	ws memdb.WatchSet,
865	entry structs.IntentionMatchEntry,
866	matchType structs.IntentionMatchType,
867) (uint64, structs.Intentions, error) {
868	// Get the table index.
869	idx := maxIndexTxn(tx, tableConnectIntentions)
870	if idx < 1 {
871		idx = 1
872	}
873
874	results, err := intentionMatchOneTxn(tx, ws, entry, matchType)
875	if err != nil {
876		return 0, nil, err
877	}
878
879	sort.Sort(structs.IntentionPrecedenceSorter(results))
880
881	return idx, results, nil
882}
883
884func intentionMatchOneTxn(tx ReadTxn, ws memdb.WatchSet,
885	entry structs.IntentionMatchEntry, matchType structs.IntentionMatchType) (structs.Intentions, error) {
886
887	// Each search entry may require multiple queries to memdb, so this
888	// returns the arguments for each necessary Get. Note on performance:
889	// this is not the most optimal set of queries since we repeat some
890	// many times (such as */*). We can work on improving that in the
891	// future, the test cases shouldn't have to change for that.
892	getParams, err := intentionMatchGetParams(entry)
893	if err != nil {
894		return nil, err
895	}
896
897	// Perform each call and accumulate the result.
898	var result structs.Intentions
899	for _, params := range getParams {
900		iter, err := tx.Get(tableConnectIntentions, string(matchType), params...)
901		if err != nil {
902			return nil, fmt.Errorf("failed intention lookup: %s", err)
903		}
904
905		ws.Add(iter.WatchCh())
906
907		for ixn := iter.Next(); ixn != nil; ixn = iter.Next() {
908			result = append(result, ixn.(*structs.Intention))
909		}
910	}
911	return result, nil
912}
913
914// intentionMatchGetParams returns the tx.Get parameters to find all the
915// intentions for a certain entry.
916func intentionMatchGetParams(entry structs.IntentionMatchEntry) ([][]interface{}, error) {
917	// We always query for "*/*" so include that. If the namespace is a
918	// wildcard, then we're actually done.
919	result := make([][]interface{}, 0, 3)
920	result = append(result, []interface{}{structs.WildcardSpecifier, structs.WildcardSpecifier})
921	if entry.Namespace == structs.WildcardSpecifier {
922		return result, nil
923	}
924
925	// Search for NS/* intentions. If we have a wildcard name, then we're done.
926	result = append(result, []interface{}{entry.Namespace, structs.WildcardSpecifier})
927	if entry.Name == structs.WildcardSpecifier {
928		return result, nil
929	}
930
931	// Search for the exact NS/N value.
932	result = append(result, []interface{}{entry.Namespace, entry.Name})
933	return result, nil
934}
935
936type ServiceWithDecision struct {
937	Name     structs.ServiceName
938	Decision structs.IntentionDecisionSummary
939}
940
941// IntentionTopology returns the upstreams or downstreams of a service. Upstreams and downstreams are inferred from
942// intentions. If intentions allow a connection from the target to some candidate service, the candidate service is considered
943// an upstream of the target.
944func (s *Store) IntentionTopology(ws memdb.WatchSet,
945	target structs.ServiceName, downstreams bool, defaultDecision acl.EnforcementDecision) (uint64, structs.ServiceList, error) {
946	tx := s.db.ReadTxn()
947	defer tx.Abort()
948
949	idx, services, err := s.intentionTopologyTxn(tx, ws, target, downstreams, defaultDecision)
950	if err != nil {
951		requested := "upstreams"
952		if downstreams {
953			requested = "downstreams"
954		}
955		return 0, nil, fmt.Errorf("failed to fetch %s for %s: %v", requested, target.String(), err)
956	}
957
958	resp := make(structs.ServiceList, 0)
959	for _, svc := range services {
960		resp = append(resp, svc.Name)
961	}
962	return idx, resp, nil
963}
964
965func (s *Store) intentionTopologyTxn(tx ReadTxn, ws memdb.WatchSet,
966	target structs.ServiceName, downstreams bool, defaultDecision acl.EnforcementDecision) (uint64, []ServiceWithDecision, error) {
967
968	var maxIdx uint64
969
970	// If querying the upstreams for a service, we first query intentions that apply to the target service as a source.
971	// That way we can check whether intentions from the source allow connections to upstream candidates.
972	// The reverse is true for downstreams.
973	intentionMatchType := structs.IntentionMatchSource
974	if downstreams {
975		intentionMatchType = structs.IntentionMatchDestination
976	}
977	entry := structs.IntentionMatchEntry{
978		Namespace: target.NamespaceOrDefault(),
979		Name:      target.Name,
980	}
981	index, intentions, err := compatIntentionMatchOneTxn(tx, ws, entry, intentionMatchType)
982	if err != nil {
983		return 0, nil, fmt.Errorf("failed to query intentions for %s", target.String())
984	}
985	if index > maxIdx {
986		maxIdx = index
987	}
988
989	// Check for a wildcard intention (* -> *) since it overrides the default decision from ACLs
990	if len(intentions) > 0 {
991		// Intentions with wildcard source and destination have the lowest precedence, so they are last in the list
992		ixn := intentions[len(intentions)-1]
993
994		if ixn.HasWildcardSource() && ixn.HasWildcardDestination() {
995			defaultDecision = acl.Allow
996			if ixn.Action == structs.IntentionActionDeny {
997				defaultDecision = acl.Deny
998			}
999		}
1000	}
1001
1002	index, allServices, err := serviceListTxn(tx, ws, func(svc *structs.ServiceNode) bool {
1003		// Only include ingress gateways as downstreams, since they cannot receive service mesh traffic
1004		// TODO(freddy): One remaining issue is that this includes non-Connect services (typical services without a proxy)
1005		//				 Ideally those should be excluded as well, since they can't be upstreams/downstreams without a proxy.
1006		//				 Maybe start tracking services represented by proxies? (both sidecar and ingress)
1007		if svc.ServiceKind == structs.ServiceKindTypical || (svc.ServiceKind == structs.ServiceKindIngressGateway && downstreams) {
1008			return true
1009		}
1010		return false
1011	}, structs.WildcardEnterpriseMeta())
1012	if err != nil {
1013		return index, nil, fmt.Errorf("failed to fetch catalog service list: %v", err)
1014	}
1015	if index > maxIdx {
1016		maxIdx = index
1017	}
1018
1019	// When checking authorization to upstreams, the match type for the decision is `destination` because we are deciding
1020	// if upstream candidates are covered by intentions that have the target service as a source.
1021	// The reverse is true for downstreams.
1022	decisionMatchType := structs.IntentionMatchDestination
1023	if downstreams {
1024		decisionMatchType = structs.IntentionMatchSource
1025	}
1026	result := make([]ServiceWithDecision, 0, len(allServices))
1027	for _, candidate := range allServices {
1028		if candidate.Name == structs.ConsulServiceName {
1029			continue
1030		}
1031		decision, err := s.IntentionDecision(candidate.Name, candidate.NamespaceOrDefault(), intentions, decisionMatchType, defaultDecision, true)
1032		if err != nil {
1033			src, dst := target, candidate
1034			if downstreams {
1035				src, dst = candidate, target
1036			}
1037			return 0, nil, fmt.Errorf("failed to get intention decision from (%s) to (%s): %v",
1038				src.String(), dst.String(), err)
1039		}
1040		if !decision.Allowed || target.Matches(candidate) {
1041			continue
1042		}
1043
1044		result = append(result, ServiceWithDecision{
1045			Name:     candidate,
1046			Decision: decision,
1047		})
1048	}
1049	return maxIdx, result, err
1050}
1051