diff --git a/README.md b/README.md index 08d0522..9d8d783 100644 --- a/README.md +++ b/README.md @@ -35,3 +35,5 @@ URL, and the clone is made over HTTPS. | Module | What it is | Status | | --- | --- | --- | | [podman](src/containers/podman) | Debian 13 VM with rootless Podman + the Cockpit web console | incomplete | + +Open work on `podman`: identity integration — see its [DESIGN.md](src/containers/podman/DESIGN.md#identity-integration--the-analysis). diff --git a/src/containers/podman/DESIGN.md b/src/containers/podman/DESIGN.md new file mode 100644 index 0000000..460b9ea --- /dev/null +++ b/src/containers/podman/DESIGN.md @@ -0,0 +1,95 @@ +# Podman — design notes + +Primary audience: whoever changes this module next. + +## Why Debian and not NixOS + +The default TAPPaaS module VM is NixOS. Podman ships in Debian 13 (trixie) main, as do +`cockpit` and `cockpit-podman`, so a Debian cloud image gets the whole module from apt with +no packaging work and no flake to maintain. The trade-off is that the VM's state lives in +`update.sh` rather than in a declarative `.nix` file — acceptable for a container *host*, +whose real content is the containers the operator runs on it. + +## Identity integration — the analysis + +The module currently authenticates against **Linux (PAM) accounts on the VM**: the +cloud-init `tappaas` user has no password until an operator sets one by hand. That is a +per-VM credential nobody manages, and it is the one part of this module that is not +platform-integrated. + +### What TAPPaaS offers + +The identity module (Authentik) provides two services, and +[USERS.md](https://tappaas.org/generated/foundation/identity/) is explicit that they must +never be stacked on one app — double login, no benefit: + +| Mode | `dependsOn` | Mechanism | In-app account | +|------|-------------|-----------|----------------| +| **OIDC** | `identity:identity` | the app is an OIDC client of Authentik; groups arrive in the `groups` claim | created just-in-time on first login | +| **Forward-auth** | `identity:accessControl` | Caddy asks the Authentik outpost, then injects `X-Authentik-*` headers | none — the URL is simply gated | + +`identity:identity` writes `OIDC_CLIENT_ID` / `OIDC_CLIENT_SECRET` / +`OIDC_DISCOVERY_URI` into the VM's secrets env file and restarts a configure service, so +the app can register the provider. `identity:accessControl` creates a Proxy Provider in +Authentik, attaches it to the embedded outpost, and flips the module's existing Caddy +handler to `ForwardAuth=1`. Both bind the application to an allow-list of groups at install +time — mandatory, because Authentik fails open. + +### What Cockpit can actually consume + +**Cockpit is not an OIDC client, and cannot become one by configuration.** Its maintainers +are direct about the alternatives ([cockpit#20814](https://github.com/cockpit-project/cockpit/discussions/20814)): + +- launching `cockpit-ws --local-session=cockpit-bridge` behind an authenticating proxy — + which means "there is _zero_ authentication on that port", so `:9090` must be made + unreachable by anything but the proxy; +- writing a custom authentication command in the shape of `cockpit-session` — "this is + _difficult_ -- this isn't configuration, this is real development". + +And in **every** approach, including the proxy one, a **local Unix account must still +exist** for the authenticated person: `cockpit-bridge` runs inside a PAM session as that +user. Cockpit does not abstract that away. + +So `identity:identity` is the wrong dependency for this module — the deck was optimistic. +There is no configuration that makes Cockpit take an OIDC login. + +### The three real options + +**1. `identity:accessControl` — gate the URL (recommended first step).** +Authentik guards `https://podman.` at Caddy and only the bound groups get through; +behind it Cockpit still shows its own PAM form. Two logins, but the exposure is centrally +managed and group-driven, and it is one line in `dependsOn` plus whatever `zone0` / +`proxyDomain` the environment resolves. No new machinery. + +**2. Authentik LDAP outpost + SSSD on the VM — one credential.** +Authentik can serve LDAP; with SSSD on the Debian VM the Linux accounts *are* the Authentik +accounts, so Cockpit's own form takes the same username and password as everything else, +and `podadmin`-style hand-made users disappear. This is the honest fix for the local-account +requirement. It is **not** something this module should carry: TAPPaaS's identity module runs +only the *embedded* outpost, so an LDAP outpost plus a posix-accounts service belongs +upstream in `identity` (a third service alongside `identity` and `accessControl`). + +**3. True single sign-on — real development.** +Option 1 or 2 plus a custom Cockpit auth command that trusts `X-Authentik-Username`, with +`:9090` firewalled to the proxy only. Highest value, highest risk: header-trust auth is a +full bypass if the port is reachable any other way, and this module currently *advertises* +direct `https://:9090` access as a feature. + +### Recommendation + +Take option 1 now, open an upstream issue for option 2, and treat option 3 as out of scope. +Note that the direct `:9090` URL stays open under option 1 — the gate is on the friendly +name only, which is honest but worth saying out loud in `README.md`. + +### One thing to verify on a live system first + +`identity:accessControl`'s install-service hard-requires `vmname`, `zone0`, `proxyDomain` +and `proxyPort` in the *installed* config and — unlike `identity:identity` — does **not** +derive `proxyDomain` when it is absent. This module deliberately sets neither `zone0` nor +`proxyDomain`, so that both come from the environment (ADR-007 P5). `copy-update-json.sh` +documents that `install-module.sh` "computes vmname/zone0/proxyDomain from the environment +file and passes them as explicit overrides", but no such computation is visible in +`install-module.sh` for `proxyDomain`. **Check the resolved +`/home/tappaas/config/podman-.json` after an install**; if `proxyDomain` is empty, +accessControl will `die`, and the fix is upstream — mirror the derivation that +`identity/install-service.sh` already does.