<?xml version="1.0" encoding="utf-8" standalone="yes" ?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom">
  <channel>
    <title>SPIFFE | 伪架构师</title>
    <link>/tags/spiffe/</link>
      <atom:link href="/tags/spiffe/index.xml" rel="self" type="application/rss+xml" />
    <description>SPIFFE</description>
    <generator>Source Themes Academic (https://sourcethemes.com/academic/)</generator><language>zh</language><lastBuildDate>Sat, 17 Dec 2022 20:16:36 +0800</lastBuildDate>
    <image>
      <url>/img/logo-wide.png</url>
      <title>SPIFFE</title>
      <link>/tags/spiffe/</link>
    </image>
    
    <item>
      <title>在 SPIRE 中用 SSH 证实节点身份</title>
      <link>/post/register-node-with-ssh/</link>
      <pubDate>Sat, 17 Dec 2022 20:16:36 +0800</pubDate>
      <guid>/post/register-node-with-ssh/</guid>
      <description>

&lt;p&gt;前面关于 SPIRE 的内容中，介绍了使用 JOIN Token 证实节点身份的方法。这种方法比较简易，但是完全依赖 SPIRE Server/Agent 的“内循环”，并不利于外部管理，同时每次节点更新，都要照本宣科的重来一遍。对于动态集群来说，这种方式并不理想，SPIRE 包含了面向 OpenStack、几大公有云以及 TPM 等的花钱证实节点身份的方案；除了这些之外，还有个经济型的证实方法——使用 SSH。&lt;/p&gt;

&lt;p&gt;我们一般使用的免密登录 SSH 方案通常是点对点的，总结来说就是服务器和客户端各自有各自的公私钥，互相进行信任操作：&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;SSHD 会自动生成服务器端的公私钥&lt;/li&gt;
&lt;li&gt;客户端通常使用 &lt;code&gt;ssh-keygen&lt;/code&gt; 命令生成自己的公私钥&lt;/li&gt;
&lt;li&gt;客户端将服务器端的证书脚印加到自己的 &lt;code&gt;know_hosts&lt;/code&gt; 文件里面，代表信任该地址和证书的组合&lt;/li&gt;
&lt;li&gt;服务器将客户端的公钥加到服务侧特定用户的 &lt;code&gt;authorized_keys&lt;/code&gt; 文件之中，代表认可以该密钥作为特定用户的身份证明。&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;不难看出，这个过程实际上是跟前面的 JOIN Token 方式是对等的，并不会提升节点证实过程的可管理性。因此 SPIRE 的 SSH 插件要求使用基于 CA 的 SSH 方法。&lt;/p&gt;

&lt;h2 id=&#34;用-ca-进行-ssh-认证&#34;&gt;用 CA 进行 SSH 认证&lt;/h2&gt;

&lt;p&gt;这种方式比上面的点对点认证方式稍微复杂一些。主要区别在于：&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;主机身份和用户身份都用 CA 进行签署&lt;/li&gt;
&lt;li&gt;同样地，主机和用户身份的互信，也是通过对 CA 的信任完成&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;大概要完成几个工作：&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;创建节点 CA 证书，SSH 客户端信任该 CA 证书&lt;/li&gt;
&lt;li&gt;用节点 CA 签发主机证书，并将服务端证书记录在 SSHD 的配置文件中。&lt;/li&gt;
&lt;li&gt;创建客户端 CA 证书，SSH 服务端信任该 CA 证书&lt;/li&gt;
&lt;li&gt;使用客户端 CA 签发用户证书，以此作为登录凭据。&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;例如 ChatGPT 告诉我的步骤是这样的：&lt;/p&gt;

&lt;p&gt;&lt;img src=&#34;images/chatgpt-sshca.png&#34; alt=&#34;chatgpt ssh ca&#34; /&gt;&lt;/p&gt;

&lt;p&gt;几个关键的命令：&lt;/p&gt;

&lt;h3 id=&#34;生成并配置-hostkey&#34;&gt;生成并配置 HostKey&lt;/h3&gt;

&lt;p&gt;下面的命令可以用于 SSHD 初始化，利用 CA 生成 HostKey：&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;language-plaintext&#34;&gt;ssh-keygen -s /etc/ssh/ca \
     -I &amp;quot;$(hostname --fqdn) host key&amp;quot; \
     -n &amp;quot;$(hostname),$(hostname --fqdn),$(hostname -I|tr &#39; &#39; &#39;,&#39;)&amp;quot; \
     -V -5m:+3650d \
     -h \
     /etc/ssh/ssh_host_rsa_key.pub \
     /etc/ssh/ssh_host_dsa_key.pub \
     /etc/ssh/ssh_host_ecdsa_key.pub
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;查看一下生成的证书内容：&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;language-command&#34;&gt;$ ssh-keygen -L -f ssh_host_rsa_key-cert.pub
ssh_host_rsa_key-cert.pub:
        Type: ssh-rsa-cert-v01@openssh.com host certificate
        Public key: RSA-CERT SHA256:[...]
        Signing CA: RSA SHA256:[...] (using rsa-sha2-512)
        Key ID: &amp;quot;ssh&amp;quot;
        Serial: 0
        Valid: from 2022-12-16T08:12:02 to 2032-12-13T08:17:02
        Principals:
                ssh
                ssh
                10.211.55.9
                fdb2:2c26:f4e4:0:21c:42ff:fe2a:18c4
        Critical Options: (none)
        Extensions: (none)
&lt;/code&gt;&lt;/pre&gt;

&lt;h3 id=&#34;配置-hostkey&#34;&gt;配置 HostKey&lt;/h3&gt;

&lt;p&gt;生成主机凭据之后，将证书和密钥信息加入 &lt;code&gt;/etc/ssh/sshd_config&lt;/code&gt;：&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;language-plaintext&#34;&gt;HostKey /etc/ssh/ssh_host_ecdsa_key
HostKey /etc/ssh/ssh_host_ed25519_key
HostKey /etc/ssh/ssh_host_rsa_key
HostCertificate /etc/ssh/ssh_host_ecdsa_key-cert.pub
HostCertificate /etc/ssh/ssh_host_ed25519_key-cert.pub
HostCertificate /etc/ssh/ssh_host_rsa_key-cert.pub
&lt;/code&gt;&lt;/pre&gt;

&lt;h3 id=&#34;让客户端信任主机-ca&#34;&gt;让客户端信任主机 CA&lt;/h3&gt;

&lt;p&gt;和前面提到的 FingerPrint 方式类似，把 CA 证书公钥加入到客户端的 &lt;code&gt;~/.ssh/know_hosts&lt;/code&gt; 之中，例如：&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;language-plaintext&#34;&gt;@cert-authority * ssh-rsa ...AAAAB3NzaC1yc2EAAAADAQABAAABgQCb... someone@ssh
&lt;/code&gt;&lt;/pre&gt;

&lt;h3 id=&#34;生成客户端证书&#34;&gt;生成客户端证书&lt;/h3&gt;

&lt;p&gt;和前面生成主机身份证书的情况类似，这次去掉了 &lt;code&gt;-h&lt;/code&gt; 参数：&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;language-plaintext&#34;&gt;ssh-keygen -s /etc/ssh/ca \
    -I &amp;quot;$(whoami)@$(hostname --fqdn) user key&amp;quot; \
    -n &amp;quot;$(whoami)&amp;quot; \
    -V -5m:+3650d \
    ~/.ssh/id_rsa.pub
&lt;/code&gt;&lt;/pre&gt;

&lt;h3 id=&#34;服务端信任客户端证书-ca&#34;&gt;服务端信任客户端证书 CA&lt;/h3&gt;

&lt;p&gt;同样在 &lt;code&gt;/etc/ssh/sshd_config&lt;/code&gt; 配置中加入 &lt;code&gt;TrustedUserCAKeys&lt;/code&gt;，具体取值为用户 CA 的公钥文件名。&lt;/p&gt;

&lt;p&gt;完成这些内容之后，如果使用新的身份证书登录成功，则代表前置任务完成。否则可以参考以下材料：&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href=&#34;https://access.redhat.com/documentation/en-us/red_hat_enterprise_linux/6/html/deployment_guide/sec-creating_ssh_ca_certificate_signing-keys&#34; target=&#34;_blank&#34;&gt;RHEL：Using OpenSSH Certificate Authentication&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://www.lorier.net/docs/ssh-ca.html&#34; target=&#34;_blank&#34;&gt;Using CA With SSH&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

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

&lt;p&gt;前面的 SSH 配置只是个铺垫。SPIRE 使用 SSHPOP 实现了 Server 和 Agent 侧的节点证实插件，两个插件需要协同工作，&lt;a href=&#34;https://github.com/spiffe/spire/blob/main/doc/plugin_server_nodeattestor_sshpop.md&#34; target=&#34;_blank&#34;&gt;官网的说明&lt;/a&gt;非常简明扼要：&lt;/p&gt;

&lt;p&gt;&lt;img src=&#34;images/NodeAttestor-ssh.png&#34; alt=&#34;ssh node attestor&#34; /&gt;&lt;/p&gt;

&lt;p&gt;稍稍延展说明一下需要注意的要点：&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;SPIRE Agent 所在的节点实际上是作为 SSH 的服务端&lt;/li&gt;
&lt;li&gt;SPIRE Agent 联系 SPIRE Server 之后，SPIRE Server 要通过 SSH 来访问 SSH 服务端来确认身份。&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;因此上面语焉不详的配置就比较清楚了：&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;SPIRE Server 的 &lt;code&gt;cert_authorities&lt;/code&gt; 需要的是客户端证书内容，例如 &lt;code&gt;[&amp;quot;ssh-rsa XXXX46IvQ+bDEXYvf8pM= someone@ssh&amp;quot;]&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;SPIRE Server 的 &lt;code&gt;cert_authorities_path&lt;/code&gt; 指向节点 CA 公钥，例如 &lt;code&gt;XXXX/ca.pub&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;SPIRE Agent 的 &lt;code&gt;host_cert_path&lt;/code&gt; 指向主机证书文件，例如 &lt;code&gt;XXXX_key-cert.pub&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;SPIRE Agent 的 &lt;code&gt;host_key_path&lt;/code&gt; 指向密钥文件，例如 &lt;code&gt;XXXX_key&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;配置完成之后，启动 SPIRE Server，获取并把 Trust Bundle 传递给 SPIRE Agent，启动 SPIRE Agent，可以看到生成了形如 &lt;code&gt;&amp;quot;spiffe://spiffe.dom/spire/agent/sshpop/XXXX&lt;/code&gt; 的 SVID，说明这个证实过程已经成功完成。&lt;/p&gt;

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

&lt;p&gt;本以为这是个顺便完成的东西，结果从来没想过 SSH 还有个 CA 这样的玩意，卡了好些时间，轻敌了。&lt;/p&gt;

&lt;p&gt;另，值此辞旧迎新之际，祝大家身体健康、事业稳定、学习进步、物资充足——最重要运气爆棚吧：）&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>用 Ghostunnel 和 SPIRE 为 NGINX 提供 SPIFFE 认证</title>
      <link>/post/ghostunnel-spire-101/</link>
      <pubDate>Sat, 22 Oct 2022 21:55:16 +0800</pubDate>
      <guid>/post/ghostunnel-spire-101/</guid>
      <description>

&lt;p&gt;之前对 SPIFFE 和 SPIRE 进行了一个相对全面/啰嗦的介绍，这一篇就反过来，用一个简单的例子来展示 SPIRE 的基本用法，本文中会以 NGINX 作为服务生产方，使用 &lt;a href=&#34;https://github.com/ghostunnel/ghostunnel&#34; target=&#34;_blank&#34;&gt;Ghostunnel&lt;/a&gt; 当做 NGINX 的反向代理，把原有的 HTTP 通信升级为支持定期正顺轮转的双向 TLS 认证协议，并且用 CURL 使用客户端证书来通过 Ghostunnel 安全地访问背后的 NGINX。这里为 CURL 和 NGINX 提供证书以及轮转的，就是 SPIRE 的 Server 和 Agent。&lt;/p&gt;

&lt;p&gt;Ghostunnel 是一个简单的 TLS 代理，能为非 TLS 的后端提供双向认证能力。Ghostunnel 能够以服务端（反向代理）或者客户端（代理）的模式进行工作，类似 stunnel。不同的是，他还支持访问控制、证书轮转、ACME 以及最近总在唠叨的 SPIFFE。&lt;/p&gt;

&lt;p&gt;本文中会演示的过程实际上是 Ghostunnel 的 &lt;a href=&#34;https://github.com/ghostunnel/ghostunnel/tree/master/docs/spiffe-workload-api-demo&#34; target=&#34;_blank&#34;&gt;SPIFFE DEMO&lt;/a&gt; 的一个精简版，会略细致讲述每个步骤涉及的内容。整个过程分为如下一些环节：&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;环境准备：准备运行环境，包括 SPIRE Agent/Server 的构建、NGINX 的安装、以及 Ghostunnel 的构建等&lt;/li&gt;
&lt;li&gt;编写 SPIRE Server 配置，并启动&lt;/li&gt;
&lt;li&gt;生成 Ghostunnel 以及 CURL 的 Agent Token，并编写配置文件启动对应的 SPIRE Agent&lt;/li&gt;
&lt;li&gt;启动 Ghostunnel&lt;/li&gt;
&lt;li&gt;获取 CURL 客户端证书并测试连接&lt;/li&gt;
&lt;/ol&gt;

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

&lt;p&gt;这里使用的是基于 ARM 的一个 Ubuntu 系统，使用 APT 安装并启动 NGINX。另外后续步骤还需要 GIT 工具以及连接 GITHUB，并使用 GOLANG 构建 SPIRE 以及 Ghostunnel。&lt;/p&gt;

&lt;p&gt;GIT 获取 SPIRE 版本，并进行构建：&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
Cloning into &#39;spire&#39;...
...
$ cd spire
$ make bin/spire-agent
Installing go1.18.4...
Building bin/spire-agent...
$ make bin/spire-server
Building bin/spire-server...
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;接下来获取 Ghostunnel 并进行构建：&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;language-command&#34;&gt;$ git clone https://github.com/ghostunnel/ghostunnel.git
Cloning into &#39;ghostunnel&#39;...
...
$ make ghostunnel
go build -ldflags &#39;-X main.version=v1.6.1-25-g8ae18ea&#39; -o ghostunnel .
...
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;构建成功后，把三个新生成的可执行文件拷贝到可见目录备用。&lt;/p&gt;

&lt;p&gt;然后建立测试目录，大致目录结构如下：&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;spire-101

&lt;ul&gt;
&lt;li&gt;certs&lt;/li&gt;
&lt;li&gt;conf&lt;/li&gt;
&lt;li&gt;data&lt;/li&gt;
&lt;li&gt;logs&lt;/li&gt;
&lt;li&gt;socks&lt;/li&gt;
&lt;/ul&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h2 id=&#34;编写-spire-server-配置并启动服务&#34;&gt;编写 SPIRE Server 配置并启动服务&lt;/h2&gt;

&lt;pre&gt;&lt;code class=&#34;language-hcl&#34;&gt;server {
    bind_address = &amp;quot;0.0.0.0&amp;quot;
    bind_port = &amp;quot;8081&amp;quot;
    socket_path = &amp;quot;socks/spire-server.sock&amp;quot;
    trust_domain = &amp;quot;spiffe.dom&amp;quot;
    data_dir = &amp;quot;data/spire-server&amp;quot;
    log_level = &amp;quot;DEBUG&amp;quot;
    ca_ttl = &amp;quot;30m&amp;quot;
    default_svid_ttl = &amp;quot;2m&amp;quot;
    ca_subject = {
        country = [&amp;quot;CN&amp;quot;],
        organization = [&amp;quot;FUNNY&amp;quot;],
        common_name = &amp;quot;&amp;quot;,
    }
}

plugins {
    DataStore &amp;quot;sql&amp;quot; {
        plugin_data {
            database_type = &amp;quot;sqlite3&amp;quot;
            connection_string = &amp;quot;data/spire-server/datastore.sqlite3&amp;quot;
        }
    }

    NodeAttestor &amp;quot;join_token&amp;quot; {
        plugin_data {
        }
    }

    KeyManager &amp;quot;disk&amp;quot; {
        plugin_data {
            keys_path = &amp;quot;data/spire-server/keys.json&amp;quot;
        }
    }
}
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;此处配置文件的几个要点：&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;TCP 监听 &lt;code&gt;0.0.0.0:8081&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;监听 Socket 路径为 &lt;code&gt;socks/spire-server.sock&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;使用 &lt;code&gt;spiffe.dom&lt;/code&gt; 作为信任域&lt;/li&gt;
&lt;li&gt;SVID 的默认寿命为 2 分钟&lt;/li&gt;
&lt;li&gt;使用 SQLite3 作为数据存储引擎，数据库文件保存在 &lt;code&gt;data/spire-server/datastore.sqlite3&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;在本地存储 Key，路径为 &lt;code&gt;data/spire-server/keys.json&lt;/code&gt;。&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;然后用这个配置文件启动 SPIRE Server：&lt;code&gt;spire-server run -config conf/spire-server.conf &amp;gt; logs/spire-server.log 2&amp;gt;&amp;amp;1 &amp;amp;&lt;/code&gt;&lt;/p&gt;

&lt;h2 id=&#34;启动-agent&#34;&gt;启动 Agent&lt;/h2&gt;

&lt;p&gt;这个小实验需要用到两个 Agent，分别负责服务端和客户端的身份。在运行 Agent 之前，首先要获取 SPIRE Server 的 Trust Bundle：&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;language-command&#34;&gt;$ spire-server bundle show \
  -socketPath socks/spire-server.sock &amp;gt; conf/bundle.crt
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;上述命令将 Trunst Bundle 保存到文件 &lt;code&gt;conf/bundle.crt&lt;/code&gt;。&lt;/p&gt;

&lt;p&gt;服务端 Agent 配置文件如下：&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;language-hcl&#34;&gt;agent {
    data_dir = &amp;quot;data/server-side-agent&amp;quot;
    log_level = &amp;quot;DEBUG&amp;quot;
    server_address = &amp;quot;127.0.0.1&amp;quot;
    server_port = &amp;quot;8081&amp;quot;
    socket_path =&amp;quot;socks/server-side-agent.sock&amp;quot;
    trust_bundle_path = &amp;quot;conf/bundle.crt&amp;quot;
    trust_domain = &amp;quot;spiffe.dom&amp;quot;
}

