Entra ID и управление доступом (RBAC)

Кто вы такой, что вам можно и на каком именно ресурсе — три вопроса, на которые в Azure отвечают Entra ID и RBAC.

Microsoft Entra ID (бывший Azure AD) отвечает на вопрос «кто ты» — это каталог удостоверений. Azure RBAC отвечает на вопрос «что тебе можно» — это система ролей поверх ресурсов. Первое без второго бесполезно, второе без первого невозможно.

Зачем это на практике

Пока в подписке один человек — вы, — вопрос доступа кажется надуманным. Но проект растёт: приходит стажёр, подключается CI/CD-пайплайн, аналитику нужны логи, приложению — файлы из Storage. И вот тут начинается: «дай ему Owner, чтоб не мучиться». А через месяц стажёр удаляет ресурсную группу prod, потому что она была рядом с dev и называлась похоже.

Второй сюжет — секреты. Приложению нужно читать из Storage. Самый простой путь: взять ключ доступа и положить в переменную окружения. Ключ утекает в git, в логи, в скриншот в чате. Правильный путь: у приложения есть собственное удостоверение, и Azure выдаёт ему токен без единого пароля. Это называется managed identity, и это одна из лучших вещей в Azure.

Кто может быть субъектом доступа

В терминах Azure субъект называется security principal. Их четыре вида:

ТипЧто этоКогда использовать
Userживой человек с учётной записью в Entra IDразработчики, админы
Groupгруппа пользователейвсегда, когда людей больше одного
Service principalудостоверение приложения с секретом или сертификатомвнешние системы, CI/CD вне Azure
Managed identityудостоверение ресурса Azure, пароля нет вообщевсё, что работает внутри Azure

Managed identity бывает двух видов. System-assigned привязана к конкретному ресурсу: создаётся вместе с ним и удаляется вместе с ним. User-assigned — отдельный ресурс, который можно назначить сразу нескольким приложениям и который переживает пересоздание виртуалки. Для продакшена с IaC чаще берут user-assigned: назначения ролей не приходится переделывать после каждого terraform destroy.

# отдельное удостоверение, живущее своей жизнью
az identity create --resource-group rg-shop --name id-shop-api

# или включить system-assigned identity у веб-приложения
PRINCIPAL_ID=$(az webapp identity assign \
  --resource-group rg-shop \
  --name app-shop-api \
  --query principalId -o tsv)

echo "Object ID удостоверения: $PRINCIPAL_ID"

Роли: три классических и много точных

Роль — это набор разрешений вида Microsoft.Storage/storageAccounts/read. Три встроенные роли знает каждый:

РольЧто можноЧего нельзя
Readerсмотреть конфигурацию ресурсовменять что-либо; читать данные внутри Storage/Key Vault
Contributorсоздавать, менять и удалять ресурсыназначать роли другим
Ownerвсё то же плюс назначать роли

Обратите внимание на строчку «Contributor не может назначать роли» — это специально сделано, чтобы Contributor не мог тихо повысить себя до Owner. Если человеку нужно раздавать доступы, но не нужно всевластие, есть роль Role Based Access Control Administrator.

И самое важное для повседневной работы: помимо трёх широких ролей в Azure есть сотни узких встроенных ролей, и почти всегда найдётся та, что подходит идеально. Storage Blob Data Reader — читать блобы, но не трогать сам аккаунт. Key Vault Secrets User — читать секреты, но не создавать хранилища. AcrPull — скачивать образы из реестра контейнеров. Именно из этих ролей и складывается принцип наименьших привилегий: выдаём ровно то, что нужно для работы, и ни разрешением больше.

Область (scope): где именно действует роль

Каждое назначение роли живёт на конкретном уровне иерархии, и роль наследуется вниз:

Management group
  └── Subscription
        └── Resource group
              └── Resource (конкретный Storage, VM, Key Vault)

Contributor на подписке — это Contributor на каждой ресурсной группе и на каждом ресурсе в ней. Отменить наследование нельзя: Azure RBAC аддитивен, разрешения складываются, «вычитающих» правил в обычном режиме нет. Отсюда практическое правило: назначайте роль на минимально возможном уровне. Приложению нужен один Storage — давайте роль на этот Storage, а не на ресурсную группу «чтобы наверняка».

SCOPE=$(az storage account show --name stshopfiles --resource-group rg-shop --query id -o tsv)

az role assignment create \
  --assignee-object-id "$PRINCIPAL_ID" \
  --assignee-principal-type ServicePrincipal \
  --role "Storage Blob Data Reader" \
  --scope "$SCOPE"

# проверяем, кому и что выдано на этом ресурсе
az role assignment list --scope "$SCOPE" -o table

