请参考:
使用 Docker、Cosign 和 Kyverno 簽名和驗證容器鏡像:完整指南:🔗 保护 CI/CD 管道以供生产部署 🔗medium.com 亚马逊EKS集群上的软件供应链安全,Nirmata 发布了这篇博客文章,描述了如何在Amazon EKS集群上使用Kyverno和Cosign进行镜像验证的方法……更多详情请参阅nirmata.comhttps://kyverno.io/docs/writing-policies/verify-images/sigstore/
前提条件:
1-创建一个具有管理员权限的用户(在实际使用中,应适当限制访问权限)并生成访问密钥和秘密访问密钥,或为EC2实例创建一个具有同样权限的IAM角色。
2-ECR registry 应该已经在您的 AWS 账户中,或者您可以创建一个。我们将使用 us-east-1 地区进行设置。
步骤 1: 启动一个 Amazon Linux 2 AMI 的 EC2 实例,并配置 AWS CLI 和 EC2 IAM 角色。
连接到此实例并运行以下指令来安装所需的包。
sudo yum install -y git docker jq # 安装必要的工具
sudo service docker start
sudo curl --silent --location -o /usr/local/bin/kubectl \
https://s3.us-west-2.amazonaws.com/amazon-eks/1.28.5/2024-01-04/bin/linux/amd64/kubectl # 下载并安装 kubectl 客户端
sudo chmod +x /usr/local/bin/kubectl
curl --silent --location "https://github.com/weaveworks/eksctl/releases/latest/download/eksctl_$(uname -s)_amd64.tar.gz" | tar xz -C /tmp
sudo mv -v /tmp/eksctl /usr/local/bin
curl -fsSL -o get_helm.sh https://raw.githubusercontent.com/helm/helm/main/scripts/get-helm-3 # 下载 Helm 安装脚本
chmod 700 get_helm.sh # 设置脚本可执行权限
./get_helm.sh
sudo usermod -aG docker ec2-user # 将 ec2-user 用户添加到 docker 组
sudo su - $USER # 切换到当前用户
docker ps # 列出正在运行的容器
步骤 2: 按照 https://docs.aws.amazon.com/cli/latest/userguide/getting-started-install.html#getting-started-install-instructions 操作,将 AWS CLI 升级到 v2。
接下来,创建一个名为 eks-cluster-config.yaml 的文件,并包含以下内容。
# 集群API版本
apiVersion: eksctl.io/v1alpha5
# 资源类型
kind: ClusterConfig
# 元数据
metadata:
# 集群名称
name: eks-cluster
# 地域
region: us-east-1
# Kubernetes版本
version: '1.28'
# IAM配置
iam:
# 开启OIDC身份验证
withOIDC: true
# 可用区
availabilityZones: ['us-east-1a','us-east-1b','us-east-1c']
# Fargate配置文件
fargateProfiles:
- # 名称
name: defaultfp
# 选择器
selectors:
- # 命名空间
namespace: default
- # 命名空间
namespace: kube-system
- # 命名空间
namespace: kyverno
# uninterrupted original English fields
cloudWatch:
# 集群日志
clusterLogging:
# 启用的日志类型
enableTypes: ["*"]
接下来运行以下命令来创建我们的EKS集群(Amazon Elastic Kubernetes Service集群)。
eksctl 创建集群 -f eks-cluster-config.yaml
大约需要20分钟才能准备好。
步骤 4: 能否通过以下命令连接到您的集群:
aws eks update-kubeconfig --region us-east-1 --name eks-cluster
kubectl get nodes
步骤5: 创建一个名为index.html的文件,文件内容如下:
<!doctype html>
<html lang="zh">
<head>
<meta charset="utf-8">
<title>使用Docker的Nginx</title>
</head>
<body>
<h2>Nginx容器v1的问候</h2>
</body>
</html>
有一个内容如下所示的Dockerfile:
从最新的nginx镜像开始,然后复制当前目录下的index.html到nginx服务器的指定位置。
构建并推送镜像到ECR注册表。
首先,运行命令 `aws ecr get-login-password --region us-east-1 | docker login --username AWS --password-stdin <账号ID>.dkr.ecr.us-east-1.amazonaws.com` 以完成身份验证。
然后,构建 Docker 镜像 `docker build -t app-api:v1 .`。
接下来,为镜像添加标签 `docker tag app-api:v1 <账号ID>.dkr.ecr.us-east-1.amazonaws.com/app-api:v1`。
最后,推送镜像到 Amazon ECR `docker push <账号ID>.dkr.ecr.us-east-1.amazonaws.com/app-api:v1`。
# app-api 是应用API的镜像名称。
步骤 6:创建名为 deployment.yaml 的文件,内容如下:
apiVersion: apps/v1
kind: Deployment
metadata:
name: nginx-deployment
spec:
replicas: 1
selector:
matchLabels:
app: nginx
template:
metadata:
labels:
app: nginx
spec:
containers:
- name: nginx
image: <ECR_IMAGE_URL>
ports:
- containerPort: 80
通过运行 kubectl apply -f deployment.yaml 这条命令来创建部署。
第七步: 运行以下命令来安装该工具 kyverno。
kubectl create namespace kyverno # 创建名为kyverno的命名空间
kubectl create secret docker-registry aws-registry -n kyverno \
--docker-server=<AWS_ACCOUNT_ID>.dkr.ecr.us-east-1.amazonaws.com \
--docker-username=AWS \
--docker-password=demo # 创建名为aws-registry的docker注册表密钥
helm repo add kyverno https://kyverno.github.io/kyverno/ # 添加kyverno Helm仓库
helm repo update # 更新Helm仓库
helm install kyverno --namespace kyverno kyverno/kyverno --create-namespace --set 'extraArgs={--imagePullSecrets=aws-registry}' # 安装kyverno并设置额外的参数
eksctl create iamserviceaccount --name kyverno-admission-controller --namespace kyverno --cluster eks-cluster --attach-policy-arn arn:aws:iam::aws:policy/AmazonEC2ContainerRegistryReadOnly --override-existing-serviceaccounts --approve # 创建IAM服务账户并附加权限策略
kubectl edit deployment kyverno-admission-controller -n kyverno # 编辑名为kyverno-admission-controller的部署
在参数部分这一行代码
--镜像拉取凭证=aws-registry
到目前为止,我们已经部署了nginx和kyverno。
第八步:我们需要部署一个定时任务来定期获取 ECR 注册表凭证。
创建一个名为cron.yaml的文件,并将以下内容写入文件。请将<AWS_ACCOUNT_ID>替换为您的AWS账户ID。
apiVersion: rbac.authorization.k8s.io/v1
kind: 角色
metadata:
namespace: kyverno
name: secret-creator-role
rules:
- apiGroups: [""]
resources: ["secrets"]
verbs: ["create", "get", "update", "delete", "list", "watch"]
---
apiVersion: rbac.authorization.k8s.io/v1
kind: 角色绑定
metadata:
name: secret-creator-binding
namespace: kyverno
subjects:
- kind: 服务账号
name: kyverno-admission-controller
namespace: kyverno
roleRef:
kind: 角色
name: secret-creator-role
apiGroup: rbac.authorization.k8s.io
---
apiVersion: batch/v1
kind: 定时作业
metadata:
name: aws-registry-credential-cron
namespace: kyverno
spec:
调度: "* */8 * * *"
successfulJobsHistoryLimit: 2
暂停: false
jobTemplate:
spec:
template:
spec:
serviceAccountName: kyverno-admission-controller
containers:
- name: ecr-registry-helper
image: omarxs/awskctl:v1.0
imagePullPolicy: IfNotPresent
command:
- /bin/bash
- -c
- |-
DOCKER_REGISTRY_SERVER=https://<AWS_ACCOUNT_ID>.dkr.ecr.us-east-1.amazonaws.com
DOCKER_USER=AWS
AWS_REGION=us-east-1
ECR_TOKEN="$(aws ecr get-login-password --region ${AWS_REGION})"
kubectl 删除密钥 --ignore-not-found aws-registry -n kyverno
kubectl 创建密钥 docker-registry aws-registry -n kyverno --docker-server=${DOCKER_REGISTRY_SERVER} --docker-username=${DOCKER_USER} --docker-password=${ECR_TOKEN}
echo "密钥已成功更新,更新时间:$(date)"
restartPolicy: Never
要应用它,可以通过 kubectl apply -f cron.yaml 命令。
我们也需要手动运行此任务一次以获取到ECR凭证。请运行以下命令,以执行此操作。
kubectl create job \
--from=cronjob/aws-registry-credential-cron -n kyverno aws-registry-credential-cron-manual-001
# 手动创建一个作业以刷新 AWS 注册表凭证
使用以下命令来检查任务日志。
运行以下命令来查看日志:
kubectl logs job/aws-registry-credential-cron-manual-001 -n kyverno
步骤9: 我们现在将使用以下命令来安装 cosign。
curl -O -L "https://github.com/sigstore/cosign/releases/latest/download/cosign-linux-amd64" cosign-linux-amd64 # 下载最新版本的cosign-linux-amd64
sudo mv cosign-linux-amd64 /usr/local/bin/cosign # 将cosign-linux-amd64移动到/usr/local/bin目录下
sudo chmod +x /usr/local/bin/cosign # 设置cosign文件的执行权限
使用以下命令生成密钥对
# Your command to generate a key pair goes here
运行 `cosign generate-key-pair` #您可以选择不为私钥设置密码
步骤 10: 接下来,我们将创建一个集群策略,限制在 EKS 集群中部署未签名的镜像。请创建一个名为 cluster-policy.yaml 的文件,并包含以下内容。相应地替换 <AWS_ACCOUNT_ID> 和 <ECR_REGISTRY_NAME>。请使用步骤 9 中生成的 cosign.pub 文件中的公钥内容。
apiVersion: kyverno.io/v1
kind: ClusterPolicy
metadata:
name: 检查签名镜像
spec:
validationFailureAction: 强制执行
background: false
webhookTimeoutSeconds: 30
failurePolicy: 失败策略
rules:
- name: 检查镜像签名
match:
any:
- resources:
kinds:
- Pod
verifyImages:
- image: "<AWS_ACCOUNT_ID>.dkr.ecr.us-east-1.amazonaws.com/*"
# 请用自己的公钥替换
key: |-
-----BEGIN 公钥-----
MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEM1z+vdFrcLuuxHjQdmLFn3icm0Xq
Rr4bTktxmpzITojnPDTiMcQBIXZY4o/+hrZ09GJ7rXEcYLns/q/iWWBiGQ==
-----END 公钥-----
运行 kubectl apply -f cluster-policy.yaml
步骤 11: 如下重新启动 nginx 服务
``nginx命令```
kubectl 滚动更新 deployment/nginx-deployment
它会被阻止
由于用于Nginx部署的ECR图像(<AWS_ACCOUNT_ID>.dkr.ecr.us-east-1.amazonaws.com/app-api:v1)没有经过签名。
步骤12: 让我们用以下命令签名ECR镜像,并相应替换<AWS_ACCOUNT_ID>和<ECR_REGISTRY_NAME>的值。
cosign sign --key cosign.key <AWS_ACCOUNT_ID>.dkr.ecr.us-east-1.amazonaws.com/<ECR_REGISTRY_NAME>:v1.
上传至“https://rekor.sigstore.dev”的透明日志记录时按 y 确认。
现在如果你检查你的Amazon ECR注册表,你会看到你的镜像及其对应的SHA256哈希值。
步骤13: 使用如下命令现在重新启动nginx。
kubectl rollout restart deployment/nginx-deployment
此命令用于重启名为 nginx-deployment 的部署。
它会成功重启的。
注意: 如果 rollout 重启失败了,尝试创建一个新的部署或更改现有部署的镜像名称为。
这是一条用于访问Amazon ECR仓库的URL,格式为:<AWS_ACCOUNT_ID>.dkr.ecr.us-east-1.amazonaws.com/<ECR_REPO>:<TAG>
到
<AWS_ACCOUNT_ID>.dkr.ecr.us-east-1.amazonaws.com/<ECR_REPO>:<TAG>@sha256:<SHA>
rollout重新启动可能失败的原因在于,在重新启动过程中,图像标签未能自动更新为图像SHA。要解决此问题,您可以这样创建另一个策略:
apiVersion: kyverno.io/v1
kind: ClusterPolicy
metadata:
name: 将镜像解析为摘要
spec:
background: false
rules:
- name: 解析为摘要值
match:
any:
- resources:
kinds:
- Pod
preconditions:
all:
- key: "{{ request.operation || 'BACKGROUNDED'}}"
operator: NotEquals
value: DELETE
mutate:
foreach:
- list: "request.object.spec.containers"
context:
- name: 解析引用
imageRegistry:
reference: "{{ element.image }}"
jmesPath: "resolvedImage"
patchStrategicMerge:
spec:
containers:
- name: "{{ element.name }}"
image: "{{ 解析引用 }}"
现在重新启动应该没问题了。