plugins {
    NodeAttestor &amp;quot;join_token&amp;quot; {
        plugin_data {
        }
    }
    KeyManager &amp;quot;disk&amp;quot; {
        plugin_data {
            directory = &amp;quot;data/server-side-agent&amp;quot;
        }
   }
   WorkloadAttestor &amp;quot;unix&amp;quot; {
        plugin_data {
             discover_workload_path = true
        }
    }
}
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;这个配置的要点是：&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;使用 &lt;code&gt;127.0.0.1:8081&lt;/code&gt; 作为 SPIRE Server&lt;/li&gt;
&lt;li&gt;监听 &lt;code&gt;socks/spire-server.sock&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;信任 &lt;code&gt;conf/bundle.crt&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;Unix Workload Attestor 中开放了选项 &lt;code&gt;discover_workload_path&lt;/code&gt;，从而可以通过二进制文件位置或者哈希识别调用 Agent 的应用的身份&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;为这个 Agent 创建一个 Token，用于标识 Agent 的身份：&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;language-command&#34;&gt;$ spire-server token generate \
    -socketPath socks/spire-server.sock \
    -spiffeID spiffe://spiffe.dom/server-node
Token: [Token Hash]
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;上面命令生成了一个 Token，其 SPIFFE ID 为  &lt;code&gt;spiffe://spiffe.dom/server-node&lt;/code&gt;。然后启动服务侧 Agent：&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;language-command&#34;&gt;$ spire-agent run \
    -config conf/client-side-agent.conf \
    -joinToken [Token Hash] &amp;gt; logs/client-side-agent.log 2&amp;gt;&amp;amp;1 &amp;amp;
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;接下来启动客户侧的 Agent，配置文件如下：&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;language-hcl&#34;&gt;agent {
    data_dir = &amp;quot;data/client-side-agent&amp;quot;
    log_level = &amp;quot;DEBUG&amp;quot;
    server_address = &amp;quot;127.0.0.1&amp;quot;
    server_port = &amp;quot;8081&amp;quot;
    socket_path =&amp;quot;socks/client-side-agent.sock&amp;quot;
    trust_bundle_path = &amp;quot;conf/bundle.crt&amp;quot;
    trust_domain = &amp;quot;spiffe.dom&amp;quot;
}

