Skip to content

Waldur-authoritative accounts in OpenLDAP

Where GLAuth renders a directory from Waldur on every refresh, an existing OpenLDAP tree is a stateful directory you write into and reconcile against. The Waldur site agent does that: it takes the username, UID, primary GID, home directory and login shell Waldur already holds and writes them into the directory as ordinary posixAccount entries, which a Linux host then consumes with SSSD.

Use this page when a provider already runs OpenLDAP — often shared by several of its services — and wants Waldur to be the source of truth for who exists in it.

Which direction the identity flows

This is the decision that matters, and the agent supports both.

account_source Who decides the username and UID Use when
ldap (default) The plugin: it derives a name from the user's first and last name and scans the directory for a free UID The directory is authoritative and Waldur is one of several consumers
waldur Waldur: the agent writes the values the POSIX ID pool allocated Waldur is the source of truth, and especially when one directory serves several of the provider's offerings

The second mode exists because of a specific failure. A provider's POSIX ID pool already guarantees one UID and one primary GID per user across all of its offerings. If the plugin allocates locally instead, two offerings pointing at the same directory hand the same person two different UIDs behind one DN and one home directory — and whichever agent writes last wins.

flowchart LR
  subgraph Waldur
    P["POSIX ID pool<br/>(service provider scope)"]
    O1["Offering A<br/>offering user"]
    O2["Offering B<br/>offering user"]
    P --> O1
    P --> O2
  end
  O1 --> AG["Site agent<br/>account_source: waldur"]
  O2 --> AG
  AG --> L[("OpenLDAP<br/>one posixAccount per user")]
  L -->|LDAP| S["SSSD on the login node"]
  S --> N["getent / id / PAM"]

Under account_source: waldur the second offering finds the entry the first one wrote and leaves it alone, so both converge on a single account.

Configuring the agent

Step by step

For an ordered checklist with screenshots — creating the pool, setting the account scope and username policy on the provider and its offerings, configuring the agent and checking the first accounts — see Shared POSIX identity, step by step.

account_source lives in the LDAP plugin's settings, alongside the connection details:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
offerings:
  - name: "HPC cluster"
    waldur_api_url: "https://waldur.example.com/api/"
    waldur_api_token: "<token>"
    waldur_offering_uuid: "<offering-uuid>"
    username_management_backend: "ldap"
    backend_settings:
      ldap:
        uri: "ldap://ldap.example.com"
        bind_dn: "cn=admin,dc=example,dc=com"
        bind_password: "<password>"
        base_dn: "dc=example,dc=com"
        people_ou: "ou=People"
        groups_ou: "ou=Groups"

        account_source: "waldur"
        on_missing_posix_ids: "error"
        on_posix_mismatch: "report"
        waldur_username_attribute: "employeeNumber"   # rename key, see "Renames"

        # Still used for project groups, which the resource backend allocates.
        gid_range_start: 1200
        gid_range_end: 1400

username_format is rejected in this mode — Waldur names the accounts, so leaving the setting in place would only mislead. The uid_range_* settings are ignored with a warning for the same reason.

Keep gid_range_* clear of the POSIX ID pool

User UIDs and primary GIDs come from Waldur, but project group GIDs are still allocated inside the directory from gid_range_*. OpenLDAP does not enforce gidNumber uniqueness, so if that range overlaps the provider's POSIX ID pool, a project group can silently take a GID already issued as somebody's primary group — and files end up ambiguously owned. Keep the two ranges disjoint. The agent cannot check this for you, but it logs the configured range at start-up so the overlap is at least visible.

These are the resource backend's own groups. Groups kept per project, with GIDs Waldur allocates, are a separate feature: see Project groups in the directory.

Naming the accounts

Under account_source: waldur the login name is whatever Waldur minted for the offering user, so the Username generation policy decides what the directory ends up holding. Set it once on the service provider's Account settings page (Accounts → Account settings); every offering inherits it unless it sets its own. Any policy works except service_provider — under that one Waldur waits for the agent to submit a name, which is the opposite of this mode.

