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. |
grant_types | list of strings or null | Allowed grant types (e.g. ["authorization_code", "refresh_token"]). null means unrestricted (all flows permitted). |
subject_type | string | "public" (default) or "pairwise". Pairwise subjects are derived per-client via HMAC so that different clients cannot correlate users. |
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. |
id_token_signed_response_alg | string | JWS algorithm for ID tokens and access tokens issued to this client (RFC 7591 §2 id_token_signed_response_alg). Overrides the server-wide jwt_signing_algorithm. Allowed values: RS256, RS384, RS512, PS256, PS384, PS512, ES256, ES384, ES512, EdDSA, ML-DSA-44, ML-DSA-65, ML-DSA-87. When absent, the server default is used. |
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. |
skip_consent | boolean | When true, the /authorize endpoint bypasses the interactive consent screen and issues an authorization code directly. Only enable for trusted first-party clients where user consent is implicit. Default: false. |
localhost_only | boolean | When true, the /token and /device_authorization endpoints reject requests whose source IP is not a loopback address or an address assigned to a local network interface. Use for daemon-to-daemon clients that only run on the same machine as ahdapa. Default: false. |
allowed_resources | list of strings | RFC 8707 resource URIs this client is allowed to request. When set, token requests with a resource parameter not in this list are rejected. When absent, no resource restriction is applied. |
allowed_token_exchange_audiences | list of strings | When set, token exchange requests (RFC 8693) with subject_token_type = urn:ietf:params:oauth:token-type:id_token accept ID tokens whose aud claim matches any value in this list, in addition to the server’s own issuer. Use this to accept ID tokens from upstream CI providers that set a custom audience. |
allowed_registry_repositories | list of {pattern, actions} | Repository access grants for the OCI Distribution v2 registry token endpoint (GET /registry/token). pattern is a glob (*/?) matched against the requested repository name; actions is a subset of pull, push, delete. None means this client cannot use the registry token endpoint at all — the opposite default polarity from allowed_resources, where None means unrestricted. |
device_code_lifetime | integer | Per-client device code lifetime in seconds (RFC 8628). Must be between 60 and 1800 seconds. When set, overrides the global [tokens] device_code_ttl for this client. When absent, the global default applies. |
sd_jwt_enabled | boolean | When true, this client may request SD-JWT (RFC 9901) credential issuance at the /credential endpoint. Default: false. |
tls_client_auth_san_dns | string | Expected dNSName SAN for tls_client_auth (RFC 8705 section 2.1.2). Exactly one SAN type must be set when using SAN-based mTLS auth. |
tls_client_auth_san_uri | string | Expected uniformResourceIdentifier SAN for tls_client_auth (RFC 8705 section 2.1.2). |
tls_client_auth_san_ip | string | Expected iPAddress SAN for tls_client_auth (RFC 8705 section 2.1.2). IPv6 addresses are compared by parsed IpAddr. |
tls_client_auth_san_email | string | Expected rfc822Name SAN for tls_client_auth (RFC 8705 section 2.1.2). |
jwks | object | Inline JWKS (RFC 7591 section 2) containing the client’s public keys. Mutually exclusive with jwks_uri. |
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.
Registration Tokens
RFC 7591 §3 initial access tokens (typ: "reg+jwt") that authorize calls to the
public POST /register endpoint without a Kerberos service-principal session
or the static server.registration_token. See Protocol – Dynamic Client
Registration, Path 2.
| Method | Path | Permission | Description |
|---|---|---|---|
POST | /api/admin/registration-tokens | clients:write | Issue a new initial access token. Body: {"lifetime_secs": 3600, "one_time": false} (both optional; lifetime_secs defaults to 3600 and must be between 60 and 31536000). Returns 201 Created with {"jti","token","issued_at","expires_at","one_time"}. token is the signed reg+jwt compact JWT — it is returned only once and not stored. |
GET | /api/admin/registration-tokens | clients:read | List all issued tokens, newest first. Each entry: {"jti","issued_at","expires_at","one_time","used_at","expired"}. used_at is null until a one_time token is consumed; expired is computed against the current time. The signed token value itself is never returned (only jti is persisted server-side). |
DELETE | /api/admin/registration-tokens/{jti} | clients:write | Revoke a token by its jti before it expires or is used. Returns 204 No Content, or 404 if the jti is not found. |
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. |
POST | /api/admin/nodes/seed | keys:rotate | Pre-seed a peer node’s ML-KEM-768 (and optionally gossip signing) public key on this node before the peer’s first encrypted gossip round. Body: {"node_id","kem_public_key_der","gossip_signing_pub_key_der"}, where the key fields are non-empty base64url-encoded SPKI DER. See Cluster – bootstrapping a peer and ahdapactl cluster nodes seed. |
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).
Federation Policies
Pattern-based identity mapping for workload federation. Federation policies
are stored in the CRDT and replicated to all cluster nodes via gossip. When
an incoming ID token’s iss and sub match a policy’s upstream_iss and
subject_pattern (glob), the token exchange resolves the subject to the
policy’s local_sub rather than requiring a federated_accounts database
row.
| Method | Path | Permission | Description |
|---|---|---|---|
GET | /api/admin/federation-policies | federation:read | List all federation policies (ordered by priority descending). |
GET | /api/admin/federation-policies/{key} | federation:read | Get a single federation policy by its composite key. |
POST | /api/admin/federation-policies | federation:write | Create a new federation policy. |
PUT | /api/admin/federation-policies/{key} | federation:write | Update an existing federation policy. |
DELETE | /api/admin/federation-policies/{key} | federation:write | Delete a federation policy. |
Request fields:
| Field | Type | Required | Description |
|---|---|---|---|
upstream_iss | string | yes | Upstream issuer URI to match against the incoming ID token’s iss claim. |
subject_pattern | string | yes | Glob pattern matched against the incoming sub claim. * matches any characters. |
local_sub | string | yes | Local subject assigned when the pattern matches. Typically a client_id for service identity resolution. |
scopes | list of strings | no | Optional scope ceiling applied on top of the standard scope intersection. When absent, no additional scope restriction is applied. |
priority | integer | no | Higher-priority policies are evaluated first. Default: 0. |
Response fields (in addition to the request fields above):
| Field | Type | Description |
|---|---|---|
key | string | Composite key ({upstream_iss}\0{subject_pattern}). Used as the path parameter for GET, PUT, and DELETE. |
Example:
POST /api/admin/federation-policies
Content-Type: application/json
{
"upstream_iss": "https://token.actions.githubusercontent.com",
"subject_pattern": "repo:myorg/*",
"local_sub": "ci-runner",
"scopes": ["openid", "profile"],
"priority": 10
}
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. |
GET | /api/admin/clients/{client_id}/hbac | clients:read | List the summary of every live HBAC rule that applies to the given OAuth2 client — rules where the client is named directly in clients, is a member of a group in client_groups, or where client_category is the wildcard. Same summary shape as GET /api/admin/hbac. |
See Identity HBAC for policy semantics.
HBAC lookup (typeahead) endpoints
Used by the admin WebUI’s HBAC rule editor to resolve human-readable names
while building a rule’s user/group/host/service/client member lists. Each
accepts a ?q=PREFIX query parameter and returns at most 20 {value, label}
objects (searching static users/groups first, then IPA/LDAP, or the CRDT for
clients).
| Method | Path | Permission | Description |
|---|---|---|---|
GET | /api/admin/hbac/lookup/users?q=PREFIX | hbac:read | Search users by username prefix. |
GET | /api/admin/hbac/lookup/groups?q=PREFIX | hbac:read | Search user groups by name prefix. |
GET | /api/admin/hbac/lookup/hosts?q=PREFIX | hbac:read | Search IPA hosts by hostname prefix (for the PAM/SSH host axis). |
GET | /api/admin/hbac/lookup/services?q=PREFIX | hbac:read | Search IPA HBAC services by name prefix. |
GET | /api/admin/hbac/lookup/clients?q=PREFIX | hbac:read | Search registered OAuth2 clients by client_id prefix or a substring match on client_name. label is "<client_name> (<client_id>)". |
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>
HBAC rule fields reference (registry token endpoint)
The following fields are present on HBAC rules and gate GET /registry/token (see Registry Token Endpoint). They
appear in GET responses and can be modified via the PATCH (PUT)
endpoint.
| Field | Type | Default | Description |
|---|---|---|---|
registry_repositories | string[] | [] | Glob patterns (*/?, ASCII-only) matched against the requested repository name (e.g. "myorg/*"). An empty list means no repository is explicitly allowed unless registry_repository_category is true. |
registry_repository_category | boolean | false | Wildcard flag. When true, any repository name is accepted by this rule, regardless of the registry_repositories list. |
registry_repository_count | integer | (read-only) | Count of patterns currently in registry_repositories. Included in the GET /api/admin/hbac list summary; ignored on writes. |
registry_actions | string[] | [] | Exact-match subset of pull, push, delete this rule permits. |
registry_action_category | boolean | false | Wildcard flag. When true, any action is accepted by this rule, regardless of the registry_actions list. |
To modify these fields via the PATCH endpoint, use:
| Patch field | Type | Effect |
|---|---|---|
add_registry_repositories | string[] | Add one or more glob patterns to registry_repositories. |
remove_registry_repositories | string[] | Remove one or more glob patterns from registry_repositories. |
registry_repository_category | boolean | Set the wildcard flag directly. |
add_registry_actions | string[] | Add one or more actions to registry_actions. |
remove_registry_actions | string[] | Remove one or more actions from registry_actions. |
registry_action_category | boolean | Set the wildcard flag directly. |
# Permit pull/push on any repository matching myorg/*
curl -s -b session.jar \
-X PUT -H 'Content-Type: application/json' \
-d '{"add_registry_repositories": ["myorg/*"], "add_registry_actions": ["pull", "push"]}' \
https://idp.example.com/api/admin/hbac/<rule-id>
SAML2 Service Providers
These endpoints manage SAML2 Service Provider registrations. SPs registered here
can send AuthnRequest messages to the /saml2/sso endpoint and receive SAML
assertions. See SAML2 Federation for the setup guide.
| Method | Path | Permission | Description |
|---|---|---|---|
GET | /api/admin/saml2/sp | saml2:read | List all registered SAML2 SPs, sorted by entity ID. |
POST | /api/admin/saml2/sp | saml2:write | Register a new SAML2 SP. Returns 201 Created. |
GET | /api/admin/saml2/sp/{entity_id} | saml2:read | Get a single SP by entity ID. Returns 404 if not found. |
PUT | /api/admin/saml2/sp/{entity_id} | saml2:write | Update an SP registration. Omitted fields are preserved from the existing record. Returns 404 if not found. |
DELETE | /api/admin/saml2/sp/{entity_id} | saml2:write | Delete an SP registration. Returns 204 No Content on success, 404 if not found. |
POST | /api/admin/saml2/sp/import-metadata | saml2:write | Register (or replace) an SP from a SAML2 metadata document instead of specifying fields individually. See Metadata import, below. Returns 201 Created with the same shape as POST /api/admin/saml2/sp. |
SP create request fields
| Field | Type | Required | Description |
|---|---|---|---|
entity_id | string | yes | The SP’s SAML2 entity ID (e.g. "https://sp.example.com/saml2/metadata"). Must be non-empty. |
acs_urls | list of strings | yes | Assertion Consumer Service URLs. At least one is required. |
slo_url | string | no | Single Logout URL for this SP. When present, LogoutResponse messages are sent here. |
name_id_format | string | no | Preferred NameID format (e.g. "persistent", "transient", "email"). |
certificate_pem | string | no | PEM-encoded X.509 certificate for verifying signed AuthnRequests from this SP. |
want_assertions_signed | boolean | no | Whether this SP expects signed assertions. Default: false. |
SP update request fields
All fields are optional. Omitted fields are preserved from the existing registration.
| Field | Type | Description |
|---|---|---|
acs_urls | list of strings | New ACS URLs (replaces the existing list). At least one is required if provided. |
slo_url | string or null | New SLO URL. Send null to clear. |
name_id_format | string or null | New NameID format. Send null to clear. |
certificate_pem | string or null | New PEM-encoded certificate. Send null to remove the certificate. |
want_assertions_signed | boolean | Whether this SP expects signed assertions. |
SP response fields
| Field | Type | Description |
|---|---|---|
entity_id | string | The SP’s entity ID. |
acs_urls | list of strings | Assertion Consumer Service URLs. |
slo_url | string or null | SLO URL. |
name_id_format | string or null | Preferred NameID format. |
want_assertions_signed | boolean | Whether this SP expects signed assertions. |
has_certificate | boolean | Whether a signing certificate was provided. |
certificate | object or null | Parsed certificate details (see Certificate info below). null when no certificate is registered. |
SAML2 Upstream Identity Providers
These endpoints manage upstream SAML2 IdP registrations for the SP role. Each
upstream IdP enables a /saml2/auth/{local_id} login flow. See
SAML2 Federation for the setup guide.
| Method | Path | Permission | Description |
|---|---|---|---|
GET | /api/admin/saml2/idp | saml2:read | List all registered upstream SAML2 IdPs, sorted by local ID. |
POST | /api/admin/saml2/idp | saml2:write | Register a new upstream IdP. Returns 201 Created. |
GET | /api/admin/saml2/idp/{local_id} | saml2:read | Get a single upstream IdP by local ID. Returns 404 if not found. |
PUT | /api/admin/saml2/idp/{local_id} | saml2:write | Update an upstream IdP registration. Omitted fields are preserved from the existing record. Returns 404 if not found. |
DELETE | /api/admin/saml2/idp/{local_id} | saml2:write | Delete an upstream IdP. Returns 204 No Content on success, 404 if not found. |
POST | /api/admin/saml2/idp/import-metadata | saml2:write | Register (or replace) an upstream IdP from a SAML2 metadata document instead of specifying fields individually. See Metadata import, below. Returns 201 Created with the same shape as POST /api/admin/saml2/idp. |
Metadata import
Both POST /api/admin/saml2/sp/import-metadata and
POST /api/admin/saml2/idp/import-metadata accept the same request shape and
parse a standard SAML2 <EntityDescriptor> (or <EntitiesDescriptor>)
document to populate the SP/IdP fields automatically, instead of specifying
acs_urls, sso_url, certificate_pem, etc. by hand.
| Field | Type | Required | Description |
|---|---|---|---|
metadata_xml | string | one of metadata_xml/metadata_url | Raw SAML2 metadata XML, provided inline. |
metadata_url | string | one of metadata_xml/metadata_url | URL to fetch the metadata document from. Rejected with 400 if the URL fails the same SSRF-safety check used for OIDC federation (is_safe_issuer_url). Stored on the resulting SP/IdP record so later re-imports can refresh from the same source. |
entity_id | string | no | Selects one <EntityDescriptor> by entity ID when the document contains more than one. Required (with a 400 listing the available entity IDs) if the document is ambiguous. |
trusted_certificate_pem | string | no | PEM-encoded certificate. When present, the metadata document’s XML signature is verified against this certificate before parsing; import fails with 400 if the signature is missing or invalid. When absent, the metadata is trusted unconditionally (only use metadata_url with a trusted, ideally HTTPS, source in that case). |
local_id | string | no (IdP import only) | URL slug for /saml2/auth/{local_id}. Defaults to the last path segment of the metadata’s entity ID when absent. Ignored by the SP import. |
For the SP import, the selected entity must contain an SPSSODescriptor with
at least one AssertionConsumerService; the signing certificate (if any) is
taken from the KeyDescriptor with use="signing", and the encryption
certificate from use="encryption". For the IdP import, the selected entity
must contain an IDPSSODescriptor with at least one SingleSignOnService;
the HTTP-Redirect binding is preferred when multiple are present, falling
back to the first listed. All signing KeyDescriptor certificates are
imported (supports key rollover).
Upstream IdP create request fields
| Field | Type | Required | Description |
|---|---|---|---|
entity_id | string | yes | The upstream IdP’s SAML2 entity ID. Must be non-empty. |
local_id | string | yes | URL slug for this upstream (alphanumeric, dashes, underscores only). Used in /saml2/auth/{local_id}. Must be non-empty. |
sso_url | string | yes | The upstream IdP’s SSO endpoint URL. Must be non-empty. |
slo_url | string | no | The upstream IdP’s SLO endpoint URL. |
certificates_pem | list of strings | no | PEM-encoded X.509 certificates for the upstream IdP’s signing keys. Supports multiple certificates for key rollover. |
name_id_format | string | no | Preferred NameID format to request from the upstream. |
preferred_binding | string | no | SAML2 binding URI for AuthnRequests and SSO responses. Allowed values: urn:oasis:names:tc:SAML:2.0:bindings:HTTP-Redirect, urn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST, or urn:oasis:names:tc:SAML:2.0:bindings:HTTP-Artifact. Default: urn:oasis:names:tc:SAML:2.0:bindings:HTTP-Redirect. |
default_groups | list of strings | no | Groups to assign to users who authenticate via this upstream. |
Upstream IdP update request fields
All fields are optional. Omitted fields are preserved from the existing registration.
| Field | Type | Description |
|---|---|---|
entity_id | string | New entity ID (replaces the existing value). |
sso_url | string | New SSO endpoint URL. |
slo_url | string or null | New SLO URL. Send null to clear. |
certificates_pem | list of strings | New PEM-encoded certificates (replaces the existing list). |
name_id_format | string or null | New NameID format. Send null to clear. |
preferred_binding | string | New SAML2 binding URI. |
default_groups | list of strings | New default groups (replaces the existing list). |
Upstream IdP response fields
| Field | Type | Description |
|---|---|---|
entity_id | string | The upstream IdP’s entity ID. |
local_id | string | URL slug. |
sso_url | string | SSO endpoint URL. |
slo_url | string or null | SLO endpoint URL. |
name_id_format | string or null | Preferred NameID format. |
preferred_binding | string | SAML2 binding URI. |
default_groups | list of strings | Default groups for authenticated users. |
certificate_count | integer | Number of signing certificates registered. |
certificates | list of objects | Parsed certificate details for each registered certificate (see Certificate info below). |
Certificate info
Both SP and upstream IdP responses include parsed certificate details when certificates are registered. Each certificate object contains:
| Field | Type | Description |
|---|---|---|
subject | string | Certificate subject distinguished name. |
issuer | string | Certificate issuer distinguished name. |
not_before | string | Validity start time (e.g. "2025-01-01 00:00:00 UTC"). |
not_after | string | Validity end time. |
algorithm | string | Signature algorithm (e.g. "sha256WithRSAEncryption", "ecdsa-with-SHA256"). |
fingerprint_sha256 | string | SHA-256 fingerprint in colon-separated hex (e.g. "AB:CD:EF:..."). |
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. |
GET | /api/admin/spiffe/lookup/hostnames?q=PREFIX | spiffe:read | Search IPA hosts by hostname prefix. Returns an array of {value, label} objects where value and label are both the hostname. Used to populate Hostname 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
Audit events are stored in a systemd journal namespace (ahdapa) via native
Unix datagram protocol, with JSONL file and in-memory daemon fallbacks. The
admin API queries this journal (or the configured [audit] log_file) and
returns structured results.
| Method | Path | Permission | Description |
|---|---|---|---|
GET | /api/admin/audit | audit:read | Query audit events, most recent first. |
Query parameters
| Parameter | Type | Default | Description |
|---|---|---|---|
type | string | — | Filter by event type (e.g. login.success). |
subject | string | — | Filter by subject. |
outcome | string | — | Filter by outcome (success or failure). |
from | string | — | Start time (RFC 3339, e.g. 2026-06-01T00:00:00Z). |
until | string | — | End time (RFC 3339). |
limit | integer | 100 | Maximum number of events to return (1–1000). |
offset | integer | 0 | Number of events to skip (for pagination). |
Response shape
{
"events": [
{
"occurred_at": "2026-06-08T12:34:56Z",
"event_type": "login.success",
"subject": "alice@EXAMPLE.COM",
"principal": null,
"outcome": "success",
"detail": null
}
],
"total_since_startup": 42,
"limit": 100,
"offset": 0
}
Audit event fields
| Field | Type | Description |
|---|---|---|
occurred_at | string | RFC 3339 timestamp. |
event_type | string | Dot-separated event type identifier (see table below). |
subject | string or null | Subject – the user or machine principal involved. Null when not applicable. |
principal | string or null | The OAuth2 client or acting principal. Null when not applicable. |
outcome | string | "success" or "failure". |
detail | string or null | Free-form detail string. Format varies by event type. |
Event types
event_type | Category | Description |
|---|---|---|
login.success | Authentication | User authenticated successfully (password, SPNEGO). |
login.failed | Authentication | Authentication attempt failed. |
passkey.login | Authentication | Successful passkey (WebAuthn) login. |
passkey.registered | Authentication | New passkey registered. |
password.changed | Authentication | Password changed successfully. |
password.change-failed | Authentication | Password change attempt failed. |
token.issued | Token lifecycle | Access token issued. |
token.refreshed | Token lifecycle | Token refreshed via refresh token. |
token.revoked | Token lifecycle | Token revoked. |
kerberos.client-auth | Token lifecycle | Kerberos client authentication at the token endpoint. |
ccache.issued | Kerberos ccache | Kerberos credential cache issued. |
ccache.denied | Kerberos ccache | Kerberos credential cache request denied. |
token-exchange.denied | Token exchange | Token exchange request denied (HBAC or policy). |
token-exchange.actor | Token exchange | OBO token exchange with actor token. |
federation.token-exchange | Federation | Federated token exchange grant. |
federation.jwt-bearer | Federation | JWT bearer assertion grant. |
federation.external-login | Federation | External IdP login completed. |
registry.token-issued | Registry | GET /registry/token issued a token (detail carries service=<host> granted_any=<bool>; granted_any=false means a valid credential but zero authorized repository/action pairs — the OCI soft-denial case). |
registry.token-denied | Registry | GET /registry/token request rejected before any token was minted (invalid credential, disallowed service, or no anonymous grant). |
saml2.idp-sso | SAML2 | SAML2 IdP SSO assertion issued to an SP. |
saml2.sp-login | SAML2 | SAML2 SP login completed (assertion consumed from upstream IdP). |
saml2.logout | SAML2 | SAML2 Single Logout completed (session revoked). |
admin.config-put | Admin | Configuration object created or updated via admin API. |
admin.config-delete | Admin | Configuration object deleted via admin API. |
Template client subject: When a kerberos_client_auth client uses kerberos_principal_pattern (template mode), the subject 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.
Production querying
In addition to the admin API, audit events can be queried directly from the
systemd journal using journalctl:
journalctl --namespace=ahdapa --output=json SYSLOG_IDENTIFIER=ahdapa-audit
Journal fields: AHDAPA_EVENT_TYPE, AHDAPA_OUTCOME, AHDAPA_SUBJECT,
AHDAPA_PRINCIPAL, AHDAPA_DETAIL, with SYSLOG_IDENTIFIER=ahdapa-audit
and PRIORITY (6 = info/success, 4 = warning/failure).