The Admin Role Name in Mach5
Every Mach5 backend service treats one role name as full admin: whatever it can resolve as its configured admin role name. This document explains where that name comes from, how services and CLIs use it, and what changed when the built-in “default to admin” fallback was removed.
Why there’s a configurable admin role name, not just "admin"
Historically, "admin" was a hardcoded literal. It is now a configurable value, resolved at runtime via the MACH5_ADMIN_ROLE_NAME environment variable, falling back to the literal "admin" only when that variable is unset (e.g. in standalone binaries like mach5-one that don’t otherwise configure it).
MACH5_ADMIN_ROLE_NAME=admin # default
MACH5_ADMIN_ROLE_NAME=platform-owner
This lets an operator rename the admin role per-deployment - useful when admin collides with an existing Keycloak role, or when a stricter naming convention is required.
How the admin role name flows through the system
values.yaml (adminRole.roleName)
│
▼
Kubernetes Secret (mach5-admin-role-secret, key: role-name)
│
▼
MACH5_ADMIN_ROLE_NAME env var
(injected into backend deployment)
│
▼
admin_role_name() / admin_role_header()
│
├── nginx, oidc.enabled: false — nginx is the trust boundary,
│ stamps X-Mach5-Authorization with ["<admin role name>"]
│ on every request it proxies (no per-user distinction)
│
├── nginx, oidc.enabled: true — no direct injection; instead the
│ admin role name is the target for whoever assigns Keycloak
│ roles: a user only gets full access if one of their realm/
│ client roles is spelled like the configured admin role name
│
└── backend services: compare a request's roles against
admin_role_name() to decide "is this caller a full admin?"
Set it in values.yaml under adminRole:
adminRole:
createSecretResources: true
name: mach5-admin-role-secret
roleName: "admin" # <- the admin role name
secretKey: role-name
When createSecretResources: false, point name at a secret you manage yourself, containing the same secretKey.
The X-Mach5-Authorization header
Every authenticated request — HTTP through nginx, or gRPC directly to a backend — carries the caller’s roles as a JSON array string in the X-Mach5-Authorization header (gRPC metadata key x-mach5-authorization, lowercase):
X-Mach5-Authorization: ["admin"]
X-Mach5-Authorization: ["namespace:prod/index:*/read"]
See Role Patterns in Mach5 for the glob syntax used inside that array.
No fallback to admin on a missing header
A missing or empty X-Mach5-Authorization header now resolves to no access (i.e. "[]"), not admin. Earlier versions defaulted an absent header to admin, which was convenient for internal/service-to-service calls but meant any caller that forgot to set the header silently got full access. Every code path that legitimately needs trusted access — nginx when oidc.enabled: false, CLIs, internal tools — must now set the header explicitly using the configured admin role name, rather than relying on that default.
Who sets the header, and how
nginx when oidc.enabled: true — roles come from the JWT
With OIDC on, nginx verifies the caller’s token and builds X-Mach5-Authorization itself from the verified JWT payload, not from anything the client sends:
- Realm roles — everything under the token’s
realm_access.roles. - Client roles — everything under
resource_access.<client_id>.roles, where<client_id>is Mach5’s configured Keycloak client (this is the “Filter by clients” →mach5list you see in the Keycloak role-mapping UI; see Authentication and Authorization in Mach5). - Both lists are merged into one array.
Because of this, there is no separate “admin toggle” in this mode: a user gets full admin access if and only if one of their assigned Keycloak roles is spelled exactly like the deployment’s configured admin role name (MACH5_ADMIN_ROLE_NAME, default "admin"). If an operator renames the admin role to platform-owner, the Keycloak role granting full access must also be named (or contain, via /admin suffix / glob patterns — see Role Patterns in Mach5) platform-owner, not admin. Nothing but the role’s name ties it to “admin”.
nginx when oidc.enabled: false — nginx is the trust boundary
When oidc.enabled: false, there is no OIDC gateway in front of nginx, so nginx itself is the trust boundary: it explicitly stamps X-Mach5-Authorization with the configured admin role name on every request it proxies, rather than leaving the header empty (which would now be denied, since there’s no fallback). Every request that reaches nginx in this mode is treated as fully trusted and gets full admin access — there’s no per-user distinction, because nothing authenticates the caller.
/mcp admin-only enforcement
The MCP endpoint enforces admin-only access directly in nginx: it decodes X-Mach5-Authorization and checks that the configured admin role name is present in the array, returning 403 Forbidden otherwise. This check runs independently of, and in addition to, whatever the backend service itself enforces.
libweave (agentic SQL/MQL tools)
libweave’s agentic tools issue their own outgoing queries to mdx-server (compile-check, schema lookups, query execution) on behalf of a chat session. These tools forward the real caller’s auth context — extracted from the inbound converse() request (extensions when running behind libweave’s own gRPC-web interceptor, falling back to metadata for server-to-server calls that skip that interceptor) — as the X-Mach5-Authorization metadata on every outgoing mdx-server request. Tool calls run with the permissions of the user driving the conversation, not with the admin role.
mdx-cli
mdx-cli query sends X-Mach5-Authorization explicitly via --auth-role (or the MDX_CLI_AUTH_ROLE environment variable), defaulting to the deployment’s admin role name:
# uses the admin role name by default
mdx-cli query --server localhost:9090 '.mach5-events | take 10'
# run as a scoped role instead
mdx-cli query --server localhost:9090 --auth-role '["namespace:prod/index:*/read"]' \
'.mach5-events | take 10'
# or via env var
export MDX_CLI_AUTH_ROLE='["namespace:prod/index:*/read"]'
mdx-cli query --server localhost:9090 '.mach5-events | take 10'
mdx-server denies requests with no role at all, so --auth-role (or its env-var fallback) must resolve to something — there’s no implicit admin default at the transport layer, only the CLI’s own explicit default value.
Practical guidance
- Renaming the admin role: change
adminRole.roleNameinvalues.yaml. Every backend service and nginx pick it up via the sharedMACH5_ADMIN_ROLE_NAMEenv var — no other configuration is needed. - Writing a new internal tool: never hardcode
["admin"]. Always setX-Mach5-Authorizationexplicitly — an absent header is denied, not admin. - Debugging a “permission denied” that used to work: check whether the caller previously relied on the removed default-to-admin fallback. If so, it now needs to send
X-Mach5-Authorizationexplicitly, either with the admin role name or a scoped role pattern (see Role Patterns in Mach5).