CI/CD-системы должны получать секреты из Stronghold на время выполнения задания и не хранить долгоживущие токены. Для этого используйте:
- метод JWT/OIDC — если CI-система выпускает подписанный OIDC-токен для каждого задания (GitLab CI, GitHub Actions);
- метод AppRole — если такого токена нет (например, Jenkins).
Инструменты, упомянутые на странице (hashicorp/vault-action, HashiCorp Vault Plugin для Jenkins, ключевое слово secrets в GitLab CI), разработаны для HashiCorp Vault. Stronghold совместим с API Vault, поэтому их можно направить на Stronghold.
Общие правила
Эти правила относятся ко всем CI/CD-системам на странице:
- Не храните токены Stronghold в переменных CI/CD. Получайте токен в начале задания через JWT или AppRole.
- Задавайте короткий срок действия токена (
token_ttl,token_explicit_max_ttl) — не больше длительности задания. - Создавайте отдельную роль и политику на каждый проект и окружение. Ограничивайте роли по ветке, тегу или окружению через
bound_claims. - Выдавайте только права
readна нужные пути. Изменение политик и точек монтирования выполняйте отдельными процессами, как описано в разделах «Terraform и Ansible» и «GitOps». - Не выводите секреты в лог задания. Используйте маскирование переменных средствами CI-системы.
- Для self-hosted раннеров можно использовать Stronghold Agent на узле раннера.
GitLab CI
GitLab выпускает для задания OIDC-токен, объявленный в ключевом слове id_tokens. Stronghold проверяет подпись токена по ключам GitLab и сопоставляет утверждения (claims) project_path, ref, ref_type с ролью.
Включите метод JWT по отдельному пути и укажите адрес GitLab:
d8 stronghold auth enable -path=gitlab jwt d8 stronghold write auth/gitlab/config oidc_discovery_url="https://gitlab.example.com" bound_issuer="https://gitlab.example.com"Создайте политику для проекта:
d8 stronghold policy write myproject-ci - <<'POLICY' path "secret/data/myproject/*" { capabilities = ["read"] } POLICYСоздайте роль, которая выдаёт токен только заданиям из ветки
mainпроектаmygroup/myproject:d8 stronghold write auth/gitlab/role/myproject-deploy - <<'ROLE' { "role_type": "jwt", "user_claim": "user_login", "bound_audiences": ["https://stronghold.example.com"], "bound_claims": { "project_path": "mygroup/myproject", "ref": "main", "ref_type": "branch" }, "token_policies": ["myproject-ci"], "token_ttl": "10m", "token_explicit_max_ttl": "30m" } ROLEВ
.gitlab-ci.ymlобъявите токен вid_tokensи обменяйте его на токен Stronghold. В образе задания должна быть утилитаd8:deploy: stage: deploy id_tokens: STRONGHOLD_ID_TOKEN: aud: https://stronghold.example.com variables: STRONGHOLD_ADDR: https://stronghold.example.com script: - export STRONGHOLD_TOKEN="$(d8 stronghold write -field=token auth/gitlab/login role=myproject-deploy jwt=$STRONGHOLD_ID_TOKEN)" - export DB_PASSWORD="$(d8 stronghold kv get -mount=secret -field=password myproject/db)" - ./deploy.sh rules: - if: $CI_COMMIT_BRANCH == "main"
Значение aud в id_tokens должно совпадать с bound_audiences роли.
GitHub Actions
GitHub Actions выпускает OIDC-токен с издателем https://token.actions.githubusercontent.com. Для получения токена в workflow нужно разрешение id-token: write.
Включите метод JWT и укажите издателя GitHub:
d8 stronghold auth enable -path=github jwt d8 stronghold write auth/github/config oidc_discovery_url="https://token.actions.githubusercontent.com" bound_issuer="https://token.actions.githubusercontent.com"Создайте политику
myproject-ci, как в шаге 2 раздела «GitLab CI», если она ещё не создана.Создайте роль для репозитория
myorg/myrepoи веткиmain:d8 stronghold write auth/github/role/myrepo-deploy - <<'ROLE' { "role_type": "jwt", "user_claim": "repository", "bound_audiences": ["https://stronghold.example.com"], "bound_claims": { "repository": "myorg/myrepo", "ref": "refs/heads/main" }, "token_policies": ["myproject-ci"], "token_ttl": "10m" } ROLEИспользуйте в workflow действие
hashicorp/vault-action:name: deploy on: push: branches: [main] permissions: id-token: write contents: read jobs: deploy: runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 - uses: hashicorp/vault-action@v3 with: url: https://stronghold.example.com method: jwt path: github role: myrepo-deploy jwtGithubAudience: https://stronghold.example.com secrets: | secret/data/myproject/db password | DB_PASSWORD - run: ./deploy.sh
Для заданий с environment ограничивайте роль утверждением environment вместо ref.
Jenkins
У Jenkins нет встроенного OIDC-токена для каждого задания, поэтому используйте AppRole.
Создайте политику
myproject-ci, как в шаге 2 раздела «GitLab CI», если она ещё не создана.Включите AppRole и создайте роль с коротким сроком жизни токена и ограниченным по времени и сети
secret_id:d8 stronghold auth enable approle d8 stronghold write auth/approle/role/jenkins-myproject token_policies=myproject-ci token_ttl=15m token_max_ttl=30m secret_id_ttl=24h secret_id_num_uses=0 secret_id_bound_cidrs=10.0.10.0/24Получите
role_idиsecret_id:d8 stronghold read -field=role_id auth/approle/role/jenkins-myproject/role-id d8 stronghold write -f -field=secret_id auth/approle/role/jenkins-myproject/secret-idСохраните их в Jenkins как учётные данные типа Vault App Role Credential плагина HashiCorp Vault Plugin и используйте в Jenkinsfile:
def configuration = [ vaultUrl: 'https://stronghold.example.com', vaultCredentialId: 'stronghold-approle', engineVersion: 2 ] def secrets = [ [path: 'secret/myproject/db', engineVersion: 2, secretValues: [ [envVar: 'DB_PASSWORD', vaultKey: 'password'] ]] ] pipeline { agent any stages { stage('Deploy') { steps { withVault([configuration: configuration, vaultSecrets: secrets]) { sh './deploy.sh' } } } } }
Регулярно перевыпускайте secret_id (например, отдельным заданием) и ограничивайте его по сети через secret_id_bound_cidrs. Для доставки secret_id можно использовать обёртывание ответа.