plugins {
    NodeAttestor &amp;quot;join_token&amp;quot; {
        plugin_data {
        }
    }
    KeyManager &amp;quot;disk&amp;quot; {
        plugin_data {
            directory = &amp;quot;data/client-side-agent&amp;quot;
        }
   }
   WorkloadAttestor &amp;quot;unix&amp;quot; {
        plugin_data {
        }
    }
}
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;跟上面的类似，我们也需要创建 Token 之后才能启动 Agent：&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;language-command&#34;&gt;$ spire-server token generate \
    -socketPath socks/spire-server.sock \
    -spiffeID spiffe://spiffe.dom/client-node
Token: [Token Hash]
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;使用上述 Token 和 配置文件启动 Agent：&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;language-command&#34;&gt;$ spire-agent run \
    -config conf/client-side-agent.conf \
    -joinToken &amp;quot;$TOKEN&amp;quot; &amp;gt; logs/client-side-agent.log 2&amp;gt;&amp;amp;1 &amp;amp;
&lt;/code&gt;&lt;/pre&gt;

&lt;h2 id=&#34;启动-ghostunnel&#34;&gt;启动 Ghostunnel&lt;/h2&gt;

&lt;p&gt;首先要给 Ghostunnel 一个身份，也就是 Entry：&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;language-command&#34;&gt;$ spire-server entry create \
    -selector unix:path:/usr/local/bin/ghostunnel \
    -socketPath socks/spire-server.sock \
    -spiffeID spiffe://spiffe.dom/ghost \
    -parentID spiffe://spiffe.dom/server-node