Policy Login name Fits a shared directory?
anonymized <prefix><POSIX UID>, for example hpc_9001 Yes — see below
waldur_username The user's Waldur username Yes, if Waldur usernames are directory-safe
identity_claim A username claim from the identity provider Yes, if the claim is present for every user
full_name jane_smith_01 — a per-offering counter Only for a single offering
freeipa The user's FreeIPA profile name Yes
service_provider Assigned by the agent Rejected in this mode

Anonymized names are derived from the pool

An anonymized name is a pure function of the account's POSIX UID: the prefix followed by the number the POSIX ID pool allocated. Because the pool is provider-wide, the same person gets the same name on every offering of the provider that draws from that pool — two site agents writing the same directory agree on uid=hpc_9001 without ever talking to each other, and a second offering's reconcile becomes a no-op on an entry the first one created.

The prefix is an account setting resolved the same way a pool is — the offering's own value first, otherwise the provider's, otherwise the default waldur_:

Where Field Scope
Service provider → Accounts → Account settings Anonymized username prefix (account_options.username_anonymized_prefix) Every offering of the provider that does not override it
Offering → Edit → Integration → User management → Accounts Username anonymized prefix (username_anonymized_prefix) This offering only

When all offerings share a directory, set it once on the provider and leave the offering field unset; the offering form then shows the value it inherits.

If no UID resolves for a user — no pool is attached, or POSIX accounts are disabled on the offering — Waldur falls back to a per-offering counter (hpc_00001) and logs the gap. Such a name is not stable across offerings, so attach the pool before users are created rather than after.

One person, one entry

A directory shared by several offerings also needs Waldur to hold one account per person per provider rather than one per offering. On the service provider's Account settings page, set Account scope to Per service provider (account_scope: provider; an individual offering can override it back to offering when it runs its own separate directory). Switching a provider that already has offering users links their existing accounts, and is refused while one person holds different usernames on different offerings. With provider-scoped accounts the UID, primary GID, home directory and — under anonymized — the username are held once and read through by every offering user, so the directory entry has exactly one owner in Waldur.

getent on a node then shows the pool identity under the derived name:

1
2
$ getent passwd hpc_9001
hpc_9001:*:9001:9001:Jane Smith:/home/hpc_9001:/bin/bash

Renames

A Linux username can change while the person stays the same — the username generation policy changes, say, or the identity provider sends another name. Waldur decides the new name; the agent has to recognise which directory entry it belongs to. It does that with a stable key stored on the entry: the person's Waldur username, in the attribute named by waldur_username_attribute. employeeNumber is the recommended attribute.

1
2
3
4
backend_settings:
  ldap:
    account_source: "waldur"
    waldur_username_attribute: "employeeNumber"
  • Stamping. The agent writes the key when it creates an entry, and on an existing entry whose uid and uidNumber already match Waldur. Entries that exist when you turn the setting on are therefore stamped on the first cycle.
  • Renaming. When Waldur reports a new Linux username, the agent looks up the entry carrying that person's Waldur username and, if its uidNumber matches, renames it in place (modrdn uid=<old> to uid=<new>). uidNumber stays; the home directory and profile follow Waldur, the personal group is renamed with it, access-group memberships are rewritten, and project groups follow from Waldur's member lists.
  • A changed Waldur username. If the Waldur username changes while the Linux name and uidNumber stay the same, the agent rewrites the stored key, but only on the entry whose uid and uidNumber both match the account, and logs the old and new value. It never overwrites a key that belongs to another account.
  • Group memberships during a rename. While it moves the renamed user's group memberships, the agent may leave a temporary description value waldur-site-agent:renamed-from=<old> on the entry, so that an interrupted rename is finished on the next cycle. The agent removes the value itself once every membership has moved; do not edit or remove it by hand.

