Admin API
Mounted under /api/admin/. All endpoints require a valid session cookie with an
appropriate RBAC permission. Configure permissions via [[rbac.role]] in the
configuration file — without a role that grants * or the specific permission,
every admin request returns 403 Forbidden.
See Configuration Reference — [rbac]
for the full permission list and role setup.
Client Registry
| Method | Path | Permission | Description |
|---|---|---|---|
GET | /api/admin/clients | clients:read | List all registered OAuth2 clients. |
POST | /api/admin/clients | clients:write | Create a new client registration. |
GET | /api/admin/clients/{client_id} | clients:read | Get a single client registration. |
PUT | /api/admin/clients/{client_id} | clients:write | Update a client registration. Fields omitted from the PUT body are preserved from the existing record. In particular, token_endpoint_auth_method is not reset to a default when the field is absent from the request — this prevents public PKCE clients (registered with token_endpoint_auth_method: "none") from being silently converted to private_key_jwt on the next edit. |
DELETE | /api/admin/clients/{client_id} | clients:write | Delete a client registration. |
Redirect URI scheme enforcement (RFC 9700 §2.6): All redirect_uris in POST and
PUT requests must use https://, or http:// with a loopback host only
(localhost, 127.0.0.1, or ::1). Any other http:// URI is rejected with
400 Bad Request.
Client fields reference
| Field | Type | Description |
|---|---|---|
client_name | string | Human-readable name shown in the admin WebUI and on consent screens. |
redirect_uris | list of strings | Allowed redirect URIs for the authorization code flow. Must be https:// or loopback http://. |
scopes | list of strings | Scopes the client is permitted to request. |
token_endpoint_auth_method | string | How the client authenticates at the token endpoint. One of: private_key_jwt, client_secret_jwt, client_secret_basic, client_secret_post, tls_client_auth, self_signed_tls_client_auth, none, or kerberos_client_auth. |
client_secret | string | Shared secret. Required for client_secret_* methods; must not be set for kerberos_client_auth. |
jwks_uri | string | URL of the client’s JWKS endpoint. Required for private_key_jwt; must not be set for kerberos_client_auth. |
tls_client_certificate | string | PEM-encoded client certificate for mTLS authentication. Required for tls_client_auth / self_signed_tls_client_auth; must not be set for kerberos_client_auth. The server stores only the SHA-256 thumbprint. |
kerberos_principal | string | Exact Kerberos service principal for single-machine kerberos_client_auth clients (e.g. "host/node1.example.com@EXAMPLE.COM"). Must contain / and @. Mutually exclusive with kerberos_principal_pattern. Only accepted when [ipa] gssapi = true. |
kerberos_principal_pattern | string | Glob pattern for template kerberos_client_auth clients (e.g. "host/*@EXAMPLE.COM"). * matches any characters except @. Must contain @. At most three wildcards allowed. Mutually exclusive with kerberos_principal. Only accepted when [ipa] gssapi = true. |
kerberos_hbac_service | string | Optional. FreeIPA HBAC service name that gates access via the replicated HBAC rule set. Only meaningful for kerberos_client_auth clients. When set and the HBAC rule set is empty, all token requests are denied (fail-closed). |
spiffe_id | string | Optional. SPIFFE ID URI (e.g. "spiffe://example.org/workload/myapp") bound to this client. When set, ahdapa recognises workloads presenting an X.509-SVID whose URI SAN matches this value for mTLS authentication. See SPIFFE Integration. |
workload_type | string or null | Optional. Machine-readable workload category label (e.g. "pipeline-agent"). When the client performs an RFC 8693 OBO token exchange as the actor, this value is embedded in the act.workload_type claim of the issued token. It is resolved from the CRDT registration at token issuance time — not from the actor token — to prevent label spoofing. Defaults to null. |
allow_token_exchange_actor | boolean | Gate that controls whether this client may supply actor_token in a token exchange request. When false (the default), any request that includes actor_token is rejected immediately with 403 access_denied before subject token validation. Set to true for clients that are authorised OBO actors. |
kerberos_client_auth constraints enforced by the admin API:
[ipa] gssapi = truemust be configured on the server; otherwise the request is rejected with400 Bad Request.- Exactly one of
kerberos_principalorkerberos_principal_patternmust be set. kerberos_principalmust match the formatservice/host@REALM(contains/and@).kerberos_principal_patternmust contain@and at most three*wildcards.kerberos_client_authis mutually exclusive withclient_secret,jwks_uri, andtls_client_certificate.kerberos_client_authis rejected byPOST /register(dynamic registration); use the admin API.
Signing Keys
| Method | Path | Permission | Description |
|---|---|---|---|
GET | /api/admin/keys | keys:read | List active signing keys and their algorithms. |
POST | /api/admin/keys/rotate | keys:rotate | Rotate the active signing key (generates a new key; old key is retained for verification). |
DELETE | /api/admin/keys/{kid} | keys:rotate | Revoke a signing key by tombstoning its OR-Map entry. The key is immediately removed from the JWKS endpoint and from all cluster nodes via gossip. Returns 404 if the kid is not found. Logs a warning if the revoked key is the currently active kid; follow with a key rotation. |
GET | /api/admin/keys/cluster | keys:read | Get the cluster AEAD wrapping key metadata (UUID identifier and rotation timestamp). The raw key is never exposed. |
PUT | /api/admin/keys/cluster | keys:rotate | Set a new cluster AEAD wrapping key. |
Cluster Nodes
| Method | Path | Permission | Description |
|---|---|---|---|
GET | /api/admin/nodes | nodes:read | List all known cluster nodes and their last-seen state. |
Sessions and Refresh Tokens
| Method | Path | Permission | Description |
|---|---|---|---|
GET | /api/admin/refresh-families | users:read | List all active refresh-token families. |
DELETE | /api/admin/refresh-families/{family_id} | users:write | Revoke all tokens in a refresh-token family (force re-login). |
Federated Accounts
| Method | Path | Permission | Description |
|---|---|---|---|
GET | /api/admin/federated-accounts | federation:read | List all federated-account linkages. |
POST | /api/admin/federated-accounts | federation:write | Create a federated-account linkage. |
DELETE | /api/admin/federated-accounts/{id} | federation:write | Remove a federated-account linkage. |
IPA Identity Providers (Auto-discovered)
These endpoints expose the IPA-sourced upstream IdPs that ahdapa discovers automatically
from cn=idp,<suffix> LDAP objects. All LDAP-sourced attributes are read-only; only the
default_acr and default_amr override fields can be written via the admin API or the
IPA Upstream IdPs page in the WebUI.
| Method | Path | Permission | Description |
|---|---|---|---|
GET | /api/admin/federation/ipa-idps | federation:read | List all IPA-discovered upstream IdPs with their current ACR/AMR override values. Returns an empty array when [ipa] gssapi is not enabled or no ipaIdP objects exist. |
GET | /api/admin/federation/ipa-idps/{id} | federation:read | Get a single IPA IdP by its identifier (e.g. ipa-google-workspace). Returns 404 if the IdP is not present in the current in-memory list (i.e. not in the last LDAP refresh). |
PUT | /api/admin/federation/ipa-idps/{id} | federation:write | Set the ACR/AMR override for an IPA IdP. Persisted in the CRDT and gossiped to all cluster nodes. The LDAP-sourced fields (issuer, client_id, scopes, callback_path) are read-only and must be managed in FreeIPA. |
GET /api/admin/federation/ipa-idps response (array of):
{
"id": "ipa-google-workspace",
"display_name": "Google Workspace",
"issuer": "https://accounts.google.com",
"client_id": "123…apps.googleusercontent.com",
"scopes": ["openid", "email", "profile"],
"callback_path":"/internal/callback/ipa-google-workspace",
"default_acr": null,
"default_amr": [],
"source": "ipa"
}
PUT /api/admin/federation/ipa-idps/{id} request body:
{
"default_acr": "urn:oasis:names:tc:SAML:2.0:ac:classes:InternetProtocolPassword",
"default_amr": ["pwd", "fed"]
}
Send "default_acr": null and "default_amr": [] to clear overrides (fall back to LDAP-sourced values or the built-in unspecified ACR).
Users and Groups (read-only)
| Method | Path | Permission | Description |
|---|---|---|---|
GET | /api/admin/users | users:read | List users visible to the server (static users file). |
GET | /api/admin/users/{username} | users:read | Get a single user record. |
GET | /api/admin/groups | users:read | List groups. |
GET | /api/admin/groups/{name} | users:read | Get a single group and its members. |
Scope Management
Scope definitions control which OIDC claims are returned for each OAuth2 scope name. Eight built-in scopes (openid, offline_access, profile, email, phone, address, groups, directory.read) are seeded on first startup and cannot be deleted. Custom scopes can be created and edited freely.
The directory.read scope gates access to the machine-readable identity API (/api/identity/). It carries no OIDC claims of its own — it is an authorization scope, not a claims scope. See Identity API for endpoint details.
| Method | Path | Permission | Description |
|---|---|---|---|
GET | /api/admin/scopes | scopes:read | List all scope definitions. Returns an array of {name, description, claims, is_system} objects. |
PUT | /api/admin/scopes/{name} | scopes:write | Create or update a scope definition. Body: {"description":"…","claims":["claim1","claim2"]}. Returns 403 for built-in system scopes. Changes are replicated to all cluster nodes via gossip. |
DELETE | /api/admin/scopes/{name} | scopes:write | Delete a custom scope via tombstone. Returns 403 for built-in system scopes. |
Identity HBAC Policies
| Method | Path | Permission | Description |
|---|---|---|---|
GET | /api/admin/hbac | hbac:read | List all HBAC policy rules. |
POST | /api/admin/hbac | hbac:write | Create a new HBAC rule. |
GET | /api/admin/hbac/{rule_id} | hbac:read | Get a single HBAC rule. |
PUT | /api/admin/hbac/{rule_id} | hbac:write | Update a rule (partial update; omitted fields are preserved). |
DELETE | /api/admin/hbac/{rule_id} | hbac:write | Delete a rule. |
See Identity HBAC for policy semantics.
HBAC rule fields reference (delegation-related)
The following fields are present on HBAC rules and control RFC 8693 OBO
delegation target enforcement. They appear in GET responses and can be
modified via the PATCH (PUT) endpoint.
| Field | Type | Default | Description |
|---|---|---|---|
delegation_targets | string[] | [] | List of Kerberos SPNs (e.g. "host/backend.example.com") this rule permits as the target_service in a token exchange request. An empty list means no SPN is explicitly allowed unless delegation_target_category is true. |
delegation_target_category | boolean | false | Wildcard flag. When true, any target_service value is accepted by this rule, regardless of the delegation_targets list. |
delegation_target_count | integer | (read-only) | Count of SPNs currently in delegation_targets. Included in GET responses; ignored on writes. |
To modify delegation targets via the PATCH endpoint, use:
| Patch field | Type | Effect |
|---|---|---|
add_delegation_targets | string[] | Add one or more SPNs to delegation_targets. |
remove_delegation_targets | string[] | Remove one or more SPNs from delegation_targets. |
delegation_target_category | boolean | Set the wildcard flag directly. |
# Permit a specific backend SPN
curl -s -b session.jar \
-X PUT -H 'Content-Type: application/json' \
-d '{"add_delegation_targets": ["host/backend.example.com"]}' \
https://idp.example.com/api/admin/hbac/<rule-id>
# Remove a previously permitted SPN
curl -s -b session.jar \
-X PUT -H 'Content-Type: application/json' \
-d '{"remove_delegation_targets": ["host/old-backend.example.com"]}' \
https://idp.example.com/api/admin/hbac/<rule-id>
# Enable wildcard (any target_service allowed by this rule)
curl -s -b session.jar \
-X PUT -H 'Content-Type: application/json' \
-d '{"delegation_target_category": true}' \
https://idp.example.com/api/admin/hbac/<rule-id>
SPIFFE Workload Entries
These endpoints are active only when [spiffe] trust_domain is set. They manage the
workload registration entries that the Workload API uses to attest callers and issue
SVIDs. See SPIFFE Integration for the full setup guide.
| Method | Path | Permission | Description |
|---|---|---|---|
GET | /api/admin/spiffe/entries | spiffe:read | List all workload registration entries, sorted by SPIFFE ID. |
POST | /api/admin/spiffe/entries | spiffe:write | Create a new workload registration entry. Returns 201 Created with the new entry including its server-assigned id. |
GET | /api/admin/spiffe/entries/{id} | spiffe:read | Get a single workload registration entry by its UUID. Returns 404 if not found. |
PUT | /api/admin/spiffe/entries/{id} | spiffe:write | Replace a workload registration entry. Returns 404 if the entry does not exist. |
DELETE | /api/admin/spiffe/entries/{id} | spiffe:write | Delete a workload registration entry. Returns 404 if not found, 204 No Content on success. |
GET | /api/admin/spiffe/status | spiffe:read | Return SPIFFE CA status for the current node. |
Selector lookup endpoints
These endpoints are used by the admin WebUI SelectorBuilder to resolve human-readable names to numeric IDs. They accept a ?q=PREFIX query parameter and return at most 20 results, searching static users first and then IPA/LDAP.
| Method | Path | Permission | Description |
|---|---|---|---|
GET | /api/admin/spiffe/lookup/users?q=PREFIX | spiffe:read | Search users by username prefix. Returns an array of {value, label, uid_number} objects where value is the username, label is the display name, and uid_number is the POSIX UID (or null if not available). Used to look up UID numbers by name for Uid selectors. |
GET | /api/admin/spiffe/lookup/groups?q=PREFIX | spiffe:read | Search groups by name prefix. Returns an array of {value, label, gid_number} objects where value is the group name, label is the display name, and gid_number is the POSIX GID (or null if not available). Used to look up GID numbers by name for Gid and SupplementalGid selectors. |
GET | /api/admin/spiffe/lookup/hostgroups?q=PREFIX | spiffe:read | Search IPA host groups by name prefix. Returns an array of {value, label} objects where value and label are both the host group name. Used to populate Hostgroup selectors. |
Example responses:
GET /api/admin/spiffe/lookup/users?q=alice:
[{"value": "alice", "label": "Alice Admin", "uid_number": 10001}]
GET /api/admin/spiffe/lookup/groups?q=web:
[{"value": "web-servers", "label": "web-servers", "gid_number": 8001}]
GET /api/admin/spiffe/lookup/hostgroups?q=web:
[{"value": "web-servers", "label": "web-servers"}]
Workload entry fields
| Field | Type | Required | Description |
|---|---|---|---|
spiffe_id | string | yes | SPIFFE ID URI to issue to matching workloads, e.g. "spiffe://example.org/workload/myapp". Must be a valid SPIFFE ID. |
selectors | list of strings | no | Workload attestation selectors stored as JSON-encoded objects. Each element is a JSON string with a "type" tag and a "value" field. Eight types are supported — see table below. A workload must match all selectors in an entry to receive that SPIFFE ID. Defaults to []. |
node_constraint | string or null | no | Restrict this entry to a specific cluster node ID. null means any node. |
ttl_seconds | integer | no | SVID TTL override in seconds. 0 uses the global [spiffe] svid_ttl_seconds default. |
The id field (UUID string) is assigned by the server on POST and returned in the response body. Supply it in the path for GET, PUT, and DELETE requests.
Selector type reference
"type" | "value" type | Match condition | Attestation path |
|---|---|---|---|
Uid | integer (u32) | Caller’s Unix UID equals the value | Local Unix socket only (SO_PEERCRED) |
Gid | integer (u32) | Caller’s primary GID equals the value | Local Unix socket only (SO_PEERCRED) |
SupplementalGid | integer (u32) | Caller’s supplemental group list (from /proc/<pid>/status Groups:) contains the value | Local Unix socket only |
Path | string | /proc/<pid>/exe symlink resolves to this absolute path | Local Unix socket only |
Hostname | string | Machine hostname equals the value | Local (from /proc/sys/kernel/hostname) or remote (caller-declared) |
Hostgroup | string | Machine belongs to this IPA host group (server-verified via LDAP) | Local and remote |
ImaHash | string ("alg:hexdigest") | Hash of the running executable matches; supported algorithms: sha256, sha512, sha1 | Local (computed at accept time) or remote (caller-declared via ima_hash field) |
NodeId | string | Attestation occurs on the named Ahdapa node | Local and remote |
Selector JSON examples:
{"type": "Uid", "value": 1000}
{"type": "Gid", "value": 1000}
{"type": "SupplementalGid","value": 5000}
{"type": "Path", "value": "/usr/bin/myapp"}
{"type": "Hostname", "value": "web01.example.org"}
{"type": "Hostgroup", "value": "web-servers"}
{"type": "ImaHash", "value": "sha256:deadbeef..."}
{"type": "NodeId", "value": "node1.example.org"}
When passed in the selectors array of a create/update request, each object must be JSON-encoded as a string:
{
"spiffe_id": "spiffe://example.org/workload/myapp",
"selectors": [
"{\"type\":\"Uid\",\"value\":1000}",
"{\"type\":\"Path\",\"value\":\"/usr/bin/myapp\"}"
]
}
The admin WebUI SelectorBuilder handles this encoding automatically.
SPIFFE status response
GET /api/admin/spiffe/status returns:
| Field | Type | Description |
|---|---|---|
trust_domain | string or null | Configured trust domain, or null when SPIFFE is not enabled. |
ca_algorithm | string | Key algorithm of the active CA (e.g. "EC-P256"). |
bundle_sequence | integer | Monotonically increasing bundle sequence counter (0 when no bundle is loaded yet). |
refresh_hint | integer | Configured bundle_refresh_hint in seconds. |
entry_count | integer | Number of live workload registration entries. |
workload_socket | string | Filesystem path of the Workload API Unix socket. |
hsm_backed | boolean | true when the CA private key is held in an HSM (PKCS#11) and not in the CRDT. |
Audit Log
| Method | Path | Permission | Description |
|---|---|---|---|
GET | /api/admin/audit | audit:read | List audit events, most recent first. Returns at most 100 events per call. Supports ?offset=<n> for pagination. |
Audit event schema
Each event object contains:
| Field | Type | Description |
|---|---|---|
id | integer | Auto-incrementing event ID. |
event_type | string | Event type identifier (see table below). |
sub | string | Subject — the user or machine principal involved. Omitted when not applicable (e.g. client-credentials flows that do not have a user subject). |
client_id | string | OAuth2 client ID involved. Omitted when not applicable (e.g. login events). |
detail | string | Free-form detail string. Format varies by event type. |
created_at | integer | Unix timestamp (seconds). |
Event types
event_type | sub | client_id | detail |
|---|---|---|---|
kerberos_client_auth | Authenticated principal (actual machine principal for template clients, e.g. host/node1.example.com@REALM). | Template client_id. | Space-separated granted scope string, e.g. openid directory.read. |
login_success | Authenticated user UPN. | — | — |
jwt_bearer | Subject from bearer assertion. | Client ID. | iss=<issuer> jti=<jti> of the upstream assertion. |
ipa_idp_acr_updated | Admin who made the change. | — | {"idp_id":"<id>"} |
Template client sub: When a kerberos_client_auth client uses kerberos_principal_pattern (template mode), the sub in the audit event is the actual authenticated machine principal (e.g. host/node1.example.com@EXAMPLE.COM), not the template client_id. This makes individual machines distinguishable in audit records even though they share a single client registration.
Admin WebUI behaviour: In the audit log page, subjects that contain / (service principals such as host/node1.example.com@REALM) are rendered as plain text. Subjects without / (regular users) are rendered as clickable links to the user detail page.