Skip to main content

Edge exposure and authentication

Internet-facing exposure on a cloud deploy (CIDR, rate limit, WAF, DNS): edge-auth-cloud.md.

Every web UI with a login of its own is exposed through the Envoy Gateway by default. One without a login renders only behind edge OIDC. Access is controlled by OIDC.

There's no bundled issuer. dfe-engine is the identity authority. It holds the local account store and maps every identity to roles and org_ids. Federated login rides external OIDC providers configured at the Envoy edge (generic OIDC: Entra, Okta, Google, Keycloak, or any conformant IdP, dex included if a deployment runs its own). dfe-engine also mints machine tokens (API keys plus engine JWKS) for services, with an audience disjoint from human sessions.

Edge OIDC is off by default. A vanilla deployment does LOCAL login through dfe-ui against the engine's account store, so it's exposed and authenticated with no external IdP required. To federate, turn on one or more providers as a Helm values change (oidc.providers). Each gets its own Envoy SecurityPolicy on the dfe-engine route.

Local accounts follow the tier. At SME they're the daily driver. At enterprise they're BREAK-GLASS ONLY (an ops-managed random secret, deliberately MFA-free, because break-glass exists for when the IdP is down) and all real users federate. MFA always comes from the federated IdP, never locally. SAML IdPs are unsupported because the mainstream connectors are unmaintained upstream. Use their OIDC face.

Identity data stays OUT of the git CRUD cycle. Accounts live in the engine's account store (a mongo-wire document store or the YAML backend), role bindings in the engine's runtime store, and git carries only deployment config (provider issuer URLs, client refs). Roles remain product vocabulary in code.

Port-forwarding is the debug fallback, not the product access path. It works on any deployment (it only needs kubectl), but nothing in the product assumes it.

Exposure topology​

One Gateway fronts the whole HTTP plane. Each UI gets a hostname under the deployment's domain. The route lives in the backend's namespace, so only the parentRef crosses namespaces and no ReferenceGrant is needed. The gateway LB type/class is swappable per deployment (envoyGateway.service values).

  • The plaintext :80 listener carries exactly one route: a 301 redirect to https. Every other HTTPRoute in the chart pins parentRefs[].sectionName: https, so nothing else can attach to :80. Without that, a :80 request gets a 404 instead of a redirect to TLS, and on an internet-facing Gateway an admin UI's Host header reaches the cluster in cleartext with no certificate warning.
  • dfe-ui is always the landing page, with dfe-hyperdx embedded as an iframe inside it. dfe-hyperdx is never presented as its own URL.
  • The links page is a convenience launch pad for admins (see below), never a landing page.

