diff --git a/agent/proxycfg/api_gateway.go b/agent/proxycfg/api_gateway.go index c7c8c57ee9..698b1359a9 100644 --- a/agent/proxycfg/api_gateway.go +++ b/agent/proxycfg/api_gateway.go @@ -4,6 +4,7 @@ import ( "context" "fmt" + cachetype "github.com/hashicorp/consul/agent/cache-types" "github.com/hashicorp/consul/agent/proxycfg/internal/watch" "github.com/hashicorp/consul/agent/structs" "github.com/hashicorp/consul/proto/pbpeering" @@ -225,7 +226,7 @@ func (h *handlerAPIGateway) handleGatewayConfigUpdate(ctx context.Context, u Upd return fmt.Errorf("invalid type for config entry: %T", resp.Entry) } - return nil + return h.watchIngressLeafCert(ctx, snap) } // handleInlineCertConfigUpdate stores the certificate for the gateway @@ -416,7 +417,7 @@ func (h *handlerAPIGateway) handleRouteConfigUpdate(ctx context.Context, u Updat // // TODO This would probably be more generally useful as a helper in the structs pkg func (h *handlerAPIGateway) referenceIsForListener(ref structs.ResourceReference, listener structs.APIGatewayListener, snap *ConfigSnapshot) bool { - if ref.Kind != snap.APIGateway.GatewayConfig.Kind { + if ref.Kind != structs.APIGateway && ref.Kind != "" { return false } if ref.Name != snap.APIGateway.GatewayConfig.Name { @@ -424,3 +425,30 @@ func (h *handlerAPIGateway) referenceIsForListener(ref structs.ResourceReference } return ref.SectionName == "" || ref.SectionName == listener.Name } + +func (h *handlerAPIGateway) watchIngressLeafCert(ctx context.Context, snap *ConfigSnapshot) error { + // Note that we DON'T test for TLS.enabled because we need a leaf cert for the + // gateway even without TLS to use as a client cert. + if !snap.APIGateway.GatewayConfigLoaded { + return nil + } + + // Watch the leaf cert + if snap.APIGateway.LeafCertWatchCancel != nil { + snap.APIGateway.LeafCertWatchCancel() + } + ctx, cancel := context.WithCancel(ctx) + err := h.dataSources.LeafCertificate.Notify(ctx, &cachetype.ConnectCALeafRequest{ + Datacenter: h.source.Datacenter, + Token: h.token, + Service: h.service, + EnterpriseMeta: h.proxyID.EnterpriseMeta, + }, leafWatchID, h.ch) + if err != nil { + cancel() + return err + } + snap.APIGateway.LeafCertWatchCancel = cancel + + return nil +} diff --git a/agent/proxycfg/snapshot.go b/agent/proxycfg/snapshot.go index b63a78ad8a..f341d53a04 100644 --- a/agent/proxycfg/snapshot.go +++ b/agent/proxycfg/snapshot.go @@ -678,6 +678,10 @@ type configSnapshotAPIGateway struct { TCPRoutes watch.Map[structs.ResourceReference, *structs.TCPRouteConfigEntry] Certificates watch.Map[structs.ResourceReference, *structs.InlineCertificateConfigEntry] + // LeafCertWatchCancel is a CancelFunc to use when refreshing this gateway's + // leaf cert watch with different parameters. + LeafCertWatchCancel context.CancelFunc + // Listeners is the original listener config from the api-gateway config // entry to save us trying to pass fields through Upstreams Listeners map[string]structs.APIGatewayListener @@ -733,6 +737,7 @@ func (c *configSnapshotAPIGateway) ToIngress(datacenter string) (configSnapshotI upstreams.DiscoveryChain = synthesizedChains return configSnapshotIngressGateway{ + Upstreams: c.Upstreams, ConfigSnapshotUpstreams: upstreams, GatewayConfigLoaded: true, Listeners: ingressListeners, @@ -928,6 +933,7 @@ func (s *ConfigSnapshot) Valid() bool { case structs.ServiceKindAPIGateway: // TODO Is this the proper set of things to validate? return s.Roots != nil && + s.APIGateway.Leaf != nil && s.APIGateway.GatewayConfigLoaded && s.APIGateway.BoundGatewayConfigLoaded && s.APIGateway.AreHostsSet && diff --git a/test/integration/connect/envoy/case-api-gateway-smoke/verify.bats b/test/integration/connect/envoy/case-api-gateway-smoke/verify.bats deleted file mode 100644 index f0adff6b46..0000000000 --- a/test/integration/connect/envoy/case-api-gateway-smoke/verify.bats +++ /dev/null @@ -1,7 +0,0 @@ -#!/usr/bin/env bats - -load helpers - -@test "api gateway proxy admin is up on :20000" { - retry_default curl -f -s localhost:20000/stats -o /dev/null -} \ No newline at end of file diff --git a/test/integration/connect/envoy/case-api-gateway-smoke/capture.sh b/test/integration/connect/envoy/case-api-gateway-tcp-simple/capture.sh similarity index 100% rename from test/integration/connect/envoy/case-api-gateway-smoke/capture.sh rename to test/integration/connect/envoy/case-api-gateway-tcp-simple/capture.sh diff --git a/test/integration/connect/envoy/case-api-gateway-smoke/service_gateway.hcl b/test/integration/connect/envoy/case-api-gateway-tcp-simple/service_gateway.hcl similarity index 100% rename from test/integration/connect/envoy/case-api-gateway-smoke/service_gateway.hcl rename to test/integration/connect/envoy/case-api-gateway-tcp-simple/service_gateway.hcl diff --git a/test/integration/connect/envoy/case-api-gateway-smoke/setup.sh b/test/integration/connect/envoy/case-api-gateway-tcp-simple/setup.sh similarity index 82% rename from test/integration/connect/envoy/case-api-gateway-smoke/setup.sh rename to test/integration/connect/envoy/case-api-gateway-tcp-simple/setup.sh index 6770555a08..5cbb7a2834 100644 --- a/test/integration/connect/envoy/case-api-gateway-smoke/setup.sh +++ b/test/integration/connect/envoy/case-api-gateway-tcp-simple/setup.sh @@ -7,7 +7,7 @@ kind = "api-gateway" name = "api-gateway" listeners = [ { - port = 9999 + port = 9999 protocol = "tcp" } ] @@ -18,11 +18,12 @@ kind = "tcp-route" name = "api-gateway-route" services = [ { - name = "s1" + name = "s1" } ] parents = [ { + kind = "api-gateway" name = "api-gateway" } ] @@ -32,4 +33,4 @@ register_services primary gen_envoy_bootstrap api-gateway 20000 primary true gen_envoy_bootstrap s1 19000 -gen_envoy_bootstrap s2 19001 +gen_envoy_bootstrap s2 19001 \ No newline at end of file diff --git a/test/integration/connect/envoy/case-api-gateway-smoke/vars.sh b/test/integration/connect/envoy/case-api-gateway-tcp-simple/vars.sh similarity index 100% rename from test/integration/connect/envoy/case-api-gateway-smoke/vars.sh rename to test/integration/connect/envoy/case-api-gateway-tcp-simple/vars.sh diff --git a/test/integration/connect/envoy/case-api-gateway-tcp-simple/verify.bats b/test/integration/connect/envoy/case-api-gateway-tcp-simple/verify.bats new file mode 100644 index 0000000000..3b61378813 --- /dev/null +++ b/test/integration/connect/envoy/case-api-gateway-tcp-simple/verify.bats @@ -0,0 +1,17 @@ +#!/usr/bin/env bats + +load helpers + +@test "api gateway proxy admin is up on :20000" { + retry_default curl -f -s localhost:20000/stats -o /dev/null +} + +@test "api gateway should have healthy endpoints for s1" { + assert_upstream_has_endpoints_in_status 127.0.0.1:20000 s1 HEALTHY 1 +} + +@test "api gateway should be able to connect to s1 via configured port" { + run retry_default curl -s -f -d hello localhost:9999 + [ "$status" -eq 0 ] + [[ "$output" == *"hello"* ]] +} \ No newline at end of file