Headless enrollment
Servers, containers and routers cannot complete an interactive login: there is no browser, and no person to log in. Such a machine enrolls in one of two ways: with an OIDC token that it already holds, or with a bootstrap token that an operator mints for it. This guide covers both, and then explains what happens when the credential of such a node expires.
With an OIDC token that the workload already has
If the platform gives the workload a token from an issuer that the control plane trusts, no operator step is needed. On Kubernetes this is a projected service account token with the right audience:
volumes:
- name: sam-token
projected:
sources:
- serviceAccountToken:
path: sam-token
expirationSeconds: 3600
audience: sam-mesh-audience
sam-node run --control-plane https://mesh.example.com --jwt-path /var/run/secrets/tokens/sam-token
The control plane must list the cluster’s issuer in --issuer and the
audience in --allowed-audiences. The policy must bind the service account
(user:system:serviceaccount:<namespace>:<name>) to sam:role:node.
Routers enroll the same way with sam-router --jwt-path. The
Kubernetes guide shows the complete setup.
A workload with an OAuth client ID and secret can use the client-credentials
grant instead, with --oidc-issuer, --client-id and
--client-secret-path (or SAM_CLIENT_SECRET).
With a bootstrap token
For a machine that has no identity of its own, an operator mints a token.
1. Mint
Call the control plane API with the admin token. sam-one prints the admin
token at start and writes it to its data directory. A separate
sam-control-plane reads it from the file given by --admin-token-path or
from the SAM_ADMIN_TOKEN environment variable.
curl -fsS -X POST https://mesh.example.com/admin/bootstrap-tokens \
-H "Authorization: Bearer $SAM_ADMIN_TOKEN" \
-H "Content-Type: application/json" \
-d '{"role":"sam:role:node","ttl_hours":24,"max_usages":1,"description":"build-runner-3"}'
{"id":"62e92ffca…","token":"sam-bt-72fb0175788dee0…","role":"sam:role:node","expires_at":"2026-09-20T15:00:00Z"}
The plaintext token is shown once. The control plane keeps only its hash.
role is the role that the token enrolls into (sam:role:router for a
router). ttl_hours defaults to 24 and max_usages to 1. The Bootstrap
Tokens view in the console and sam-one token create do the same thing. An
OIDC user who is already enrolled can mint tokens for their own machines
through POST /user/bootstrap-tokens with their ID token. Such tokens
record the user as owner, and banning the user disables them.
Write the token to a file on the target machine. The node reads it from a file and not from a flag, so it does not appear in process listings or in the shell history.
2. Enroll
sam-node join https://mesh.example.com --bootstrap-token-path /etc/sam/bootstrap-token
or, to enroll and start in one step:
sam-node run --control-plane https://mesh.example.com --bootstrap-token-path /etc/sam/bootstrap-token
The node submits its public key, the token, and a signature over a fresh
challenge to POST /enroll. If the control plane runs with
--auto-approve-enrollment (the default in the Helm chart and in sam-one),
the credential comes back immediately. Otherwise the request is queued and
the node polls until an administrator decides.
3. Approve
curl -fsS https://mesh.example.com/admin/enrollments -H "Authorization: Bearer $SAM_ADMIN_TOKEN"
curl -fsS -X POST https://mesh.example.com/admin/enrollments/<request-id>/approve \
-H "Authorization: Bearer $SAM_ADMIN_TOKEN"
.../reject refuses the request. The console lists pending requests with
the same two actions. Approval spends one usage of the token and checks again
that the token is still valid. If the token was revoked, expired or used up
between the request and the approval, the approval fails. It also fails if
the node declared a label that the token’s role does not permit.
Labels
A node enrolled with a bootstrap token declares labels in its configuration
file, like any other node, and the token’s role must allow them through
allowed_labels. Approval by an administrator does not bypass this check.
The grant decides which labels a node may carry.
Revoking a token
curl -fsS -X DELETE https://mesh.example.com/admin/bootstrap-tokens/<id> \
-H "Authorization: Bearer $SAM_ADMIN_TOKEN"
The token stays in the list, marked as revoked, so the record of what it enrolled is kept. Nodes that already enrolled with it are not affected. To remove one of those, ban the node.
When the credential expires
A node enrolled with a bootstrap token has no login to fall back on. Its
credential is refreshed automatically while it runs. But if the node is off
for longer than the control plane’s key grace period (--key-grace-period,
one hour by default), it comes back with a credential signed by a retired
key, and /refresh refuses it. There are three ways out, in order of
preference.
Enroll again. Mint a new token and run sam-node join with it. The
control plane already knows the peer ID, so it mints a new credential
directly instead of queueing a new approval, as long as the token’s role
matches and the node is not banned. Each re-enrollment spends one token
usage, so max_usages limits how often a machine can be brought back this
way.
Widen the grace period. A longer --key-grace-period on the control
plane gives a quiet node more time to refresh. The trade-off is that a
machine that has been lost or stolen can keep renewing for the same time.
Allow autonomous recovery. A node whose record has
autonomous_recovery set may refresh on proof of its own key alone, even
after the signing key that issued its credential is gone. Set it on the token
so that every node enrolled with it inherits the flag, or set it on one node
afterwards:
# on the token, at mint time
-d '{"role":"sam:role:node","max_usages":10,"autonomous_recovery":true}'
# on an enrolled node
curl -fsS -X POST https://mesh.example.com/admin/nodes/<peer-id>/autonomous-recovery \
-H "Authorization: Bearer $SAM_ADMIN_TOKEN" -H "Content-Type: application/json" \
-d '{"enabled":true}'
This is off by default, because a node that can always recover holds a credential that never expires in practice. Only a ban stops it. It suits fleets where an operator round trip per stale node is impractical and the machines are controlled in other ways.
Banning
curl -fsS -X POST https://mesh.example.com/admin/revoke \
-H "Authorization: Bearer $SAM_ADMIN_TOKEN" -H "Content-Type: application/json" \
-d '{"peer_id":"12D3KooW…"}'
The node’s next refresh fails and its daemon exits. Connected peers drop it
as soon as the ban event reaches them. POST /admin/nodes/<peer-id>/unban
reverses the ban. sam-control-plane admin ban --peer <id> and unban do
the same directly in the database, for cases where the API is not reachable.