Per-surface OIDC behaviour (Kafbat, dfe-engine's browser vs machine paths, and the rest) is in Per-surface policy below.

The identity plane: engine authority + external OIDC​

There are two browser login paths, and both resolve to the same engine-owned identity:

  • Local (default). dfe-ui serves its own login form and authenticates the user against the engine's account store.
  • Federated (opt-in). The Envoy edge fronts the dfe-engine route with a SecurityPolicy per configured external OIDC provider. Envoy is the OIDC client and the engine only reads the forwarded claim headers. Adding or removing a provider is a Helm values change plus an ArgoCD sync, never a dfe-engine API call.

The groups claim a federated provider forwards is what the edge policies match on, and what the engine maps to roles and tenancy (org_viewer -> pinned ClickHouse identity). Local users carry no groups, so the engine grants their roles through per-user bindings instead.

Route class, and the switches that reach it​

Every route carries its class as data (routes.<name>.class), and the class decides which switch can turn it off.

ClassExposure groupRoutesWhat can disable it
productconsoledfe-ui, dfe-engine (browser families), dfe-hyperdxexposure.groups.console.enabled: false, then its own enabled
apiapidfe-engine-cli (CLI families, /openapi.json, SCIM)exposure.groups.api.enabled: false, then its own enabled
infraadminArgo CD, Forgejo, Kafbat, links, Cruise Controlexposure.groups.admin.enabled: false first, then no login in front of it, then its backend not deployed, then its own enabled
ingestingestotel, receiverits own enabled, and no UI switch reaches it

Each group also carries the allow and deny lists its routes admit by address (edge-auth-cloud.md). Every web UI with a login renders by default, through a five-step cascade where the first match wins:

  1. Class product, api or infra with its group's enabled: false means not rendered.
  2. Class infra with ownLogin: false (links, Cruise Control) means not rendered unless the edge policy is in front of it: oidc.enabled, an oidc.providers entry and edgePolicy: true.
  3. A backend another chart has not deployed means not rendered: Cruise Control off the Kafka cluster tier, Forgejo unless deployRepo.bundled (the dfe.hyperi.io/bundled-deploy-repo cluster label).
  4. routes.<name>.enabled: false means not rendered.
  5. Otherwise rendered.

A public flag on a route with no login of its own (exposure.groups.admin.tools.links: true) and no edge OIDC fails the render by name, rather than serving the route behind a CIDR-only policy. Under Argo CD that leaves the gateway Application in ComparisonError until the flag is removed or a provider is configured.

exposure.groups.admin.enabled is the one flip that takes every ops surface off the edge. A route's own enabled: true doesn't override it, and the switch withdraws their edge policies too. It never touches dfe-ui, the engine API or dfe-hyperdx. exposure.groups.admin.tools is the one list of admin UIs, one key per class-infra route.

On a cloud deploy with an internet-facing Gateway Service, this defaults off instead. See edge-auth-cloud.md.

The exposure: key is shared with the dfe-receiver chart, which reads exposure.groups.ingest for its ingest door and exposure.public for its load balancer's shape. One block per deployment, each chart reading the keys it owns.

Per-surface policy​

SurfaceHostnameClassExposed by defaultEdge policy
dfe-ui (+ dfe-hyperdx iframe)dfe.{domain}productyeslocal login by default, OIDC when a provider is configured
dfe-engine browser pathsdfe.{domain}/api interactiveproductyesOIDC when configured, machine paths API-key/JWT, never redirected
Argo CDargocd.{domain}infrayesedge OIDC + group check, Argo signs in through the same provider, dfe-admins and dfe-infra -> admin, dfe-infra-viewers -> read-only (gateway-oidc.md)
Links pagelinks.{domain}infraonly behind edge OIDCedge OIDC + group check, no auth of its own
Cruise Control UIcruise-control.{domain}infraonly behind edge OIDC, Kafka cluster tieredge OIDC + group check, no auth of its own
Forgejo (bundled fallback)git.{domain}infraonly where bundlededge OIDC + group check
Kafbatkafbat.{domain}infrayesits own OIDC (integrated-app pattern) -- group check is app-side
dfe-hyperdxhyperdx.{domain}productyesits own PEP on the dfe_token cookie -- group check is app-side
otel OTLP ingestotel.{domain}ingestno (otel.ingress.enabled)the ingest group's lists, then a bearer token checked by the collector
dfe-receiver ingestreceiver.{domain}ingeston-prem only (exposure.groups.ingest.door: gateway)the ingest group's lists, then the receiver's own server.auth, restated as routes.receiver.authMode (header or bearer when internet-facing)

The gate on an infra route is a deny-by-default Envoy SecurityPolicy carrying three blocks that only work together. oidc logs the user in and mints a per-route access-token cookie. jwt re-validates that cookie against the provider's JWKS. authorization requires the groups claim to carry one of adminGroups (argocd/values/common.yaml, names from dfe-engine docs/control-plane/rbac-vocabulary.md, default dfe-admins, dfe-infra). A token with no groups claim is refused. Per-route cookie names stop a session minted on one infra host being replayed on another.

Kafbat and dfe-hyperdx are exempt from the EDGE gate and check groups app-side instead. Kafbat runs its own OIDC against the same provider, and dfe-hyperdx can't complete an interactive redirect inside the dfe-ui iframe. Kafbat stays class infra, under the kill switch. dfe-hyperdx is class product, beside the console that frames it.

Gating Forgejo also closes git-over-HTTP from OUTSIDE the cluster. Nothing in the product needs that, because Argo CD and dfe-engine reach the deploy repo on its in-cluster Service DNS.

Public hostnames on a cloud deploy, the render guard, CIDR, rate limit, WAF and DNS specifics: edge-auth-cloud.md.

The ingest edge​

Data ingest is a second door with its own model, the ingest exposure group exposure.groups.ingest. On a cloud the door is the receiver's own load balancers, which render only while allow names the senders, so a default deploy has no public ingest. On-prem the door is the gateway route on receiver.{domain}, HTTP /ingest only. The receiver's own server.auth.mode defaults to none on either door. See ingest-edge.md.

A bundled static launch pad answering "where is everything?" for admins: every surface the deployment exposes, with reachability dots. It renders from the same GitOps values that deploy the services, so the sync that moves a URL re-renders the page. Admins-only at the edge. On an undomained rig it falls back to the port-forward layout.

The gateway publishes the admin UIs it renders as ConfigMap dfe-admin-links (key admin_links.json) in the app namespace, and the engine reads it as DFE_ENGINE_ADMIN_LINKS. The list comes from the same exposure cascade as the routes, so an infra route that does not render is not listed, and each entry carries the route's own hostname and its backend Service as the probe. Every infra route that renders must carry adminLink with a name and a one-line purpose, and one without fails the render by name. The engine reads the variable only at startup, so a list created after the engine started lands on its next restart.

Gateway install​

argocd/bootstrap/envoy-gateway-app.yaml installs the operator and CRDs, and envoy-gateway-config configures it.