OIDC и секреты облаков
Security-версия OIDC: claims, subject templates, cloud trust policies, environment protection, short-lived tokens и замена статических ключей.
OIDC и секреты облаков
OIDC в GitHub Actions — это способ дать workflow доступ к облаку без долгоживущего cloud key в secrets. Вместо AWS_ACCESS_KEY_ID, AZURE_CLIENT_SECRET или JSON key от service account job запрашивает у GitHub короткоживущий OIDC JWT, отдаёт его cloud provider’у, а cloud provider проверяет claims и выдаёт временные credentials.
Это не «секрет стал безопаснее храниться». Секрета в GitHub больше нет. Security-модель переносится в cloud trust policy: какому repo, branch, environment, workflow или reusable workflow можно получить роль. Поэтому OIDC тесно связан с Permissions и GITHUB_TOKEN, Environments и protection rules, Deployment workflows и Secure use и threat model.
sequenceDiagram
participant Job as GitHub Actions job
participant GitHub as GitHub OIDC provider
participant Cloud as Cloud provider STS/IAM
participant Resource as Cloud resource
Job->>GitHub: Request OIDC JWT (id-token: write)
GitHub-->>Job: Signed JWT with iss/aud/sub/claims
Job->>Cloud: Exchange JWT for cloud credentials
Cloud->>Cloud: Validate signature, aud, sub, environment/ref
Cloud-->>Job: Short-lived access token
Job->>Resource: Deploy using temporary credentialsЧто происходит при deploy
Минимальный поток такой: job получает право id-token: write, cloud login action просит у GitHub OIDC token, cloud provider валидирует подпись и claims, затем выдаёт короткоживущий access token для конкретной роли или service account.
name: deploy
on:
push:
branches: [main]
permissions:
id-token: write
contents: read
jobs:
deploy:
runs-on: ubuntu-24.04
environment: production
steps:
- uses: actions/checkout@v6
- name: Configure AWS credentials via OIDC
uses: aws-actions/configure-aws-credentials@v5
with:
role-to-assume: arn:aws:iam::123456789012:role/github-prod-deploy
aws-region: eu-central-1
- run: ./scripts/deploy.shid-token: write выглядит тревожно, но это не write-доступ к репозиторию и не доступ к облаку сам по себе. Это только разрешение job запросить OIDC JWT у GitHub. Реальный доступ появляется позже, если cloud trust policy решит: «да, этот JWT соответствует моей роли».
Claims: что именно проверяет cloud
OIDC token — это signed JWT с claims о запуске workflow. Ключевые поля:
iss: issuer, для GitHub Actions этоhttps://token.actions.githubusercontent.com;aud: audience, напримерsts.amazonaws.comдля AWS илиapi://AzureADTokenExchangeдля Azure;sub: subject — главная строка, по которой обычно ограничивают repo, ref или environment;repository,repository_id,repository_owner,ref,workflow_ref,job_workflow_ref,environment,runner_environmentи другие claims.
Практически важный вывод: policy не должна доверять всему issuer’у GitHub. GitHub — общий identity provider для огромного количества репозиториев. Если cloud role принимает любой token от token.actions.githubusercontent.com, вы построили публичную дверь. Нужны условия на aud и sub, а для больших org — ещё на reusable workflow, repository id или custom properties.
Для AWS trust policy обычно выглядит так:
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Principal": {
"Federated": "arn:aws:iam::123456789012:oidc-provider/token.actions.githubusercontent.com"
},
"Action": "sts:AssumeRoleWithWebIdentity",
"Condition": {
"StringEquals": {
"token.actions.githubusercontent.com:aud": "sts.amazonaws.com",
"token.actions.githubusercontent.com:sub": "repo:octo-org/octo-repo:environment:production"
}
}
}
]
}Такой sub означает: роль можно получить только job из octo-org/octo-repo, который ссылается на environment production. Если job не указывает environment: production, claim не совпадёт и AWS STS не выдаст credentials.
Почему environment важнее branch-only policy
Можно ограничить роль branch’ем:
"token.actions.githubusercontent.com:sub": "repo:octo-org/octo-repo:ref:refs/heads/main"Это лучше, чем wildcard, но для production часто слабее, чем связка с environment. Branch говорит только «запуск пришёл из main». Environment добавляет GitHub-side gate: required reviewers, wait timer, branch/tag restrictions, environment secrets и deployment history. В результате cloud trust policy проверяет sub, а GitHub environment решает, можно ли job вообще дойти до sensitive deploy stage.
Хороший production-паттерн:
jobs:
deploy-prod:
if: github.ref == 'refs/heads/main'
environment: production
permissions:
id-token: write
contents: read
runs-on: ubuntu-24.04
steps:
- uses: actions/checkout@v6
- uses: azure/login@8c334a195cbb38e46038007b304988d888bf676a
with:
client-id: ${{ secrets.AZURE_CLIENT_ID }}
tenant-id: ${{ secrets.AZURE_TENANT_ID }}
subscription-id: ${{ secrets.AZURE_SUBSCRIPTION_ID }}
- run: az webapp deploy --name app-prod --resource-group rg-prodВ Azure при OIDC всё ещё часто хранятся client-id, tenant-id, subscription-id как GitHub secrets или vars. Это не cloud password: это идентификаторы Entra application или managed identity. Секрет, который раньше назывался AZURE_CLIENT_SECRET, больше не нужен, если настроена federated identity credential.
Subject templates и reusable workflows
В enterprise-сценариях repo-level policy быстро становится шумной: десятки сервисов, одинаковый deploy, разные environments. Здесь помогают Reusable workflows. GitHub добавляет claim job_workflow_ref, по которому cloud может проверить не только «какой repo запустил deploy», но и «через какой централизованный deploy workflow он прошёл».
Пример идеи для subject template:
repo:octo-org/payments-api:environment:production:job_workflow_ref:octo-org/platform/.github/workflows/deploy.yml@refs/heads/mainЭто сильнее, чем разрешить каждому repo писать свой cloud login как угодно. Platform team может держать pinning actions, audit logging, artifact validation и deployment summary в одном reusable workflow. Команды вызывают его через workflow_call, а cloud trust policy требует именно этот путь.
Есть нюанс: cloud providers отличаются. AWS в официальной GitHub docs отмечен как provider без поддержки custom claims, поэтому там чаще приходится работать через aud и sub. Google Cloud и некоторые secret managers позволяют более гибкие attribute mappings. В любом случае порядок внедрения одинаковый: сначала настройте cloud condition, потом меняйте workflow или subject template. Иначе новые tokens начнут выпускаться в формате, который cloud ещё не принимает.
Миграция со статических ключей
Безопасная миграция не начинается с YAML. Сначала выпишите, где сейчас лежат cloud secrets: repo secrets, org secrets, environment secrets, secrets в reusable workflow, переменные CI в старой системе. Затем для каждого deploy создайте отдельную cloud role или service account с минимальными правами. Не переносите один «super deploy key» в одну «super OIDC role».
Практический порядок:
1. Создать OIDC identity provider или workload identity federation в cloud.
2. Создать роль/service account с минимальными permissions.
3. Добавить trust condition на aud и конкретный sub.
4. В GitHub job добавить permissions: id-token: write и cloud login action.
5. Привязать production job к environment с protection rules.
6. Запустить dry run или non-prod deploy.
7. Удалить старый static secret и отозвать ключ в cloud provider.
Не оставляйте старый ключ «на всякий случай» в repository secrets. Если он остаётся рабочим, реальная поверхность атаки почти не уменьшилась.
Типовые ошибки
Самая частая ошибка — слишком широкий subject:
"StringLike": {
"token.actions.githubusercontent.com:sub": "repo:octo-org/*"
}Иногда это выглядит удобно для monorepo или org-wide deploy, но фактически любой repo в org может получить роль, если совпадёт остальная логика. Для production лучше сузить до repo + environment или repo + reusable workflow.
Вторая ошибка — давать OIDC job untrusted code. Если workflow запускается на PR из fork и затем получает cloud credentials, вы вернулись к проблеме из pull_request_target и untrusted PR, только вместо GITHUB_TOKEN теперь рискуете cloud role. Deploy с OIDC должен идти из trusted ref, после merge, release tag или manual approval.
Третья ошибка — считать OIDC заменой всех secrets. OIDC хорошо заменяет cloud access keys. Но Docker registry password, API token стороннего SaaS, signing key или private npm token могут оставаться обычными secrets, если сервис не поддерживает federation. Для них всё ещё нужны Variables, env и secrets, masking, rotation и минимальный scope.
- Что реально разрешает `permissions: id-token: write`?
- Какой `sub` обычно безопаснее для production deploy?
- Что нужно сделать со старым `AWS_SECRET_ACCESS_KEY` после успешной миграции на OIDC?
See also
- Secure use и threat model — общая модель угроз для tokens, secrets, logs и third-party actions.
- Permissions и GITHUB_TOKEN — почему
id-token: writeне заменяет обычныеpermissions. - Environments и protection rules — approval, branch restrictions и environment secrets перед production deploy.
- Deployment workflows — как строить deploy jobs вокруг environments и deployment history.
- Reusable workflows — как централизовать cloud login и policy-compliant deploy.
- pull_request_target и untrusted PR — почему privileged credentials нельзя выдавать коду из PR.
- Runner governance — как runner isolation влияет на безопасность cloud credentials.
Внешние справки: GitHub Docs по OpenID Connect, OIDC reference, OIDC в AWS/Azure/GCP и OIDC with reusable workflows; AWS IAM docs по trust policy для shared OIDC providers; Microsoft Learn по Azure Login with OIDC.
Источники
- GitHub Docs — OpenID Connect
- GitHub Docs — OpenID Connect reference
- GitHub Docs — Configuring OpenID Connect in Amazon Web Services
- GitHub Docs — Configuring OpenID Connect in Azure
- GitHub Docs — Configuring OpenID Connect in Google Cloud Platform
- GitHub Docs — Using OpenID Connect with reusable workflows
- AWS IAM User Guide — Create a role for OpenID Connect federation
- Microsoft Learn — Use the Azure Login action with OpenID Connect
