Source Code Repository Verification
在 Tekton Chains 中,可以收集来自 PipelineRun
的特定输入和输出,并将其记录在 SLSA Provenance 中。
我们可以利用此功能将代码仓库信息包含在 SLSA Provenance 信息中,随后在 kyverno 中验证代码仓库。
目录
功能概述
该方法使用 Chains 自动为构建的镜像生成 SLSA Provenance,然后使用 Kyverno 验证该溯源信息:
- 配置 Tekton Chains 自动为构建的镜像生成 SLSA Provenance。
- 使用
git
Tekton Task 获取源代码仓库。
- 使用
buildah
Tekton Task 构建镜像。
- 在 Pipeline 的结果中声明
git
和 buildah
的结果信息,方便记录镜像的源代码仓库和提交信息。
- 配置 Kyverno 规则以验证源代码仓库。
- 使用该镜像创建 Pod 以验证源代码仓库。
使用场景
以下场景需要参考本文档的指导:
- 在 Kubernetes 集群中使用 Kyverno 实现源代码仓库验证
- 强制安全策略,仅允许从特定源代码仓库构建的镜像部署
- 在 CI/CD 流水线中设置自动化的源代码仓库验证
- 确保生产环境中镜像的溯源和源代码真实性
- 通过验证容器镜像的源代码来源,实现供应链安全控制
前提条件
- 已安装 Tekton Pipelines、Tekton Chains 和 Kyverno 的 Kubernetes 集群
- 支持镜像推送的镜像仓库
- 已安装并配置好访问集群的
kubectl
CLI
- 已安装
cosign
CLI 工具
- 已安装
jq
CLI 工具
流程概览
步骤 | 操作 | 说明 |
---|
1 | 生成签名密钥 | 使用 cosign 创建用于签名工件的密钥对 |
2 | 设置认证 | 配置镜像推送的仓库凭证 |
3 | 配置 Tekton Chains | 设置 Chains 使用 OCI 存储并配置签名,禁用 TaskRun 的 SLSA Provenance |
4 | 创建示例流水线 | 创建包含 git clone 任务和 buildah 任务的流水线定义 |
5 | 运行示例流水线 | 创建并运行配置正确的 PipelineRun |
6 | 等待签名 | 等待 PipelineRun 被 Chains 签名 |
7 | 获取镜像信息 | 从 PipelineRun 中提取镜像 URI 和摘要 |
8 | (可选)获取 SLSA Provenance | 获取并验证 SLSA Provenance 证明 |
9 | 使用 Kyverno 验证 | 创建并应用 Kyverno 策略验证镜像的源代码仓库 |
10 | 清理 | 删除测试资源和策略 |
逐步操作指南
步骤 1-3:基础配置
这些步骤与快速开始:签名溯源指南相同。请按照该指南完成:
步骤 4:创建示例流水线
在之前的镜像构建流水线中,添加一个 git
clone 任务,并将 git
任务的输出保存到 PipelineRun
的 results
中。
apiVersion: tekton.dev/v1
kind: Pipeline
metadata:
name: chains-demo-3
spec:
params:
- default: |-
echo "Simulate cloning the code and write the repository URL and commit message into the results."
# This commit sha must be a valid commit sha [0-9a-f]{40}.
cat << 'EOF' > $(results.array-result.path)
[
"https://github.com/tektoncd/pipeline",
"cccccaaaa0000000000000000000000000000000"
]
EOF
echo -e "\nResults:"
echo "-------------------"
cat $(results.array-result.path)
echo "-------------------"
echo -e "\nClone successfully!"
description: A script to simulate cloning the code and write the repository URL and commit message into the results.
name: generate-git-clone-results
type: string
- default: |-
echo "Generate a Dockerfile for building an image."
cat << 'EOF' > Dockerfile
FROM ubuntu:latest
ENV TIME=1
EOF
echo -e "\nDockerfile contents:"
echo "-------------------"
cat Dockerfile
echo "-------------------"
echo -e "\nDockerfile generated successfully!"
description: A script to generate a Dockerfile for building an image.
name: generate-dockerfile
type: string
- default: <registry>/test/chains/demo-3:latest
description: The target image address built
name: image
type: string
results:
- description: first image artifact output
name: first_image_ARTIFACT_OUTPUTS
type: object
value:
digest: $(tasks.build-image.results.IMAGE_DIGEST)
uri: $(tasks.build-image.results.IMAGE_URL)
- description: first repo artifact input
name: source_repo_ARTIFACT_INPUTS
type: object
value:
digest: sha1:$(tasks.git-clone.results.array-result[1])
uri: $(tasks.git-clone.results.array-result[0])
tasks:
- name: git-clone
params:
- name: script
value: $(params.generate-git-clone-results)
taskRef:
params:
- name: kind
value: task
- name: catalog
value: catalog
- name: name
value: run-script
- name: version
value: "0.1"
resolver: hub
timeout: 30m0s
workspaces:
- name: source
workspace: source
- name: generate-dockerfile
params:
- name: script
value: $(params.generate-dockerfile)
runAfter:
- git-clone
taskRef:
params:
- name: kind
value: task
- name: catalog
value: catalog
- name: name
value: run-script
- name: version
value: "0.1"
resolver: hub
timeout: 30m0s
workspaces:
- name: source
workspace: source
- name: build-image
params:
- name: IMAGES
value:
- $(params.image)
- name: TLS_VERIFY
value: "false"
runAfter:
- generate-dockerfile
taskRef:
params:
- name: kind
value: task
- name: catalog
value: catalog
- name: name
value: buildah
- name: version
value: "0.9"
resolver: hub
timeout: 30m0s
workspaces:
- name: source
workspace: source
- name: dockerconfig
workspace: dockerconfig
workspaces:
- name: source
description: The workspace for source code.
- name: dockerconfig
description: The workspace for Docker configuration.
TIP
本教程通过在流水线中内联生成 Dockerfile
和 git-clone
任务输出,演示了简化的工作流。
在生产环境中,通常应:
- 使用
git-clone
任务从仓库拉取源代码
- 使用源代码中的 Dockerfile 构建镜像
- 该方法确保了版本控制的正确性,并保持代码与流水线配置的分离
YAML 字段说明
- 大部分字段与步骤 4:创建示例流水线相同,以下仅介绍差异。
params
generate-git-clone-results
:模拟克隆代码并将仓库 URL 和提交信息写入结果的脚本。
results
source_repo_ARTIFACT_INPUTS
:源代码仓库 URL 和提交信息。
- 此格式符合 Tekton Chains 规范,详情请参见上文的 Tekton Chains 类型提示。
需调整配置
params
:
generate-dockerfile
image
:
保存为 chains.demo-3.pipeline.yaml
文件并应用:
$ export NAMESPACE=<default>
$ kubectl apply -n $NAMESPACE -f chains.demo-3.pipeline.yaml
步骤 5:运行示例流水线
这是一个 PipelineRun 资源,用于运行流水线。
apiVersion: tekton.dev/v1
kind: PipelineRun
metadata:
generateName: chains-demo-3-
spec:
pipelineRef:
name: chains-demo-3
taskRunTemplate:
serviceAccountName: <default>
workspaces:
- name: dockerconfig
secret:
secretName: <registry-credentials>
- name: source
volumeClaimTemplate:
spec:
accessModes:
- ReadWriteOnce
resources:
requests:
storage: 1Gi
storageClassName: <nfs>
YAML 字段说明
保存为 chains.demo-3.pipelinerun.yaml
文件并应用:
$ export NAMESPACE=<default>
# 在命名空间中创建 PipelineRun 资源
$ kubectl create -n $NAMESPACE -f chains.demo-3.pipelinerun.yaml
等待 PipelineRun 完成。
$ kubectl get pipelinerun -n $NAMESPACE -w
chains-demo-3-<xxxxx> True Succeeded 2m 2m
步骤 6:等待 PipelineRun 被签名
等待 PipelineRun 带有 chains.tekton.dev/signed: "true"
注解。
$ export NAMESPACE=<default>
$ export PIPELINERUN_NAME=<chains-demo-3-xxxxx>
$ kubectl get pipelinerun -n $NAMESPACE $PIPELINERUN_NAME -o yaml | grep "chains.tekton.dev/signed"
chains.tekton.dev/signed: "true"
当 PipelineRun 有 chains.tekton.dev/signed: "true"
注解时,表示镜像已被签名。
步骤 7:从 PipelineRun 获取镜像信息
# 获取镜像 URI
$ export IMAGE_URI=$(kubectl get pipelinerun -n $NAMESPACE $PIPELINERUN_NAME -o jsonpath='{.status.results[?(@.name=="first_image_ARTIFACT_OUTPUTS")].value.uri}')
# 获取镜像摘要
$ export IMAGE_DIGEST=$(kubectl get pipelinerun -n $NAMESPACE $PIPELINERUN_NAME -o jsonpath='{.status.results[?(@.name=="first_image_ARTIFACT_OUTPUTS")].value.digest}')
# 组合镜像 URI 和摘要,形成完整镜像引用
$ export IMAGE=$IMAGE_URI@$IMAGE_DIGEST
# 输出镜像引用
$ echo $IMAGE
<registry>/test/chains/demo-3:latest@sha256:93635f39cb31de5c6988cdf1f10435c41b3fb85570c930d51d41bbadc1a90046
该镜像将用于验证源代码仓库。
步骤 8:(可选)获取 SLSA Provenance 证明
TIP
如果您对 SLSA Provenance 证明内容感兴趣,可以继续阅读以下内容。
更多关于 SLSA Provenance 证明的详情,请参见 SLSA Provenance
根据获取签名公钥章节获取签名公钥。
# 禁用 tlog 上传,启用私有基础设施
$ export COSIGN_TLOG_UPLOAD=false
$ export COSIGN_PRIVATE_INFRASTRUCTURE=true
$ export IMAGE=<<registry>/test/chains/demo-3:latest@sha256:db2607375049e8defa75a8317a53fd71fd3b448aec3c507de7179ded0d4b0f20>
$ cosign verify-attestation --key cosign.pub --type slsaprovenance $IMAGE | jq -r '.payload | @base64d' | jq -s
输出类似如下,包含 SLSA Provenance 证明。
SLSA Provenance 证明
{
"_type": "https://in-toto.io/Statement/v0.1",
"subject": [
{
"name": "<registry>/test/chains/demo-3:latest",
"digest": {
"sha256": "db2607375049e8defa75a8317a53fd71fd3b448aec3c507de7179ded0d4b0f20"
}
}
],
"predicateType": "https://slsa.dev/provenance/v0.2",
"predicate": {
"buildConfig": {
"tasks": null
},
"buildType": "tekton.dev/v1beta1/PipelineRun",
"builder": {
"id": "https://alauda.io/builders/tekton/v1"
},
"invocation": {
"parameters": {
"image": "<registry>/test/chains/demo-3:latest"
}
},
"materials": [
{
"digest": {
"sha256": "bad5d84ded24307d12cacc9ef37fc38bce90ea5d00501f43b27d0c926be26f19"
},
"uri": "oci://<registry>/devops/tektoncd/hub/run-script"
},
{
"digest": {
"sha1": "cccccaaaa0000000000000000000000000000000"
},
"uri": "https://github.com/tektoncd/pipeline"
}
],
"metadata": {
"buildFinishedOn": "2025-06-06T10:28:21Z",
"buildStartedOn": "2025-06-06T10:27:34Z"
}
}
}
字段说明
predicateType
:谓词类型。
predicate
:
buildConfig
:
buildType
:构建类型,此处为 tekton.dev/v1beta1/PipelineRun
。
builder
:
id
:构建者 ID,此处为 https://alauda.io/builders/tekton/v1
。
invocation
:
materials
:构建材料。
uri
:
oci://<registry>/devops/tektoncd/hub/run-script
:使用的任务镜像。
https://github.com/tektoncd/pipeline
:任务的源代码仓库。
metadata
:构建元数据。
buildFinishedOn
:构建完成时间。
buildStartedOn
:构建开始时间。
步骤 9:使用 Kyverno 验证镜像源代码仓库限制
步骤 9.1:创建 Kyverno 策略,仅允许从特定源代码仓库构建的镜像部署
策略如下:
apiVersion: kyverno.io/v1
kind: ClusterPolicy
metadata:
name: verify-code-repository-material
spec:
webhookConfiguration:
failurePolicy: Fail
timeoutSeconds: 30
background: false
rules:
- name: check-image
match:
any:
- resources:
kinds:
- Pod
namespaces:
- policy
verifyImages:
- imageReferences:
- "*"
# - "<registry>/test/*"
skipImageReferences:
- "ghcr.io/trusted/*"
failureAction: Enforce
verifyDigest: false
required: false
useCache: false
imageRegistryCredentials:
allowInsecureRegistry: true
secrets:
# 凭证需存在于 kyverno 所在命名空间
- registry-credentials
attestations:
- type: https://slsa.dev/provenance/v0.2
attestors:
- entries:
- keys:
publicKeys: |- # <- 签名者的公钥
-----BEGIN PUBLIC KEY-----
MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEFZNGfYwn7+b4uSdEYLKjxWi3xtP3
UkR8hQvGrG25r0Ikoq0hI3/tr0m7ecvfM75TKh5jGAlLKSZUJpmCGaTToQ==
-----END PUBLIC KEY-----
ctlog:
ignoreSCT: true
rekor:
ignoreTlog: true
conditions:
- all:
- key: "{{ buildType }}"
operator: Equals
value: "tekton.dev/v1beta1/PipelineRun"
message: "The buildType must be equal to tekton.dev/v1beta1/PipelineRun, not {{ buildType }}"
- key: "{{ materials[?starts_with(uri, 'https://github.com/tektoncd/')] | length(@) }}"
operator: GreaterThan
value: 0
message: "The materials must have at least one entry starts with https://github.com/tektoncd/, {{ materials }}"
YAML 字段说明
- 策略与镜像签名验证中的类似。
spec.rules[].verifyImages[].attestations[].conditions
:验证条件。
all
:所有条件必须满足。
key: "{{ buildType }}"
:构建类型必须等于 tekton.dev/v1beta1/PipelineRun
。
key: "{{ materials[?starts_with(uri, 'https://github.com/tektoncd/')] | length(@) }}"
:材料中必须至少有一条以 https://github.com/tektoncd/
开头。
保存为 verify-code-repository-material.yaml
并应用:
$ kubectl create -f verify-code-repository-material.yaml
clusterpolicy.kyverno.io/verify-code-repository-material created
步骤 9.2:验证策略
在定义策略的 policy
命名空间中,创建 Pod 以验证策略。
使用构建好的镜像创建 Pod。
$ export NAMESPACE=<policy>
$ export IMAGE=<<registry>/test/chains/demo-3:latest@sha256:db2607375049e8defa75a8317a53fd71fd3b448aec3c507de7179ded0d4b0f20>
$ kubectl run -n $NAMESPACE built-from-specific-repo --image=${IMAGE} -- sleep 3600
pod/built-from-specific-repo created
Pod 会成功创建。
$ kubectl get pod -n $NAMESPACE built-from-specific-repo
NAME READY STATUS RESTARTS AGE
built-from-specific-repo 1/1 Running 0 10s
将 ClusterPolicy
中的代码仓库改为其他值 https://gitlab.com/
,再次验证。
conditions:
- all:
- key: "{{ buildType }}"
operator: Equals
value: "tekton.dev/v1beta1/PipelineRun"
message: "The buildType must be equal to tekton.dev/v1beta1/PipelineRun, not {{ buildType }}"
- key: "{{ materials[?starts_with(uri, 'https://gitlab.com/')] | length(@) }}"
operator: GreaterThan
value: 0
message: "The materials must have at least one entry starts with https://gitlab.com/, {{ materials }}"
$ kubectl run -n $NAMESPACE unbuilt-from-specific-repo --image=${IMAGE} -- sleep 3600
会收到如下输出,表示 Pod 被策略阻止。
Error from server: admission webhook "mutate.kyverno.svc-fail" denied the request:
resource Pod/policy/unbuilt-from-specific-repo was blocked due to the following policies
verify-code-repository-material:
check-image: 'image attestations verification failed, verifiedCount: 0, requiredCount:
1, error: .attestations[0].attestors[0].entries[0].keys: attestation checks failed
for <registry>/test/chains/demo-3:latest and predicate https://slsa.dev/provenance/v0.2:
The materials must have at least one entry starts with https://gitlab.com/,
[{"digest":{"sha256":"bad5d84ded24307d12cacc9ef37fc38bce90ea5d00501f43b27d0c926be26f19"},"uri":"oci://<registry>/devops/tektoncd/hub/run-script"},{"digest":{"sha256":"7a63e6c2d1b4c118e9a974e7850dd3e9321e07feec8302bcbcd16653c512ac59"},"uri":"http://tekton-hub-api.tekton-pipelines:8000/v1/resource/catalog/task/run-script/0.1/yaml"},{"digest":{"sha256":"8d5ea9ecd9b531e798fecd87ca3b64ee1c95e4f2621d09e893c58ed593bfd4c4"},"uri":"oci://<registry>/devops/tektoncd/hub/buildah"},{"digest":{"sha256":"3225653d04c223be85d173747372290058a738427768c5668ddc784bf24de976"},"uri":"http://tekton-hub-api.tekton-pipelines:8000/v1/resource/catalog/task/buildah/0.9/yaml"},{"digest":{"sha1":"cccccaaaa0000000000000000000000000000000"},"uri":"https://github.com/tektoncd/pipeline"}]'
步骤 10:清理资源
删除前面步骤中创建的 Pod。
$ export NAMESPACE=<policy>
$ kubectl delete pod -n $NAMESPACE built-from-specific-repo
删除策略。
$ kubectl delete clusterpolicy verify-code-repository-material
预期结果
完成本指南后:
- 您已成功配置 Tekton Chains 生成 SLSA Provenance,及 Kyverno 进行源代码仓库验证
- 容器镜像自动包含源代码仓库信息于其 SLSA Provenance 中
- 仅允许从指定源代码仓库构建的镜像在指定命名空间中部署
- 来自未授权源代码仓库的镜像会被 Kyverno 策略自动阻止
- 通过验证容器镜像的源代码来源,实现了基础的供应链安全控制
本指南为在 CI/CD 流水线中实现供应链安全提供了基础。在生产环境中,您应:
- 配置合适的命名空间隔离和访问控制
- 实施安全的签名密钥管理
- 设置策略违规的监控和告警
- 定期轮换签名密钥并更新安全策略
- 考虑实施额外的安全控制,如漏洞扫描
参考资料