Skip to content

Separate the UI leg from the data leg in the exposure model #3491

Description

@myasnikovdaniil

Summary

The exposure half of #2811 is passthrough by design, and passthrough is the right answer for a data protocol and the wrong answer for a browser. Several managed apps ship both: an admin UI over HTTP and a client protocol over TLS. Nothing in the epic currently owns the UI leg, and because #2815 reads as "external exposure, solved", the gap is easy to mistake for covered. This issue names the two legs, records why they cannot share one mechanism, and collects the decisions the UI leg needs.

The two legs

For an app like rabbitmq or opensearch the same release has to be reachable two ways, and the two ways want opposite treatment:

data leg (e.g. AMQPS 5671, OpenSearch HTTP API 9200) UI leg (e.g. management 15672/15671, Dashboards 5601)
Consumer a client the tenant configures a human in a browser
Path Gateway TLS-passthrough, routed by SNI edge-terminated ingress or HTTPRoute
Certificate the interior, operator- or chart-issued one a publicly-trusted wildcard or per-host ACME cert
What the consumer must obtain first ca.crt, per the #2814 contract nothing
Owner today #2815 nobody

The decisive asymmetry is what the consumer can be asked to do. Handing an AMQP client a ca.crt and a --cafile flag is a documented, one-time configuration step, which is exactly what makes passthrough acceptable there: nothing terminates, so the certificate the client validates is byte-identical to the interior one, and no private key sits at the edge. A browser cannot be configured that way. Route a management UI through a passthrough listener and every visitor gets an interstitial warning, per release, forever, because the interior certificate is a per-release self-signed CA. Telling tenants to import a CA into their browser trust store is not a supported answer, and telling them to click through is worse than not publishing the UI at all.

So the UI leg has to terminate at the edge, on the wildcard or ACME certificate the edge workstream already delivers (#2812, #2400, #2820). That is not a weakening of the end-to-end story recorded in #2816 — it is a different consumer with a different threat model, and it should be stated as a deliberate split rather than discovered later as an inconsistency.

How it works today

Four incompatible shapes, verified against main at 2823b7680. Only three app charts in packages/apps render any HTTP routing object at all.

  1. Edge-terminated, both data planes — harbor. packages/apps/harbor/templates/ingress.yaml and httproute.yaml, branching on the injected _namespace.gateway to render either an Ingress with ingressClassName or an HTTPRoute with parentRefs at the cozystack Gateway. Host is .Values.host | default "<release>.<_namespace.host>". Three certificate modes are branched in-template: an operator wildcard (skip the per-host secret, let nginx serve its default SSL cert), http01 (cert-manager.io/cluster-issuer plus acme.cert-manager.io/http01-ingress-ingressclassname), and dns01 (issuer annotation only). Backend is backend-protocol: "HTTP". This is the only shape a UI leg should want, and it exists exactly once.
  2. nginx-level passthrough for a nested cluster — kubernetes. packages/apps/kubernetes/templates/ingress.yaml sets ssl-passthrough: "true", backend-protocol: AUTO_HTTP and a configuration-snippet rewriting $proxy_upstream_name, republishing the tenant cluster's own ingress-nginx. Correct for what it does and not reusable as a UI-leg pattern.
  3. Published by the tenant module, not the app — bucket. packages/apps/bucket/templates renders no routing object; the S3 endpoint is published by packages/extra/seaweedfs, and the app chart retains only hooks/cleanup-acme.yaml to delete the legacy per-bucket Ingress and Certificate it used to render.
  4. A UI with no publish path at all. opensearch ships Dashboards behind dashboards.enabled with a Service on 5601 and no route of any kind, so it is reachable only by port-forward. rabbitmq is the same: the chart pins the -management image, so the plugin is enabled and the operator's client Service carries the management port, and there is no route to it. The one way to reach either from outside today is external: true, which turns the app's Service into a LoadBalancer and publishes the admin UI on a public IP in plaintext, next to the plaintext data port.

Point 4 is the actual state of the art for every data app with a UI, and it is the thing this issue exists to fix. Point 1 is the shape to factor out, not to copy a fourth time.

Why this needs a decision before the per-app TLS PRs land

#2683 (rabbitmq) makes tls.disableNonTLSListeners reachable while adding no route, and #2682 does the equivalent for OpenSearch Dashboards. That is not a defect in either PR — routing was never in their scope — but it means the interaction below arrives with them and is caught by nothing:

The UI leg's backend protocol depends on a TLS value the app owns. With disableNonTLSListeners off, the edge speaks plaintext to the management port, exactly like harbor. With it on, that port is gone and the edge must speak HTTPS to the TLS management port against a per-release private CA, which means backend-protocol: "HTTPS" plus either proxy-ssl-secret pointed at the trust anchor or proxy-ssl-verify: "off" on the nginx path, and a BackendTLSPolicy on the Gateway path. A UI route that hardcodes HTTP, as the only existing example does, breaks silently the moment a tenant sets the flag. Whatever renders the UI leg has to derive the backend protocol from the app's resolved TLS state rather than assume it.

Decisions this issue is asking for

Non-goals

Part of #2811. Related: #2814 (trust-anchor convergence, which the data leg depends on and the UI leg does not), #2815 (data leg), #2683 and #2682 (the two per-app PRs that make the backend-protocol interaction reachable), #3164 and PR #3218 (the exposure API this may or may not belong to).

Metadata

Metadata

Labels

area/networkingIssues or PRs related to networking (ingress, gateway, vpn, metallb, cilium, kube-ovn)area/platformIssues or PRs related to platform infrastructure (bundle, flux, talos, installer)kind/featureCategorizes issue or PR as related to a new featurepriority/important-longtermImportant over the long term, but may not be staffed and/or may need multiple releases to complete

Type

No type

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions