proxycfg: Ensure that endpoints for explicit upstreams in other datacenters are watched in transparent mode (#10391)
Co-authored-by: Freddy Vallenilla <freddy@hashicorp.com>
This commit is contained in:
parent
4b75f15fb7
commit
08ed3edf71
|
@ -0,0 +1,3 @@
|
||||||
|
```release-note:bug
|
||||||
|
proxycfg: Ensure that endpoints for explicit upstreams in other datacenters are watched in transparent mode.
|
||||||
|
```
|
|
@ -380,7 +380,7 @@ func (s *state) initWatchesConnectProxy(snap *ConfigSnapshot) error {
|
||||||
OverrideConnectTimeout: cfg.ConnectTimeout(),
|
OverrideConnectTimeout: cfg.ConnectTimeout(),
|
||||||
}, "discovery-chain:"+u.Identifier(), s.ch)
|
}, "discovery-chain:"+u.Identifier(), s.ch)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return fmt.Errorf("failed to watch discovery chain for %s: %v", u.Identifier(), err)
|
||||||
}
|
}
|
||||||
|
|
||||||
default:
|
default:
|
||||||
|
@ -815,6 +815,7 @@ func (s *state) handleUpdateConnectProxy(u cache.UpdateEvent, snap *ConfigSnapsh
|
||||||
id: svc.String(),
|
id: svc.String(),
|
||||||
name: svc.Name,
|
name: svc.Name,
|
||||||
namespace: svc.NamespaceOrDefault(),
|
namespace: svc.NamespaceOrDefault(),
|
||||||
|
datacenter: s.source.Datacenter,
|
||||||
cfg: cfg,
|
cfg: cfg,
|
||||||
meshGateway: meshGateway,
|
meshGateway: meshGateway,
|
||||||
}
|
}
|
||||||
|
@ -826,26 +827,46 @@ func (s *state) handleUpdateConnectProxy(u cache.UpdateEvent, snap *ConfigSnapsh
|
||||||
|
|
||||||
// Clean up data from services that were not in the update
|
// Clean up data from services that were not in the update
|
||||||
for sn := range snap.ConnectProxy.WatchedUpstreams {
|
for sn := range snap.ConnectProxy.WatchedUpstreams {
|
||||||
|
upstream := snap.ConnectProxy.UpstreamConfig[sn]
|
||||||
|
if upstream.Datacenter != "" && upstream.Datacenter != s.source.Datacenter {
|
||||||
|
continue
|
||||||
|
}
|
||||||
if _, ok := seenServices[sn]; !ok {
|
if _, ok := seenServices[sn]; !ok {
|
||||||
delete(snap.ConnectProxy.WatchedUpstreams, sn)
|
delete(snap.ConnectProxy.WatchedUpstreams, sn)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
for sn := range snap.ConnectProxy.WatchedUpstreamEndpoints {
|
for sn := range snap.ConnectProxy.WatchedUpstreamEndpoints {
|
||||||
|
upstream := snap.ConnectProxy.UpstreamConfig[sn]
|
||||||
|
if upstream.Datacenter != "" && upstream.Datacenter != s.source.Datacenter {
|
||||||
|
continue
|
||||||
|
}
|
||||||
if _, ok := seenServices[sn]; !ok {
|
if _, ok := seenServices[sn]; !ok {
|
||||||
delete(snap.ConnectProxy.WatchedUpstreamEndpoints, sn)
|
delete(snap.ConnectProxy.WatchedUpstreamEndpoints, sn)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
for sn := range snap.ConnectProxy.WatchedGateways {
|
for sn := range snap.ConnectProxy.WatchedGateways {
|
||||||
|
upstream := snap.ConnectProxy.UpstreamConfig[sn]
|
||||||
|
if upstream.Datacenter != "" && upstream.Datacenter != s.source.Datacenter {
|
||||||
|
continue
|
||||||
|
}
|
||||||
if _, ok := seenServices[sn]; !ok {
|
if _, ok := seenServices[sn]; !ok {
|
||||||
delete(snap.ConnectProxy.WatchedGateways, sn)
|
delete(snap.ConnectProxy.WatchedGateways, sn)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
for sn := range snap.ConnectProxy.WatchedGatewayEndpoints {
|
for sn := range snap.ConnectProxy.WatchedGatewayEndpoints {
|
||||||
|
upstream := snap.ConnectProxy.UpstreamConfig[sn]
|
||||||
|
if upstream.Datacenter != "" && upstream.Datacenter != s.source.Datacenter {
|
||||||
|
continue
|
||||||
|
}
|
||||||
if _, ok := seenServices[sn]; !ok {
|
if _, ok := seenServices[sn]; !ok {
|
||||||
delete(snap.ConnectProxy.WatchedGatewayEndpoints, sn)
|
delete(snap.ConnectProxy.WatchedGatewayEndpoints, sn)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
for sn, cancelFn := range snap.ConnectProxy.WatchedDiscoveryChains {
|
for sn, cancelFn := range snap.ConnectProxy.WatchedDiscoveryChains {
|
||||||
|
upstream := snap.ConnectProxy.UpstreamConfig[sn]
|
||||||
|
if upstream.Datacenter != "" && upstream.Datacenter != s.source.Datacenter {
|
||||||
|
continue
|
||||||
|
}
|
||||||
if _, ok := seenServices[sn]; !ok {
|
if _, ok := seenServices[sn]; !ok {
|
||||||
cancelFn()
|
cancelFn()
|
||||||
delete(snap.ConnectProxy.WatchedDiscoveryChains, sn)
|
delete(snap.ConnectProxy.WatchedDiscoveryChains, sn)
|
||||||
|
@ -1721,9 +1742,10 @@ func (s *state) handleUpdateIngressGateway(u cache.UpdateEvent, snap *ConfigSnap
|
||||||
u := makeUpstream(service)
|
u := makeUpstream(service)
|
||||||
|
|
||||||
watchOpts := discoveryChainWatchOpts{
|
watchOpts := discoveryChainWatchOpts{
|
||||||
id: u.Identifier(),
|
id: u.Identifier(),
|
||||||
name: u.DestinationName,
|
name: u.DestinationName,
|
||||||
namespace: u.DestinationNamespace,
|
namespace: u.DestinationNamespace,
|
||||||
|
datacenter: s.source.Datacenter,
|
||||||
}
|
}
|
||||||
err := s.watchDiscoveryChain(snap, watchOpts)
|
err := s.watchDiscoveryChain(snap, watchOpts)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -1781,6 +1803,7 @@ type discoveryChainWatchOpts struct {
|
||||||
id string
|
id string
|
||||||
name string
|
name string
|
||||||
namespace string
|
namespace string
|
||||||
|
datacenter string
|
||||||
cfg reducedUpstreamConfig
|
cfg reducedUpstreamConfig
|
||||||
meshGateway structs.MeshGatewayConfig
|
meshGateway structs.MeshGatewayConfig
|
||||||
}
|
}
|
||||||
|
@ -1795,7 +1818,7 @@ func (s *state) watchDiscoveryChain(snap *ConfigSnapshot, opts discoveryChainWat
|
||||||
Datacenter: s.source.Datacenter,
|
Datacenter: s.source.Datacenter,
|
||||||
QueryOptions: structs.QueryOptions{Token: s.token},
|
QueryOptions: structs.QueryOptions{Token: s.token},
|
||||||
Name: opts.name,
|
Name: opts.name,
|
||||||
EvaluateInDatacenter: s.source.Datacenter,
|
EvaluateInDatacenter: opts.datacenter,
|
||||||
EvaluateInNamespace: opts.namespace,
|
EvaluateInNamespace: opts.namespace,
|
||||||
OverrideProtocol: opts.cfg.Protocol,
|
OverrideProtocol: opts.cfg.Protocol,
|
||||||
OverrideConnectTimeout: opts.cfg.ConnectTimeout(),
|
OverrideConnectTimeout: opts.cfg.ConnectTimeout(),
|
||||||
|
|
|
@ -3,11 +3,12 @@ package proxycfg
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"fmt"
|
"fmt"
|
||||||
"github.com/hashicorp/consul/agent/connect"
|
|
||||||
"sync"
|
"sync"
|
||||||
"testing"
|
"testing"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
"github.com/hashicorp/consul/agent/connect"
|
||||||
|
|
||||||
"github.com/stretchr/testify/require"
|
"github.com/stretchr/testify/require"
|
||||||
|
|
||||||
"github.com/hashicorp/consul/agent/cache"
|
"github.com/hashicorp/consul/agent/cache"
|
||||||
|
@ -362,6 +363,10 @@ func ingressConfigWatchEvent(tlsEnabled bool) cache.UpdateEvent {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func upstreamIDForDC2(name string) string {
|
||||||
|
return fmt.Sprintf("%s?dc=dc2", name)
|
||||||
|
}
|
||||||
|
|
||||||
// This test is meant to exercise the various parts of the cache watching done by the state as
|
// This test is meant to exercise the various parts of the cache watching done by the state as
|
||||||
// well as its management of the ConfigSnapshot
|
// well as its management of the ConfigSnapshot
|
||||||
//
|
//
|
||||||
|
@ -1942,6 +1947,179 @@ func TestState_WatchesAndUpdates(t *testing.T) {
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
// Receiving an empty upstreams from Intentions list shouldn't delete explicit upstream watches
|
||||||
|
"transparent-proxy-handle-update-explicit-cross-dc": {
|
||||||
|
ns: structs.NodeService{
|
||||||
|
Kind: structs.ServiceKindConnectProxy,
|
||||||
|
ID: "api-proxy",
|
||||||
|
Service: "api-proxy",
|
||||||
|
Address: "10.0.1.1",
|
||||||
|
Proxy: structs.ConnectProxyConfig{
|
||||||
|
DestinationServiceName: "api",
|
||||||
|
Mode: structs.ProxyModeTransparent,
|
||||||
|
Upstreams: structs.Upstreams{
|
||||||
|
{
|
||||||
|
CentrallyConfigured: true,
|
||||||
|
DestinationName: structs.WildcardSpecifier,
|
||||||
|
DestinationNamespace: structs.WildcardSpecifier,
|
||||||
|
Config: map[string]interface{}{
|
||||||
|
"connect_timeout_ms": 6000,
|
||||||
|
},
|
||||||
|
MeshGateway: structs.MeshGatewayConfig{Mode: structs.MeshGatewayModeRemote},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
DestinationName: db.Name,
|
||||||
|
DestinationNamespace: db.NamespaceOrDefault(),
|
||||||
|
Datacenter: "dc2",
|
||||||
|
MeshGateway: structs.MeshGatewayConfig{Mode: structs.MeshGatewayModeLocal},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
sourceDC: "dc1",
|
||||||
|
stages: []verificationStage{
|
||||||
|
// Empty on initialization
|
||||||
|
{
|
||||||
|
requiredWatches: map[string]verifyWatchRequest{
|
||||||
|
rootsWatchID: genVerifyRootsWatch("dc1"),
|
||||||
|
intentionUpstreamsID: genVerifyServiceSpecificRequest(intentionUpstreamsID,
|
||||||
|
"api", "", "dc1", false),
|
||||||
|
leafWatchID: genVerifyLeafWatch("api", "dc1"),
|
||||||
|
intentionsWatchID: genVerifyIntentionWatch("api", "dc1"),
|
||||||
|
meshConfigEntryID: genVerifyMeshConfigWatch("dc1"),
|
||||||
|
"discovery-chain:" + upstreamIDForDC2(db.String()): genVerifyDiscoveryChainWatch(&structs.DiscoveryChainRequest{
|
||||||
|
Name: "db",
|
||||||
|
EvaluateInDatacenter: "dc2",
|
||||||
|
EvaluateInNamespace: "default",
|
||||||
|
Datacenter: "dc1",
|
||||||
|
OverrideMeshGateway: structs.MeshGatewayConfig{Mode: structs.MeshGatewayModeLocal},
|
||||||
|
}),
|
||||||
|
},
|
||||||
|
verifySnapshot: func(t testing.TB, snap *ConfigSnapshot) {
|
||||||
|
require.False(t, snap.Valid(), "proxy without roots/leaf/intentions is not valid")
|
||||||
|
require.True(t, snap.MeshGateway.IsEmpty())
|
||||||
|
require.True(t, snap.IngressGateway.IsEmpty())
|
||||||
|
require.True(t, snap.TerminatingGateway.IsEmpty())
|
||||||
|
|
||||||
|
// Centrally configured upstream defaults should be stored so that upstreams from intentions can inherit them
|
||||||
|
require.Len(t, snap.ConnectProxy.UpstreamConfig, 2)
|
||||||
|
|
||||||
|
wc := structs.NewServiceName(structs.WildcardSpecifier, structs.WildcardEnterpriseMeta())
|
||||||
|
require.Contains(t, snap.ConnectProxy.UpstreamConfig, wc.String())
|
||||||
|
require.Contains(t, snap.ConnectProxy.UpstreamConfig, upstreamIDForDC2(db.String()))
|
||||||
|
},
|
||||||
|
},
|
||||||
|
// Valid snapshot after roots, leaf, and intentions
|
||||||
|
{
|
||||||
|
events: []cache.UpdateEvent{
|
||||||
|
rootWatchEvent(),
|
||||||
|
{
|
||||||
|
CorrelationID: leafWatchID,
|
||||||
|
Result: issuedCert,
|
||||||
|
Err: nil,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
CorrelationID: intentionsWatchID,
|
||||||
|
Result: TestIntentions(),
|
||||||
|
Err: nil,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
CorrelationID: meshConfigEntryID,
|
||||||
|
Result: &structs.ConfigEntryResponse{
|
||||||
|
Entry: &structs.MeshConfigEntry{
|
||||||
|
TransparentProxy: structs.TransparentProxyMeshConfig{},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
Err: nil,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
verifySnapshot: func(t testing.TB, snap *ConfigSnapshot) {
|
||||||
|
require.True(t, snap.Valid(), "proxy with roots/leaf/intentions is valid")
|
||||||
|
require.Equal(t, indexedRoots, snap.Roots)
|
||||||
|
require.Equal(t, issuedCert, snap.Leaf())
|
||||||
|
require.Equal(t, TestIntentions().Matches[0], snap.ConnectProxy.Intentions)
|
||||||
|
require.True(t, snap.MeshGateway.IsEmpty())
|
||||||
|
require.True(t, snap.IngressGateway.IsEmpty())
|
||||||
|
require.True(t, snap.TerminatingGateway.IsEmpty())
|
||||||
|
require.True(t, snap.ConnectProxy.MeshConfigSet)
|
||||||
|
require.NotNil(t, snap.ConnectProxy.MeshConfig)
|
||||||
|
},
|
||||||
|
},
|
||||||
|
// Discovery chain updates should be stored
|
||||||
|
{
|
||||||
|
requiredWatches: map[string]verifyWatchRequest{
|
||||||
|
"discovery-chain:" + upstreamIDForDC2(db.String()): genVerifyDiscoveryChainWatch(&structs.DiscoveryChainRequest{
|
||||||
|
Name: "db",
|
||||||
|
EvaluateInDatacenter: "dc2",
|
||||||
|
EvaluateInNamespace: "default",
|
||||||
|
Datacenter: "dc1",
|
||||||
|
OverrideMeshGateway: structs.MeshGatewayConfig{Mode: structs.MeshGatewayModeLocal},
|
||||||
|
}),
|
||||||
|
},
|
||||||
|
events: []cache.UpdateEvent{
|
||||||
|
{
|
||||||
|
CorrelationID: "discovery-chain:" + upstreamIDForDC2(db.String()),
|
||||||
|
Result: &structs.DiscoveryChainResponse{
|
||||||
|
Chain: discoverychain.TestCompileConfigEntries(t, "db", "default", "dc2", "trustdomain.consul", "dc1",
|
||||||
|
func(req *discoverychain.CompileRequest) {
|
||||||
|
req.OverrideMeshGateway.Mode = structs.MeshGatewayModeLocal
|
||||||
|
}),
|
||||||
|
},
|
||||||
|
Err: nil,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
verifySnapshot: func(t testing.TB, snap *ConfigSnapshot) {
|
||||||
|
require.Len(t, snap.ConnectProxy.WatchedGateways, 1)
|
||||||
|
require.Len(t, snap.ConnectProxy.WatchedGateways[upstreamIDForDC2(db.String())], 1)
|
||||||
|
require.Len(t, snap.ConnectProxy.WatchedUpstreams, 1)
|
||||||
|
require.Len(t, snap.ConnectProxy.WatchedUpstreams[upstreamIDForDC2(db.String())], 1)
|
||||||
|
},
|
||||||
|
},
|
||||||
|
// Empty list of upstreams should only clean up implicit upstreams. The explicit upstream db should not
|
||||||
|
// be deleted from the snapshot.
|
||||||
|
{
|
||||||
|
requiredWatches: map[string]verifyWatchRequest{
|
||||||
|
rootsWatchID: genVerifyRootsWatch("dc1"),
|
||||||
|
intentionUpstreamsID: genVerifyServiceSpecificRequest(intentionUpstreamsID,
|
||||||
|
"api", "", "dc1", false),
|
||||||
|
leafWatchID: genVerifyLeafWatch("api", "dc1"),
|
||||||
|
intentionsWatchID: genVerifyIntentionWatch("api", "dc1"),
|
||||||
|
"discovery-chain:" + upstreamIDForDC2(db.String()): genVerifyDiscoveryChainWatch(&structs.DiscoveryChainRequest{
|
||||||
|
Name: "db",
|
||||||
|
EvaluateInDatacenter: "dc2",
|
||||||
|
EvaluateInNamespace: "default",
|
||||||
|
Datacenter: "dc1",
|
||||||
|
OverrideMeshGateway: structs.MeshGatewayConfig{Mode: structs.MeshGatewayModeLocal},
|
||||||
|
}),
|
||||||
|
},
|
||||||
|
events: []cache.UpdateEvent{
|
||||||
|
{
|
||||||
|
CorrelationID: intentionUpstreamsID,
|
||||||
|
Result: &structs.IndexedServiceList{
|
||||||
|
Services: structs.ServiceList{},
|
||||||
|
},
|
||||||
|
Err: nil,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
verifySnapshot: func(t testing.TB, snap *ConfigSnapshot) {
|
||||||
|
require.True(t, snap.Valid(), "should still be valid")
|
||||||
|
|
||||||
|
// Explicit upstream discovery chain watches don't get stored in these maps because they don't
|
||||||
|
// get canceled unless the proxy registration is modified.
|
||||||
|
require.Empty(t, snap.ConnectProxy.WatchedDiscoveryChains)
|
||||||
|
|
||||||
|
// Explicit upstreams should not be deleted when the empty update event happens since that is
|
||||||
|
// for intention upstreams.
|
||||||
|
require.Len(t, snap.ConnectProxy.DiscoveryChain, 1)
|
||||||
|
require.Contains(t, snap.ConnectProxy.DiscoveryChain, upstreamIDForDC2(db.String()))
|
||||||
|
require.Len(t, snap.ConnectProxy.WatchedGateways, 1)
|
||||||
|
require.Len(t, snap.ConnectProxy.WatchedGateways[upstreamIDForDC2(db.String())], 1)
|
||||||
|
require.Len(t, snap.ConnectProxy.WatchedUpstreams, 1)
|
||||||
|
require.Len(t, snap.ConnectProxy.WatchedUpstreams[upstreamIDForDC2(db.String())], 1)
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
"connect-proxy": newConnectProxyCase(structs.MeshGatewayModeDefault),
|
"connect-proxy": newConnectProxyCase(structs.MeshGatewayModeDefault),
|
||||||
"connect-proxy-mesh-gateway-local": newConnectProxyCase(structs.MeshGatewayModeLocal),
|
"connect-proxy-mesh-gateway-local": newConnectProxyCase(structs.MeshGatewayModeLocal),
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue