cat > /tmp/policy-ro.json <<'EOF'
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Action": ["s3:GetBucketLocation", "s3:ListBucket"],
"Resource": ["arn:aws:s3:::backup"]
},
{
"Effect": "Allow",
"Action": ["s3:GetObject"],
"Resource": ["arn:aws:s3:::backup/*"]
}
]
}
EOF
mc admin policy create s3 backup-ro /tmp/policy-ro.jsoncat > /tmp/policy-wo.json <<'EOF'
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Action": ["s3:PutObject"],
"Resource": ["arn:aws:s3:::artifacts/*"]
}
]
}
EOF
mc admin policy create s3 artifacts-wo /tmp/policy-wo.jsonmc admin policy attach s3 backup-ro --user app_ro
mc admin user svcacct add s3 app_ro --name app-ro-sa --policy /tmp/policy-ro.jsonmc admin policy info s3 backup-ro
mc admin user info s3 app_roКлючевая деталь, из-за которой политики пишут руками, а не берут готовый readwrite: Resource для операций над бакетом и над объектами — разные ARN. arn:aws:s3:::backup — сам бакет, arn:aws:s3:::backup/* — объекты в нём. ListBucket относится к первому, GetObject и PutObject — ко второму. Если перепутать, политика применится без ошибок и молча не будет работать.
Матрица, по которой удобно собирать:
| Режим | ListBucket | GetObject | PutObject | DeleteObject |
|---|---|---|---|---|
| read-only | да | да | нет | нет |
| read-write | да | да | да | да |
| write-only | нет | нет | да | нет |
У write-only ListBucket отсутствует намеренно: агент, который складывает артефакты или логи, не должен иметь возможности перечислить чужие. Это ровно тот случай, когда «дадим на всякий случай» превращает односторонний канал в утечку содержимого хранилища.
Сервисный аккаунт с --policy предпочтительнее пользователя с паролем: он наследует права родителя, ограничивается своей политикой и отзывается одной командой, не задевая остальных потребителей. Но родителем стоит делать пользователя с узкой политикой, а не admin — иначе «сервисный» ключ упирается в права root.