Entry ID         : fe4b1fd5-9e0a-440b-b08e-5c2c886b6a6e
SPIFFE ID        : spiffe://spiffe.dom/ghost
Parent ID        : spiffe://spiffe.dom/server-node
Revision         : 0
TTL              : default
Selector         : unix:path:/usr/local/bin/ghostunnel
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;上面的命令参数解释如下：&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;selector：类似 Kubernetes 中的 Label Selector，用 Workload 属性来界定身份，这里使用的是二进制路径：&lt;code&gt;unix:path:/usr/local/bin/ghostunnel&lt;/code&gt;，此文件启动之后，可以使用 Workload API 向 Agent 请求 SVID&lt;/li&gt;
&lt;li&gt;socketPath：指定 SPIRE Server 的监听 Socket&lt;/li&gt;
&lt;li&gt;spiffeID：Workload 的 SPIFFE ID&lt;/li&gt;
&lt;li&gt;parentID：Node 的 SPIFFE ID&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;创建这个 Entry 之后，SPIRE Server 会据此创建 SVID 下发给 Agent，Agent 只要根据 Selector 判断 Workload 身份，如果符合就可以发放 SVID 了。&lt;/p&gt;

&lt;p&gt;接下来启动 Ghostunnel：&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;language-command&#34;&gt;$ ghostunnel server \
    --use-workload-api-addr unix:///$(pwd)/socks/server-side-agent.sock \
    --listen=0.0.0.0:9099 \
    --target=localhost:80 \
    --allow-uri=spiffe://spiffe.dom/curl
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;这里使用了一个参数 &lt;code&gt;--use-workload-api-addr&lt;/code&gt;，要求使用 SPIFFE Workload API，对应 Agent Socket 为前面生成的 &lt;code&gt;socks/server-side-agent.sock&lt;/code&gt;。&lt;code&gt;--listen&lt;/code&gt; 和 &lt;code&gt;--target&lt;/code&gt; 分别代表了监听端口和被代理端口（也就是 NGINX）。而 &lt;code&gt;--allow-uri&lt;/code&gt; 参数则是一种访问控制手段，此处是允许 &lt;code&gt;spiffe://spiffe.dom/curl&lt;/code&gt; 的 SPIFFE ID 访问本服务。除了这种死板的方式之外，Ghostunnel 还能对接 OPA 实现更加复杂的符合生产要求的策略管控能力。&lt;/p&gt;

