This is the canonical reference for how groups and permissions work across the theta42 suite (SSO Manager, Proxy, Jump-Host) and how downstream apps and Linux hosts should read and use them. It is written to be implementable by both humans and LLM agents.
Everything below assumes LDAP is the single source of truth for identity and group membership. Group membership is managed in the SSO Manager Directory, generated from adopted resources — there is no standalone "Groups" page.
host and app. A host administers
hosts; an app administers apps. They do not inherit from each other.admin, access, and opaque capability.
admin implies access. Capabilities are explicit and never implied by
admin.groupOfNames (RBAC)
with no gidNumber. A Linux host uses SSSD to import only the groups it
needs and generate their GIDs on the fly (see §8) — no mass import, no GID
bloat. Only the meta groups are never imported by hosts.god_admin is the sole
group that does not belong to a resource or site.S = site slug (see §7 for normalization). <host>/<app> = the resource slug.
<capability> = an opaque, app-defined capability token (see §4).
| Group | Scope | Meaning |
|---|---|---|
god_admin |
global | Everything, everywhere (all sites, hosts, apps, consoles, all capabilities). The only non-site group. |
S_super_admin |
site | Everything on site S (all hosts, apps, consoles, all capabilities at S). |
S_hosts_admin |
site | Admin on all hosts at S. |
S_hosts_access |
site | Access to all hosts at S. |
S_hosts_<capability> |
site | Capability <capability> on all hosts at S. |
S_host_<host>_admin |
host | Admin on host <host>. |
S_host_<host>_access |
host | Access to host <host>. |
S_host_<host>_<capability> |
host | Capability <capability> on host <host>. |
S_apps_admin |
site | Admin on all apps at S. |
S_apps_access |
site | Access to all apps at S. |
S_apps_<capability> |
site | Capability <capability> on all apps at S. |
S_app_<app>_admin |
app | Admin on app <app>. |
S_app_<app>_access |
app | Access to app <app>. |
S_app_<app>_<capability> |
app | Capability <capability> on app <app>. |
| Group | Scope | Meaning |
|---|---|---|
everyone |
global | All authenticated users, any site. |
S_everyone |
site | All authenticated users at site S. |
These are resolved by the directory (any authenticated user passes), never enumerated as LDAP members, and cannot be used as Unix groups.
_. It appears only between the fixed segments
of a group name._. Normalize to lowercase;
spaces and _ → -; strip other non-[a-z0-9-]. A host named Web 01 and a
site Main Office produce slugs web-01 and main-office.hosts, apps); per-resource groups
use the singular (host, app). This makes S_hosts_admin unambiguous even
if a host were named admin (that host would be S_host_admin_admin).admin or access it is a known
level; any other value is an opaque capability owned by a downstream app.groupOfNames (RFC 2307bis) with no gidNumber. GIDs are
generated on the host by SSSD for only the groups that host imports (see §8).admin — manage (create/update/delete/config) the resource.access — use/read the resource.<capability> — an arbitrary token the SSO does not interpret. The SSO
manages membership and exposes the group to the app; the downstream app
defines and enforces what the capability means (e.g. emby_admin,
gitea_maintain, reboot, backup).The directory recognizes admin, access, super_admin, and the meta groups.
Everything else on a resource group is treated as an opaque capability group and
passed through to consumers.
Define a user's effective permission on a resource by checking, from most specific to most general, whether they are a member of any applicable group. The rule: a higher group implies everything below it.
H at site S| Wanted | Granted if the user is a member of any of |
|---|---|
admin on H |
god_admin · S_super_admin · S_hosts_admin · S_host_H_admin |
access on H |
(any admin rule above) · S_hosts_access · S_host_H_access |
capability C on H |
god_admin · S_super_admin · S_hosts_C · S_host_H_C |
A at site SIdentical, with app/apps substituted for host/hosts.
Each console is registered as an app on its site, so console admin is:
god_admin · S_super_admin · S_app_<console>_admin
def effective(resource, level_or_cap, site):
if user in "god_admin": return True
if user in f"{site}_super_admin": return True
if level_or_cap in ("admin","access"):
agg = f"{site}_{resource.kind}s_{level_or_cap}"
if user in agg: return True
specific = f"{site}_{resource.kind}_{resource.slug}_{level_or_cap}"
if user in specific: return True
if level_or_cap == "access": return effective(resource, "admin", site)
if level_or_cap == "admin": return False # access does not imply admin
return False
everyone / S_everyone are a special grantee: if a resource grants a group to
everyone (or S_everyone), any authenticated user (at that site) passes.
_admin and _access groups (and
site aggregates if configured). Capability groups are created on demand.S_super_admin, S_hosts_*, S_apps_*, S_everyone site groups and the
global god_admin/everyone are managed at the site level (not on a single
host/app resource).One LDAP tree can serve many sites ("Main Office", "Branch Office", "co-lo",
"Mikes Homelab", …). Each site S has its own fully independent set of S_*
groups behind its prefix. A main-office_super_admin or main-office_hosts_admin
touches nothing in branch-office_* or steves-homelab_*. Only god_admin and
everyone cross site boundaries.
Directory groups are groupOfNames (RFC 2307bis) and carry no gidNumber.
There are hundreds of them and only a handful matter on any given host, so we do
not bloat LDAP with GIDs. Instead, each Linux host uses SSSD to import only the
groups it cares about and map them to GIDs on the fly (algorithmic ID mapping).
This keeps the directory clean and the per-host surface tiny.
[domain/example]
id_provider = ldap
auth_provider = ldap
ldap_uri = ldaps://ldap.example
ldap_search_base = dc=example,dc=com
# groupOfNames (RFC 2307bis) schema
ldap_schema = rfc2307bis
ldap_group_object_class = groupOfNames
ldap_group_member = member
# Map GIDs mathematically from the LDAP UUID — no gidNumber in LDAP
ldap_id_mapping = true
ldap_group_uuid = entryUUID
# Import ONLY the groups this host needs (e.g. a naming convention or an OU)
ldap_group_search_filter = (&(objectClass=groupOfNames)(cn=linux-*))
Key ideas:
ldap_id_mapping = true + ldap_group_uuid = entryUUID make SSSD derive a
stable GID for any group it imports, so no gidNumber attribute is required
in LDAP.ldap_group_search_filter is the gatekeeper: SSSD imports only groups that
match, discarding the other hundreds. After changing the filter, clear the
cache (sss_cache -E; rm -f /var/lib/sss/db/*; restart sssd) and verify with
getent group <cn>.A host should import its own resource groups (plus any explicitly granted
ones). Because the schema is predictable, ldap-client can generate the per-host
ldap_group_search_filter from the enrolled host's identity, e.g. a host web01
at site main-office imports:
(&(objectClass=groupOfNames)(|(cn=main-office_host_web01_access)
(cn=main-office_host_web01_admin)
(cn=main-office_host_web01_sudo)))
So the operator (or ldap-client) selects a small allowlist of the host's _access
/ _admin / capability groups to feed sudoers, SSH AllowGroups, and filesystem
ACLs. Only those groups are imported — no GID bloat, no mass import.
input)SSSD cannot merge an LDAP group into a local group whose GID varies per host. Two host-side mechanisms cover it:
pam_exec — a script in the login stack adds the user to the local group for the session:
#!/bin/bash
if id -Gn "$PAM_USER" | grep -q "host_input"; then usermod -a -G input "$PAM_USER"; fi
session optional pam_exec.so /usr/local/bin/add_to_input.sh in
/etc/pam.d/common-session.
nss-groupmerge — merge an LDAP group into a local group at NSS time
(/etc/groupmerge.conf: input: host_input, then group: files sssd groupmerge
in /etc/nsswitch.conf), so any service querying input sees the LDAP group's
members regardless of the local GID.
god_admin, everyone, and S_everyone are NOT imported by hosts — they have
implicit membership and are resolved by the directory only.
A downstream app (Emby, Gitea, a custom service, a shell script) reads group membership from LDAP and interprets it as follows:
memberOf). Groups are groupOfNames (member DN), so query
by the user's DN, e.g. (&(objectClass=groupOfNames)(member=<user_dn>)), or use
the memberOf reverse attribute on the user's entry.god_admin → the user is a global administrator.{site}_super_admin → site administrator for that site.{site}_hosts_* / {site}_app_* (aggregate) → applies to all hosts/apps at the site.{site}_host_<host>_* / {site}_app_<app>_* → applies to that one resource.everyone / {site}_everyone → the user is implicitly a member.admin → full control of that resource.access → read/use.{site}_host_web01_access can reach web01; a user with
{site}_host_web01_reboot (if you define reboot) may reboot it; a user with
{site}_app_emby_emby_admin administers Emby.The app must never treat an unknown last segment as admin or access.
app_* groupsThe current global groups (app_sso_admin, app_super_admin,
app_sso_directory_admin, app_jump_admin) are replaced by the new model:
| Legacy | New |
|---|---|
app_super_admin |
god_admin |
app_sso_admin |
S_app_sso_admin (+ S_super_admin for site admins) |
app_sso_directory_admin |
S_app_sso_admin |
app_jump_admin |
S_app_jump_admin |
During the transition the legacy groups may be kept as short-lived aliases that resolve to the same effective permission; once everything is moved, remove them.
The SSO, Proxy, and Jump-Host each register themselves as an app on their site and
receive their auto-generated groups (S_app_sso_admin, S_app_proxy_admin,
S_app_jump_admin, plus _access). Their admin UIs gate on
god_admin · S_super_admin · S_app_<console>_admin. This keeps everything
self-consistent: the SSO is "just another app."