connect: add toggle to globally disable wildcard outbound network access when transparent proxy is enabled (#9973)
This adds a new config entry kind "cluster" with a single special name "cluster" where this can be controlled.
This commit is contained in:
parent
a711e119e7
commit
82245585c6
|
@ -0,0 +1,3 @@
|
||||||
|
```release-note:feature
|
||||||
|
connect: add toggle to globally disable wildcard outbound network access when transparent proxy is enabled
|
||||||
|
```
|
|
@ -4095,6 +4095,116 @@ func TestLoad_IntegrationWithFlags(t *testing.T) {
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
|
run(t, testCase{
|
||||||
|
desc: "ConfigEntry bootstrap cluster (snake-case)",
|
||||||
|
args: []string{`-data-dir=` + dataDir},
|
||||||
|
json: []string{`{
|
||||||
|
"config_entries": {
|
||||||
|
"bootstrap": [
|
||||||
|
{
|
||||||
|
"kind": "cluster",
|
||||||
|
"name": "cluster",
|
||||||
|
"meta" : {
|
||||||
|
"foo": "bar",
|
||||||
|
"gir": "zim"
|
||||||
|
},
|
||||||
|
"transparent_proxy": {
|
||||||
|
"catalog_destinations_only": true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}`,
|
||||||
|
},
|
||||||
|
hcl: []string{`
|
||||||
|
config_entries {
|
||||||
|
bootstrap {
|
||||||
|
kind = "cluster"
|
||||||
|
name = "cluster"
|
||||||
|
meta {
|
||||||
|
"foo" = "bar"
|
||||||
|
"gir" = "zim"
|
||||||
|
}
|
||||||
|
transparent_proxy {
|
||||||
|
catalog_destinations_only = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
`,
|
||||||
|
},
|
||||||
|
expected: func(rt *RuntimeConfig) {
|
||||||
|
rt.DataDir = dataDir
|
||||||
|
rt.ConfigEntryBootstrap = []structs.ConfigEntry{
|
||||||
|
&structs.ClusterConfigEntry{
|
||||||
|
Kind: "cluster",
|
||||||
|
Name: "cluster",
|
||||||
|
Meta: map[string]string{
|
||||||
|
"foo": "bar",
|
||||||
|
"gir": "zim",
|
||||||
|
},
|
||||||
|
EnterpriseMeta: *defaultEntMeta,
|
||||||
|
TransparentProxy: structs.TransparentProxyClusterConfig{
|
||||||
|
CatalogDestinationsOnly: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
},
|
||||||
|
})
|
||||||
|
run(t, testCase{
|
||||||
|
desc: "ConfigEntry bootstrap cluster (camel-case)",
|
||||||
|
args: []string{`-data-dir=` + dataDir},
|
||||||
|
json: []string{`{
|
||||||
|
"config_entries": {
|
||||||
|
"bootstrap": [
|
||||||
|
{
|
||||||
|
"Kind": "cluster",
|
||||||
|
"Name": "cluster",
|
||||||
|
"Meta" : {
|
||||||
|
"foo": "bar",
|
||||||
|
"gir": "zim"
|
||||||
|
},
|
||||||
|
"TransparentProxy": {
|
||||||
|
"CatalogDestinationsOnly": true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}`,
|
||||||
|
},
|
||||||
|
hcl: []string{`
|
||||||
|
config_entries {
|
||||||
|
bootstrap {
|
||||||
|
Kind = "cluster"
|
||||||
|
Name = "cluster"
|
||||||
|
Meta {
|
||||||
|
"foo" = "bar"
|
||||||
|
"gir" = "zim"
|
||||||
|
}
|
||||||
|
TransparentProxy {
|
||||||
|
CatalogDestinationsOnly = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
`,
|
||||||
|
},
|
||||||
|
expected: func(rt *RuntimeConfig) {
|
||||||
|
rt.DataDir = dataDir
|
||||||
|
rt.ConfigEntryBootstrap = []structs.ConfigEntry{
|
||||||
|
&structs.ClusterConfigEntry{
|
||||||
|
Kind: "cluster",
|
||||||
|
Name: "cluster",
|
||||||
|
Meta: map[string]string{
|
||||||
|
"foo": "bar",
|
||||||
|
"gir": "zim",
|
||||||
|
},
|
||||||
|
EnterpriseMeta: *defaultEntMeta,
|
||||||
|
TransparentProxy: structs.TransparentProxyClusterConfig{
|
||||||
|
CatalogDestinationsOnly: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
///////////////////////////////////
|
///////////////////////////////////
|
||||||
// Defaults sanity checks
|
// Defaults sanity checks
|
||||||
|
|
|
@ -419,6 +419,16 @@ func TestFSM_SnapshotRestore_OSS(t *testing.T) {
|
||||||
}
|
}
|
||||||
require.NoError(t, fsm.state.EnsureConfigEntry(26, serviceIxn))
|
require.NoError(t, fsm.state.EnsureConfigEntry(26, serviceIxn))
|
||||||
|
|
||||||
|
// cluster config entry
|
||||||
|
clusterConfig := &structs.ClusterConfigEntry{
|
||||||
|
Kind: structs.ClusterConfig,
|
||||||
|
Name: structs.ClusterConfigCluster,
|
||||||
|
TransparentProxy: structs.TransparentProxyClusterConfig{
|
||||||
|
CatalogDestinationsOnly: true,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
require.NoError(t, fsm.state.EnsureConfigEntry(27, clusterConfig))
|
||||||
|
|
||||||
// Snapshot
|
// Snapshot
|
||||||
snap, err := fsm.Snapshot()
|
snap, err := fsm.Snapshot()
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
@ -691,6 +701,11 @@ func TestFSM_SnapshotRestore_OSS(t *testing.T) {
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
require.Equal(t, serviceIxn, serviceIxnEntry)
|
require.Equal(t, serviceIxn, serviceIxnEntry)
|
||||||
|
|
||||||
|
// Verify cluster config entry is restored
|
||||||
|
_, clusterConfigEntry, err := fsm2.state.ConfigEntry(nil, structs.ClusterConfig, structs.ClusterConfigCluster, structs.DefaultEnterpriseMeta())
|
||||||
|
require.NoError(t, err)
|
||||||
|
require.Equal(t, clusterConfig, clusterConfigEntry)
|
||||||
|
|
||||||
// Snapshot
|
// Snapshot
|
||||||
snap, err = fsm2.Snapshot()
|
snap, err = fsm2.Snapshot()
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
|
|
@ -362,6 +362,7 @@ func validateProposedConfigEntryInGraph(
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
case structs.ServiceIntentions:
|
case structs.ServiceIntentions:
|
||||||
|
case structs.ClusterConfig:
|
||||||
default:
|
default:
|
||||||
return fmt.Errorf("unhandled kind %q during validation of %q", kind, name)
|
return fmt.Errorf("unhandled kind %q during validation of %q", kind, name)
|
||||||
}
|
}
|
||||||
|
|
|
@ -5,8 +5,9 @@ import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"sort"
|
"sort"
|
||||||
|
|
||||||
"github.com/hashicorp/consul/agent/structs"
|
|
||||||
"github.com/mitchellh/copystructure"
|
"github.com/mitchellh/copystructure"
|
||||||
|
|
||||||
|
"github.com/hashicorp/consul/agent/structs"
|
||||||
)
|
)
|
||||||
|
|
||||||
// TODO(ingress): Can we think of a better for this bag of data?
|
// TODO(ingress): Can we think of a better for this bag of data?
|
||||||
|
@ -59,6 +60,9 @@ type configSnapshotConnectProxy struct {
|
||||||
// intentions.
|
// intentions.
|
||||||
Intentions structs.Intentions
|
Intentions structs.Intentions
|
||||||
IntentionsSet bool
|
IntentionsSet bool
|
||||||
|
|
||||||
|
ClusterConfig *structs.ClusterConfigEntry
|
||||||
|
ClusterConfigSet bool
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *configSnapshotConnectProxy) IsEmpty() bool {
|
func (c *configSnapshotConnectProxy) IsEmpty() bool {
|
||||||
|
@ -75,7 +79,8 @@ func (c *configSnapshotConnectProxy) IsEmpty() bool {
|
||||||
len(c.WatchedGatewayEndpoints) == 0 &&
|
len(c.WatchedGatewayEndpoints) == 0 &&
|
||||||
len(c.WatchedServiceChecks) == 0 &&
|
len(c.WatchedServiceChecks) == 0 &&
|
||||||
len(c.PreparedQueryEndpoints) == 0 &&
|
len(c.PreparedQueryEndpoints) == 0 &&
|
||||||
len(c.UpstreamConfig) == 0
|
len(c.UpstreamConfig) == 0 &&
|
||||||
|
!c.ClusterConfigSet
|
||||||
}
|
}
|
||||||
|
|
||||||
type configSnapshotTerminatingGateway struct {
|
type configSnapshotTerminatingGateway struct {
|
||||||
|
@ -355,6 +360,9 @@ type ConfigSnapshot struct {
|
||||||
func (s *ConfigSnapshot) Valid() bool {
|
func (s *ConfigSnapshot) Valid() bool {
|
||||||
switch s.Kind {
|
switch s.Kind {
|
||||||
case structs.ServiceKindConnectProxy:
|
case structs.ServiceKindConnectProxy:
|
||||||
|
if s.Proxy.TransparentProxy && !s.ConnectProxy.ClusterConfigSet {
|
||||||
|
return false
|
||||||
|
}
|
||||||
return s.Roots != nil &&
|
return s.Roots != nil &&
|
||||||
s.ConnectProxy.Leaf != nil &&
|
s.ConnectProxy.Leaf != nil &&
|
||||||
s.ConnectProxy.IntentionsSet
|
s.ConnectProxy.IntentionsSet
|
||||||
|
|
|
@ -46,6 +46,7 @@ const (
|
||||||
serviceResolverIDPrefix = "service-resolver:"
|
serviceResolverIDPrefix = "service-resolver:"
|
||||||
serviceIntentionsIDPrefix = "service-intentions:"
|
serviceIntentionsIDPrefix = "service-intentions:"
|
||||||
intentionUpstreamsID = "intention-upstreams"
|
intentionUpstreamsID = "intention-upstreams"
|
||||||
|
clusterConfigEntryID = "cluster-config"
|
||||||
svcChecksWatchIDPrefix = cachetype.ServiceHTTPChecksName + ":"
|
svcChecksWatchIDPrefix = cachetype.ServiceHTTPChecksName + ":"
|
||||||
serviceIDPrefix = string(structs.UpstreamDestTypeService) + ":"
|
serviceIDPrefix = string(structs.UpstreamDestTypeService) + ":"
|
||||||
preparedQueryIDPrefix = string(structs.UpstreamDestTypePreparedQuery) + ":"
|
preparedQueryIDPrefix = string(structs.UpstreamDestTypePreparedQuery) + ":"
|
||||||
|
@ -315,6 +316,17 @@ func (s *state) initWatchesConnectProxy(snap *ConfigSnapshot) error {
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
err = s.cache.Notify(s.ctx, cachetype.ConfigEntryName, &structs.ConfigEntryQuery{
|
||||||
|
Kind: structs.ClusterConfig,
|
||||||
|
Name: structs.ClusterConfigCluster,
|
||||||
|
Datacenter: s.source.Datacenter,
|
||||||
|
QueryOptions: structs.QueryOptions{Token: s.token},
|
||||||
|
EnterpriseMeta: *structs.DefaultEnterpriseMeta(),
|
||||||
|
}, clusterConfigEntryID, s.ch)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Watch for updates to service endpoints for all upstreams
|
// Watch for updates to service endpoints for all upstreams
|
||||||
|
@ -846,6 +858,24 @@ func (s *state) handleUpdateConnectProxy(u cache.UpdateEvent, snap *ConfigSnapsh
|
||||||
}
|
}
|
||||||
svcID := structs.ServiceIDFromString(strings.TrimPrefix(u.CorrelationID, svcChecksWatchIDPrefix))
|
svcID := structs.ServiceIDFromString(strings.TrimPrefix(u.CorrelationID, svcChecksWatchIDPrefix))
|
||||||
snap.ConnectProxy.WatchedServiceChecks[svcID] = resp
|
snap.ConnectProxy.WatchedServiceChecks[svcID] = resp
|
||||||
|
|
||||||
|
case u.CorrelationID == clusterConfigEntryID:
|
||||||
|
resp, ok := u.Result.(*structs.ConfigEntryResponse)
|
||||||
|
if !ok {
|
||||||
|
return fmt.Errorf("invalid type for response: %T", u.Result)
|
||||||
|
}
|
||||||
|
|
||||||
|
if resp.Entry != nil {
|
||||||
|
clusterConf, ok := resp.Entry.(*structs.ClusterConfigEntry)
|
||||||
|
if !ok {
|
||||||
|
return fmt.Errorf("invalid type for config entry: %T", resp.Entry)
|
||||||
|
}
|
||||||
|
snap.ConnectProxy.ClusterConfig = clusterConf
|
||||||
|
} else {
|
||||||
|
snap.ConnectProxy.ClusterConfig = nil
|
||||||
|
}
|
||||||
|
snap.ConnectProxy.ClusterConfigSet = true
|
||||||
|
|
||||||
default:
|
default:
|
||||||
return s.handleUpdateUpstreams(u, &snap.ConnectProxy.ConfigSnapshotUpstreams)
|
return s.handleUpdateUpstreams(u, &snap.ConnectProxy.ConfigSnapshotUpstreams)
|
||||||
}
|
}
|
||||||
|
|
|
@ -288,6 +288,18 @@ func genVerifyDiscoveryChainWatch(expected *structs.DiscoveryChainRequest) verif
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func genVerifyClusterConfigWatch(expectedDatacenter string) verifyWatchRequest {
|
||||||
|
return func(t testing.TB, cacheType string, request cache.Request) {
|
||||||
|
require.Equal(t, cachetype.ConfigEntryName, cacheType)
|
||||||
|
|
||||||
|
reqReal, ok := request.(*structs.ConfigEntryQuery)
|
||||||
|
require.True(t, ok)
|
||||||
|
require.Equal(t, expectedDatacenter, reqReal.Datacenter)
|
||||||
|
require.Equal(t, structs.ClusterConfigCluster, reqReal.Name)
|
||||||
|
require.Equal(t, structs.ClusterConfig, reqReal.Kind)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func genVerifyGatewayWatch(expectedDatacenter string) verifyWatchRequest {
|
func genVerifyGatewayWatch(expectedDatacenter string) verifyWatchRequest {
|
||||||
return func(t testing.TB, cacheType string, request cache.Request) {
|
return func(t testing.TB, cacheType string, request cache.Request) {
|
||||||
require.Equal(t, cachetype.InternalServiceDumpName, cacheType)
|
require.Equal(t, cachetype.InternalServiceDumpName, cacheType)
|
||||||
|
@ -1538,8 +1550,9 @@ func TestState_WatchesAndUpdates(t *testing.T) {
|
||||||
rootsWatchID: genVerifyRootsWatch("dc1"),
|
rootsWatchID: genVerifyRootsWatch("dc1"),
|
||||||
intentionUpstreamsID: genVerifyServiceSpecificRequest(intentionUpstreamsID,
|
intentionUpstreamsID: genVerifyServiceSpecificRequest(intentionUpstreamsID,
|
||||||
"api", "", "dc1", false),
|
"api", "", "dc1", false),
|
||||||
leafWatchID: genVerifyLeafWatch("api", "dc1"),
|
leafWatchID: genVerifyLeafWatch("api", "dc1"),
|
||||||
intentionsWatchID: genVerifyIntentionWatch("api", "dc1"),
|
intentionsWatchID: genVerifyIntentionWatch("api", "dc1"),
|
||||||
|
clusterConfigEntryID: genVerifyClusterConfigWatch("dc1"),
|
||||||
},
|
},
|
||||||
verifySnapshot: func(t testing.TB, snap *ConfigSnapshot) {
|
verifySnapshot: func(t testing.TB, snap *ConfigSnapshot) {
|
||||||
require.False(t, snap.Valid(), "proxy without roots/leaf/intentions is not valid")
|
require.False(t, snap.Valid(), "proxy without roots/leaf/intentions is not valid")
|
||||||
|
@ -1562,6 +1575,13 @@ func TestState_WatchesAndUpdates(t *testing.T) {
|
||||||
Result: TestIntentions(),
|
Result: TestIntentions(),
|
||||||
Err: nil,
|
Err: nil,
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
CorrelationID: clusterConfigEntryID,
|
||||||
|
Result: &structs.ConfigEntryResponse{
|
||||||
|
Entry: nil, // no explicit config
|
||||||
|
},
|
||||||
|
Err: nil,
|
||||||
|
},
|
||||||
},
|
},
|
||||||
verifySnapshot: func(t testing.TB, snap *ConfigSnapshot) {
|
verifySnapshot: func(t testing.TB, snap *ConfigSnapshot) {
|
||||||
require.True(t, snap.Valid(), "proxy with roots/leaf/intentions is valid")
|
require.True(t, snap.Valid(), "proxy with roots/leaf/intentions is valid")
|
||||||
|
@ -1571,6 +1591,8 @@ func TestState_WatchesAndUpdates(t *testing.T) {
|
||||||
require.True(t, snap.MeshGateway.IsEmpty())
|
require.True(t, snap.MeshGateway.IsEmpty())
|
||||||
require.True(t, snap.IngressGateway.IsEmpty())
|
require.True(t, snap.IngressGateway.IsEmpty())
|
||||||
require.True(t, snap.TerminatingGateway.IsEmpty())
|
require.True(t, snap.TerminatingGateway.IsEmpty())
|
||||||
|
require.True(t, snap.ConnectProxy.ClusterConfigSet)
|
||||||
|
require.Nil(t, snap.ConnectProxy.ClusterConfig)
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
@ -1594,8 +1616,9 @@ func TestState_WatchesAndUpdates(t *testing.T) {
|
||||||
rootsWatchID: genVerifyRootsWatch("dc1"),
|
rootsWatchID: genVerifyRootsWatch("dc1"),
|
||||||
intentionUpstreamsID: genVerifyServiceSpecificRequest(intentionUpstreamsID,
|
intentionUpstreamsID: genVerifyServiceSpecificRequest(intentionUpstreamsID,
|
||||||
"api", "", "dc1", false),
|
"api", "", "dc1", false),
|
||||||
leafWatchID: genVerifyLeafWatch("api", "dc1"),
|
leafWatchID: genVerifyLeafWatch("api", "dc1"),
|
||||||
intentionsWatchID: genVerifyIntentionWatch("api", "dc1"),
|
intentionsWatchID: genVerifyIntentionWatch("api", "dc1"),
|
||||||
|
clusterConfigEntryID: genVerifyClusterConfigWatch("dc1"),
|
||||||
},
|
},
|
||||||
verifySnapshot: func(t testing.TB, snap *ConfigSnapshot) {
|
verifySnapshot: func(t testing.TB, snap *ConfigSnapshot) {
|
||||||
require.False(t, snap.Valid(), "proxy without roots/leaf/intentions is not valid")
|
require.False(t, snap.Valid(), "proxy without roots/leaf/intentions is not valid")
|
||||||
|
@ -1619,6 +1642,17 @@ func TestState_WatchesAndUpdates(t *testing.T) {
|
||||||
Result: TestIntentions(),
|
Result: TestIntentions(),
|
||||||
Err: nil,
|
Err: nil,
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
CorrelationID: clusterConfigEntryID,
|
||||||
|
Result: &structs.ConfigEntryResponse{
|
||||||
|
Entry: &structs.ClusterConfigEntry{
|
||||||
|
Kind: structs.ClusterConfig,
|
||||||
|
Name: structs.ClusterConfigCluster,
|
||||||
|
TransparentProxy: structs.TransparentProxyClusterConfig{},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
Err: nil,
|
||||||
|
},
|
||||||
},
|
},
|
||||||
verifySnapshot: func(t testing.TB, snap *ConfigSnapshot) {
|
verifySnapshot: func(t testing.TB, snap *ConfigSnapshot) {
|
||||||
require.True(t, snap.Valid(), "proxy with roots/leaf/intentions is valid")
|
require.True(t, snap.Valid(), "proxy with roots/leaf/intentions is valid")
|
||||||
|
@ -1628,6 +1662,8 @@ func TestState_WatchesAndUpdates(t *testing.T) {
|
||||||
require.True(t, snap.MeshGateway.IsEmpty())
|
require.True(t, snap.MeshGateway.IsEmpty())
|
||||||
require.True(t, snap.IngressGateway.IsEmpty())
|
require.True(t, snap.IngressGateway.IsEmpty())
|
||||||
require.True(t, snap.TerminatingGateway.IsEmpty())
|
require.True(t, snap.TerminatingGateway.IsEmpty())
|
||||||
|
require.True(t, snap.ConnectProxy.ClusterConfigSet)
|
||||||
|
require.NotNil(t, snap.ConnectProxy.ClusterConfig)
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
// Receiving an intention should lead to spinning up a discovery chain watch
|
// Receiving an intention should lead to spinning up a discovery chain watch
|
||||||
|
|
|
@ -26,8 +26,10 @@ const (
|
||||||
IngressGateway string = "ingress-gateway"
|
IngressGateway string = "ingress-gateway"
|
||||||
TerminatingGateway string = "terminating-gateway"
|
TerminatingGateway string = "terminating-gateway"
|
||||||
ServiceIntentions string = "service-intentions"
|
ServiceIntentions string = "service-intentions"
|
||||||
|
ClusterConfig string = "cluster"
|
||||||
|
|
||||||
ProxyConfigGlobal string = "global"
|
ProxyConfigGlobal string = "global"
|
||||||
|
ClusterConfigCluster string = "cluster"
|
||||||
|
|
||||||
DefaultServiceProtocol = "tcp"
|
DefaultServiceProtocol = "tcp"
|
||||||
)
|
)
|
||||||
|
@ -41,6 +43,7 @@ var AllConfigEntryKinds = []string{
|
||||||
IngressGateway,
|
IngressGateway,
|
||||||
TerminatingGateway,
|
TerminatingGateway,
|
||||||
ServiceIntentions,
|
ServiceIntentions,
|
||||||
|
ClusterConfig,
|
||||||
}
|
}
|
||||||
|
|
||||||
// ConfigEntry is the interface for centralized configuration stored in Raft.
|
// ConfigEntry is the interface for centralized configuration stored in Raft.
|
||||||
|
@ -496,6 +499,8 @@ func MakeConfigEntry(kind, name string) (ConfigEntry, error) {
|
||||||
return &TerminatingGatewayConfigEntry{Name: name}, nil
|
return &TerminatingGatewayConfigEntry{Name: name}, nil
|
||||||
case ServiceIntentions:
|
case ServiceIntentions:
|
||||||
return &ServiceIntentionsConfigEntry{Name: name}, nil
|
return &ServiceIntentionsConfigEntry{Name: name}, nil
|
||||||
|
case ClusterConfig:
|
||||||
|
return &ClusterConfigEntry{Name: name}, nil
|
||||||
default:
|
default:
|
||||||
return nil, fmt.Errorf("invalid config entry kind: %s", kind)
|
return nil, fmt.Errorf("invalid config entry kind: %s", kind)
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,102 @@
|
||||||
|
package structs
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
|
||||||
|
"github.com/hashicorp/consul/acl"
|
||||||
|
)
|
||||||
|
|
||||||
|
type ClusterConfigEntry struct {
|
||||||
|
Kind string
|
||||||
|
Name string
|
||||||
|
|
||||||
|
// TransparentProxy contains cluster-wide options pertaining to TPROXY mode
|
||||||
|
// when enabled.
|
||||||
|
TransparentProxy TransparentProxyClusterConfig `alias:"transparent_proxy"`
|
||||||
|
|
||||||
|
Meta map[string]string `json:",omitempty"`
|
||||||
|
EnterpriseMeta `hcl:",squash" mapstructure:",squash"`
|
||||||
|
RaftIndex
|
||||||
|
}
|
||||||
|
|
||||||
|
// TransparentProxyClusterConfig contains cluster-wide options pertaining to
|
||||||
|
// TPROXY mode when enabled.
|
||||||
|
type TransparentProxyClusterConfig struct {
|
||||||
|
// CatalogDestinationsOnly can be used to disable the pass-through that
|
||||||
|
// allows traffic to destinations outside of the mesh.
|
||||||
|
CatalogDestinationsOnly bool `alias:"catalog_destinations_only"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func (e *ClusterConfigEntry) GetKind() string {
|
||||||
|
return ClusterConfig
|
||||||
|
}
|
||||||
|
|
||||||
|
func (e *ClusterConfigEntry) GetName() string {
|
||||||
|
if e == nil {
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
|
||||||
|
return e.Name
|
||||||
|
}
|
||||||
|
|
||||||
|
func (e *ClusterConfigEntry) GetMeta() map[string]string {
|
||||||
|
if e == nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
return e.Meta
|
||||||
|
}
|
||||||
|
|
||||||
|
func (e *ClusterConfigEntry) Normalize() error {
|
||||||
|
if e == nil {
|
||||||
|
return fmt.Errorf("config entry is nil")
|
||||||
|
}
|
||||||
|
|
||||||
|
e.Kind = ClusterConfig
|
||||||
|
e.Name = ClusterConfigCluster
|
||||||
|
|
||||||
|
e.EnterpriseMeta.Normalize()
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (e *ClusterConfigEntry) Validate() error {
|
||||||
|
if e == nil {
|
||||||
|
return fmt.Errorf("config entry is nil")
|
||||||
|
}
|
||||||
|
|
||||||
|
if e.Name != ClusterConfigCluster {
|
||||||
|
return fmt.Errorf("invalid name (%q), only %q is supported", e.Name, ClusterConfigCluster)
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := validateConfigEntryMeta(e.Meta); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
return e.validateEnterpriseMeta()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (e *ClusterConfigEntry) CanRead(authz acl.Authorizer) bool {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
func (e *ClusterConfigEntry) CanWrite(authz acl.Authorizer) bool {
|
||||||
|
var authzContext acl.AuthorizerContext
|
||||||
|
e.FillAuthzContext(&authzContext)
|
||||||
|
return authz.OperatorWrite(&authzContext) == acl.Allow
|
||||||
|
}
|
||||||
|
|
||||||
|
func (e *ClusterConfigEntry) GetRaftIndex() *RaftIndex {
|
||||||
|
if e == nil {
|
||||||
|
return &RaftIndex{}
|
||||||
|
}
|
||||||
|
|
||||||
|
return &e.RaftIndex
|
||||||
|
}
|
||||||
|
|
||||||
|
func (e *ClusterConfigEntry) GetEnterpriseMeta() *EnterpriseMeta {
|
||||||
|
if e == nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
return &e.EnterpriseMeta
|
||||||
|
}
|
|
@ -0,0 +1,5 @@
|
||||||
|
package structs
|
||||||
|
|
||||||
|
func (e *ClusterConfigEntry) validateEnterpriseMeta() error {
|
||||||
|
return nil
|
||||||
|
}
|
|
@ -1300,6 +1300,42 @@ func TestDecodeConfigEntry(t *testing.T) {
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
name: "cluster",
|
||||||
|
snake: `
|
||||||
|
kind = "cluster"
|
||||||
|
name = "cluster"
|
||||||
|
meta {
|
||||||
|
"foo" = "bar"
|
||||||
|
"gir" = "zim"
|
||||||
|
}
|
||||||
|
transparent_proxy {
|
||||||
|
catalog_destinations_only = true
|
||||||
|
}
|
||||||
|
`,
|
||||||
|
camel: `
|
||||||
|
Kind = "cluster"
|
||||||
|
Name = "cluster"
|
||||||
|
Meta {
|
||||||
|
"foo" = "bar"
|
||||||
|
"gir" = "zim"
|
||||||
|
}
|
||||||
|
TransparentProxy {
|
||||||
|
CatalogDestinationsOnly = true
|
||||||
|
}
|
||||||
|
`,
|
||||||
|
expect: &ClusterConfigEntry{
|
||||||
|
Kind: "cluster",
|
||||||
|
Name: "cluster",
|
||||||
|
Meta: map[string]string{
|
||||||
|
"foo": "bar",
|
||||||
|
"gir": "zim",
|
||||||
|
},
|
||||||
|
TransparentProxy: TransparentProxyClusterConfig{
|
||||||
|
CatalogDestinationsOnly: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
} {
|
} {
|
||||||
tc := tc
|
tc := tc
|
||||||
|
|
||||||
|
|
|
@ -223,19 +223,23 @@ func (s *Server) listenersFromSnapshotConnectProxy(cInfo connectionInfo, cfgSnap
|
||||||
})
|
})
|
||||||
|
|
||||||
// Add a catch-all filter chain that acts as a TCP proxy to non-catalog destinations
|
// Add a catch-all filter chain that acts as a TCP proxy to non-catalog destinations
|
||||||
filterChain, err := s.makeUpstreamFilterChainForDiscoveryChain(
|
if cfgSnap.ConnectProxy.ClusterConfig == nil ||
|
||||||
"passthrough",
|
!cfgSnap.ConnectProxy.ClusterConfig.TransparentProxy.CatalogDestinationsOnly {
|
||||||
OriginalDestinationClusterName,
|
|
||||||
"tcp",
|
filterChain, err := s.makeUpstreamFilterChainForDiscoveryChain(
|
||||||
nil,
|
"passthrough",
|
||||||
nil,
|
OriginalDestinationClusterName,
|
||||||
cfgSnap,
|
"tcp",
|
||||||
nil,
|
nil,
|
||||||
)
|
nil,
|
||||||
if err != nil {
|
cfgSnap,
|
||||||
return nil, err
|
nil,
|
||||||
|
)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
outboundListener.FilterChains = append(outboundListener.FilterChains, filterChain)
|
||||||
}
|
}
|
||||||
outboundListener.FilterChains = append(outboundListener.FilterChains, filterChain)
|
|
||||||
|
|
||||||
resources = append(resources, outboundListener)
|
resources = append(resources, outboundListener)
|
||||||
}
|
}
|
||||||
|
|
|
@ -2,7 +2,16 @@ package xds
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"bytes"
|
"bytes"
|
||||||
|
"path/filepath"
|
||||||
|
"sort"
|
||||||
|
"testing"
|
||||||
|
"text/template"
|
||||||
|
"time"
|
||||||
|
|
||||||
envoy_listener_v3 "github.com/envoyproxy/go-control-plane/envoy/config/listener/v3"
|
envoy_listener_v3 "github.com/envoyproxy/go-control-plane/envoy/config/listener/v3"
|
||||||
|
testinf "github.com/mitchellh/go-testing-interface"
|
||||||
|
"github.com/stretchr/testify/require"
|
||||||
|
|
||||||
"github.com/hashicorp/consul/agent/connect"
|
"github.com/hashicorp/consul/agent/connect"
|
||||||
"github.com/hashicorp/consul/agent/consul/discoverychain"
|
"github.com/hashicorp/consul/agent/consul/discoverychain"
|
||||||
"github.com/hashicorp/consul/agent/proxycfg"
|
"github.com/hashicorp/consul/agent/proxycfg"
|
||||||
|
@ -10,13 +19,6 @@ import (
|
||||||
"github.com/hashicorp/consul/agent/xds/proxysupport"
|
"github.com/hashicorp/consul/agent/xds/proxysupport"
|
||||||
"github.com/hashicorp/consul/sdk/testutil"
|
"github.com/hashicorp/consul/sdk/testutil"
|
||||||
"github.com/hashicorp/consul/types"
|
"github.com/hashicorp/consul/types"
|
||||||
testinf "github.com/mitchellh/go-testing-interface"
|
|
||||||
"github.com/stretchr/testify/require"
|
|
||||||
"path/filepath"
|
|
||||||
"sort"
|
|
||||||
"testing"
|
|
||||||
"text/template"
|
|
||||||
"time"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestListenersFromSnapshot(t *testing.T) {
|
func TestListenersFromSnapshot(t *testing.T) {
|
||||||
|
@ -481,6 +483,48 @@ func TestListenersFromSnapshot(t *testing.T) {
|
||||||
setup: func(snap *proxycfg.ConfigSnapshot) {
|
setup: func(snap *proxycfg.ConfigSnapshot) {
|
||||||
snap.Proxy.TransparentProxy = true
|
snap.Proxy.TransparentProxy = true
|
||||||
|
|
||||||
|
snap.ConnectProxy.ClusterConfigSet = true
|
||||||
|
|
||||||
|
// DiscoveryChain without an UpstreamConfig should yield a filter chain when in TransparentProxy mode
|
||||||
|
snap.ConnectProxy.DiscoveryChain["google"] = discoverychain.TestCompileConfigEntries(
|
||||||
|
t, "google", "default", "dc1",
|
||||||
|
connect.TestClusterID+".consul", "dc1", nil)
|
||||||
|
snap.ConnectProxy.WatchedUpstreamEndpoints["google"] = map[string]structs.CheckServiceNodes{
|
||||||
|
"google.default.dc1": {
|
||||||
|
structs.CheckServiceNode{
|
||||||
|
Node: &structs.Node{
|
||||||
|
Address: "8.8.8.8",
|
||||||
|
Datacenter: "dc1",
|
||||||
|
},
|
||||||
|
Service: &structs.NodeService{
|
||||||
|
Service: "google",
|
||||||
|
Port: 9090,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
// DiscoveryChains without endpoints do not get a filter chain because there are no addresses to match on.
|
||||||
|
snap.ConnectProxy.DiscoveryChain["no-endpoints"] = discoverychain.TestCompileConfigEntries(
|
||||||
|
t, "no-endpoints", "default", "dc1",
|
||||||
|
connect.TestClusterID+".consul", "dc1", nil)
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "transparent-proxy-catalog-destinations-only",
|
||||||
|
create: proxycfg.TestConfigSnapshot,
|
||||||
|
setup: func(snap *proxycfg.ConfigSnapshot) {
|
||||||
|
snap.Proxy.TransparentProxy = true
|
||||||
|
|
||||||
|
snap.ConnectProxy.ClusterConfigSet = true
|
||||||
|
snap.ConnectProxy.ClusterConfig = &structs.ClusterConfigEntry{
|
||||||
|
Kind: structs.ClusterConfig,
|
||||||
|
Name: structs.ClusterConfigCluster,
|
||||||
|
TransparentProxy: structs.TransparentProxyClusterConfig{
|
||||||
|
CatalogDestinationsOnly: true,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
// DiscoveryChain without an UpstreamConfig should yield a filter chain when in TransparentProxy mode
|
// DiscoveryChain without an UpstreamConfig should yield a filter chain when in TransparentProxy mode
|
||||||
snap.ConnectProxy.DiscoveryChain["google"] = discoverychain.TestCompileConfigEntries(
|
snap.ConnectProxy.DiscoveryChain["google"] = discoverychain.TestCompileConfigEntries(
|
||||||
t, "google", "default", "dc1",
|
t, "google", "default", "dc1",
|
||||||
|
|
157
agent/xds/testdata/listeners/transparent-proxy-catalog-destinations-only.envoy-1-17-x.golden
vendored
Normal file
157
agent/xds/testdata/listeners/transparent-proxy-catalog-destinations-only.envoy-1-17-x.golden
vendored
Normal file
|
@ -0,0 +1,157 @@
|
||||||
|
{
|
||||||
|
"versionInfo": "00000001",
|
||||||
|
"resources": [
|
||||||
|
{
|
||||||
|
"@type": "type.googleapis.com/envoy.config.listener.v3.Listener",
|
||||||
|
"name": "db:127.0.0.1:9191",
|
||||||
|
"address": {
|
||||||
|
"socketAddress": {
|
||||||
|
"address": "127.0.0.1",
|
||||||
|
"portValue": 9191
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"filterChains": [
|
||||||
|
{
|
||||||
|
"filters": [
|
||||||
|
{
|
||||||
|
"name": "envoy.filters.network.tcp_proxy",
|
||||||
|
"typedConfig": {
|
||||||
|
"@type": "type.googleapis.com/envoy.extensions.filters.network.tcp_proxy.v3.TcpProxy",
|
||||||
|
"statPrefix": "upstream.db.default.dc1",
|
||||||
|
"cluster": "db.default.dc1.internal.11111111-2222-3333-4444-555555555555.consul"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"trafficDirection": "OUTBOUND"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"@type": "type.googleapis.com/envoy.config.listener.v3.Listener",
|
||||||
|
"name": "outbound_listener:127.0.0.1:15001",
|
||||||
|
"address": {
|
||||||
|
"socketAddress": {
|
||||||
|
"address": "127.0.0.1",
|
||||||
|
"portValue": 15001
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"filterChains": [
|
||||||
|
{
|
||||||
|
"filterChainMatch": {
|
||||||
|
"prefixRanges": [
|
||||||
|
{
|
||||||
|
"addressPrefix": "8.8.8.8",
|
||||||
|
"prefixLen": 32
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"filters": [
|
||||||
|
{
|
||||||
|
"name": "envoy.filters.network.tcp_proxy",
|
||||||
|
"typedConfig": {
|
||||||
|
"@type": "type.googleapis.com/envoy.extensions.filters.network.tcp_proxy.v3.TcpProxy",
|
||||||
|
"statPrefix": "upstream.google.default.dc1",
|
||||||
|
"cluster": "google.default.dc1.internal.11111111-2222-3333-4444-555555555555.consul"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"listenerFilters": [
|
||||||
|
{
|
||||||
|
"name": "envoy.filters.listener.original_dst"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"trafficDirection": "OUTBOUND"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"@type": "type.googleapis.com/envoy.config.listener.v3.Listener",
|
||||||
|
"name": "prepared_query:geo-cache:127.10.10.10:8181",
|
||||||
|
"address": {
|
||||||
|
"socketAddress": {
|
||||||
|
"address": "127.10.10.10",
|
||||||
|
"portValue": 8181
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"filterChains": [
|
||||||
|
{
|
||||||
|
"filters": [
|
||||||
|
{
|
||||||
|
"name": "envoy.filters.network.tcp_proxy",
|
||||||
|
"typedConfig": {
|
||||||
|
"@type": "type.googleapis.com/envoy.extensions.filters.network.tcp_proxy.v3.TcpProxy",
|
||||||
|
"statPrefix": "upstream.prepared_query_geo-cache",
|
||||||
|
"cluster": "geo-cache.default.dc1.query.11111111-2222-3333-4444-555555555555.consul"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"trafficDirection": "OUTBOUND"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"@type": "type.googleapis.com/envoy.config.listener.v3.Listener",
|
||||||
|
"name": "public_listener:0.0.0.0:9999",
|
||||||
|
"address": {
|
||||||
|
"socketAddress": {
|
||||||
|
"address": "0.0.0.0",
|
||||||
|
"portValue": 9999
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"filterChains": [
|
||||||
|
{
|
||||||
|
"filters": [
|
||||||
|
{
|
||||||
|
"name": "envoy.filters.network.rbac",
|
||||||
|
"typedConfig": {
|
||||||
|
"@type": "type.googleapis.com/envoy.extensions.filters.network.rbac.v3.RBAC",
|
||||||
|
"rules": {
|
||||||
|
|
||||||
|
},
|
||||||
|
"statPrefix": "connect_authz"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "envoy.filters.network.tcp_proxy",
|
||||||
|
"typedConfig": {
|
||||||
|
"@type": "type.googleapis.com/envoy.extensions.filters.network.tcp_proxy.v3.TcpProxy",
|
||||||
|
"statPrefix": "public_listener",
|
||||||
|
"cluster": "local_app"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"transportSocket": {
|
||||||
|
"name": "tls",
|
||||||
|
"typedConfig": {
|
||||||
|
"@type": "type.googleapis.com/envoy.extensions.transport_sockets.tls.v3.DownstreamTlsContext",
|
||||||
|
"commonTlsContext": {
|
||||||
|
"tlsParams": {
|
||||||
|
|
||||||
|
},
|
||||||
|
"tlsCertificates": [
|
||||||
|
{
|
||||||
|
"certificateChain": {
|
||||||
|
"inlineString": "-----BEGIN CERTIFICATE-----\nMIICjDCCAjKgAwIBAgIIC5llxGV1gB8wCgYIKoZIzj0EAwIwFDESMBAGA1UEAxMJ\nVGVzdCBDQSAyMB4XDTE5MDMyMjEzNTgyNloXDTI5MDMyMjEzNTgyNlowDjEMMAoG\nA1UEAxMDd2ViMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEADPv1RHVNRfa2VKR\nAB16b6rZnEt7tuhaxCFpQXPj7M2omb0B9Favq5E0ivpNtv1QnFhxtPd7d5k4e+T7\nSkW1TaOCAXIwggFuMA4GA1UdDwEB/wQEAwIDuDAdBgNVHSUEFjAUBggrBgEFBQcD\nAgYIKwYBBQUHAwEwDAYDVR0TAQH/BAIwADBoBgNVHQ4EYQRfN2Q6MDc6ODc6M2E6\nNDA6MTk6NDc6YzM6NWE6YzA6YmE6NjI6ZGY6YWY6NGI6ZDQ6MDU6MjU6NzY6M2Q6\nNWE6OGQ6MTY6OGQ6Njc6NWU6MmU6YTA6MzQ6N2Q6ZGM6ZmYwagYDVR0jBGMwYYBf\nZDE6MTE6MTE6YWM6MmE6YmE6OTc6YjI6M2Y6YWM6N2I6YmQ6ZGE6YmU6YjE6OGE6\nZmM6OWE6YmE6YjU6YmM6ODM6ZTc6NWU6NDE6NmY6ZjI6NzM6OTU6NTg6MGM6ZGIw\nWQYDVR0RBFIwUIZOc3BpZmZlOi8vMTExMTExMTEtMjIyMi0zMzMzLTQ0NDQtNTU1\nNTU1NTU1NTU1LmNvbnN1bC9ucy9kZWZhdWx0L2RjL2RjMS9zdmMvd2ViMAoGCCqG\nSM49BAMCA0gAMEUCIGC3TTvvjj76KMrguVyFf4tjOqaSCRie3nmHMRNNRav7AiEA\npY0heYeK9A6iOLrzqxSerkXXQyj5e9bE4VgUnxgPU6g=\n-----END CERTIFICATE-----\n"
|
||||||
|
},
|
||||||
|
"privateKey": {
|
||||||
|
"inlineString": "-----BEGIN EC PRIVATE KEY-----\nMHcCAQEEIMoTkpRggp3fqZzFKh82yS4LjtJI+XY+qX/7DefHFrtdoAoGCCqGSM49\nAwEHoUQDQgAEADPv1RHVNRfa2VKRAB16b6rZnEt7tuhaxCFpQXPj7M2omb0B9Fav\nq5E0ivpNtv1QnFhxtPd7d5k4e+T7SkW1TQ==\n-----END EC PRIVATE KEY-----\n"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"validationContext": {
|
||||||
|
"trustedCa": {
|
||||||
|
"inlineString": "-----BEGIN CERTIFICATE-----\nMIICXDCCAgKgAwIBAgIICpZq70Z9LyUwCgYIKoZIzj0EAwIwFDESMBAGA1UEAxMJ\nVGVzdCBDQSAyMB4XDTE5MDMyMjEzNTgyNloXDTI5MDMyMjEzNTgyNlowFDESMBAG\nA1UEAxMJVGVzdCBDQSAyMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEIhywH1gx\nAsMwuF3ukAI5YL2jFxH6Usnma1HFSfVyxbXX1/uoZEYrj8yCAtdU2yoHETyd+Zx2\nThhRLP79pYegCaOCATwwggE4MA4GA1UdDwEB/wQEAwIBhjAPBgNVHRMBAf8EBTAD\nAQH/MGgGA1UdDgRhBF9kMToxMToxMTphYzoyYTpiYTo5NzpiMjozZjphYzo3Yjpi\nZDpkYTpiZTpiMTo4YTpmYzo5YTpiYTpiNTpiYzo4MzplNzo1ZTo0MTo2ZjpmMjo3\nMzo5NTo1ODowYzpkYjBqBgNVHSMEYzBhgF9kMToxMToxMTphYzoyYTpiYTo5Nzpi\nMjozZjphYzo3YjpiZDpkYTpiZTpiMTo4YTpmYzo5YTpiYTpiNTpiYzo4MzplNzo1\nZTo0MTo2ZjpmMjo3Mzo5NTo1ODowYzpkYjA/BgNVHREEODA2hjRzcGlmZmU6Ly8x\nMTExMTExMS0yMjIyLTMzMzMtNDQ0NC01NTU1NTU1NTU1NTUuY29uc3VsMAoGCCqG\nSM49BAMCA0gAMEUCICOY0i246rQHJt8o8Oya0D5PLL1FnmsQmQqIGCi31RwnAiEA\noR5f6Ku+cig2Il8T8LJujOp2/2A72QcHZA57B13y+8o=\n-----END CERTIFICATE-----\n"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"requireClientCertificate": true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"trafficDirection": "INBOUND"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"typeUrl": "type.googleapis.com/envoy.config.listener.v3.Listener",
|
||||||
|
"nonce": "00000001"
|
||||||
|
}
|
|
@ -0,0 +1,157 @@
|
||||||
|
{
|
||||||
|
"versionInfo": "00000001",
|
||||||
|
"resources": [
|
||||||
|
{
|
||||||
|
"@type": "type.googleapis.com/envoy.api.v2.Listener",
|
||||||
|
"name": "db:127.0.0.1:9191",
|
||||||
|
"address": {
|
||||||
|
"socketAddress": {
|
||||||
|
"address": "127.0.0.1",
|
||||||
|
"portValue": 9191
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"filterChains": [
|
||||||
|
{
|
||||||
|
"filters": [
|
||||||
|
{
|
||||||
|
"name": "envoy.filters.network.tcp_proxy",
|
||||||
|
"typedConfig": {
|
||||||
|
"@type": "type.googleapis.com/envoy.config.filter.network.tcp_proxy.v2.TcpProxy",
|
||||||
|
"statPrefix": "upstream.db.default.dc1",
|
||||||
|
"cluster": "db.default.dc1.internal.11111111-2222-3333-4444-555555555555.consul"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"trafficDirection": "OUTBOUND"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"@type": "type.googleapis.com/envoy.api.v2.Listener",
|
||||||
|
"name": "outbound_listener:127.0.0.1:15001",
|
||||||
|
"address": {
|
||||||
|
"socketAddress": {
|
||||||
|
"address": "127.0.0.1",
|
||||||
|
"portValue": 15001
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"filterChains": [
|
||||||
|
{
|
||||||
|
"filterChainMatch": {
|
||||||
|
"prefixRanges": [
|
||||||
|
{
|
||||||
|
"addressPrefix": "8.8.8.8",
|
||||||
|
"prefixLen": 32
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"filters": [
|
||||||
|
{
|
||||||
|
"name": "envoy.filters.network.tcp_proxy",
|
||||||
|
"typedConfig": {
|
||||||
|
"@type": "type.googleapis.com/envoy.config.filter.network.tcp_proxy.v2.TcpProxy",
|
||||||
|
"statPrefix": "upstream.google.default.dc1",
|
||||||
|
"cluster": "google.default.dc1.internal.11111111-2222-3333-4444-555555555555.consul"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"listenerFilters": [
|
||||||
|
{
|
||||||
|
"name": "envoy.filters.listener.original_dst"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"trafficDirection": "OUTBOUND"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"@type": "type.googleapis.com/envoy.api.v2.Listener",
|
||||||
|
"name": "prepared_query:geo-cache:127.10.10.10:8181",
|
||||||
|
"address": {
|
||||||
|
"socketAddress": {
|
||||||
|
"address": "127.10.10.10",
|
||||||
|
"portValue": 8181
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"filterChains": [
|
||||||
|
{
|
||||||
|
"filters": [
|
||||||
|
{
|
||||||
|
"name": "envoy.filters.network.tcp_proxy",
|
||||||
|
"typedConfig": {
|
||||||
|
"@type": "type.googleapis.com/envoy.config.filter.network.tcp_proxy.v2.TcpProxy",
|
||||||
|
"statPrefix": "upstream.prepared_query_geo-cache",
|
||||||
|
"cluster": "geo-cache.default.dc1.query.11111111-2222-3333-4444-555555555555.consul"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"trafficDirection": "OUTBOUND"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"@type": "type.googleapis.com/envoy.api.v2.Listener",
|
||||||
|
"name": "public_listener:0.0.0.0:9999",
|
||||||
|
"address": {
|
||||||
|
"socketAddress": {
|
||||||
|
"address": "0.0.0.0",
|
||||||
|
"portValue": 9999
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"filterChains": [
|
||||||
|
{
|
||||||
|
"filters": [
|
||||||
|
{
|
||||||
|
"name": "envoy.filters.network.rbac",
|
||||||
|
"typedConfig": {
|
||||||
|
"@type": "type.googleapis.com/envoy.config.filter.network.rbac.v2.RBAC",
|
||||||
|
"rules": {
|
||||||
|
|
||||||
|
},
|
||||||
|
"statPrefix": "connect_authz"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "envoy.filters.network.tcp_proxy",
|
||||||
|
"typedConfig": {
|
||||||
|
"@type": "type.googleapis.com/envoy.config.filter.network.tcp_proxy.v2.TcpProxy",
|
||||||
|
"statPrefix": "public_listener",
|
||||||
|
"cluster": "local_app"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"transportSocket": {
|
||||||
|
"name": "tls",
|
||||||
|
"typedConfig": {
|
||||||
|
"@type": "type.googleapis.com/envoy.api.v2.auth.DownstreamTlsContext",
|
||||||
|
"commonTlsContext": {
|
||||||
|
"tlsParams": {
|
||||||
|
|
||||||
|
},
|
||||||
|
"tlsCertificates": [
|
||||||
|
{
|
||||||
|
"certificateChain": {
|
||||||
|
"inlineString": "-----BEGIN CERTIFICATE-----\nMIICjDCCAjKgAwIBAgIIC5llxGV1gB8wCgYIKoZIzj0EAwIwFDESMBAGA1UEAxMJ\nVGVzdCBDQSAyMB4XDTE5MDMyMjEzNTgyNloXDTI5MDMyMjEzNTgyNlowDjEMMAoG\nA1UEAxMDd2ViMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEADPv1RHVNRfa2VKR\nAB16b6rZnEt7tuhaxCFpQXPj7M2omb0B9Favq5E0ivpNtv1QnFhxtPd7d5k4e+T7\nSkW1TaOCAXIwggFuMA4GA1UdDwEB/wQEAwIDuDAdBgNVHSUEFjAUBggrBgEFBQcD\nAgYIKwYBBQUHAwEwDAYDVR0TAQH/BAIwADBoBgNVHQ4EYQRfN2Q6MDc6ODc6M2E6\nNDA6MTk6NDc6YzM6NWE6YzA6YmE6NjI6ZGY6YWY6NGI6ZDQ6MDU6MjU6NzY6M2Q6\nNWE6OGQ6MTY6OGQ6Njc6NWU6MmU6YTA6MzQ6N2Q6ZGM6ZmYwagYDVR0jBGMwYYBf\nZDE6MTE6MTE6YWM6MmE6YmE6OTc6YjI6M2Y6YWM6N2I6YmQ6ZGE6YmU6YjE6OGE6\nZmM6OWE6YmE6YjU6YmM6ODM6ZTc6NWU6NDE6NmY6ZjI6NzM6OTU6NTg6MGM6ZGIw\nWQYDVR0RBFIwUIZOc3BpZmZlOi8vMTExMTExMTEtMjIyMi0zMzMzLTQ0NDQtNTU1\nNTU1NTU1NTU1LmNvbnN1bC9ucy9kZWZhdWx0L2RjL2RjMS9zdmMvd2ViMAoGCCqG\nSM49BAMCA0gAMEUCIGC3TTvvjj76KMrguVyFf4tjOqaSCRie3nmHMRNNRav7AiEA\npY0heYeK9A6iOLrzqxSerkXXQyj5e9bE4VgUnxgPU6g=\n-----END CERTIFICATE-----\n"
|
||||||
|
},
|
||||||
|
"privateKey": {
|
||||||
|
"inlineString": "-----BEGIN EC PRIVATE KEY-----\nMHcCAQEEIMoTkpRggp3fqZzFKh82yS4LjtJI+XY+qX/7DefHFrtdoAoGCCqGSM49\nAwEHoUQDQgAEADPv1RHVNRfa2VKRAB16b6rZnEt7tuhaxCFpQXPj7M2omb0B9Fav\nq5E0ivpNtv1QnFhxtPd7d5k4e+T7SkW1TQ==\n-----END EC PRIVATE KEY-----\n"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"validationContext": {
|
||||||
|
"trustedCa": {
|
||||||
|
"inlineString": "-----BEGIN CERTIFICATE-----\nMIICXDCCAgKgAwIBAgIICpZq70Z9LyUwCgYIKoZIzj0EAwIwFDESMBAGA1UEAxMJ\nVGVzdCBDQSAyMB4XDTE5MDMyMjEzNTgyNloXDTI5MDMyMjEzNTgyNlowFDESMBAG\nA1UEAxMJVGVzdCBDQSAyMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEIhywH1gx\nAsMwuF3ukAI5YL2jFxH6Usnma1HFSfVyxbXX1/uoZEYrj8yCAtdU2yoHETyd+Zx2\nThhRLP79pYegCaOCATwwggE4MA4GA1UdDwEB/wQEAwIBhjAPBgNVHRMBAf8EBTAD\nAQH/MGgGA1UdDgRhBF9kMToxMToxMTphYzoyYTpiYTo5NzpiMjozZjphYzo3Yjpi\nZDpkYTpiZTpiMTo4YTpmYzo5YTpiYTpiNTpiYzo4MzplNzo1ZTo0MTo2ZjpmMjo3\nMzo5NTo1ODowYzpkYjBqBgNVHSMEYzBhgF9kMToxMToxMTphYzoyYTpiYTo5Nzpi\nMjozZjphYzo3YjpiZDpkYTpiZTpiMTo4YTpmYzo5YTpiYTpiNTpiYzo4MzplNzo1\nZTo0MTo2ZjpmMjo3Mzo5NTo1ODowYzpkYjA/BgNVHREEODA2hjRzcGlmZmU6Ly8x\nMTExMTExMS0yMjIyLTMzMzMtNDQ0NC01NTU1NTU1NTU1NTUuY29uc3VsMAoGCCqG\nSM49BAMCA0gAMEUCICOY0i246rQHJt8o8Oya0D5PLL1FnmsQmQqIGCi31RwnAiEA\noR5f6Ku+cig2Il8T8LJujOp2/2A72QcHZA57B13y+8o=\n-----END CERTIFICATE-----\n"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"requireClientCertificate": true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"trafficDirection": "INBOUND"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"typeUrl": "type.googleapis.com/envoy.api.v2.Listener",
|
||||||
|
"nonce": "00000001"
|
||||||
|
}
|
|
@ -21,8 +21,10 @@ const (
|
||||||
IngressGateway string = "ingress-gateway"
|
IngressGateway string = "ingress-gateway"
|
||||||
TerminatingGateway string = "terminating-gateway"
|
TerminatingGateway string = "terminating-gateway"
|
||||||
ServiceIntentions string = "service-intentions"
|
ServiceIntentions string = "service-intentions"
|
||||||
|
ClusterConfig string = "cluster"
|
||||||
|
|
||||||
ProxyConfigGlobal string = "global"
|
ProxyConfigGlobal string = "global"
|
||||||
|
ClusterConfigCluster string = "cluster"
|
||||||
)
|
)
|
||||||
|
|
||||||
type ConfigEntry interface {
|
type ConfigEntry interface {
|
||||||
|
@ -260,6 +262,8 @@ func makeConfigEntry(kind, name string) (ConfigEntry, error) {
|
||||||
return &TerminatingGatewayConfigEntry{Kind: kind, Name: name}, nil
|
return &TerminatingGatewayConfigEntry{Kind: kind, Name: name}, nil
|
||||||
case ServiceIntentions:
|
case ServiceIntentions:
|
||||||
return &ServiceIntentionsConfigEntry{Kind: kind, Name: name}, nil
|
return &ServiceIntentionsConfigEntry{Kind: kind, Name: name}, nil
|
||||||
|
case ClusterConfig:
|
||||||
|
return &ClusterConfigEntry{Kind: kind, Name: name}, nil
|
||||||
default:
|
default:
|
||||||
return nil, fmt.Errorf("invalid config entry kind: %s", kind)
|
return nil, fmt.Errorf("invalid config entry kind: %s", kind)
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,39 @@
|
||||||
|
package api
|
||||||
|
|
||||||
|
type ClusterConfigEntry struct {
|
||||||
|
Kind string
|
||||||
|
Name string
|
||||||
|
Namespace string `json:",omitempty"`
|
||||||
|
TransparentProxy TransparentProxyClusterConfig `alias:"transparent_proxy"`
|
||||||
|
Meta map[string]string `json:",omitempty"`
|
||||||
|
CreateIndex uint64
|
||||||
|
ModifyIndex uint64
|
||||||
|
}
|
||||||
|
|
||||||
|
type TransparentProxyClusterConfig struct {
|
||||||
|
CatalogDestinationsOnly bool `alias:"catalog_destinations_only"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func (e *ClusterConfigEntry) GetKind() string {
|
||||||
|
return e.Kind
|
||||||
|
}
|
||||||
|
|
||||||
|
func (e *ClusterConfigEntry) GetName() string {
|
||||||
|
return e.Name
|
||||||
|
}
|
||||||
|
|
||||||
|
func (e *ClusterConfigEntry) GetNamespace() string {
|
||||||
|
return e.Namespace
|
||||||
|
}
|
||||||
|
|
||||||
|
func (e *ClusterConfigEntry) GetMeta() map[string]string {
|
||||||
|
return e.Meta
|
||||||
|
}
|
||||||
|
|
||||||
|
func (e *ClusterConfigEntry) GetCreateIndex() uint64 {
|
||||||
|
return e.CreateIndex
|
||||||
|
}
|
||||||
|
|
||||||
|
func (e *ClusterConfigEntry) GetModifyIndex() uint64 {
|
||||||
|
return e.ModifyIndex
|
||||||
|
}
|
|
@ -1124,6 +1124,33 @@ func TestDecodeConfigEntry(t *testing.T) {
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
name: "cluster",
|
||||||
|
body: `
|
||||||
|
{
|
||||||
|
"Kind": "cluster",
|
||||||
|
"Name": "cluster",
|
||||||
|
"Meta" : {
|
||||||
|
"foo": "bar",
|
||||||
|
"gir": "zim"
|
||||||
|
},
|
||||||
|
"TransparentProxy": {
|
||||||
|
"CatalogDestinationsOnly": true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
`,
|
||||||
|
expect: &ClusterConfigEntry{
|
||||||
|
Kind: "cluster",
|
||||||
|
Name: "cluster",
|
||||||
|
Meta: map[string]string{
|
||||||
|
"foo": "bar",
|
||||||
|
"gir": "zim",
|
||||||
|
},
|
||||||
|
TransparentProxy: TransparentProxyClusterConfig{
|
||||||
|
CatalogDestinationsOnly: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
} {
|
} {
|
||||||
tc := tc
|
tc := tc
|
||||||
|
|
||||||
|
|
|
@ -8,11 +8,12 @@ import (
|
||||||
|
|
||||||
"github.com/hashicorp/consul/agent/structs"
|
"github.com/hashicorp/consul/agent/structs"
|
||||||
|
|
||||||
|
"github.com/mitchellh/cli"
|
||||||
|
"github.com/stretchr/testify/require"
|
||||||
|
|
||||||
"github.com/hashicorp/consul/agent"
|
"github.com/hashicorp/consul/agent"
|
||||||
"github.com/hashicorp/consul/api"
|
"github.com/hashicorp/consul/api"
|
||||||
"github.com/hashicorp/consul/sdk/testutil"
|
"github.com/hashicorp/consul/sdk/testutil"
|
||||||
"github.com/mitchellh/cli"
|
|
||||||
"github.com/stretchr/testify/require"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestConfigWrite_noTabs(t *testing.T) {
|
func TestConfigWrite_noTabs(t *testing.T) {
|
||||||
|
@ -2534,6 +2535,68 @@ func TestParseConfigEntry(t *testing.T) {
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
name: "cluster",
|
||||||
|
snake: `
|
||||||
|
kind = "cluster"
|
||||||
|
name = "cluster"
|
||||||
|
meta {
|
||||||
|
"foo" = "bar"
|
||||||
|
"gir" = "zim"
|
||||||
|
}
|
||||||
|
transparent_proxy {
|
||||||
|
catalog_destinations_only = true
|
||||||
|
}
|
||||||
|
`,
|
||||||
|
camel: `
|
||||||
|
Kind = "cluster"
|
||||||
|
Name = "cluster"
|
||||||
|
Meta {
|
||||||
|
"foo" = "bar"
|
||||||
|
"gir" = "zim"
|
||||||
|
}
|
||||||
|
TransparentProxy {
|
||||||
|
CatalogDestinationsOnly = true
|
||||||
|
}
|
||||||
|
`,
|
||||||
|
snakeJSON: `
|
||||||
|
{
|
||||||
|
"kind": "cluster",
|
||||||
|
"name": "cluster",
|
||||||
|
"meta" : {
|
||||||
|
"foo": "bar",
|
||||||
|
"gir": "zim"
|
||||||
|
},
|
||||||
|
"transparent_proxy": {
|
||||||
|
"catalog_destinations_only": true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
`,
|
||||||
|
camelJSON: `
|
||||||
|
{
|
||||||
|
"Kind": "cluster",
|
||||||
|
"Name": "cluster",
|
||||||
|
"Meta" : {
|
||||||
|
"foo": "bar",
|
||||||
|
"gir": "zim"
|
||||||
|
},
|
||||||
|
"TransparentProxy": {
|
||||||
|
"CatalogDestinationsOnly": true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
`,
|
||||||
|
expect: &api.ClusterConfigEntry{
|
||||||
|
Kind: "cluster",
|
||||||
|
Name: "cluster",
|
||||||
|
Meta: map[string]string{
|
||||||
|
"foo": "bar",
|
||||||
|
"gir": "zim",
|
||||||
|
},
|
||||||
|
TransparentProxy: api.TransparentProxyClusterConfig{
|
||||||
|
CatalogDestinationsOnly: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
} {
|
} {
|
||||||
tc := tc
|
tc := tc
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue