ADG Оставить заявку
Блог Информационная безопасность 5 мин чтения

GitHub Actions: пинируем сторонние actions по SHA и ограничиваем GITHUB_TOKEN

GitHub Security Lab публикует рекомендации по безопасности Actions. Вводим pin по SHA-коммиту, permissions block для GITHUB_TOKEN и self-hosted runners в изолированной сети.

Контекст момента

GitHub Security Lab публикует рекомендации по безопасности GitHub Actions: компрометация через actions третьих сторон с широкими правами GITHUB_TOKEN

GitHub Security Lab на прошлой неделе выпустил документ с разбором векторов атак на GitHub Actions. Ничего принципиально нового там нет - люди в теме про это говорили давно - но теперь это официальный разбор от команды безопасности GitHub, и ссылаться стало удобнее. Мы в июле переводили клиентов на Actions и настраивали self-hosted runners - так что посмотрели на все свои workflows свежим взглядом. Картина оказалась местами некомфортной.

Что конкретно беспокоит Security Lab

Основной вектор - actions третьих сторон с доступом к GITHUB_TOKEN. Actions - это чужой код, который запускается в контексте вашего репозитория. Если action скомпрометирован (через компромат аккаунта автора, через зависимость action-а), он получает всё, что получил бы сам токен: читает секреты, пишет в репозиторий, публикует релизы, постит комментарии от имени вашего CI.

Второй момент - ссылки по тегу или ветке. Когда workflow пишет uses: actions/checkout@v2 - это ref, который может переехать. Автор action может перетегировать v2 на другой коммит. Это не гипотетический сценарий: supply chain attack через npm и PyPI работает ровно так же, мы разбирали это месяц назад.

Что сделали: pin по SHA-коммиту

Первое изменение - самое механическое. Везде, где в workflows стояла ссылка по тегу, заменили на ссылку по полному SHA коммита:

# Было
- uses: actions/checkout@v2
- uses: docker/login-action@v1
- uses: aws-actions/amazon-ecr-login@v1

# Стало
- uses: actions/checkout@a81bbbf8298c0fa03ea29cdc473d45769f953675
- uses: docker/login-action@f054a8b539a109f9f41c372932f1ae047eff08c9
- uses: aws-actions/amazon-ecr-login@aaf69d68aa3fb14c1d5a6be9ac61fe15b48d050f

Неудобно? Да. SHA в diff-е выглядит некрасиво и непонятно какой версии соответствует. Мы добавляем комментарий рядом:

- uses: actions/checkout@a81bbbf8298c0fa03ea29cdc473d45769f953675 # v2.3.4

Так хотя бы читаемо. SHA фиксирует конкретное состояние кода action-а - если кто-то перепишет историю тега, workflow это не почувствует.

Для первопартийных action-ов GitHub (actions/checkout, actions/setup-node и т.д.) риск немного ниже - они под контролем самого GitHub. Но мы пинируем и их: привычка должна быть единообразной.

Следующий шаг: permissions block для GITHUB_TOKEN

По умолчанию GITHUB_TOKEN в GitHub Actions имеет широкие права: может писать в репозиторий, создавать issues, управлять релизами. Для большинства workflows это избыточно.

GitHub анонсировал permissions block - его планируется указывать на уровне всего workflow или отдельного job-а:

permissions:
  contents: read
  packages: read

jobs:
  build:
    permissions:
      contents: read
      statuses: write   # только для этого job-а
    steps:
      ...

Принцип простой: явно указываешь минимальный набор прав, которые нужны. Workflow, который только читает код и запускает тесты, не должен иметь права писать в репозиторий. Workflow для публикации пакета в registry нуждается в packages: write, но не в issues: write.

Матрицу уже составили: что делает каждый workflow и какие права ему реально нужны. В большинстве случаев достаточно contents: read. Расставим ограничения как только фича станет доступна в продакшне.

Self-hosted runners в изолированной сети

Это решение, к которому мы шли независимо от Security Lab - просто теперь у него появилось дополнительное обоснование. Для клиентских проектов с чувствительными данными мы и так разворачивали self-hosted runners, которые не имеют прямого выхода в интернет.

Схема такая: runner находится в выделенном VLAN-е без исходящего доступа в публичный интернет. Из этой сети доступны только внутренние ресурсы - artifact storage, container registry, deployment targets. Сам runner связывается с GitHub через outbound HTTPS - это достаточно для регистрации и получения заданий. Но если скомпрометированный action попытается что-то отправить наружу - он упрётся в firewall.

Это не защищает от всего. Action всё равно может читать секреты, которые переданы ему в workflow, и использовать их внутри job-а. Но вектор «action ворует секрет и отправляет на внешний C2» - закрыт.

GitHub.com
    |
    | HTTPS outbound (outgoing only)
    |
+---v------------------+
| Runner VLAN          |
| (no inbound, no www) |
+--+-------------------+
   |
   | internal only
   v
Nexus / Registry / Deploy targets

Для публичных репозиториев с pull request-ами от форков это особенно важно: Actions по умолчанию может запускать workflows из форков с ограниченным GITHUB_TOKEN, но всё равно запускает код форка на runner-е. Self-hosted runners для публичных репозиториев - отдельная тема с отдельными рисками, там мы их не используем.

Состояние на сейчас

Внутренние репозитории и клиентские проекты на self-hosted runners - переведены на SHA-пин. Для репозиториев на GitHub-hosted runners сделали аудит и зафиксировали матрицу прав - расставим permissions block, когда фича выйдет. SHA-пин там тоже нужен - это следующий проход.

Автоматизировать проверку помог бы Dependabot - он умеет следить за версиями actions и предлагать обновления. Но он работает с тегами, а не с SHA - придётся либо скриптовать проверку вручную, либо ждать когда инструменты подтянутся к новой практике. Пока - ручной аудит при каждом изменении workflow.

Если у вас есть GitHub Actions в продакшне - аудит конфигурации workflows занимает полдня и часто обнаруживает неочевидные избыточные права. Это не паранойя, это стандартный принцип DevSecOps, просто перенесённый в CI.

Контакт

Нужна такая же инженерная работа?

Опишите задачу и контекст. Ответим в течение рабочего дня, при необходимости подпишем NDA.