1069 lines
31 KiB
Go
1069 lines
31 KiB
Go
package state
|
|
|
|
import (
|
|
"errors"
|
|
"fmt"
|
|
"sort"
|
|
|
|
"github.com/hashicorp/go-memdb"
|
|
|
|
"github.com/hashicorp/consul/acl"
|
|
"github.com/hashicorp/consul/agent/connect"
|
|
"github.com/hashicorp/consul/agent/structs"
|
|
)
|
|
|
|
const tableConnectIntentions = "connect-intentions"
|
|
|
|
// intentionsTableSchema returns a new table schema used for storing
|
|
// intentions for Connect.
|
|
func intentionsTableSchema() *memdb.TableSchema {
|
|
return &memdb.TableSchema{
|
|
Name: tableConnectIntentions,
|
|
Indexes: map[string]*memdb.IndexSchema{
|
|
indexID: {
|
|
Name: indexID,
|
|
AllowMissing: false,
|
|
Unique: true,
|
|
Indexer: &memdb.UUIDFieldIndex{
|
|
Field: "ID",
|
|
},
|
|
},
|
|
"destination": {
|
|
Name: "destination",
|
|
AllowMissing: true,
|
|
// This index is not unique since we need uniqueness across the whole
|
|
// 4-tuple.
|
|
Unique: false,
|
|
Indexer: &memdb.CompoundIndex{
|
|
Indexes: []memdb.Indexer{
|
|
&memdb.StringFieldIndex{
|
|
Field: "DestinationNS",
|
|
Lowercase: true,
|
|
},
|
|
&memdb.StringFieldIndex{
|
|
Field: "DestinationName",
|
|
Lowercase: true,
|
|
},
|
|
},
|
|
},
|
|
},
|
|
"source": {
|
|
Name: "source",
|
|
AllowMissing: true,
|
|
// This index is not unique since we need uniqueness across the whole
|
|
// 4-tuple.
|
|
Unique: false,
|
|
Indexer: &memdb.CompoundIndex{
|
|
Indexes: []memdb.Indexer{
|
|
&memdb.StringFieldIndex{
|
|
Field: "SourceNS",
|
|
Lowercase: true,
|
|
},
|
|
&memdb.StringFieldIndex{
|
|
Field: "SourceName",
|
|
Lowercase: true,
|
|
},
|
|
},
|
|
},
|
|
},
|
|
"source_destination": {
|
|
Name: "source_destination",
|
|
AllowMissing: true,
|
|
Unique: true,
|
|
Indexer: &memdb.CompoundIndex{
|
|
Indexes: []memdb.Indexer{
|
|
&memdb.StringFieldIndex{
|
|
Field: "SourceNS",
|
|
Lowercase: true,
|
|
},
|
|
&memdb.StringFieldIndex{
|
|
Field: "SourceName",
|
|
Lowercase: true,
|
|
},
|
|
&memdb.StringFieldIndex{
|
|
Field: "DestinationNS",
|
|
Lowercase: true,
|
|
},
|
|
&memdb.StringFieldIndex{
|
|
Field: "DestinationName",
|
|
Lowercase: true,
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
}
|
|
}
|
|
|
|
// LegacyIntentions is used to pull all the intentions from the snapshot.
|
|
//
|
|
// Deprecated: service-intentions config entries are handled as config entries
|
|
// in the snapshot.
|
|
func (s *Snapshot) LegacyIntentions() (structs.Intentions, error) {
|
|
ixns, err := s.tx.Get(tableConnectIntentions, "id")
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
var ret structs.Intentions
|
|
for wrapped := ixns.Next(); wrapped != nil; wrapped = ixns.Next() {
|
|
ret = append(ret, wrapped.(*structs.Intention))
|
|
}
|
|
|
|
return ret, nil
|
|
}
|
|
|
|
// LegacyIntention is used when restoring from a snapshot.
|
|
//
|
|
// Deprecated: service-intentions config entries are handled as config entries
|
|
// in the snapshot.
|
|
func (s *Restore) LegacyIntention(ixn *structs.Intention) error {
|
|
// Insert the intention
|
|
if err := s.tx.Insert(tableConnectIntentions, ixn); err != nil {
|
|
return fmt.Errorf("failed restoring intention: %s", err)
|
|
}
|
|
if err := indexUpdateMaxTxn(s.tx, ixn.ModifyIndex, tableConnectIntentions); err != nil {
|
|
return fmt.Errorf("failed updating index: %s", err)
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
// AreIntentionsInConfigEntries determines which table is the canonical store
|
|
// for intentions data.
|
|
func (s *Store) AreIntentionsInConfigEntries() (bool, error) {
|
|
tx := s.db.Txn(false)
|
|
defer tx.Abort()
|
|
return areIntentionsInConfigEntries(tx, nil)
|
|
}
|
|
|
|
func areIntentionsInConfigEntries(tx ReadTxn, ws memdb.WatchSet) (bool, error) {
|
|
_, entry, err := systemMetadataGetTxn(tx, ws, structs.SystemMetadataIntentionFormatKey)
|
|
if err != nil {
|
|
return false, fmt.Errorf("failed system metadatalookup: %s", err)
|
|
}
|
|
if entry == nil {
|
|
return false, nil
|
|
}
|
|
return entry.Value == structs.SystemMetadataIntentionFormatConfigValue, nil
|
|
}
|
|
|
|
// LegacyIntentions is like Intentions() but only returns legacy intentions.
|
|
// This is exposed for migration purposes.
|
|
func (s *Store) LegacyIntentions(ws memdb.WatchSet, entMeta *structs.EnterpriseMeta) (uint64, structs.Intentions, error) {
|
|
tx := s.db.Txn(false)
|
|
defer tx.Abort()
|
|
|
|
idx, results, _, err := legacyIntentionsListTxn(tx, ws, entMeta)
|
|
return idx, results, err
|
|
}
|
|
|
|
// Intentions returns the list of all intentions. The boolean response value is true if it came from config entries.
|
|
func (s *Store) Intentions(ws memdb.WatchSet, entMeta *structs.EnterpriseMeta) (uint64, structs.Intentions, bool, error) {
|
|
tx := s.db.Txn(false)
|
|
defer tx.Abort()
|
|
|
|
usingConfigEntries, err := areIntentionsInConfigEntries(tx, ws)
|
|
if err != nil {
|
|
return 0, nil, false, err
|
|
}
|
|
if !usingConfigEntries {
|
|
return legacyIntentionsListTxn(tx, ws, entMeta)
|
|
}
|
|
return configIntentionsListTxn(tx, ws, entMeta)
|
|
}
|
|
|
|
func legacyIntentionsListTxn(tx ReadTxn, ws memdb.WatchSet, entMeta *structs.EnterpriseMeta) (uint64, structs.Intentions, bool, error) {
|
|
// Get the index
|
|
idx := maxIndexTxn(tx, tableConnectIntentions)
|
|
if idx < 1 {
|
|
idx = 1
|
|
}
|
|
|
|
iter, err := intentionListTxn(tx, entMeta)
|
|
if err != nil {
|
|
return 0, nil, false, fmt.Errorf("failed intention lookup: %s", err)
|
|
}
|
|
|
|
ws.Add(iter.WatchCh())
|
|
|
|
var results structs.Intentions
|
|
for ixn := iter.Next(); ixn != nil; ixn = iter.Next() {
|
|
results = append(results, ixn.(*structs.Intention))
|
|
}
|
|
|
|
// Sort by precedence just because that's nicer and probably what most clients
|
|
// want for presentation.
|
|
sort.Sort(structs.IntentionPrecedenceSorter(results))
|
|
|
|
return idx, results, false, nil
|
|
}
|
|
|
|
var ErrLegacyIntentionsAreDisabled = errors.New("Legacy intention modifications are disabled after the config entry migration.")
|
|
|
|
func (s *Store) IntentionMutation(idx uint64, op structs.IntentionOp, mut *structs.IntentionMutation) error {
|
|
tx := s.db.WriteTxn(idx)
|
|
defer tx.Abort()
|
|
|
|
usingConfigEntries, err := areIntentionsInConfigEntries(tx, nil)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
if !usingConfigEntries {
|
|
return errors.New("state: IntentionMutation() is not allowed when intentions are not stored in config entries")
|
|
}
|
|
|
|
switch op {
|
|
case structs.IntentionOpCreate:
|
|
if err := s.intentionMutationLegacyCreate(tx, idx, mut.Destination, mut.Value); err != nil {
|
|
return err
|
|
}
|
|
case structs.IntentionOpUpdate:
|
|
if err := s.intentionMutationLegacyUpdate(tx, idx, mut.ID, mut.Value); err != nil {
|
|
return err
|
|
}
|
|
case structs.IntentionOpDelete:
|
|
if mut.ID == "" {
|
|
if err := s.intentionMutationDelete(tx, idx, mut.Destination, mut.Source); err != nil {
|
|
return err
|
|
}
|
|
} else {
|
|
if err := s.intentionMutationLegacyDelete(tx, idx, mut.ID); err != nil {
|
|
return err
|
|
}
|
|
}
|
|
case structs.IntentionOpUpsert:
|
|
if err := s.intentionMutationUpsert(tx, idx, mut.Destination, mut.Source, mut.Value); err != nil {
|
|
return err
|
|
}
|
|
case structs.IntentionOpDeleteAll:
|
|
// This is an internal operation initiated by the leader and is not
|
|
// exposed for general RPC use.
|
|
return fmt.Errorf("Invalid Intention mutation operation '%s'", op)
|
|
default:
|
|
return fmt.Errorf("Invalid Intention mutation operation '%s'", op)
|
|
}
|
|
|
|
return tx.Commit()
|
|
}
|
|
|
|
func (s *Store) intentionMutationLegacyCreate(
|
|
tx WriteTxn,
|
|
idx uint64,
|
|
dest structs.ServiceName,
|
|
value *structs.SourceIntention,
|
|
) error {
|
|
_, configEntry, err := configEntryTxn(tx, nil, structs.ServiceIntentions, dest.Name, &dest.EnterpriseMeta)
|
|
if err != nil {
|
|
return fmt.Errorf("service-intentions config entry lookup failed: %v", err)
|
|
}
|
|
|
|
var upsertEntry *structs.ServiceIntentionsConfigEntry
|
|
if configEntry == nil {
|
|
upsertEntry = &structs.ServiceIntentionsConfigEntry{
|
|
Kind: structs.ServiceIntentions,
|
|
Name: dest.Name,
|
|
EnterpriseMeta: dest.EnterpriseMeta,
|
|
Sources: []*structs.SourceIntention{value},
|
|
}
|
|
} else {
|
|
prevEntry := configEntry.(*structs.ServiceIntentionsConfigEntry)
|
|
|
|
if err := checkLegacyIntentionApplyAllowed(prevEntry); err != nil {
|
|
return err
|
|
}
|
|
|
|
upsertEntry = prevEntry.Clone()
|
|
upsertEntry.Sources = append(upsertEntry.Sources, value)
|
|
}
|
|
|
|
if err := upsertEntry.LegacyNormalize(); err != nil {
|
|
return err
|
|
}
|
|
if err := upsertEntry.LegacyValidate(); err != nil {
|
|
return err
|
|
}
|
|
|
|
if err := ensureConfigEntryTxn(tx, idx, upsertEntry); err != nil {
|
|
return err
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
func (s *Store) intentionMutationLegacyUpdate(
|
|
tx WriteTxn,
|
|
idx uint64,
|
|
legacyID string,
|
|
value *structs.SourceIntention,
|
|
) error {
|
|
// This variant is just for legacy UUID-based intentions.
|
|
|
|
_, prevEntry, ixn, err := s.IntentionGet(nil, legacyID)
|
|
if err != nil {
|
|
return fmt.Errorf("Intention lookup failed: %v", err)
|
|
}
|
|
if ixn == nil || prevEntry == nil {
|
|
return fmt.Errorf("Cannot modify non-existent intention: '%s'", legacyID)
|
|
}
|
|
|
|
if err := checkLegacyIntentionApplyAllowed(prevEntry); err != nil {
|
|
return err
|
|
}
|
|
|
|
upsertEntry := prevEntry.Clone()
|
|
|
|
foundMatch := upsertEntry.UpdateSourceByLegacyID(
|
|
legacyID,
|
|
value,
|
|
)
|
|
if !foundMatch {
|
|
return fmt.Errorf("Cannot modify non-existent intention: '%s'", legacyID)
|
|
}
|
|
|
|
if err := upsertEntry.LegacyNormalize(); err != nil {
|
|
return err
|
|
}
|
|
if err := upsertEntry.LegacyValidate(); err != nil {
|
|
return err
|
|
}
|
|
|
|
if err := ensureConfigEntryTxn(tx, idx, upsertEntry); err != nil {
|
|
return err
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
func (s *Store) intentionMutationDelete(
|
|
tx WriteTxn,
|
|
idx uint64,
|
|
dest structs.ServiceName,
|
|
src structs.ServiceName,
|
|
) error {
|
|
_, configEntry, err := configEntryTxn(tx, nil, structs.ServiceIntentions, dest.Name, &dest.EnterpriseMeta)
|
|
if err != nil {
|
|
return fmt.Errorf("service-intentions config entry lookup failed: %v", err)
|
|
}
|
|
if configEntry == nil {
|
|
return nil
|
|
}
|
|
|
|
prevEntry := configEntry.(*structs.ServiceIntentionsConfigEntry)
|
|
upsertEntry := prevEntry.Clone()
|
|
|
|
deleted := upsertEntry.DeleteSourceByName(src)
|
|
if !deleted {
|
|
return nil
|
|
}
|
|
|
|
if upsertEntry == nil || len(upsertEntry.Sources) == 0 {
|
|
return deleteConfigEntryTxn(
|
|
tx,
|
|
idx,
|
|
structs.ServiceIntentions,
|
|
dest.Name,
|
|
&dest.EnterpriseMeta,
|
|
)
|
|
}
|
|
|
|
if err := upsertEntry.Normalize(); err != nil {
|
|
return err
|
|
}
|
|
if err := upsertEntry.Validate(); err != nil {
|
|
return err
|
|
}
|
|
|
|
if err := ensureConfigEntryTxn(tx, idx, upsertEntry); err != nil {
|
|
return err
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
func (s *Store) intentionMutationLegacyDelete(
|
|
tx WriteTxn,
|
|
idx uint64,
|
|
legacyID string,
|
|
) error {
|
|
_, prevEntry, ixn, err := s.IntentionGet(nil, legacyID)
|
|
if err != nil {
|
|
return fmt.Errorf("Intention lookup failed: %v", err)
|
|
}
|
|
if ixn == nil || prevEntry == nil {
|
|
return fmt.Errorf("Cannot delete non-existent intention: '%s'", legacyID)
|
|
}
|
|
|
|
if err := checkLegacyIntentionApplyAllowed(prevEntry); err != nil {
|
|
return err
|
|
}
|
|
|
|
upsertEntry := prevEntry.Clone()
|
|
|
|
deleted := upsertEntry.DeleteSourceByLegacyID(legacyID)
|
|
if !deleted {
|
|
return fmt.Errorf("Cannot delete non-existent intention: '%s'", legacyID)
|
|
}
|
|
|
|
if upsertEntry == nil || len(upsertEntry.Sources) == 0 {
|
|
return deleteConfigEntryTxn(
|
|
tx,
|
|
idx,
|
|
structs.ServiceIntentions,
|
|
prevEntry.Name,
|
|
&prevEntry.EnterpriseMeta,
|
|
)
|
|
}
|
|
|
|
if err := upsertEntry.LegacyNormalize(); err != nil {
|
|
return err
|
|
}
|
|
if err := upsertEntry.LegacyValidate(); err != nil {
|
|
return err
|
|
}
|
|
|
|
if err := ensureConfigEntryTxn(tx, idx, upsertEntry); err != nil {
|
|
return err
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
func (s *Store) intentionMutationUpsert(
|
|
tx WriteTxn,
|
|
idx uint64,
|
|
dest structs.ServiceName,
|
|
src structs.ServiceName,
|
|
value *structs. |