README: the mail user boundary rollout, in order
Set CARLOS_MAIL_USER_BOUNDARY everywhere first, IAM step 1, roll, run the sweep, check every /carlos-mail/ user, then IAM step 2.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
1 file changed,
+10
−4
README.md+10 −4
diff --git a/README.md b/README.md| index 940768627..497ee1b62 100644 |
| --- a/README.md |
| +++ b/README.md |
| @@ -500,10 +500,16 @@ This release stops every box and the console from reading the whole deployment b |
| This release creates every email sending user (IAM path `/carlos-mail/`) under a permissions boundary, the way store users already are. Without one, anything that can act as the console's role can create a user on that path, give it an administrator policy and mint it a key. |
| -- a console with `CARLOS_MAIL_REGION` set now refuses to start without `CARLOS_MAIL_USER_BOUNDARY`. set it to the ARN of an IAM managed policy that allows `ses:SendRawEmail` and nothing else. |
| -- create that policy and give the console role `iam:PutUserPermissionsBoundary` on `user/carlos-mail/*` before this rolls. condition it on `iam:PermissionsBoundary` equal to the boundary ARN, or the role can set any boundary it likes. never grant `iam:DeleteUserPermissionsBoundary`. nothing calls it. |
| -- existing users are fixed lazily. a user without the boundary, or with a different one, gets the configured one the next time the console ensures it: `carlos email enable` for the app, a new standalone credential, or a domain change that refreshes the app's policies. until then it stays unbounded. list the ones still missing it with `aws iam get-user` over `aws iam list-users --path-prefix /carlos-mail/`. |
| -- once every console runs this release and every existing user carries the boundary, condition the role's `iam:CreateUser` and `iam:PutUserPolicy` on `iam:PermissionsBoundary` too. the console sets the boundary before it writes the policy, so the condition does not block the backfill. |
| +A console with `CARLOS_MAIL_REGION` set refuses to start on this release without `CARLOS_MAIL_USER_BOUNDARY`. Its value is the ARN of a customer managed policy in the same account, `arn:aws:iam::<12-digit account>:policy/<name>`, that allows `ses:SendRawEmail` and nothing else. A console without `CARLOS_MAIL_REGION` needs nothing. |
| + |
| +Roll it in this order: |
| + |
| +1. create the boundary policy. set `CARLOS_MAIL_USER_BOUNDARY` to its ARN in every console's environment before any console gets this release. an older console ignores the variable. this release refuses to start without it. |
| +2. apply IAM step 1 to the console role. add `iam:PutUserPermissionsBoundary` on `user/carlos-mail/*`, conditioned on `iam:PermissionsBoundary` equal to the boundary ARN. without that condition the role can set any boundary it likes. add `iam:ListUsers` for the sweep in step 4. AWS does not scope `ListUsers` to a path, so it is granted on `Resource "*"`. it only reveals user names and paths, and the sweep asks only for `/carlos-mail/`. the role already has `iam:GetUser` on the path. never grant `iam:DeleteUserPermissionsBoundary`. nothing calls it. |
| +3. roll the console. from now on every new sending user gets the boundary. an existing user gets it the next time the console touches it: enable, a standalone credential, a domain change, and key rotation too, since no key is minted for a user without the boundary. |
| +4. run the sweep, which also reaches users nothing will touch again. set `CARLOS_MAIL_BOUNDARY_SWEEP=1` on one console and restart it. it logs `set the boundary on <user>` for each user it fixes. its last line is either `all N mail users bounded` or the users that are not, with why. fix any failure and run it again. then remove the variable. |
| +5. check it yourself. for every user in `aws iam list-users --path-prefix /carlos-mail/`, `aws iam get-user --user-name <user>` must show `PermissionsBoundary` with the boundary ARN. |
| +6. apply IAM step 2. condition the role's `iam:CreateUser` and `iam:PutUserPolicy` on `iam:PermissionsBoundary` equal to the boundary ARN. do this only after every console runs this release, because an older console creates users without the boundary and step 2 refuses them. |
| ## App config & secrets |