OIDC вместо long-lived secrets в CI/CD: как убрать лишние ключи и выстроить least-privilege доступ
Почему static credentials — это системная проблема, и как workload identity federation меняет подход к безопасности пайплайнов
Почему ключ в секрете — это не решение
Стандартная практика подключения CI/CD к облаку выглядит примерно так: создаём service account или IAM user, генерируем ключ, кладём его в переменную окружения системы CI. Вроде бы всё под контролем. На самом деле нет.
Long-lived credentials — access keys, service account JSON, PAT-токены — остаются валидными месяцами и годами. Они копируются между проектами, утекают в логах, попадают в fork-репозитории, оседают в истории CI-переменных. Именно такой ключ оказался в публичном логе в инциденте с CircleCI в 2023 году, когда атакующие получили доступ к секретам клиентов через скомпрометированного сотрудника [1].
Проблема не в небрежности конкретной команды — она архитектурная. Если секрет существует физически, он может утечь. OIDC (OpenID Connect) меняет логику: вместо того чтобы хранить секрет, пайплайн доказывает свою идентичность через подписанный токен и получает краткосрочный доступ именно для этого запуска.
Это не просто удобнее — это принципиально другая модель угроз.
Как работает OIDC в CI/CD: механика за пять шагов
OpenID Connect — это слой поверх OAuth 2.0, добавляющий стандартизированный identity token в формате JWT [2]. В контексте CI/CD схема выглядит следующим образом.
Когда начинается job в GitHub Actions или GitLab CI, CI-платформа выступает в роли Identity Provider (IdP) и генерирует подписанный JWT-токен. Этот токен содержит claims — структурированные утверждения о контексте запуска: какой репозиторий, какая ветка, какое окружение, кто запустил workflow [3].
Раннер отправляет этот токен облачному провайдеру (AWS, GCP, Azure) или секрет-менеджеру (HashiCorp Vault). Провайдер скачивает публичный ключ CI-платформы с well-known JWKS endpoint, верифицирует подпись токена и проверяет claims против заранее настроенных условий trust policy. Если всё совпало — выдаёт краткосрочные credentials, которые действуют только для этого job-run [4].
Никакого постоянного секрета нет. Если токен утечёт — он истечёт вместе с job. Blast radius сведён к минимуму.
Ключевые claims в GitHub Actions OIDC-токене [5]:
sub— полная строка идентичности:repo:org/repo:ref:refs/heads/mainилиrepo:org/repo:environment:productionaud— аудитория токена, напримерsts.amazonaws.comiss— issuer:https://token.actions.githubusercontent.comrepository— имя репозиторияref— Git ref (ветка, тег)environment— GitHub Environment, если он объявлен в workflowjob_workflow_ref— ссылка на reusable workflow
GitLab CI формирует аналогичную структуру через keyword id_tokens в .gitlab-ci.yml, добавляя claims namespace_path, project_path, ref_protected, deployment_tier [6].
Практика: GitHub Actions → AWS
Разберём самый распространённый сценарий: деплой из GitHub Actions в AWS без хранения access key.
Шаг 1. Регистрация OIDC-провайдера в AWS
В AWS IAM нужно один раз создать OIDC Identity Provider:
Provider URL: https://token.actions.githubusercontent.com
Audience: sts.amazonaws.comНа уровне аккаунта AWS допускается только один такой провайдер для GitHub — он используется повторно для всех ролей [7].
Шаг 2. Trust policy с жёсткими условиями
Создаём IAM Role с trust policy. Критически важный момент: trust policy обязана содержать условие на sub. Без него любой репозиторий в GitHub может запросить credentials для этой роли — именно это является наиболее распространённой уязвимостью в OIDC-конфигурациях [8].
Пример для деплоя только из ветки main конкретного репозитория:
{
"Version": "2012-10-17",
"Statement": [{
"Effect": "Allow",
"Principal": {
"Federated": "arn:aws:iam::ACCOUNT_ID: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:myorg/myrepo:ref:refs/heads/main"
}
}
}]
}Для продакшн-деплоя через GitHub Environment лучше использовать:
"token.actions.githubusercontent.com:sub": "repo:myorg/myrepo:environment:production"Это позволяет дополнительно включить required reviewers в GitHub Environment — human gate перед деплоем в prod [9].
Шаг 3. Workflow в GitHub Actions
permissions:
id-token: write
contents: read
jobs:
deploy:
runs-on: ubuntu-latest
environment: production
steps:
- uses: actions/checkout@v4
- name: Configure AWS credentials
uses: aws-actions/configure-aws-credentials@v4
with:
role-to-assume: arn:aws:iam::ACCOUNT_ID:role/prod-deploy-role
aws-region: eu-west-1Две ключевые вещи: id-token: write обязательно (GitHub не генерирует OIDC-токен по умолчанию [3]), и permission лучше объявлять на уровне job, а не workflow — это ограничивает scope для каждого конкретного шага [10].
Практика: GitHub Actions → GCP
Google Cloud пошёл по пути Workload Identity Federation (WIF) — прослойки между внешними IdP и Service Account [11]. Схема чуть сложнее AWS, но принцип тот же.
Создаётся Workload Identity Pool, к нему подключается GitHub как OIDC Provider с issuer https://token.actions.githubusercontent.com. Настраивается attribute mapping — как claims из GitHub JWT переводятся в атрибуты GCP IAM. Затем Service Account привязывается к pool с условием на конкретный репозиторий.
Результирующий access token истекает через час. Никакой JSON-ключ service account в раннер не передаётся [11].
Google официально объявил service account key files deprecated для CI/CD-сценариев в 2023 году; в новых организациях создание ключей заблокировано по умолчанию [12].
В workflow используется google-github-actions/auth@v2:
- id: auth
uses: google-github-actions/auth@v2
with:
workload_identity_provider: 'projects/PROJECT_NUM/locations/global/workloadIdentityPools/POOL/providers/PROVIDER'
service_account: 'deploy-sa@PROJECT.iam.gserviceaccount.com'Для продакшна рекомендуется добавить attribute_condition с ограничением по ветке или окружению прямо в конфигурации Workload Identity Pool [13].
Практика: GitLab CI → AWS / GCP / Azure
GitLab реализовал OIDC через keyword id_tokens в .gitlab-ci.yml [14]:
deploy-production:
id_tokens:
GITLAB_OIDC_TOKEN:
aud: https://gitlab.example.com
script:
- aws sts assume-role-with-web-identity
--role-arn $ROLE_ARN
--role-session-name gitlab-$CI_PROJECT_ID-$CI_PIPELINE_ID
--web-identity-token $GITLAB_OIDC_TOKEN
--duration-seconds 3600Важный нюанс: старый CI_JOB_JWT_V2 deprecated с GitLab 15.9 и убран в GitLab 17.0. Использовать нужно именно id_tokens [15].
Для AWS trust policy sub claim GitLab формирует строку вида project_path:mygroup/myproject:ref_type:branch:ref:main — это позволяет ограничить доступ до конкретного проекта и ветки [16].
Для Azure аналогично: в Entra ID создаётся App Registration, добавляются Federated Identity Credentials с issuer = адрес GitLab-инстанса и subject = sub claim из GitLab JWT [17].
Практика: OIDC + HashiCorp Vault
OIDC с Vault даёт особенно гибкую модель: пайплайн аутентифицируется через OIDC, Vault выдаёт короткоживущий токен с доступом только к нужным путям secrets [18].
Настройка на стороне Vault:
vault auth enable jwt
vault write auth/jwt/config \
bound_issuer="https://token.actions.githubusercontent.com" \
oidc_discovery_url="https://token.actions.githubusercontent.com"
vault write auth/jwt/role/deploy-prod \
bound_audiences="https://github.com/myorg" \
bound_subject="repo:myorg/myrepo:environment:production" \
policies="deploy-prod-policy" \
ttl=60mПолитика Vault может быть хирургически точной — например, read-only к ops/data/production/db-password и ничему больше [19]. Это уровень grantularity, который с IAM-политиками труднее достичь.
Проектирование least-privilege: роли по операциям и окружениям
OIDC снимает проблему storage credentials, но не решает за вас вопрос, какие permissions получает каждый job. Здесь начинается настоящая работа по least-privilege.
Принципы, которые работают на практике [20]:
Разделение по операциям. Build-job не должен иметь прав на деплой. Scan-job не должен иметь доступа к production secrets. Принцип: одна роль — одна функция. Например, в AWS это могут быть отдельные IAM Role: ci-build-role (push в ECR, read из S3), ci-test-role (read из Parameter Store для тестовых конфигов), deploy-staging-role (обновление ECS-сервиса в staging), deploy-prod-role (только после approve через GitHub Environment).
Разделение по окружениям. Dev ≠ Stage ≠ Prod — не просто разные параметры, а разные роли с разными trust conditions [21]. Для prod trust policy должна ссылаться на конкретный GitHub Environment (например, environment:production), который сам настроен с required reviewers. Это даёт двойную защиту: облачный провайдер проверяет claims, GitHub проверяет, что деплой был approved.
Минимальный scope ресурсов. Роль для деплоя в S3 должна разрешать s3:PutObject только на конкретный bucket и конкретный prefix, а не s3: на . В GCP — Storage Object Creator только на нужный bucket, не Project Editor [22].
Короткий TTL сессии. AWS STS по умолчанию даёт 1 час; для большинства деплоев достаточно 15–30 минут. Чем короче — тем меньше blast radius при компрометации токена [23].
IAM Access Analyzer. В AWS можно автоматически анализировать CloudTrail-логи и генерировать минимально необходимые политики на основе реального использования. Это инструмент для рефинирования overly permissive ролей, которые создаются «с запасом» [24].
Типичные ошибки и атаки: что идёт не так с OIDC
Переход на OIDC не означает автоматической безопасности. Unit 42 (Palo Alto Networks) в докладе на DEF CON 32 (2024) описал три класса атак на OIDC в CI/CD [25]:
1. Отсутствие или слишком широкий sub claim.
Самая распространённая ошибка — trust policy без sub или с wildcard repo:MyOrg*. Любой пользователь GitHub может создать организацию MyOrgFake и репозиторий с подходящим именем, получив токен, который пройдёт проверку [8]. В AWS рекомендуется использовать StringEquals, а не StringLike там, где это возможно [9].
2. Namespace confusion в GitLab.
В GitLab claim namespace_path содержит путь namespace, но злоумышленник может создать аккаунт с именем, начинающимся как целевой namespace. Если attribute condition проверяет только начало строки или не использует namespace_id (числовой, неизменяемый), возможна атака через совпадение имени [26].
3. Poisoned Pipeline Execution (PPE) + OIDC.
Если в репозитории разрешены PR от форков с OIDC-токенами — атакующий с доступом к форку может получить credentials для основного репозитория. Именно такая уязвимость была обнаружена в CircleCI: fork workflows имели доступ к OIDC-токенам защищённых ресурсов. После её обнаружения CircleCI ввёл ограничения по умолчанию [25].
4. Слишком широкие IAM-роли.
OIDC убирает проблему storage credentials, но если роль, которую получает пайплайн, имеет iam: или s3: на "*" — компрометация job равна компрометации всего аккаунта. Wildcard policies — отдельная задача для аудита независимо от метода аутентификации.
5. Атаки на audience.
Если aud claim не специфичен (например, слишком общий URL), один и тот же токен может использоваться для аутентификации к нескольким сервисам. Рекомендация: aud должен быть максимально специфичным и уникальным для каждого целевого сервиса [19].
Когда OIDC не снимает проблему: пограничные случаи
OIDC не является универсальным решением для каждого сценария.
Self-hosted runners в изолированных сетях. Если GitLab-инстанс не имеет публичного доступа, облачный провайдер не может скачать JWKS для верификации токена. Нужно либо использовать кастомный issuer с доступным endpoint, либо загружать публичный ключ вручную [27].
Сервисы, не поддерживающие OIDC. Не все инструменты умеют принимать JWT напрямую. В таких случаях Vault как посредник — компромисс: пайплайн аутентифицируется в Vault через OIDC, Vault выдаёт short-lived секрет для legacy-системы [18].
Множество облаков и множество сервисов. При масштабировании на десятки репозиториев и несколько облаков конфигурация OIDC может стать сложной для управления. Часть команд решает это через Workload IAM — control plane поверх OIDC, который стандартизирует trust policy и TTL [21].
Dependabot и автоматические PR. GitHub поддерживает OIDC для Dependabot-jobs для аутентификации в приватных registry, что позволяет убрать статические credentials и для автоматических update-job [5].
Чеклист: переход с long-lived secrets на OIDC
Операционный порядок, проверенный на практике:
- Инвентаризация: найти все места, где в CI/CD хранятся long-lived credentials — CI-переменные, repository secrets, runner-конфигурации, Vault static secrets.
- Определить, какой облачный провайдер или secrets backend поддерживает OIDC (AWS — да; GCP — да, через WIF; Azure — да, через Workload Identity Federation; Vault — да, через JWT/OIDC auth).
- Создать OIDC-провайдер в облаке (один раз на аккаунт/проект).
- Для каждой операции создать отдельную IAM-роль с минимальными permissions и жёсткой trust policy (sub, aud, iss — все три).
- Обновить workflow: добавить
permissions: id-token: write, заменить action, убрать secret из переменных. - Добавить GitHub Environment для prod-деплоев с required reviewers.
- Удалить (а не просто «не использовать») старые ключи и service account JSON.
- Настроить CloudTrail / Cloud Audit Logs для мониторинга AssumeRole событий.
- Задокументировать trust policy как IaC (Terraform/Pulumi) и ревьюить её изменения так же, как код.
- Провести fire drill: отозвать один identity и убедиться, что именно этот pipeline остановился, а остальные — нет.
CI/CD-конфиги читают хуже всего: где здесь HighVibe
Есть характерная закономерность в том, как ревьюируются PR: бизнес-логика читается внимательно, а yaml-файлы пайплайнов — по диагонали. Изменение в trust policy или в permissions: блоке workflow может кардинально расширить доступ, и это останется незамеченным.
Именно этот разрыв — когда изменение в CI/CD-конфиге работает корректно технически, но несёт скрытый security-риск — закрывает HighVibe. Он анализирует конкретное изменение в контексте релиза: появился новый trust condition с wildcard? расширились permissions в workflow? добавилась новая cloud role без явного ограничения по ресурсам? Это именно тот сигнал, который помогает поймать проблему до merge, а не после инцидента.
Будущее: workload identity как стандарт
OIDC в CI/CD — уже состоявшаяся практика, а не эксперимент. Google deprecated service account keys в 2023 [12], AWS публично рекомендует OIDC вместо IAM users с access keys для пайплайнов. GitHub добавил поддержку custom claims и repository custom properties в OIDC-токенах [5] — это открывает возможности для attribute-based access control на уровне cloud trust policy.
Следующий шаг в этом направлении — интеграция OIDC с SPIFFE/SPIRE для выдачи cryptographic workload identity не только CI/CD-раннерам, но и микросервисам внутри кластера [28]. Это ведёт к модели, где каждый workload имеет проверяемую идентичность, а не разделяет credentials с соседями.
Для команд, которые ещё работают со static credentials в пайплайнах: переход на OIDC — не сложная архитектурная задача. Это несколько часов конфигурации, после которых целый класс угроз исчезает структурно. Вопрос не «делать или нет», а «в каком порядке».
Заключение
OIDC в CI/CD убирает long-lived credentials не за счёт лучшего хранения секрета, а за счёт отказа от секрета как такового. Пайплайн получает доступ потому, что он доказал свою идентичность через подписанный токен с claims о репозитории, ветке и окружении — и этот доступ исчезает вместе с job.
Переход требует аккуратной работы с двух сторон: правильно настроить trust conditions в облаке (sub, aud, iss без wildcards) и правильно спроектировать IAM-роли по принципу одна роль — одна операция — одно окружение. Оба шага влекут изменения в конфигурационных файлах, которые нужно ревьюировать так же строго, как прикладной код.
Что делать прямо сейчас: проверить, есть ли в вашем CI/CD long-lived credentials с правами на prod, и запустить процесс их замены на OIDC-федерацию — начиная с самого чувствительного пайплайна.
Источники
[1] CircleCI — "CircleCI Security Incident Report" — https://circleci.com/blog/jan-4-2023-incident-report/
[2] OpenID Foundation — "OpenID Connect Core 1.0" — https://openid.net/specs/openid-connect-core-1_0.html
[3] GitHub Docs — "About security hardening with OpenID Connect" — https://docs.github.com/en/actions/concepts/security/openid-connect
[4] GitHub Docs — "Configuring OpenID Connect in cloud providers" — https://docs.github.com/actions/deployment/security-hardening-your-deployments/configuring-openid-connect-in-cloud-providers
[5] GitHub Docs — "OpenID Connect reference" — https://docs.github.com/actions/reference/openid-connect-reference
[6] GitLab Docs — "OpenID Connect Authentication Using ID Tokens" — https://docs.gitlab.com/ci/secrets/id_token_authentication/
[7] Paul Duvall — "Securing CI/CD Pipelines with GitHub OIDC and AWS IAM" — https://www.paulmduvall.com/securing-ci-cd-pipelines-with-github-oidc-and-aws-iam/
[8] Palo Alto Networks — "OpenID Connect: Let's Talk Security" — https://www.paloaltonetworks.com/blog/cloud-security/openid-connect-oidc-security/
[9] GitHub Docs — "Configuring OpenID Connect in Amazon Web Services" — https://docs.github.com/en/actions/deployment/security-hardening-your-deployments/configuring-openid-connect-in-amazon-web-services
[10] GitHub Docs — "Secure use reference" — https://docs.github.com/en/actions/reference/security/secure-use
[11] Akhilesh Mishra / Living DevOps — "Keyless Authentication to Google Cloud from GitHub Actions with Workload Identity Federation" — https://medium.com/living-devops/keyless-authentication-to-google-cloud-from-github-actions-with-workload-identity-federation-in-a-fc4fc7527574
[12] computingforgeeks — "GCP Workload Identity Federation for GitHub Actions (2026)" — https://computingforgeeks.com/gcp-workload-identity-federation-github-actions/
[13] Firefly — "Setting Up Workload Identity Federation Between GitHub Actions and Google Cloud Platform" — https://www.firefly.ai/academy/setting-up-workload-identity-federation-between-github-actions-and-google-cloud-platform
[14] GitLab Docs — "Connect to cloud services" — https://docs.gitlab.com/ci/cloud_services/
[15] GitLab Docs — "Configure OpenID Connect in AWS to retrieve temporary credentials" — https://docs.gitlab.com/ci/cloud_services/aws/
[16] AWS Partner Network Blog — "Setting Up OpenID Connect with GitLab CI/CD to Provide Secure Access to Environments in AWS Accounts" — https://aws.amazon.com/blogs/apn/setting-up-openid-connect-with-gitlab-ci-cd-to-provide-secure-access-to-environments-in-aws-accounts/
[17] GitLab Docs — "Configure OpenID Connect in Azure to retrieve temporary credentials" — https://docs.gitlab.com/ci/cloud_services/azure/
[18] HashiCorp Developer — "Retrieve Vault secrets from GitHub Actions" — https://developer.hashicorp.com/validated-patterns/vault/retrieve-vault-secrets-from-github-actions
[19] GitHub Docs — "Configuring OpenID Connect in HashiCorp Vault" — https://docs.github.com/actions/deployment/security-hardening-your-deployments/configuring-openid-connect-in-hashicorp-vault
[20] CircleCI Blog — "Minimize risk using the Principle of Least Privilege and AWS IAM permissions" — https://circleci.com/blog/minimize-risk-using-the-principle-of-least-privilege-and-aws-iam-permissions/
[21] Identity Defined Security Alliance — "Do Your CI/CD Pipelines Need Identities? Yes." — https://www.idsalliance.org/blog/do-your-ci-cd-pipelines-need-identities-yes/
[22] Cycode — "Enhancing CI/CD Pipeline Security with OIDC Tokens for Cloud Authentication" — https://cycode.com/blog/enhancing-ci-cd-pipeline-security-with-oidc-tokens-for-cloud-authentication/
[23] Firefly — "Implementing OIDC Authentication in GitLab CI/CD for AWS Deployments" — https://www.firefly.ai/academy/implementing-oidc-authentication-in-gitlab-ci-cd-for-aws-deployments
[24] AWS re:Post — "Automate IAM Role Creation with Least Privilege by Integrating AWS IAM and CI/CD" — https://repost.aws/questions/QU2yhPpVexQhm4bk0YNhwJvw/automate-iam-role-creation-with-least-privilege-by-integrating-aws-iam-and-ci-cd-for-scalable-policy-as-code-and-continuous-compliance
[25] Unit 42 / Palo Alto Networks — "OH-MY-DC: OIDC Misconfigurations in CI/CD" — https://unit42.paloaltonetworks.com/oidc-misconfigurations-in-ci-cd/
[26] GBHackers — "Threat Actors Exploit CI/CD Environments to Gain Unauthorized Access to Restricted Resources" — https://gbhackers.com/threat-actors-exploit-ci-cd-environments-to-gain-unauthorized-access/
[27] Rohan Singh / Google Cloud Community — "Configure GCP Workload Identity Federation for GitLab" — https://medium.com/google-cloud/configure-gcp-workload-identity-federation-for-gitlab-c526e6eb0517
[28] SPIFFE — "Using SPIRE and OIDC to Authenticate Workloads to Retrieve Vault Secrets" — https://spiffe.io/docs/latest/keyless/vault/readme/