2026-08-22 20:40:25 +02:00
|
|
|
# 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.<domain>` 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://<vm-ip>:9090` access as a feature.
|
|
|
|
|
|
2026-08-25 09:30:11 +02:00
|
|
|
### What was actually done (2026-08-25)
|
|
|
|
|
|
|
|
|
|
**None of the three.** All of them are workarounds for a console that cannot take an OIDC
|
|
|
|
|
login, and the requirement had grown: registered people managing containers *across the hosts
|
|
|
|
|
in the zone*, which Cockpit's deprecated host switcher cannot do securely either.
|
|
|
|
|
|
|
|
|
|
So Cockpit was dropped and **Portainer CE** became this module's GUI, with Podman still the
|
|
|
|
|
engine underneath it. Portainer *is* an OIDC client, so `identity:identity` — the native TAPPaaS
|
|
|
|
|
OIDC path — works exactly as designed, and multi-host is an agent per host rather than a
|
|
|
|
|
browser loading remote JavaScript. The analysis above is kept because it is the reason for
|
|
|
|
|
the change, and because anyone proposing "just put Cockpit behind forward-auth" should read it
|
|
|
|
|
first.
|
2026-08-22 20:40:25 +02:00
|
|
|
|
|
|
|
|
### 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-<env>.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.
|