2018-02-28 06:25:05 +00:00
package state
import (
2020-10-06 18:24:05 +00:00
"errors"
2018-02-28 06:25:05 +00:00
"fmt"
2018-03-02 20:56:39 +00:00
"sort"
2018-02-28 06:25:05 +00:00
2020-09-03 23:38:03 +00:00
"github.com/hashicorp/go-memdb"
2020-10-08 00:35:34 +00:00
"github.com/hashicorp/consul/acl"
2021-09-08 15:59:30 +00:00
"github.com/hashicorp/consul/agent/connect"
2018-02-28 06:25:05 +00:00
"github.com/hashicorp/consul/agent/structs"
)
2021-01-30 00:17:40 +00:00
const tableConnectIntentions = "connect-intentions"
2018-02-28 06:25:05 +00:00
// intentionsTableSchema returns a new table schema used for storing
// intentions for Connect.
func intentionsTableSchema ( ) * memdb . TableSchema {
return & memdb . TableSchema {
2021-01-30 00:17:40 +00:00
Name : tableConnectIntentions ,
2018-02-28 06:25:05 +00:00
Indexes : map [ string ] * memdb . IndexSchema {
2021-01-30 00:17:40 +00:00
indexID : {
Name : indexID ,
2018-02-28 06:25:05 +00:00
AllowMissing : false ,
Unique : true ,
Indexer : & memdb . UUIDFieldIndex {
Field : "ID" ,
} ,
} ,
2020-06-16 17:19:31 +00:00
"destination" : {
2018-02-28 06:25:05 +00:00
Name : "destination" ,
AllowMissing : true ,
2018-04-05 11:41:49 +00:00
// This index is not unique since we need uniqueness across the whole
// 4-tuple.
Unique : false ,
2018-02-28 06:25:05 +00:00
Indexer : & memdb . CompoundIndex {
Indexes : [ ] memdb . Indexer {
& memdb . StringFieldIndex {
Field : "DestinationNS" ,
Lowercase : true ,
} ,
& memdb . StringFieldIndex {
Field : "DestinationName" ,
Lowercase : true ,
} ,
} ,
} ,
} ,
2020-06-16 17:19:31 +00:00
"source" : {
2018-02-28 06:25:05 +00:00
Name : "source" ,
AllowMissing : true ,
2018-04-05 11:41:49 +00:00
// 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 ,
} ,
} ,
} ,
} ,
2020-06-16 17:19:31 +00:00
"source_destination" : {
2018-04-05 11:41:49 +00:00
Name : "source_destination" ,
AllowMissing : true ,
2018-02-28 06:25:05 +00:00
Unique : true ,
Indexer : & memdb . CompoundIndex {
Indexes : [ ] memdb . Indexer {
& memdb . StringFieldIndex {
Field : "SourceNS" ,
Lowercase : true ,
} ,
& memdb . StringFieldIndex {
Field : "SourceName" ,
Lowercase : true ,
} ,
2018-04-05 11:41:49 +00:00
& memdb . StringFieldIndex {
Field : "DestinationNS" ,
Lowercase : true ,
} ,
& memdb . StringFieldIndex {
Field : "DestinationName" ,
Lowercase : true ,
} ,
2018-02-28 06:25:05 +00:00
} ,
} ,
} ,
} ,
}
}
2020-10-06 18:24:05 +00:00
// 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 ) {
2021-01-30 00:17:40 +00:00
ixns , err := s . tx . Get ( tableConnectIntentions , "id" )
2018-03-06 17:31:21 +00:00
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
}
2020-10-06 18:24:05 +00:00
// 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 {
2018-03-06 17:31:21 +00:00
// Insert the intention
2021-01-30 00:17:40 +00:00
if err := s . tx . Insert ( tableConnectIntentions , ixn ) ; err != nil {
2018-03-06 17:31:21 +00:00
return fmt . Errorf ( "failed restoring intention: %s" , err )
}
2021-01-30 00:17:40 +00:00
if err := indexUpdateMaxTxn ( s . tx , ixn . ModifyIndex , tableConnectIntentions ) ; err != nil {
2018-03-06 17:31:21 +00:00
return fmt . Errorf ( "failed updating index: %s" , err )
}
return nil
}
2020-10-06 18:24:05 +00:00
// AreIntentionsInConfigEntries determines which table is the canonical store
// for intentions data.
func ( s * Store ) AreIntentionsInConfigEntries ( ) ( bool , error ) {
2018-02-28 17:53:21 +00:00
tx := s . db . Txn ( false )
defer tx . Abort ( )
2020-10-29 20:28:31 +00:00
return areIntentionsInConfigEntries ( tx , nil )
2020-10-06 18:24:05 +00:00
}
2020-10-29 20:28:31 +00:00
func areIntentionsInConfigEntries ( tx ReadTxn , ws memdb . WatchSet ) ( bool , error ) {
_ , entry , err := systemMetadataGetTxn ( tx , ws , structs . SystemMetadataIntentionFormatKey )
2020-10-06 18:24:05 +00:00
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 ( )
2021-03-10 18:00:29 +00:00
idx , results , _ , err := legacyIntentionsListTxn ( tx , ws , entMeta )
2020-10-06 18:24:05 +00:00
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 ( )
2020-10-29 20:28:31 +00:00
usingConfigEntries , err := areIntentionsInConfigEntries ( tx , ws )
2020-10-06 18:24:05 +00:00
if err != nil {
return 0 , nil , false , err
}
if ! usingConfigEntries {
2021-03-10 18:00:29 +00:00
return legacyIntentionsListTxn ( tx , ws , entMeta )
2020-10-06 18:24:05 +00:00
}
2021-03-10 18:00:29 +00:00
return configIntentionsListTxn ( tx , ws , entMeta )
2020-10-06 18:24:05 +00:00
}
2018-02-28 17:53:21 +00:00
2021-03-10 18:00:29 +00:00
func legacyIntentionsListTxn ( tx ReadTxn , ws memdb . WatchSet , entMeta * structs . EnterpriseMeta ) ( uint64 , structs . Intentions , bool , error ) {
2018-02-28 17:53:21 +00:00
// Get the index
2021-01-30 00:17:40 +00:00
idx := maxIndexTxn ( tx , tableConnectIntentions )
2018-06-15 20:03:19 +00:00
if idx < 1 {
idx = 1
}
2018-02-28 17:53:21 +00:00
2020-07-10 00:56:43 +00:00
iter , err := intentionListTxn ( tx , entMeta )
2018-02-28 17:53:21 +00:00
if err != nil {
2020-10-06 18:24:05 +00:00
return 0 , nil , false , fmt . Errorf ( "failed intention lookup: %s" , err )
2018-02-28 17:53:21 +00:00
}
2020-06-26 21:59:15 +00:00
2018-02-28 17:53:21 +00:00
ws . Add ( iter . WatchCh ( ) )
var results structs . Intentions
for ixn := iter . Next ( ) ; ixn != nil ; ixn = iter . Next ( ) {
2018-06-12 11:26:12 +00:00
results = append ( results , ixn . ( * structs . Intention ) )
2018-02-28 17:53:21 +00:00
}
2018-06-08 12:10:06 +00:00
// Sort by precedence just because that's nicer and probably what most clients
// want for presentation.
sort . Sort ( structs . IntentionPrecedenceSorter ( results ) )
2020-10-06 18:24:05 +00:00
return idx , results , false , nil
2018-02-28 17:53:21 +00:00
}
2020-10-06 18:24:05 +00:00
var ErrLegacyIntentionsAreDisabled = errors . New ( "Legacy intention modifications are disabled after the config entry migration." )
2020-11-13 20:42:21 +00:00
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
}
2021-02-03 23:10:38 +00:00
if err := ensureConfigEntryTxn ( tx , idx , upsertEntry ) ; err != nil {
2020-11-13 20:42:21 +00:00
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
}
2021-02-03 23:10:38 +00:00
if err := ensureConfigEntryTxn ( tx , idx , upsertEntry ) ; err != nil {
2020-11-13 20:42:21 +00:00
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
}
2021-02-03 23:10:38 +00:00
if err := ensureConfigEntryTxn ( tx , idx , upsertEntry ) ; err != nil {
2020-11-13 20:42:21 +00:00
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
}
2021-02-03 23:10:38 +00:00
if err := ensureConfigEntryTxn ( tx , idx , upsertEntry ) ; err != nil {
2020-11-13 20:42:21 +00:00
return err
}
return nil
}
func ( s * Store ) intentionMutationUpsert (
tx WriteTxn ,
idx uint64 ,
dest structs . ServiceName ,
src structs . ServiceName ,
value * structs . SourceIntention ,
) error {
// This variant is just for config-entry based intentions.
_ , 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 prevEntry * structs . ServiceIntentionsConfigEntry
if configEntry != nil {
prevEntry = configEntry . ( * structs . ServiceIntentionsConfigEntry )
}
var upsertEntry * structs . ServiceIntentionsConfigEntry
if prevEntry == nil {
upsertEntry = & structs . ServiceIntentionsConfigEntry {
Kind : structs . ServiceIntentions ,
Name : dest . Name ,
EnterpriseMeta : dest . EnterpriseMeta ,
Sources : [ ] * structs . SourceIntention { value } ,
}
} else {
upsertEntry = prevEntry . Clone ( )
upsertEntry . UpsertSourceByName ( src , value )
}
if err := upsertEntry . Normalize ( ) ; err != nil {
return err
}
if err := upsertEntry . Validate ( ) ; err != nil {
return err
}
2021-02-03 23:10:38 +00:00
if err := ensureConfigEntryTxn ( tx , idx , upsertEntry ) ; err != nil {
2020-11-13 20:42:21 +00:00
return err
}
return nil
}
func checkLegacyIntentionApplyAllowed ( prevEntry * structs . ServiceIntentionsConfigEntry ) error {
if prevEntry == nil {
return nil
}
if prevEntry . LegacyIDFieldsAreAllSet ( ) {
return nil
}
sn := prevEntry . DestinationServiceName ( )
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 ( ) )
}
2020-10-06 18:24:05 +00:00
// LegacyIntentionSet creates or updates an intention.
//
// Deprecated: Edit service-intentions config entries directly.
func ( s * Store ) LegacyIntentionSet ( idx uint64 , ixn * structs . Intention ) error {
2020-03-19 13:11:20 +00:00
tx := s . db . WriteTxn ( idx )
2018-02-28 06:25:05 +00:00
defer tx . Abort ( )
2020-10-29 20:28:31 +00:00
usingConfigEntries , err := areIntentionsInConfigEntries ( tx , nil )
2020-10-06 18:24:05 +00:00
if err != nil {
return err
}
if usingConfigEntries {
return ErrLegacyIntentionsAreDisabled
}
if err := legacyIntentionSetTxn ( tx , idx , ixn ) ; err != nil {
2018-02-28 06:25:05 +00:00
return err
}
2020-06-02 20:34:56 +00:00
return tx . Commit ( )
2018-02-28 06:25:05 +00:00
}
2020-10-06 18:24:05 +00:00
// legacyIntentionSetTxn is the inner method used to insert an intention with
2018-02-28 06:25:05 +00:00
// the proper indexes into the state store.
2020-09-03 23:38:03 +00:00
func legacyIntentionSetTxn ( tx WriteTxn , idx uint64 , ixn * structs . Intention ) error {
2018-02-28 06:25:05 +00:00
// ID is required
if ixn . ID == "" {
return ErrMissingIntentionID
}
2018-06-12 11:26:12 +00:00
// Ensure Precedence is populated correctly on "write"
2020-10-06 18:24:05 +00:00
//nolint:staticcheck
2018-06-12 11:26:12 +00:00
ixn . UpdatePrecedence ( )
2018-02-28 06:25:05 +00:00
// Check for an existing intention
2021-01-30 00:17:40 +00:00
existing , err := tx . First ( tableConnectIntentions , "id" , ixn . ID )
2018-02-28 06:25:05 +00:00
if err != nil {
2018-04-05 11:41:49 +00:00
return fmt . Errorf ( "failed intention lookup: %s" , err )
2018-02-28 06:25:05 +00:00
}
if existing != nil {
2018-03-03 16:43:19 +00:00
oldIxn := existing . ( * structs . Intention )
ixn . CreateIndex = oldIxn . CreateIndex
ixn . CreatedAt = oldIxn . CreatedAt
2018-02-28 06:25:05 +00:00
} else {
ixn . CreateIndex = idx
}
ixn . ModifyIndex = idx
2018-04-05 11:41:49 +00:00
// Check for duplicates on the 4-tuple.
2021-01-30 00:17:40 +00:00
duplicate , err := tx . First ( tableConnectIntentions , "source_destination" ,
2018-04-05 11:41:49 +00:00
ixn . SourceNS , ixn . SourceName , ixn . DestinationNS , ixn . DestinationName )
if err != nil {
return fmt . Errorf ( "failed intention lookup: %s" , err )
}
if duplicate != nil {
dupIxn := duplicate . ( * structs . Intention )
2018-04-19 11:06:32 +00:00
// Same ID is OK - this is an update
if dupIxn . ID != ixn . ID {
return fmt . Errorf ( "duplicate intention found: %s" , dupIxn . String ( ) )
}
2018-04-05 11:41:49 +00:00
}
2018-03-03 17:16:26 +00:00
// We always force meta to be non-nil so that we its an empty map.
// This makes it easy for API responses to not nil-check this everywhere.
if ixn . Meta == nil {
ixn . Meta = make ( map [ string ] string )
}
2018-02-28 06:25:05 +00:00
// Insert
2021-01-30 00:17:40 +00:00
if err := tx . Insert ( tableConnectIntentions , ixn ) ; err != nil {
2018-02-28 06:25:05 +00:00
return err
}
2021-03-08 18:02:41 +00:00
if err := tx . Insert ( tableIndex , & IndexEntry { tableConnectIntentions , idx } ) ; err != nil {
2018-02-28 06:25:05 +00:00
return fmt . Errorf ( "failed updating index: %s" , err )
}
return nil
}
// IntentionGet returns the given intention by ID.
2020-10-06 18:24:05 +00:00
func ( s * Store ) IntentionGet ( ws memdb . WatchSet , id string ) ( uint64 , * structs . ServiceIntentionsConfigEntry , * structs . Intention , error ) {
2018-02-28 06:25:05 +00:00
tx := s . db . Txn ( false )
defer tx . Abort ( )
2020-10-29 20:28:31 +00:00
usingConfigEntries , err := areIntentionsInConfigEntries ( tx , ws )
2020-10-06 18:24:05 +00:00
if err != nil {
return 0 , nil , nil , err
}
if ! usingConfigEntries {
2021-03-10 18:00:29 +00:00
idx , ixn , err := legacyIntentionGetTxn ( tx , ws , id )
2020-10-06 18:24:05 +00:00
return idx , nil , ixn , err
}
2021-03-10 18:00:29 +00:00
return configIntentionGetTxn ( tx , ws , id )
2020-10-06 18:24:05 +00:00
}
2021-03-10 18:00:29 +00:00
func legacyIntentionGetTxn ( tx ReadTxn , ws memdb . WatchSet , id string ) ( uint64 , * structs . Intention , error ) {
2018-02-28 06:25:05 +00:00
// Get the table index.
2021-01-30 00:17:40 +00:00
idx := maxIndexTxn ( tx , tableConnectIntentions )
2018-06-15 20:03:19 +00:00
if idx < 1 {
idx = 1
}
2018-02-28 06:25:05 +00:00
// Look up by its ID.
2021-01-30 00:17:40 +00:00
watchCh , intention , err := tx . FirstWatch ( tableConnectIntentions , "id" , id )
2018-02-28 06:25:05 +00:00
if err != nil {
return 0 , nil , fmt . Errorf ( "failed intention lookup: %s" , err )
}
ws . Add ( watchCh )
// Convert the interface{} if it is non-nil
var result * structs . Intention
if intention != nil {
result = intention . ( * structs . Intention )
}
return idx , result , nil
}
2018-03-01 05:21:59 +00:00
2020-06-26 21:59:15 +00:00
// IntentionGetExact returns the given intention by it's full unique name.
2020-10-06 18:24:05 +00:00
func ( s * Store ) IntentionGetExact ( ws memdb . WatchSet , args * structs . IntentionQueryExact ) ( uint64 , * structs . ServiceIntentionsConfigEntry , * structs . Intention , error ) {
2020-06-26 21:59:15 +00:00
tx := s . db . Txn ( false )
defer tx . Abort ( )
2020-10-29 20:28:31 +00:00
usingConfigEntries , err := areIntentionsInConfigEntries ( tx , ws )
2020-10-06 18:24:05 +00:00
if err != nil {
return 0 , nil , nil , err
}
if ! usingConfigEntries {
idx , ixn , err := s . legacyIntentionGetExactTxn ( tx , ws , args )
return idx , nil , ixn , err
}
return s . configIntentionGetExactTxn ( tx , ws , args )
}
2020-09-03 23:38:03 +00:00
func ( s * Store ) legacyIntentionGetExactTxn ( tx ReadTxn , ws memdb . WatchSet , args * structs . IntentionQueryExact ) ( uint64 , * structs . Intention , error ) {
2020-06-26 21:59:15 +00:00
if err := args . Validate ( ) ; err != nil {
return 0 , nil , err
}
// Get the table index.
2021-01-30 00:17:40 +00:00
idx := maxIndexTxn ( tx , tableConnectIntentions )
2020-06-26 21:59:15 +00:00
if idx < 1 {
idx = 1
}
// Look up by its full name.
2021-01-30 00:17:40 +00:00
watchCh , intention , err := tx . FirstWatch ( tableConnectIntentions , "source_destination" ,
2020-06-26 21:59:15 +00:00
args . SourceNS , args . SourceName , args . DestinationNS , args . DestinationName )
if err != nil {
return 0 , nil , fmt . Errorf ( "failed intention lookup: %s" , err )
}
ws . Add ( watchCh )
// Convert the interface{} if it is non-nil
var result * structs . Intention
if intention != nil {
result = intention . ( * structs . Intention )
}
return idx , result , nil
}
2020-10-06 18:24:05 +00:00
// LegacyIntentionDelete deletes the given intention by ID.
//
// Deprecated: Edit service-intentions config entries directly.
func ( s * Store ) LegacyIntentionDelete ( idx uint64 , id string ) error {
2020-03-19 13:11:20 +00:00
tx := s . db . WriteTxn ( idx )
2018-03-01 05:21:59 +00:00
defer tx . Abort ( )
2020-10-29 20:28:31 +00:00
usingConfigEntries , err := areIntentionsInConfigEntries ( tx , nil )
2020-10-06 18:24:05 +00:00
if err != nil {
return err
}
if usingConfigEntries {
return ErrLegacyIntentionsAreDisabled
}
if err := legacyIntentionDeleteTxn ( tx , idx , id ) ; err != nil {
2018-03-01 05:21:59 +00:00
return fmt . Errorf ( "failed intention delete: %s" , err )
}
2020-06-02 20:34:56 +00:00
return tx . Commit ( )
2018-03-01 05:21:59 +00:00
}
2020-10-06 18:24:05 +00:00
// legacyIntentionDeleteTxn is the inner method used to delete a legacy intention
2018-03-01 05:21:59 +00:00
// with the proper indexes into the state store.
2020-09-03 23:38:03 +00:00
func legacyIntentionDeleteTxn ( tx WriteTxn , idx uint64 , queryID string ) error {
2018-03-01 05:21:59 +00:00
// Pull the query.
2021-01-30 00:17:40 +00:00
wrapped , err := tx . First ( tableConnectIntentions , "id" , queryID )
2018-03-01 05:21:59 +00:00
if err != nil {
return fmt . Errorf ( "failed intention lookup: %s" , err )
}
if wrapped == nil {
return nil
}
// Delete the query and update the index.
2021-01-30 00:17:40 +00:00
if err := tx . Delete ( tableConnectIntentions , wrapped ) ; err != nil {
2018-03-01 05:21:59 +00:00
return fmt . Errorf ( "failed intention delete: %s" , err )
}
2021-03-08 18:02:41 +00:00
if err := tx . Insert ( tableIndex , & IndexEntry { tableConnectIntentions , idx } ) ; err != nil {
2018-03-01 05:21:59 +00:00
return fmt . Errorf ( "failed updating index: %s" , err )
}
return nil
}
2018-03-02 20:56:39 +00:00
2020-10-06 18:24:05 +00:00
// LegacyIntentionDeleteAll deletes all legacy intentions. This is part of the
// config entry migration code.
func ( s * Store ) LegacyIntentionDeleteAll ( idx uint64 ) error {
tx := s . db . WriteTxn ( idx )
defer tx . Abort ( )
// Delete the table and update the index.
2021-01-30 00:17:40 +00:00
if _ , err := tx . DeleteAll ( tableConnectIntentions , "id" ) ; err != nil {
2020-10-06 18:24:05 +00:00
return fmt . Errorf ( "failed intention delete-all: %s" , err )
}
2021-03-08 18:02:41 +00:00
if err := tx . Insert ( tableIndex , & IndexEntry { tableConnectIntentions , idx } ) ; err != nil {
2020-10-06 18:24:05 +00:00
return fmt . Errorf ( "failed updating index: %s" , err )
}
// Also bump the index for the config entry table so that
// secondaries can correctly know when they've replicated all of the service-intentions
// config entries that USED to exist in the old intentions table.
2021-03-08 18:02:41 +00:00
if err := tx . Insert ( tableIndex , & IndexEntry { tableConfigEntries , idx } ) ; err != nil {
2020-10-06 18:24:05 +00:00
return fmt . Errorf ( "failed updating index: %s" , err )
}
// Also set a system metadata flag indicating the transition has occurred.
metadataEntry := & structs . SystemMetadataEntry {
Key : structs . SystemMetadataIntentionFormatKey ,
Value : structs . SystemMetadataIntentionFormatConfigValue ,
RaftIndex : structs . RaftIndex {
CreateIndex : idx ,
ModifyIndex : idx ,
} ,
}
if err := systemMetadataSetTxn ( tx , idx , metadataEntry ) ; err != nil {
return fmt . Errorf ( "failed updating system metadata key %q: %s" , metadataEntry . Key , err )
}
return tx . Commit ( )
}
2021-09-16 19:31:19 +00:00
type IntentionDecisionOpts struct {
Target string
Namespace string
Partition string
Intentions structs . Intentions
MatchType structs . IntentionMatchType
DefaultDecision acl . EnforcementDecision
AllowPermissions bool
}
2021-03-13 04:59:47 +00:00
// IntentionDecision returns whether a connection should be allowed to a source or destination given a set of intentions.
//
// allowPermissions determines whether the presence of L7 permissions leads to a DENY decision.
// This should be false when evaluating a connection between a source and destination, but not the request that will be sent.
2021-09-16 19:31:19 +00:00
func ( s * Store ) IntentionDecision ( opts IntentionDecisionOpts ) ( structs . IntentionDecisionSummary , error ) {
2020-10-08 00:35:34 +00:00
// Figure out which source matches this request.
var ixnMatch * structs . Intention
2021-09-16 19:31:19 +00:00
for _ , ixn := range opts . Intentions {
if _ , ok := connect . AuthorizeIntentionTarget ( opts . Target , opts . Namespace , opts . Partition , ixn , opts . MatchType ) ; ok {
2020-10-08 00:35:34 +00:00
ixnMatch = ixn
break
}
}
2021-04-13 01:32:09 +00:00
resp := structs . IntentionDecisionSummary {
2021-09-16 19:31:19 +00:00
DefaultAllow : opts . DefaultDecision == acl . Allow ,
2021-04-13 01:32:09 +00:00
}
2020-10-08 00:35:34 +00:00
if ixnMatch == nil {
// No intention found, fall back to default
2021-04-13 01:32:09 +00:00
resp . Allowed = resp . DefaultAllow
2020-10-08 00:35:34 +00:00
return resp , nil
}
// Intention found, combine action + permissions
resp . Allowed = ixnMatch . Action == structs . IntentionActionAllow
if len ( ixnMatch . Permissions ) > 0 {
2021-03-16 14:03:52 +00:00
// If any permissions are present, fall back to allowPermissions.
// We are not evaluating requests so we cannot know whether the L7 permission requirements will be met.
2021-09-16 19:31:19 +00:00
resp . Allowed = opts . AllowPermissions
2020-10-08 00:35:34 +00:00
resp . HasPermissions = true
}
resp . ExternalSource = ixnMatch . Meta [ structs . MetaExternalSource ]
2020-10-23 16:45:41 +00:00
// Intentions with wildcard namespaces but specific names are not allowed (*/web -> */api)
// So we don't check namespaces to see if there's an exact intention
if ixnMatch . SourceName != structs . WildcardSpecifier && ixnMatch . DestinationName != structs . WildcardSpecifier {
resp . HasExact = true
}
2020-10-08 00:35:34 +00:00
return resp , nil
}
2018-03-02 20:56:39 +00:00
// IntentionMatch returns the list of intentions that match the namespace and
// name for either a source or destination. This applies the resolution rules
// so wildcards will match any value.
//
// The returned value is the list of intentions in the same order as the
// entries in args. The intentions themselves are sorted based on the
// intention precedence rules. i.e. result[0][0] is the highest precedent
// rule to match for the first entry.
func ( s * Store ) IntentionMatch ( ws memdb . WatchSet , args * structs . IntentionQueryMatch ) ( uint64 , [ ] structs . Intentions , error ) {
tx := s . db . Txn ( false )
defer tx . Abort ( )
2020-10-29 20:28:31 +00:00
usingConfigEntries , err := areIntentionsInConfigEntries ( tx , ws )
2020-10-06 18:24:05 +00:00
if err != nil {
return 0 , nil , err
}
if ! usingConfigEntries {
return s . legacyIntentionMatchTxn ( tx , ws , args )
}
return s . configIntentionMatchTxn ( tx , ws , args )
}
2020-09-03 23:38:03 +00:00
func ( s * Store ) legacyIntentionMatchTxn ( tx ReadTxn , ws memdb . WatchSet , args * structs . IntentionQueryMatch ) ( uint64 , [ ] structs . Intentions , error ) {
2018-03-02 20:56:39 +00:00
// Get the table index.
2021-01-30 00:17:40 +00:00
idx := maxIndexTxn ( tx , tableConnectIntentions )
2018-06-15 20:03:19 +00:00
if idx < 1 {
idx = 1
}
2018-03-02 20:56:39 +00:00
// Make all the calls and accumulate the results
results := make ( [ ] structs . Intentions , len ( args . Entries ) )
for i , entry := range args . Entries {
2020-09-03 23:38:03 +00:00
ixns , err := intentionMatchOneTxn ( tx , ws , entry , args . Type )
2018-03-02 20:56:39 +00:00
if err != nil {
return 0 , nil , err
}
// Sort the results by precedence
sort . Sort ( structs . IntentionPrecedenceSorter ( ixns ) )
// Store the result
results [ i ] = ixns
}
return idx , results , nil
}
2020-08-11 23:20:41 +00:00
// IntentionMatchOne returns the list of intentions that match the namespace and
// name for a single source or destination. This applies the resolution rules
// so wildcards will match any value.
//
// The returned intentions are sorted based on the intention precedence rules.
// i.e. result[0] is the highest precedent rule to match
2020-10-06 18:24:05 +00:00
func ( s * Store ) IntentionMatchOne (
ws memdb . WatchSet ,
entry structs . IntentionMatchEntry ,
matchType structs . IntentionMatchType ,
) ( uint64 , structs . Intentions , error ) {
2020-08-11 23:20:41 +00:00
tx := s . db . Txn ( false )
defer tx . Abort ( )
2021-03-16 14:03:52 +00:00
return compatIntentionMatchOneTxn ( tx , ws , entry , matchType )
}
func compatIntentionMatchOneTxn (
tx ReadTxn ,
ws memdb . WatchSet ,
entry structs . IntentionMatchEntry ,
matchType structs . IntentionMatchType ,
) ( uint64 , structs . Intentions , error ) {
2020-10-29 20:28:31 +00:00
usingConfigEntries , err := areIntentionsInConfigEntries ( tx , ws )
2020-10-06 18:24:05 +00:00
if err != nil {
return 0 , nil , err
}
if ! usingConfigEntries {
2020-09-03 23:38:03 +00:00
return legacyIntentionMatchOneTxn ( tx , ws , entry , matchType )
2020-10-06 18:24:05 +00:00
}
2020-09-03 23:38:03 +00:00
return configIntentionMatchOneTxn ( tx , ws , entry , matchType )
2020-10-06 18:24:05 +00:00
}
2020-09-03 23:38:03 +00:00
func legacyIntentionMatchOneTxn (
tx ReadTxn ,
2020-10-06 18:24:05 +00:00
ws memdb . WatchSet ,
entry structs . IntentionMatchEntry ,
matchType structs . IntentionMatchType ,
) ( uint64 , structs . Intentions , error ) {
2020-08-11 23:20:41 +00:00
// Get the table index.
2021-01-30 00:17:40 +00:00
idx := maxIndexTxn ( tx , tableConnectIntentions )
2020-08-11 23:20:41 +00:00
if idx < 1 {
idx = 1
}
2020-09-03 23:38:03 +00:00
results , err := intentionMatchOneTxn ( tx , ws , entry , matchType )
2020-08-11 23:20:41 +00:00
if err != nil {
return 0 , nil , err
}
sort . Sort ( structs . IntentionPrecedenceSorter ( results ) )
return idx , results , nil
}
2020-09-03 23:38:03 +00:00
func intentionMatchOneTxn ( tx ReadTxn , ws memdb . WatchSet ,
2020-08-11 23:20:41 +00:00
entry structs . IntentionMatchEntry , matchType structs . IntentionMatchType ) ( structs . Intentions , error ) {
// Each search entry may require multiple queries to memdb, so this
// returns the arguments for each necessary Get. Note on performance:
// this is not the most optimal set of queries since we repeat some
// many times (such as */*). We can work on improving that in the
// future, the test cases shouldn't have to change for that.
getParams , err := intentionMatchGetParams ( entry )
if err != nil {
return nil , err
}
// Perform each call and accumulate the result.
var result structs . Intentions
for _ , params := range getParams {
2021-01-30 00:17:40 +00:00
iter , err := tx . Get ( tableConnectIntentions , string ( matchType ) , params ... )
2020-08-11 23:20:41 +00:00
if err != nil {
return nil , fmt . Errorf ( "failed intention lookup: %s" , err )
}
ws . Add ( iter . WatchCh ( ) )
for ixn := iter . Next ( ) ; ixn != nil ; ixn = iter . Next ( ) {
result = append ( result , ixn . ( * structs . Intention ) )
}
}
return result , nil
}
2021-09-15 15:37:46 +00:00
// TODO(partitions): Update for partitions
2018-03-02 20:56:39 +00:00
// intentionMatchGetParams returns the tx.Get parameters to find all the
// intentions for a certain entry.
2020-08-11 23:20:41 +00:00
func intentionMatchGetParams ( entry structs . IntentionMatchEntry ) ( [ ] [ ] interface { } , error ) {
2018-03-02 20:56:39 +00:00
// We always query for "*/*" so include that. If the namespace is a
// wildcard, then we're actually done.
result := make ( [ ] [ ] interface { } , 0 , 3 )
2020-01-13 20:51:40 +00:00
result = append ( result , [ ] interface { } { structs . WildcardSpecifier , structs . WildcardSpecifier } )
if entry . Namespace == structs . WildcardSpecifier {
2018-03-02 20:56:39 +00:00
return result , nil
}
// Search for NS/* intentions. If we have a wildcard name, then we're done.
2020-01-13 20:51:40 +00:00
result = append ( result , [ ] interface { } { entry . Namespace , structs . WildcardSpecifier } )
if entry . Name == structs . WildcardSpecifier {
2018-03-02 20:56:39 +00:00
return result , nil
}
// Search for the exact NS/N value.
result = append ( result , [ ] interface { } { entry . Namespace , entry . Name } )
return result , nil
}
2021-03-14 04:44:01 +00:00
2021-04-13 16:12:13 +00:00
type ServiceWithDecision struct {
Name structs . ServiceName
Decision structs . IntentionDecisionSummary
}
2021-03-14 04:44:01 +00:00
// IntentionTopology returns the upstreams or downstreams of a service. Upstreams and downstreams are inferred from
// intentions. If intentions allow a connection from the target to some candidate service, the candidate service is considered
// an upstream of the target.
func ( s * Store ) IntentionTopology ( ws memdb . WatchSet ,
target structs . ServiceName , downstreams bool , defaultDecision acl . EnforcementDecision ) ( uint64 , structs . ServiceList , error ) {
2021-03-16 14:03:52 +00:00
tx := s . db . ReadTxn ( )
defer tx . Abort ( )
2021-04-13 16:12:13 +00:00
idx , services , err := s . intentionTopologyTxn ( tx , ws , target , downstreams , defaultDecision )
if err != nil {
requested := "upstreams"
if downstreams {
requested = "downstreams"
}
return 0 , nil , fmt . Errorf ( "failed to fetch %s for %s: %v" , requested , target . String ( ) , err )
}
2021-04-14 16:52:05 +00:00
resp := make ( structs . ServiceList , 0 )
2021-04-13 16:12:13 +00:00
for _ , svc := range services {
resp = append ( resp , svc . Name )
}
return idx , resp , nil
}
func ( s * Store ) intentionTopologyTxn ( tx ReadTxn , ws memdb . WatchSet ,
target structs . ServiceName , downstreams bool , defaultDecision acl . EnforcementDecision ) ( uint64 , [ ] ServiceWithDecision , error ) {
2021-03-14 04:44:01 +00:00
var maxIdx uint64
// If querying the upstreams for a service, we first query intentions that apply to the target service as a source.
// That way we can check whether intentions from the source allow connections to upstream candidates.
2021-03-16 15:49:24 +00:00
// The reverse is true for downstreams.
intentionMatchType := structs . IntentionMatchSource
2021-03-14 04:44:01 +00:00
if downstreams {
2021-03-16 15:49:24 +00:00
intentionMatchType = structs . IntentionMatchDestination
2021-03-14 04:44:01 +00:00
}
entry := structs . IntentionMatchEntry {
Namespace : target . NamespaceOrDefault ( ) ,
2021-09-16 19:31:19 +00:00
Partition : target . PartitionOrDefault ( ) ,
2021-03-14 04:44:01 +00:00
Name : target . Name ,
}
2021-03-16 15:49:24 +00:00
index , intentions , err := compatIntentionMatchOneTxn ( tx , ws , entry , intentionMatchType )
2021-03-14 04:44:01 +00:00
if err != nil {
return 0 , nil , fmt . Errorf ( "failed to query intentions for %s" , target . String ( ) )
}
if index > maxIdx {
maxIdx = index
}
// Check for a wildcard intention (* -> *) since it overrides the default decision from ACLs
if len ( intentions ) > 0 {
// Intentions with wildcard source and destination have the lowest precedence, so they are last in the list
ixn := intentions [ len ( intentions ) - 1 ]
2021-03-18 04:15:48 +00:00
if ixn . HasWildcardSource ( ) && ixn . HasWildcardDestination ( ) {
2021-03-14 04:44:01 +00:00
defaultDecision = acl . Allow
if ixn . Action == structs . IntentionActionDeny {
defaultDecision = acl . Deny
}
}
}
2021-03-16 15:33:08 +00:00
index , allServices , err := serviceListTxn ( tx , ws , func ( svc * structs . ServiceNode ) bool {
2021-03-14 04:44:01 +00:00
// Only include ingress gateways as downstreams, since they cannot receive service mesh traffic
// TODO(freddy): One remaining issue is that this includes non-Connect services (typical services without a proxy)
// Ideally those should be excluded as well, since they can't be upstreams/downstreams without a proxy.
// Maybe start tracking services represented by proxies? (both sidecar and ingress)
if svc . ServiceKind == structs . ServiceKindTypical || ( svc . ServiceKind == structs . ServiceKindIngressGateway && downstreams ) {
return true
}
return false
2021-09-08 15:59:30 +00:00
} , target . WildcardEnterpriseMetaForPartition ( ) )
2021-03-14 04:44:01 +00:00
if err != nil {
return index , nil , fmt . Errorf ( "failed to fetch catalog service list: %v" , err )
}
if index > maxIdx {
maxIdx = index
}
2021-03-16 15:49:24 +00:00
// When checking authorization to upstreams, the match type for the decision is `destination` because we are deciding
// if upstream candidates are covered by intentions that have the target service as a source.
// The reverse is true for downstreams.
decisionMatchType := structs . IntentionMatchDestination
if downstreams {
decisionMatchType = structs . IntentionMatchSource
}
2021-04-13 16:12:13 +00:00
result := make ( [ ] ServiceWithDecision , 0 , len ( allServices ) )
2021-03-14 04:44:01 +00:00
for _ , candidate := range allServices {
2021-03-17 19:40:04 +00:00
if candidate . Name == structs . ConsulServiceName {
continue
}
2021-09-16 19:31:19 +00:00
opts := IntentionDecisionOpts {
Target : candidate . Name ,
Namespace : candidate . NamespaceOrDefault ( ) ,
Partition : candidate . PartitionOrDefault ( ) ,
Intentions : intentions ,
MatchType : decisionMatchType ,
DefaultDecision : defaultDecision ,
AllowPermissions : true ,
}
decision , err := s . IntentionDecision ( opts )
2021-03-14 04:44:01 +00:00
if err != nil {
src , dst := target , candidate
if downstreams {
src , dst = candidate , target
}
return 0 , nil , fmt . Errorf ( "failed to get intention decision from (%s) to (%s): %v" ,
src . String ( ) , dst . String ( ) , err )
}
if ! decision . Allowed || target . Matches ( candidate ) {
continue
}
2021-04-13 16:12:13 +00:00
result = append ( result , ServiceWithDecision {
Name : candidate ,
Decision : decision ,
} )
2021-03-14 04:44:01 +00:00
}
return maxIdx , result , err
}