Материал

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:production
  • aud — аудитория токена, например sts.amazonaws.com
  • iss — issuer: https://token.actions.githubusercontent.com
  • repository — имя репозитория
  • ref — Git ref (ветка, тег)
  • environment — GitHub Environment, если он объявлен в workflow
  • job_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

Операционный порядок, проверенный на практике:

  1. Инвентаризация: найти все места, где в CI/CD хранятся long-lived credentials — CI-переменные, repository secrets, runner-конфигурации, Vault static secrets.
  2. Определить, какой облачный провайдер или secrets backend поддерживает OIDC (AWS — да; GCP — да, через WIF; Azure — да, через Workload Identity Federation; Vault — да, через JWT/OIDC auth).
  3. Создать OIDC-провайдер в облаке (один раз на аккаунт/проект).
  4. Для каждой операции создать отдельную IAM-роль с минимальными permissions и жёсткой trust policy (sub, aud, iss — все три).
  5. Обновить workflow: добавить permissions: id-token: write, заменить action, убрать secret из переменных.
  6. Добавить GitHub Environment для prod-деплоев с required reviewers.
  7. Удалить (а не просто «не использовать») старые ключи и service account JSON.
  8. Настроить CloudTrail / Cloud Audit Logs для мониторинга AssumeRole событий.
  9. Задокументировать trust policy как IaC (Terraform/Pulumi) и ревьюить её изменения так же, как код.
  10. Провести 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/

FAQ

Быстрые вопросы по теме

Что такое OIDC?

OIDC — это протокол аутентификации, основанный на OAuth 2.0.

Как OIDC улучшает безопасность CI/CD?

OIDC устраняет необходимость в долгосрочных секретах, предоставляя краткосрочные токены.

Что такое trust policy в AWS?

Это политика, определяющая, какие репозитории могут запрашивать доступ к ресурсам.

Как настроить OIDC для GitHub Actions?

Необходимо зарегистрировать OIDC провайдера в AWS и создать соответствующую trust policy.

Почему стоит использовать OIDC вместо статических ключей?

OIDC снижает риск утечек, так как токены действуют только во время выполнения job.

Нужен такой же контроль в ваших изменениях и релизах?

Покажем пример такого замечания на вашем сценарии и соберём пилотный контур без лишней суеты.

Запросить пилот Все статьи
Дальше по теме

Ещё материалы

Все статьи
27 апреля 2026 18 мин AI

GitHub Actions, Dockerfile, миграции и скрипты: невидимая зона риска в AI‑кодинге

Пока ваш ревью сфокусирован на бизнес логике, атаки приходят через .yml файлы, Dockerfile и хелпер скрипты, которые никто не дочитывает до конца TL;DR AI инструменты ускорили разработку, но риск сместился во вспомогател…

Открыть материал
04 мая 2026 11 мин безопасность

Dependency Confusion: как коллизия имён в реестрах превращается в RCE и что с этим делать

Подзаголовок: Механика private/public name collision, реальные взломы и практический список защит — от naming конвенций до review политики для каждого PR TL;DR Dependency confusion — атака на supply chain: злоумышленник…

Открыть материал
02 мая 2026 18 мин безопасность

Как читать lock‑файл перед merge и не пропустить риск

Lock файл — это не шум на фоне кода. Это карта доверия к вашей зависимости. И именно через неё чаще всего приходит supply chain атака. TL;DR Lock файлы package lock.json, yarn.lock, poetry.lock фиксируют три вещи: верси…

Открыть материал