The agent refuses, logs the reason and leaves the entry alone when two entries carry the same key, when the entry carrying the key has another uidNumber, or when the wanted UID is held by an entry whose key is missing or different. It also refuses when the Linux username and the Waldur username change at once: neither the old key nor the old name then points at the entry. Rename such an entry by hand — set its uid to the new name and the key to the new Waldur username — and the next cycle picks it up.

Without waldur_username_attribute the agent never renames: a renamed account is reported as a UID collision until the entry is renamed by hand.

Renames also need the offering to expose Waldur usernames to the service provider: Username under User attribute exposure in the offering's integration settings, on by default. Without it the agent cannot see the key it compares, logs once that renames cannot be recognised, and treats a renamed account as a UID collision.

Reserve the attribute for the agent

The agent owns the attribute it stores the key in. Choose one that nothing else in your directory writes — if employeeNumber already holds HR data, pick another attribute your schema allows on the user entries — and do not edit its values by hand except to repair a refused rename.

The agent does not move files: a home directory path that embeds the old name needs moving by the site.

When an account cannot be written

Situation What the agent does
No POSIX ID pool resolves, or the offering has POSIX accounts disabled Governed by on_missing_posix_ids. The account is never given a locally-invented id — that is the behaviour this mode exists to remove
The entry exists but its uidNumber/gidNumber disagree with Waldur Governed by on_posix_mismatch
The UID Waldur wants is already held by an unrelated entry Always an error, whether the account would be created on that UID or renumbered onto it. No on_posix_mismatch setting overrides this

on_missing_posix_ids defaults to error: an account Waldur holds no ids for is logged with the remedy — attach a POSIX ID pool to the provider, or enable POSIX accounts on the offering — and skipped, while the rest of the cycle carries on. Set it to skip to log at debug instead, which is useful while rolling the mode out across offerings that are not all configured yet.

Two shapes of "no ids" are reported differently, because they mean different things. If every account comes back without them, the server never returned the fields at all — an older Waldur, or an agent not requesting them — and that is one error for the offering rather than one per user.

on_posix_mismatch defaults to report: it logs a before/after diff and changes nothing. That default is deliberate — rewriting a live account's uidNumber orphans every file that user owns. Set it to adopt for a one-shot migration once you are ready to follow up with chown -R, or fail to stop the cycle outright.

Project groups in the directory

Waldur keeps one project group per project at each service provider: a name, a GID from the provider's POSIX ID pool, and the usernames of the project's members who hold an account at the provider. With project_groups.enabled the agent writes those groups into the directory and lists them in the entries that grant access to a cluster, so a new project's members can use the cluster without anyone editing the directory.

Example layout

A classic RFC 2307 (nis schema) directory, in which posixGroup is structural and allows memberUid only:

1
2
3
4
5
dc=example,dc=org
  ou=users       uid=<name>     inetOrgPerson + posixAccount, gidNumber = primary GID
  ou=projects    cn=<group>     top + posixGroup, gidNumber = Waldur's GID, memberUid = members
  ou=clusters    cn=<cluster>   groupOfNames, member = DN of each project group in use on the offering
  ou=bind_users  service binds

For example, with the project groups shown on the Project groups page:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
dn: cn=genomics,ou=projects,dc=example,dc=org
objectClass: top
objectClass: posixGroup
cn: genomics
gidNumber: 20001
memberUid: demo-member
memberUid: demo-user1
description: organization=big-science

dn: cn=hpc-cluster,ou=clusters,dc=example,dc=org
objectClass: groupOfNames
cn: hpc-cluster
member: cn=genomics,ou=projects,dc=example,dc=org
member: cn=climate-models,ou=projects,dc=example,dc=org
member: cn=physics,ou=projects,dc=example,dc=org

