Do you actually need this?

Probably not. Answer one question: Is the S3 bucket in the same AWS account as the credentials the agent uses? “The credentials the agent uses” means the account your access_key_id belongs to, or the account of the EC2 instance role if the agent runs on EC2 without keys.
These are two different settings.bucket_arn: which bucket to read. arn:aws:s3:::my-logs. Always required in bucket-polling mode.role_arn: an identity to assume. arn:aws:iam::123456789012:role/AtatusLogReader. Only on this page’s cross-account setup.

What Atatus configures in AWS: nothing

Worth stating plainly, because it differs from most SaaS integrations. The agent runs on your machine and reads your bucket. The role is assumed between two accounts you control, so there is no Atatus AWS account to trust and no Atatus-issued external ID, so you choose both sides. The only thing that reaches Atatus is the log data, authenticated with the license key already in atatus.yml.

How it works

  1. The agent authenticates with whatever resolves first: the access keys in your config, or the EC2 instance role when there are none.
  2. Those credentials are used for one call only: sts:AssumeRole.
  3. AWS returns temporary credentials for the role.
  4. Every S3 and SQS call is signed with those, and the agent refreshes them before they expire.
Nothing belonging to the bucket’s account is ever written to the agent machine. Access is revoked by editing the trust policy, not by rotating a key you hold.

Terminology used below

Two accounts, and it matters which is which: Get both IDs before you start:
Pick an external ID too, which is any hard-to-guess string you invent, for example atatus-7f3c1b. It is not a secret AWS issues; it is a value you set on both sides.

Step 1: Read policy (bucket account)

What the role is allowed to do. Save as read-policy.json, replacing the bucket name:
Read-only: the agent never writes to your bucket and never deletes an object. Using SQS notifications instead of bucket polling? Add the queue actions:

Step 2: Trust policy (bucket account)

Who is allowed to assume the role. This is the piece that is usually wrong when things fail. Save as trust-policy.json and put the agent account ID in Principal:
:root here does not mean the root user. It means “any principal in that account that has been granted sts:AssumeRole”, which is why Step 4 is also required. It is the normal way to write this.
Not using an external ID? Delete the whole Condition block and leave external_id out of the agent config too. Setting it on one side only makes the assume fail. To restrict further, condition on the session name the agent uses:

Step 3: Create the role (bucket account)

The Arn in the output is your role_arn:
  1. IAM → Roles → Create role
  2. Trusted entity type: AWS account → Another AWS account
  3. Enter the agent account ID
  4. Tick Require external ID and enter yours
  5. Skip permissions for now → name it AtatusLogReader → Create role
  6. Open the role → Add permissions → Create inline policy → JSON → paste the read policy from Step 1
  7. Copy the ARN shown at the top of the role page

Step 4: Allow the assume (agent account)

The role trusts the account; now the identity inside it needs permission. Attach to the IAM user whose access key is in the config, or to the EC2 instance role:

Step 5: Verify before touching the agent

Run this on the machine running the agent, with the credentials it will use. It should return a set of temporary credentials:
Then confirm the role can actually see the objects:
Open a new shell afterwards, or unset those three, so the rest of your session goes back to your own credentials. That last command also gives you the real key path for bucket_list_prefix, so copy it from the output rather than guessing.
Do not skip this step. If the assume fails, the agent reports it in its log and collects nothing, and that looks identical to “no logs have arrived yet”.

Step 6: Configure the agent

Add role_arn and external_id to the stanza in /etc/atatus-infra-agent/conf.d/s3.d/s3.yml:
The same two lines work for any family. VPC Flow Logs only differ in source: and the prefix:
On EC2, leave the access keys out entirely. The instance role assumes the target role, and no long-lived key is ever written to disk. Just make sure Step 4 is attached to the instance role.
Note that bucket_list_prefix contains the bucket account ID, because that is whose logs are in the bucket.

Multiple accounts

One stanza per account, each with its own role. They collect in parallel:
An organisation trail writing every account into one bucket needs only a single stanza, and the account each event came from is preserved on the event itself.

Configuration reference

Setting external_id without role_arn is rejected at startup, because on its own it would be silently ignored.

Troubleshooting

Errors appear in the agent log at /var/log/atatus-infra-agent/. In the bucket owner’s CloudTrail, the agent’s assume appears with roleSessionName: atatus-infra-agent, which makes it easy to tell apart from anything else using the role.