&lt;p&gt;如果此时用浏览器或者 CURL 访问该节点的 9099 端口，就会出现客户端证书不匹配的错误。&lt;/p&gt;

&lt;h2 id=&#34;获取-curl-客户端证书并测试连接&#34;&gt;获取 CURL 客户端证书并测试连接&lt;/h2&gt;

&lt;p&gt;类似的，我们给 CURL 创建一个 SVID：&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;language-command&#34;&gt;$ spire-server entry create \
    -selector unix:uid:1000 \
    -socketPath socks/spire-server.sock \
    -spiffeID spiffe://spiffe.dom/curl \
    -ttl 600 \
    -parentID spiffe://spiffe.dom/client-node
Entry ID         : 50911ef7-f191-4917-adde-1bf4e6192002
SPIFFE ID        : spiffe://spiffe.dom/curl
Parent ID        : spiffe://spiffe.dom/client-node
Revision         : 0
TTL              : 600
Selector         : unix:uid:1000
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;因为我们用的是 CURL，并不具备直接访问 Workload API 的能力，所以这里用了比较特别的参数：&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Selector 设置为当前用户的 ID，也就是说该用户执行的进程是可以匹配到这个 Entry 从而获取 SVID 的&lt;/li&gt;
&lt;li&gt;设置了 10 分钟的 TTL，满足我们后续手动操作的需要&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;然后用 &lt;code&gt;spire-agent api fetch&lt;/code&gt; 的方式获取证书：&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;language-command&#34;&gt;$ spire-agent api fetch \
    --socketPath socks/client-side-agent.sock \
    -write certs
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;命令执行后，会在 &lt;code&gt;certs&lt;/code&gt; 发现导出的证书文件，CURL 加上这个证书就能成功访问到 NGINX 了。&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;language-command&#34;&gt;$ curl -kv https://127.0.0.1:9099 \
    --cert certs/svid.0.pem --key certs/svid.0.key