Agent settings

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
backend_settings:
  ldap:
    uri: "ldaps://ldap.example.org"
    bind_dn: "cn=waldur-agent,ou=bind_users,dc=example,dc=org"
    bind_password: "<password>"
    base_dn: "dc=example,dc=org"
    people_ou: "ou=users"
    account_source: "waldur"
    waldur_username_attribute: "employeeNumber"   # key for renames, see "Renames"
    personal_groups: false          # primary GIDs come from Waldur, no personal group entries
    project_groups:
      enabled: true
      ou: "ou=projects"
      member_attribute: "memberUid"   # or "member" for rfc2307bis directories
      membership: "sync"              # or "add_only"
      on_gid_mismatch: "report"       # or "adopt"
      organization_description: "organization={slug}"   # optional
      parents:
        - dn: "cn=hpc-cluster,ou=clusters,dc=example,dc=org"
          attribute: "member"
Setting Default Meaning
waldur_username_attribute — Attribute holding each person's Waldur username, the key renames are recognised by; recommended employeeNumber, reserved for the agent. Unset: no renames (see Renames)
personal_groups true true gives every account a group cn=<username> in groups_ou with its primary GID. false writes accounts with the primary GID from Waldur and no group entry; it requires account_source: waldur
project_groups.enabled false Write the project groups
project_groups.ou ou=projects OU, relative to base_dn, the groups are written under; it must exist
project_groups.object_classes ["top", "posixGroup"] Object classes of a new group entry
project_groups.member_attribute memberUid memberUid writes usernames; member writes user DNs (uid=<name>,<people_ou>,<base_dn>)
project_groups.membership sync sync adds and removes members to match Waldur; add_only never removes one
project_groups.on_gid_mismatch report A same-named entry with another GID: report keeps the directory's GID and logs it every cycle; adopt rewrites it to Waldur's
project_groups.organization_description unset Template of a description value naming the project's organization; it must contain {slug} once, e.g. organization={slug}. Unset, nothing is written (see The organization in the description)
project_groups.parents [] Entries that list the DN of each group whose project has a resource on the offering
parents[].dn — Full DN of the entry
parents[].attribute member Attribute that holds the group DNs
parents[].offering_uuids the agent's offering Offerings whose projects the entry lists, for an entry shared by several offerings

The project-group pass runs on the agent's periodic reconcile, after the accounts, and also when the offering has no accounts left, so cluster entries are still cleaned up.

With event processing (STOMP) enabled, the offering also subscribes to Waldur's project-group events — a group numbered, renumbered or deleted, or project groups switched on or off for the provider — and the same pass runs a few seconds after one arrives, so a new project's group reaches the directory without waiting for the periodic reconcile. Events arriving close together, such as an import or the backfill when project groups are switched on, share one pass. The pass always covers every group, since which groups a cluster entry lists is decided across all of them, and it never runs at the same time as the periodic one. An agent waits for its first periodic reconcile before acting on events.

What the agent writes

For each group Waldur lists:

  • No entry yet — the agent creates it with Waldur's name, GID and members, unless the GID is already used anywhere under base_dn (users' primary GIDs included) or a posixGroup of the same name exists outside the project OU. Such a group is reported every cycle and not written; fix it in Waldur with Change GID or by adopting the directory's GID.
  • An entry with the same name and GID — the agent adopts it and reconciles the members and cluster entries.
  • An entry with the same name and another GID — under report the GID is left alone and logged every cycle, while members and cluster entries are still reconciled. Under adopt the agent rewrites gidNumber to Waldur's.
  • No GID in Waldur yet — the group is skipped and logged.

Use on_gid_mismatch: adopt only after chgrp

Renumbering a group orphans every file owned by its old GID. Switch to adopt only once those files have been chgrp-ed to Waldur's GID, which is why report is the default.

Group entries are never deleted: their GIDs stay reserved in Waldur. A user is named in a group only once their account entry exists under people_ou.

Cluster entries

The agent makes each cluster entry in parents list the DNs of the groups whose project has a resource on the entry's offerings. It only ever removes a DN that lies under the project OU and belongs to a group Waldur lists, and then only when its project no longer has a resource on those offerings, or the group could not be written.

