GitHub Actions и OpenBao: получаем секреты для развёртывания через OIDC
Секрет для развёртывания хранится в настройках репозитория. Он работает месяцами, переживает смену сотрудников и копируется в соседний проект «временно». Система знает значение пароля, но почти ничего не знает о задаче, которая его использует.
OIDC позволяет построить другой процесс. GitHub выдаёт выполняющемуся заданию короткоживущее подтверждение его происхождения. OpenBao проверяет репозиторий, ветку и среду, после чего выдаёт ограниченный токен доступа к нужному секрету. Постоянный токен OpenBao в настройках GitHub для этого не требуется.
Настроим учебную проверку: только задание из определённого репозитория, ветки main и среды production сможет прочитать тестовое значение. До настоящего развёртывания сначала докажем, что разрешённый запуск проходит, а посторонний получает отказ.
Содержание
- Кто кому доверяет
- Подготовим отдельный путь для проверки
- Политика читает ровно один секрет
- Ограничиваем роль репозиторием и средой
- Проверочный workflow без вывода секретов
- Обязательно проверьте отказ
- Как локализовать отказ, не раскрывая токен
- Подключаем настоящее развёртывание
- Если развёртывание оборвалось посередине
Отделите хранилище секретов от сборок
Для пилота OpenBao рассмотрите VPS MAATRIX с 2 vCPU и 4 GB памяти. Размещайте хранилище отдельно от runner, исполняющего код сборок.
ВЫБРАТЬ VPSКто кому доверяет
GitHub подписывает JWT со сведениями о запуске. OpenBao проверяет подпись, издателя, аудиторию, срок и заданные ограничения claims. После успешной проверки он применяет свою политику доступа.
Важно доверять конкретному контексту, а не любому токену GitHub. Правило «издатель правильный» без ограничений репозитория слишком широкое: GitHub обслуживает множество проектов. Названия репозитория и владельца полезны, но устойчивые числовые идентификаторы дополнительно защищают от неоднозначности при переименовании и повторном использовании имён.
Модель описана в документации GitHub OIDC. OpenBao использует для машинного входа роль типа JWT; это не интерактивный вход человека через браузер.
Прежде чем создавать роль, перечислите разрешённые события. Ручной выпуск из защищённой ветки и тест pull request — разные операции. Для среды production определите допустимые ветки, необходимость подтверждения и людей, которые вправе менять правила. Проверьте, может ли автор непроверенного изменения самостоятельно изменить workflow и обойти согласование.
Выделите отдельный безопасный процесс для внешних pull requests: ему не нужен производственный секрет даже после успешных тестов. Проверенный код выпускается другим заданием в разрешённом контексте. Такая граница должна одинаково отражаться в настройках GitHub и ограничениях роли OpenBao.
Подготовим отдельный путь для проверки
Понадобится работающая OpenBao с HTTPS, постоянным хранилищем и контролируемым административным доступом. У API должен быть маршрут, доступный выбранному runner. Не открывайте весь административный интерфейс миру только ради одного задания.
Для пилота на отдельном сервере можно рассмотреть MAATRIX Uab Start с 2 vCPU и 4 GB памяти. Если используется собственный runner рядом с приложением, отделяйте его от хранилища секретов: выполняемый код сборки имеет другой уровень доверия.
Поддерживаемая Ubuntu или Debian подойдёт для инфраструктуры, но готовность OpenBao определяется ещё и восстановлением, политиками, TLS и синхронизацией времени. Режим разработки здесь не годится. До интеграции проверьте перезапуск и процедуру разблокировки хранилища.
Создайте тестовый секрет в KV v2 по логическому пути secret/deploy/production. В примере он содержит поле probe. Используйте безобидное учебное значение; настоящий ключ развёртывания добавите после проверки границ доступа.
Политика читает ровно один секрет
Для KV v2 путь API чтения содержит дополнительную часть data. Сохраните политику в отдельном файле:
path "secret/data/deploy/production" {
capabilities = ["read"]
}
Загрузите её под именем deploy-read предусмотренной командой bao policy write. Политика не разрешает перечислять соседние пути, изменять секрет или настраивать auth-методы. Проверьте, что в этой среде действительно используется KV v2, а не другой движок с похожим названием.
Включите отдельный JWT auth mount, например github, чтобы не смешивать его с существующим входом сотрудников. Настройте discovery URL и bound issuer на https://token.actions.githubusercontent.com по руководству JWT OpenBao.
На новом, ещё не занятом пути настройки выглядят так. Если mount уже существует, сначала проверьте его владельца и конфигурацию, не повторяйте включение механически:
bao auth enable -path=github jwt
bao write auth/github/config \
oidc_discovery_url=https://token.actions.githubusercontent.com \
bound_issuer=https://token.actions.githubusercontent.com
Сохранённый HCL-файл политики, например deploy-read.hcl, загружается командой bao policy write deploy-read deploy-read.hcl. После применения прочитайте политику обратно и сравните путь. Ошибка в имени mount или версии KV часто выглядит как проблема OIDC, хотя аутентификация уже прошла успешно. Разделяйте диагностику входа и диагностику прав на данные: это два последовательных решения.
Административные команды выполняются отдельно от workflow. Задание развёртывания не должно иметь права менять собственную политику допуска — это было бы похоже на экзамен, где студент сам выставляет проходной балл.
Ограничиваем роль репозиторием и средой
Ниже пример тела роли. Замените организацию, репозиторий, числовой ID и домен своими значениями. В учебном варианте используется стандартный формат subject GitHub без пользовательских изменений:
{
"role_type": "jwt",
"user_claim": "sub",
"bound_audiences": ["https://bao.example.com"],
"bound_subject": "repo:ORG/REPO:environment:production",
"bound_claims": {
"repository_id": "123456789",
"ref": "refs/heads/main"
},
"token_policies": ["deploy-read"],
"token_ttl": 300,
"token_max_ttl": 600
}
Сохраните JSON как github-production.json и примените к роли:
bao write auth/github/role/production @github-production.json
Если в организации изменён формат subject или используются повторно вызываемые workflows, проверьте фактические claims и настройте более точную привязку. Для важных выпусков полезно ограничивать и доверенный workflow. Не копируйте правило из примера, не сопоставив его с собственным процессом.
Аудитория в запросе GitHub должна точно совпадать с разрешённой. Она обозначает получателя токена и помогает не принимать подтверждение, выпущенное для другой системы. Не используйте широкие шаблоны там, где известны точные значения.
Нужен сервер под эту задачу?
Разверните VPS MAATRIX за пару минут: NVMe, AMD EPYC, root-доступ, локации UK, США и РФ. Оплата картой РФ и по СБП.
АРЕНДОВАТЬ СЕРВЕРПроверочный workflow без вывода секретов
Создайте защищённую среду production и выполняйте тест только из согласованной ветки. В задании нужны разрешение id-token: write и доступ runner к вашему HTTPS API. Ниже целостная проверка чтения учебного поля; она ничего не развёртывает:
name: Check OpenBao identity
on: workflow_dispatch
jobs:
check:
runs-on: ubuntu-latest
environment: production
permissions:
id-token: write
contents: read
env:
BAO_ADDR: https://bao.example.com
steps:
- name: Read test secret
shell: bash
run: |
python3 - <<'PY'
import json, os, urllib.parse, urllib.request
base = os.environ['BAO_ADDR'].rstrip('/')
def request(url, headers=None, payload=None):
body = None if payload is None else json.dumps(payload).encode()
h = dict(headers or {})
if body is not None:
h['Content-Type'] = 'application/json'
req = urllib.request.Request(url, data=body, headers=h)
with urllib.request.urlopen(req, timeout=20) as response:
return json.load(response)
url = os.environ['ACTIONS_ID_TOKEN_REQUEST_URL']
url += '&audience=' + urllib.parse.quote(base, safe='')
oidc = request(url, {
'Authorization': 'Bearer ' + os.environ['ACTIONS_ID_TOKEN_REQUEST_TOKEN']
})['value']
login = request(base + '/v1/auth/github/login', payload={
'role': 'production', 'jwt': oidc
})
token = login['auth']['client_token']
print('::add-mask::' + token)
value = request(base + '/v1/secret/data/deploy/production', {
'X-Vault-Token': token
})['data']['data']
if 'probe' not in value:
raise RuntimeError('Test field missing')
print('The authorized job read the test field.')
PY
Домен в YAML также заменяется вашим. Проверка TLS остаётся включённой. Не добавляйте вывод переменных окружения и полный JSON ответа ради отладки: именно так короткоживущий секрет иногда превращается в долговечную запись журнала.
Обязательно проверьте отказ
Успешный запуск подтверждает лишь половину настройки. Повторите тест из другой ветки, без нужной среды и из отдельного учебного репозитория. Каждый неподходящий контекст должен получить отказ до чтения секрета.
Для проверки чужого пути используйте тот же разрешённый токен, но запросите другой учебный секрет. Ожидается запрет политики. Не расширяйте политику до secret/*, если один запрос не прошёл: сначала проверьте версию KV и точный путь.
Учитывайте особенности pull request и событий с повышенными правами. Не выдавайте production-доступ коду, который пришёл из недоверенного изменения. Защита workflow, зависимостей и действий остаётся важной даже при правильном OIDC.
Официальная инструкция настройки OIDC у провайдеров объясняет разрешение выдачи ID-токена и построение доверия. Право получить токен само по себе не даёт доступ к секрету: решение принимает OpenBao.
Как локализовать отказ, не раскрывая токен
Разберите ошибку по этапам. Если GitHub не выдаёт удостоверение, проверьте разрешение задания и наличие ожидаемого контекста запуска. Если ответ получен, но OpenBao отклоняет вход, сравните issuer, audience, subject, идентификатор репозитория и ветку с ролью. Отдельно проверьте время на сервере и доступ OpenBao к данным обнаружения и ключам издателя.
Если вход прошёл, а чтение запрещено, проблема обычно находится уже в политике или пути KV. Сравните именно mount и наличие части data; не расширяйте права до всех секретов ради диагностики. Если чтение успешно, но поле отсутствует, проверьте структуру учебного значения и версию движка.
В отчёт достаточно записать этап, код ответа и идентификатор запуска. Полный JWT, токен OpenBao и содержимое секрета не требуются. Разбор по трём границам быстрее, чем последовательное отключение всех проверок, и оставляет рабочую модель доверия неизменной.
Подключаем настоящее развёртывание
После проверки передавайте секрет только тому шагу, которому он нужен, на минимальный срок. Не записывайте его в артефакты сборки, Docker-слои или общий файл, который потом загружается как результат CI.
Если инструменту требуется файл, создавайте его с ограниченными правами, удаляйте после использования и учитывайте аварийное завершение. На постоянных self-hosted runners очистка особенно важна: рабочая директория может пережить несколько заданий.
Короткий токен OpenBao закрывает только участок входа в хранилище. Если прочитанный пароль базы живёт годами, его срок от этого не изменится. Поэтому опишите две сущности: чем workflow подтверждает происхождение и какие полномочия получает затем.
Для первого рабочего внедрения выберите одну операцию, например обращение к служебному API выпуска. Ограничьте получаемый секрет этой операцией, предусмотрите отзыв и не передавайте его в контейнер приложения, если во время работы он не нужен. Для сервисов с поддержкой динамических учётных данных следующим этапом можно сократить срок уже самого доступа, а не только токена хранилища.
Настройте аудит входов и чтения, предупреждения об отказах и сроки токенов. При недоступности OpenBao выпуск должен остановиться с понятной ошибкой, а не переходить на забытый запасной пароль в репозитории.
Если развёртывание оборвалось посередине
Истечение токена не отменяет уже выполненное изменение. Если образ скачан, а переключение приложения не состоялось, система останется в промежуточном состоянии независимо от того, насколько аккуратно был устроен вход. Поэтому сценарий выпуска должен проверять текущее состояние и безопасно повторять шаги.
Запишите в журнал идентификатор выпуска, digest образа и результат проверки работоспособности. Храните предыдущую конфигурацию и понятный способ возврата. При откате не пытайтесь воскресить старый OIDC-токен: новый разрешённый запуск получает новое короткое удостоверение. Такой подход разделяет восстановление приложения и восстановление доступа, не превращая секреты в неофициальный архив релизов.
Назначьте владельца этой интеграции. Он должен знать, где меняются ограничения репозитория, кто следит за сертификатом OpenBao и как отозвать доступ при переносе проекта. Без владельца даже короткоживущие токены постепенно окружаются долговечными исключениями, которые никто не берётся удалить.
Готовая схема позволяет ответить, какой запуск получил доступ и почему. Это гораздо полезнее знания, что где-то существует ещё один правильный пароль. А тестовый VPS даёт возможность спокойно проверить границы доверия до того, как от них начнёт зависеть рабочее приложение.
Нужен сервер под эту задачу?
Разверните VPS MAATRIX за пару минут: NVMe, AMD EPYC, root-доступ, локации UK, США и РФ. Оплата картой РФ и по СБП.
АРЕНДОВАТЬ СЕРВЕРОбсудить статью, задать вопрос или начать новую тему
Есть вопрос по этой статье, идея для обсуждения или просто хотите поделиться опытом? Сообщество MAATRIX ждёт. Для общения, пожалуйста, зарегистрируйтесь в нашем личном кабинете.
Перейти в сообщество →