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 successful SSO login grants no file access, and a correct UID lets nobody log in.
What authenticates each service
Every edu-ID login first passes the realm's admission gate
(realm-wide since 2026-09-06): a NetBird login needs students / staff, any other client needs
isc or one of those two roster groups. Someone who fails it is refused on the Keycloak page with a
sentence, before the service is ever reached. The table below is what each service checks after
that.
| 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: vpn-carnaval / vpn-rack-operators / vpn-rack-mgmt | Access model |
| Admin consoles (Keycloak, status, …) | the admin gate — oauth2-proxy requiring role-rack-admins | Admin gate |
| Published user tools (Stirling-PDF) | the user gate — oauth2-proxy requiring isc, students, staff or guests; hevs no longer admits (2026-09-06) | User gate |
| 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.
vpn-rack-operators and vpn-rack-mgmt grant reach: they decide which subnets your packets may
enter over the VPN, nothing more. role-rack-admins and role-pve-auditor grant rights: what you may
do on the admin applications (the admin gate needs no
VPN at all, and the PVE groups role-rack-admins-isc / role-pve-auditor-isc are Administrator and
read-only on the clusters). The two kinds compose: reach without rights gets you to a login page
and an empty UI; rights without reach cannot even load the page — except through the gate, which is
reachable from anywhere by design. Management differs within the rights kind: role-pve-auditor is a
two-way roster entitlement like the reach groups, while role-rack-admins is add-only, revoked only in
the console.
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
provisioning/uid/onboard.sh <nasname> [key.pub] --commit chains steps 2–3 below (plus the
lab-VM key file, keycloak/ssh-keys) after the roster edit; without --commit it shows the
plan. Every step skips what is already in place, so a rerun after a mid-way failure is safe.
The steps, for when one is needed alone:
-
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 (vpn-carnaval,vpn-rack-operators,vpn-rack-mgmt, androle-pve-auditorfor read-only Proxmox),;-separated.role-rack-adminson top for admin-console access. The sync refuses a line with entitlements but no identity group — that person could never log in.nasname(optional): the person's home directory on the NAS, only when it differs from the email's local part — it is the email → home mapping UID seeding reads.- 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 -
Give them a number and a home, if they are to use the student homes on the NAS — that is the other chain, and the roster line does nothing for it:
provisioning/uid/uid-alloc.py # the planprovisioning/uid/uid-alloc.py --commit # write itprovisioning/uid/create-home.sh <nasname> --commit # only if they have no home yetprovisioning/uid/uid-alloc.py --verify # register against the NASIn that order:
create-home.shrefuses a register row with no email, and the email is what--commitwrites — a number nobody on the roster holds has no home to create.The allocator reads the roster itself. Someone whose home already exists claims that row and keeps their number, which is permanent — including a staff member sitting on a student-band number, a normal outcome that is not to be renumbered. Everyone else gets the next free number in their band. Commit the register diff.
create-home.shworks throughcalypsomaster, the only host the NAS exports to without root squashing, so it needs that host in yourknown_hostsand your key in the agent;--verifytalks to the NAS directly and needs the same. -
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 a new
Proxmox administrator needs one more deliberate act beyond role-rack-admins: an ADMINS= entry
in provisioning/pve/sso-realm.sh, re-run once per cluster — PVE admits
pre-created users only. Read-only Proxmox is the same shape,
role-pve-auditor plus an AUDITORS= entry. A NetBird administrator is a third act:
role-rack-admins opens the dashboard's admin gate, but the
NetBird account role is not derived from groups and every federated user lands as user, seeing
only their own peers. After their first login, provisioning/netbird/set-user-role.sh <email> admin
(owner PAT; or Team → Users → role). Found this way 2026-09-07, six days after an enrolment.
An identity name is wired into the admission gate's
vpn-access and isc-access roles and into NetBird's jwt_allow_groups: add it to ROLE_GROUPS
in roster-sync.sh and to both *_ROLE_GROUPS lists in 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 sequence. A rights-only entitlement such as role-pve-auditor needs no policy:
its grant lives on the service (sso-realm.sh), and NetBird still materialises an unused jwt group
of that name at the first login carrying it.
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 VPN logins are refused with the explicit
"not enrolled" page. An ISC member keeps the published
tools through
isc; a HEI colleague who was rostered as an exception loses them too. - The tail: an already-enrolled VPN device keeps working until its session expires
(
peer_login_expiration, 48 h). For an instant cut, block the user or delete the device in the NetBird dashboard (Team → Users / Peers). - Two deliberate exceptions:
role-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. A former Proxmox admin needs two more acts, mirroring how they were added: drop them fromADMINS=insso-realm.sh, andpveum user delete <email>@iscon each cluster — until their next PVE login the stored group membership still grants, and an existing account without privileges is one ACL slip from access. Exercised end to end 2026-08-06.- 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 the teaching nodes and lab VMs |
| Root SSH keys on the PVE nodes | provisioning/pve/root-ssh-keys.sh | shell access to the hypervisors |
| What each group may reach | provisioning/netbird/config-snapshot.json | VPN policies |
All five 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 secretzone/.