You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
The Sharing card on a container (owner + collaborators) only controls who sees the container in the manager. It has no effect on SSH.
Today any directory user who matches SSSD_LDAP_ACCESS_FILTER (default (objectClass=*)) and has an LDAP sshPublicKey can SSH into every container — shared or not. Sharing and SSH access should be the same thing: not shared ⇒ only the owner can log in; shared ⇒ owner + collaborators.
Why not fix it in LDAP?
Every container binds to the same authentik LDAP outpost as ldap-proxyuser with identical queries. The directory cannot tell which container is asking, so it cannot answer per-container.
A per-container ldap_access_filter (e.g. (memberOf=cn=ct-<id>,…)) still needs a per-container value rendered into sssd.conf, which only happens at boot, adds a second source of truth (authentik groups vs the manager DB), and inherits SSSD access-cache latency.
Approach: evaluate at connect time
sshd already runs a program per login (AuthorizedKeysCommand). Use it, plus a PAM account hook for the password/MFA path, to ask the manager whether <user> is allowed on <this container>.
sequenceDiagram
participant U as ssh client
participant S as sshd (container)
participant M as Manager API
participant L as SSSD / LDAP
U->>S: connect as alice
S->>S: AuthorizedKeysCommand ssh-access-authorized-keys alice
S->>M: GET /api/v1/containers/:id/ssh-access/alice (Bearer container token)
M-->>S: 204 (owner or collaborator) / 403
alt allowed
S->>L: sss_ssh_authorizedkeys alice
L-->>S: keys
else denied
S-->>U: no keys → permission denied
end
Loading
The manager DB (Container.username + ContainerCollaborator) stays the single source of truth.
Share/unshare takes effect on the next connection — no timers, versions, sshd reloads.
Authentication is unchanged: keys still come from the user's LDAP sshPublicKey.
Manager (create-a-container)
Migration: Containers.sshAccessTokenHash (argon2, nullable; null = not enrolled).
middlewares/containerToken.js: Bearer token → argon2.verify against the container's hash.
POST /api/v1/containers/:id/ssh-access/token (owner/admin) → mint/rotate, plaintext returned once.
bin/create-container.js mints a token and injects CONTAINER_SSH_TOKEN, MANAGER_URL, CONTAINER_ID as reserved env keys (not user-editable, hidden from the serialized env).
ssh-access-check <user>: reads /etc/ssh-access/{token,url,id} (root, 0600), validates the username, calls the endpoint. Manager unreachable ⇒ honour /var/cache/ssh-access/<user> (touched on every allow, 24 h TTL), otherwise deny. No token file ⇒ allow with a logged warning (unenrolled containers behave as today).
AuthorizedKeysCommand /usr/local/bin/ssh-access-authorized-keys %u (check, then exec sss_ssh_authorizedkeys), AuthorizedKeysCommandUser root.
Boot oneshot ssh-access-setup.service writes /etc/ssh-access/* from PID 1's environment; environment.sh filters the token out of /etc/environment.
Existing containers (follow-up PR)
bin/enroll-ssh-access.sh, run on each Proxmox node with an admin API key: for each pct list vmid → look up the container → mint a token → pct push the files/hooks → pct exec sshd -t && systemctl reload ssh. Idempotent, --dry-run, no container restart.
Verification
Jest: token middleware; endpoint (owner/collaborator 204, stranger 403, bad username 400); mint permissions; serializer flag; reserved env keys not overridable.
Shell test for ssh-access-check with a mocked curl (204 / 403 / timeout ± cache).
Disposable container: owner OK; collaborator OK; stranger with a valid LDAP key denied on both key and password+MFA paths; unshare ⇒ denied on next attempt; manager down ⇒ owner OK via cache, stranger denied.
Problem
The Sharing card on a container (owner + collaborators) only controls who sees the container in the manager. It has no effect on SSH.
Today any directory user who matches
SSSD_LDAP_ACCESS_FILTER(default(objectClass=*)) and has an LDAPsshPublicKeycan SSH into every container — shared or not. Sharing and SSH access should be the same thing: not shared ⇒ only the owner can log in; shared ⇒ owner + collaborators.Why not fix it in LDAP?
ldap-proxyuserwith identical queries. The directory cannot tell which container is asking, so it cannot answer per-container.ldap_access_filter(e.g.(memberOf=cn=ct-<id>,…)) still needs a per-container value rendered intosssd.conf, which only happens at boot, adds a second source of truth (authentik groups vs the manager DB), and inherits SSSD access-cache latency.Approach: evaluate at connect time
sshd already runs a program per login (
AuthorizedKeysCommand). Use it, plus a PAMaccounthook for the password/MFA path, to ask the manager whether<user>is allowed on<this container>.sequenceDiagram participant U as ssh client participant S as sshd (container) participant M as Manager API participant L as SSSD / LDAP U->>S: connect as alice S->>S: AuthorizedKeysCommand ssh-access-authorized-keys alice S->>M: GET /api/v1/containers/:id/ssh-access/alice (Bearer container token) M-->>S: 204 (owner or collaborator) / 403 alt allowed S->>L: sss_ssh_authorizedkeys alice L-->>S: keys else denied S-->>U: no keys → permission denied endContainer.username+ContainerCollaborator) stays the single source of truth.sshdreloads.sshPublicKey.Manager (
create-a-container)Containers.sshAccessTokenHash(argon2, nullable;null= not enrolled).middlewares/containerToken.js: Bearer token →argon2.verifyagainst the container's hash.GET /api/v1/containers/:id/ssh-access/:username→204owner/collaborator,403otherwise,400invalid username.Cache-Control: no-store; denials logged.POST /api/v1/containers/:id/ssh-access/token(owner/admin) → mint/rotate, plaintext returned once.bin/create-container.jsmints a token and injectsCONTAINER_SSH_TOKEN,MANAGER_URL,CONTAINER_IDas reserved env keys (not user-editable, hidden from the serialized env).sshAccess.enforced; Sharing card copy updated + "Not enforced" badge for unenrolled containers.Base image (
images/base)ssh-access-check <user>: reads/etc/ssh-access/{token,url,id}(root, 0600), validates the username, calls the endpoint. Manager unreachable ⇒ honour/var/cache/ssh-access/<user>(touched on every allow, 24 h TTL), otherwise deny. No token file ⇒ allow with a logged warning (unenrolled containers behave as today).AuthorizedKeysCommand /usr/local/bin/ssh-access-authorized-keys %u(check, thenexec sss_ssh_authorizedkeys),AuthorizedKeysCommandUser root./etc/pam.d/sshd:account required pam_exec.so quiet /usr/local/bin/ssh-access-check-pam.ssh-access-setup.servicewrites/etc/ssh-access/*from PID 1's environment;environment.shfilters the token out of/etc/environment.Existing containers (follow-up PR)
bin/enroll-ssh-access.sh, run on each Proxmox node with an admin API key: for eachpct listvmid → look up the container → mint a token →pct pushthe files/hooks →pct exec sshd -t && systemctl reload ssh. Idempotent,--dry-run, no container restart.Verification
ssh-access-checkwith a mockedcurl(204 / 403 / timeout ± cache).Out of scope
ldapuserspasswordless sudo — sharing remains admin-level trust inside the container.