arizuko

arizukoproducts › 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

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
Attribution tip: the keys resolve for whoever sends the triggering message. For clean one-human attribution, point each engineer at a DM with the bot or a linked web identity, so “the caller” is always one known person. In a shared incident channel it still runs as the sender of each message — the per-turn container never outlives the turn, so keys don’t bleed between people.

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

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-identity to 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 sub that sent the message. Check arizuko user-secret acme list <sub>; the sub must match the sender identity (e.g. telegram:user/7), which you can read at /dash/ → Users.
uvx aws is slow the first time.
It resolves awscli from pypi on first use. Keep pypi.org + files.pythonhosted.org allowlisted, or bake awscli into a custom agent image for an instant cold start.

go deeper