A group under the project OU that Waldur does not list is never removed, nor is any DN outside the project OU. That covers a hand-made group, and also a group Waldur stops listing because its offering moved to another provider or the provider was deleted — take such a DN out of the cluster entry by hand. Waldur never renames or deletes a project group otherwise. The directory is left untouched for the cycle when Waldur cannot be read or returns no groups at all.

One cluster entry, several offerings

Two offerings of one provider on one directory see the same groups with the same GIDs: the first agent creates a group and the second adopts it. If they share a cluster entry, list both offerings in parents[].offering_uuids on both; otherwise each removes the groups the other adds. The agent warns at start-up when two offerings in its configuration point at one entry without offering_uuids.

Groups written by earlier agents

Earlier agents added a waldur-managed value to the description of every group they created or adopted. Current agents neither read nor write it, and leave existing values in place; remove them by hand if you like.

The organization in the description

With organization_description set, every group also gets a description value naming the organization its project belongs to: the template with {slug} replaced by the organization's slug. organization={slug} gives organization=big-science in the example above. The value is written on new groups, added to adopted ones next to your own values, and checked on every pass:

  • Text around {slug} — when an organization's slug changes, the agent replaces its previous value, recognised by that text, with the new one. Your own description values stay as long as they do not have the same shape.
  • A bare {slug} — the agent cannot tell its own values from yours, so it only ever adds the current one and never removes an old one. It warns about this at start-up; prefer a template with a prefix.

A group whose project has been deleted keeps the value it has.

Members and renames

With membership: sync the members follow Waldur: a user who leaves the project, or whose account is removed or restricted, is taken out. When Waldur renames an account and the entry carries the person's key, the agent renames the entry in place and the groups pick up the new username (see Renames). With membership: add_only the old username stays in the group next to the new one, since that mode never removes members.

Connecting SSSD

The agent writes standard posixAccount entries keyed on uid, and personal groups as posixGroup. Unlike the GLAuth setup, no attribute-mapping overrides are needed — GLAuth serves users as cn and groups as ou, which forces ldap_user_name/ldap_group_name overrides; here the stock RFC 2307 schema applies:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
# /etc/sssd/sssd.conf   (mode 0600, root-owned)
[sssd]
config_file_version = 2
services = nss, pam
domains = waldur

[domain/waldur]
id_provider = ldap
auth_provider = ldap
ldap_uri = ldap://ldap.example.com
ldap_search_base = dc=example,dc=com
ldap_user_search_base = ou=People,dc=example,dc=com
ldap_group_search_base = ou=Groups,dc=example,dc=com
ldap_schema = rfc2307

ldap_default_bind_dn = cn=readonly,dc=example,dc=com
ldap_default_authtok = <service-account-password>

SSSD must read group membership the way the groups are written:

project_groups.member_attribute Members stored as sssd.conf
memberUid (default) Usernames ldap_schema = rfc2307
member User DNs ldap_schema = rfc2307bis

With the wrong schema the groups still resolve by name and GID, but id and getent group show them without members, so access through them fails. With project groups under their own OU, point ldap_group_search_base at it (or at the base, to see personal and project groups together).

Bind with a dedicated read-only service account rather than the directory admin. Add sss to the passwd, group and shadow databases in /etc/nsswitch.conf, enable pam_sss in your PAM stack, and start SSSD.

A Waldur user then resolves as an ordinary POSIX account:

1
2
3
4
5
$ getent passwd jsmith
jsmith:*:9001:9001:Jane Smith:/home/jsmith:/bin/bash

$ id jsmith
uid=9001(jsmith) gid=9001(jsmith) groups=9001(jsmith)

Add pam_mkhomedir to the session stack if home directories should be created on first login.

When a user leaves

