Хранение и использование secrets, encryption, ротация ключей.
Секреты — критический компонент безопасности пайплайнов. Неправильное управление секретами приводит к утечкам данных и компрометации инфраструктуры.
Секреты (Secrets) — конфиденциальные данные, которые используются в пайплайнах, но не должны храниться в открытом виде в репозитории.
Типы секретов:
Почему нельзя хранить секреты в коде:
# ❌ НИКОГДА НЕ ДЕЛАЙТЕ ТАК
jobs:
deploy:
steps:
- run: |
export AWS_SECRET_KEY="AKIAIOSFODNN7EXAMPLE"
aws s3 cp dist/ s3://my-bucket/Последствия утечки:
GitHub поддерживает три уровня секретов:
| Уровень | Область действия | Где настраивать |
|---|---|---|
| Repository | Один репозиторий | Settings → Secrets and variables → Actions |
| Environment | Конкретное окружение (staging, production) | Settings → Environments |
| Organization | Все репозитории организации | Organization Settings → Secrets and variables → Actions |
Через веб-интерфейс:
DATABASE_URL) и значениеЧерез GitHub CLI:
# Установка секрета для репозитория
gh secret set DATABASE_URL --body "postgresql://user:pass@host:5432/db"
# Установка секрета для организации
gh secret set DATABASE_URL --body "postgresql://user:pass@host:5432/db" --org my-org
# Просмотр списка секретов
gh secret listБазовое использование:
jobs:
deploy:
runs-on: ubuntu-latest
steps:
- name: Deploy to production
run: ./deploy.sh
env:
DATABASE_URL: ${{ secrets.DATABASE_URL }}
API_KEY: ${{ secrets.API_KEY }}Важно: Секреты недоступны в логах. GitHub автоматически маскирует их значения.
| Параметр | Ограничение |
|---|---|
| Максимальный размер | 64 KB на секрет |
| Имя секрета | Только латиница, цифры, подчёркивание |
| Количество секретов | 100 на репозиторий (бесплатный тариф) |
Секреты окружений обеспечивают дополнительную защиту для production:
jobs:
deploy-staging:
runs-on: ubuntu-latest
environment: staging # Использует секреты staging
steps:
- run: ./deploy.sh
env:
DATABASE_URL: ${{ secrets.DATABASE_URL }}
deploy-production:
runs-on: ubuntu-latest
environment: production # Использует секреты production
needs: deploy-staging
steps:
- run: ./deploy.sh
env:
DATABASE_URL: ${{ secrets.DATABASE_URL }}Защита окружений:
Важное ограничение: Секреты репозитория недоступны в workflow, запущенных из fork.
# Workflow из fork НЕ получит доступ к secrets
on:
pull_request: # Запуск из fork — secrets недоступныРешение — workflow_run:
# Шаг 1: Workflow для PR (без секретов)
name: PR Tests
on:
pull_request:
jobs:
test:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- run: npm test
# Шаг 2: Workflow с секретами (запускается после мержа)
name: Deploy
on:
workflow_run:
workflows: ["PR Tests"]
types:
- completed
branches:
- main
jobs:
deploy:
runs-on: ubuntu-latest
if: ${{ github.event.workflow_run.conclusion == 'success' }}
steps:
- run: ./deploy.sh
env:
DEPLOY_TOKEN: ${{ secrets.DEPLOY_TOKEN }}GitLab поддерживает несколько уровней переменных:
| Уровень | Область действия | Приоритет |
|---|---|---|
| Project | Один проект | Низкий |
| Group | Все проекты группы | Средний |
| Instance | Весь GitLab instance | Высокий |
| Environment | Конкретное окружение | Наивысший |
Через веб-интерфейс:
DATABASE_URL) и значениеЧерез API:
curl --request POST \
--header "PRIVATE-TOKEN: $GITLAB_TOKEN" \
--data "key=DATABASE_URL&value=postgresql://user:pass@host:5432/db" \
"https://gitlab.com/api/v4/projects/$PROJECT_ID/variables"Через GitLab CLI (glab):
glab variable create DATABASE_URL "postgresql://user:pass@host:5432/db"Базовое использование:
deploy:
stage: deploy
script:
- ./deploy.sh
variables:
# Переопределение или добавление переменных
DEPLOY_ENV: productionДоступ к переменным:
test:
script:
# Прямое использование
- echo "Deploying to $DEPLOY_ENV"
# Через export
- export API_KEY=$API_KEY
- ./run-tests.sh
# В команде
- DATABASE_URL=$DATABASE_URL npm run migrateЗащищённые переменные доступны только на защищённых ветках и тегах:
# .gitlab-ci.yml
deploy-production:
stage: deploy
variables:
# Эта переменная должна быть защищена в настройках
PROD_SECRET: $PROD_SECRET
script:
- ./deploy-prod.sh
only:
- main # Защищённая веткаНастройка защиты:
main)Маскированные переменные скрываются в логах:
Требования для маскировки:
CI_ или GITLAB__, -, /, =, +, %Проверка маскировки:
test:
script:
- echo "Secret: $SECRET_VAR" # В логе будет: Secret: [MASKED]Традиционный подход (небезопасный):
# ❌ Хранение долгосрочных секретов
jobs:
deploy:
steps:
- run: aws s3 cp dist/ s3://bucket/
env:
AWS_ACCESS_KEY_ID: ${{ secrets.AWS_ACCESS_KEY_ID }}
AWS_SECRET_ACCESS_KEY: ${{ secrets.AWS_SECRET_ACCESS_KEY }}Проблемы:
OpenID Connect (OIDC) позволяет получать временные токены доступа:
┌─────────────────┐ ┌─────────────────┐ ┌─────────────────┐
│ GitHub Actions │────▶│ OIDC Provider │────▶│ Cloud Provider │
│ │ │ (GitHub) │ │ (AWS/GCP/Azure)│
│ 1. Запрос токена│ │ 2. Выдача JWT │ │ 3. Обмен на │
│ │ │ │ │ временные creds│
└─────────────────┘ └─────────────────┘ └─────────────────┘
Шаг 1: Создание OIDC провайдера в AWS
# Создание Identity Provider
aws iam create-open-id-connect-provider \
--url https://token.actions.githubusercontent.com \
--client-id-list sts.amazonaws.com \
--thumbprint-list 6938fd4d98bab03faadb97b34396831e3780aea1Шаг 2: Создание IAM Role с доверием
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Principal": {
"Federated": "arn:aws:iam::123456789012:oidc-provider/token.actions.githubusercontent.com"
},
"Action": "sts:AssumeRoleWithWebIdentity",
"Condition": {
"StringEquals": {
"token.actions.githubusercontent.com:aud": "sts.amazonaws.com"
},
"StringLike": {
"token.actions.githubusercontent.com:sub": "repo:my-org/my-repo:*"
}
}
}
]
}Шаг 3: Workflow с OIDC
jobs:
deploy:
runs-on: ubuntu-latest
permissions:
id-token: write # Обязательно для OIDC
contents: read
steps:
- uses: actions/checkout@v4
- name: Configure AWS credentials
uses: aws-actions/configure-aws-credentials@v4
with:
role-to-assume: arn:aws:iam::123456789012:role/github-actions-role
aws-region: us-east-1
- name: Deploy to S3
run: aws s3 cp dist/ s3://my-bucket/Шаг 1: Создание Workload Identity Pool
gcloud iam workload-identity-pools create "github-pool" \
--project="my-project" \
--location="global" \
--display-name="GitHub Actions Pool"Шаг 2: Создание провайдера
gcloud iam workload-identity-pools providers create-oidc "github-provider" \
--project="my-project" \
--location="global" \
--workload-identity-pool="github-pool" \
--display-name="GitHub Provider" \
--attribute-mapping="google.subject=assertion.sub,attribute.actor=assertion.actor,attribute.repository=assertion.repository" \
--issuer-uri="https://token.actions.githubusercontent.com"Шаг 3: Workflow для GCP
jobs:
deploy:
runs-on: ubuntu-latest
permissions:
id-token: write
contents: read
steps:
- uses: actions/checkout@v4
- name: Authenticate to Google Cloud
uses: google-github-actions/auth@v2
with:
workload_identity_provider: 'projects/123456789/locations/global/workloadIdentityPools/github-pool/providers/github-provider'
service_account: 'deploy@my-project.iam.gserviceaccount.com'
- name: Deploy to GKE
run: gcloud container clusters get-credentials my-clusterШаг 1: Создание Federated Identity Credential
az identity federated-credential create \
--name "github-credential" \
--identity-name "github-identity" \
--resource-group "my-rg" \
--issuer "https://token.actions.githubusercontent.com" \
--subject "repo:my-org/my-repo:ref:refs/heads/main"Шаг 2: Workflow для Azure
jobs:
deploy:
runs-on: ubuntu-latest
permissions:
id-token: write
contents: read
steps:
- uses: actions/checkout@v4
- name: Azure Login
uses: azure/login@v1
with:
client-id: ${{ secrets.AZURE_CLIENT_ID }}
tenant-id: ${{ secrets.AZURE_TENANT_ID }}
subscription-id: ${{ secrets.AZURE_SUBSCRIPTION_ID }}
- name: Deploy to Azure
run: az webapp deploy --name my-app --src-path dist/Плохо:
# Слишком широкие права
permissions:
contents: write
packages: write
actions: writeХорошо:
# Только необходимые права
permissions:
contents: read # Только чтение кода
id-token: write # Для OIDCАвтоматическая ротация через GitHub Actions:
name: Rotate Secrets
on:
schedule:
- cron: '0 0 1 * *' # 1-го числа каждого месяца
jobs:
rotate:
runs-on: ubuntu-latest
steps:
- name: Generate new API key
run: |
NEW_KEY=$(curl -X POST https://api.service.com/keys | jq .key)
gh secret set API_KEY --body "$NEW_KEY"Включение аудита в GitHub:
Пример запроса в audit log:
action:secret.created OR action:secret.updated
Использование age для шифрования:
# Генерация ключа
age-keygen -o key.txt
# Шифрование секрета
age -R key.txt secret.env > secret.env.age
# Дешифрование в пайплайне
age -d -i key.txt secret.env.age > secret.envjobs:
deploy:
steps:
- name: Decrypt secrets
run: |
echo "${{ secrets.AGE_KEY }}" > key.txt
age -d -i key.txt secret.env.age > secret.envПроверка наличия обязательных секретов:
jobs:
validate:
runs-on: ubuntu-latest
steps:
- name: Check required secrets
run: |
required_secrets=("DATABASE_URL" "API_KEY" "DEPLOY_TOKEN")
for secret in "${required_secrets[@]}"; do
if [ -z "${!secret}" ]; then
echo "Missing required secret: $secret"
exit 1
fi
done
env:
DATABASE_URL: ${{ secrets.DATABASE_URL }}
API_KEY: ${{ secrets.API_KEY }}
DEPLOY_TOKEN: ${{ secrets.DEPLOY_TOKEN }}| Функция | GitHub Actions | GitLab CI |
|---|---|---|
| Repository secrets | ✅ | ✅ |
| Environment secrets | ✅ | ✅ (через Environment scope) |
| Organization/Group secrets | ✅ | ✅ |
| Masked variables | ✅ (автоматически) | ✅ (требует настройки) |
| Protected variables | ❌ | ✅ |
| OIDC | ✅ | ✅ (с GitLab 15.0) |
| File variables | ❌ | ✅ |
| Максимальный размер | 64 KB | 36 KB |
Управление секретами — критический аспект безопасности CI/CD:
В следующей теме вы изучите permissions и безопасность workflow в деталях.
Вопросы ещё не добавлены
Вопросы для этой подтемы ещё не добавлены.