Platform
ScaiWave ScaiGrid ScaiCore ScaiBot ScaiDrive ScaiKey Models Tools & Services
Solutions
Organisations Developers Internet Service Providers Managed Service Providers AI-in-a-Box
Resources
Support Documentation Blog Downloads
Company
About Research Careers Investment Opportunities Contact
Log in

Enrollment

Enrollment tokens let an unprovisioned host obtain its own service account and API key, so you never have to copy a long-lived credential onto every machine.

Copying one key to a fleet is the least auditable step in a rollout: it is valid forever, identical everywhere, and nothing records where it went. An enrollment token is the opposite — short-lived, single-use by default, and every redemption is recorded with the hostname and source IP that consumed it.

Mint a token#

http
1
POST /api/v1/enrollment/tokens

Requires service_accounts:write.

json
1
2
3
4
5
6
7
{
  "roles": ["pki-agent"],
  "description": "web tier rollout",
  "ttl_seconds": 3600,
  "max_uses": 1,
  "fqdn_pattern": "web*.example.com"
}
Field Default Notes
roles required Granted to every service account minted from this token. Keep them narrow — an agent doing local-CSR renewal needs pki:write and no more.
ttl_seconds 3600 Maximum 86400. A token that outlives the rollout window is just a long-lived credential under another name.
max_uses 1 Raise it for an autoscaling group, but a reusable credential is a credential.
fqdn_pattern none Glob restricting which hostnames may redeem.

The response includes the plaintext token once. Only its SHA-256 is stored; it is not recoverable afterwards.

bash
1
2
3
curl -sS -X POST https://scaivault.scailabs.ai/api/v1/enrollment/tokens \
  -H "X-API-Key: $ADMIN_KEY" -H 'Content-Type: application/json' \
  -d '{"roles":["pki-agent"],"ttl_seconds":3600,"fqdn_pattern":"web*.example.com"}'

Redeem a token#

http
1
POST /api/v1/enrollment/redeem

Unauthenticated — the caller is a host that has no credential yet, which is the problem enrollment exists to solve.

json
1
{ "token": "enroll_...", "fqdn": "web01.example.com" }

Returns the host's own API key, once:

json
1
2
3
4
5
6
{
  "api_key": "key_...",
  "service_account_id": "sva_...",
  "service_account_name": "agent-web01.example.com",
  "roles": ["pki-agent"]
}

In practice the agent does this for you:

bash
1
scaivault-agent enroll --token enroll_...

List and revoke#

http
1
2
GET    /api/v1/enrollment/tokens          # service_accounts:read
DELETE /api/v1/enrollment/tokens/{id}     # service_accounts:write

Listing shows uses / max_uses and a redemptions array recording which hosts consumed the token, from which IP, and when. Revocation takes effect immediately, whether or not the token has been used.

How the endpoint is protected#

Because redemption cannot require authentication, the safety lives in the token and the endpoint:

  • 256 bits of entropy, stored only as a hash.
  • Short TTL — one hour by default, 24 hours maximum.
  • Use-limited, single-use by default. The claim is atomic, so two hosts racing the same token cannot both succeed.
  • Optional hostname glob via fqdn_pattern.
  • Rate-limited per source IP.
  • Uniform failure. Unknown, expired, revoked, spent and wrong-hostname all return the same 401 enrollment_failed. The specifics go to the audit log, not to the caller, so the endpoint cannot be used as an oracle.

What fqdn_pattern does and does not do#

The hostname is self-asserted. It names the resulting service account and is recorded for audit, so fqdn_pattern narrows the blast radius of a leaked token — it does not prove who the caller is. Binding to a hardware or cloud instance identity would be the next step; today, treat the pattern as a guard rail rather than authentication.

Operational notes#

Each host gets a distinct service account named agent-<fqdn>, so revoking one machine does not affect the rest. A rebuilt host that enrols again receives a fresh account rather than colliding with its old one.

Grant tokens the narrowest roles that work. For the certificate agent that is pki:write in csr mode — enough to request certificates, not enough to export existing private keys.

What's next#

Updated 2026-08-11 21:00:56 View source (.md) rev 2