Skip to content

Credentials: Bootstrap vs Manual

Every deployment path supports two ways of giving the agent its first credential.

Mode What you give the agent When it fits
Bootstrap (enrollment token) A reusable enrollment token. The agent calls POST /api/v1/agents/bootstrap on first start, exchanges the token for a per-container API key, and persists that key for restart-safe auth. The default for any cloud deployment. One enrollment token can register many agents (Fargate autoscale, multi-host fleets, ephemeral tasks).
Manual (permanent API key) A long-lived agent API key minted from the Argus dashboard. The agent uses it directly with no bootstrap call. Single-host deployments where you want the simplest possible setup, or where you cannot tolerate a /bootstrap round trip on every restart.

The dashboard’s Deploy Agent drawer shows you which mode is being used and warns you (yellow banner) if you pick a mode that does not fit the deployment shape well (for example, Manual mode on a cloud autoscale target).

Deployment Recommended mode
Cloud (managed) Fargate / EC2 Bootstrap
Cloud (DIY) Terraform / ECS Bootstrap
Local (Compose, docker run, offline) Either

See Pre-flight, step 3 for the exact dashboard steps.

  • Bootstrap: enrollment tokens look like argus_et_..., are reusable, and default to no expiry. Every container that runs with one exchanges it for its own per-container API key.
  • Manual: API keys look like argus_ak_... and are shown exactly once at mint time. Treat the key like a password; if it leaks, rotate from the agent’s detail page.

The external ID is a different credential from the two above, and it protects a different thing. The API key authenticates the agent to Argus. The external ID is presented by the agent to AWS when it assumes the role in your account, and your role’s trust policy is what checks it. They are separate channels, which has one consequence worth knowing before you read anything else on this page:

Argus stores no part of your external ID. That is deliberate and it is described in the deployment overview: the control plane cannot hold a secret that grants access to your account. It also means Argus cannot rotate it for you, and there is no field for it in Settings. The friction is the security property, not a gap.

Your trust policy accepts exactly one external ID at a time unless you tell it otherwise. Change it in AWS first and every scan fails until the agent catches up.

  1. Widen the trust policy to accept BOTH values. Edit the role’s trust policy so the sts:ExternalId condition accepts the old value and the new one. The agent is still presenting the old one and keeps working throughout.
  2. Update the agent with the new value, and recreate the container (see below).
  3. Verify a scan completes. Run one from the dashboard and wait for it to finish, not just to start. A queued job proves nothing.
  4. Narrow the trust policy to the new value alone. This step is the rotation; until you do it the old value still works.

A container’s environment is fixed when it is created. docker restart reuses the old environment and the agent carries on presenting the old external ID, which reads exactly like the rotation not having worked.

Terminal window
# docker run: remove and re-run with the new value
docker rm -f argus-agent
docker run -d --name argus-agent -e AWS_EXTERNAL_ID='<new-value>' ... argus-agent:latest
# compose: recreate the service
docker compose up -d --force-recreate argus-agent

AWS requires the external ID to match [\w+=,.@:/-]*, and rejects anything else with a ValidationError. The failure that is easy to miss is quote characters picked up by a copy and paste: the value looks right in a terminal and is wrong to AWS.

Terminal window
# Length, and any character AWS will not accept. Expect the second line to be empty.
printf %s "$AWS_EXTERNAL_ID" | wc -c
printf %s "$AWS_EXTERNAL_ID" | tr -d '[:alnum:]_+=,.@:/-'

If the second command prints anything at all, the value carries a character AWS will reject. Quotes and a trailing newline are the two that show up in practice.

In the dashboard both look the same: scans stop, and the agent stays green. What distinguishes them is the AWS error code, and today that is visible in the agent’s own container logs:

Terminal window
docker logs argus-agent 2>&1 | grep "Failed to assume role"
# Failed to assume role: ClientError (aws error code: AccessDenied)
Error code What it means Fix
AccessDenied The value the agent presented is not one your trust policy accepts. Usually a rotation where the agent was not updated, or was restarted instead of recreated. Re-check steps 1 to 3 above.
ValidationError The value is malformed and AWS rejected it before checking the trust policy. It never got as far as your trust policy. Run the character check above.

If you cannot reach the container logs, work through the steps above in order regardless. The character check is quick and rules out the malformed case outright, which leaves the trust policy as the thing to re-check.

Rotate from Cloud Accounts > the account’s menu > View enrollment token > Rotate token. The new token is shown once. Rotation is a platform-side act with an agent-side consequence, and the console only performs the first half:

What rotation does immediately. The previous token is revoked: any new bootstrap with it is refused (HTTP 401, “enrollment token invalid, expired, or revoked”). Agents that already exchanged it for their own API key are unaffected and keep polling; nothing on the Agents page changes.

What it does later, and this is the part to plan for. Every deployment still holds the old token, and any agent that bootstraps again will present it. When that happens the agent’s bootstrap is refused, it idles and retries every ten minutes, and its row on the Agents page reads Offline with nothing on it about the token.

Deployment When it bootstraps again How to carry the new token
Cloud managed: Fargate On every task start: the task has no volume, so a restart, a scale event or the weekly stable refresh re-enrols from the stack’s secret Two steps, in order: (1) update the stack with the new EnrollmentToken parameter, which rewrites the secret and rolls nothing; then (2) force a new deployment (aws ecs update-service --cluster <cluster> --service <service> --force-new-deployment) so a task starts against the new secret. Without step 2 the agent stays Offline until the next refresh, up to seven days
Cloud managed: EC2 Only if the persisted key at /var/lib/argus/agent.key is lost or revoked. The token is baked into the instance’s /etc/argus/agent.env at launch A stack update does not carry it: the parameter changes the secret and the instance is not replaced. Rewrite ENROLLMENT_TOKEN in /etc/argus/agent.env over SSM or SSH and restart the agent service, or delete and recreate the stack
Local: Compose, docker run Only if the persisted key is lost, or the container is recreated without its volume Recreate the container with the new ENROLLMENT_TOKEN (docker compose up -d --force-recreate); a restart reuses the old environment

Rotate when a token has leaked, or when you want every future bootstrap to go through a token you control the rate limit on. If nothing has leaked and your agents are running, there is nothing to rotate for: an agent’s day-to-day credential is its per-container API key, which has its own rotation with a grace window.