User authentication
How a person proves who they are on ISC³, and how that turns into access. There are two independent identity systems here, and most confusion comes from mistaking one for the other:
- The SSO chain — edu-ID → Keycloak → each service. This decides who you are and which groups you carry.
- The numeric UID — the number NFS compares against the ownership of a home directory. It decides which files you can touch, and it knows nothing about logins.
They meet in exactly one place: roster.csv names a person, and the UID register gives
that person a number. Nowhere else do the two systems talk.
Looking for where to go and what it will ask you for? That is the service access paths index. This page is the mechanism underneath.
The two chains
The dotted edges are the only couplings, and both run through the roster. A perfect SSO login grants no file access, and a correct UID lets nobody log in.
What authenticates each service
| Service | What it asks | Detail |
|---|---|---|
| VPN (NetBird) | edu-ID via Keycloak; admission is membership of students / staff | Identity |
| What the VPN then reaches | the per-resource groups from the roster: calypso-users / carnaval-users / appliances-users / mgmt-users | Access model |
| Admin consoles (Keycloak, status, …) | the admin gate — oauth2-proxy requiring rack-admins | Admin gate |
| Vaultwarden | edu-ID via Keycloak (SSO_ONLY), plus the master password it can never see | Vaultwarden |
| Proxmox (rumba, carnaval) | edu-ID via Keycloak, pre-created users only; root@pam untouched | SSO on the node |
| SSH to a node or lab VM | an SSH key from conf/authorized_keys/ — not the SSO chain | Carnaval labs |
| The student homes over NFS | nothing but a numeric UID — sec=sys means the client asserts it | UIDs |
The last two rows are the ones that surprise people: neither goes anywhere near edu-ID.
The exports are sec=sys: the server authorises on the number the client claims, not on any account
it knows. Root on any machine in the node subnet can therefore become any student
(what sudo on a lab VM grants). That is
why the homes are 750 per person, why --sudo takes names, and why the numbers have to be issued
rather than rediscovered.
Enrolling and un-enrolling people
Everything is one file plus one script — no invite links, no passwords to hand out, no console clicking. Validated end to end 2026-08-06 (auto-link, groups, VPN gate, with real students).
Enroll
- Add a line to
provisioning/keycloak/roster.csv—email,groups[,nasname]:- the email must be the address edu-ID releases for the person (usually the
@hevs.ch/@students.hevs.chone). A different edu-ID primary address shows up as "logs in fine, no access" — put their real edu-ID address here. groups: exactly one identity group (students|staff— this is what admits to the VPN, nothing more) plus the entitlements they need (calypso-users,carnaval-users,appliances-users,mgmt-users),;-separated.rack-adminson top for admin-console access. The sync refuses a line with entitlements but no identity group — that person could never log in.- commit the diff: it is the authorization record.
- the email must be the address edu-ID releases for the person (usually the
- Run the sync (over the VPN;
--dry-runfirst shows the plan):scp provisioning/keycloak/roster.csv root@srv-keycloak:/root/roster.csvssh root@srv-keycloak 'bash -s -- --dry-run' < provisioning/keycloak/roster-sync.shssh root@srv-keycloak 'bash -s' < provisioning/keycloak/roster-sync.sh - Done — tell the person to sign in with edu-ID (for the VPN: Connecting). Their first login links silently to the pre-created account and fills names from edu-ID; nothing to type, nothing to approve.
VPN access is all a roster line grants. SSH to a node needs their key in
provisioning/ansible/conf/authorized_keys/, and file access needs a
UID register entry — the other chain, see the top of this page. And a new
Proxmox administrator needs one more deliberate act beyond rack-admins: an ADMINS= entry
in provisioning/pve/sso-realm.sh, re-run once per cluster — PVE admits
pre-created users only.
An identity name is wired into the VPN gate's vpn-access role
and NetBird's jwt_allow_groups: add it to ROLE_GROUPS in both roster-sync.sh and
vpn-access-gate.sh, re-run both, and add it to the NetBird account setting (API, owner PAT). A
new entitlement additionally needs a NetBird policy, and the jwt group only exists once a login
has carried the claim — provisioning/netbird/migrate-role-groups.py (2026-08-06) is a worked
example of the whole dance.
Un-enroll
- Remove the line (or just the groups being revoked), commit, re-run the sync. The next token carries no identity group, so new logins are refused with the explicit "not enrolled" page.
- The tail: an already-enrolled VPN device keeps working until its session expires
(
peer_login_expiration, 24 h). For an instant cut, block the user or delete the device in the NetBird dashboard (Team → Users / Peers). - Two deliberate exceptions:
rack-adminsis add-only — removing an admin is a console action in Keycloak, so a bad roster edit can never lock everyone out of the tool needed to fix it.- The Keycloak account itself is never deleted — it keeps the edu-ID link and the audit trail. Disable it in the console if the person must be locked out SSO-wide immediately.
A departure from HES-SO handles part of itself: the claim-derived groups (hes-so, …) vanish at
the next login (syncMode=FORCE), and edu-ID refuses people with no active affiliation upstream.
Where the records live
| Record | File | Authoritative for |
|---|---|---|
| Who is authorized, and as what | provisioning/keycloak/roster.csv | role and resource groups |
| Which number is whose | provisioning/uid/uid-map.csv | UIDs and GIDs |
| SSH keys | provisioning/ansible/conf/authorized_keys/ | shell access to nodes |
| What each group may reach | provisioning/netbird/config-snapshot.json | VPN policies |
All four are committed, so every grant is a reviewable diff rather than a click in a console. None of
them holds a secret; credentials live in docs/secretzone/.