[ Switch to styled version → ]
Self-hosted registry. Every command on this page needs the registry admin token, so it applies to a registry you run. See Self-hosting the registry.
This document describes integration with external identity providers (IDPs), JWT validation, JWKS caching, external IDs, and directory sync.
A registry you run can tie Pilot identities to an external identity system. Two mechanisms are native: validating OIDC/JWT tokens (RS256 or HS256, with keys from a JWKS URL) and a verification webhook you host. SAML, Entra ID, LDAP and other systems connect only through a bridge you run: a service that issues JWTs signed with keys in a JWKS, or that answers the webhook. Network admission still follows the configured join rule and Pilot identity checks.
Identity integration is registry-global. A single IDP configuration applies across the registry. Setting a new configuration replaces the previous one.
Configure an identity provider with `pilotctl extras idp set` or the `set_idp_config` protocol command. The `url` is the JWKS endpoint itself (the `jwks_uri` in your provider’s discovery document), not the discovery document.
pilotctl extras idp set --type oidc \
--url https://accounts.example.com/.well-known/jwks.json \
--issuer https://accounts.example.com --client-id pilot-network-prod
# The same as a protocol command
{
"type": "set_idp_config",
"idp_type": "oidc",
"url": "https://accounts.example.com/.well-known/jwks.json",
"issuer": "https://accounts.example.com",
"client_id": "pilot-network-prod",
"admin_token": "your-admin-token"
}The IDP configuration is stored in the registry and persists across restarts. It can also be set through a blueprint using the `identity_provider` field.
To query the current configuration (`pilotctl extras idp get`):
{
"type": "get_idp_config",
"admin_token": "your-admin-token"
}An `idp.configured` audit event is emitted when the IDP is set or changed.
The registry includes built-in JWT validation supporting two algorithms:
Validate a token with the `validate_token` protocol command:
{
"type": "validate_token",
"token": "eyJhbGciOiJSUzI1NiIs...",
"admin_token": "your-admin-token"
}The command returns `verified` (boolean), `subject`, `issuer`, and an `error` string on failure.
The validator checks the following claims:
For RS256 tokens, the registry fetches the provider’s JSON Web Key Set (JWKS) to obtain public keys for signature verification. JWKS responses are cached.
If the JWKS endpoint is unreachable and the cache has expired, validation fails. The registry does not fall back to accepting unverified tokens.
The validator prevents algorithm confusion attacks by cross-checking the JWT's `alg` header against the matched JWKS key's type. An HS256 token cannot be verified against an RSA signing key. RS256 and HS256 are the only supported algorithms.
A 60-second clock skew tolerance is applied to time-based claims (`exp` and `nbf`) to accommodate minor clock differences between the IDP and the registry.
For systems that don’t issue JWTs, set a verification webhook. When a registration carries an `identity_token` field, the registry POSTs `{"token": "..."}` to your endpoint. Your endpoint answers HTTP 200 with `{"verified": true, "external_id": "..."}`, or `{"verified": false, "error": "..."}` to refuse. The registry stores the returned external ID on the node; a refusal, a non-200 status or an empty external ID fails the registration.
{
"type": "set_identity_webhook",
"url": "https://auth.example.com/verify-agent",
"admin_token": "your-admin-token"
}This command returns a status of `enabled` or `disabled`. The URL is validated for SSRF safety before being stored, and it can also be set through the `identity_url` field of a blueprint's `webhooks` object.
The stock daemon doesn’t send an identity token, so this path is for clients you build against your registry. If you embed the registry with a webhook secret (`Server.SetIdentityWebhookSecret` in Go), requests and responses carry an `X-Pilot-Signature-256` HMAC-SHA256 header and unsigned responses are rejected.
Agents can be mapped to external identity systems using external IDs.
{
"type": "set_external_id",
"node_id": 5,
"external_id": "user@example.com",
"admin_token": "your-admin-token"
}
{
"type": "get_identity",
"node_id": 5,
"admin_token": "your-admin-token"
}External IDs are free-form strings, such as email addresses or UPNs. They are stored in the registry and included in audit events. An `identity.external_id_set` audit event is emitted on change.
Query a node's key lifecycle metadata with the `get_key_info` command.
{
"type": "get_key_info",
"node_id": 5
}The command returns `node_id`, `created_at`, `rotate_count`, `rotated_at` (if set), `expires_at` (if set), and a derived `key_age_days`.
Directory sync takes entries you export from your directory (AD, Entra ID, LDAP or any other system) and pushes them to the registry, with `pilotctl extras directory-sync <directory.json> --network <id> [--remove-unlisted]` or the `directory_sync` command. The registry doesn’t connect to the directory itself. It updates roles for existing members, removes members no longer in the directory, and stores role pre-assignments for identities that have not yet joined. It does not add new members.
The sync operation is performed with the `directory_sync` command.
{
"type": "directory_sync",
"network_id": 1,
"entries": [
{
"external_id": "alice@example.com",
"display_name": "Alice",
"role": "admin"
},
{
"external_id": "bob@example.com",
"display_name": "Bob",
"role": "member"
}
],
"remove_unlisted": true,
"admin_token": "your-admin-token"
}The sync operation performs the following actions:
Directory sync supports role pre-assignment. An identity not yet in the network has its assigned role stored. When the matching node later joins, it receives that role instead of the default.
Query the sync status with `pilotctl extras directory-status <network_id>` or the `directory_status` command.
{
"type": "directory_status",
"network_id": 1,
"admin_token": "your-admin-token"
}This command returns `network_id`, `total`, `mapped`, `unmapped`, `pre_assignments`, `enterprise`, and `last_sync`. A `directory.synced` audit event is emitted after each sync.