Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

connect: add toggle to globally disable wildcard outbound network access when transparent proxy is enabled #9973

Merged
merged 2 commits into from
Apr 6, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions .changelog/9973.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
```release-note:feature
connect: add toggle to globally disable wildcard outbound network access when transparent proxy is enabled
```
110 changes: 110 additions & 0 deletions agent/config/runtime_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
15 changes: 15 additions & 0 deletions agent/consul/fsm/snapshot_oss_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -419,6 +419,16 @@ func TestFSM_SnapshotRestore_OSS(t *testing.T) {
}
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
snap, err := fsm.Snapshot()
require.NoError(t, err)
Expand Down Expand Up @@ -691,6 +701,11 @@ func TestFSM_SnapshotRestore_OSS(t *testing.T) {
require.NoError(t, err)
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
snap, err = fsm2.Snapshot()
require.NoError(t, err)
Expand Down
1 change: 1 addition & 0 deletions agent/consul/state/config_entry.go
Original file line number Diff line number Diff line change
Expand Up @@ -362,6 +362,7 @@ func validateProposedConfigEntryInGraph(
return err
}
case structs.ServiceIntentions:
case structs.ClusterConfig:
default:
return fmt.Errorf("unhandled kind %q during validation of %q", kind, name)
}
Expand Down
12 changes: 10 additions & 2 deletions agent/proxycfg/snapshot.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,9 @@ import (
"fmt"
"sort"

"github.com/hashicorp/consul/agent/structs"
"github.com/mitchellh/copystructure"

"github.com/hashicorp/consul/agent/structs"
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should this have moved?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah I picked this tweak up from @dnephin for invoking goimports:

let g:go_fmt_options = {
    \ 'goimports': '-local github.com/hashicorp/consul',
    \ }

)

// TODO(ingress): Can we think of a better for this bag of data?
Expand Down Expand Up @@ -59,6 +60,9 @@ type configSnapshotConnectProxy struct {
// intentions.
Intentions structs.Intentions
IntentionsSet bool

ClusterConfig *structs.ClusterConfigEntry
ClusterConfigSet bool
}

