Skip to main content

The edge module

The public gateway (helm/edge/gateway) and the fleet tunnel (culvert's published thin chart, beside helm/charts/dfe-extras) are one add-on, deployed by argocd/appsets/layer2-edge.yaml: everything that decides whether traffic from outside reaches a DFE workload.

It is ON by default: a deployment with no door reaches nothing from outside. edge.enabled: false generates no Application, and the apps stay on ClusterIP behind whatever the deployer brings. Turning it off AFTER a load balancer exists ORPHANS it, so the off path is destroy, then disable.

It also takes external-dns's and the load balancer controller's IAM identities. Both keep running, start failing AccessDenied while reporting Ready, and the PRIVATE zone stops being published. Only the tunnel's PKI volume survives (Prune=false,Delete=false).

The tier table is per flavour, in argocd/values/edge-<flavour>.yaml.

The five groups​

Every surface falls into exactly one:

  • (a) dfe-ui and HyperDX -- the product's own console and the search it frames, public on a cloud deploy to the addresses exposure.groups.console.allow names, and to none while it is empty.
  • (b) the engine API -- on dfe-ui's own hostname, because the browser calls it at that origin. Its browser families follow the console group; its CLI families, /openapi.json and SCIM are a second route in the api group, with its own list.
  • (c) the admin UIs -- Argo CD, Kafbat, Forgejo, links, Cruise Control. Off one at a time, behind a class-wide kill switch.
  • (d) the ingest doors -- the receiver and the OTLP route, plus the tunnel that reaches the receiver without a public address.
  • (e) everything else -- Kafka, ClickHouse, Keeper, PostgreSQL, OpenBao, the Kubernetes API. Never exposed, on any flavour.

The three tiers​

  1. On by default, where a DDoS attempt cannot blow the budget.
  2. Opt-in. Most carry a spend line. The engine's two family switches and OTLP ingress are opt-in for what they EXPOSE, and cost nothing.
  3. Not offered. No key here reaches one.

Buckets are aws.md's: relative to the cluster's own compute, the pricing model named, never a rate.

AWS​

MechanismGroupTierDefaultDial keyCost
Public gateway listener on an NLBa b c1onedge.product.publicXS hourly
Engine API on the product hostname, browser familiesb1onedge.engine_api.with_productnone
The CLI's families, and /openapi.jsonb2offedge.engine_api.cli_families_publicno spend
SCIM, /api/v1/scim/v2b2offedge.engine_api.scim_publicno spend
TLS floor and HSTSa b1onedge.product.tlsnone
Internal-name certificates from the internal CA, because public ACME cannot validate a private zonea b c1dfe-internal-catls.issuerName (tls.acme.* or tls.vault.* for a browser-trusted leaf)none
Rate limit, per proxy replicaa b1onedge.product.rate_limitnone
CIDR filter per group, at Envoy (web) and the load balancer (the union, everything)a b c d1empty, so every web route answers 403exposure.groups.<group>.allow and .deny in the deploy repo (edge.product.allowed_cidrs is the dial's note of it)none
A CDN's X-Forwarded-For believed on the public listenersa b2none believededge.product.trusted_proxy_cidrsno spend
Edge OIDC on the admin routesc1onedge.admin_uis.oidcnone
Admin UI routes, one at a timec1offedge.admin_uis.public.*none
Class-wide admin kill switchc1offedge.admin_uis.externalnone
Receiver on its own load balancer, once senders are namedd2loadbalancer, closed while allow is emptyedge.groups.ingest (exposure.groups.ingest)L, per GB processed
Elastic IPs for the receiver's NLBd1onedge.aws.receiver_eipsXS hourly, while closed too
Receiver on ClusterIP, tunnel-reachedd1offedge.groups.ingest.door: tunnelnone
Tunnel on a NodePortd1 once onoffthe engine's instance values filenone
An Elastic IP forwarder in front of itd2byoedge.ingest.tunnel.address.mode (forwarder is unproven, edge-vpn.md)XS hourly
Admin reach-back to one applianced1onedge.ingest.tunnel.admin_peerXS hourly
OTLP ingress on otel.<domain>, bearer token onlyd2offedge.ingest.otel.enabledno spend
A CDN or managed WAF in fronta2noneedge.product.waf.modeS, per GB and per request
Shield Advanced, Global Acceleratora3absent----
Group (e)e3absent----

Public web access is default-deny on every cloud flavour (EDGE-AUTH-cloud.md). The load-balancer half fences the WHOLE front door, so it takes the ingest group's list into its union while an ingest route renders. The aws root applies both dial keys: it adds every NAT gateway's address to the list, so the cluster's own callers on the public hostname still get in, and passes edge.product.trusted_proxy_cidrs through as it stands. It trusts no subnet of its own: the NLB hands Envoy the client as the TCP peer, so no host in the VPC can name a client with a forged X-Forwarded-For (EDGE-AUTH-cloud.md). A guarded test run replaces the list with its runner's /32.

What the engine API's split gets wrong​

The families are chart data (routes.dfeEngine), not a dial list: they move with every router the engine adds. Five traps:

  • A PathPrefix matches whole path elements, so /api/v1/auth never matches /api/v1/authoring and /api/v1/sample never matches /api/v1/samples.
  • GET /api/v1/auth/oidc/{provider}/callback is a top-level browser navigation, so an edge login in front of it swallows the engine's own external-IdP login. The two are ALTERNATIVES on a hostname.
  • The sampler is POST /api/v1/sources/{source}/sample, on the public sources prefix -- opening the sample family is a different thing.
  • The HyperDX fork reaches /api/v1/hyperdx from in-cluster, so its engine origin stays the internal hostname or the Service.
  • /api/v1/tasks carries the only SSE stream, and stays private until the console wants it.

Response hardening​

HTTPS routes send a one-year HSTS header: public listeners on edge.product.tls.hsts, the wildcard on the gateway chart's tls.edge.hsts. A browser holding it gets no click-through on a certificate it does not trust, so a root re-minted on every rebuild would lock the admin out of the console. tls.edge.hsts left empty follows the edge issuer:

Edge issuerWildcard HSTS
tls.issuerName is tls.internalCA.issuerName, tls.internalCA.persist offoff
the same, tls.internalCA.persist onon
Vault/OpenBao PKI, ACME, or any other issueron

An explicit true or false wins over the table. edge.product.tls.hsts does not follow it.

bootstrap.sh sets tls.internalCA.persist.enabled=true only for its own pre-apply of the root restore; no appset sets it for Argo's gateway app. A deployment that persists its root (DFE_CA_PERSIST=true) must also set tls.internalCA.persist.enabled: true, and the store name, in its deploy repo's gateway overlay, or Argo renders the persist objects and the wildcard's HSTS off.

routes.<key>.hiddenPaths answers a path with a 404 from the proxy; dfe-ui hides its unauthenticated /metrics. /docs and /redoc follow api.docsEnabled in infra/common.yaml, read by the engine, the gateway and the links page alike.

GCP and Azure​

Every row above holds, with these differences. Neither has a tofu root yet, so both are values-only.

MechanismGroupTierDefaultDial keyCost
Tunnel on the chart's own LoadBalancerd1 once onoffexposure.serviceTypeS, per GB
An address in front of the tunneld2none built----

A deployment that brings the tunnel pays that cloud's per-GB rate until the flavour file carries the AWS NodePort override.

On-prem​

Every row holds except these. There is no cloud bill, so a tier-2 line here is about the node's NIC and disk.

MechanismGroupTierDefaultDial keyCost
Gateway and tunnel on MetalLBa b c d1onenvoyGateway.service, exposure.serviceTypenone
Receiver routed through the gatewayd1gatewayexposure.groups.ingest.doornone
Admin reach-backd3absent----

The reach-back is cloud-only: on-prem the operator is already on the LAN.

OTLP ingress​

Telemetry from outside the cluster -- edge agents, other estates -- lands on otel.<domain>. It is OFF on every flavour. The stack's own senders reach the collector on its Service, on 4317/4318, and never need it.

On, the gateway routes otel.<domain> to a second collector receiver on 4319 that checks a bearer token before any pipeline sees the request. The token comes from the deployment's secret store through an ExternalSecret. There is no unauthenticated mode.

The dial's edge.ingest.otel block is the otel.ingress: block of the deploy repo's infra/common.yaml, key for key. Paste it there, because the gateway and the collector are two Applications and that is the one file both read:

otel:
ingress:
enabled: true
auth:
remoteKey: <path of the token in the secret store> # property: token

On with remoteKey empty is refused by render_dial.py, the gateway chart and the collector chart alike. Senders POST to /v1/traces, /v1/metrics or /v1/logs with Authorization: Bearer <token>. dfe-ops otel-ingress says whether the door is exposed and where, and the readiness gate and the access summary print the same lines.

The tunnel's inbound, and reaching one appliance​

Appliances DIAL IN and hold the tunnel open, so nothing on an appliance's own network is ever exposed. exposure.loadBalancerSourceRanges starts empty -- every source address on earth, which is the normal case for a fleet. Per-client PKI, WireGuard peer keys and tls-crypt-v2 authenticate a client; peers.classes.appliance.isolation stops one compromised appliance reaching the fleet through the hub.

Reaching one appliance is a ROUTE, not a dial-in. culvert's client-to-client verdict drops an admin that dials in, so dfe-ops bastion hub <peer> programs the tunnel's client range at the culvert pod and opens the logged Session Manager shell; down removes the range. The appliance accepts ssh on 22 and https on 443 on its tunnel interface. Run the isolation regression on every change to the class policy: one appliance peer still cannot reach another. None of it has been exercised against a cluster. edge-vpn.md carries the VPC routing, what bounds it, and the hyperi-io/culvert#40 peer shape that replaces it.

Proving it​

dfe-ops edge-probe proves from outside what can be proven from outside; the render cases need no cluster.

ClaimTierProofVenue
The module off renders no door1render caserender
Admin UIs off by default, kill switch beats a route flag1scripts/test-route-exposure.shrender
Every admin UI the gateway lists loads: no redirect loop, no 5xx, no 4xx but 401/403/4041dfe-ops admin-probe, run by the readiness gatekind, on-prem
A public route with no auth is refused1scripts/test-route-exposure.shrender
The engine's public route: browser families, the opt-ins add theirs, never /docs1scripts/test-route-exposure.shrender
A browser family answers and a private one 404s1dfe-ops edge-probekind, on-prem
Tier-3 keys are absent1render caserender
TLS floor, HSTS, rate limit, CIDR filter1dfe-ops edge-probekind, on-prem
HSTS on every HTTPS route, each listener on its own switch, the wildcard's default following the edge issuer; dfe-ui's /metrics a 404 on both faces1scripts/tests/test_gateway_route_hardening.pyrender
/docs and /redoc routed and listed only where the engine serves them1scripts/tests/test_engine_docs_surface.pyrender
The receiver is private in vpn mode1dfe-ops edge-probekind, on-prem
OTLP ingress renders no route while off, and refuses to render on with no token1scripts/tests/test_otel_ingress.py, scripts/test-route-exposure.shrender
otel.<domain> answers 404 while off, and 401 to a tokenless request while on1dfe-ops edge-probekind, on-prem, AWS
Edge OIDC login and group check1dfe-ops idp and the onboarding suiteon-prem
A client reaches the receiver only through the tunnel1dial in, post, then post direct and failon-prem
One appliance peer cannot reach another1the isolation regressionon-prem
The CIDR filter bites at the load balancer1dfe-ops edge-probe off-listAWS
preserve_client_ip and the frontend security group1describe the load balancerAWS
The public certificate is issued by DNS-011chain assert on the public nameAWS
The reach-back reaches an appliance1dfe-ops bastion hub, then sshAWS