Коротко. Аккаунт, пользователь и ключ созданы — но S3 отвечает 403 AccessDenied даже на листинг бакета. Причина почти всегда одна: у бакета нет resource policy. Ключи без неё не дают доступа, сколько бы прав ни было у пользователя. Resource policy назначается на бакет через службу IAM (PutResourcePolicy), а не правкой модели и не системным ключом.
1. Две разные вещи: права пользователя и политика ресурса
В ActiveScale доступ к S3 складывается из двух слоёв, и путать их нельзя:
| Слой | Где живёт | Что задаёт |
|---|---|---|
| Identity | IAM: аккаунты, пользователи, группы, политики | кто ты и что тебе разрешено в принципе |
| Resource policy | на конкретном ресурсе (бакете) | кто и что может делать с этим бакетом |
Ключ доступа — это идентификатор пользователя, а не пропуск к данным. Если на бакете нет политики
ресурса, запрос отклоняется независимо от того, что пользователь существует и ключ активен.
Отсюда типовой симптом: aws s3 ls отвечает AccessDenied, хотя ключи «только что созданы и
точно верные».
Важно: аккаунты и пользователи живут не в модели, а в службе IAM (gRPC). Поэтому
/model/account/ не существует, а GET к IAM без параметров отвечает 400 — служба жива и ждёт
корректный запрос. Не ищите пользователей в /model/.
2. IAM-домен: обязательное условие
IAM-вызовы работают по FQDN, не по IP-адресу. У S3 и у IAM — разные домены, и это разделение не косметическое: на узле есть карта хостов, которая маршрутизирует запрос либо в S3-шлюз, либо в службу IAM.
Механизм:
- Конфигурация шлюза содержит отдельный параметр для IAM-домена (в наших сборках —
iam_domain_set), а рядом — карту вида «хост → IAM-бэкенд». - Если в карту попадает S3-домен, весь HTTPS-трафик с этим
Hostуходит в IAM. Клиент вместо S3 получает400с текстомInvalidAction ... NO_VERSION_SPECIFIED— это ответ IAM-REST, а не S3. - Лечится разделением: в карте — только IAM-домен, в параметре IAM — только он же. S3-домена в карте быть не должно.
- Карта читается при старте шлюза: правка файла без перезапуска службы фронта не действует. Перезапускать нужно только службу фронта, не весь стек.
Проверка различия — по коду ответа на один и тот же путь с разным Host:
curl -sk -o /dev/null -w '%{http_code}\n' https://s3.<DOMAIN>/
curl -sk -o /dev/null -w '%{http_code}\n' https://iam.<DOMAIN>/
Ожидается: S3-хост → 307 (штатный редирект фронта), IAM-хост → 400 (ответ IAM на пустой
запрос). Если S3-хост отдаёт 400 InvalidAction — домен попал в IAM-карту.
3. Resource policy на бакете
Назначается вендорским путём через службу IAM. В инженерной консоли это один вызов:
iam = cli._connection_factory.create_iamd_connection()
iam.PutResourcePolicy(resourceArn='arn:aws:s3:::<BUCKET>',
resourcePolicyDocument=<json>, expectedResourceId='')
Три детали, на которых спотыкаются:
- Имя поля — именно
resourcePolicyDocument, неpolicyи неdocument. expectedResourceIdпередаётся пустой строкой при создании политики. Семантика непустого значения в нашей практике не проверялась — уточнять в документации версии, а не угадывать.- Пустой ответ
{}— это успех, а не ошибка. Не ждите текста «OK».
Документ — JSON в языке политик AWS (иллюстрация структуры, не дословный шаблон вендора; готовый документ брать из документации версии или у поддержки):
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Principal": {"AWS": "arn:aws:iam::<ACCOUNT_ID>:user/<USER>"},
"Action": ["s3:ListBucket", "s3:GetObject", "s3:PutObject"],
"Resource": ["arn:aws:s3:::<BUCKET>", "arn:aws:s3:::<BUCKET>/*"]
}
]
}
Проверять после выдачи надо не «настройку», а реальный доступ: листинг и round-trip объекта.
Вендорский способ без установки клиента — cli.s3_connect('<ACCESS_KEY>') с
validate=False, затем PUT и GET пробного объекта. Подробнее —
CLI sherpa.
Чего не делать. Обходить политику системным ключом служебного бакета — это не ремонт:
такой доступ не переживёт следующую проверку и ломает картину прав. Служебный бакет по своей
политике разрешает только запись (PutObject); GET/DELETE к нему отвечают 403 штатно.
4. Условия и переменные политик (с 7.1)
С версии 7.1 в IAM появились conditions и переменные политик:
- условия ограничивают доступ по IP-адресу источника, по времени, по тиру Glacier и по длительности Object Lock;
- переменные работают как подстановки — например, префикс бакета может быть равен имени пользователя, и один шаблон политики обслуживает много пользователей.
Практический смысл: одна политика на бакет вместо ручной политики на каждого пользователя, и ограничение доступа «только из нужной сети» без отдельного шлюза.
Ограничения, которые надо учитывать:
- Условия по IP работают только при FQDN-доступе; запрос «по IP» в IAM не проходит в принципе.
- Условия по времени чувствительны к часам: расхождение времени на узле ломает и подпись, и оконные условия.
- Тир Glacier в условиях имеет смысл только при настроенном холодном хранении и lifecycle.
Если политика с условиями «не срабатывает», проверять надо три вещи по порядку: домен (FQDN),
время на узле и фактический Host в запросе. Это разные слои, и симптом у них один — 403.
5. Порядок выдачи доступа
Штатная последовательность для нового пользователя:
CreateAccount— отдельный аккаунт (лимит ключей у системного аккаунта — 2, поэтому пользователей разносят по аккаунтам, а не лепят ключи на системный).CreateUser— пользователь в этом аккаунте.CreateAccessKey— пара ключей; секрет показывается один раз, хранить вне репозитория.PutResourcePolicy— политика на бакет с этим пользователем вPrincipal.
Через вендорский CLI прикладного уровня это выглядит короче:
ascli s3 account access-key generate, ascli account quota show (квота аккаунта). ascli —
отдельный инструмент, не замена sherpa: см. CLI sherpa.
Для проверки списка аккаунтов и пользователей в консоли:
from google.protobuf.json_format import MessageToDict
iam = cli._connection_factory.create_iamd_connection()
MessageToDict(iam.ListAccounts(marker='', onlyIfQuotaExceeded=False,
nameOrEmailRegex='', maxItems=100, allowDisabled=True))
ListUsers использует oneof: передаётся ровно одно из accountId / accountCanonicalId /
accountEmail, остальные — пустыми. Ошибка про missing oneof arguments — подсказка, какие поля
заполнить.
6. Проверка и профилактика
Признаки, что всё сделано правильно:
- листинг бакета с клиентским ключом →
200и список объектов; - PUT/GET пробного объекта →
200, тело совпадает; - S3-хост →
307, IAM-хост →400(разные домены, разные ответы); - в событиях нет
IAMD_HEALTH_FAILUREв момент проверки.
Профилактика:
- Политика — часть приёмки бакета, а не «допишем потом». Новый бакет без resource policy выглядит как сломанный S3.
- Один пользователь — один аккаунт, если нужны независимые ключи и квоты.
- Ключи не переиспользовать между сервисами: отдельная пара на интеграцию упрощает отзыв.
- Домены S3 и IAM не смешивать; после любой правки карты доменов — перезапуск службы фронта и проверка обоих хостов.
- NTP держать в норме:
403из-за времени выглядит как проблема прав, но лечится часами. - MFA для администраторов (с 7.2) — для UI, CLI и REST; это не заменяет resource policy, но закрывает доступ к управлению.
7. Источники
- Quantum Software Documentation Center (7.3) (2026-11-07)
- Release Notes 7.3 — IAM conditions/variables (7.1), IAM на scaled-out нодах и удаление последнего IAM-домена (7.2), MFA (7.2)
- ActiveScale Z200 datasheet — IAM, квоты аккаунтов
- Наша практика (обезличенно):
PutResourcePolicyчерез IAM gRPC, разделение S3/IAM-доменов и проверка round-trip на кластере из трёх нод, ActiveScale 7.3.1.2. - Обновлено: 2026-11-07.