Открыть бакет на чтение, но запретить листинг

mc anonymous set download заодно разрешает перечислять содержимое бакета — кто угодно увидит все ключи; нужна своя политика с одним GetObject

mc anonymous set download s3/mybucket
mc anonymous get-json s3/mybucket
cat > /tmp/getonly.json <<'EOF'
{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Effect": "Allow",
      "Principal": {"AWS": ["*"]},
      "Action": ["s3:GetObject"],
      "Resource": ["arn:aws:s3:::mybucket/*"]
    }
  ]
}
EOF
mc anonymous set none s3/mybucket
mc anonymous set-json /tmp/getonly.json s3/mybucket
curl -s -o /dev/null -w '%{http_code}\n' 'https://s3.example.com/mybucket?list-type=2'
curl -s 'https://s3.example.com/mybucket/file.txt'

mc anonymous set download звучит как «только скачивание», но выдаёт анониму ещё и s3:ListBucket. Проверено на живом MinIO — get-json после этой команды возвращает:

{"Action":["s3:GetBucketLocation","s3:ListBucket"],"Effect":"Allow",
 "Principal":{"AWS":["*"]},"Resource":["arn:aws:s3:::testb"]}

Практическое следствие: запрос GET /bucket?list-type=2 без всякой авторизации отдаёт 200 и полный перечень ключей. Для бакета со статикой это выкладывает наружу карту всего, что там лежит, — включая файлы, на которые нигде нет ссылок.

Со своей политикой из одного GetObject тот же запрос отдаёт 403, а объект по прямому адресу по-прежнему читается — то, что и нужно раздаче статики через CDN, где фронт ходит в хранилище без подписи.

Порядок важен: сначала set none, потом set-json. Политики не складываются, но сброс перед применением избавляет от сюрприза, если на бакете уже висело что-то, выданное раньше.

Проверять результат нужно именно снаружи и без ключей. С настроенным алиасом mc ls покажет содержимое всегда — он ходит подписанным, и по нему нельзя судить о правах анонима. И следите за слэшем: GET /bucket?list-type=2 работает, а /bucket/?list-type=2 может не дойти вовсе.