2019-03-19 22:56:17 +00:00
package state
import (
2020-09-22 22:05:09 +00:00
"errors"
2019-03-19 22:56:17 +00:00
"fmt"
2020-04-16 21:00:48 +00:00
2020-12-11 21:10:00 +00:00
memdb "github.com/hashicorp/go-memdb"
2022-04-05 21:10:06 +00:00
"github.com/hashicorp/consul/acl"
2022-02-22 16:36:36 +00:00
"github.com/hashicorp/consul/agent/configentry"
2020-09-22 22:05:09 +00:00
"github.com/hashicorp/consul/agent/connect"
2019-07-01 20:23:36 +00:00
"github.com/hashicorp/consul/agent/consul/discoverychain"
2019-03-19 22:56:17 +00:00
"github.com/hashicorp/consul/agent/structs"
2020-04-23 23:16:04 +00:00
"github.com/hashicorp/consul/lib"
2022-05-19 19:21:44 +00:00
"github.com/hashicorp/consul/lib/maps"
2019-03-19 22:56:17 +00:00
)
2019-07-01 20:23:36 +00:00
type ConfigEntryLinkIndex struct {
}
type discoveryChainConfigEntry interface {
// ListRelatedServices returns a list of other names of services referenced
// in this config entry.
2020-01-24 15:04:58 +00:00
ListRelatedServices ( ) [ ] structs . ServiceID
2019-07-01 20:23:36 +00:00
}
func ( s * ConfigEntryLinkIndex ) FromObject ( obj interface { } ) ( bool , [ ] [ ] byte , error ) {
entry , ok := obj . ( structs . ConfigEntry )
if ! ok {
return false , nil , fmt . Errorf ( "object is not a ConfigEntry" )
}
dcEntry , ok := entry . ( discoveryChainConfigEntry )
if ! ok {
return false , nil , nil
}
linkedServices := dcEntry . ListRelatedServices ( )
numLinks := len ( linkedServices )
if numLinks == 0 {
return false , nil , nil
}
vals := make ( [ ] [ ] byte , 0 , numLinks )
for _ , linkedService := range linkedServices {
2020-01-24 15:04:58 +00:00
vals = append ( vals , [ ] byte ( linkedService . String ( ) + "\x00" ) )
2019-07-01 20:23:36 +00:00
}
return true , vals , nil
}
func ( s * ConfigEntryLinkIndex ) FromArgs ( args ... interface { } ) ( [ ] byte , error ) {
if len ( args ) != 1 {
return nil , fmt . Errorf ( "must provide only a single argument" )
}
2020-01-24 15:04:58 +00:00
arg , ok := args [ 0 ] . ( structs . ServiceID )
2019-07-01 20:23:36 +00:00
if ! ok {
2020-01-24 15:04:58 +00:00
return nil , fmt . Errorf ( "argument must be a structs.ServiceID: %#v" , args [ 0 ] )
2019-07-01 20:23:36 +00:00
}
// Add the null character as a terminator
2020-01-24 15:04:58 +00:00
return [ ] byte ( arg . String ( ) + "\x00" ) , nil
2019-07-01 20:23:36 +00:00
}
func ( s * ConfigEntryLinkIndex ) PrefixFromArgs ( args ... interface { } ) ( [ ] byte , error ) {
val , err := s . FromArgs ( args ... )
if err != nil {
return nil , err
}
// Strip the null terminator, the rest is a prefix
n := len ( val )
if n > 0 {
return val [ : n - 1 ] , nil
}
return val , nil
}
2019-03-19 22:56:17 +00:00
// ConfigEntries is used to pull all the config entries for the snapshot.
func ( s * Snapshot ) ConfigEntries ( ) ( [ ] structs . ConfigEntry , error ) {
2021-01-29 01:34:34 +00:00
entries , err := s . tx . Get ( tableConfigEntries , "id" )
2019-03-19 22:56:17 +00:00
if err != nil {
return nil , err
}
var ret [ ] structs . ConfigEntry
2019-03-27 23:52:38 +00:00
for wrapped := entries . Next ( ) ; wrapped != nil ; wrapped = entries . Next ( ) {
2019-03-19 22:56:17 +00:00
ret = append ( ret , wrapped . ( structs . ConfigEntry ) )
}
return ret , nil
}
2019-03-20 23:13:13 +00:00
// ConfigEntry is used when restoring from a snapshot.
2019-03-19 22:56:17 +00:00
func ( s * Restore ) ConfigEntry ( c structs . ConfigEntry ) error {
2020-07-10 00:56:43 +00:00
return insertConfigEntryWithTxn ( s . tx , c . GetRaftIndex ( ) . ModifyIndex , c )
2019-03-19 22:56:17 +00:00
}
2019-03-20 23:13:13 +00:00
// ConfigEntry is called to get a given config entry.
2022-04-05 21:10:06 +00:00
func ( s * Store ) ConfigEntry ( ws memdb . WatchSet , kind , name string , entMeta * acl . EnterpriseMeta ) ( uint64 , structs . ConfigEntry , error ) {
2019-03-27 23:52:38 +00:00
tx := s . db . Txn ( false )
2019-03-19 22:56:17 +00:00
defer tx . Abort ( )
2020-07-10 00:56:43 +00:00
return configEntryTxn ( tx , ws , kind , name , entMeta )
2019-06-27 17:37:43 +00:00
}
2019-03-19 22:56:17 +00:00
2022-04-05 21:10:06 +00:00
func configEntryTxn ( tx ReadTxn , ws memdb . WatchSet , kind , name string , entMeta * acl . EnterpriseMeta ) ( uint64 , structs . ConfigEntry , error ) {
2019-03-19 22:56:17 +00:00
// Get the index
2021-01-29 01:34:34 +00:00
idx := maxIndexTxn ( tx , tableConfigEntries )
2019-03-19 22:56:17 +00:00
// Get the existing config entry.
2022-02-22 16:36:36 +00:00
watchCh , existing , err := tx . FirstWatch ( tableConfigEntries , "id" , configentry . NewKindName ( kind , name , entMeta ) )
2019-03-19 22:56:17 +00:00
if err != nil {
return 0 , nil , fmt . Errorf ( "failed config entry lookup: %s" , err )
}
2019-05-02 19:25:29 +00:00
ws . Add ( watchCh )
2019-03-19 22:56:17 +00:00
if existing == nil {
2019-03-27 23:52:38 +00:00
return idx , nil , nil
2019-03-19 22:56:17 +00:00
}
conf , ok := existing . ( structs . ConfigEntry )
if ! ok {
return 0 , nil , fmt . Errorf ( "config entry %q (%s) is an invalid type: %T" , name , kind , conf )
}
return idx , conf , nil
}
2019-03-20 23:13:13 +00:00
// ConfigEntries is called to get all config entry objects.
2022-04-05 21:10:06 +00:00
func ( s * Store ) ConfigEntries ( ws memdb . WatchSet , entMeta * acl . EnterpriseMeta ) ( uint64 , [ ] structs . ConfigEntry , error ) {
2020-01-24 15:04:58 +00:00
return s . ConfigEntriesByKind ( ws , "" , entMeta )
2019-03-27 23:52:38 +00:00
}
// ConfigEntriesByKind is called to get all config entry objects with the given kind.
// If kind is empty, all config entries will be returned.
2022-04-05 21:10:06 +00:00
func ( s * Store ) ConfigEntriesByKind ( ws memdb . WatchSet , kind string , entMeta * acl . EnterpriseMeta ) ( uint64 , [ ] structs . ConfigEntry , error ) {
2019-03-27 23:52:38 +00:00
tx := s . db . Txn ( false )
2019-03-20 23:13:13 +00:00
defer tx . Abort ( )
2020-07-10 00:56:43 +00:00
return configEntriesByKindTxn ( tx , ws , kind , entMeta )
2019-07-02 16:01:17 +00:00
}
2019-03-20 23:13:13 +00:00
2022-07-12 16:03:41 +00:00
func listDiscoveryChainNamesTxn (
tx ReadTxn ,
ws memdb . WatchSet ,
overrides map [ configentry . KindName ] structs . ConfigEntry ,
entMeta acl . EnterpriseMeta ,
) ( uint64 , [ ] structs . ServiceName , error ) {
2022-05-19 19:21:44 +00:00
// Get the index and watch for updates
idx := maxIndexWatchTxn ( tx , ws , tableConfigEntries )
// List all discovery chain top nodes.
seen := make ( map [ structs . ServiceName ] struct { } )
for _ , kind := range [ ] string {
structs . ServiceRouter ,
structs . ServiceSplitter ,
structs . ServiceResolver ,
} {
iter , err := getConfigEntryKindsWithTxn ( tx , kind , & entMeta )
if err != nil {
return 0 , nil , fmt . Errorf ( "failed config entry lookup: %s" , err )
}
ws . Add ( iter . WatchCh ( ) )
for v := iter . Next ( ) ; v != nil ; v = iter . Next ( ) {
entry := v . ( structs . ConfigEntry )
sn := structs . NewServiceName ( entry . GetName ( ) , entry . GetEnterpriseMeta ( ) )
seen [ sn ] = struct { } { }
}
2022-07-12 16:03:41 +00:00
for kn , entry := range overrides {
sn := structs . NewServiceName ( kn . Name , & kn . EnterpriseMeta )
if entry != nil {
seen [ sn ] = struct { } { }
} else {
delete ( seen , sn )
}
}
2022-05-19 19:21:44 +00:00
}
results := maps . SliceOfKeys ( seen )
structs . ServiceList ( results ) . Sort ( )
return idx , results , nil
}
2022-04-05 21:10:06 +00:00
func configEntriesByKindTxn ( tx ReadTxn , ws memdb . WatchSet , kind string , entMeta * acl . EnterpriseMeta ) ( uint64 , [ ] structs . ConfigEntry , error ) {
2020-10-06 18:24:05 +00:00
// Get the index and watch for updates
2021-01-29 01:34:34 +00:00
idx := maxIndexWatchTxn ( tx , ws , tableConfigEntries )
2019-03-20 23:13:13 +00:00
2019-03-27 23:52:38 +00:00
// Lookup by kind, or all if kind is empty
var iter memdb . ResultIterator
var err error
if kind != "" {
2020-01-24 15:04:58 +00:00
iter , err = getConfigEntryKindsWithTxn ( tx , kind , entMeta )
2019-03-27 23:52:38 +00:00
} else {
2020-01-24 15:04:58 +00:00
iter , err = getAllConfigEntriesWithTxn ( tx , entMeta )
2019-03-27 23:52:38 +00:00
}
2019-03-20 23:13:13 +00:00
if err != nil {
return 0 , nil , fmt . Errorf ( "failed config entry lookup: %s" , err )
}
2019-04-07 06:38:08 +00:00
ws . Add ( iter . WatchCh ( ) )
2019-03-20 23:13:13 +00:00
var results [ ] structs . ConfigEntry
for v := iter . Next ( ) ; v != nil ; v = iter . Next ( ) {
results = append ( results , v . ( structs . ConfigEntry ) )
}
return idx , results , nil
}
2019-03-27 23:52:38 +00:00
// EnsureConfigEntry is called to do an upsert of a given config entry.
2021-02-03 23:10:38 +00:00
func ( s * Store ) EnsureConfigEntry ( idx uint64 , conf structs . ConfigEntry ) error {
2020-03-19 13:11:20 +00:00
tx := s . db . WriteTxn ( idx )
2019-03-19 22:56:17 +00:00
defer tx . Abort ( )
2023-01-27 19:34:11 +00:00
if err := ensureConfigEntryTxn ( tx , idx , false , conf ) ; err != nil {
2019-03-27 23:52:38 +00:00
return err
}
2020-06-02 20:34:56 +00:00
return tx . Commit ( )
2019-03-27 23:52:38 +00:00
}
// ensureConfigEntryTxn upserts a config entry inside of a transaction.
2023-01-27 19:34:11 +00:00
func ensureConfigEntryTxn ( tx WriteTxn , idx uint64 , statusUpdate bool , conf structs . ConfigEntry ) error {
2021-04-15 22:12:35 +00:00
q := newConfigEntryQuery ( conf )
existing , err := tx . First ( tableConfigEntries , indexID , q )
2019-03-19 22:56:17 +00:00
if err != nil {
return fmt . Errorf ( "failed configuration lookup: %s" , err )
}
raftIndex := conf . GetRaftIndex ( )
if existing != nil {
existingIdx := existing . ( structs . ConfigEntry ) . GetRaftIndex ( )
raftIndex . CreateIndex = existingIdx . CreateIndex
2020-10-06 18:24:05 +00:00
// Handle optional upsert logic.
if updatableConf , ok := conf . ( structs . UpdatableConfigEntry ) ; ok {
if err := updatableConf . UpdateOver ( existing . ( structs . ConfigEntry ) ) ; err != nil {
return err
}
}
2023-01-27 19:34:11 +00:00
if ! statusUpdate {
if controlledConf , ok := conf . ( structs . ControlledConfigEntry ) ; ok {
controlledConf . SetStatus ( existing . ( structs . ControlledConfigEntry ) . GetStatus ( ) )
}
}
2019-03-19 22:56:17 +00:00
} else {
2023-01-27 19:34:11 +00:00
if ! statusUpdate {
if controlledConf , ok := conf . ( structs . ControlledConfigEntry ) ; ok {
controlledConf . SetStatus ( controlledConf . DefaultStatus ( ) )
}
}
2019-03-19 22:56:17 +00:00
raftIndex . CreateIndex = idx
}
raftIndex . ModifyIndex = idx
2021-04-15 22:12:35 +00:00
err = validateProposedConfigEntryInGraph ( tx , q , conf )
2019-07-01 20:23:36 +00:00
if err != nil {
return err // Err is already sufficiently decorated.
}
2020-07-10 00:56:43 +00:00
if err := validateConfigEntryEnterprise ( tx , conf ) ; err != nil {
2020-05-08 18:24:33 +00:00
return err
2019-03-19 22:56:17 +00:00
}
2020-07-10 00:56:43 +00:00
return insertConfigEntryWithTxn ( tx , idx , conf )
2019-03-19 22:56:17 +00:00
}
2019-03-27 23:52:38 +00:00
// EnsureConfigEntryCAS is called to do a check-and-set upsert of a given config entry.
2021-02-03 23:10:38 +00:00
func ( s * Store ) EnsureConfigEntryCAS ( idx , cidx uint64 , conf structs . ConfigEntry ) ( bool , error ) {
2020-03-19 13:11:20 +00:00
tx := s . db . WriteTxn ( idx )
2019-03-19 22:56:17 +00:00
defer tx . Abort ( )
2019-03-27 23:52:38 +00:00
// Check for existing configuration.
2021-02-09 17:37:57 +00:00
existing , err := tx . First ( tableConfigEntries , indexID , newConfigEntryQuery ( conf ) )
2019-03-27 23:52:38 +00:00
if err != nil {
return false , fmt . Errorf ( "failed configuration lookup: %s" , err )
}
// Check if the we should do the set. A ModifyIndex of 0 means that
// we are doing a set-if-not-exists.
var existingIdx structs . RaftIndex
if existing != nil {
existingIdx = * existing . ( structs . ConfigEntry ) . GetRaftIndex ( )
}
if cidx == 0 && existing != nil {
return false , nil
}
if cidx != 0 && existing == nil {
return false , nil
}
if existing != nil && cidx != 0 && cidx != existingIdx . ModifyIndex {
return false , nil
}
2023-01-27 19:34:11 +00:00
if err := ensureConfigEntryTxn ( tx , idx , false , conf ) ; err != nil {
return false , err
}
err = tx . Commit ( )
return err == nil , err
}
// EnsureConfigEntryWithStatusCAS is called to do a check-and-set upsert of a given config entry and its status.
func ( s * Store ) EnsureConfigEntryWithStatusCAS ( idx , cidx uint64 , conf structs . ConfigEntry ) ( bool , error ) {
tx := s . db . WriteTxn ( idx )
defer tx . Abort ( )
// Check for existing configuration.
existing , err := tx . First ( tableConfigEntries , indexID , newConfigEntryQuery ( conf ) )
if err != nil {
return false , fmt . Errorf ( "failed configuration lookup: %s" , err )
}
// Check if we should do the set. A ModifyIndex of 0 means that
// we are doing a set-if-not-exists.
var existingIdx structs . RaftIndex
if existing != nil {
existingIdx = * existing . ( structs . ConfigEntry ) . GetRaftIndex ( )
}
if cidx == 0 && existing != nil {
return false , nil
}
if cidx != 0 && existing == nil {
return false , nil
}
if existing != nil && cidx != 0 && cidx != existingIdx . ModifyIndex {
return false , nil
}
if err := ensureConfigEntryTxn ( tx , idx , true , conf ) ; err != nil {
2019-03-27 23:52:38 +00:00
return false , err
}
2020-06-02 20:34:56 +00:00
err = tx . Commit ( )
return err == nil , err
2019-03-27 23:52:38 +00:00
}
2021-11-01 16:42:01 +00:00
// DeleteConfigEntryCAS performs a check-and-set deletion of a config entry
// with the given raft index. If the index is not specified, or is not equal
// to the entry's current ModifyIndex then the call is a noop, otherwise the
// normal deletion is performed.
func ( s * Store ) DeleteConfigEntryCAS ( idx , cidx uint64 , conf structs . ConfigEntry ) ( bool , error ) {
tx := s . db . WriteTxn ( idx )
defer tx . Abort ( )
existing , err := tx . First ( tableConfigEntries , indexID , newConfigEntryQuery ( conf ) )
if err != nil {
return false , fmt . Errorf ( "failed config entry lookup: %s" , err )
}
if existing == nil {
return false , nil
}
if existing . ( structs . ConfigEntry ) . GetRaftIndex ( ) . ModifyIndex != cidx {
return false , nil
}
if err := deleteConfigEntryTxn (
tx ,
idx ,
conf . GetKind ( ) ,
conf . GetName ( ) ,
conf . GetEnterpriseMeta ( ) ,
) ; err != nil {
return false , err
}
err = tx . Commit ( )
return err == nil , err
}
2022-04-05 21:10:06 +00:00
func ( s * Store ) DeleteConfigEntry ( idx uint64 , kind , name string , entMeta * acl . EnterpriseMeta ) error {
2020-03-19 13:11:20 +00:00
tx := s . db . WriteTxn ( idx )
2019-03-27 23:52:38 +00:00
defer tx . Abort ( )
2019-03-19 22:56:17 +00:00
2020-11-13 20:42:21 +00:00
if err := deleteConfigEntryTxn ( tx , idx , kind , name , entMeta ) ; err != nil {
return err
}
return tx . Commit ( )
}
2021-02-09 17:37:57 +00:00
// TODO: accept structs.ConfigEntry instead of individual fields
2022-04-05 21:10:06 +00:00
func deleteConfigEntryTxn ( tx WriteTxn , idx uint64 , kind , name string , entMeta * acl . EnterpriseMeta ) error {
2022-02-22 16:36:36 +00:00
q := configentry . NewKindName ( kind , name , entMeta )
2021-04-15 22:12:35 +00:00
existing , err := tx . First ( tableConfigEntries , indexID , q )
2019-03-19 22:56:17 +00:00
if err != nil {
return fmt . Errorf ( "failed config entry lookup: %s" , err )
}
if existing == nil {
return nil
}
2020-04-16 21:00:48 +00:00
// If the config entry is for terminating or ingress gateways we delete entries from the memdb table
2020-04-08 18:37:24 +00:00
// that associates gateways <-> services.
2020-10-08 15:47:09 +00:00
sn := structs . NewServiceName ( name , entMeta )
2020-04-16 21:00:48 +00:00
if kind == structs . TerminatingGateway || kind == structs . IngressGateway {
2021-03-16 17:54:05 +00:00
if _ , err := tx . DeleteAll ( tableGatewayServices , indexGateway , sn ) ; err != nil {
2020-04-08 18:37:24 +00:00
return fmt . Errorf ( "failed to truncate gateway services table: %v" , err )
}
2021-01-29 01:48:51 +00:00
if err := indexUpdateMaxTxn ( tx , idx , tableGatewayServices ) ; err != nil {
2020-04-16 21:00:48 +00:00
return fmt . Errorf ( "failed updating gateway-services index: %v" , err )
2020-04-08 18:37:24 +00:00
}
}
2022-05-31 20:20:12 +00:00
c := existing . ( structs . ConfigEntry )
switch x := c . ( type ) {
case * structs . ServiceConfigEntry :
if x . Destination != nil {
gsKind , err := GatewayServiceKind ( tx , sn . Name , & sn . EnterpriseMeta )
if err != nil {
return fmt . Errorf ( "failed to get gateway service kind for service %s: %v" , sn . Name , err )
}
if gsKind == structs . GatewayServiceKindDestination {
gsKind = structs . GatewayServiceKindUnknown
}
2022-06-07 19:03:59 +00:00
serviceName := structs . NewServiceName ( c . GetName ( ) , c . GetEnterpriseMeta ( ) )
2022-07-28 19:51:01 +00:00
if err := checkGatewayWildcardsAndUpdate ( tx , idx , & serviceName , nil , gsKind ) ; err != nil {
2022-05-31 20:20:12 +00:00
return fmt . Errorf ( "failed updating gateway mapping: %s" , err )
}
2022-08-08 17:44:18 +00:00
if err := cleanupGatewayWildcards ( tx , idx , serviceName , true ) ; err != nil {
return fmt . Errorf ( "failed to cleanup gateway mapping: \"%s\"; err: %v" , serviceName , err )
}
2022-06-07 19:03:59 +00:00
if err := checkGatewayAndUpdate ( tx , idx , & serviceName , gsKind ) ; err != nil {
2022-05-31 20:20:12 +00:00
return fmt . Errorf ( "failed updating gateway mapping: %s" , err )
}
2022-06-07 19:03:59 +00:00
if err := cleanupKindServiceName ( tx , idx , serviceName , structs . ServiceKindDestination ) ; err != nil {
return fmt . Errorf ( "failed to cleanup service name: \"%s\"; err: %v" , serviceName , err )
}
2022-05-31 20:20:12 +00:00
}
}
2020-10-08 15:47:09 +00:00
// Also clean up associations in the mesh topology table for ingress gateways
if kind == structs . IngressGateway {
2021-03-16 17:15:14 +00:00
if _ , err := tx . DeleteAll ( tableMeshTopology , indexDownstream , sn ) ; err != nil {
2021-01-29 01:48:51 +00:00
return fmt . Errorf ( "failed to truncate %s table: %v" , tableMeshTopology , err )
2020-10-08 15:47:09 +00:00
}
2021-01-29 01:48:51 +00:00
if err := indexUpdateMaxTxn ( tx , idx , tableMeshTopology ) ; err != nil {
return fmt . Errorf ( "failed updating %s index: %v" , tableMeshTopology , err )
2020-10-08 15:47:09 +00:00
}
}
2020-04-08 18:37:24 +00:00
2021-04-15 22:12:35 +00:00
err = validateProposedConfigEntryInGraph ( tx , q , nil )
2019-07-01 20:23:36 +00:00
if err != nil {
return err // Err is already sufficiently decorated.
}
2019-03-19 22:56:17 +00:00
// Delete the config entry from the DB and update the index.
2021-01-29 01:34:34 +00:00
if err := tx . Delete ( tableConfigEntries , existing ) ; err != nil {
2020-10-06 18:24:05 +00:00
return fmt . Errorf ( "failed removing config entry: %s" , err )
2019-03-19 22:56:17 +00:00
}
2021-03-08 18:02:41 +00:00
if err := tx . Insert ( tableIndex , & IndexEntry { tableConfigEntries , idx } ) ; err != nil {
2019-03-19 22:56:17 +00:00
return fmt . Errorf ( "failed updating index: %s" , err )
}
2020-11-13 20:42:21 +00:00
return nil
2019-03-19 22:56:17 +00:00
}
2019-07-01 20:23:36 +00:00
2020-11-13 20:42:21 +00:00
func insertConfigEntryWithTxn ( tx WriteTxn , idx uint64 , conf structs . ConfigEntry ) error {
2020-05-08 18:24:33 +00:00
if conf == nil {
return fmt . Errorf ( "cannot insert nil config entry" )
}
2023-02-08 21:52:12 +00:00
2020-05-08 18:24:33 +00:00
// If the config entry is for a terminating or ingress gateway we update the memdb table
// that associates gateways <-> services.
if conf . GetKind ( ) == structs . TerminatingGateway || conf . GetKind ( ) == structs . IngressGateway {
2020-07-10 00:56:43 +00:00
err := updateGatewayServices ( tx , idx , conf , conf . GetEnterpriseMeta ( ) )
2020-05-08 18:24:33 +00:00
if err != nil {
return fmt . Errorf ( "failed to associate services to gateway: %v" , err )
}
}
2022-05-31 20:20:12 +00:00
switch conf . GetKind ( ) {
case structs . ServiceDefaults :
if conf . ( * structs . ServiceConfigEntry ) . Destination != nil {
sn := structs . ServiceName { Name : conf . GetName ( ) , EnterpriseMeta : * conf . GetEnterpriseMeta ( ) }
gsKind , err := GatewayServiceKind ( tx , sn . Name , & sn . EnterpriseMeta )
if gsKind == structs . GatewayServiceKindUnknown {
gsKind = structs . GatewayServiceKindDestination
}
if err != nil {
return fmt . Errorf ( "failed updating gateway mapping: %s" , err )
}
2022-07-28 19:51:01 +00:00
if err := checkGatewayWildcardsAndUpdate ( tx , idx , & sn , nil , gsKind ) ; err != nil {
2022-05-31 20:20:12 +00:00
return fmt . Errorf ( "failed updating gateway mapping: %s" , err )
}
if err := checkGatewayAndUpdate ( tx , idx , & sn , gsKind ) ; err != nil {
return fmt . Errorf ( "failed updating gateway mapping: %s" , err )
}
2022-06-07 19:03:59 +00:00
if err := upsertKindServiceName ( tx , idx , structs . ServiceKindDestination , sn ) ; err != nil {
return fmt . Errorf ( "failed to persist service name: %v" , err )
}
2022-05-31 20:20:12 +00:00
}
2023-03-13 21:19:11 +00:00
case structs . SamenessGroup :
err := checkSamenessGroup ( tx , conf )
if err != nil {
return err
}
2022-05-31 20:20:12 +00:00
}
2020-05-08 18:24:33 +00:00
// Insert the config entry and update the index
2021-01-29 01:34:34 +00:00
if err := tx . Insert ( tableConfigEntries , conf ) ; err != nil {
2020-05-08 18:24:33 +00:00
return fmt . Errorf ( "failed inserting config entry: %s" , err )
}
2021-01-29 01:34:34 +00:00
if err := indexUpdateMaxTxn ( tx , idx , tableConfigEntries ) ; err != nil {
2020-05-08 18:24:33 +00:00
return fmt . Errorf ( "failed updating index: %v" , err )
}
return nil
}
2019-07-01 20:23:36 +00:00
// validateProposedConfigEntryInGraph can be used to verify graph integrity for
// a proposed graph create/update/delete.
//
// This must be called before any mutations occur on the config entries table!
//
// May return *ConfigEntryGraphValidationError if there is a concern to surface
// to the caller that they can correct.
2020-08-11 20:31:23 +00:00
func validateProposedConfigEntryInGraph (
tx ReadTxn ,
2022-02-22 16:36:36 +00:00
kindName configentry . KindName ,
2021-04-15 22:12:35 +00:00
newEntry structs . ConfigEntry ,
2019-07-01 20:23:36 +00:00
) error {
2021-04-15 22:12:35 +00:00
switch kindName . Kind {
2019-07-01 20:23:36 +00:00
case structs . ProxyDefaults :
2021-04-15 22:12:35 +00:00
// TODO: why handle an invalid case?
if kindName . Name != structs . ProxyConfigGlobal {
2019-07-02 16:01:17 +00:00
return nil
}
2019-07-01 20:23:36 +00:00
case structs . ServiceDefaults :
case structs . ServiceRouter :
case structs . ServiceSplitter :
case structs . ServiceResolver :
2020-03-31 16:59:10 +00:00
case structs . IngressGateway :
2021-04-15 22:12:35 +00:00
err := checkGatewayClash ( tx , kindName , structs . TerminatingGateway )
2020-04-16 21:00:48 +00:00
if err != nil {
return err
}
2020-03-31 19:27:32 +00:00
case structs . TerminatingGateway :
2021-04-15 22:12:35 +00:00
err := checkGatewayClash ( tx , kindName , structs . IngressGateway )
2020-04-16 21:00:48 +00:00
if err != nil {
return err
}
2023-03-13 21:19:11 +00:00
case structs . SamenessGroup :
2020-10-06 18:24:05 +00:00
case structs . ServiceIntentions :
2021-04-28 22:13:29 +00:00
case structs . MeshConfig :
2021-12-03 06:50:38 +00:00
case structs . ExportedServices :
2023-01-18 22:14:34 +00:00
case structs . APIGateway : // TODO Consider checkGatewayClash
case structs . BoundAPIGateway :
case structs . InlineCertificate :
case structs . HTTPRoute :
case structs . TCPRoute :
2023-03-15 18:21:24 +00:00
case structs . RateLimitIPConfig :
2019-07-01 20:23:36 +00:00
default :
2021-04-15 22:12:35 +00:00
return fmt . Errorf ( "unhandled kind %q during validation of %q" , kindName . Kind , kindName . Name )
2019-07-01 20:23:36 +00:00
}
2019-07-02 16:01:17 +00:00
2021-04-15 22:12:35 +00:00
return validateProposedConfigEntryInServiceGraph ( tx , kindName , newEntry )
2019-07-02 16:01:17 +00:00
}
2022-02-22 16:36:36 +00:00
func checkGatewayClash ( tx ReadTxn , kindName configentry . KindName , otherKind string ) error {
2021-04-15 22:12:35 +00:00
_ , entry , err := configEntryTxn ( tx , nil , otherKind , kindName . Name , & kindName . EnterpriseMeta )
2020-04-16 21:00:48 +00:00
if err != nil {
return err
}
if entry != nil {
return fmt . Errorf ( "cannot create a %q config entry with name %q, " +
2021-04-15 22:12:35 +00:00
"a %q config entry with that name already exists" , kindName . Kind , kindName . Name , otherKind )
2020-04-16 21:00:48 +00:00
}
return nil
}
2019-07-02 16:01:17 +00:00
var serviceGraphKinds = [ ] string {
structs . ServiceRouter ,
structs . ServiceSplitter ,
structs . ServiceResolver ,
2019-07-01 20:23:36 +00:00
}
2020-09-29 01:42:03 +00:00
// discoveryChainTargets will return a list of services listed as a target for the input's discovery chain
2022-04-05 21:10:06 +00:00
func ( s * Store ) discoveryChainTargetsTxn ( tx ReadTxn , ws memdb . WatchSet , dc , service string , entMeta * acl . EnterpriseMeta ) ( uint64 , [ ] structs . ServiceName , error ) {
2022-07-12 16:03:41 +00:00
idx , targets , err := discoveryChainOriginalTargetsTxn ( tx , ws , dc , service , entMeta )
2020-09-27 20:24:42 +00:00
if err != nil {
2022-06-27 19:37:18 +00:00
return 0 , nil , err
2020-09-27 20:24:42 +00:00
}
var resp [ ] structs . ServiceName
2022-06-27 19:37:18 +00:00
for _ , t := range targets {
2022-04-05 21:10:06 +00:00
em := acl . NewEnterpriseMetaWithPartition ( entMeta . PartitionOrDefault ( ) , t . Namespace )
2021-09-17 23:24:51 +00:00
target := structs . NewServiceName ( t . Service , & em )
2020-09-27 20:24:42 +00:00
// TODO (freddy): Allow upstream DC and encode in response
if t . Datacenter == dc {
resp = append ( resp , target )
}
}
return idx , resp , nil
}
2022-07-12 16:03:41 +00:00
func discoveryChainOriginalTargetsTxn (
tx ReadTxn ,
ws memdb . WatchSet ,
dc , service string ,
entMeta * acl . EnterpriseMeta ,
) ( uint64 , [ ] * structs . DiscoveryTarget , error ) {
2022-06-27 19:37:18 +00:00
source := structs . NewServiceName ( service , entMeta )
req := discoverychain . CompileRequest {
ServiceName : source . Name ,
EvaluateInNamespace : source . NamespaceOrDefault ( ) ,
EvaluateInPartition : source . PartitionOrDefault ( ) ,
EvaluateInDatacenter : dc ,
}
2022-07-12 16:03:41 +00:00
idx , chain , _ , err := serviceDiscoveryChainTxn ( tx , ws , source . Name , entMeta , req )
2022-06-27 19:37:18 +00:00
if err != nil {
return 0 , nil , fmt . Errorf ( "failed to fetch discovery chain for %q: %v" , source . String ( ) , err )
}
return idx , maps . SliceOfValues ( chain . Targets ) , nil
}
2020-10-02 00:10:49 +00:00
// discoveryChainSourcesTxn will return a list of services whose discovery chains have the given service as a target
func ( s * Store ) discoveryChainSourcesTxn ( tx ReadTxn , ws memdb . WatchSet , dc string , destination structs . ServiceName ) ( uint64 , [ ] structs . ServiceName , error ) {
seenLink := map [ structs . ServiceName ] bool { destination : true }
2020-09-22 22:05:09 +00:00
2020-10-02 00:10:49 +00:00
queue := [ ] structs . ServiceName { destination }
2020-09-22 22:05:09 +00:00
for len ( queue ) > 0 {
// The "link" index returns config entries that reference a service
2021-04-15 17:26:53 +00:00
iter , err := tx . Get ( tableConfigEntries , indexLink , queue [ 0 ] . ToServiceID ( ) )
2020-09-22 22:05:09 +00:00
if err != nil {
return 0 , nil , err
}
ws . Add ( iter . WatchCh ( ) )
for raw := iter . Next ( ) ; raw != nil ; raw = iter . Next ( ) {
entry := raw . ( structs . ConfigEntry )
sn := structs . NewServiceName ( entry . GetName ( ) , entry . GetEnterpriseMeta ( ) )
if ! seenLink [ sn ] {
seenLink [ sn ] = true
queue = append ( queue , sn )
}
}
queue = queue [ 1 : ]
}
var (
2020-10-02 00:10:49 +00:00
maxIdx uint64 = 1
2020-09-22 22:05:09 +00:00
resp [ ] structs . ServiceName
)
2020-10-02 00:10:49 +00:00
// Only return the services that target the destination anywhere in their discovery chains.
2020-09-22 22:05:09 +00:00
seenSource := make ( map [ structs . ServiceName ] bool )
2020-09-30 14:23:19 +00:00
for sn := range seenLink {
2020-09-22 22:05:09 +00:00
req := discoverychain . CompileRequest {
2020-10-02 00:10:49 +00:00
ServiceName : sn . Name ,
EvaluateInNamespace : sn . NamespaceOrDefault ( ) ,
2021-09-07 20:29:32 +00:00
EvaluateInPartition : sn . PartitionOrDefault ( ) ,
2020-09-22 22:05:09 +00:00
EvaluateInDatacenter : dc ,
}
2022-07-12 16:03:41 +00:00
idx , chain , _ , err := serviceDiscoveryChainTxn ( tx , ws , sn . Name , & sn . EnterpriseMeta , req )
2020-09-22 22:05:09 +00:00
if err != nil {
return 0 , nil , fmt . Errorf ( "failed to fetch discovery chain for %q: %v" , sn . String ( ) , err )
}
for _ , t := range chain . Targets {
2022-04-05 21:10:06 +00:00
em := acl . NewEnterpriseMetaWithPartition ( sn . PartitionOrDefault ( ) , t . Namespace )
2021-09-17 23:24:51 +00:00
candidate := structs . NewServiceName ( t . Service , & em )
2020-09-22 22:05:09 +00:00
2020-12-11 21:10:00 +00:00
if ! candidate . Matches ( destination ) {
2020-09-22 22:05:09 +00:00
continue
}
if idx > maxIdx {
maxIdx = idx
}
if ! seenSource [ sn ] {
seenSource [ sn ] = true
resp = append ( resp , sn )
}
}
}
return maxIdx , resp , nil
}
2020-08-11 20:31:23 +00:00
func validateProposedConfigEntryInServiceGraph (
tx ReadTxn ,
2022-02-22 16:36:36 +00:00
kindName configentry . KindName ,
2021-04-15 22:12:35 +00:00
newEntry structs . ConfigEntry ,
2019-07-01 20:23:36 +00:00
) error {
// Collect all of the chains that could be affected by this change
// including our own.
2020-08-12 16:19:20 +00:00
var (
checkChains = make ( map [ structs . ServiceID ] struct { } )
checkIngress [ ] * structs . IngressGatewayConfigEntry
2020-10-06 22:09:13 +00:00
checkIntentions [ ] * structs . ServiceIntentionsConfigEntry
2020-08-12 16:19:20 +00:00
enforceIngressProtocolsMatch bool
)
2019-07-02 16:01:17 +00:00
2021-09-17 23:36:20 +00:00
wildcardEntMeta := kindName . WithWildcardNamespace ( )
2021-08-20 19:34:23 +00:00
2021-04-15 22:12:35 +00:00
switch kindName . Kind {
2022-07-12 16:03:41 +00:00
case structs . ExportedServices :
// This is the case for deleting a config entry
if newEntry == nil {
return nil
}
entry := newEntry . ( * structs . ExportedServicesConfigEntry )
_ , serviceList , err := listServicesExportedToAnyPeerByConfigEntry ( nil , tx , entry , nil )
if err != nil {
return err
}
for _ , sn := range serviceList {
if err := validateChainIsPeerExportSafe ( tx , sn , nil ) ; err != nil {
return err
}
}
return nil
case structs . MeshConfig :
2022-05-23 16:16:39 +00:00
// Exported services and mesh config do not influence discovery chains.
return nil
2021-04-15 22:12:35 +00:00
case structs . ProxyDefaults :
2019-07-02 16:01:17 +00:00
// Check anything that has a discovery chain entry. In the future we could
// somehow omit the ones that have a default protocol configured.
for _ , kind := range serviceGraphKinds {
2021-08-20 19:34:23 +00:00
_ , entries , err := configEntriesByKindTxn ( tx , nil , kind , wildcardEntMeta )
2019-07-02 16:01:17 +00:00
if err != nil {
return err
}
for _ , entry := range entries {
2020-01-24 15:04:58 +00:00
checkChains [ structs . NewServiceID ( entry . GetName ( ) , entry . GetEnterpriseMeta ( ) ) ] = struct { } { }
2019-07-02 16:01:17 +00:00
}
}
2020-08-12 16:19:20 +00:00
2021-08-20 19:34:23 +00:00
_ , ingressEntries , err := configEntriesByKindTxn ( tx , nil , structs . IngressGateway , wildcardEntMeta )
2020-08-12 16:19:20 +00:00
if err != nil {
return err
}
2020-10-06 22:09:13 +00:00
for _ , entry := range ingressEntries {
2020-08-12 16:19:20 +00:00
ingress , ok := entry . ( * structs . IngressGatewayConfigEntry )
if ! ok {
return fmt . Errorf ( "type %T is not an ingress gateway config entry" , entry )
}
checkIngress = append ( checkIngress , ingress )
}
2021-08-20 19:34:23 +00:00
_ , ixnEntries , err := configEntriesByKindTxn ( tx , nil , structs . ServiceIntentions , wildcardEntMeta )
2020-10-06 22:09:13 +00:00
if err != nil {
return err
}
for _ , entry := range ixnEntries {
ixn , ok := entry . ( * structs . ServiceIntentionsConfigEntry )
if ! ok {
return fmt . Errorf ( "type %T is not a service intentions config entry" , entry )
}
checkIntentions = append ( checkIntentions , ixn )
}
2021-04-15 22:12:35 +00:00
case structs . ServiceIntentions :
2020-10-06 22:09:13 +00:00
// Check that the protocols match.
// This is the case for deleting a config entry
2021-04-15 22:12:35 +00:00
if newEntry == nil {
2020-10-06 22:09:13 +00:00
return nil
}
2021-04-15 22:12:35 +00:00
ixn , ok := newEntry . ( * structs . ServiceIntentionsConfigEntry )
2020-10-06 22:09:13 +00:00
if ! ok {
2021-04-15 22:12:35 +00:00
return fmt . Errorf ( "type %T is not a service intentions config entry" , newEntry )
2020-10-06 22:09:13 +00:00
}
checkIntentions = append ( checkIntentions , ixn )
2021-04-15 22:12:35 +00:00
case structs . IngressGateway :
2020-08-12 16:19:20 +00:00
// Checking an ingress pointing to multiple chains.
// This is the case for deleting a config entry
2021-04-15 22:12:35 +00:00
if newEntry == nil {
2020-08-12 16:19:20 +00:00
return nil
}
2021-04-15 22:12:35 +00:00
ingress , ok := newEntry . ( * structs . IngressGatewayConfigEntry )
2020-08-12 16:19:20 +00:00
if ! ok {
2021-04-15 22:12:35 +00:00
return fmt . Errorf ( "type %T is not an ingress gateway config entry" , newEntry )
2020-08-12 16:19:20 +00:00
}
checkIngress = append ( checkIngress , ingress )
// When editing an ingress-gateway directly we are stricter about
// validating the protocol equivalence.
enforceIngressProtocolsMatch = true
2021-04-15 22:12:35 +00:00
default :
2019-07-02 16:01:17 +00:00
// Must be a single chain.
2019-07-01 20:23:36 +00:00
2020-10-06 22:09:13 +00:00
// Check to see if we should ensure L7 intentions have an L7 protocol.
_ , ixn , err := getServiceIntentionsConfigEntryTxn (
2021-04-15 22:12:35 +00:00
tx , nil , kindName . Name , nil , & kindName . EnterpriseMeta ,
2020-10-06 22:09:13 +00:00
)
if err != nil {
return err
} else if ixn != nil {
checkIntentions = append ( checkIntentions , ixn )
}
2021-08-20 19:34:23 +00:00
_ , ixnEntries , err := configEntriesByKindTxn ( tx , nil , structs . ServiceIntentions , wildcardEntMeta )
2020-10-06 22:09:13 +00:00
if err != nil {
return err
}
for _ , entry := range ixnEntries {
ixn , ok := entry . ( * structs . ServiceIntentionsConfigEntry )
if ! ok {
return fmt . Errorf ( "type %T is not a service intentions config entry" , entry )
}
checkIntentions = append ( checkIntentions , ixn )
}
2021-04-15 22:12:35 +00:00
sid := structs . NewServiceID ( kindName . Name , & kindName . EnterpriseMeta )
2020-01-24 15:04:58 +00:00
checkChains [ sid ] = struct { } { }
2019-07-02 16:01:17 +00:00
2021-04-15 17:26:53 +00:00
iter , err := tx . Get ( tableConfigEntries , indexLink , sid )
2020-06-23 17:18:22 +00:00
if err != nil {
return err
}
2019-07-02 16:01:17 +00:00
for raw := iter . Next ( ) ; raw != nil ; raw = iter . Next ( ) {
entry := raw . ( structs . ConfigEntry )
2020-08-12 16:19:20 +00:00
switch entry . GetKind ( ) {
case structs . ServiceRouter , structs . ServiceSplitter , structs . ServiceResolver :
svcID := structs . NewServiceID ( entry . GetName ( ) , entry . GetEnterpriseMeta ( ) )
checkChains [ svcID ] = struct { } { }
case structs . IngressGateway :
ingress , ok := entry . ( * structs . IngressGatewayConfigEntry )
if ! ok {
return fmt . Errorf ( "type %T is not an ingress gateway config entry" , entry )
}
checkIngress = append ( checkIngress , ingress )
}
}
}
2020-10-06 22:09:13 +00:00
// Ensure if any ingress or intention is affected that we fetch all of the
// chains needed to fully validate them.
2020-08-12 16:19:20 +00:00
for _ , ingress := range checkIngress {
for _ , svcID := range ingress . ListRelatedServices ( ) {
checkChains [ svcID ] = struct { } { }
2019-07-02 16:01:17 +00:00
}
2019-07-01 20:23:36 +00:00
}
2020-10-06 22:09:13 +00:00
for _ , ixn := range checkIntentions {
sn := ixn . DestinationServiceName ( )
checkChains [ sn . ToServiceID ( ) ] = struct { } { }
}
2019-07-01 20:23:36 +00:00
2022-02-22 16:36:36 +00:00
overrides := map [ configentry . KindName ] structs . ConfigEntry {
2021-04-15 22:12:35 +00:00
kindName : newEntry ,
2019-07-01 20:23:36 +00:00
}
2020-08-12 16:19:20 +00:00
var (
2022-07-12 16:03:41 +00:00
svcProtocols = make ( map [ structs . ServiceID ] string )
svcTopNodeType = make ( map [ structs . ServiceID ] string )
exportedServicesByPartition = make ( map [ string ] map [ structs . ServiceName ] struct { } )
2020-08-12 16:19:20 +00:00
)
2020-06-16 17:19:31 +00:00
for chain := range checkChains {
2022-07-12 16:17:33 +00:00
protocol , topNode , newTargets , err := testCompileDiscoveryChain ( tx , chain . ID , overrides , & chain . EnterpriseMeta )
2020-08-12 16:19:20 +00:00
if err != nil {
2019-07-01 20:23:36 +00:00
return err
}
2020-08-12 16:19:20 +00:00
svcProtocols [ chain ] = protocol
svcTopNodeType [ chain ] = topNode . Type
2022-07-12 16:03:41 +00:00
chainSvc := structs . NewServiceName ( chain . ID , & chain . EnterpriseMeta )
// Validate that we aren't adding a cross-datacenter or cross-partition
// reference to a peer-exported service's discovery chain by this pending
// edit.
partition := chain . PartitionOrDefault ( )
exportedServices , ok := exportedServicesByPartition [ partition ]
if ! ok {
entMeta := structs . NodeEnterpriseMetaInPartition ( partition )
_ , exportedServices , err = listAllExportedServices ( nil , tx , overrides , * entMeta )
if err != nil {
return err
}
exportedServicesByPartition [ partition ] = exportedServices
}
if _ , exported := exportedServices [ chainSvc ] ; exported {
if err := validateChainIsPeerExportSafe ( tx , chainSvc , overrides ) ; err != nil {
return err
}
2022-07-12 16:17:33 +00:00
// If a TCP (L4) discovery chain is peer exported we have to take
// care to prohibit certain edits to service-resolvers.
if ! structs . IsProtocolHTTPLike ( protocol ) {
_ , _ , oldTargets , err := testCompileDiscoveryChain ( tx , chain . ID , nil , & chain . EnterpriseMeta )
if err != nil {
return fmt . Errorf ( "error compiling current discovery chain for %q: %w" , chainSvc , err )
}
// Ensure that you can't introduce any new targets that would
// produce a new SpiffeID for this L4 service.
oldSpiffeIDs := convertTargetsToTestSpiffeIDs ( oldTargets )
newSpiffeIDs := convertTargetsToTestSpiffeIDs ( newTargets )
for id , targetID := range newSpiffeIDs {
if _ , exists := oldSpiffeIDs [ id ] ; ! exists {
return fmt . Errorf ( "peer exported service %q uses protocol=%q and cannot introduce new discovery chain targets like %q" ,
chainSvc , protocol , targetID ,
)
}
}
}
2022-07-12 16:03:41 +00:00
}
2020-08-12 16:19:20 +00:00
}
// Now validate all of our ingress gateways.
for _ , e := range checkIngress {
for _ , listener := range e . Listeners {
expectedProto := listener . Protocol
for _ , service := range listener . Services {
if service . Name == structs . WildcardSpecifier {
continue
}
svcID := structs . NewServiceID ( service . Name , & service . EnterpriseMeta )
svcProto := svcProtocols [ svcID ]
if svcProto != expectedProto {
// The only time an ingress gateway and its upstreams can
// have differing protocols is when:
//
// 1. ingress is tcp and the target is not-tcp
// AND
// 2. the disco chain has a resolver as the top node
topNodeType := svcTopNodeType [ svcID ]
if enforceIngressProtocolsMatch ||
( expectedProto != "tcp" ) ||
( expectedProto == "tcp" && topNodeType != structs . DiscoveryGraphNodeTypeResolver ) {
return fmt . Errorf (
"service %q has protocol %q, which does not match defined listener protocol %q" ,
svcID . String ( ) ,
svcProto ,
expectedProto ,
)
}
}
}
}
2019-07-01 20:23:36 +00:00
}
2020-10-06 22:09:13 +00:00
// Now validate that intentions with L7 permissions reference HTTP services
for _ , e := range checkIntentions {
// We only have to double check things that try to use permissions
if e . HasWildcardDestination ( ) || ! e . HasAnyPermissions ( ) {
continue
}
sn := e . DestinationServiceName ( )
svcID := sn . ToServiceID ( )
svcProto := svcProtocols [ svcID ]
if ! structs . IsProtocolHTTPLike ( svcProto ) {
return fmt . Errorf (
"service %q has protocol %q, which is incompatible with L7 intentions permissions" ,
svcID . String ( ) ,
svcProto ,
)
}
}
2019-07-01 20:23:36 +00:00
return nil
}
2022-07-12 16:03:41 +00:00
func validateChainIsPeerExportSafe (
tx ReadTxn ,
exportedSvc structs . ServiceName ,
overrides map [ configentry . KindName ] structs . ConfigEntry ,
) error {
_ , chainEntries , err := readDiscoveryChainConfigEntriesTxn ( tx , nil , exportedSvc . Name , overrides , & exportedSvc . EnterpriseMeta )
if err != nil {
return fmt . Errorf ( "error reading discovery chain for %q during config entry validation: %w" , exportedSvc , err )
}
emptyOrMatchesEntryPartition := func ( entry structs . ConfigEntry , found string ) bool {
if found == "" {
return true
}
return acl . EqualPartitions ( entry . GetEnterpriseMeta ( ) . PartitionOrEmpty ( ) , found )
}
for _ , e := range chainEntries . Routers {
for _ , route := range e . Routes {
if route . Destination == nil {
continue
}
if ! emptyOrMatchesEntryPartition ( e , route . Destination . Partition ) {
return fmt . Errorf ( "peer exported service %q contains cross-partition route destination" , exportedSvc )
}
}
}
for _ , e := range chainEntries . Splitters {
for _ , split := range e . Splits {
if ! emptyOrMatchesEntryPartition ( e , split . Partition ) {
return fmt . Errorf ( "peer exported service %q contains cross-partition split destination" , exportedSvc )
}
}
}
for _ , e := range chainEntries . Resolvers {
if e . Redirect != nil {
if e . Redirect . Datacenter != "" {
return fmt . Errorf ( "peer exported service %q contains cross-datacenter resolver redirect" , exportedSvc )
}
if ! emptyOrMatchesEntryPartition ( e , e . Redirect . Partition ) {
return fmt . Errorf ( "peer exported service %q contains cross-partition resolver redirect" , exportedSvc )
}
}
if e . Failover != nil {
for _ , failover := range e . Failover {
if len ( failover . Datacenters ) > 0 {
return fmt . Errorf ( "peer exported service %q contains cross-datacenter failover" , exportedSvc )
}
}
}
}
return nil
}
2020-08-12 16:19:20 +00:00
// testCompileDiscoveryChain speculatively compiles a discovery chain with
// pending modifications to see if it would be valid. Also returns the computed
// protocol and topmost discovery chain node.
2022-07-12 16:03:41 +00:00
//
// If provided, the overrides map will service reads of specific config entries
// instead of the state store if the config entry kind name is present in the
// map. A nil in the map implies that the config entry should be tombstoned
// during evaluation and treated as erased.
//
// The override map lets us speculatively compile a discovery chain to see if
// doing so would error, so we can ultimately block config entry writes from
// happening.
2020-08-11 20:31:23 +00:00
func testCompileDiscoveryChain (
tx ReadTxn ,
2019-07-02 16:01:17 +00:00
chainName string ,
2022-02-22 16:36:36 +00:00
overrides map [ configentry . KindName ] structs . ConfigEntry ,
2022-04-05 21:10:06 +00:00
entMeta * acl . EnterpriseMeta ,
2022-07-12 16:17:33 +00:00
) ( string , * structs . DiscoveryGraphNode , map [ string ] * structs . DiscoveryTarget , error ) {
2020-08-11 20:31:23 +00:00
_ , speculativeEntries , err := readDiscoveryChainConfigEntriesTxn ( tx , nil , chainName , overrides , entMeta )
2019-07-02 16:01:17 +00:00
if err != nil {
2022-07-12 16:17:33 +00:00
return "" , nil , nil , err
2019-07-02 16:01:17 +00:00
}
// Note we use an arbitrary namespace and datacenter as those would not
// currently affect the graph compilation in ways that matter here.
2019-08-05 18:30:35 +00:00
//
2019-08-19 18:03:03 +00:00
// TODO(rb): we should thread a better value than "dc1" and the throwaway trust domain down here as that is going to sometimes show up in user facing errors
2019-07-02 16:01:17 +00:00
req := discoverychain . CompileRequest {
2019-08-19 18:03:03 +00:00
ServiceName : chainName ,
2020-01-24 15:04:58 +00:00
EvaluateInNamespace : entMeta . NamespaceOrDefault ( ) ,
2021-09-07 20:29:32 +00:00
EvaluateInPartition : entMeta . PartitionOrDefault ( ) ,
2019-08-19 18:03:03 +00:00
EvaluateInDatacenter : "dc1" ,
EvaluateInTrustDomain : "b6fc9da3-03d4-4b5a-9134-c045e9b20152.consul" ,
Entries : speculativeEntries ,
2019-07-02 16:01:17 +00:00
}
2020-08-12 16:19:20 +00:00
chain , err := discoverychain . Compile ( req )
if err != nil {
2022-07-12 16:17:33 +00:00
return "" , nil , nil , err
2020-08-12 16:19:20 +00:00
}
2022-07-12 16:17:33 +00:00
return chain . Protocol , chain . Nodes [ chain . StartNode ] , chain . Targets , nil
2019-07-02 16:01:17 +00:00
}
2020-09-22 22:05:09 +00:00
func ( s * Store ) ServiceDiscoveryChain (
ws memdb . WatchSet ,
serviceName string ,
2022-04-05 21:10:06 +00:00
entMeta * acl . EnterpriseMeta ,
2020-09-22 22:05:09 +00:00
req discoverychain . CompileRequest ,
2022-03-03 22:54:41 +00:00
) ( uint64 , * structs . CompiledDiscoveryChain , * configentry . DiscoveryChainSet , error ) {
2020-10-02 00:10:49 +00:00
tx := s . db . ReadTxn ( )
defer tx . Abort ( )
2022-07-12 16:03:41 +00:00
return serviceDiscoveryChainTxn ( tx , ws , serviceName , entMeta , req )
2020-10-02 00:10:49 +00:00
}
2022-07-12 16:03:41 +00:00
func serviceDiscoveryChainTxn (
2020-10-02 00:10:49 +00:00
tx ReadTxn ,
ws memdb . WatchSet ,
serviceName string ,
2022-04-05 21:10:06 +00:00
entMeta * acl . EnterpriseMeta ,
2020-10-02 00:10:49 +00:00
req discoverychain . CompileRequest ,
2022-03-03 22:54:41 +00:00
) ( uint64 , * structs . CompiledDiscoveryChain , * configentry . DiscoveryChainSet , error ) {
2020-09-22 22:05:09 +00:00
2020-10-02 00:10:49 +00:00
index , entries , err := readDiscoveryChainConfigEntriesTxn ( tx , ws , serviceName , nil , entMeta )
2020-09-22 22:05:09 +00:00
if err != nil {
2022-03-03 22:54:41 +00:00
return 0 , nil , nil , err
2020-09-22 22:05:09 +00:00
}
req . Entries = entries
2022-07-12 16:03:41 +00:00
_ , config , err := caConfigTxn ( tx , ws )
2020-09-22 22:05:09 +00:00
if err != nil {
2022-03-03 22:54:41 +00:00
return 0 , nil , nil , err
2020-09-22 22:05:09 +00:00
} else if config == nil {
2022-03-03 22:54:41 +00:00
return 0 , nil , nil , errors . New ( "no cluster ca config setup" )
2020-09-22 22:05:09 +00:00
}
// Build TrustDomain based on the ClusterID stored.
2021-11-05 22:20:24 +00:00
signingID := connect . SpiffeIDSigningForCluster ( config . ClusterID )
2020-09-22 22:05:09 +00:00
if signingID == nil {
// If CA is bootstrapped at all then this should never happen but be
// defensive.
2022-03-03 22:54:41 +00:00
return 0 , nil , nil , errors . New ( "no cluster trust domain setup" )
2020-09-22 22:05:09 +00:00
}
req . EvaluateInTrustDomain = signingID . Host ( )
// Then we compile it into something useful.
chain , err := discoverychain . Compile ( req )
if err != nil {
2022-03-03 22:54:41 +00:00
return 0 , nil , nil , fmt . Errorf ( "failed to compile discovery chain: %v" , err )
2020-09-22 22:05:09 +00:00
}
2022-03-03 22:54:41 +00:00
return index , chain , entries , nil
2020-09-22 22:05:09 +00:00
}
2022-02-25 21:46:34 +00:00
func ( s * Store ) ReadResolvedServiceConfigEntries (
ws memdb . WatchSet ,
serviceName string ,
2022-04-05 21:10:06 +00:00
entMeta * acl . EnterpriseMeta ,
2022-02-25 21:46:34 +00:00
upstreamIDs [ ] structs . ServiceID ,
proxyMode structs . ProxyMode ,
) ( uint64 , * configentry . ResolvedServiceConfigSet , error ) {
tx := s . db . Txn ( false )
defer tx . Abort ( )
var res configentry . ResolvedServiceConfigSet
// The caller will likely calculate this again, but we need to do it here
// to determine if we are going to traverse into implicit upstream
// definitions.
var inferredProxyMode structs . ProxyMode
index , proxyEntry , err := configEntryTxn ( tx , ws , structs . ProxyDefaults , structs . ProxyConfigGlobal , entMeta )
if err != nil {
return 0 , nil , err
}
maxIndex := index
if proxyEntry != nil {
var ok bool
proxyConf , ok := proxyEntry . ( * structs . ProxyConfigEntry )
if ! ok {
return 0 , nil , fmt . Errorf ( "invalid proxy config type %T" , proxyEntry )
}
res . AddProxyDefaults ( proxyConf )
inferredProxyMode = proxyConf . Mode
}
index , serviceEntry , err := configEntryTxn ( tx , ws , structs . ServiceDefaults , serviceName , entMeta )
if err != nil {
return 0 , nil , err
}
if index > maxIndex {
maxIndex = index
}
var serviceConf * structs . ServiceConfigEntry
if serviceEntry != nil {
var ok bool
serviceConf , ok = serviceEntry . ( * structs . ServiceConfigEntry )
if ! ok {
return 0 , nil , fmt . Errorf ( "invalid service config type %T" , serviceEntry )
}
res . AddServiceDefaults ( serviceConf )
if serviceConf . Mode != structs . ProxyModeDefault {
inferredProxyMode = serviceConf . Mode
}
}
var (
noUpstreamArgs = len ( upstreamIDs ) == 0
// Check the args and the resolved value. If it was exclusively set via a config entry, then proxyMode
// will never be transparent because the service config request does not use the resolved value.
tproxy = proxyMode == structs . ProxyModeTransparent || inferredProxyMode == structs . ProxyModeTransparent
)
// The upstreams passed as arguments to this endpoint are the upstreams explicitly defined in a proxy registration.
// If no upstreams were passed, then we should only return the resolved config if the proxy is in transparent mode.
// Otherwise we would return a resolved upstream config to a proxy with no configured upstreams.
if noUpstreamArgs && ! tproxy {
return maxIndex , & res , nil
}
// First collect all upstreams into a set of seen upstreams.
// Upstreams can come from:
// - Explicitly from proxy registrations, and therefore as an argument to this RPC endpoint
// - Implicitly from centralized upstream config in service-defaults
seenUpstreams := map [ structs . ServiceID ] struct { } { }
for _ , sid := range upstreamIDs {
if _ , ok := seenUpstreams [ sid ] ; ! ok {
seenUpstreams [ sid ] = struct { } { }
}
}
if serviceConf != nil && serviceConf . UpstreamConfig != nil {
for _ , override := range serviceConf . UpstreamConfig . Overrides {
if override . Name == "" {
continue // skip this impossible condition
}
2023-02-03 15:51:53 +00:00
if override . Peer != "" {
continue // Peer services do not have service-defaults config entries to fetch.
}
sid := override . PeeredServiceName ( ) . ServiceName . ToServiceID ( )
seenUpstreams [ sid ] = struct { } { }
2022-02-25 21:46:34 +00:00
}
}
for upstream := range seenUpstreams {
index , rawEntry , err := configEntryTxn ( tx , ws , structs . ServiceDefaults , upstream . ID , & upstream . EnterpriseMeta )
if err != nil {
return 0 , nil , err
}
if index > maxIndex {
maxIndex = index
}
if rawEntry != nil {
entry , ok := rawEntry . ( * structs . ServiceConfigEntry )
if ! ok {
return 0 , nil , fmt . Errorf ( "invalid service config type %T" , rawEntry )
}
res . AddServiceDefaults ( entry )
}
}
return maxIndex , & res , nil
}
2019-07-01 20:23:36 +00:00
// ReadDiscoveryChainConfigEntries will query for the full discovery chain for
// the provided service name. All relevant config entries will be recursively
// fetched and included in the result.
//
// Once returned, the caller still needs to assemble these into a useful graph
// structure.
func ( s * Store ) ReadDiscoveryChainConfigEntries (
ws memdb . WatchSet ,
serviceName string ,
2022-04-05 21:10:06 +00:00
entMeta * acl . EnterpriseMeta ,
2022-02-22 16:36:36 +00:00
) ( uint64 , * configentry . DiscoveryChainSet , error ) {
2020-01-24 15:04:58 +00:00
return s . readDiscoveryChainConfigEntries ( ws , serviceName , nil , entMeta )
2019-07-01 20:23:36 +00:00
}
// readDiscoveryChainConfigEntries will query for the full discovery chain for
// the provided service name. All relevant config entries will be recursively
// fetched and included in the result.
//
// If 'overrides' is provided then it will use entries in that map instead of
// the database to simulate the entries that go into a modified discovery chain
// without actually modifying it yet. Nil values are tombstones to simulate
// deleting an entry.
//
// Overrides is not mutated.
func ( s * Store ) readDiscoveryChainConfigEntries (
ws memdb . WatchSet ,
serviceName string ,
2022-02-22 16:36:36 +00:00
overrides map [ configentry . KindName ] structs . ConfigEntry ,
2022-04-05 21:10:06 +00:00
entMeta * acl . EnterpriseMeta ,
2022-02-22 16:36:36 +00:00
) ( uint64 , * configentry . DiscoveryChainSet , error ) {
2019-07-01 20:23:36 +00:00
tx := s . db . Txn ( false )
defer tx . Abort ( )
2020-08-11 20:31:23 +00:00
return readDiscoveryChainConfigEntriesTxn ( tx , ws , serviceName , overrides , entMeta )
2019-07-01 20:23:36 +00:00
}
2020-08-11 20:31:23 +00:00
func readDiscoveryChainConfigEntriesTxn (
tx ReadTxn ,
2019-07-01 20:23:36 +00:00
ws memdb . WatchSet ,
serviceName string ,
2022-02-22 16:36:36 +00:00
overrides map [ configentry . KindName ] structs . ConfigEntry ,
2022-04-05 21:10:06 +00:00
entMeta * acl . EnterpriseMeta ,
2022-02-22 16:36:36 +00:00
) ( uint64 , * configentry . DiscoveryChainSet , error ) {
res := configentry . NewDiscoveryChainSet ( )
2019-07-01 20:23:36 +00:00
// Note that below we always look up splitters and resolvers in pairs, even
// in some circumstances where both are not strictly necessary.
//
// For now we'll just eat the cost of fetching pairs of splitter/resolver
// config entries even though we may not always need both. In the common
// case we will need the pair so there's not a big drive to optimize this
// here at this time.
// Both Splitters and Resolvers maps will contain placeholder nils until
// the end of this function to indicate "no such entry".
var (
2023-03-17 14:48:06 +00:00
todoSplitters = make ( map [ structs . ServiceID ] struct { } )
todoResolvers = make ( map [ structs . ServiceID ] struct { } )
todoDefaults = make ( map [ structs . ServiceID ] struct { } )
todoPeers = make ( map [ string ] struct { } )
todoSamenessGroups = make ( map [ string ] struct { } )
2019-07-01 20:23:36 +00:00
)
2020-01-24 15:04:58 +00:00
sid := structs . NewServiceID ( serviceName , entMeta )
2021-11-13 01:57:05 +00:00
// At every step we'll need service and proxy defaults.
2020-01-24 15:04:58 +00:00
todoDefaults [ sid ] = struct { } { }
2019-07-01 20:23:36 +00:00
2021-11-13 01:57:05 +00:00
var maxIdx uint64
2019-07-01 20:23:36 +00:00
// first fetch the router, of which we only collect 1 per chain eval
2021-11-13 01:57:05 +00:00
idx , router , err := getRouterConfigEntryTxn ( tx , ws , serviceName , overrides , entMeta )
2019-07-01 20:23:36 +00:00
if err != nil {
return 0 , nil , err
} else if router != nil {
2020-01-24 15:04:58 +00:00
res . Routers [ sid ] = router
2019-07-01 20:23:36 +00:00
}
2021-11-13 01:57:05 +00:00
if idx > maxIdx {
maxIdx = idx
}
2019-07-01 20:23:36 +00:00
if router != nil {
for _ , svc := range router . ListRelatedServices ( ) {
todoSplitters [ svc ] = struct { } { }
}
} else {
// Next hop in the chain is the splitter.
2020-01-24 15:04:58 +00:00
todoSplitters [ sid ] = struct { } { }
2019-07-01 20:23:36 +00:00
}
for {
2020-01-24 15:04:58 +00:00
splitID , ok := anyKey ( todoSplitters )
2019-07-01 20:23:36 +00:00
if ! ok {
break
}
2020-01-24 15:04:58 +00:00
delete ( todoSplitters , splitID )
2019-07-01 20:23:36 +00:00
2020-01-24 15:04:58 +00:00
if _ , ok := res . Splitters [ splitID ] ; ok {
2019-07-01 20:23:36 +00:00
continue // already fetched
}
// Yes, even for splitters.
2020-01-24 15:04:58 +00:00
todoDefaults [ splitID ] = struct { } { }
2019-07-01 20:23:36 +00:00
2021-11-13 01:57:05 +00:00
idx , splitter , err := getSplitterConfigEntryTxn ( tx , ws , splitID . ID , overrides , & splitID . EnterpriseMeta )
2019-07-01 20:23:36 +00:00
if err != nil {
return 0 , nil , err
}
2021-11-13 01:57:05 +00:00
if idx > maxIdx {
maxIdx = idx
}
2019-07-01 20:23:36 +00:00
if splitter == nil {
2020-01-24 15:04:58 +00:00
res . Splitters [ splitID ] = nil
2019-07-01 20:23:36 +00:00
// Next hop in the chain is the resolver.
2020-01-24 15:04:58 +00:00
todoResolvers [ splitID ] = struct { } { }
2019-07-01 20:23:36 +00:00
continue
}
2020-01-24 15:04:58 +00:00
res . Splitters [ splitID ] = splitter
2019-07-01 20:23:36 +00:00
2020-01-24 15:04:58 +00:00
todoResolvers [ splitID ] = struct { } { }
2019-07-01 20:23:36 +00:00
for _ , svc := range splitter . ListRelatedServices ( ) {
// If there is no splitter, this will end up adding a resolver
// after another iteration.
todoSplitters [ svc ] = struct { } { }
}
}
for {
2020-01-24 15:04:58 +00:00
resolverID , ok := anyKey ( todoResolvers )
2019-07-01 20:23:36 +00:00
if ! ok {
break
}
2020-01-24 15:04:58 +00:00
delete ( todoResolvers , resolverID )
2019-07-01 20:23:36 +00:00
2020-01-24 15:04:58 +00:00
if _ , ok := res . Resolvers [ resolverID ] ; ok {
2019-07-01 20:23:36 +00:00
continue // already fetched
}
// And resolvers, too.
2020-01-24 15:04:58 +00:00
todoDefaults [ resolverID ] = struct { } { }
2019-07-01 20:23:36 +00:00
2021-11-13 01:57:05 +00:00
idx , resolver , err := getResolverConfigEntryTxn ( tx , ws , resolverID . ID , overrides , & resolverID . EnterpriseMeta )
2019-07-01 20:23:36 +00:00
if err != nil {
return 0 , nil , err
}
2021-11-13 01:57:05 +00:00
if idx > maxIdx {
maxIdx = idx
}
2019-07-01 20:23:36 +00:00
if resolver == nil {
2020-01-24 15:04:58 +00:00
res . Resolvers [ resolverID ] = nil
2019-07-01 20:23:36 +00:00
continue
}
2020-01-24 15:04:58 +00:00
res . Resolvers [ resolverID ] = resolver
2019-07-01 20:23:36 +00:00
for _ , svc := range resolver . ListRelatedServices ( ) {
todoResolvers [ svc ] = struct { } { }
}
2023-03-10 17:59:47 +00:00
for _ , peer := range resolver . RelatedPeers ( ) {
todoPeers [ peer ] = struct { } { }
}
2023-03-17 14:48:06 +00:00
for _ , peer := range resolver . RelatedSamenessGroups ( ) {
todoSamenessGroups [ peer ] = struct { } { }
}
2019-07-01 20:23:36 +00:00
}
for {
2020-01-24 15:04:58 +00:00
svcID , ok := anyKey ( todoDefaults )
2019-07-01 20:23:36 +00:00
if ! ok {
break
}
2020-01-24 15:04:58 +00:00
delete ( todoDefaults , svcID )
2019-07-01 20:23:36 +00:00
2020-01-24 15:04:58 +00:00
if _ , ok := res . Services [ svcID ] ; ok {
2019-07-01 20:23:36 +00:00
continue // already fetched
}
2021-11-13 01:57:05 +00:00
if _ , ok := res . ProxyDefaults [ svcID . PartitionOrDefault ( ) ] ; ! ok {
idx , proxy , err := getProxyConfigEntryTxn ( tx , ws , structs . ProxyConfigGlobal , overrides , & svcID . EnterpriseMeta )
if err != nil {
return 0 , nil , err
}
if idx > maxIdx {
maxIdx = idx
}
if proxy != nil {
res . ProxyDefaults [ proxy . PartitionOrDefault ( ) ] = proxy
}
}
idx , entry , err := getServiceConfigEntryTxn ( tx , ws , svcID . ID , overrides , & svcID . EnterpriseMeta )
2019-07-01 20:23:36 +00:00
if err != nil {
return 0 , nil , err
}
2021-11-13 01:57:05 +00:00
if idx > maxIdx {
maxIdx = idx
}
2019-07-01 20:23:36 +00:00
if entry == nil {
2020-01-24 15:04:58 +00:00
res . Services [ svcID ] = nil
2019-07-01 20:23:36 +00:00
continue
}
2020-01-24 15:04:58 +00:00
res . Services [ svcID ] = entry
2019-07-01 20:23:36 +00:00
}
2023-03-10 17:59:47 +00:00
peerEntMeta := structs . DefaultEnterpriseMetaInPartition ( entMeta . PartitionOrDefault ( ) )
2023-03-17 14:48:06 +00:00
for sg := range todoSamenessGroups {
idx , entry , err := getSamenessGroupConfigEntryTxn ( tx , ws , sg , overrides , peerEntMeta )
if err != nil {
return 0 , nil , err
}
if idx > maxIdx {
maxIdx = idx
}
if entry == nil {
continue
}
for _ , e := range entry . Members {
if e . Peer != "" {
todoPeers [ e . Peer ] = struct { } { }
}
}
res . SamenessGroups [ sg ] = entry
}
2023-03-10 17:59:47 +00:00
for peerName := range todoPeers {
q := Query {
Value : peerName ,
EnterpriseMeta : * peerEntMeta ,
}
idx , entry , err := peeringReadTxn ( tx , ws , q )
if err != nil {
return 0 , nil , err
}
if idx > maxIdx {
maxIdx = idx
}
res . Peers [ peerName ] = entry
}
2019-07-01 20:23:36 +00:00
// Strip nils now that they are no longer necessary.
2020-01-24 15:04:58 +00:00
for sid , entry := range res . Routers {
2019-07-01 20:23:36 +00:00
if entry == nil {
2020-01-24 15:04:58 +00:00
delete ( res . Routers , sid )
2019-07-01 20:23:36 +00:00
}
}
2020-01-24 15:04:58 +00:00
for sid , entry := range res . Splitters {
2019-07-01 20:23:36 +00:00
if entry == nil {
2020-01-24 15:04:58 +00:00
delete ( res . Splitters , sid )
2019-07-01 20:23:36 +00:00
}
}
2020-01-24 15:04:58 +00:00
for sid , entry := range res . Resolvers {
2019-07-01 20:23:36 +00:00
if entry == nil {
2020-01-24 15:04:58 +00:00
delete ( res . Resolvers , sid )
2019-07-01 20:23:36 +00:00
}
}
2020-01-24 15:04:58 +00:00
for sid , entry := range res . Services {
2019-07-01 20:23:36 +00:00
if entry == nil {
2020-01-24 15:04:58 +00:00
delete ( res . Services , sid )
2019-07-01 20:23:36 +00:00
}
}
2021-12-06 21:36:52 +00:00
return maxIdx , res , nil
2019-07-01 20:23:36 +00:00
}
// anyKey returns any key from the provided map if any exist. Useful for using
// a map as a simple work queue of sorts.
2020-01-24 15:04:58 +00:00
func anyKey ( m map [ structs . ServiceID ] struct { } ) ( structs . ServiceID , bool ) {
2019-07-01 20:23:36 +00:00
if len ( m ) == 0 {
2020-01-24 15:04:58 +00:00
return structs . ServiceID { } , false
2019-07-01 20:23:36 +00:00
}
2020-06-16 17:19:31 +00:00
for k := range m {
2019-07-01 20:23:36 +00:00
return k , true
}
2020-01-24 15:04:58 +00:00
return structs . ServiceID { } , false
2019-07-01 20:23:36 +00:00
}
2019-07-02 16:01:17 +00:00
// getProxyConfigEntryTxn is a convenience method for fetching a
// proxy-defaults kind of config entry.
//
2022-07-12 16:03:41 +00:00
// If an override KEY is present for the requested config entry, the index
// returned will be 0. Any override VALUE (nil or otherwise) will be returned
// if there is a KEY match.
2020-08-11 20:31:23 +00:00
func getProxyConfigEntryTxn (
tx ReadTxn ,
2019-07-02 16:01:17 +00:00
ws memdb . WatchSet ,
name string ,
2022-02-22 16:36:36 +00:00
overrides map [ configentry . KindName ] structs . ConfigEntry ,
2022-04-05 21:10:06 +00:00
entMeta * acl . EnterpriseMeta ,
2019-07-02 16:01:17 +00:00
) ( uint64 , * structs . ProxyConfigEntry , error ) {
2020-07-10 00:56:43 +00:00
idx , entry , err := configEntryWithOverridesTxn ( tx , ws , structs . ProxyDefaults , name , overrides , entMeta )
2019-07-02 16:01:17 +00:00
if err != nil {
return 0 , nil , err
} else if entry == nil {
return idx , nil , nil
}
proxy , ok := entry . ( * structs . ProxyConfigEntry )
if ! ok {
return 0 , nil , fmt . Errorf ( "invalid service config type %T" , entry )
}
return idx , proxy , nil
}
2019-07-01 20:23:36 +00:00
// getServiceConfigEntryTxn is a convenience method for fetching a
// service-defaults kind of config entry.
//
2022-07-12 16:03:41 +00:00
// If an override KEY is present for the requested config entry, the index
// returned will be 0. Any override VALUE (nil or otherwise) will be returned
// if there is a KEY match.
2020-08-11 20:31:23 +00:00
func getServiceConfigEntryTxn (
tx ReadTxn ,
2019-07-01 20:23:36 +00:00
ws memdb . WatchSet ,
serviceName string ,
2022-02-22 16:36:36 +00:00
overrides map [ configentry . KindName ] structs . ConfigEntry ,
2022-04-05 21:10:06 +00:00
entMeta * acl . EnterpriseMeta ,
2019-07-01 20:23:36 +00:00
) ( uint64 , * structs . ServiceConfigEntry , error ) {
2020-07-10 00:56:43 +00:00
idx , entry , err := configEntryWithOverridesTxn ( tx , ws , structs . ServiceDefaults , serviceName , overrides , entMeta )
2019-07-01 20:23:36 +00:00
if err != nil {
return 0 , nil , err
} else if entry == nil {
return idx , nil , nil
}
service , ok := entry . ( * structs . ServiceConfigEntry )
if ! ok {
return 0 , nil , fmt . Errorf ( "invalid service config type %T" , entry )
}
return idx , service , nil
}
// getRouterConfigEntryTxn is a convenience method for fetching a
// service-router kind of config entry.
//
2022-07-12 16:03:41 +00:00
// If an override KEY is present for the requested config entry, the index
// returned will be 0. Any override VALUE (nil or otherwise) will be returned
// if there is a KEY match.
2020-08-11 20:31:23 +00:00
func getRouterConfigEntryTxn (
tx ReadTxn ,
2019-07-01 20:23:36 +00:00
ws memdb . WatchSet ,
serviceName string ,
2022-02-22 16:36:36 +00:00
overrides map [ configentry . KindName ] structs . ConfigEntry ,
2022-04-05 21:10:06 +00:00
entMeta * acl . EnterpriseMeta ,
2019-07-01 20:23:36 +00:00
) ( uint64 , * structs . ServiceRouterConfigEntry , error ) {
2020-07-10 00:56:43 +00:00
idx , entry , err := configEntryWithOverridesTxn ( tx , ws , structs . ServiceRouter , serviceName , overrides , entMeta )
2019-07-01 20:23:36 +00:00
if err != nil {
return 0 , nil , err
} else if entry == nil {
return idx , nil , nil
}
router , ok := entry . ( * structs . ServiceRouterConfigEntry )
if ! ok {
return 0 , nil , fmt . Errorf ( "invalid service config type %T" , entry )
}
return idx , router , nil
}
// getSplitterConfigEntryTxn is a convenience method for fetching a
// service-splitter kind of config entry.
//
2022-07-12 16:03:41 +00:00
// If an override KEY is present for the requested config entry, the index
// returned will be 0. Any override VALUE (nil or otherwise) will be returned
// if there is a KEY match.
2020-08-11 20:31:23 +00:00
func getSplitterConfigEntryTxn (
tx ReadTxn ,
2019-07-01 20:23:36 +00:00
ws memdb . WatchSet ,
serviceName string ,
2022-02-22 16:36:36 +00:00
overrides map [ configentry . KindName ] structs . ConfigEntry ,
2022-04-05 21:10:06 +00:00
entMeta * acl . EnterpriseMeta ,
2019-07-01 20:23:36 +00:00
) ( uint64 , * structs . ServiceSplitterConfigEntry , error ) {
2020-07-10 00:56:43 +00:00
idx , entry , err := configEntryWithOverridesTxn ( tx , ws , structs . ServiceSplitter , serviceName , overrides , entMeta )
2019-07-01 20:23:36 +00:00
if err != nil {
return 0 , nil , err
} else if entry == nil {
return idx , nil , nil
}
splitter , ok := entry . ( * structs . ServiceSplitterConfigEntry )
if ! ok {
return 0 , nil , fmt . Errorf ( "invalid service config type %T" , entry )
}
return idx , splitter , nil
}
// getResolverConfigEntryTxn is a convenience method for fetching a
// service-resolver kind of config entry.
//
2022-07-12 16:03:41 +00:00
// If an override KEY is present for the requested config entry, the index
// returned will be 0. Any override VALUE (nil or otherwise) will be returned
// if there is a KEY match.
2020-08-11 20:31:23 +00:00
func getResolverConfigEntryTxn (
tx ReadTxn ,
2019-07-01 20:23:36 +00:00
ws memdb . WatchSet ,
serviceName string ,
2022-02-22 16:36:36 +00:00
overrides map [ configentry . KindName ] structs . ConfigEntry ,
2022-04-05 21:10:06 +00:00
entMeta * acl . EnterpriseMeta ,
2019-07-01 20:23:36 +00:00
) ( uint64 , * structs . ServiceResolverConfigEntry , error ) {
2020-07-10 00:56:43 +00:00
idx , entry , err := configEntryWithOverridesTxn ( tx , ws , structs . ServiceResolver , serviceName , overrides , entMeta )
2019-07-01 20:23:36 +00:00
if err != nil {
return 0 , nil , err
} else if entry == nil {
return idx , nil , nil
}
resolver , ok := entry . ( * structs . ServiceResolverConfigEntry )
if ! ok {
return 0 , nil , fmt . Errorf ( "invalid service config type %T" , entry )
}
return idx , resolver , nil
}
2020-10-06 18:24:05 +00:00
// getServiceIntentionsConfigEntryTxn is a convenience method for fetching a
// service-intentions kind of config entry.
//
2022-07-12 16:03:41 +00:00
// If an override KEY is present for the requested config entry, the index
// returned will be 0. Any override VALUE (nil or otherwise) will be returned
// if there is a KEY match.
2020-10-06 18:24:05 +00:00
func getServiceIntentionsConfigEntryTxn (
tx ReadTxn ,
ws memdb . WatchSet ,
name string ,
2022-02-22 16:36:36 +00:00
overrides map [ configentry . KindName ] structs . ConfigEntry ,
2022-04-05 21:10:06 +00:00
entMeta * acl . EnterpriseMeta ,
2020-10-06 18:24:05 +00:00
) ( uint64 , * structs . ServiceIntentionsConfigEntry , error ) {
idx , entry , err := configEntryWithOverridesTxn ( tx , ws , structs . ServiceIntentions , name , overrides , entMeta )
if err != nil {
return 0 , nil , err
} else if entry == nil {
return idx , nil , nil
}
ixn , ok := entry . ( * structs . ServiceIntentionsConfigEntry )
if ! ok {
return 0 , nil , fmt . Errorf ( "invalid service config type %T" , entry )
}
return idx , ixn , nil
}
2022-07-12 16:03:41 +00:00
// getExportedServicesConfigEntryTxn is a convenience method for fetching a
// exported-services kind of config entry.
//
// If an override KEY is present for the requested config entry, the index
// returned will be 0. Any override VALUE (nil or otherwise) will be returned
// if there is a KEY match.
func getExportedServicesConfigEntryTxn (
tx ReadTxn ,
ws memdb . WatchSet ,
overrides map [ configentry . KindName ] structs . ConfigEntry ,
entMeta * acl . EnterpriseMeta ,
) ( uint64 , * structs . ExportedServicesConfigEntry , error ) {
idx , entry , err := configEntryWithOverridesTxn ( tx , ws , structs . ExportedServices , entMeta . PartitionOrDefault ( ) , overrides , entMeta )
if err != nil {
return 0 , nil , err
} else if entry == nil {
return idx , nil , nil
}
export , ok := entry . ( * structs . ExportedServicesConfigEntry )
if ! ok {
return 0 , nil , fmt . Errorf ( "invalid service config type %T" , entry )
}
return idx , export , nil
}
2020-07-10 00:56:43 +00:00
func configEntryWithOverridesTxn (
2020-08-11 20:31:23 +00:00
tx ReadTxn ,
2019-07-01 20:23:36 +00:00
ws memdb . WatchSet ,
kind string ,
name string ,
2022-02-22 16:36:36 +00:00
overrides map [ configentry . KindName ] structs . ConfigEntry ,
2022-04-05 21:10:06 +00:00
entMeta * acl . EnterpriseMeta ,
2019-07-01 20:23:36 +00:00
) ( uint64 , structs . ConfigEntry , error ) {
if len ( overrides ) > 0 {
2022-02-22 16:36:36 +00:00
kn := configentry . NewKindName ( kind , name , entMeta )
2021-03-31 20:21:21 +00:00
kn . Normalize ( )
2020-09-02 15:47:19 +00:00
entry , ok := overrides [ kn ]
2019-07-01 20:23:36 +00:00
if ok {
return 0 , entry , nil // a nil entry implies it should act like it is erased
}
}
2020-07-10 00:56:43 +00:00
return configEntryTxn ( tx , ws , kind , name , entMeta )
2019-07-01 20:23:36 +00:00
}
2020-04-23 23:16:04 +00:00
// protocolForService returns the service graph protocol associated to the
// provided service, checking all relevant config entries.
2020-07-10 00:56:43 +00:00
func protocolForService (
2020-08-11 20:31:23 +00:00
tx ReadTxn ,
2020-04-23 23:16:04 +00:00
ws memdb . WatchSet ,
2020-06-12 14:57:41 +00:00
svc structs . ServiceName ,
2020-04-23 23:16:04 +00:00
) ( uint64 , string , error ) {
// Get the global proxy defaults (for default protocol)
2022-07-12 16:03:41 +00:00
maxIdx , proxyConfig , err := getProxyConfigEntryTxn ( tx , ws , structs . ProxyConfigGlobal , nil , & svc . EnterpriseMeta )
2020-04-23 23:16:04 +00:00
if err != nil {
return 0 , "" , err
}
2022-07-12 16:03:41 +00:00
idx , serviceDefaults , err := getServiceConfigEntryTxn ( tx , ws , svc . Name , nil , & svc . EnterpriseMeta )
2020-04-23 23:16:04 +00:00
if err != nil {
return 0 , "" , err
}
maxIdx = lib . MaxUint64 ( maxIdx , idx )
2022-02-22 16:36:36 +00:00
entries := configentry . NewDiscoveryChainSet ( )
2020-04-23 23:16:04 +00:00
if proxyConfig != nil {
entries . AddEntries ( proxyConfig )
}
if serviceDefaults != nil {
entries . AddEntries ( serviceDefaults )
}
req := discoverychain . CompileRequest {
2020-06-12 14:57:41 +00:00
ServiceName : svc . Name ,
2020-04-23 23:16:04 +00:00
EvaluateInNamespace : svc . NamespaceOrDefault ( ) ,
2021-09-07 20:29:32 +00:00
EvaluateInPartition : svc . PartitionOrDefault ( ) ,
2020-04-23 23:16:04 +00:00
EvaluateInDatacenter : "dc1" ,
// Use a dummy trust domain since that won't affect the protocol here.
2022-07-12 16:17:33 +00:00
EvaluateInTrustDomain : dummyTrustDomain ,
2020-04-23 23:16:04 +00:00
Entries : entries ,
}
chain , err := discoverychain . Compile ( req )
if err != nil {
return 0 , "" , err
}
return maxIdx , chain . Protocol , nil
}
2021-02-03 23:25:33 +00:00
2022-07-12 16:17:33 +00:00
const dummyTrustDomain = "b6fc9da3-03d4-4b5a-9134-c045e9b20152.consul"
2022-02-22 16:36:36 +00:00
func newConfigEntryQuery ( c structs . ConfigEntry ) configentry . KindName {
return configentry . NewKindName ( c . GetKind ( ) , c . GetName ( ) , c . GetEnterpriseMeta ( ) )
2021-02-09 17:37:57 +00:00
}
2021-03-10 19:05:43 +00:00
// ConfigEntryKindQuery is used to lookup config entries by their kind.
type ConfigEntryKindQuery struct {
Kind string
2022-04-05 21:10:06 +00:00
acl . EnterpriseMeta
2021-03-10 19:05:43 +00:00
}
2021-03-31 20:21:21 +00:00
// NamespaceOrDefault exists because structs.EnterpriseMeta uses a pointer
// receiver for this method. Remove once that is fixed.
func ( q ConfigEntryKindQuery ) NamespaceOrDefault ( ) string {
return q . EnterpriseMeta . NamespaceOrDefault ( )
}
// PartitionOrDefault exists because structs.EnterpriseMeta uses a pointer
// receiver for this method. Remove once that is fixed.
func ( q ConfigEntryKindQuery ) PartitionOrDefault ( ) string {
return q . EnterpriseMeta . PartitionOrDefault ( )
}
2022-07-12 16:17:33 +00:00
// convertTargetsToTestSpiffeIDs indexes the provided targets by their eventual
// spiffeid values using a dummy trust domain. Returns a map of SpiffeIDs to
// targetID values which can be used for error output.
func convertTargetsToTestSpiffeIDs ( targets map [ string ] * structs . DiscoveryTarget ) map [ string ] string {
out := make ( map [ string ] string )
for tid , t := range targets {
testSpiffeID := connect . SpiffeIDService {
Host : dummyTrustDomain ,
Partition : t . Partition ,
Namespace : t . Namespace ,
Datacenter : t . Datacenter ,
Service : t . Service ,
}
uri := testSpiffeID . URI ( ) . String ( )
if _ , ok := out [ uri ] ; ! ok {
out [ uri ] = tid
}
}
return out
}