eumel8/cosignwebhook
GitHub: eumel8/cosignwebhook
用于验证 Cosign 镜像签名的 Kubernetes 验证准入控制器,保障运行时镜像来源可信。
Stars: 12 | Forks: 3
[](https://www.repostatus.org/#active)
[](https://goreportcard.com/report/github.com/eumel8/cosignwebhook)
[](https://github.com/eumel8/cosignwebhook/releases)
[](https://app.codacy.com/gh/eumel8/cosignwebhook/dashboard?utm_source=gh&utm_medium=referral&utm_content=&utm_campaign=Badge_grade)
[](https://github.com/eumel8/cosignwebhook/actions/workflows/gotest.yaml)
[](https://github.com/eumel8/cosignwebhook/actions/workflows/end2end.yaml)
[]([https://github.com/eumel8/cosignwebhook/actions/workflows/build.yaml)
[]([https://github.com/eumel8/cosignwebhook/actions/workflows/trivy.yaml)
# Cosign Webhook
用于验证 Cosign 镜像签名的 Kubernetes 验证准入控制器。
此准入控制器会监听 Pod 创建事件,并验证找到的所有容器镜像是否具有现有的
RSA 公钥(如果存在)。
# Helm 安装
```
helm -n cosignwebhook upgrade -i cosignwebhook oci://ghcr.io/eumel8/charts/cosignwebhook --versi
on 5.0.0 --create-namespace
```
此安装方式具有以下优势:
* 自动生成 TLS 密钥对
* 自动设置 ServiceMonitor 和 Grafana 仪表盘
如果你使用自己的镜像,你需要先对其进行签名。不要忘记修改 `cosign.scwebhook.key` 值为你的
公钥,用于签名镜像。
# 清单文件安装
作为集群管理员,创建一个命名空间并安装准入控制器:
```
kubectl create namespace cosignwebhook
kubectl -n cosignwebhook apply -f manifests/rbac.yaml
kubectl -n cosignwebhook apply -f manifests/manifest.yaml
```
该清单包含一个自签名的示例 CA、TLS 证书和密钥。这仅用于演示效果,你应该生成自己的证书,参见下文:
## 证书生成
运行 `hack` 文件夹中的生成证书脚本,以生成 TLS 密钥对和准入控制器的 CA 证书:
```
generate-certs.sh --service cosignwebhook --webhook cosignwebhook --namespace cosignwebhook --secret cosignwebhook
```
## 验证容器镜像
要使用此准入控制器,你需要先用 `cosign` 签名你的镜像,然后使用以下 **一种** 验证方式:
- [公钥作为环境变量](#public-key-as-environment-variable)
- [公钥作为密钥引用](#public-key-as-secret-reference)
- [公钥作为命名空间的默认密钥](#public-key-as-default-secret-for-namespace)
此外,如果你尝试验证的镜像签名 **不在** 与镜像相同的仓库中,你需要向容器的环境变量添加 `COSIGN_REPOSITORY`:
```
# 工作负载容器规范中的
env:
- name: COSIGN_REPOSITORY
value: myregistry.io/signatures
```
此选项类似于 `cosign verify` 和 `cosign sign` 命令行工具中使用的 `COSIGN_REPOSITORY` 环境变量,用于指定镜像签名所在的仓库(如果不在与镜像相同的仓库中)。
### 公钥作为环境变量
在第一个容器的容器规范中添加你的 Cosign 公钥作为环境变量:
```
env:
- name: COSIGNPUBKEY
value: |
-----BEGIN PUBLIC KEY-----
MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEGOrnlJ1lFxAFTY2LF1vCuVHNZr9H
QryRDinn+JhPrDYR2wqCP+BUkeWja+RWrRdmskA0AffxBzaQrN/SwZI6fA==
-----END PUBLIC KEY-----
```
### 公钥作为密钥引用
不要在部署中硬编码公钥,你可以使用密钥引用。只要密钥包含有效的公钥,密钥和密钥名称可以自由命名:
```
apiVersion: v1
kind: Secret
data:
COSIGNPUBKEY: LS0tLS1CRUdJTiBQVUJMSUMgS0VZLS0tLS0KTUZrd0V3WUhLb1pJemowQ0FRWUlLb1pJemowREFRY0RRZ0FFS1BhWUhnZEVEQ3ltcGx5emlIdkJ5UjNxRkhZdgppaWxlMCtFMEtzVzFqWkhJa1p4UWN3aGsySjNqSm5VdTdmcjcrd05DeENkVEdYQmhBSTJveE1LbWx3PT0KLS0tLS1FTkQgUFVCTElDIEtFWS0tLS0t
metadata:
name: cosign_secret # Can be choosen freely
type: Opaque
```
```
env:
- name: COSIGNPUBKEY
valueFrom:
secretKeyRef:
name: cosign_secret # Must be equal to metadata.name of the secrect
key: COSIGNPUBKEY
```
### 公钥作为命名空间的默认密钥
为命名空间中的所有镜像创建一个默认密钥,准入控制器在验证此命名空间中的镜像时会始终查找该密钥:
```
apiVersion: v1
kind: Secret
data:
COSIGNPUBKEY: LS0tLS1CRUdJTiBQVUJMSUMgS0VZLS0tLS0KTUZrd0V3WUhLb1pJemowQ0FRWUlLb1pJemowREFRY0RRZ0FFS1BhWUhnZEVEQ3ltcGx5emlIdkJ5UjNxRkhZdgppaWxlMCtFMEtzVzFqWkhJa1p4UWN3aGsySjNqSm5VdTdmcjcrd05DeENkVEdYQmhBSTJveE1LbWx3PT0KLS0tLS1FTkQgUFVCTElDIEtFWS0tLS0t
metadata:
name: cosignwebhook
type: Opaque
```
密钥名称必须为 `cosignwebhook`,密钥键为 `COSIGNPUBKEY`。`COSIGNPUBKEY` 的值必须与用于签名镜像的公钥匹配。
##
## 测试
要测试准入控制器,可以运行以下命令:
```
# 单元测试
make test-unit
# E2E 测试
make e2e-prep
make test-e2e
```
### E2E 测试
E2E 测试需要一个正在运行的 Kubernetes 集群。目前,命名空间和准入控制器是通过辅助的 Makefile 目标进行部署的。要仅运行测试,需要以下条件:
* Docker
* cosign (v3)
要运行完整的 E2E 测试,需要按顺序执行以下步骤:
* 创建用于测试的 k3d 本地集群和本地镜像仓库 (`make e2e-cluster`)
* 生成签名密钥 (`make e2e-keys`)
* 构建并签名一个新的 `cosignwebhook` 镜像(使用临时密钥)(`make e2e-images`)
* 将镜像推送到本地注册表并部署到测试集群 (`make e2e-deploy`)
要执行以上所有操作,只需运行 `make e2e-prep`。每个步骤也可以单独执行。要清理 E2E 环境,运行 `make e2e-cleanup`。
这将删除 E2E 准备过程中创建的所有内容。如果已经创建了集群和密钥,并且你正在积极测试新代码,可以运行 `make e2e-images e2e-deploy test-e2e` 来测试你的更改。
如果你在 Apple 设备上运行测试,可能需要停用 k3s DNS 修复(已在 Makefile 中实现)。如果集群中的容器因跳过修复而无法启动,可以将 `K3S_FIX_DNS` 设置回 `1`(在 `e2e-cluster` 目标中)。
## 本地构建
```
CGO_ENABLED=0 GOOS=linux go build -a -ldflags '-extldflags "-static"' -o cosignwebhook
```
## 致谢
* Bruno Bressi
* Frank Kloeker
分享即关怀。如果你对代码有问题或希望改进,欢迎打开 Issue 或 Pull 请求。
该控制器的灵感来源于 [@pipo02mix](https://github.com/pipo02mix/grumpy),这是学习准入控制器基础知识的好地方
此准入控制器会监听 Pod 创建事件,并验证找到的所有容器镜像是否具有现有的
RSA 公钥(如果存在)。
# Helm 安装
```
helm -n cosignwebhook upgrade -i cosignwebhook oci://ghcr.io/eumel8/charts/cosignwebhook --versi
on 5.0.0 --create-namespace
```
此安装方式具有以下优势:
* 自动生成 TLS 密钥对
* 自动设置 ServiceMonitor 和 Grafana 仪表盘
如果你使用自己的镜像,你需要先对其进行签名。不要忘记修改 `cosign.scwebhook.key` 值为你的
公钥,用于签名镜像。
# 清单文件安装
作为集群管理员,创建一个命名空间并安装准入控制器:
```
kubectl create namespace cosignwebhook
kubectl -n cosignwebhook apply -f manifests/rbac.yaml
kubectl -n cosignwebhook apply -f manifests/manifest.yaml
```
该清单包含一个自签名的示例 CA、TLS 证书和密钥。这仅用于演示效果,你应该生成自己的证书,参见下文:
## 证书生成
运行 `hack` 文件夹中的生成证书脚本,以生成 TLS 密钥对和准入控制器的 CA 证书:
```
generate-certs.sh --service cosignwebhook --webhook cosignwebhook --namespace cosignwebhook --secret cosignwebhook
```
## 验证容器镜像
要使用此准入控制器,你需要先用 `cosign` 签名你的镜像,然后使用以下 **一种** 验证方式:
- [公钥作为环境变量](#public-key-as-environment-variable)
- [公钥作为密钥引用](#public-key-as-secret-reference)
- [公钥作为命名空间的默认密钥](#public-key-as-default-secret-for-namespace)
此外,如果你尝试验证的镜像签名 **不在** 与镜像相同的仓库中,你需要向容器的环境变量添加 `COSIGN_REPOSITORY`:
```
# 工作负载容器规范中的
env:
- name: COSIGN_REPOSITORY
value: myregistry.io/signatures
```
此选项类似于 `cosign verify` 和 `cosign sign` 命令行工具中使用的 `COSIGN_REPOSITORY` 环境变量,用于指定镜像签名所在的仓库(如果不在与镜像相同的仓库中)。
### 公钥作为环境变量
在第一个容器的容器规范中添加你的 Cosign 公钥作为环境变量:
```
env:
- name: COSIGNPUBKEY
value: |
-----BEGIN PUBLIC KEY-----
MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEGOrnlJ1lFxAFTY2LF1vCuVHNZr9H
QryRDinn+JhPrDYR2wqCP+BUkeWja+RWrRdmskA0AffxBzaQrN/SwZI6fA==
-----END PUBLIC KEY-----
```
### 公钥作为密钥引用
不要在部署中硬编码公钥,你可以使用密钥引用。只要密钥包含有效的公钥,密钥和密钥名称可以自由命名:
```
apiVersion: v1
kind: Secret
data:
COSIGNPUBKEY: LS0tLS1CRUdJTiBQVUJMSUMgS0VZLS0tLS0KTUZrd0V3WUhLb1pJemowQ0FRWUlLb1pJemowREFRY0RRZ0FFS1BhWUhnZEVEQ3ltcGx5emlIdkJ5UjNxRkhZdgppaWxlMCtFMEtzVzFqWkhJa1p4UWN3aGsySjNqSm5VdTdmcjcrd05DeENkVEdYQmhBSTJveE1LbWx3PT0KLS0tLS1FTkQgUFVCTElDIEtFWS0tLS0t
metadata:
name: cosign_secret # Can be choosen freely
type: Opaque
```
```
env:
- name: COSIGNPUBKEY
valueFrom:
secretKeyRef:
name: cosign_secret # Must be equal to metadata.name of the secrect
key: COSIGNPUBKEY
```
### 公钥作为命名空间的默认密钥
为命名空间中的所有镜像创建一个默认密钥,准入控制器在验证此命名空间中的镜像时会始终查找该密钥:
```
apiVersion: v1
kind: Secret
data:
COSIGNPUBKEY: LS0tLS1CRUdJTiBQVUJMSUMgS0VZLS0tLS0KTUZrd0V3WUhLb1pJemowQ0FRWUlLb1pJemowREFRY0RRZ0FFS1BhWUhnZEVEQ3ltcGx5emlIdkJ5UjNxRkhZdgppaWxlMCtFMEtzVzFqWkhJa1p4UWN3aGsySjNqSm5VdTdmcjcrd05DeENkVEdYQmhBSTJveE1LbWx3PT0KLS0tLS1FTkQgUFVCTElDIEtFWS0tLS0t
metadata:
name: cosignwebhook
type: Opaque
```
密钥名称必须为 `cosignwebhook`,密钥键为 `COSIGNPUBKEY`。`COSIGNPUBKEY` 的值必须与用于签名镜像的公钥匹配。
##
## 测试
要测试准入控制器,可以运行以下命令:
```
# 单元测试
make test-unit
# E2E 测试
make e2e-prep
make test-e2e
```
### E2E 测试
E2E 测试需要一个正在运行的 Kubernetes 集群。目前,命名空间和准入控制器是通过辅助的 Makefile 目标进行部署的。要仅运行测试,需要以下条件:
* Docker
* cosign (v3)
要运行完整的 E2E 测试,需要按顺序执行以下步骤:
* 创建用于测试的 k3d 本地集群和本地镜像仓库 (`make e2e-cluster`)
* 生成签名密钥 (`make e2e-keys`)
* 构建并签名一个新的 `cosignwebhook` 镜像(使用临时密钥)(`make e2e-images`)
* 将镜像推送到本地注册表并部署到测试集群 (`make e2e-deploy`)
要执行以上所有操作,只需运行 `make e2e-prep`。每个步骤也可以单独执行。要清理 E2E 环境,运行 `make e2e-cleanup`。
这将删除 E2E 准备过程中创建的所有内容。如果已经创建了集群和密钥,并且你正在积极测试新代码,可以运行 `make e2e-images e2e-deploy test-e2e` 来测试你的更改。
如果你在 Apple 设备上运行测试,可能需要停用 k3s DNS 修复(已在 Makefile 中实现)。如果集群中的容器因跳过修复而无法启动,可以将 `K3S_FIX_DNS` 设置回 `1`(在 `e2e-cluster` 目标中)。
## 本地构建
```
CGO_ENABLED=0 GOOS=linux go build -a -ldflags '-extldflags "-static"' -o cosignwebhook
```
## 致谢
* Bruno Bressi 标签:APT组织, CI/CD 安全, Cosign, EVTX分析, Go, Grafana, Helm, PyVis, RSA, Ruby工具, SLSA, TLS, Webhook, Web截图, 代码签名, 公钥, 准入控制器, 力导向图, 子域名突变, 容器安全, 数据投毒防御, 日志审计, 服务监控, 特权提升, 签名验证, 自动化部署, 镜像签名, 防御工具, 验证准入控制器