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 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.

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: vpn-carnaval / vpn-rack-operators / vpn-rack-mgmtAccess model
Admin consoles (Keycloak, status, …)the admin gate — oauth2-proxy requiring role-rack-adminsAdmin 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 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.

Reach vs rights — the groups are not all the same kind

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.

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

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:

  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 (vpn-carnaval, vpn-rack-operators, vpn-rack-mgmt, and role-pve-auditor for read-only Proxmox), ;-separated. role-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.
    • 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.
  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. 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 plan
    provisioning/uid/uid-alloc.py --commit # write it
    provisioning/uid/create-home.sh <nasname> --commit # only if they have no home yet
    provisioning/uid/uid-alloc.py --verify # register against the NAS

    In that order: create-home.sh refuses a register row with no email, and the email is what --commit writes — 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.sh works through calypsomaster, the only host the NAS exports to without root squashing, so it needs that host in your known_hosts and your key in the agent; --verify talks to the NAS directly and needs the same.

  4. 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.

Introducing a new group name is more than a roster edit

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

  1. 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.
  2. 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).
  3. Two deliberate exceptions:
    • role-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. A former Proxmox admin needs two more acts, mirroring how they were added: drop them from ADMINS= in sso-realm.sh, and pveum user delete <email>@isc on 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

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 the teaching nodes and lab VMs
Root SSH keys on the PVE nodesprovisioning/pve/root-ssh-keys.shshell access to the hypervisors
What each group may reachprovisioning/netbird/config-snapshot.jsonVPN 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/.