arizuko › products › AWS DevOps agent — setup
AWS DevOps agent — setup
Deploy an SRE agent that reads your runbooks and drives AWS with each engineer’s own keys: instance, egress allowlist, a shared fallback role, per-operator credentials, and IAM that caps the blast radius.
prerequisites
- A host with Docker, ports 80/443 reachable behind a TLS terminator (or run with
WEB_HOSTon a reverse proxy that handles certs). - The
arizukobinary and thearizuko-antagent image built locally (make build,sudo make images,sudo make agent). - Anthropic API access for the in-container agent.
- A Telegram bot (BotFather) for the oncall channel. Slack or Discord is optional for the team thread.
- At least one AWS IAM user or role you can mint access keys for — a shared team key to start, ideally a personal key per engineer.
1. seed the instance
arizuko create acme --product aws-devops
cd /srv/data/arizuko_acme
$EDITOR .env
arizuko create --product aws-devops seeds
/srv/data/arizuko_acme/ with the product applied: the
main group gets the aws-devops CLAUDE.md
overlay (whose-keys awareness, read-before-write, egress reality,
incident flow) and the skills bash, commit,
find, oracle, diary,
issues, users. It also generates
AUTH_SECRET and SECRETS_KEY — leave
those alone; SECRETS_KEY is what encrypts every AWS key at
rest.
2. fill in .env
AWS credentials are not here — they live in the secrets
table (steps 4–5). .env holds only platform anchors:
# Identity + web
ASSISTANT_NAME=argus
WEB_HOST=argus.example.com # public host for /dash, /auth/login, /dash/me/secrets
AUTH_SECRET=… # pre-generated by arizuko create
SECRETS_KEY=… # pre-generated; encrypts AWS keys at rest
# Model
ANTHROPIC_API_KEY=sk-ant-… # in-container agent
# Oncall channel (primary)
TELEGRAM_BOT_TOKEN=123456:ABC… # BotFather — alerts + destructive-action confirmations
# Team channel (optional)
SLACK_BOT_TOKEN=xoxb-…
SLACK_SIGNING_SECRET=…
# OAuth so engineers self-serve their own keys (optional)
GITHUB_CLIENT_ID=…
GITHUB_CLIENT_SECRET=…
See reference / env for the full list and the daemon that reads each one.
3. open AWS egress
The agent container is default-deny — it can reach nothing until
you allowlist a host. crackbox matches subdomains, so a single
amazonaws.com entry covers every regional endpoint
(ec2.us-east-1.amazonaws.com, s3.amazonaws.com,
sts.amazonaws.com, …):
arizuko network acme allow main amazonaws.com
arizuko network acme allow main pypi.org
arizuko network acme allow main files.pythonhosted.org
The two pypi hosts let the agent fetch the AWS CLI and
boto3 on demand via uvx. Drop them if you bake
awscli into a custom agent image instead. A rule at
main cascades to child folders, so
main/prod and main/staging inherit it. Confirm:
arizuko network acme list main
4. shared team fallback role
Set folder-scoped AWS keys as the break-glass credential — used for any caller who hasn’t set their own. Keep this key’s IAM policy tight (read-first, no destructive):
arizuko secret acme set main AWS_ACCESS_KEY_ID --value AKIA…
arizuko secret acme set main AWS_SECRET_ACCESS_KEY --value …
arizuko secret acme set main AWS_DEFAULT_REGION --value us-east-1
arizuko secret acme list main
Values are AES-256-GCM encrypted at rest under SECRETS_KEY.
routd decrypts them at spawn and injects them as the container’s
env for that turn; they never touch .env or disk in the
clear.
5. per-operator keys (the point)
Each engineer sets their own AWS keys. Their keys shadow the folder fallback for the turns they trigger — so their calls run as them and land in CloudTrail under their IAM principal. Two ways:
self-serve (recommended)
The engineer signs in at
https://<WEB_HOST>/auth/login (GitHub/Google/Discord),
which links their chat identity to a web login, then pastes their keys at
https://<WEB_HOST>/dash/me/secrets:
AWS_ACCESS_KEY_ID, AWS_SECRET_ACCESS_KEY, and
optionally AWS_SESSION_TOKEN / AWS_DEFAULT_REGION.
operator sets them
Before an engineer has logged in, the operator can seed a user-scoped
key by their sub (find it at /dash/ →
Users, e.g. telegram:user/7):
arizuko user-secret acme set telegram:user/7 AWS_ACCESS_KEY_ID --value AKIB…
arizuko user-secret acme set telegram:user/7 AWS_SECRET_ACCESS_KEY --value …
arizuko user-secret acme list telegram:user/7
6. IAM caps the blast radius
arizuko decides who the agent runs as; IAM decides what that identity may do. Scope each key’s policy so read-first actions are wide and destructive actions are narrow or denied. A starting shape for a personal key:
{
"Version": "2012-10-17",
"Statement": [
{ "Sid": "ReadWide", "Effect": "Allow",
"Action": ["ec2:Describe*","elasticloadbalancing:Describe*",
"autoscaling:Describe*","cloudwatch:Get*","cloudwatch:List*",
"logs:Get*","logs:FilterLogEvents","rds:Describe*",
"s3:List*","s3:GetObject","sts:GetCallerIdentity"],
"Resource": "*" },
{ "Sid": "NoNukes", "Effect": "Deny",
"Action": ["ec2:TerminateInstances","rds:DeleteDBInstance",
"s3:DeleteBucket","iam:*"],
"Resource": "*" }
]
}
Widen per engineer as trust allows. Because the call runs under the engineer’s key, CloudTrail records the action against their principal — change control gets a real name, not a shared bot.
7. run the instance
arizuko run acme
This generates the Docker Compose file under
/srv/data/arizuko_acme/ and brings the stack up. For
production, install a systemd unit and deploy with
sudo systemctl restart arizuko_acme; see
getting started for the unit template.
8. route the oncall channel
Map your Telegram chat to the main folder so DMs and
mentions reach the agent. In the root agent or via /dash/
→ Routing:
seq match target
-2 chat_jid=telegram:*/dm/* main
-1 chat_jid=telegram:*/group/* verb=mention main
For a Slack team thread as well, add the Slack adapter (see the Slack team setup for the app + scopes) and route its channel JID to the same folder. One folder, many channels — the agent doesn’t care which surface a message arrives on.
9. seed runbooks
The agent answers from facts/. Drop one Markdown file per
topic into the group folder — a service map, alert definitions,
and a runbook per failure mode:
$EDITOR /srv/data/arizuko_acme/groups/main/facts/service-map.md
$EDITOR /srv/data/arizuko_acme/groups/main/facts/alerts.md
$EDITOR /srv/data/arizuko_acme/groups/main/facts/rds-failover.md
Edit these over WebDAV at
https://<WEB_HOST>/dav/main/facts/ too. Without
facts/ the agent can only reason from the live AWS state it
reads — useful, but it won’t know your procedures.
10. verify
sudo systemctl status arizuko_acme(ordocker compose ps) shows all services up.- DM the bot
@argus who am I running as?— it runsuvx --from awscli aws sts get-caller-identityand reports the ARN. Set your own user key first, then confirm the ARN is your principal, not the folder fallback’s. - Ask
@argus list our ec2 instances— a read-only call should succeed and cite the region. - Ask for something destructive — it should propose the command and wait, not run it.
- From the container, a non-allowlisted host must 403 on every path.
A quick check:
@argus curl -sI https://example.com/returns a crackbox 403;https://ec2.us-east-1.amazonaws.comconnects.
11. isolate prod from staging
Give each environment its own folder so keys, egress, and grants don’t cross. Add a child group per environment and route its channel there:
arizuko group acme add 'telegram:*/group/-100PROD' main/prod --product aws-devops
arizuko group acme add 'telegram:*/group/-100STAGING' main/staging --product aws-devops
# environment-specific keys — prod keys never reach the staging folder
arizuko secret acme set main/prod AWS_DEFAULT_REGION --value us-east-1
arizuko secret acme set main/staging AWS_DEFAULT_REGION --value us-west-2
Each child runs its own container with its own DB view and egress
allowlist — the main/staging agent can’t see or
touch main/prod. Full model:
primitives and the
security page.
common issues
- Every AWS call is
AccessDenied. - That’s IAM, not arizuko. The resolved key lacks the action.
Check
aws sts get-caller-identityto see which principal is active, then widen that key’s policy — or the caller set no user key and is falling back to the tight folder role. - A host 403s on every path.
- crackbox is refusing the host, not the site rejecting auth. Add it
with
arizuko network acme allow main <host>and restart. A real auth gate gives mixed codes (200 on public paths); an egress block is a uniform 403. - The agent runs as the folder key, not mine.
- Your user-scoped key isn’t set for the
subthat sent the message. Checkarizuko user-secret acme list <sub>; thesubmust match the sender identity (e.g.telegram:user/7), which you can read at/dash/→ Users. uvx awsis slow the first time.- It resolves
awsclifrom pypi on first use. Keeppypi.org+files.pythonhosted.orgallowlisted, or bakeawscliinto a custom agent image for an instant cold start.
go deeper
- AWS DevOps agent — intro — what the team sees
- secrets — folder vs user scope, the BYOA overlay
- grants — the
bashgrant and action gating - crackbox — default-deny egress
- CLI reference —
secret,user-secret,network,group - security model — tenant boundary, ephemeral containers, secret injection