Skip to main content

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

ServiceWhat it asksDetail
VPN (NetBird)edu-ID via Keycloak; admission is membership of students / staffIdentity
What the VPN then reachesthe per-resource groups from the roster: calypso-users / carnaval-users / appliances-users / mgmt-usersAccess model
Admin consoles (Keycloak, status, …)the admin gate — oauth2-proxy requiring rack-adminsAdmin gate
Vaultwardenedu-ID via Keycloak (SSO_ONLY), plus the master password it can never seeVaultwarden
Proxmox (rumba, carnaval)edu-ID via Keycloak, pre-created users only; root@pam untouchedSSO on the node
SSH to a node or lab VMan SSH key from conf/authorized_keys/not the SSO chainCarnaval labs
The student homes over NFSnothing but a numeric UIDsec=sys means the client asserts itUIDs

The last two rows are the ones that surprise people: neither goes anywhere near edu-ID.

NFS trusts the client, so a node's root is everyone

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

  1. Add a line to provisioning/keycloak/roster.csvemail,groups[,nasname]:
    • the email must be the address edu-ID releases for the person (usually the @hevs.ch / @students.hevs.ch one). 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-admins on 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.
  2. Run the sync (over the VPN; --dry-run first shows the plan):
    scp provisioning/keycloak/roster.csv root@srv-keycloak:/root/roster.csv
    ssh root@srv-keycloak 'bash -s -- --dry-run' < provisioning/keycloak/roster-sync.sh
    ssh root@srv-keycloak 'bash -s' < provisioning/keycloak/roster-sync.sh
  3. 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.

Introducing a NEW group name is more than a roster edit

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

  1. 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.
  2. 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).
  3. Two deliberate exceptions:
    • rack-admins is 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

RecordFileAuthoritative for
Who is authorized, and as whatprovisioning/keycloak/roster.csvrole and resource groups
Which number is whoseprovisioning/uid/uid-map.csvUIDs and GIDs
SSH keysprovisioning/ansible/conf/authorized_keys/shell access to nodes
What each group may reachprovisioning/netbird/config-snapshot.jsonVPN 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/.