BeyondTrust/POV

← Back to dashboard

Attesting an agent through SPIRE

Audience: operator · Profile: both · Read this when: you want a remote agent to hold no signing key on disk, are moving an enrolled agent from Ed25519 to SPIRE, or an attested agent stopped authenticating.

Part of Remote Agents. By default an agent enrols with a one-time code and keeps an Ed25519 key in its state volume (identity.json), and whoever can read that volume is the agent. With SPIRE, the agent proves who it is at every start with a short-lived JWT-SVID from the dashboard's own SPIRE server, and keeps its signing key only in memory. Ed25519 stays the default and stays supported; nothing changes for an agent you do not migrate.

Preview. Built and covered by tests, but not yet run against a live agent host. The design is SPIRE for agents, Dex for people. The server itself, and every other feature that uses it, is on SPIFFE and SPIRE.

What runs where

Where What From
The dashboard host a SPIRE server for the dashboard's trust domain docker-compose.spire.yml, layered over docker-compose.yml; on a managed runtime, a sidecar of the app
The agent host spire-agent, which attests the host; spiffe-helper, which keeps a JWT-SVID in a memory-only volume as the agent's uid (10001); the agent, which reads it examples/remote-agent/docker-compose.spire.yml, layered over the agent's docker-compose.yml

The agent needs 2.6.0 or later (AGENT_SPIFFE_JWT_FILE; see the agent README). The Agents page banner says "update the image first" for an older one.

Ports

From To Port Notes
the agent host the dashboard's SPIRE server tcp/8081 (SPIRE_BIND_PORT) mTLS gRPC. Not through Caddy, and not through a TLS-inspecting proxy: ask for a bypass for this host:port, or keep that agent on Ed25519
the agent the dashboard's agent gateway as today one single-use, audience-bound token per start, then ordinary signed requests

The server's admin API is a unix socket on a volume mounted only into the dashboard's own containers. Nothing administrative listens on the network. tcp/8082 (SPIRE_FEDERATION_PORT) is the bundle endpoint for federation with a Workload Lab; attestation does not use it.

Setting it up

  1. Start the dashboard's SPIRE server. On Docker Compose, layer the overlay as below. On Azure Container Apps, Cloud Run, ECS or Kubernetes, it is a sidecar of the app instead: see Running it on a managed container runtime.

bash SPIRE_TRUST_DOMAIN=dashboard.example.com \ docker compose -f docker-compose.yml -f docker-compose.spire.yml up -d

powershell $env:SPIRE_TRUST_DOMAIN = 'dashboard.example.com' docker compose -f docker-compose.yml -f docker-compose.spire.yml up -d

  1. Pin the agent audience under Settings → Integrations → Remote Agents, if it is not already. The agent host reaches the SPIRE server at that URL's host name.
  2. Tick "Let agents attest through SPIRE" (spire_attest_enabled) on the same panel. Leave SPIRE server container (spire_server_container) at vmdash-spire-server unless you renamed the container.
  3. Migrate the agent. On the Agents page, open the agent and press Migrate to SPIRE. In one step the dashboard: - creates a one-use join token for spiffe://<td>/node/<agent-id>, valid for 15 minutes; - creates (or reuses) the workload entry spiffe://<td>/agent/<agent-id>, selecting unix:uid:10001 under that node; - registers its own trust domain, so attestation can be verified; - binds the agent to that SPIFFE ID. Binding does not cut off the agent's current key. That happens when it first attests.

The modal shows the token once, the .env lines and the trust bundle. 5. On the agent host, in the agent's directory: - save the trust bundle as spire/bootstrap.crt; - add the SPIRE_SERVER_ADDRESS, SPIRE_SERVER_PORT, SPIRE_TRUST_DOMAIN and SPIRE_JOIN_TOKEN lines to .env, beside DASHBOARD_URL; - start it with the overlay:

bash docker compose -f docker-compose.yml -f docker-compose.spire.yml up -d

The join token is spent on first use. If 15 minutes pass first, press Migrate to SPIRE again: it reuses the entry and mints a new token. 6. Check it. The agent's detail on the Agents page reads SPIRE (key in memory) instead of Ed25519 (key on disk). The attestation is audited as agent.attest.

Cloud VMs with no key at rest

On a cloud VM the agent host can attest with the instance's own identity (aws_iid, azure_imds, gcp_iit), so it stores no SPIRE key either. Migrate to SPIRE does not create those node entries: the node's SPIFFE ID comes from the instance and is not known until it attests. Create the node entry by hand on the dashboard's SPIRE server, then migrate as above.

Then: keep credentials in the dashboard

An attested agent is the reason to store credentials centrally rather than on the host: nothing on the host's disk can ask for them any more. See Central storage with SPIRE, and tick Release dashboard-held credentials only to SPIRE-attested agents (dashboard_secrets_require_spire) once every agent that needs a credential has migrated.

Rolling back

Drop the overlay on the agent host, and re-issue an enrolment code for the agent. That also clears its SPIFFE binding and puts it back on Ed25519.

Settings

All on Settings → Integrations → Remote Agents.

Setting Key Default What it does
Let agents attest through SPIRE spire_attest_enabled off Turns on POST /api/agent/attest, the Migrate to SPIRE button and the banner
SPIRE server container, SPIRE server address for agents spire_server_container, spire_server_address About the server itself: see SPIFFE and SPIRE → Settings
Release dashboard-held credentials only to SPIRE-attested agents dashboard_secrets_require_spire off Hypervisor credentials, Gateway deploy keys, Config-Management bundles and file-share passwords go only to agents whose key came from a SPIRE attestation
Give the dashboard its own SPIFFE identity dashboard_spiffe_identity_enabled off Not needed for attestation; see The dashboard's own SPIFFE identity

The server's environment variables, on the overlay, the sidecar and the dashboard's own containers, are on SPIFFE and SPIRE.

Troubleshooting

Symptom Likely cause Fix
The SPIRE agent container cannot connect a TLS-inspecting proxy, or tcp/8081 not published to the agent host a proxy bypass for the server's host:port, or stay on Ed25519
The join token is refused spent, or older than 15 minutes press Migrate to SPIRE again for a new one
After the host was off, the SPIRE agent logs SVID is expired then join token does not exist or has already been used, and restarts in a loop. The dashboard agent warns still no JWT-SVID after a minute, then exits after six the agent's own SPIRE identity (7 days at most, about 3.5 at least) ran out while the host was offline, and a join-token agent cannot attest again with its spent token press New SPIRE token on the agent's row, put the new SPIRE_JOIN_TOKEN in its .env and up -d again. Never down -v
The agent waits at start instead of authenticating its last token was already presented, and a replay is refused nothing: it waits for spiffe-helper's next rotation, half the token's lifetime
Migrate to SPIRE says the server is not running the server, not the agent SPIFFE and SPIRE → Troubleshooting
403 … only to agents attested through SPIRE the release setting is on and this agent is still Ed25519 migrate it, or keep its credential on its host for now