This commit is contained in:
2026-03-26 18:58:53 +07:00
commit 38ecc2ad24
12 changed files with 225 additions and 0 deletions

1
.gitignore vendored Normal file
View File

@@ -0,0 +1 @@
keys/*

62
README.md Normal file
View File

@@ -0,0 +1,62 @@
# Custom Nginx + Image Signing + Kyverno Policy
## Описание
Демонстрация полного цикла: сборка кастомного nginx-образа, подпись через HashiCorp Vault, деплой в Kubernetes через Helm и валидация подписи Kyverno.
---
## Шаги запуска
### 1. Подготовка Vault
- Настройте 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.
### 2. Настройка GitLab CI/CD
- В настройках проекта добавьте переменные:
- `VAULT_ROLE_ID` — из Vault
- `VAULT_SECRET_ID` — из Vault
- Убедитесь, что `.gitlab-ci.yml` корректно настроен под ваш реестр и адрес Vault.
### 3. Сборка и подпись образа
- Запустите pipeline в GitLab.
- Образ будет собран, отправлен в реестр и подписан через cosign с использованием Vault.
### 4. Деплой в Kubernetes
- Перейдите в директорию helm-чарта:
```sh
cd helm/nginx
helm install nginx .
```
- Проверьте, что деплой прошёл успешно.
### 5. Настройка Kyverno
- Получите публичный ключ из Vault:
```sh
vault read -field=public_key transit/keys/container-sign
```
- Вставьте ключ в `kyverno-image-signature-policy.yaml` вместо `<VAULT_PUBLIC_KEY>`.
- Примените политику:
```sh
kubectl apply -f kyverno-image-signature-policy.yaml
```
---
## Итог
- Только подписанные образы будут запускаться в кластере.
- Весь процесс автоматизирован через CI/CD и Vault.

View File

@@ -0,0 +1,73 @@
# .gitea/workflows/build-sign-push.yaml
name: build, sign and push
on:
push:
branches: [main]
env:
REGISTRY: git.realmanual.ru
IMAGE: git.realmanual.ru/${{ gitea.repository }}
jobs:
build-and-sign:
runs-on: ubuntu-latest
permissions:
contents: read
packages: write
steps:
- name: checkout
uses: actions/checkout@v4
# --- build ---
- name: set up docker buildx
uses: docker/setup-buildx-action@v3
- name: Read Version
id: version
run: echo "VERSION=$(cat image/VERSION)" >> $GITHUB_OUTPUT
- name: login to registry
uses: docker/login-action@v3
with:
registry: ${{ env.REGISTRY }}
username: ${{ gitea.actor }}
password: ${{ secrets.PUSH_TOKEN }}
- name: build and push
id: build
uses: docker/build-push-action@v5
with:
context: ./image
push: true
# тегируем и по SHA и по latest
tags: |
${{ env.IMAGE }}:${{ gitea.sha }}
${{ env.IMAGE }}:${{ steps.version.outputs.VERSION }}
# digest понадобится для подписи — по тегу подписывать нельзя
outputs: type=image,push=true
# --- sign ---
# cosign надо ставить отдельно — в ubuntu-latest его нет
- name: install cosign
uses: sigstore/cosign-installer@v3
with:
cosign-release: 'v3.0.5'
- name: sign image
env:
COSIGN_PRIVATE_KEY: ${{ secrets.COSIGN_PRIVATE_KEY }}
COSIGN_PASSWORD: ${{ secrets.COSIGN_PASSWORD }}
# digest в формате sha256:abc123...
IMAGE_DIGEST: ${{ steps.build.outputs.digest }}
run: |
cosign sign --yes \
--key env://COSIGN_PRIVATE_KEY \
${{ env.IMAGE }}@${IMAGE_DIGEST}
# --- verify (self-check в CI) ---
- name: verify signature
env:
IMAGE_DIGEST: ${{ steps.build.outputs.digest }}
run: |
cosign verify \
--key cosign.pub \
${{ env.IMAGE }}@${IMAGE_DIGEST}

5
helm/nginx/Chart.yaml Normal file
View File

@@ -0,0 +1,5 @@
apiVersion: v2
name: nginx
version: 0.1.0
description: Custom nginx with config
appVersion: "1.25"

View File

@@ -0,0 +1,7 @@
{{- define "nginx.name" -}}
nginx
{{- end -}}
{{- define "nginx.fullname" -}}
{{ include "nginx.name" . }}
{{- end -}}

View File

@@ -0,0 +1,22 @@
apiVersion: apps/v1
kind: Deployment
metadata:
name: { { include "nginx.fullname" . } }
labels:
app: { { include "nginx.name" . } }
spec:
replicas: { { .Values.replicaCount } }
selector:
matchLabels:
app: { { include "nginx.name" . } }
template:
metadata:
labels:
app: { { include "nginx.name" . } }
spec:
containers:
- name: nginx
image: "{{ .Values.image.repository }}:{{ .Values.image.tag }}"
imagePullPolicy: { { .Values.image.pullPolicy } }
ports:
- containerPort: 80

View File

@@ -0,0 +1,11 @@
apiVersion: v1
kind: Service
metadata:
name: { { include "nginx.fullname" . } }
spec:
type: { { .Values.service.type } }
ports:
- port: { { .Values.service.port } }
targetPort: 80
selector:
app: { { include "nginx.name" . } }

8
helm/nginx/values.yaml Normal file
View File

@@ -0,0 +1,8 @@
replicaCount: 1
image:
repository: git.realmanual.ru/pub/cosign-images
tag: 0.1.0
pullPolicy: IfNotPresent
service:
type: ClusterIP
port: 80

2
image/Dockerfile Normal file
View File

@@ -0,0 +1,2 @@
FROM nginx:1.25-alpine
COPY nginx.conf /etc/nginx/nginx.conf

1
image/VERSION Normal file
View File

@@ -0,0 +1 @@
0.1.0

9
image/nginx.conf Normal file
View File

@@ -0,0 +1,9 @@
events {}
http {
server {
listen 80;
location / {
return 200 'Hello from custom nginx!';
}
}
}

View File

@@ -0,0 +1,24 @@
apiVersion: kyverno.io/v1
kind: ClusterPolicy
metadata:
name: require-image-signature
spec:
validationFailureAction: Enforce
rules:
- name: verify-image-signature
match:
resources:
kinds:
- Pod
include:
resources:
namespaces:
- cosign-test
verifyImages:
- image: "git.ntk.novotelecom.ru/adm/docker-trust*"
key: "-----BEGIN PUBLIC KEY-----MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA1kmEd1dzkY0MLMhNlkPz8LbX70tdw5acXoKYvOGzcTUK4jppKBCLst121UMC0L5DcgqNE9uly0S78aE8pbIxpBSgVdM8NPRa90vGTi50rauzOGiVRSxOzmkh3BVErqga84U9xb8QmS28rwjdSCbZSx27quzkDrvHwrfid5DroCSkNFQo7Bb84jlgTbrV5KwXkd7G5bMB3qaAzIpBQH+LbKn8/76rlU9/NfUpzftFdOwVVOWQIC7PYU8z2cKI9C+Su+MkrozuGSLrR/Z/urCK9xibrUzRMX7N2v5ORXGhili4pFJG7asxQjPzl2a23iYGkt8c5egxlXWFk4zrVnmawIDAQAB-----END PUBLIC KEY-----"
attestors:
- entries:
- keyless:
subject: "*"
issuer: "*"