init
This commit is contained in:
1
.gitignore
vendored
Normal file
1
.gitignore
vendored
Normal file
@@ -0,0 +1 @@
|
||||
keys/*
|
||||
62
README.md
Normal file
62
README.md
Normal 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.
|
||||
73
gitea/workflows/build-sign-push.yaml
Normal file
73
gitea/workflows/build-sign-push.yaml
Normal 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
5
helm/nginx/Chart.yaml
Normal file
@@ -0,0 +1,5 @@
|
||||
apiVersion: v2
|
||||
name: nginx
|
||||
version: 0.1.0
|
||||
description: Custom nginx with config
|
||||
appVersion: "1.25"
|
||||
7
helm/nginx/templates/_helpers.tpl
Normal file
7
helm/nginx/templates/_helpers.tpl
Normal file
@@ -0,0 +1,7 @@
|
||||
{{- define "nginx.name" -}}
|
||||
nginx
|
||||
{{- end -}}
|
||||
|
||||
{{- define "nginx.fullname" -}}
|
||||
{{ include "nginx.name" . }}
|
||||
{{- end -}}
|
||||
22
helm/nginx/templates/deployment.yaml
Normal file
22
helm/nginx/templates/deployment.yaml
Normal 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
|
||||
11
helm/nginx/templates/service.yaml
Normal file
11
helm/nginx/templates/service.yaml
Normal 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
8
helm/nginx/values.yaml
Normal 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
2
image/Dockerfile
Normal file
@@ -0,0 +1,2 @@
|
||||
FROM nginx:1.25-alpine
|
||||
COPY nginx.conf /etc/nginx/nginx.conf
|
||||
1
image/VERSION
Normal file
1
image/VERSION
Normal file
@@ -0,0 +1 @@
|
||||
0.1.0
|
||||
9
image/nginx.conf
Normal file
9
image/nginx.conf
Normal file
@@ -0,0 +1,9 @@
|
||||
events {}
|
||||
http {
|
||||
server {
|
||||
listen 80;
|
||||
location / {
|
||||
return 200 'Hello from custom nginx!';
|
||||
}
|
||||
}
|
||||
}
|
||||
24
policies/kyverno-image-signature-policy.yaml
Normal file
24
policies/kyverno-image-signature-policy.yaml
Normal 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: "*"
|
||||
Reference in New Issue
Block a user