Removing the SLURM association is the resource backend's job; releasing the directory entry is the LDAP plugin's, and in this mode it is on by default. The chain starts in Waldur, not in the agent:

  1. Turn on Enable automatic deletion of offering users on the offering (offering_user_auto_deletion: true). When a person loses their last project role on the offering, Waldur moves the offering user to Requested deletion. Without this option the offering user stays OK and the entry is kept — the agent only ever acts on Waldur's own request.
  2. The agent picks the request up (on the next membership cycle, or at once from the offering-user event when it runs in event mode), drops the SLURM association, then releases the entry as on_departure says.
  3. Because one directory serves every offering of the provider, the agent first asks Waldur whether an account with the same username is still live on any sibling offering. If one is, the entry stays enabled; only when every remaining account is in a deletion state (or none remains) is it released. The check runs against Waldur, never the directory, and a failed lookup keeps the entry and retries next cycle.

What "release" means is on_departure:

1
2
3
4
backend_settings:
  ldap:
    account_source: "waldur"
    on_departure: "disable"   # disable (default in this mode) | delete
on_departure Directory result
disable (default under waldur) The entry is parked: DN, uidNumber, gidNumber and personal group stay as they are. loginShell becomes /usr/sbin/nologin, the shadowAccount class is added with shadowExpire: 1 (an expiry in the past), every memberUid it held in access and project groups is dropped, and description: waldur-site-agent:disabled marks the entry as parked by the agent rather than by an operator
delete The entry, its personal group and its group memberships are removed

disable is the default because a UID must never be reissued while files owned by it exist: keeping the entry keeps ls -l honest and keeps the pool's reservation and the directory in agreement. The name still resolves on the cluster — getent passwd answers — but the login is shut and, on SLURM, it is the missing association that refuses a job.

For the parked entry to lock the account out, SSSD on the nodes must honour the shadow expiry. Add to the [domain/...] section of sssd.conf:

1
ldap_account_expire_policy = shadow

Coming back

A returning member — re-added to a project that still holds an allocation — gets the same entry re-enabled, not a new one. Waldur re-mints the same username (an anonymized name derives from the pool UID, which the provider-wide account keeps), so the reconcile finds a live offering user whose entry exists but carries the parked marker. It restores loginShell from Waldur, drops shadowExpire and the marker, re-adds the configured access_groups, and the SLURM association brings project group membership back with it. An entry an operator disabled by hand (no marker) is treated as an ordinary profile update and left disabled.

Authentication

Identity and authentication are separate concerns, and the table above only covers identity. For a user to log in, the directory also needs a credential:

  • Password. The agent writes userPassword only when generate_vpn_password is enabled, which produces a random secret delivered by the welcome email. A provider-wide shared_user_password on the offering is the other option.
  • SSH keys. No sshPublicKey attribute is written on this path yet, so sss_ssh_authorizedkeys — the mechanism the GLAuth path uses for key-based login — has nothing to serve. The reason is an API one rather than a directory one: a user's keys are exposed on the GLAuth-specific endpoint but not on the offering-user list the agent reads, so the agent never sees them. (The schema is usually present: distributions of OpenLDAP that bundle openssh-lpk already define sshPublicKey.) Tracked in waldur-site-agent#18. Until it lands, use the GLAuth path where key-based SSH is the requirement.

Trying it end to end

The site agent repository ships a runnable demo that brings up Waldur and OpenLDAP, populates the directory, and logs in through SSSD on a throwaway host:

1
2
./ci/sssd-demo/run-demo.sh          # bring up, populate, verify
./ci/sssd-demo/run-demo.sh --down   # tear down

It prints the whole chain — Waldur's values, the directory entries, getent/id, a login session, and a PAM check against both a correct and an incorrect password — and its fixture gives one provider two offerings against one directory, so the convergence behaviour above is visible in the reconcile log.

For the full chain — pool-derived names, the directory, a Keycloak token claim and FirecREST acting on two clusters under one identity — see POSIX identity with OpenLDAP and FirecREST.