func (c *configSnapshotConnectProxy) IsEmpty() bool {
Expand All @@ -75,7 +79,8 @@ func (c *configSnapshotConnectProxy) IsEmpty() bool {
len(c.WatchedGatewayEndpoints) == 0 &&
len(c.WatchedServiceChecks) == 0 &&
len(c.PreparedQueryEndpoints) == 0 &&
len(c.UpstreamConfig) == 0
len(c.UpstreamConfig) == 0 &&
!c.ClusterConfigSet
}

type configSnapshotTerminatingGateway struct {
Expand Down Expand Up @@ -355,6 +360,9 @@ type ConfigSnapshot struct {
func (s *ConfigSnapshot) Valid() bool {
switch s.Kind {
case structs.ServiceKindConnectProxy:
if s.Proxy.TransparentProxy && !s.ConnectProxy.ClusterConfigSet {
return false
}
return s.Roots != nil &&
s.ConnectProxy.Leaf != nil &&
s.ConnectProxy.IntentionsSet
Expand Down
30 changes: 30 additions & 0 deletions agent/proxycfg/state.go
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,7 @@ const (
serviceResolverIDPrefix = "service-resolver:"
serviceIntentionsIDPrefix = "service-intentions:"
intentionUpstreamsID = "intention-upstreams"
clusterConfigEntryID = "cluster-config"
svcChecksWatchIDPrefix = cachetype.ServiceHTTPChecksName + ":"
serviceIDPrefix = string(structs.UpstreamDestTypeService) + ":"
preparedQueryIDPrefix = string(structs.UpstreamDestTypePreparedQuery) + ":"
Expand Down Expand Up @@ -315,6 +316,17 @@ func (s *state) initWatchesConnectProxy(snap *ConfigSnapshot) error {
if err != nil {
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
Expand Down Expand Up @@ -846,6 +858,24 @@ func (s *state) handleUpdateConnectProxy(u cache.UpdateEvent, snap *ConfigSnapsh
}
svcID := structs.ServiceIDFromString(strings.TrimPrefix(u.CorrelationID, svcChecksWatchIDPrefix))
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:
return s.handleUpdateUpstreams(u, &snap.ConnectProxy.ConfigSnapshotUpstreams)
}
Expand Down
44 changes: 40 additions & 4 deletions agent/proxycfg/state_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
return func(t testing.TB, cacheType string, request cache.Request) {
require.Equal(t, cachetype.InternalServiceDumpName, cacheType)
Expand Down Expand Up @@ -1538,8 +1550,9 @@ func TestState_WatchesAndUpdates(t *testing.T) {
rootsWatchID: genVerifyRootsWatch("dc1"),
intentionUpstreamsID: genVerifyServiceSpecificRequest(intentionUpstreamsID,
"api", "", "dc1", false),
leafWatchID: genVerifyLeafWatch("api", "dc1"),
intentionsWatchID: genVerifyIntentionWatch("api", "dc1"),
leafWatchID: genVerifyLeafWatch("api", "dc1"),
intentionsWatchID: genVerifyIntentionWatch("api", "dc1"),
clusterConfigEntryID: genVerifyClusterConfigWatch("dc1"),
},
verifySnapshot: func(t testing.TB, snap *ConfigSnapshot) {
require.False(t, snap.Valid(), "proxy without roots/leaf/intentions is not valid")
Expand All @@ -1562,6 +1575,13 @@ func TestState_WatchesAndUpdates(t *testing.T) {
Result: TestIntentions(),
Err: nil,
},
{
CorrelationID: clusterConfigEntryID,
Result: &structs.ConfigEntryResponse{
Entry: nil, // no explicit config
},
Err: nil,
},
},
verifySnapshot: func(t testing.TB, snap *ConfigSnapshot) {
require.True(t, snap.Valid(), "proxy with roots/leaf/intentions is valid")
Expand All @@ -1571,6 +1591,8 @@ func TestState_WatchesAndUpdates(t *testing.T) {
require.True(t, snap.MeshGateway.IsEmpty())
require.True(t, snap.IngressGateway.IsEmpty())
require.True(t, snap.TerminatingGateway.IsEmpty())
require.True(t, snap.ConnectProxy.ClusterConfigSet)
require.Nil(t, snap.ConnectProxy.ClusterConfig)
},
},
},
Expand All @@ -1594,8 +1616,9 @@ func TestState_WatchesAndUpdates(t *testing.T) {
rootsWatchID: genVerifyRootsWatch("dc1"),
intentionUpstreamsID: genVerifyServiceSpecificRequest(intentionUpstreamsID,
"api", "", "dc1", false),
leafWatchID: genVerifyLeafWatch("api", "dc1"),
intentionsWatchID: genVerifyIntentionWatch("api", "dc1"),
leafWatchID: genVerifyLeafWatch("api", "dc1"),
intentionsWatchID: genVerifyIntentionWatch("api", "dc1"),
clusterConfigEntryID: genVerifyClusterConfigWatch("dc1"),
},
verifySnapshot: func(t testing.TB, snap *ConfigSnapshot) {
require.False(t, snap.Valid(), "proxy without roots/leaf/intentions is not valid")
Expand All @@ -1619,6 +1642,17 @@ func TestState_WatchesAndUpdates(t *testing.T) {
Result: TestIntentions(),
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) {
require.True(t, snap.Valid(), "proxy with roots/leaf/intentions is valid")
Expand All @@ -1628,6 +1662,8 @@ func TestState_WatchesAndUpdates(t *testing.T) {
require.True(t, snap.MeshGateway.IsEmpty())
require.True(t, snap.IngressGateway.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
Expand Down
7 changes: 6 additions & 1 deletion agent/structs/config_entry.go
Original file line number Diff line number Diff line change
Expand Up @@ -26,8 +26,10 @@ const (
IngressGateway string = "ingress-gateway"
TerminatingGateway string = "terminating-gateway"
ServiceIntentions string = "service-intentions"
ClusterConfig string = "cluster"

ProxyConfigGlobal string = "global"
ProxyConfigGlobal string = "global"
ClusterConfigCluster string = "cluster"

DefaultServiceProtocol = "tcp"
)
Expand All @@ -41,6 +43,7 @@ var AllConfigEntryKinds = []string{
IngressGateway,
TerminatingGateway,
ServiceIntentions,
ClusterConfig,
}

// ConfigEntry is the interface for centralized configuration stored in Raft.
Expand Down Expand Up @@ -496,6 +499,8 @@ func MakeConfigEntry(kind, name string) (ConfigEntry, error) {
return &TerminatingGatewayConfigEntry{Name: name}, nil
case ServiceIntentions:
return &ServiceIntentionsConfigEntry{Name: name}, nil
case ClusterConfig:
return &ClusterConfigEntry{Name: name}, nil
default:
return nil, fmt.Errorf("invalid config entry kind: %s", kind)
}
Expand Down
Loading