Identity and authorization: tickets, CSRF, the ACL walk over user.cfg
Find a file
Repository files (latest commit first)
Filename Latest commit message Latest commit date
2026-09-14 09:00:00 +02:00
debian Initial import 2026-09-14 09:00:00 +02:00
src/pmx_cork_access Initial import 2026-09-14 09:00:00 +02:00
tests Initial import 2026-09-14 09:00:00 +02:00
.gitignore Initial import 2026-09-14 09:00:00 +02:00
LICENSE Initial import 2026-09-14 09:00:00 +02:00
pyproject.toml Initial import 2026-09-14 09:00:00 +02:00
README.md Initial import 2026-09-14 09:00:00 +02:00

pmx-cork-access

The auth provider of the pmx-cork suite: the backend behind the replaceable auth seam, and the libpve-access-control analog. It reimplements the Proxmox identity and authorization primitives and presents them as one provider object.

Module Mirrors
auth PVE::AccessControl: the PVEAuthCookie ticket (RSA), CSRF (HMAC-SHA256), API tokens (verified against pmxcfs over the IPC transport of pmx-cork-pmxcfs)
userid PVE::AccessControl and PVE::Auth::Plugin: the user@realm and user@realm!token grammar
permissions PVE::RPCEnvironment: the ACL walk over /etc/pve/user.cfg
cluster the peer's certificate, read from /etc/pve/nodes/<node>/ (the address to dial comes from pmx-cork-pmxcfs's members)
keycache content-keyed caching for the key files every request reads
provider PROVIDER, the object [auth] provider resolves to

Cross-node TLS trust

PVE pins a peer's certificate by the SHA-256 of the leaf it serves, and this provider does the same thing with the same input: the fingerprint is taken from /etc/pve/nodes/<node>/pveproxy-ssl.pem, or pve-ssl.pem when the node carries no custom certificate, and handed to aiohttp as a Fingerprint.

It is a pin, not chain validation and not a hostname check. That is not a shortcut but the model itself: PVE::APIServer::AnyEvent returns 1 from its verify callback for every depth above the leaf and defers the whole decision to PVE::CertCache::check_cert_fingerprint.

Reproducing it matters for one supported configuration in particular. A node with its own pveproxy-ssl.pem, from Let's Encrypt for instance, has a certificate that does not chain to the cluster CA at all. Verifying a chain instead would refuse exactly the peers PVE accepts.

An unreadable certificate means no fingerprint, and the hop fails closed with a 502 rather than dialling unverified. Reads are cached by content, so a renewed certificate takes effect on the next request without a watcher or a restart.

The provider object

PROVIDER is what [auth] provider names, and the runtime's load_auth_backend() is the one place that turns the name into an object. The object provides the auth methods (authenticate, is_enabled, check_privileges, check_api_permissions, token_owner), peer resolution (remote_node_ip, peer_ssl, cert_hostname) and credential_headers.

The surface is duck-typed. The resolved object is injected, as app.state.AUTH and through task_api.set_auth, so no other layer imports an auth package or looks up which backend is configured. Resolution fails loudly at boot.

Replacing the backend

[auth]
provider = "pmx_cork_access:PROVIDER"   # the default

To run another identity and RBAC backend, ship a package that exports an object with the same methods and point [auth] provider at it. A missing package or an object without the methods the first request needs refuses service. The permission vocabulary stays PVE-shaped (ACL paths, the route permissions= spec); a provider replaces the mechanism, not the spec plugins are written against.

Ships as python3-pmx-cork-access. Depends on pmx-cork-tasks, pmx-cork-pmxcfs and python3-cryptography.