尧图网站设计 尧图网站设计YAOTU DESIGN
ARTICLE DETAIL

资讯详情

深耕网站设计与一线实操的经验洞察。

Kubernetes镜像签名验证:Connaisseur准入控制器部署与安全实践

Kubernetes镜像签名验证:Connaisseur准入控制器部署与安全实践 1. 项目概述Kubernetes集群的“守门人”在云原生和容器化技术成为主流的今天Kubernetes 集群内部署的容器镜像其来源和完整性直接关系到整个应用乃至基础设施的安全。你是否想过一个恶意或被篡改的镜像一旦被部署到生产环境会带来多大的风险传统的安全扫描工具侧重于镜像运行后的漏洞检测但如何确保在镜像被拉取和启动之前它就是那个“对”的、未被篡改的镜像呢这正是Connaisseur要解决的核心问题。简单来说Connaisseur 是一个 Kubernetes 准入控制器它扮演着集群“守门人”的角色。每当有新的 Pod、Deployment 或其他包含容器镜像的资源被创建或更新时Connaisseur 会主动拦截这个请求对其中声明的每一个容器镜像进行数字签名验证。它检查这个镜像是否由你信任的发布者签名以及其内容自签名后是否被篡改。只有验证通过的请求才会被放行否则将被直接拒绝从根本上杜绝了不可信镜像的部署。这个工具的设计理念围绕三个核心价值展开安全性、易用性和兼容性。它不仅仅是一个简单的“是/否”检查器更提供了一套可扩展的框架目前支持主流的镜像签名方案包括 Docker Content Trust/Notary v1 和 Sigstore/Cosign并计划支持 Notary v2。这意味着无论你的开发团队习惯使用哪种签名工具Connaisseur 都能无缝集成到你的 CI/CD 流水线和集群安全策略中。2. 核心原理与架构设计解析2.1 准入控制器Kubernetes 的请求拦截点要理解 Connaisseur 如何工作首先得明白 Kubernetes 准入控制器的机制。这不是 Connaisseur 独有的概念而是 Kubernetes 提供的一种强大的扩展机制。当一个 API 请求比如kubectl apply -f deployment.yaml到达 Kubernetes API Server 后在持久化到 etcd 之前会经过两个阶段的准入控制变更Mutating和验证Validating。Connaisseur 主要作为一个验证准入控制器运行。它不会修改你的请求内容比如偷偷给镜像换个标签而是专注于“验证”。它会检查请求对象如 PodSpec中所有image字段的值。一旦发现某个镜像的签名验证失败或根本没有签名它就会立即向 API Server 返回一个拒绝Deny响应并附带清晰的错误信息。整个过程对用户是透明的你只会看到kubectl报错而镜像不会被拉取Pod 也不会被创建。注意准入控制器是集群级别的组件一旦部署会影响所有命名空间除非配置了命名空间过滤。因此在将其投入生产环境前务必在测试集群中充分验证其配置避免因配置错误导致合法的部署也被阻断。2.2 信任锚与策略引擎如何决定信任谁Connaisseur 的验证逻辑核心在于其配置的信任策略。这就像你为集群设置了一份“可信发布者白名单”。策略通常通过一个 ConfigMap 或直接通过 Helm values 进行配置其核心结构定义了信任目标Trust Target 针对哪个镜像仓库或镜像模式如docker.io/library/*。验证器类型Validator Type 使用哪种签名方案进行验证例如notaryv1或cosign。信任锚Trust Anchor 这是最关键的部分即用于验证签名的公钥。对于 Notary这可能是根证书对于 Cosign这就是 Cosign 公钥。Connaisseur 需要预先配置这些公钥。其工作流程可以概括为拦截 拦截 Kubernetes API 请求。提取 解析资源对象提取所有容器镜像引用包括 Init 容器、Sidecar 等。匹配 根据镜像仓库地址匹配预先配置的信任策略。验证 调用对应的验证器如 Cosign 客户端库使用配置的公钥去镜像仓库或指定的 Rekor 透明日志中查找并验证签名。裁决 根据验证结果允许或拒绝请求。这种设计使得策略非常灵活。你可以为来自不同仓库的镜像如公司内部仓库、公有云仓库、第三方可信仓库配置不同的公钥和验证规则。2.3 支持的签名方案深度对比Connaisseur 的多方案支持是其一大亮点但理解它们之间的区别对于正确配置至关重要。Docker Content Trust / Notary v1这是 Docker 早期推出的签名方案集成在 Docker CLI 中docker trust命令。它基于 TUFThe Update Framework框架提供了一套完整的密钥管理和签名分发体系。其验证过程需要与 Notary 服务器交互。在配置 Connaisseur 时你需要提供 Notary 服务器的地址和相应的根证书作为信任锚。这种方案比较成熟但在云原生生态中其复杂性使得它逐渐被新的方案所补充。Sigstore / Cosign这是目前云原生社区的事实标准由 Red Hat、Google 等公司主导。Cosign 的设计哲学是“简单粗暴”签名存储 签名直接作为 OCI 镜像层sha256-*.sig推送到镜像仓库与镜像本身共存无需额外的元数据服务器。密钥可选 支持基于密钥对的签名也支持完全免密钥的、基于 OIDC如 GitHub Actions的“密钥less”签名大大降低了使用门槛。透明日志Rekor 所有签名事件都可以记录到公开的 Rekor 日志中提供不可篡改的证明。对于 Connaisseur 而言配置 Cosign 验证器通常更简单。如果是密钥签名只需配置对应的公钥如果是密钥less 签名则需要配置相应的 Fulcio 证书颁发机构根证书。这也是我目前更推荐的方案因为它与现代化的 CI/CD 流程如 GitHub Actions集成度更高流程更清晰。3. 从零开始部署与核心配置实战纸上得来终觉浅绝知此事要躬行。下面我们一步步在测试集群中部署和配置 Connaisseur并验证其效果。3.1 前置准备与 Helm 部署首先你需要一个可用的 Kubernetes 测试集群Minikube, Kind, K3s 或任何云上的集群。确保kubectl和helm客户端已正确配置。Connaisseur 强烈推荐使用 Helm 进行部署这能帮你管理所有复杂的 Kubernetes 资源Deployment, Service, ValidatingWebhookConfiguration 等。# 1. 克隆仓库其中包含了 Helm Chart 和文档 git clone https://github.com/sse-secure-systems/connaisseur.git cd connaisseur # 2. 查看默认的 values.yaml 配置了解可配置项 cat helm/values.yaml | head -100 # 3. 使用默认配置进行安装。--atomic 确保安装失败时完全回滚--create-namespace 自动创建命名空间。 helm install connaisseur helm/ \ --atomic \ --create-namespace \ --namespace connaisseur安装完成后使用以下命令检查关键组件是否就绪kubectl get pods,validatingwebhookconfiguration -n connaisseur你应该能看到一个名为connaisseur-xxx的 Pod 状态为Running以及一个同名的ValidatingWebhookConfiguration资源。3.2 解读与定制核心配置文件直接使用默认配置安装后Connaisseur 已经预置了对 Docker 官方镜像库docker.io/library/*和其自身测试镜像的信任策略。这很方便我们做快速测试但要用于自己的环境必须自定义配置。核心配置在于helm/values.yaml文件中的validators和policy部分。让我们创建一个自定义的my-values.yaml文件# my-values.yaml replicaCount: 2 # 生产环境建议至少2个副本以确保高可用 validators: # 定义一个名为 “my_cosign” 的验证器类型为 cosign - name: my_cosign type: cosign trust_roots: # 这里配置你的 Cosign 公钥。 # 你可以通过 cosign public-key --key cosign.key 获取 PEM 格式的公钥内容。 - name: default key: | -----BEGIN PUBLIC KEY----- YOUR_COSIGN_PUBLIC_KEY_HERE -----END PUBLIC KEY----- policy: # 定义具体的验证策略规则 - pattern: myregistry.example.com/myteam/* # 匹配你私有仓库的所有镜像 validator: my_cosign # 使用上面定义的 cosign 验证器 with: # 可选的 Cosign 特定参数例如指定签名镜像的标签模式默认查找与镜像摘要关联的签名 # args: # - --signature-tag-pattern # - “{{.Repository}}:{{.Digest}}-sig” - pattern: docker.io/library/* # 继续信任 Docker 官方镜像使用内置的默认信任锚 validator: default - pattern: * # 兜底规则对于所有其他未匹配的镜像执行什么操作 # 你可以选择 “reject” 直接拒绝或者 “allow” 允许不推荐 # 或者在排查期使用 “audit” 模式仅记录日志不拒绝。 validator: reject # 或者使用内置的 “allow” 验证器validator: allow然后使用自定义配置进行安装或升级# 首次安装 helm install connaisseur helm/ -f my-values.yaml --namespace connaisseur # 或升级现有安装 helm upgrade connaisseur helm/ -f my-values.yaml --namespace connaisseur实操心得在配置pattern时要特别注意镜像引用在 Kubernetes 中的完整格式。例如如果你从 Docker Hub 拉取nginx:latest它在 Pod Spec 中实际是docker.io/library/nginx:latest。而你的私有仓库镜像可能是myregistry.com:5000/app/frontend:v1.2.3。确保你的通配符*能正确匹配。建议先在策略中为关键应用配置精确的镜像路径再逐步扩大范围。3.3 基础功能验证与问题排查部署完成后让我们进行一些简单的测试确保 Connaisseur 正在工作。测试1运行一个已签名的官方镜像kubectl run nginx-signed-test --imagenginx:latest如果成功你会看到pod/nginx-signed-test created。这是因为默认策略信任docker.io/library/*。测试2运行一个未签名的自定义镜像假设你有一个未签名的测试镜像kubectl run unsigned-test --imagemyregistry.example.com/test:unsigned根据你的兜底策略这个请求很可能被拒绝。你会看到一个明确的错误信息例如Error from server: admission webhook connaisseur-svc.connaisseur.svc denied the request: Unable to find signed digest for image myregistry.example.com/test:unsigned. No valid trust data for myregistry.example.com/test:unsigned.恭喜这说明 Connaisseur 正在起作用测试3查看 Connaisseur 的日志以获取更详细信息当验证失败或你想了解内部决策过程时日志是最佳帮手。kubectl logs -l app.kubernetes.io/nameconnaisseur -n connaisseur --tail50日志会显示它拦截了哪个请求、提取了哪些镜像、匹配了哪条策略、调用了哪个验证器以及最终结果。常见部署问题排查Webhook 连接失败如果kubectl run报错Internal error occurred: failed calling webhook “connaisseur-svc.connaisseur.svc”这通常是网络或 TLS 问题。检查 Connaisseur Service 是否正常 (kubectl get svc -n connaisseur)以及ValidatingWebhookConfiguration中的caBundle是否已正确注入Helm 通常会自动处理。在测试环境有时需要确保集群网络插件如 Calico, Flannel允许控制平面与 Pod 之间的通信。镜像拉取策略干扰Connaisseur 只验证镜像引用不负责拉取镜像。即使签名验证通过如果镜像在节点上不存在且拉取失败如网络问题或凭证错误Pod 会处于ImagePullBackOff状态这是 Kubelet 的行为与 Connaisseur 无关需分开排查。4. 高级特性与生产环境最佳实践当基础验证跑通后我们需要考虑如何将 Connaisseur 平稳、安全地集成到生产环境中。4.1 检测模式平滑上线的安全网直接在生产环境开启强制验证是危险的一个错误的策略可能导致关键应用无法部署。Connaisseur 的检测模式就是为了这个过渡期设计的。在检测模式下Connaisseur 会执行所有的签名验证检查但即使验证失败它也不会拒绝 API 请求而是会在日志中记录一个警告级别的事件并允许部署继续进行。这为你提供了一个“观察期”你可以收集日志分析有多少现有工作负载的镜像缺乏签名而不会中断业务。启用检测模式非常简单在values.yaml中修改# values.yaml detectionMode: true # 默认为 false部署后尝试部署未签名镜像你会发现 Pod 被创建了可能因其他原因失败但 Connaisseur 的日志中会清晰记录验证失败的警告。利用这个阶段完善你的镜像签名流水线和 Connaisseur 策略。4.2 命名空间级验证实现安全边界在大型多团队集群中可能不是所有团队都立即准备好了镜像签名。或者你可能希望只对运行敏感工作负载的命名空间如production,finance强制执行签名验证而对开发测试命名空间如dev,staging放宽要求。Connaisseur 支持通过命名空间标签来精细控制验证行为。你需要做两步配置在 Connaisseur 的 Helm values 中启用该特性并定义标签键# values.yaml namespacedValidation: enabled: true labelKey: “security/connaisseur-policy” # 自定义标签键名在特定的命名空间上打标签强制验证给命名空间打上标签其值指向一个预定义的策略名称在policy中配置。例如security/connaisseur-policy: strict。跳过验证给命名空间打上标签security/connaisseur-policy: ignore则该命名空间的所有资源创建请求都不会经过 Connaisseur 验证。无标签或默认如果命名空间没有此标签则遵循 Connaisseur 的全局默认策略。# 为生产命名空间启用名为 “production_policy” 的严格策略 kubectl label namespace production security/connaisseur-policyproduction_policy # 为开发命名空间跳过所有验证 kubectl label namespace dev security/connaisseur-policyignore这个功能极大地增加了策略的灵活性允许安全团队逐步推进安全规范同时不影响其他团队的开发节奏。4.3 警报集成将安全事件纳入监控安全不能只靠阻断还需要可见性。Connaisseur 可以配置警报功能当镜像验证事件无论是成功、失败还是在检测模式下的警告发生时向外部系统发送通知。目前它支持集成Slack和Elasticsearch。以 Slack 为例配置如下# values.yaml alerting: enabled: true slack: webhook: “https://hooks.slack.com/services/XXX/YYY/ZZZ” # 你的 Slack Incoming Webhook URL channel: “#cluster-security-alerts” # 可选默认使用 Webhook 配置的频道 # 可以自定义消息的格式和内容配置后每当有部署请求被 Connaisseur 处理相关消息就会发送到你的 Slack 频道。这对于安全团队实时感知集群内的镜像部署活动、及时发现异常尝试如反复部署未签名镜像至关重要。4.4 密钥管理安全性的生命线信任锚公钥是 Connaisseur 安全模型的基石。如何安全地管理这些密钥避免硬编码永远不要将真实的私钥或包含敏感信息的values.yaml文件提交到版本控制系统。应该使用 Helm 的--set-file参数从本地文件注入密钥或者更好的方式是使用密钥管理工具。使用 Secrets虽然 Connaisseur 的trust_roots目前直接配置在 values 中但在生产环境你应该考虑将这些公钥存储在 Kubernetes Secrets 中并通过环境变量或卷挂载的方式传递给 Connaisseur Pod。这可能需要你自定义 Helm Chart 或使用类似helm-secrets的插件。轮转与更新制定公钥的轮转策略。当签名密钥对需要更新时你需要在 Connaisseur 配置中添加新的公钥并确保在一段时间内新旧公钥共存以验证新旧签名的镜像。之后再移除旧公钥。这个过程需要与你的镜像构建和签名流水线协同规划。5. 集成到 CI/CD 流水线实现“安全左移”Connaisseur 在集群侧提供了“最后一道防线”但最佳实践是将签名验证“左移”到镜像构建和推送阶段即在 CI/CD 流水线中完成签名。5.1 使用 Cosign 在 CI 中签名镜像以下是一个 GitHub Actions 工作流的简化示例展示如何在构建推送镜像后自动签名# .github/workflows/build-and-sign.yaml name: Build, Push and Sign Container Image on: push: tags: - ‘v*’ # 仅在打版本标签时触发 jobs: build-and-sign: runs-on: ubuntu-latest permissions: id-token: write # 这是密钥less签名所必需的 contents: read packages: write steps: - name: Checkout code uses: actions/checkoutv3 - name: Set up Docker Buildx uses: docker/setup-buildx-actionv2 - name: Log in to Container Registry uses: docker/login-actionv2 with: registry: ghcr.io username: ${{ github.actor }} password: ${{ secrets.GITHUB_TOKEN }} - name: Build and push Docker image uses: docker/build-push-actionv4 with: context: . push: true tags: | ghcr.io/${{ github.repository }}:${{ github.ref_name }} ghcr.io/${{ github.repository }}:latest cache-from: typegha cache-to: typegha,modemax - name: Install Cosign uses: sigstore/cosign-installerv3 - name: Sign the container image with Cosign (Keyless) run: | cosign sign \ --yes \ ghcr.io/${{ github.repository }}:${{ github.ref_name }}这个工作流使用了 Cosign 的密钥less 签名模式。它利用 GitHub Actions 的 OIDC 令牌自动完成签名无需你管理任何私钥。签名会推送至镜像仓库并与镜像关联。5.2 在 Connaisseur 中配置验证密钥less 签名对于密钥less 签名验证时需要的不是公钥而是签发临时证书的 Fulcio 根证书。幸运的是Connaisseur 的 Cosign 验证器默认已经信任了 Sigstore 公共实例的根证书。如果你的 CI 使用的是公共的 GitHub Actions 环境通常无需额外配置。如果你使用的是自建的 Sigstore 服务如 Fulcio、Rekor则需要在 Connaisseur 的trust_roots中配置你自己的根证书。validators: - name: my_keyless_cosign type: cosign trust_roots: - name: fulcio_my_company key: | -----BEGIN CERTIFICATE----- # 你的自建 Fulcio 根证书内容 -----END CERTIFICATE----- # 可能还需要指定自建的 Rekor 实例 URL args: - “--rekor-url” - “https://rekor.mycompany.com”5.3 处理多架构镜像和镜像摘要在生产环境中强烈建议使用镜像摘要而非标签来引用镜像。标签是可变的latest今天和明天可能指向不同的层而摘像是内容的密码学哈希是唯一的、不可变的标识符。当你的 Deployment YAML 使用镜像摘要时spec: containers: - name: myapp image: myregistry.com/myappsha256:a1b2c3d4e5f6...Connaisseur 的验证逻辑同样有效。Cosign 的签名正是与镜像摘要绑定的因此验证会更加精确和安全。你应该推动团队在生成 Kubernetes 清单文件时使用cosign或crane等工具将标签解析为摘要并固化在配置中。6. 故障排除与经验实录即使配置正确在实际运行中也可能遇到各种问题。以下是我在多次部署和运维中积累的一些常见问题与解决思路。6.1 常见错误与解决方案速查表错误现象可能原因排查步骤与解决方案Error from server: admission webhook “connaisseur…” denied the request: Unable to find signed digest for image…1. 镜像确实未签名。2. 策略未匹配或匹配了错误的验证器。3. 公钥配置错误与签名私钥不匹配。4. 对于 Notary服务器无法访问或证书错误。1. 确认镜像已签名 (cosign verify image)。2. 检查 Connaisseur 日志看镜像匹配了哪条策略。3. 核对trust_roots中的公钥是否与签名密钥对应。4. 检查网络连通性和 TLS 证书。Internal error occurred: failed calling webhook “connaisseur…”1. Connaisseur Pod 未就绪或崩溃。2. ValidatingWebhookConfiguration 中的caBundle错误或过期。3. 网络策略阻止了 API Server 对 Connaisseur Service 的访问。1.kubectl get pods -n connaisseur检查 Pod 状态和日志。2. 检查 Webhook 配置中的service名称和端口是否正确。3. 临时将failurePolicy设置为Ignore以绕过 Webhook 进行集群操作排查。签名验证通过但 Pod 仍部署失败1. 镜像拉取错误凭证、网络。2. 资源配额不足。3. 节点选择器/污点问题。1. 查看 Pod 事件 (kubectl describe pod name) 和 Kubelet 日志。2. 确认这是镜像运行阶段的问题与 Connaisseur 准入控制无关。在检测模式下未看到警告日志1. 镜像匹配了validator: allow的策略。2. 日志级别设置过高。3. 请求未触发验证如非 Pod 创建请求。1. 检查策略确保未签名镜像匹配的是你想监控的规则。2. 调整 Connaisseur 的日志级别通过环境变量LOG_LEVELDEBUG。3. 确认 Webhook 规则 (rules) 覆盖了你的资源类型。6.2 性能考量与优化建议准入控制器会为每个匹配的 API 请求增加延迟。虽然单次签名验证很快通常几百毫秒内但在高并发部署场景下仍需关注。资源限制为 Connaisseur Pod 设置合理的 CPU/内存请求和限制。它本身不消耗太多资源但避免因资源不足导致 Pod 被驱逐或响应缓慢。副本与高可用生产环境至少部署 2 个副本并通过 Pod 反亲和性将其调度到不同节点避免单点故障。超时设置在ValidatingWebhookConfiguration中可以设置timeoutSeconds默认 10 秒。确保这个时间足够 Connaisseur 完成验证包括可能的网络 I/O。如果验证经常超时需要排查网络或后端服务如镜像仓库、Rekor的性能。作用域控制使用namespaceSelector或 Connaisseur 自带的命名空间标签功能尽可能缩小验证范围避免不必要的拦截。6.3 我踩过的几个“坑”Helm Upgrade 导致 Webhook 失效有一次更新 Connaisseur 版本后所有部署都卡住了。原因是 Helm upgrade 过程中新的 ValidatingWebhookConfiguration 资源被应用但其引用的新版本 Pod 还未完全就绪导致 API Server 调用 Webhook 失败。解决方案在 Helm 升级命令中始终使用--atomic参数这样在失败时会自动回滚。或者先手动将failurePolicy临时改为Ignore升级完成后再改回来。公有镜像仓库的速率限制频繁验证 Docker Hub 等公有仓库的镜像签名可能会触发仓库的速率限制导致验证失败。解决方案为 Connaisseur 配置镜像仓库的访问凭证如果支持或者考虑在集群内部搭建一个缓存代理如registry mirror并配置 Connaisseur 通过代理访问仓库。“latest”标签的陷阱早期我们允许使用:latest标签。结果在一次镜像更新后新镜像的签名还没来得及同步到所有区域导致部分集群部署失败。教训强制要求使用不可变的镜像摘要或带版本的标签如:v1.2.3并在策略中明确拒绝*:latest模式。将 Connaisseur 集成到你的 Kubernetes 集群是为你的软件供应链安全增加了一道坚实的自动门禁。它迫使团队建立起镜像签名的规范让“不可信镜像无法运行”从安全愿望变成可执行的技术策略。从开启检测模式观察现状到逐步推行命名空间强制策略再到与 CI/CD 流水线无缝集成这个过程需要开发和运维团队的共同协作。
返回列表