From 5db0ab10f6bcfcad75b6877a123ea15f9930b3a9 Mon Sep 17 00:00:00 2001 From: Vassiliy Yegorov Date: Thu, 26 Mar 2026 19:53:49 +0700 Subject: [PATCH] add doc --- .gitlab-ci.yml.example | 65 ++++++++++ README.md | 123 +++++++++++++------ helm/nginx/values.yaml | 1 + policies/kyverno-image-signature-policy.yaml | 3 +- 4 files changed, 151 insertions(+), 41 deletions(-) create mode 100644 .gitlab-ci.yml.example diff --git a/.gitlab-ci.yml.example b/.gitlab-ci.yml.example new file mode 100644 index 0000000..be06430 --- /dev/null +++ b/.gitlab-ci.yml.example @@ -0,0 +1,65 @@ +# .gitlab-ci.yml.example +# GitLab CI/CD pipeline: build, sign and push container image with Cosign +# +# Required CI/CD variables (Settings → CI/CD → Variables): +# COSIGN_PRIVATE_KEY — contents of cosign.key (type: Variable, masked, protected) +# COSIGN_PASSWORD — password for the private key (type: Variable, masked, protected) +# REGISTRY_USER — registry login username (type: Variable) +# REGISTRY_PASSWORD — registry login password (type: Variable, masked, protected) + +stages: + - build + - sign + - verify + +variables: + REGISTRY: git.realmanual.ru + IMAGE: ${REGISTRY}/${CI_PROJECT_PATH} + +build: + stage: build + image: docker:27 + services: + - docker:27-dind + variables: + DOCKER_TLS_CERTDIR: "/certs" + before_script: + - echo "${REGISTRY_PASSWORD}" | docker login ${REGISTRY} -u ${REGISTRY_USER} --password-stdin + - export VERSION=$(cat image/VERSION) + script: + - docker build -t ${IMAGE}:${CI_COMMIT_SHA} -t ${IMAGE}:${VERSION} image/ + - docker push ${IMAGE}:${CI_COMMIT_SHA} + - docker push ${IMAGE}:${VERSION} + # save digest for sign/verify stages + - docker inspect --format='{{index .RepoDigests 0}}' ${IMAGE}:${CI_COMMIT_SHA} | cut -d@ -f2 > digest.txt + artifacts: + paths: + - digest.txt + rules: + - if: $CI_COMMIT_BRANCH == "main" + +sign: + stage: sign + image: alpine:3.20 + before_script: + - apk add --no-cache cosign + script: + - export IMAGE_DIGEST=$(cat digest.txt) + - cosign sign --yes + --key env://COSIGN_PRIVATE_KEY + ${IMAGE}@${IMAGE_DIGEST} + rules: + - if: $CI_COMMIT_BRANCH == "main" + +verify: + stage: verify + image: alpine:3.20 + before_script: + - apk add --no-cache cosign + script: + - export IMAGE_DIGEST=$(cat digest.txt) + - cosign verify + --key keys/cosign.pub + ${IMAGE}@${IMAGE_DIGEST} + rules: + - if: $CI_COMMIT_BRANCH == "main" diff --git a/README.md b/README.md index fcba3ef..204fbc2 100644 --- a/README.md +++ b/README.md @@ -1,62 +1,105 @@ -# Custom Nginx + Image Signing + Kyverno Policy +# Cosign Image Signing + Kyverno Policy ## Описание -Демонстрация полного цикла: сборка кастомного nginx-образа, подпись через HashiCorp Vault, деплой в Kubernetes через Helm и валидация подписи Kyverno. +Демонстрация полного цикла безопасного деплоя контейнеров: сборка кастомного nginx-образа, криптографическая подпись через Cosign (Sigstore), деплой в Kubernetes через Helm и валидация подписи политикой Kyverno. + +--- + +## Структура проекта + +``` +. +├── .gitea/workflows/ +│ └── build-sign-push.yaml # CI/CD пайплайн для Gitea Actions +├── .gitlab-ci.yml.example # Пример CI/CD пайплайна для GitLab +├── helm/nginx/ # Helm чарт для деплоя +│ ├── Chart.yaml +│ ├── values.yaml +│ └── templates/ +│ ├── _helpers.tpl +│ ├── deployment.yaml +│ └── service.yaml +├── image/ +│ ├── Dockerfile # Кастомный nginx (non-root, порт 8080) +│ ├── nginx.conf +│ └── VERSION +├── keys/ +│ ├── cosign.key # Приватный ключ (не в git) +│ └── cosign.pub # Публичный ключ для верификации +└── policies/ + └── kyverno-image-signature-policy.yaml # Политика Kyverno +``` + +--- + +## Как это работает + +1. **Push в main** → Gitea Actions запускает пайплайн +2. **Сборка** → Docker-образ собирается и пушится в реестр `git.realmanual.ru` +3. **Подпись** → Cosign подписывает образ по digest приватным ключом +4. **Верификация в CI** → Cosign проверяет подпись публичным ключом +5. **Деплой** → Helm разворачивает образ в namespace `cosign-test` +6. **Enforce** → Kyverno перехватывает создание Pod, проверяет подпись образа и блокирует неподписанные --- ## Шаги запуска -### 1. Подготовка Vault +### 1. Генерация ключей Cosign -- Настройте Transit Engine и сгенерируйте ключ `container-sign` в Vault. -- Примените политику из `vault_approle_policy.hcl`: - ```sh - vault policy write container-sign vault_approle_policy.hcl - ``` -- Создайте appRole и получите RoleID/SecretID: - ```sh - sh vault_approle_setup.sh - ``` -- Сохраните RoleID и SecretID для переменных CI/CD. +```sh +cosign generate-key-pair +``` -### 2. Настройка GitLab CI/CD +Поместите `cosign.key` и `cosign.pub` в директорию `keys/`. -- В настройках проекта добавьте переменные: - - `VAULT_ROLE_ID` — из Vault - - `VAULT_SECRET_ID` — из Vault -- Убедитесь, что `.gitlab-ci.yml` корректно настроен под ваш реестр и адрес Vault. +### 2. Настройка секретов CI/CD + +#### Gitea (Settings → Secrets) + +| Секрет | Описание | +|--------|----------| +| `COSIGN_PRIVATE_KEY` | Содержимое файла `cosign.key` целиком (включая `-----BEGIN/END-----`) | +| `COSIGN_PASSWORD` | Пароль от приватного ключа, заданный при `cosign generate-key-pair` | +| `PUSH_TOKEN` | Токен доступа к реестру контейнеров | + +#### GitLab (Settings → CI/CD → Variables) + +Для GitLab используйте файл `.gitlab-ci.yml.example` (переименуйте в `.gitlab-ci.yml`). + +| Переменная | Тип | Masked | Protected | Описание | +|------------|-----|--------|-----------|----------| +| `COSIGN_PRIVATE_KEY` | Variable | Yes | Yes | Содержимое файла `cosign.key` целиком | +| `COSIGN_PASSWORD` | Variable | Yes | Yes | Пароль от приватного ключа | +| `REGISTRY_USER` | Variable | No | No | Логин для реестра контейнеров | +| `REGISTRY_PASSWORD` | Variable | Yes | Yes | Пароль/токен для реестра контейнеров | + +> **Важно:** переменные с флагом **Masked** не отображаются в логах джобов. Флаг **Protected** ограничивает доступ только защищёнными ветками (main). ### 3. Сборка и подпись образа -- Запустите pipeline в GitLab. -- Образ будет собран, отправлен в реестр и подписан через cosign с использованием Vault. +Пуш в ветку `main` автоматически запустит пайплайн, который соберёт, подпишет и верифицирует образ. -### 4. Деплой в Kubernetes +### 4. Применение политики Kyverno -- Перейдите в директорию helm-чарта: - ```sh - cd helm/nginx - helm install nginx . - ``` -- Проверьте, что деплой прошёл успешно. +```sh +kubectl apply -f policies/kyverno-image-signature-policy.yaml +``` -### 5. Настройка Kyverno +Политика требует подпись для всех образов в namespace `cosign-test`. -- Получите публичный ключ из Vault: - ```sh - vault read -field=public_key transit/keys/container-sign - ``` -- Вставьте ключ в `kyverno-image-signature-policy.yaml` вместо ``. -- Примените политику: - ```sh - kubectl apply -f kyverno-image-signature-policy.yaml - ``` +### 5. Деплой в Kubernetes + +```sh +helm upgrade -i -n cosign-test cosign-test helm/nginx +``` --- -## Итог +## Требования -- Только подписанные образы будут запускаться в кластере. -- Весь процесс автоматизирован через CI/CD и Vault. +- Kubernetes кластер с установленным Kyverno +- Gitea с поддержкой Actions или GitLab CI/CD +- Cosign (sigstore) +- Helm 3 diff --git a/helm/nginx/values.yaml b/helm/nginx/values.yaml index f4a17e0..c5b2de3 100644 --- a/helm/nginx/values.yaml +++ b/helm/nginx/values.yaml @@ -1,6 +1,7 @@ replicaCount: 1 image: repository: git.realmanual.ru/pub/cosign-images + # repository: nginx tag: 0.1.0 pullPolicy: Always service: diff --git a/policies/kyverno-image-signature-policy.yaml b/policies/kyverno-image-signature-policy.yaml index d220f7f..84d1652 100644 --- a/policies/kyverno-image-signature-policy.yaml +++ b/policies/kyverno-image-signature-policy.yaml @@ -14,7 +14,8 @@ spec: namespaces: ["cosign-test"] verifyImages: - imageReferences: - - "git.realmanual.ru/pub/*" + # - "git.realmanual.ru/pub/*" + - "*" attestors: - entries: - keys: