zitadel-rbac-mapper

module
v0.18.0 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Jul 25, 2026 License: MIT

README

Zitadel RBAC Mapper

CI Release Go Report Card

Org-aware groups-to-grants mapping webhook for Zitadel Actions V2 — the authorization policy engine of a multi-company platform.

One Zitadel instance hosts one org per legal entity (company); each company has its own directory (google-group-sync for Google Workspace, entra-group-sync for Microsoft Entra — same resolver HTTP contract). During every login, a single mapper deployment routes by the user's org, resolves their directory groups, maps them to Zitadel project roles (exact keys and glob patterns), reconciles UserGrants (ProjectGrant-aware, delta-only), and appends a groups claim to the token. Role keys become Kubernetes RBAC group names downstream.

Security posture

Input is trusted only after verification: the webhook payload is a JWS signed by the Zitadel instance (verified against its JWKS, expired payloads rejected), and routing uses the org ID from the verified payload. Unknown orgs fail closed — no config entry means no enrichment and no grants, ever (the login itself never breaks). Each org is isolated behind its own bulkhead and circuit breaker, so one company's directory outage cannot touch another company's logins; grant writes are idempotent deltas, and the batch /sync path is the sole authority for pruning revoked access.

[login] → Zitadel Actions V2 → POST /webhook (JWS)
             → verify JWT → route by org (fail-closed)
             → org resolver (bulkhead + breaker) → rules + role catalog
             → UserGrant sync → append_claims: groups

Claim contract

The mapper appends a single groups claim to the token:

  • Default: the user's resolved directory group emails, passed through from the org's resolver unmodified.
  • appendRoleClaims: true: additionally, {projectName}:{roleKey} entries for the user's Zitadel grants — both the grants in the verified payload's user_grants and the desired grants the mapper just computed from the rules (so the very first login already carries the roles the sync is creating). The whole list is deduplicated and sorted. Downstream Kubernetes ClusterRoleBindings and ArgoCD RBAC bind these {projectName}:{roleKey} strings instead of group emails.
  • roleClaimsOnly: true (requires appendRoleClaims): the role entries replace the group emails rather than joining them — the claim becomes pure authorization vocabulary. Enable only once nothing downstream binds directory groups: Kubernetes RBAC subjects, ArgoCD's policy.csv and its AppProject roles, gateway claim rules. A missed consumer fails silently (the user simply loses access), so audit before flipping.

Directory groups are the mapper's input in every mode — they match the rules and drive grant sync. These flags control only what downstream systems receive.

The three modes form a migration path: emails only → emails + roles (parallel run, one org at a time) → roles only.

Quickstart (Kubernetes)

kubectl -n zitadel-rbac-mapper create secret generic zitadel-rbac-mapper-secrets \
  --from-literal=ZITADEL_KEY_JSON="$(cat machine-user-key.json)" \
  --from-literal=SYNC_API_KEY="$(openssl rand -hex 32)"

helm install zitadel-rbac-mapper oci://ghcr.io/truvity/charts/zitadel-rbac-mapper \
  --namespace zitadel-rbac-mapper -f values.yaml   # env, envFrom, orgsConfig

orgsConfig is the v2 per-org config document — fully commented example in config.example.yaml, every field in the configuration reference, full walkthrough (including Zitadel credential requirements and Actions V2 wiring) in the install guide.

Documentation

The docs/ tree is the reference:

Development

devbox shell            # activate dev environment
just check              # build + unit + integration + lint + vuln
just test-integration   # hermetic suite: fake Zitadel + per-org fake resolvers
just test-e2e           # against a real Zitadel instance (credentials required)

The hermetic integration harness is documented in tests/integration/README.md.

License

MIT

Directories

Path Synopsis
cmd
zitadel-rbac-mapper command
Package main is the entry point for zitadel-rbac-mapper.
Package main is the entry point for zitadel-rbac-mapper.
zitadel-rbac-mapper-lambda command
Package main is the Lambda entry point for zitadel-rbac-mapper.
Package main is the Lambda entry point for zitadel-rbac-mapper.
pkg
app
Package app wires all components and runs the service.
Package app wires all components and runs the service.
catalog
Package catalog caches the per-org Zitadel project catalog: which projects an org can grant (owned directly or received via ProjectGrant), the projectGrantId when granted, and the role keys available on each project.
Package catalog caches the per-org Zitadel project catalog: which projects an org can grant (owned directly or received via ProjectGrant), the projectGrantId when granted, and the role keys available on each project.
config
Package config provides environment-based configuration for zitadel-rbac-mapper.
Package config provides environment-based configuration for zitadel-rbac-mapper.
mapper
Package mapper provides the rules engine that maps directory groups to desired Zitadel UserGrants, and the per-org configuration schema (v2).
Package mapper provides the rules engine that maps directory groups to desired Zitadel UserGrants, and the per-org configuration schema (v2).
metrics
Package metrics defines the Prometheus metrics for zitadel-rbac-mapper.
Package metrics defines the Prometheus metrics for zitadel-rbac-mapper.
reconcile
Package reconcile contains the shared grant-planning and full-reconciliation logic used by both the login webhook (single user) and the batch sync paths (POST /sync and the `sync` subcommand).
Package reconcile contains the shared grant-planning and full-reconciliation logic used by both the login webhook (single user) and the batch sync paths (POST /sync and the `sync` subcommand).
resolver
Package resolver provides per-org HTTP clients for the directory-groups resolver services (google-group-sync / entra-group-sync — same HTTP contract), with per-org isolation: independent timeouts, bounded concurrency (bulkhead) and a circuit breaker.
Package resolver provides per-org HTTP clients for the directory-groups resolver services (google-group-sync / entra-group-sync — same HTTP contract), with per-org isolation: independent timeouts, bounded concurrency (bulkhead) and a circuit breaker.
server
Package server provides the HTTP server for zitadel-rbac-mapper.
Package server provides the HTTP server for zitadel-rbac-mapper.
zitadeljwt
Package zitadeljwt verifies JWT-signed payloads from Zitadel Actions V2 webhooks.
Package zitadeljwt verifies JWT-signed payloads from Zitadel Actions V2 webhooks.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL