Mapping LDAP Groups to Portal Roles
Most enterprises already encode who-belongs-to-what in LDAP or Active Directory, so the cleanest way to drive portal permissions is to reuse those groups rather than maintain a second membership list. This how-to ingests LDAP groups into the catalog and maps them onto portal roles, so a change in the directory flows through to portal access automatically. It extends Role-Based Access Control Setup within Authentication, RBAC & Security Governance.
Prerequisites
- Backstage 1.20+ with RBAC already enforcing permissions on identity.
- Read access to your LDAP/AD directory: a bind account, base DN, and the group and user object filters.
- The
@backstage/plugin-catalog-backend-module-ldapinstalled in the backend. - A clear mapping from directory groups to portal roles agreed with whoever owns access policy.
Exact Configuration
-
Configure the LDAP ingestion so users and groups become catalog entities.
# app-config.yaml # Requires @backstage/plugin-catalog-backend-module-ldap >= 0.9.0 catalog: providers: ldap: default: target: ldaps://ldap.example.com bind: dn: ${LDAP_BIND_DN} secret: ${LDAP_BIND_SECRET} users: dn: ou=people,dc=example,dc=com map: { rdn: uid, name: uid, displayName: cn, email: mail } groups: dn: ou=groups,dc=example,dc=com map: { rdn: cn, name: cn, members: member } -
Map directory groups to portal roles in the RBAC policy so membership grants a role.
# rbac-policy.csv # role assignments derive from ingested Group entities g, group:default/platform-admins, role:default/admin g, group:default/service-owners, role:default/owner p, role:default/admin, catalog-entity, update, allow p, role:default/owner, catalog-entity, read, allow -
Schedule re-ingestion so directory changes propagate on a cadence.
# app-config.yaml catalog: providers: ldap: default: schedule: frequency: { minutes: 30 } timeout: { minutes: 5 }
Validation
# Requires curl, jq
# 1. LDAP groups ingested as Group entities
curl -s "${PORTAL_URL}/api/catalog/entities?filter=kind=group" | jq '[.[].metadata.name] | length'
# Expected: matches the directory group count
# 2. A user carries the expected group membership
curl -s "${PORTAL_URL}/api/catalog/entities/by-name/user/default/asmith" \
| jq '.relations[] | select(.type=="memberOf") | .targetRef'
# Expected: group:default/platform-admins (or the user's real groups)
# 3. The role grants the expected permission (check an authorized action)
curl -s -o /dev/null -w "%{http_code}\n" -X PUT "${PORTAL_URL}/api/catalog/..." \
-H "Authorization: Bearer ${ADMIN_USER_TOKEN}"
# Expected: 200 for a user in an admin-mapped group
Edge Cases & Troubleshooting
| Symptom | Root Cause | Resolution |
|---|---|---|
| No groups ingested | Wrong base DN or group filter | Verify the group DN and object class against the directory |
| Users have no membership | members attribute mapped wrong |
Map the correct attribute (member vs memberUid) |
| Nested groups missing | LDAP does not expand nesting | Flatten nested groups during ingestion or in the directory |
| Access lags a directory change | Re-ingestion interval too long | Shorten the schedule, or trigger on directory events |
| Stale members retain access | Removed users not pruned | Ensure ingestion removes members no longer in the group |
Frequently Asked Questions
Map groups to roles, or assign roles to individuals?
Map groups. Individual assignments drift the moment someone changes teams and become an audit nightmare at scale. Binding roles to directory groups means access follows the directory automatically — a leaver removed from the group loses portal access without anyone touching the portal.
What about nested LDAP groups?
Backstage does not expand LDAP nesting on its own, so a role bound to a parent group will not pick up members of child groups unless you flatten the hierarchy. Either resolve nesting in the directory before ingestion or post-process ingested groups to expand membership; decide which and document it, because silent non-expansion is a common source of “why can’t they access it” tickets.