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.