*   Trying 127.0.0.1:9099...
* Connected to 127.0.0.1 (127.0.0.1) port 9099 (#0)
* ALPN, offering h2
* ALPN, offering http/1.1
* TLSv1.0 (OUT), TLS header, Certificate Status (22):
...
&amp;lt;!DOCTYPE html&amp;gt;
&amp;lt;html&amp;gt;
&amp;lt;head&amp;gt;
&amp;lt;title&amp;gt;Welcome to nginx!&amp;lt;/title&amp;gt;
&amp;lt;style&amp;gt;
...
&lt;/code&gt;&lt;/pre&gt;

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

&lt;p&gt;如果观察 &lt;code&gt;logs&lt;/code&gt; 目录中的日志，会看到在两个 Agent 的目录中会频频出现 Node 和 Workload 的 SVID 轮转的信息。那么如果 Server 挂了呢？这里就会发现，SPIRE Server 是系统中的一个单点，各个 Node 会因为 SVID 无法更新而异常退出，例如：&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;language-plaintext&#34;&gt;level=error msg=&amp;quot;Agent crashed&amp;quot; error=&amp;quot;current SVID has already expired and rotation failed: failed to dial dns:///127.0.0.1:8081: connection error: desc = \&amp;quot;transport: error while dialing: dial tcp 127.0.0.1:8081: connect: connection refused\&amp;quot;&amp;quot;
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;因此需要对 SPIRE Server 进行高可用部署。另外这个手工过程中我们也会看到，手工创建 Entry、传播 Bundle 以及获取证书、参数授权等，是不可能适应快速变更的云服务环境的，因此自动注册机制、策略执行机制以及相应的防篡改机制都是 SPIFFE 体系落地的必要条件。&lt;/p&gt;

&lt;p&gt;后续还会根据这些问题进行进一步的尝试。&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>
    
  </channel>
</rss>
