HTTPS upstreams with per-service CA trust (capability #3 of #11) #28

Closed
opened 2026-08-26 10:12:21 +00:00 by james.oates · 1 comment
Owner

Implements HTTPS upstream support so ER can reverse-proxy to a TLS backend (e.g. OpenBao's UI) and verify its cert against a per-service CA — bridging trust so a browser reaches the backend over ER's real (ACME) cert without installing the internal CA. Delivers capability #3 of #11.

Design/plan (Obsidian, source of truth):

  • Spec: Personal/Projects/edge-router/specs/2026-08-26-https-upstream-design.md
  • Plan: Personal/Projects/edge-router/plans/2026-08-26-https-upstream.md

Status: code complete on branch feat/https-upstream (pushed); full suite 253/0, zero warnings. Awaiting merge-to-main decision.

Tasks (TDD):

  • 1. Model fields + migration 011 (target_scheme, upstream_ca_cert)
  • 2. Cached CA-verifying upstream client builder (upstream.rs, on AppState)
  • 3. Wire HTTP proxy path (scheme-aware URL, cached client, WS-on-https guard)
  • 4. Health checks honour scheme + CA
  • 5. Config + API surface (TOML, REST, scheme validation)
  • 6. Docs

Scope: health checks in; wss-to-backend out (raw-TCP path → 501 for now); CA replaces roots (pinned), mirroring broker.rs; no skip-verify; fail-closed → 502. Independent of BAO_CACERT (that's ER-as-consumer trust; this is ER-as-proxy trust).

Live verification (DevInfra, after merge/publish) — two deployment preconditions:

  • target_host must be bao.dev.oates.ws (devnet alias) and that name must be a SAN on OpenBao's actual cert (verify with openssl s_client).
  • The internal CA PEM must be mounted into ER's container at the upstream_ca_cert path (can be the same file as BAO_CACERT).

Note on #11: this delivers capability #3 only. Capability #2 (ER presenting a real *.dev.oates.ws cert to the browser) is assumed satisfied by ER's ACME/[[tls]]; #11 hints devinfra may use an externally-renewed lego cert, which ER cannot import — that path is unverified. Do not close #11 on this alone.

Implements HTTPS upstream support so ER can reverse-proxy to a TLS backend (e.g. OpenBao's UI) and verify its cert against a per-service CA — bridging trust so a browser reaches the backend over ER's real (ACME) cert without installing the internal CA. Delivers capability #3 of #11. **Design/plan (Obsidian, source of truth):** - Spec: `Personal/Projects/edge-router/specs/2026-08-26-https-upstream-design.md` - Plan: `Personal/Projects/edge-router/plans/2026-08-26-https-upstream.md` **Status: code complete on branch `feat/https-upstream` (pushed); full suite 253/0, zero warnings. Awaiting merge-to-main decision.** **Tasks (TDD):** - [x] 1. Model fields + migration `011` (`target_scheme`, `upstream_ca_cert`) - [x] 2. Cached CA-verifying upstream client builder (`upstream.rs`, on `AppState`) - [x] 3. Wire HTTP proxy path (scheme-aware URL, cached client, WS-on-https guard) - [x] 4. Health checks honour scheme + CA - [x] 5. Config + API surface (TOML, REST, scheme validation) - [x] 6. Docs **Scope:** health checks in; wss-to-backend out (raw-TCP path → 501 for now); CA replaces roots (pinned), mirroring `broker.rs`; no skip-verify; fail-closed → 502. Independent of `BAO_CACERT` (that's ER-as-consumer trust; this is ER-as-proxy trust). **Live verification (DevInfra, after merge/publish) — two deployment preconditions:** - `target_host` must be `bao.dev.oates.ws` (devnet alias) and that name must be a SAN on OpenBao's actual cert (verify with `openssl s_client`). - The internal CA PEM must be mounted into ER's container at the `upstream_ca_cert` path (can be the same file as `BAO_CACERT`). **Note on #11:** this delivers capability **#3** only. Capability #2 (ER presenting a real `*.dev.oates.ws` cert to the browser) is assumed satisfied by ER's ACME/`[[tls]]`; #11 hints devinfra may use an externally-renewed lego cert, which ER cannot import — that path is unverified. Do not close #11 on this alone.
Author
Owner

Closing — the merge decision this was waiting on was taken: merged to main as c99cc76 on 2026-08-26. The body above still says "awaiting merge-to-main decision", which is stale.

Verified on main today: src/upstream.rs (cached CA-verifying client, apply_ca_pinning shared by the proxy path and the health probe) and migrations/011_service_https_upstream.sql (target_scheme, upstream_ca_cert). All six tasks above are ticked and match the code.

The remaining work is devinfra's, not EdgeRouter's — the live verification with its two preconditions (bao.dev.oates.ws present as a SAN on OpenBao's cert; the internal CA PEM mounted at the service's upstream_ca_cert path). Keeping an ER issue open to track a deployment step would leave it open indefinitely, so it belongs on devinfra if it needs tracking.

Per the note in the body, this is capability #3 of #11 only — #11 stays open, and capability #2 (ER presenting a real *.dev.oates.ws cert) remains unverified.

Closing — the merge decision this was waiting on was taken: merged to `main` as `c99cc76` on 2026-08-26. The body above still says "awaiting merge-to-main decision", which is stale. Verified on `main` today: `src/upstream.rs` (cached CA-verifying client, `apply_ca_pinning` shared by the proxy path and the health probe) and `migrations/011_service_https_upstream.sql` (`target_scheme`, `upstream_ca_cert`). All six tasks above are ticked and match the code. **The remaining work is devinfra's, not EdgeRouter's** — the live verification with its two preconditions (`bao.dev.oates.ws` present as a SAN on OpenBao's cert; the internal CA PEM mounted at the service's `upstream_ca_cert` path). Keeping an ER issue open to track a deployment step would leave it open indefinitely, so it belongs on devinfra if it needs tracking. Per the note in the body, this is capability #3 of #11 only — **#11 stays open**, and capability #2 (ER presenting a real `*.dev.oates.ws` cert) remains unverified.
Sign in to join this conversation.
No labels
No milestone
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set

Reference
IsoHex/edge-router#28
No description provided.