<?xml version="1.0" encoding="utf-8" standalone="yes" ?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom">
  <channel>
    <title>opa | 伪架构师</title>
    <link>/tags/opa/</link>
      <atom:link href="/tags/opa/index.xml" rel="self" type="application/rss+xml" />
    <description>opa</description>
    <generator>Source Themes Academic (https://sourcethemes.com/academic/)</generator><language>zh</language><lastBuildDate>Tue, 23 Feb 2021 16:57:55 +0800</lastBuildDate>
    <image>
      <url>/img/logo-wide.png</url>
      <title>opa</title>
      <link>/tags/opa/</link>
    </image>
    
    <item>
      <title>Kubernetes 策略引擎对比：OPA/Gatekeeper vs Kyverno</title>
      <link>/post/k8s-policy-comparison/</link>
      <pubDate>Tue, 23 Feb 2021 16:57:55 +0800</pubDate>
      <guid>/post/k8s-policy-comparison/</guid>
      <description>

&lt;p&gt;Kubernetes 的 &lt;a href=&#34;https://kubernetes.io/docs/concepts/policy/pod-security-policy/&#34; target=&#34;_blank&#34;&gt;Pod Security Policy（PSP）&lt;/a&gt;即将被&lt;a href=&#34;https://github.com/kubernetes/kubernetes/pull/97171&#34; target=&#34;_blank&#34;&gt;淘汰和移除&lt;/a&gt;，所以需要找到一个替代方案来填补这个即将出现的空白。目前看来，Kubernetes 自身并没有准备相应的替代方案，因此需要在 Kubernetes 之外寻求解决之道。CNCF 的两个头部项目可能会成为首选的替代产品，它们分别是基于 &lt;a href=&#34;https://www.openpolicyagent.org/&#34; target=&#34;_blank&#34;&gt;Open Policy Agent（OPA）&lt;/a&gt;的 &lt;a href=&#34;https://github.com/open-policy-agent/gatekeeper&#34; target=&#34;_blank&#34;&gt;Gatekeeper&lt;/a&gt; 以及 &lt;a href=&#34;https://kyverno.io/&#34; target=&#34;_blank&#34;&gt;Kyverno&lt;/a&gt;，两个产品各行有千秋，但是目前还没有对这两个产品进行过正式的比较，这就让面临选择的用户无从下手了。这两个项目都是全功能的 Kubernetes 策略引擎，因此其功能不仅限于替代 PSP。本文尝试对 Gatekeeper 和 Kyverno 进行一个中立客观的比较，让用户能够据此作出决策。这里仅从 Kubernetes 的视角来对这两个项目来进行评价。&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;因为本文仅仅涉及 Kubernetes，因此对后续对 OPA/Gatekeeper 项目会简称为 Gatekeeper。&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;为了透明起见，我想公开说明我个人的立场。我是 Kyverno 而不是 GateKeeper 的撰稿人。我在 Kyverno 上写过几篇博客，在 Gatekeeper 上则没有。我过去还曾对 OPA Rego 提出过一些批评。然而，我的目标是把所有这些和任何个人感情放在一边，并试图以全新的方式来对待这两个项目，没有任何偏见和偏爱。&lt;/p&gt;

&lt;p&gt;在和 Kyverno 和 OPA 两个社区进行平等地沟通，让双方的管理者和贡献者公平地对比较标准和结果进行评论。在参与比较、评论等方面均没有偏向任何项目。&lt;/p&gt;

&lt;h2 id=&#34;导言&#34;&gt;导言&lt;/h2&gt;

&lt;h3 id=&#34;kubernetes-策略是什么&#34;&gt;Kubernetes 策略是什么&lt;/h3&gt;

&lt;p&gt;Kubernetes 的 Pod Security Policy，正如其名字所暗示的，仅是针对 Pod 工作的，是一种用来验证和控制 Pod 及其属性的机制。另外 PSP 只能屏蔽非法 Pod 的创建，无法执行任何补救/纠正措施。而 Gatekeeper 和 Kyverno 的作用范围就不是局限在 Pod 上，并且也有更多更深入的功能，而不只是简单的验证功能。策略引擎是一种能对整个 Kubernetes 环境进行全局控制的方法。&lt;/p&gt;

&lt;h3 id=&#34;gatekeeper-简介&#34;&gt;Gatekeeper 简介&lt;/h3&gt;

&lt;p&gt;Gatekeeper 是一个由 Google、微软等多个公司合作推出的开源项目，后来捐赠给了 CNCF。现已经历了三次迭代。Gatekeeper 是通用策略引擎 Open Policy Agent（OPA）的 Kubernetes 专用实现。由于 Open Policy Agent 与 Gatekeeper 之间的关系，该项目经常被写成“OPA Gatekeeper”来表明这层关系。Gatekeeper 实现了请求验证功能，最近还加入了变异能力。OPA 的一个主要特征是依赖于使用一种叫做 Rego 的专用编程语言，这种语言被用来实现策略决策的必要逻辑。通过 Rego，OPA 能够广泛适用于包括 Kubernetes 在内的多种不同的软件，实现高层次的逻辑操作。&lt;/p&gt;

&lt;h3 id=&#34;kyverno-简介&#34;&gt;Kyverno 简介&lt;/h3&gt;

&lt;p&gt;Kyverno 是来自 Nirmata 的开源项目，后来也捐赠给了 CNCF。和 Gatekeeper一样，Kyverno 也是一个具有验证和变异能力的 Kubernetes 策略引擎，但是它还有生成资源的功能，最近还加入了 API 对象查询的能力。与 Gatekeeper 不同，Kyverno 原本就是为 Kubernetes 编写的。和 Gatekeeper 相比，Kyverno 除了对象生成功能之外，还无需专用语言即可编写策略，从实现语言的角度上来看，Kyverno 的模型更为简洁。&lt;/p&gt;

&lt;h2 id=&#34;对比&#34;&gt;对比&lt;/h2&gt;

&lt;p&gt;下面的三个表格对两个项目的特征和质量进行分类，并试图以最客观的方式进行对比。这些维度分别是：&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;特征/功能维度用于描述技术属性；&lt;/li&gt;
&lt;li&gt;社区/生态系统维度用于描述落地情况和组织属性；&lt;/li&gt;
&lt;li&gt;杂项。&lt;/li&gt;
&lt;/ul&gt;

&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;特征/功能&lt;/th&gt;
&lt;th&gt;Gatekeeper&lt;/th&gt;
&lt;th&gt;Kyverno&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;

&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;验证&lt;/td&gt;
&lt;td&gt;✅&lt;/td&gt;
&lt;td&gt;✅&lt;/td&gt;
&lt;/tr&gt;

&lt;tr&gt;
&lt;td&gt;变异&lt;/td&gt;
&lt;td&gt;✅（Alpha）&lt;/td&gt;
&lt;td&gt;✅&lt;/td&gt;
&lt;/tr&gt;

&lt;tr&gt;
&lt;td&gt;生成&lt;/td&gt;
&lt;td&gt;❌&lt;/td&gt;
&lt;td&gt;✅&lt;/td&gt;
&lt;/tr&gt;

&lt;tr&gt;
&lt;td&gt;原生策略对象&lt;/td&gt;
&lt;td&gt;✅&lt;/td&gt;
&lt;td&gt;✅&lt;/td&gt;
&lt;/tr&gt;

&lt;tr&gt;
&lt;td&gt;监控指标&lt;/td&gt;
&lt;td&gt;✅&lt;/td&gt;
&lt;td&gt;❌&lt;/td&gt;
&lt;/tr&gt;

&lt;tr&gt;
&lt;td&gt;OpenAPI 验证（&lt;code&gt;kubectl explain&lt;/code&gt;）&lt;/td&gt;
&lt;td&gt;❌&lt;/td&gt;
&lt;td&gt;✅&lt;/td&gt;
&lt;/tr&gt;

&lt;tr&gt;
&lt;td&gt;高可用&lt;/td&gt;
&lt;td&gt;✅&lt;/td&gt;
&lt;td&gt;❌&lt;/td&gt;
&lt;/tr&gt;

&lt;tr&gt;
&lt;td&gt;API 对象查询&lt;/td&gt;
&lt;td&gt;✅&lt;/td&gt;
&lt;td&gt;✅（Alpha）&lt;/td&gt;
&lt;/tr&gt;

&lt;tr&gt;
&lt;td&gt;具备测试能力的 CLI 工具&lt;/td&gt;
&lt;td&gt;✅ 独立的客户端&lt;/td&gt;
&lt;td&gt;✅&lt;/td&gt;
&lt;/tr&gt;

&lt;tr&gt;
&lt;td&gt;策略审计&lt;/td&gt;
&lt;td&gt;✅&lt;/td&gt;
&lt;td&gt;✅&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;

&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;社区/生态系统&lt;/th&gt;
&lt;th&gt;Gatekeeper&lt;/th&gt;
&lt;th&gt;Kyverno&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;

&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;CNCF 状态&lt;/td&gt;
&lt;td&gt;毕业（OPA）&lt;/td&gt;
&lt;td&gt;沙箱&lt;/td&gt;
&lt;/tr&gt;

&lt;tr&gt;
&lt;td&gt;合作伙伴生态系统采用（注 1）&lt;/td&gt;
&lt;td&gt;◗&lt;/td&gt;
&lt;td&gt;◔&lt;/td&gt;
&lt;/tr&gt;

&lt;tr&gt;
&lt;td&gt;Github 状态（星，分叉、版本、提交）&lt;/td&gt;
&lt;td&gt;1,543, 280, 38, 510&lt;/td&gt;
&lt;td&gt;702, 72, 60, 3,034&lt;/td&gt;
&lt;/tr&gt;

&lt;tr&gt;
&lt;td&gt;社区认同（注 1）&lt;/td&gt;
&lt;td&gt;◗&lt;/td&gt;
&lt;td&gt;◔&lt;/td&gt;
&lt;/tr&gt;

&lt;tr&gt;
&lt;td&gt;策略样本库&lt;/td&gt;
&lt;td&gt;✅&lt;/td&gt;
&lt;td&gt;✅&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;

&lt;blockquote&gt;
&lt;p&gt;注 1：无精确定义，Gatekeeper 看起来比 Kyverno 采用数量更多，但是并没有具体数字。
注 2：无客观标准，Gatekeeper 历史更长，社区认可度可能更高。&lt;/p&gt;
&lt;/blockquote&gt;

&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;杂项&lt;/th&gt;
&lt;th&gt;Gatekeeper&lt;/th&gt;
&lt;th&gt;Kyverno&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;

&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;需要编程&lt;/td&gt;
&lt;td&gt;✅&lt;/td&gt;
&lt;td&gt;✅&lt;/td&gt;
&lt;/tr&gt;

&lt;tr&gt;
&lt;td&gt;可以在 Kubernetes 之外工作&lt;/td&gt;
&lt;td&gt;✅&lt;/td&gt;
&lt;td&gt;❌&lt;/td&gt;
&lt;/tr&gt;

&lt;tr&gt;
&lt;td&gt;诞生时间&lt;/td&gt;
&lt;td&gt;2017 年 7 月&lt;/td&gt;
&lt;td&gt;2019 年 5 月&lt;/td&gt;
&lt;/tr&gt;

&lt;tr&gt;
&lt;td&gt;创始公司&lt;/td&gt;
&lt;td&gt;Styra（OPA）&lt;/td&gt;
&lt;td&gt;Nirmata&lt;/td&gt;
&lt;/tr&gt;

&lt;tr&gt;
&lt;td&gt;文档成熟度&lt;/td&gt;
&lt;td&gt;◗（注 1）&lt;/td&gt;
&lt;td&gt;◕&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;

&lt;blockquote&gt;
&lt;p&gt;注 1：并没有统一的评判标准。这里的评价基于 Gatekeeper 的功能，而不是 Rego。&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2 id=&#34;分析&#34;&gt;分析&lt;/h2&gt;

&lt;p&gt;根据前面的功能对比，我做了一个简单的归纳，列出两个产品的优劣，这里只写出了标题内容，并不够详尽。&lt;/p&gt;

&lt;h3 id=&#34;gatekeeper-的优势&#34;&gt;Gatekeeper 的优势&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;能够表达非常复杂的策略；&lt;/li&gt;
&lt;li&gt;社区更为成熟；&lt;/li&gt;
&lt;li&gt;支持多副本模式，更好的可用性和伸缩性。&lt;/li&gt;
&lt;/ul&gt;

&lt;h3 id=&#34;gatekeeper-的劣势&#34;&gt;Gatekeeper 的劣势&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;需要编程语言支持，该语言的学习曲线较为陡峭，可能会产生大量技术债，并延长交付时间；&lt;/li&gt;
&lt;li&gt;变异能力还处在萌芽期；&lt;/li&gt;
&lt;li&gt;没有生成能力，意味着它的主要应用场景就在验证方面；&lt;/li&gt;
&lt;li&gt;策略复杂冗长，需要多个对象协同实现。&lt;/li&gt;
&lt;/ul&gt;

&lt;h3 id=&#34;kyverno-的优势&#34;&gt;Kyverno 的优势&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;Kubernetes 风格的策略表达方式，非常易于编写；&lt;/li&gt;
&lt;li&gt;成熟的变异能力；&lt;/li&gt;
&lt;li&gt;独特的生成和同步能力，扩展了应用场景；&lt;/li&gt;
&lt;li&gt;快速交付，场景丰富。&lt;/li&gt;
&lt;/ul&gt;

&lt;h3 id=&#34;kyverno-的劣势&#34;&gt;Kyverno 的劣势&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;受到语言能力的限制，难以实现复杂策略；&lt;/li&gt;
&lt;li&gt;较为年轻，社区接受度不高；&lt;/li&gt;
&lt;li&gt;API 对象查询能力还很初级；&lt;/li&gt;
&lt;li&gt;没有高可用能力（还在路线图阶段）。&lt;/li&gt;
&lt;/ul&gt;

&lt;blockquote&gt;
&lt;p&gt;警告：下面的内容是我根据前面的对比表和优势劣势列表，再加上自己对这两个工具的体验，以及在云原生社区的走访，综合起来的意见分析。如果你没有兴趣看我的观点，文章就到此为止了。&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Kubernetes 是一个声明式的系统：用户向 Kubernetes 提出对状态的要求，Kubernetes 通过各种控制器，去协调观察到的状态，以使其与用户期望的状态一致。这就是云原生平台的核心价值主张。为了实现这一目标，逻辑实现的重任从用户身上转移到了平台本身。每个资源类型都存在一些内部逻辑，这些逻辑就是协调其状态所需的能力。对于 Gatekeeper 来说，到目前为止最大的弱点是它需要一种叫做 Rego 的专门的编程语言来实现这种逻辑，这种语言在其他地方都无法使用。这是一个现实，因为 OPA 是一个通用的策略引擎。只有通过 Gatekeeper 将其改编成 Kubernetes 形式，才能利用其能力。那么实际上，用户负责描述他们希望调和的对象（策略），以及提供必要的逻辑（Rego）来调和它。使用外部 DSL 来管理 Kubernetes 策略，在很多方面都会变得繁琐和复杂，并给项目增加技术债务。作为一种权衡，其明显的优势是可以实现非常强大的策略。毕竟，当一个人需要编写一种编程语言时，他只受限于该语言的能力及其输入。不过，如果可以在其他地方利用 OPA，就可以分摊这种费用。&lt;/p&gt;

&lt;p&gt;相比 Gatekeeper 来说，Kyverno 的第一印象就是没有那么复杂的技术需求。因为它是专门为 Kubernetes 构建的，并且用声明式的方法来表达策略，所以它的心理模型与 Kubernetes 对象的描述和协调方式是相同的。执行策略决策所需的逻辑被从用户的负担中移除，成为工具本身的领域。这种模式导致策略的编写方式得到了极大的简化，全面的降低了策略引擎的使用难度。Kyverno 的编译和生成能力，使它从一个简单的准入控制器转变为一个真正的自动化工具。通过结合这三种能力，再加上最近增加的 API 查询能力，Kyverno 能够执行 Gatekeeper 所不能执行的任务，而且还能够消除可能在整个集群和/或组织中分散使用的其他和不同的工具。这种简单性加上它的自动化能力和对其他工具的整合，为新用户以及有经验的用户和操作者带来了巨大的价值。&lt;/p&gt;

&lt;p&gt;根据所介绍的信息，我认为 Kyverno 应该是应用 Kubernetes 策略的一个比较自然的选择。但如果用户符合下面两个用例中的一种或两种，就更应该选择 Gatekeeper。&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;有一种需求和具体意图，使用一致的核心工具将策略应用于组织内不同的系统（即，不仅仅是Kubernetes）。&lt;/p&gt;

&lt;p&gt;反对意见：根据我的经验，无论是在云原生社区内部还是外部，大多数组织目前已经在使用其他工具将策略应用于现有系统。这通常是因为这些系统以及为这些系统实施策略的软件在 Kubernetes 以及 OPA 和 Gatekeeper 之前就已经存在。此外，这些现有工具通常不要求使用编程语言来实现其策略。因此，考虑到现有的知识、运营和资本投资，大多数组织不太可能为了实现工具一致性带来的价值，选择放弃这些工具，转而使用技术负担较重的新工具。&lt;/p&gt;

&lt;p&gt;太长不看：如果你正在寻找一个跨 Kubernetes 和其他系统使用的单一策略引擎，Kyverno 不适合你。&lt;/p&gt;&lt;/li&gt;

&lt;li&gt;&lt;p&gt;策略的复杂度很高。&lt;/p&gt;

&lt;p&gt;反对意见：根据我的经验，大多数 Kubernetes 用户都没有使用包括 PSP 在内的任何策略支持。而 2020 年对在 AWS 上运行容器化工作负载的客户的调查也得到了类似的结果，只有 49% 的客户使用策略。这些用户中的绝大多数都在做的是重复的策略——例如“容器不应该有特权”或“确保所有命名空间都带有给定的标签”或“验证 Pods 没有使用 hostPath 卷”等。“复杂”这个词是相对的，有点主观，但这样的策略表达方式绝对不复杂。Kyverno 允许以最简单的形式编写策略，这反过来又更容易推理和维护。如果要为一个更复杂、更困难的工具支付额外的价格，就应该尽量物尽其用，否则无法获得价值。&lt;/p&gt;

&lt;p&gt;太长不看：如果无需实现高度复杂的策略，Gatekeeper 不会带来好处。&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;h2 id=&#34;结语&#34;&gt;结语&lt;/h2&gt;

&lt;p&gt;Gatekeeper 和 Kyverno 项目本身都是有价值、有能力的策略引擎，每个项目都有各自的优缺点。最终，用户应该根据自己的需求和限制条件进行评估并做出最明智的决定，但作为一般建议，所有生产用户都应该计划使用策略引擎来保护集群的安全并简化 Kubernetes 管理。&lt;/p&gt;
</description>
    </item>
    
    <item>
      <title>用 Notary 和 OPA 在 Kubernetes 上使用内容签名</title>
      <link>/post/k8s-notary-and-opa/</link>
      <pubDate>Sat, 17 Oct 2020 01:18:58 +0800</pubDate>
      <guid>/post/k8s-notary-and-opa/</guid>
      <description>

&lt;p&gt;原文：&lt;a href=&#34;https://medium.com/@siegert.maximilian/ensure-content-trust-on-kubernetes-using-notary-and-open-policy-agent-485ab3a9423c&#34; target=&#34;_blank&#34;&gt;Ensure Content Trust on Kubernetes using Notary and Open Policy Agent&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;作者：&lt;a href=&#34;https://medium.com/u/42543b7496a6?source=post_page-----485ab3a9423c--------------------------------&#34; target=&#34;_blank&#34;&gt;Daniel Geiger&lt;/a&gt; &lt;a href=&#34;https://medium.com/u/185afb909cc2?source=post_page-----485ab3a9423c--------------------------------&#34; target=&#34;_blank&#34;&gt;Maximilian Siegert&lt;/a&gt;&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;在 Kubernetes 上使用策略对部署行为进行限制，仅允许运行有签名的镜像。&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;我们希望借助本文，让读者了解到如何在 Kubernetes 中使用可信镜像，其中依赖两个著名的 CNCF 开源项目：Notary 和 OPA。主要思路是使用 OPA 策略来定义自己的内容限制策略。&lt;/p&gt;

&lt;p&gt;主要内容如下：&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;完成示例的先决条件&lt;/li&gt;
&lt;li&gt;Notary 和镜像信任的基本概念&lt;/li&gt;
&lt;li&gt;在 Kubernetes 上安装 Kubernetes&lt;/li&gt;
&lt;li&gt;OPA 和 Admission Control 的基本概念&lt;/li&gt;
&lt;li&gt;在 Kubernetes 上安装 OPA&lt;/li&gt;
&lt;li&gt;定义 Validating Admission Control 控制内容信任&lt;/li&gt;
&lt;li&gt;定义 Mutating Admission Control 完成自动化&lt;/li&gt;
&lt;li&gt;总结和展望&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;如果读者已经熟知 Notary 或者 OPA 的相关内容，可以跳过上述的两节基本概念部分。&lt;/p&gt;

&lt;h2 id=&#34;完成示例的先决条件&#34;&gt;完成示例的先决条件&lt;/h2&gt;

&lt;p&gt;如果要遵循后续的安装步骤，需要下列准备：&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;如果是 Kubernetes 集群，至少启用了 &lt;code&gt;MutatingAdmissionWebhook&lt;/code&gt; 和&lt;code&gt;ValidatingAdmissionWebhook&lt;/code&gt;；如果是 Minikube，应该使用如下启动方式：&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;language-text&#34;&gt;$ minikube start \
--extra-config=apiserver.enable-admission-plugins=MutatingAdmissionWebhook,ValidatingAdmissionWebhook
&lt;/code&gt;&lt;/pre&gt;&lt;/li&gt;

&lt;li&gt;&lt;p&gt;私有镜像库，或者一个 Docker Hub ID，用于推送签名镜像。&lt;/p&gt;&lt;/li&gt;

&lt;li&gt;&lt;p&gt;从&lt;a href=&#34;https://github.com/k8s-gadgets/k8s-content-trust&#34; target=&#34;_blank&#34;&gt;我们的 Github 仓库&lt;/a&gt;获取用于安装 OPA、Notary 以及 Notary-Wrapper 的 Helm Chart。&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;h2 id=&#34;notary-和镜像信任的基本概念&#34;&gt;Notary 和镜像信任的基本概念&lt;/h2&gt;

&lt;p&gt;将代码、可执行文件或者脚本进行签名，保障仅有受信内容才可运行，这是一个已知的最佳实践。软件签名不是什么新概念，有很多相关的供应商和方案，每个组织都有自己的方式来处理制品的签署和信任。然而如果把目光投向容器领域，可能会发现并没有那么多选择。&lt;/p&gt;

&lt;h3 id=&#34;notary-是什么&#34;&gt;Notary 是什么&lt;/h3&gt;

&lt;p&gt;你可能已经听说过 Notary，这是一个基于 &lt;a href=&#34;https://theupdateframework.github.io/&#34; target=&#34;_blank&#34;&gt;TUF 项目&lt;/a&gt;的用于软件制品签名的开源软件。&lt;/p&gt;

&lt;h3 id=&#34;notary-如何运作&#34;&gt;Notary 如何运作&lt;/h3&gt;

&lt;p&gt;首先说说 Notary 的核心概念。Notary 使用角色和元数据文件对受信集合内容进行签署，这些内容被称为全局唯一名称（GUN——Global Unique Name）。&lt;/p&gt;

&lt;p&gt;以 Docker 镜像为例，GUN 相当于 &lt;code&gt;[registry]/[repository name]:[tag]&lt;/code&gt;。&lt;/p&gt;

&lt;p&gt;&lt;code&gt;[registry]&lt;/code&gt; 是镜像的源仓库，&lt;code&gt;[repository name]&lt;/code&gt; 是镜像的名称。&lt;code&gt;[tag]&lt;/code&gt; 对镜像进行标记（通常代表版本）。&lt;/p&gt;

&lt;p&gt;Notary 借助 TUF 的角色和密钥层级关系对镜像进行签名。有五种密钥类型用于对元数据文件进行签署，并用 &lt;code&gt;.json&lt;/code&gt; 的方式保存到 &lt;a href=&#34;https://docs.docker.com/notary/service_architecture/&#34; target=&#34;_blank&#34;&gt;Notary 数据库&lt;/a&gt;。下图描述了密钥层级以及这些密钥的典型存储位置。&lt;/p&gt;

&lt;p&gt;&lt;img src=&#34;images/key-hierarchy-in-notary.png&#34; alt=&#34;key-hierarchy-in-notary&#34; /&gt;&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;根密钥：每个 GUN 都有自己的根角色和密钥。根密钥是所有信任关系的基础，用于对根级元数据文件（其中包含根 ID、目标、快照以及时间戳公钥的 ID）进行签名。通常这个密钥是由（GUN）的属主管理的，并使用离线的方式进行保存（例如在本地目录或者硬件密钥设备）。&lt;/p&gt;&lt;/li&gt;

&lt;li&gt;&lt;p&gt;目标密钥：目标密钥负责签署目标元数据文件，其中包含该集合中的所有文件名、尺寸以及对应的哈希值。这个元数据文件用于对该仓库中的所有实际内容进行完整性验证。这还表示目标元数据文件包含了每个镜像标签的入口。目标密钥可以使用委托角色把信任关系委托给其它的合作者。目标密钥也是属于 GUN 属主的，同样用离线方式保存。&lt;/p&gt;&lt;/li&gt;

&lt;li&gt;&lt;p&gt;委托密钥：如上文所说，目标密钥能够委托给其它角色。这些角色会有自己的密钥来签署被委托的元数据文件，其中同样会包含该集合中的文件名、尺寸以及对应的哈希。委托元数据文件能用于校验仓库中部分或者全部内容的完整性。这些密钥属于这个集合的协作者。&lt;/p&gt;&lt;/li&gt;

&lt;li&gt;&lt;p&gt;快照密钥：快照密钥负责签署快照元数据文件，其中遍历了每个 GUN 的根、目标和委托元数据。这个元数据文件的目标就是验证其它元数据文件的完整性。快照密钥属于协作属主（本地），或者如果 Notary 服务（通过委托角色使用多个协作者）。&lt;/p&gt;&lt;/li&gt;

&lt;li&gt;&lt;p&gt;时间戳密钥：时间戳密钥用于签署时间戳元数据文件，这个密钥的存在目的是保障集合的时效性。这其中包含了元数据的最短过期时间、最近快照的文件名、尺寸以及哈希。这个元数据文件用来检验快照文件的完整性。时间戳密钥由 Notary 服务保存，这样这个密钥就能自动的根据服务器的请求自动重新生成。&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;管理密钥的 Notary 服务架构包括两个组件：&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;Notary 服务器，用来保存和更新信任 GUN 的签署后元数据文件。&lt;/p&gt;&lt;/li&gt;

&lt;li&gt;&lt;p&gt;Notary Signer 保存了私钥，用于为 Notary Server 提供元数据签署能力。&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Docker 文档中&lt;a href=&#34;https://docs.docker.com/notary/service_architecture/&#34; target=&#34;_blank&#34;&gt;这张 Notary 的示意图&lt;/a&gt;很好的概括了客户端与 Notary Server 以及 Signer 之间的通信。下图是一个简化版本：&lt;/p&gt;

&lt;p&gt;&lt;img src=&#34;images/notary-server-signer-client.png&#34; alt=&#34;notary-server-signer-client.png&#34; /&gt;&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;Notary 服务器可以使用 JWT Token 进行认证。如果没有使用这个功能，可以简单地上传新的元数据文件。如果客户端上传了新的元数据文件，Notary Server 会对老版本进行冲突检测，并对签名、校验和以及元数据的有效性进行检测。&lt;/p&gt;&lt;/li&gt;

&lt;li&gt;&lt;p&gt;上传的元数据通过验证以后，Notary 服务器会生成时间戳元数据，并将元数据发给 Signer 进行签名。&lt;/p&gt;&lt;/li&gt;

&lt;li&gt;&lt;p&gt;Notary Signer 从数据库中获取加密的密钥，解密后对元数据进行签署。如果签署成功，则将签名发回给 Notary 服务器。&lt;/p&gt;&lt;/li&gt;

&lt;li&gt;&lt;p&gt;Notary Server 是所有受信集合（GUN）真实状态的来源，TUF 数据库中存储了客户端上传和服务器生成的元数据。生成的时间戳和快照元数据证明客户端上传的元数据是该可信集合的最新数据。Notary 服务器会通知客户其上传成功。&lt;/p&gt;&lt;/li&gt;

&lt;li&gt;&lt;p&gt;客户端能够从服务器下载最新的元数据。Notary 服务器从数据库中取出元数据即可。&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;如果时间戳过期，Notary 服务器会重新完成流程，生成新的时间戳，申请 Signer 签名，并在数据库中保存新签署的时间戳。然后发送新的时间戳以及用户请求的其它元数据。&lt;/p&gt;

&lt;p&gt;Notary 签署过程看起来很复杂，不过一个好消息就是，Docker 客户端中集成了用 Notary 签署镜像的能力。可以轻松地使用环境变量在本地设备上启用镜像信任机制：&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;code&gt;DOCKER_CONTENT_TRUST=1&lt;/code&gt;：在客户端启用 Notary&lt;/p&gt;&lt;/li&gt;

&lt;li&gt;&lt;p&gt;&lt;code&gt;DOCKER_CONTENT_TRUST_SERVER=”&amp;lt;url-to-your-Notary-server&amp;gt;”&lt;/code&gt;：使用自己的 Notary 服务提供信任关系&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;设置这些之后，Docker 客户端就会在拉取之前检查签名，并在推送之前请求签署凭据来对镜像进行签名。Docker HUB 还提供了自己的缺省 Notary 服务 &lt;code&gt;https://notary.docker.io&lt;/code&gt;，如果启用了内容信任，会用它对推送镜像进行签署。&lt;/p&gt;

&lt;p&gt;如果拉取镜像是有签名的，可以简单的使用 &lt;code&gt;docker trust inspect &amp;lt;GUN&amp;gt;&lt;/code&gt; 来检查签名情况：&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;language-command&#34;&gt;$ docker trust inspect nginx:latest
[
    {
        &amp;quot;Name&amp;quot;: &amp;quot;nginx:latest&amp;quot;,
        &amp;quot;SignedTags&amp;quot;: [
            {
                &amp;quot;SignedTag&amp;quot;: &amp;quot;latest&amp;quot;,
                &amp;quot;Digest&amp;quot;: &amp;quot;b2xxxxxxxxxxxxx4a0395f18b9f7999b768f2&amp;quot;,
                &amp;quot;Signers&amp;quot;: [
                    &amp;quot;Repo Admin&amp;quot;
                ]
            }
        ],
        &amp;quot;Signers&amp;quot;: [],
        &amp;quot;AdministrativeKeys&amp;quot;: [
            {
                &amp;quot;Name&amp;quot;: &amp;quot;Root&amp;quot;,
                &amp;quot;Keys&amp;quot;: [
                    {
                        &amp;quot;ID&amp;quot;: &amp;quot;d2fxxxxxxx042989d4655a176e8aad40d&amp;quot;
                    }
                ]
            },
            ...
        ]
    }
]
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;除了使用 &lt;code&gt;docker trust&lt;/code&gt; 之外，也可以下载 &lt;a href=&#34;https://github.com/theupdateframework/notary/releases&#34; target=&#34;_blank&#34;&gt;Notary 客户端&lt;/a&gt;，直接和服务器进行通信。&lt;/p&gt;

&lt;h2 id=&#34;在-kubernetes-上安装-notary&#34;&gt;在 Kubernetes 上安装 Notary&lt;/h2&gt;

&lt;p&gt;到现在我们已经对 Notary 的工作机制有了个初步的认识。我们可以更进一步，在 Kubernetes 上安装自己的 Notary 服务。我们准备了两个 Shell 脚本和 Helm Chart，这样就可以很方便的进行安装了。开始之前请克隆我们的代码仓库：&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;language-command&#34;&gt;$ git clone https://github.com/k8s-gadgets/k8s-content-trust
...
&lt;/code&gt;&lt;/pre&gt;

&lt;h3 id=&#34;安装&#34;&gt;安装&lt;/h3&gt;

&lt;p&gt;进入 &lt;code&gt;notary-k8s&lt;/code&gt; 目录。&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;可选项目：构建 Notary 并加入自己的镜像库。
要从头构建最新的 Notary 镜像，需要从 &lt;code&gt;build&lt;/code&gt; 目录开始。如果要构建和推送 Notary 镜像到你自己的镜像仓库，可以编辑 &lt;code&gt;build.sh&lt;/code&gt; 文件，编辑 &lt;code&gt;REGISTRY&lt;/code&gt; 变量，使之匹配自己的镜像库，并执行 &lt;code&gt;build.sh&lt;/code&gt; 脚本。&lt;/p&gt;
&lt;/blockquote&gt;

&lt;pre&gt;&lt;code class=&#34;language-command&#34;&gt;$ bash build.sh
...
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;接下来需要进入 &lt;code&gt;helm/notary&lt;/code&gt; 目录，并生成 TLS 证书，来确保和 Notary 服务通信的安全性：&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;language-command&#34;&gt;$ cd helm/notary
...
$ bash generateCerts.sh
...
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;在准备好 Docker 镜像并把 TLS 证书写入 Chart 之后，就可以使用 Helm 在 Kubernetes 上进行部署了。另外也可以看看 &lt;code&gt;values.yaml&lt;/code&gt; 文件，修改一些必要的参数，例如缺省密码（&lt;code&gt;passwordalias1Name&lt;/code&gt;、 &lt;code&gt;passwordalias1Value&lt;/code&gt;）或者私有仓库。&lt;/p&gt;

&lt;p&gt;然后就是创建命名空间并安装 Helm Chart：&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;language-command&#34;&gt;$ kubectl create namespace notary
# 切换到 notary 命名空间
$ helm install notary notary
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;检查镜像是否已经启动运行：&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;language-command&#34;&gt;$ kubectl get pods –n notary
...
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;如果 Pod 已经运行，就表明 Notary 安装成功了。然而在我们试用 Notary 服务之前，我们应该提交最后生成的 Notary Wrapper 模板。&lt;/p&gt;

&lt;p&gt;&lt;code&gt;Notary Wrapper&lt;/code&gt; 是我们写的一个扩展，借助这个扩展，OPA 就能就能和 Notary 服务进行交互了。这是一个 CLI REST 界面，仅实现了获取已签名镜像哈希以及在服务上检查新人数据的功能。&lt;/p&gt;

&lt;p&gt;从 &lt;code&gt;notary-k8s/helm/certs&lt;/code&gt; 复制证书文件到 &lt;code&gt;helm/notary-wrapper/certs&lt;/code&gt;：&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;notary-wrapper.crt&lt;/li&gt;
&lt;li&gt;notary-wrapper.key&lt;/li&gt;
&lt;li&gt;root-ca.crt&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;进入源码的 &lt;code&gt;notary-wrapper&lt;/code&gt; 子目录。创建 OPA 命名空间并执行 Helm 安装过程。&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;language-command&#34;&gt;$ kubectl create namespace opa
# switch to namespace opa
helm install notary-wrapper notary-wrapper
&lt;/code&gt;&lt;/pre&gt;

&lt;h2 id=&#34;测试-notary&#34;&gt;测试 Notary&lt;/h2&gt;

&lt;p&gt;组件安装结束之后，就可以开始用我们的信任数据来测试 Notary 了，下图展示了这个过程：&lt;/p&gt;

&lt;p&gt;&lt;img src=&#34;images/testing-notary.png&#34; alt=&#34;testing notary&#34; /&gt;&lt;/p&gt;

&lt;p&gt;我们需要签署一些本地镜像作为测试素材，所以首先从 Docker Hub 拉取一些镜像：&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;如果你已经启用了 &lt;code&gt;DOCKER_CONTENT_TRUST&lt;/code&gt;，并且没有指定 &lt;code&gt;DOCKER_CONTENT_TRUST_SERVER&lt;/code&gt;，或者指定到了你的新服务器，拉取过程可能会失败。&lt;/p&gt;
&lt;/blockquote&gt;

&lt;pre&gt;&lt;code class=&#34;language-command&#34;&gt;docker pull nginx:latest
docker pull busybox:latest
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;下一步就要连接我们的 Notary 客户端和服务器了：&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;把 Notary 服务器加入 &lt;code&gt;/etc/hosts&lt;/code&gt;：&lt;code&gt;127.0.0.1 notary-server-svc&lt;/code&gt;&lt;/p&gt;&lt;/li&gt;

&lt;li&gt;&lt;p&gt;在终端中打开第二个 Tab，并为 Notary Server 的 Pod 创建一个端口转发，以便本地使用：&lt;code&gt;kubectl port-forward notary-server-&amp;lt;...&amp;gt; 4443:4443&lt;/code&gt;&lt;/p&gt;&lt;/li&gt;

&lt;li&gt;&lt;p&gt;第一次要签名之前，要把你的 &lt;code&gt;root-ca.crt&lt;/code&gt; 从安装目录拷贝到你的 &lt;code&gt;.docker/tls&lt;/code&gt; 目录：&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;language-command&#34;&gt;mkdir -p $HOME/.docker/tls/notary-server-svc:4443
cp &amp;lt;...&amp;gt;/helm/notary/certs/root-ca.crt $HOME/.docker/tls/notary-server-svc:4443/
&lt;/code&gt;&lt;/pre&gt;&lt;/li&gt;

&lt;li&gt;&lt;p&gt;回到第一个终端 Tab，启用内容信任机制：&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;language-command&#34;&gt;export DOCKER_CONTENT_TRUST_SERVER=https://notary-server-svc:4443
export DOCKER_CONTENT_TRUST=1
&lt;/code&gt;&lt;/pre&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Notary 已经启动，应该已经无法拉取任何没有被你的 Notary 服务签名的镜像了。不过可以打标签、签名和推送镜像（在我们的例子中，我们会简单的推送到我们自己的 Docker Hub 空间，使用的是我们自己的镜像签名）：&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;language-command&#34;&gt;docker tag nginx:latest docker.io/&amp;lt;hub-id&amp;gt;/nginx:1 
docker push docker.io/&amp;lt;hub-id&amp;gt;/nginx:1
docker tag busybox:latest docker.io/&amp;lt;hub-id&amp;gt;busybox:1
docker push docker.io/&amp;lt;hub-id&amp;gt;/busybox:1
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;这个推送命令会提示生成密码，用于请求签名密钥。这些步骤完成后，镜像会被推送到 Docker Hub，信任数据则会保存到 Notary Server。要进行校验，可以使用前面提到的 &lt;code&gt;docker trust inspect&lt;/code&gt; 命令，如果安装了 Notary 客户端，也可以用 &lt;code&gt;notary list&lt;/code&gt; 命令。命令执行结果类似：&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;language-command&#34;&gt;$ notary -s https://notary-server-svc:4443 --tlscacert $HOME/.docker/tls/notary-server-svc:4443/root-ca.crt list docker.io/&amp;lt;hub-id&amp;gt;/nginx
# output
NAME    DIGEST                                SIZE (BYTES)  ROLE
----    ------                                ------------  ----
1       cccef6d6bdea671c394954b0dxxxxxxxx     948           targets
&lt;/code&gt;&lt;/pre&gt;

&lt;blockquote&gt;
&lt;p&gt;如果必须重新部署 Notary，并使用新的密钥进行镜像签署，必须删除之前存储在 &lt;code&gt;.docker/tls&lt;/code&gt; 目录中保存的密钥。另外还需要删除 &lt;code&gt;.docker/trust/tuf&lt;/code&gt; 中现存的需要重新签署的镜像的信任数据。&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;现在可以开始测试 Notary Wrapper。再新开一个终端 Tab，在 /etc/hosts 文件中加入该服务的地址：&lt;code&gt;127.0.0.1 notary-wrapper-svc&lt;/code&gt;。&lt;/p&gt;

&lt;p&gt;保存之后，对端口 4445 进行端口转发：&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;language-command&#34;&gt;# switch to namespace opa
kubectl port-forward notary-wrapper-&amp;lt;...&amp;gt; 4445:4445
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;完成后就可以使用两个操作来检查 GUN、Tag 后者哈希的信任数据了，因为我们用的是 TLS 连接，要信任前面生成的根证书：&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;把 GUN 和 Tag 数据提交给 &lt;code&gt;https://notary-wrapper-svc:4445/list&lt;/code&gt;，获取最新的镜像信任数据，例如：&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;language-command&#34;&gt;$ curl -X POST https://notary-wrapper-svc:4445/list -H “Content-Type: application/json” -d ‘{“GUN”:”docker.io/&amp;lt;hub-id&amp;gt;/nginx”, “Tag”:”1&amp;quot;, “notaryServer”:”notary-server-svc.notary.svc:4443”}’ --cacert PATH/TO/YOUR/NOTARY/certs/root-ca.crt
# output - One item
{
    &amp;quot;Name&amp;quot;:&amp;quot;1&amp;quot;,
    &amp;quot;Digest&amp;quot;:&amp;quot;cccef6d6bdexxxxxx422&amp;quot;,
    &amp;quot;Size&amp;quot;:&amp;quot;948&amp;quot;,
    &amp;quot;Role&amp;quot;:&amp;quot;targets&amp;quot;
}
&lt;/code&gt;&lt;/pre&gt;&lt;/li&gt;

&lt;li&gt;&lt;p&gt;把 GUN 和哈希码发送到 &lt;code&gt;https://notary-wrapper-svc:4445/verify&lt;/code&gt; 验证这个哈希对应的信任数据是否存在（返回码 200 或 404）。如果不知道哈希吗，可以使用 &lt;code&gt;docker inspect GUN:Tag&lt;/code&gt; 命令查看。&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;language-command&#34;&gt;$ curl -X POST https://notary-wrapper-svc:4445/verify -H “Content-Type: application/json” -d ‘{“GUN”:”docker.io/&amp;lt;hub-id&amp;gt;/nginx”, “SHA”:”&amp;lt;your-RepoDigest&amp;gt;”, “notaryServer”:”notary-server-svc.notary.svc:4443”}’ --cacert PATH/TO/YOUR/NOTARY/certs/root-ca.crt
...
&lt;/code&gt;&lt;/pre&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;后面会使用 Notary Wrapper 来实现内容信任。完成这个测试之后，就可以关闭端口转发，继续下面的内容了。&lt;/p&gt;

&lt;h3 id=&#34;在-kubernetes-上实施内容信任&#34;&gt;在 Kubernetes 上实施内容信任&lt;/h3&gt;

&lt;p&gt;现在我们已经可以签署镜像生成信任数据了，拼图还差最后一块——在 Kubernetes 上实施内容信任策略。这临门一脚的难处在于，Kubernetes 中并没有提供什么开关可以激活内容信任。&lt;/p&gt;

&lt;p&gt;又一个可能的方案就是依赖底层的 Docker 引擎，调用镜像验证插件，启用 &lt;code&gt;DOCKER_CONTENT_TRUST&lt;/code&gt;（可以参考这个 &lt;a href=&#34;https://github.com/kubernetes/kubernetes/issues/30603#issuecomment-430889781&#34; target=&#34;_blank&#34;&gt;Issue&lt;/a&gt;），这种方法有两个弊端：&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;集群节点需要依赖 Docker 引擎完成信任工作。&lt;/p&gt;&lt;/li&gt;

&lt;li&gt;&lt;p&gt;&lt;code&gt;DOCKER_CONTENT_TRUST&lt;/code&gt; 是个非此即彼的开关，打开之后，无法拉取没有在 Notary 上签名的镜像。&lt;/p&gt;&lt;/li&gt;

&lt;li&gt;&lt;p&gt;&lt;code&gt;DOCKER_CONTENT_TRUST&lt;/code&gt; 只能检查一个镜像是否存在签名元数据，但是并不负责检查该签名是否属于这个 Tag。&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;为了克服几个弊端，我们把注意力放在了 Kubernetes Admission Control 上。&lt;/p&gt;

&lt;h2 id=&#34;opa-和-admission-control-的基本概念&#34;&gt;OPA 和 Admission Control 的基本概念&lt;/h2&gt;

&lt;p&gt;长话短说。Kubernetes Admission Controller 是一种插件机制，可以用来对集群上的资源进行校验和配置。它的作用包含在 Kubernetes API 请求的生命周期之中，除了内置的 30 个控制器（例如 &lt;a href=&#34;https://kubernetes.io/docs/concepts/policy/pod-security-policy/#enabling-pod-security-policies&#34; target=&#34;_blank&#34;&gt;PodSecurity Policy&lt;/a&gt;）之外，还会有使用自己的控制规则的需要。就可以创建自己的 Validating 或者 Mutating Webhook 了。&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Mutating&lt;/strong&gt;：这种 Webhook 会对请求对象进行变更，来满足特定的配置需求。&lt;/p&gt;&lt;/li&gt;

&lt;li&gt;&lt;p&gt;&lt;strong&gt;Validating&lt;/strong&gt;：它可以对请求对象进行验证，拒绝验证失败的请求。&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Admission Control 触发的顺序是非常重要的知识点：&lt;/p&gt;

&lt;p&gt;&lt;img src=&#34;images/kube-api-lifecycle.png&#34; alt=&#34;Kubernetes API Lifecycle&#34; /&gt;&lt;/p&gt;

&lt;p&gt;Kubernetes 会首先执行 Mutating 过程，然后才是进行验证。这样就能确保被变更过的请求对象能够正确地被校验。OPA 就是最好的实现 Mutaiting 和 Validating Webhook 的方法之一。&lt;/p&gt;

&lt;h3 id=&#34;什么是-opa&#34;&gt;什么是 OPA&lt;/h3&gt;

&lt;p&gt;OPA 是一个通用的策略引擎，它使用一种高级的声明式语言（Rego）编写策略。下图展示了 OPA 集成到 Kubernetes API 生命周期的形式：&lt;/p&gt;

&lt;p&gt;&lt;img src=&#34;images/api-with-opa.png&#34; alt=&#34;api-with-opa&#34; /&gt;&lt;/p&gt;

&lt;h2 id=&#34;在-kubernetes-上安装-opa&#34;&gt;在 Kubernetes 上安装 OPA&lt;/h2&gt;

&lt;p&gt;我们希望在 Kubernetes 上借助 OPA/Rego 的弹性策略实现内容信任机制。然而在开始之前，首先要在集群上部署 OPA。&lt;/p&gt;

&lt;p&gt;假设你已经有了符合条件的集群，在完成命名空间创建和 Notary 步骤之后，就可以开始进入仓库中的 OPA 目录开始安装了。&lt;/p&gt;

&lt;p&gt;Kubernetes 和 OPA 之间的通信必须是 TLS 加密的，因此需要给 OPA 创建额外的证书和密钥。&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;language-command&#34;&gt;# copy the root-ca
cp ~/PATH/TO/k8-content-trust/notary-k8s/helm/notary/certs/root-ca.crt ~/PATH/TO/k8-content-trust/open-policy-agent/helm/opa/certs
# generate the additional OPA certs 
cd helm/opa
bash generateCerts.sh
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;OPA 在安装后是自动生效的，因此应该排除一些命名空间：&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;language-command&#34;&gt;kubectl label ns kube-system openpolicyagent.org/webhook=ignore
kubectl label ns opa openpolicyagent.org/webhook=ignore
kubectl label ns notary openpolicyagent.org/webhook=ignore
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;接下来我们要确认一下 &lt;code&gt;values.yaml&lt;/code&gt; 中的 &lt;code&gt;validating&lt;/code&gt; 和 &lt;code&gt;mutating&lt;/code&gt; 是否已经配置（晚些时候我们会设置 &lt;code&gt;mutating: true&lt;/code&gt;）：&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;language-yaml&#34;&gt;# open-policy-agent/helm/opa/values.yml
...
validating: true
mutating: false
...
&lt;/code&gt;&lt;/pre&gt;

&lt;pre&gt;&lt;code class=&#34;language-command&#34;&gt;# switch to namespace opa
helm upgrade --install opa opa
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;在安装结束之后，可以在终端打开一个新 Tab，会看到 OPA 日志中 API Server 的进入请求。&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;language-command&#34;&gt;# ctrl-c to exit
kubectl logs -n opa -f opa-deploy-&amp;lt;...&amp;gt; opa
&lt;/code&gt;&lt;/pre&gt;

&lt;h2 id=&#34;定义-validating-admission-control-控制内容信任&#34;&gt;定义 Validating Admission Control 控制内容信任&lt;/h2&gt;

&lt;p&gt;总算到了有意思的部分了，开始实现内容信任机制。Notary 和 OPA 都已整装待发，首先我们想拒绝一切不受信任的镜像。要完成这个任务，要先搞清楚 Docker Tag 和哈希之间的关系。&lt;/p&gt;

&lt;p&gt;一般来说，我们会使用 GUN 以及标签来部署镜像。然而多数人会忽略一个事实，镜像标签是可以覆盖的，因此它的唯一性是靠不住的。一个集合的所有者能够用同样的 Tag 多次推送变更了的已签署镜像。为了避免这种情况，应该使用唯一摘要进行镜像拉取。&lt;/p&gt;

&lt;p&gt;我们定义两条 Rego 规则来完成这个 Webhook：&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;拒绝只使用普通 Tag (包括 &lt;code&gt;latest&lt;/code&gt;)的部署。&lt;/li&gt;
&lt;li&gt;拒绝使用了哈希但是没有被 Notary 签名的镜像。&lt;/li&gt;
&lt;/ol&gt;

&lt;blockquote&gt;
&lt;p&gt;已经随 Helm 安装好。&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;先看看第一条规则（&lt;code&gt;helm/opa/policy/validating/rules.rego&lt;/code&gt;）&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;language-rego&#34;&gt;package policy.validating

operations := {&amp;quot;CREATE&amp;quot;, &amp;quot;UPDATE&amp;quot;}

kind := {&amp;quot;Pod&amp;quot;, &amp;quot;Deployment&amp;quot;}

# rule to deny digests for pods and deployments
deny[msg] {
  operations[input.request.operation]
  kind[input.request.kind.kind]
  image = get_images[_]
  not contains(image.name, &amp;quot;@sha256:&amp;quot;)
  msg := sprintf(&amp;quot;%v contains tag; only images with checksum are allowed&amp;quot;, [image.name])
}

# rule deny if digest is not in notary
deny[msg] {
  operations[input.request.operation]
  kind[input.request.kind.kind]
  image = get_images[_]
  contains(image.name, &amp;quot;@sha256:&amp;quot;)

  # Example to mock digest comparison
  # parts := split_image(image.name)
  # not parts.digest == &amp;quot;@sha256:50&amp;quot;

  get_checksum_status(image.name) != 200
  msg := sprintf(&amp;quot;No trust data found for the following image: %v &amp;quot;, [image.name])
}

# helper rules
# get images if pod
get_images[x] {
  input.request.kind.kind == &amp;quot;Pod&amp;quot;
  name := input.request.object.spec.containers[i].image
  x := {
    &amp;quot;index&amp;quot;: i,
    &amp;quot;name&amp;quot;: name,
  }
}

## get images if deployment
get_images[x] {
  input.request.kind.kind == &amp;quot;Deployment&amp;quot;
  name := input.request.object.spec.template.spec.containers[i].image
  x := {
    &amp;quot;index&amp;quot;: i,
    &amp;quot;name&amp;quot;: name,
  }
}

# rule to split gun and tag
split_image(image) = x {
  parts := split(image, &amp;quot;@sha256:&amp;quot;)
  x := {
    &amp;quot;gun&amp;quot;: parts[0],
    &amp;quot;digest&amp;quot;: parts[1],
  }
}

# rule to get digest from notary-wrapper
get_checksum_status(image) = status {
  wrapperRootCa := &amp;quot;/etc/certs/notary/root-ca.crt&amp;quot;
  notaryWrapperURL = &amp;quot;https://notary-wrapper-svc.opa.svc:4445/verify&amp;quot;
  parts := split_image(image)
  body := {
    &amp;quot;GUN&amp;quot;: parts.gun,
    &amp;quot;SHA&amp;quot;: parts.digest,
    &amp;quot;notaryServer&amp;quot;: &amp;quot;notary-server-svc.notary.svc:4443&amp;quot;,
  }

  headers_json := {&amp;quot;Content-Type&amp;quot;: &amp;quot;application/json&amp;quot;}
  output := http.send({&amp;quot;method&amp;quot;: &amp;quot;post&amp;quot;, &amp;quot;url&amp;quot;: notaryWrapperURL, &amp;quot;headers&amp;quot;: headers_json, &amp;quot;body&amp;quot;: body, &amp;quot;tls_ca_cert_file&amp;quot;: wrapperRootCa})
  status := output.status_code
}
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;上面的规则会检查尝试创建或更新 Pod 或者 Deployment 类型的 API 请求。&lt;/p&gt;

&lt;p&gt;根据资源类型，&lt;code&gt;get_image[x]&lt;/code&gt; 规则会确保遍历请求中的所有容器，检查这些容器是否用摘要（例如 &lt;code&gt;[GUN]@sha256:[digest hash]&lt;/code&gt;）进行拉取。&lt;/p&gt;

&lt;p&gt;因此简单的检查一下，镜像是否用了 &lt;code&gt;@sha256&lt;/code&gt; 就可以了。否则我们会认为此次尝试部署的是一个用 Tag 标识的镜像。如果这一规则被触发，请求就会被阻拦，并得到返回的错误消息。&lt;/p&gt;

&lt;p&gt;接下来我们继续定义第二个规则，拒绝没有被 Notary 信任的摘要。&lt;/p&gt;

&lt;p&gt;在这个规则里，我们在 &lt;code&gt;get_checksum_status(image)&lt;/code&gt; 中用了 OPA 中集成的 &lt;code&gt;http.send&lt;/code&gt; 函数。首先会从请求中获取每个镜像的哈希，然后在 &lt;code&gt;get_checksum_status(image)&lt;/code&gt; 中发送镜像的 GUN 和摘要到 Notary Wrapper，Notary Wrapper 会检查每个镜像是否都已签名。如果请求返回的不是 200，那么部署动作会被制止。&lt;/p&gt;

&lt;p&gt;简单说 &lt;code&gt;http.send&lt;/code&gt; 函数在目标不可用时不会返回响应（可以参考 OPA 的一个&lt;a href=&#34;https://github.com/open-policy-agent/opa/issues/2187&#34; target=&#34;_blank&#34;&gt;功能申请&lt;/a&gt;）。在我们这里因为有了 Notary Wrapper，只要它正常工作，就不会遇到这个困扰。然而一旦 Notary Wrapper 不可用，OPA 也会故障，会被 &lt;code&gt;ValidatingWebhookConfiguration&lt;/code&gt; 中的 &lt;code&gt;failurePolicy: Fail&lt;/code&gt; 定义所捕获。&lt;/p&gt;

&lt;p&gt;上面描述的两条规则就足以在 Kubernetes 集群中完成对内容信任的控制了。&lt;/p&gt;

&lt;p&gt;要进行测试，只需要简单的部署一个新的 Pod：&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;language-yaml&#34;&gt;# trust-pinning-test
apiVersion: v1
kind: Pod
metadata:
  name: trust-pinning-test
  namespace: default
spec:
  containers:
  # trigger rule 1:
  - image: GUN/&amp;lt;hub-id&amp;gt;/nginx:1
  # trigger rule 2:
  # - image: GUN/&amp;lt;hub-id&amp;gt;/nginx@sha256:89cce606b29fb2xxxxx
  # valid deployment:
  # - image: GUN/&amp;lt;hub-id&amp;gt;/nginx@sha256:&amp;lt;your-signed-RepoDigest&amp;gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;另外在 &lt;code&gt;open-policy-agent/tests&lt;/code&gt; 中还包含了多个针对不同需求的过个测试。&lt;/p&gt;

&lt;p&gt;接下来的示意图展示了我们目前的工作成果：&lt;/p&gt;

&lt;p&gt;&lt;img src=&#34;images/opa-image-verification.png&#34; alt=&#34;opa-image-verification&#34; /&gt;&lt;/p&gt;

&lt;p&gt;每次部署都会发出 API 请求，随即开始校验过程：&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;请求触发了校验 Webhook，发起对 OPA 的调用。&lt;/li&gt;
&lt;li&gt;OPA 会检查镜像的拉取方式，如果使用的是摘要方式，就会向 Notary Wrapper 请求信任数据。Notary Wrapper 则会从 Notary 服务器查询数据，并返回给 OPA，OPA 据此进行决策。如果没有触发规则，Kubernetes 会继续部署。&lt;/li&gt;
&lt;li&gt;根据哈希从镜像库拉取（本例中是 DockerHub）。&lt;/li&gt;
&lt;li&gt;部署 Pod。&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;到此为止，我们已经成功的实现了内容信任机制。然而查询 &lt;code&gt;RepoDigests&lt;/code&gt; 是个很麻烦的事情。如果能基于 Tag 使用内容信任就两全其美了。&lt;/p&gt;

&lt;h2 id=&#34;定义-mutating-admission-control-完成自动化&#34;&gt;定义 Mutating Admission Control 完成自动化&lt;/h2&gt;

&lt;p&gt;Mutating Webhook 是用于在校验之前对请求内容进行变更的，我们接下来会编写这样一个功能。每次用户尝试部署一个带标签的镜像时，就启动 Webhook，自动将镜像引用改为哈希模式。大致工作流程如下：&lt;/p&gt;

&lt;p&gt;&lt;img src=&#34;images/mutation-pod-deploy.png&#34; alt=&#34;mutation-pod-deploy&#34; /&gt;&lt;/p&gt;

&lt;p&gt;API 请求流经 Webhook：&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;如果请求中包含 Pod，操作类型是创建或者更新，并且镜像是用 Tag 标识的，就会触发 OPA 的 Mutating Webhook（在所有的验证之前）。&lt;/li&gt;
&lt;li&gt;OPA 会用 Tag 去检查镜像，接下来 OPA 会为每个标签发起新的 &lt;code&gt;http.send&lt;/code&gt; 请求到 Notary Wrapper，向 Notary 服务器发起查询。&lt;/li&gt;
&lt;li&gt;如果 Notary Wrapper 在 Notary 服务器上找到了对应这个标签的条目，就会返回最新的 &lt;code&gt;RepoDigest&lt;/code&gt; 给 OPA，否则报错。&lt;/li&gt;
&lt;li&gt;OPA 对 Deployment 进行修改，把镜像标签更换为哈希，并把变更后的请求内容发送给 API Server。&lt;/li&gt;
&lt;li&gt;API Server 继续完成创建或更新流程，校验 Webhook 会对请求进行检查，如果请求有效，就用 &lt;code&gt;RepoDigest&lt;/code&gt; 从可信的仓库拉取镜像，并完成部署。&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;因为我们已经在安装过程中给 OPA 注册了 Mutating Webhook，我们只需要加入新的 Rego 规则就可以了。最简单的方式就是回到本地的 Helm 目录，启用 &lt;code&gt;mutating&lt;/code&gt;，然后执行 &lt;code&gt;helm upgrade&lt;/code&gt;：&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;language-yaml&#34;&gt;# open-policy-agent/helm/opa/values.yml
...
validating: true
mutating: true
&lt;/code&gt;&lt;/pre&gt;

&lt;pre&gt;&lt;code class=&#34;language-command&#34;&gt;# switch to namespace opa
helm upgrade --install opa opa
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;OPA 中的 Mutating Webhook 是 &lt;code&gt;main&lt;/code&gt; 方法的一部分，这个方法会在 API 请求时发起变更。&lt;code&gt;helm upgrade&lt;/code&gt; 会加入下面的新规则：&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;language-rego&#34;&gt;package policy.mutating

import data.k8s.matches

main = {
  &amp;quot;apiVersion&amp;quot;: &amp;quot;admission.k8s.io/v1&amp;quot;,
  &amp;quot;kind&amp;quot;: &amp;quot;AdmissionReview&amp;quot;,
  &amp;quot;response&amp;quot;: response,
}

default uid = &amp;quot;missing-uid&amp;quot;

uid = input.request.uid

# default allow without patch
response = r {
  count(patch) == 0
  r := {
    &amp;quot;uid&amp;quot;: uid,
    &amp;quot;allowed&amp;quot;: true,
  }
}

# response with patch
response = {
  &amp;quot;uid&amp;quot;: input.request.uid,
  &amp;quot;allowed&amp;quot;: true,
  &amp;quot;patchType&amp;quot;: &amp;quot;JSONPatch&amp;quot;,
  &amp;quot;patch&amp;quot;: patch_bytes,
} {
  count(patch) &amp;gt; 0
  patch_json = json.marshal(patch)
  patch_bytes = base64url.encode(patch_json)
}

# patch
default patch = []

patch = result {
  operations := {&amp;quot;CREATE&amp;quot;, &amp;quot;UPDATE&amp;quot;}
  kind := {&amp;quot;Pod&amp;quot;, &amp;quot;Deployment&amp;quot;}
  
  
  operations[input.request.operation]
  kind[input.request.kind.kind]

  # construct patch for each image in the container array that requires it.
  result := [p |
    image = get_images[_]
    not contains(image.name, &amp;quot;@sha256:&amp;quot;)

    parts := split_image(image.name)

    # format: registry/project@sha256:xxx
    patchedImage := concat(&amp;quot;&amp;quot;, [parts.gun, &amp;quot;@sha256:&amp;quot;, get_digest(image.name)])

    # cconstruct JSON Patch for the deployment.
    # kube-apiserver expects changes to be represented as
    # JSON Patch operation against the resource.
    # the JSON Patch must be JSON serialized and base64 encoded.
    p := {
      &amp;quot;op&amp;quot;: &amp;quot;replace&amp;quot;,
      &amp;quot;path&amp;quot;: get_path(image.index),
      &amp;quot;value&amp;quot;: patchedImage,
    }
  ]
}

# helper rules

# rule to compute images set
# the first line ensures that its matched to the right k8s resource
# the second line iterates over each container and extracts the image
get_images[x] {
  input.request.kind.kind == &amp;quot;Pod&amp;quot;
  name := input.request.object.spec.containers[i].image
  x := {
    &amp;quot;index&amp;quot;: i,
    &amp;quot;name&amp;quot;: name,
  }
}

get_images[x] {
  input.request.kind.kind == &amp;quot;Deployment&amp;quot;
  name := input.request.object.spec.template.spec.containers[i].image
  x := {
    &amp;quot;index&amp;quot;: i,
    &amp;quot;name&amp;quot;: name,
  }
}

# construct and returns json path for &amp;quot;Pods&amp;quot;
get_path(index) = path {
  input.request.kind.kind == &amp;quot;Pod&amp;quot;
  path := concat(&amp;quot;/&amp;quot;, [&amp;quot;&amp;quot;, &amp;quot;spec&amp;quot;, &amp;quot;containers&amp;quot;, format_int(index, 10), &amp;quot;image&amp;quot;])
}

# construct and returns json path for &amp;quot;Deployment&amp;quot;
get_path(index) = path {
  input.request.kind.kind == &amp;quot;Deployment&amp;quot;
  path := concat(&amp;quot;/&amp;quot;, [&amp;quot;&amp;quot;, &amp;quot;spec&amp;quot;, &amp;quot;template&amp;quot;, &amp;quot;spec&amp;quot;, &amp;quot;containers&amp;quot;, format_int(index, 10), &amp;quot;image&amp;quot;])
}

split_image(image) = x {
  parts := split(image, &amp;quot;:&amp;quot;)
  x := {
    &amp;quot;gun&amp;quot;: parts[0],
    &amp;quot;tag&amp;quot;: parts[1],
  }
}

# helper rule to retrieve the digest from notary using notary-wrapper
get_digest(image) = digest {
  wrapperRootCa := &amp;quot;/etc/certs/notary/root-ca.crt&amp;quot;
  notaryWrapperURL = &amp;quot;https://notary-wrapper-svc.opa.svc:4445/list&amp;quot;
  parts := split_image(image)
  body := {
    &amp;quot;GUN&amp;quot;: parts.gun,
    &amp;quot;Tag&amp;quot;: parts.tag,
    &amp;quot;notaryServer&amp;quot;: &amp;quot;notary-server-svc.notary.svc:4443&amp;quot;
  }

  headers_json := {&amp;quot;Content-Type&amp;quot;: &amp;quot;application/json&amp;quot;}
  output := http.send({&amp;quot;method&amp;quot;: &amp;quot;post&amp;quot;, &amp;quot;url&amp;quot;: notaryWrapperURL, &amp;quot;headers&amp;quot;: headers_json, &amp;quot;body&amp;quot;: body, &amp;quot;tls_ca_cert_file&amp;quot;: wrapperRootCa})
  digest := output.body.Digest
}
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;简单说一下这段代码的功能：&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;OPA 会使用 &lt;code&gt;response&lt;/code&gt; 规则中的代码加入需要的响应。&lt;/li&gt;
&lt;li&gt;第一个 &lt;code&gt;response&lt;/code&gt; 针对的是无需变更的请求，允许任意的 API 请求通过。&lt;/li&gt;
&lt;li&gt;第二个 &lt;code&gt;response&lt;/code&gt; 会调用 &lt;code&gt;patch&lt;/code&gt; 规则。&lt;/li&gt;
&lt;li&gt;&lt;code&gt;patch&lt;/code&gt; 规则会对任何面向 &lt;code&gt;Pod&lt;/code&gt; 或者 &lt;code&gt;Deployment&lt;/code&gt; 的 API 请求进行变更。结果参数首先会获取 API 请求中的镜像，检查是否每个镜像都是使用哈希进行拉取的（URL 中包含了 &lt;code&gt;@shar256:&lt;/code&gt;）。&lt;/li&gt;
&lt;li&gt;如果不满足上一个条件，就会使用 &lt;code&gt;split_image&lt;/code&gt; 规则将镜像分为名称和标签两部分。&lt;/li&gt;
&lt;li&gt;&lt;code&gt;split_image&lt;/code&gt; 返回的是一个数组，&lt;code&gt;get_digest&lt;/code&gt; 中使用这个数组调用 &lt;code&gt;http.send&lt;/code&gt; 函数通过 Notary Wrapper 向 Notary 请求哈希。如果 Notary 没有对应的哈希，会得到 404 的返回值。&lt;/li&gt;
&lt;li&gt;Kubernetes 中使用 &lt;code&gt;.json&lt;/code&gt; 格式的补丁。&lt;code&gt;.json&lt;/code&gt; 补丁（赋值给 &lt;code&gt;p&lt;/code&gt;）需要在 &lt;code&gt;path&lt;/code&gt; 参数中指定的路径上执行 &lt;code&gt;replace&lt;/code&gt; 操作，从而替换原有的拉取方式。在 Pod 和 Deployment 中，镜像字段的路径是不同的，我们需要创建两个 &lt;code&gt;get_digest&lt;/code&gt; 和  &lt;code&gt;get_path&lt;/code&gt; 来应对两种情况。&lt;/li&gt;
&lt;li&gt;OPA 会对补丁进行编码，并返回变更后的 API 请求给 API Server，继续后续操作。&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;如果想要测试这个 Webhook，可以看看 &lt;code&gt;open-policy-agent/tests&lt;/code&gt;，如果保存了前面的校验 Webhook，可以测试一下有效和无效的 Tag 或者哈希。下表总结了 Webhook 的响应情况：&lt;/p&gt;

&lt;p&gt;&lt;img src=&#34;images/table.png&#34; alt=&#34;table&#34; /&gt;&lt;/p&gt;

&lt;h2 id=&#34;总结和展望&#34;&gt;总结和展望&lt;/h2&gt;

&lt;p&gt;最终，我们成功地在 Kubernetes 集群上，无需改动部署习惯的情况下，实现了内容信任机制，除了这个，OPA 还能做很多其它的校验工作。&lt;/p&gt;

&lt;p&gt;我们知道这篇文章很长，但是我希望尽可能多地为读者提供更多细节。我们认为，虽然有很多的容器扫描和加固方面的技术，镜像签署和信任是目前容器安全方面的最大盲区之一。&lt;/p&gt;

&lt;p&gt;下一步需要做点什么呢？还有很多细节我们没能说明：&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;性能&lt;/strong&gt;：校验和变更过程的性能测试。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;生产就绪&lt;/strong&gt;：提供高可用的 Notary 部署，并把客户端（包括 Docker 客户端）做到硬件安全模块。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;CI-CD 集成&lt;/strong&gt;：在 CI/CD 中自动化地进行签名。&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;感谢阅读全文，希望对你有所助益。这里尤其要感谢来自 OPA/Styra 的 Asad、Torin 以及 Jeff，对我们编写的规则作出很多支持。&lt;/p&gt;

&lt;h2 id=&#34;相关链接&#34;&gt;相关链接&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Github 仓库：&lt;code&gt;https://github.com/k8s-gadgets/k8s-content-trust&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;TUF 项目：&lt;code&gt;https://theupdateframework.github.io/&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;Notary 数据库：&lt;code&gt;https://docs.docker.com/notary/service_architecture/&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;Notary 架构：&lt;code&gt;https://docs.docker.com/notary/service_architecture/&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;Notary 客户端：&lt;code&gt;https://github.com/theupdateframework/notary/releases&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;Kubernetes 关于内容信任的讨论：&lt;code&gt;https://github.com/kubernetes/kubernetes/issues/30603#issuecomment-430889781&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
</description>
    </item>
    
  </channel>
</rss>