Флаг --assignee-principal-type ServicePrincipal лучше указывать явно: без него CLI лезет в Entra ID за уточнением типа и на свежесозданном удостоверении может промахнуться из-за задержки репликации каталога.

Как это работает

Внутри всё построено на OAuth 2.0 и токенах. Когда приложение с managed identity хочет обратиться к Storage, происходит вот что:

  1. Приложение делает локальный HTTP-запрос к эндпоинту удостоверения — на виртуалке это 169.254.169.254 (IMDS), в App Service — адрес из переменной IDENTITY_ENDPOINT. Никаких паролей: платформа знает, какой ресурс её спрашивает.
  2. В ответ приходит JWT-токен от Entra ID, в котором записан oid — идентификатор удостоверения — и для какого ресурса токен выписан (audience).
  3. Приложение шлёт этот токен в Storage. Storage проверяет подпись токена и спрашивает у RBAC: «у принципала с таким oid есть право .../blobs/read на этот контейнер?»
  4. Если есть — данные отдаются. Если нет — 403 Forbidden.

Из этой механики следуют две вещи, о которые все спотыкаются. Во-первых, токен кэшируется (обычно до часа), а изменения назначений ролей распространяются не мгновенно — до нескольких минут, иногда дольше. Выдали роль, а приложение всё ещё получает 403? Подождите и перезапустите его, прежде чем чинить то, что не сломано.

Во-вторых, есть разделение на control plane и data plane. Control plane — это операции с самим ресурсом (создать, удалить, посмотреть настройки), их обслуживает Azure Resource Manager. Data plane — это операции с данными внутри ресурса (прочитать блоб, достать секрет). Роль Reader относится к control plane: она позволит увидеть, что Storage существует, но не даст прочитать ни одного файла. Для данных нужны отдельные роли вроде Storage Blob Data Reader. Это, пожалуй, самый частый источник недоумения у новичков в Azure.

Частые ошибки

  • Owner для CI/CD-пайплайна. Скомпрометированный токен сборки с ролью Owner на подписке — это конец. Пайплайну почти всегда достаточно Contributor на одной ресурсной группе.
  • «Дам Contributor, пусть настроит доступы». Не сможет: Contributor не назначает роли. Нужен Owner или RBAC Administrator.
  • Роли на людей вместо групп. Через год вы не вспомните, кому и что выдавали, а при увольнении будете вычищать назначения по всей подписке. Назначайте роли на группы Entra ID.
  • Ключи вместо удостоверений. Ключи доступа Storage и строки подключения — это пароли, которые не истекают и не аудируются. Managed identity бесплатна и решает проблему целиком.
  • Путаница ролей Entra и ролей Azure. Global Administrator в Entra ID — это власть над каталогом (пользователи, приложения), а не над подпиской. Пока он не включит переключатель «Access management for Azure resources», ресурсов он не увидит.
  • Осиротевшие назначения. Удалили виртуалку с system-assigned identity — в списке ролей останется запись Identity not found. Чистите их: это мусор, который мешает аудиту.

Про деньги: сам RBAC, назначения ролей, managed identity и базовый Entra ID Free ничего не стоят. Платные вещи — это лицензии Entra ID P1/P2 (порядка 6–9 $ за пользователя в месяц), которые нужны для Conditional Access и PIM (выдача роли на время, с подтверждением). Для учебного проекта они не нужны, для боевой команды — почти обязательны.

Итоги

  • Entra ID хранит удостоверения, Azure RBAC раздаёт права на ресурсы — это две разные системы, и роли у них разные.
  • Для всего, что работает внутри Azure, используйте managed identity: пароля нет, значит, нечему утекать.
  • Owner раздаёт роли, Contributor — нет, Reader смотрит конфигурацию, но не данные.
  • Роль наследуется вниз по иерархии и складывается с другими; назначайте её на минимальном уровне.
  • Control plane и data plane — разные миры: чтобы читать данные, нужна data-роль вроде Storage Blob Data Reader.
  • Изменения ролей и кэш токена дают задержку — не паникуйте от 403 в первые минуты.
Проверьте себя
1. Приложению выдали роль Reader на аккаунт Storage. Оно пытается скачать файл из контейнера и получает 403. Почему?
AReader — роль control plane: она позволяет видеть ресурс, но не читать данные внутри него
BРоль назначена на неправильный scope, надо назначить на подписку
CReader работает только для пользователей, но не для managed identity
DНужно подождать сутки, пока роль применится
2. Какое утверждение о роли Contributor верно?
AContributor может создавать и удалять ресурсы, но не может назначать роли другим
BContributor может всё то же, что Owner, только в пределах одной подписки
CContributor может назначать роли, но не может удалять ресурсы
DContributor даёт доступ только на чтение