内容摘要与导读:文章介绍如何在非三大云环境(本地/自建虚拟化)实现机器身份认证与密钥管理。作者以 Proxmox 上的 vTPM 为信任根,通过 SPIFFE/SPIRE 完成节点和工作负载证明,将身份精确绑定到 systemd 服务;再用 Spire 签发的 JWT 登录 Infisical 获取密钥,避免硬编码凭据。文中给出 Spire Server/Agent 配置、TPM 哈希注册、systemd 单元文件、SPIFFE 注册条目和登录 Infisical 的命令示例,并说明该方案如何实现短期凭证、自动轮换和工作负载级隔离。
在英国生活让我学到的云知识,几乎和在 AWS 工作十年学到的一样多。我仍然记得第一次使用 EC2 身份验证的时候:机器身份当时还在兴起,不用把 Token 硬编码到实例里就能获取 secret,这简直不可思议。自那以后,AWS IAM 已经发展了很多,其他云基础设施也是如此。
再往后,我需要为本地部署基础设施实现机器到机器身份验证。手头有几个还不错的答案,但没有什么能像 AWS IAM、GCP 的 service accounts 或 Azure 托管身份那样顺畅,并且具备密码学保障。
十年过去了,曾被预测会全面迁移上云的工作负载并没有完全发生。一些组织甚至重新购买硬件,某些虚拟化公司也被收购了。本地部署世界仍然生机勃勃,尤其是在合规要求严格的受监管行业。但对于本地部署工作负载来说,安全性还没有像三大云那样顺畅。
其中一个重要部分就是 secrets 管理。如今硬编码凭据不可接受,既不安全又繁琐,所以大多数组织选择 secrets manager 来集中和自动化管理。
大型云厂商提供原生 secrets manager,通过存储凭据并在运行时将其交付给工作负载,来集中和自动化这一过程。这些方案很顺畅,因为它们与各自云中的工作负载原生集成。AWS Secrets Manager 能理解 EC2 实例的机器身份,因为 AWS 在为它背书。
如果你购买自己的硬件,情况就不是这样。在本地部署中,你的机器默认不受信任,所以需要有东西证明自己的身份,而这个东西不能是硬编码的 secret。
我的目标很简单:我有几个自研应用运行在我的虚拟化集群上,我想让它们安全地向 Infisical 认证并读取 secrets。额外好处是,我还弄清楚了如何将该登录限制到特定的 systemd 服务。
如何解决大型云之外的机器身份证明
机器身份作为一条信任链运作:在信任机器运行的工作负载之前,我们必须有理由信任机器本身。
TPM:硬件信任根
TPM(Trusted Platform Module)解决了第一个难题。每个 Windows 11 管理员都知道它,对于机器身份证明链中的信任根来说,它是一个极好的选择。它们包含一个背书密钥(EK),在工厂烧录,由公共证书颁发机构签名。如果你使用 vTPM(虚拟 TPM),Proxmox 节点会生成自己的 vTPM。它们大多是防篡改的(这个“大多”有分量,取决于你的威胁建模——如果有人拆开你的某个 TPM 芯片,就完了)。TPM 锚定机器的身份。
SPIFFE 和 Spire:验证每台机器
但我们现在需要验证每台机器。为此,我使用 Spire(SPIFFE Runtime Environment),它是 SPIFFE(Secure Production Identity Framework For Everyone)API 的一种实现。SPIFFE 是一个框架,描述如何跨异构基础设施识别工作负载。所以虽然我在这里将它与 TPM 一起使用,它也可以使用其他证明方法。
从核心上看,Spire 有几个简单的组件:
- 一个 Spire server,包含 trust domain、注册条目、CA/签名机构等。
- Spire 节点/agent,它们告诉 server 这台机器被允许代表哪些工作负载。
在实践中,我们不需要从头配置所有这些。Spire 有许多插件,允许在 server、node 和 workload 级别进行证明。
证明流程如何工作
假设我有一台带有 TPM 的 VM,运行 Spire agent 和 Spire Server。我会刻意保持通用:
- 虚拟机运行在 Proxmox 中,这不是背书,只是个人偏好。
- Spire Server 有一个 Proxmox 插件,可以简化设置,因为我不需要收集节点中 EK 的公共哈希。
因此在我的情况下,我按以下方式进行了设置。

- 使用由 Spire tpm 插件提供的
get_tpm_pubhash 二进制文件,从正在被认证的服务器中提取哈希,参考 tpm 插件仓库。
- spire-agent 使用预先提供的 bundle 认证 spire-server。
- spire-server 可以验证该密钥有效,因为它由受信任的 CA 签名,并且提前知道服务器的哈希。

- 基于这条信任链,服务器中的 spire-agent 会为自身获得一个 SVID(SPIFFE Verifiable Identity Document,即证明 SPIFFE 身份的证书或 JWT),并且可能使用 workload 插件为服务器上运行的工作负载获得一个 SVID。

现在你信任系统,甚至信任主机中运行的工作负载。
如何使用 Infisical 通过本地部署的 SPIFFE/Spire 证明来管理 secrets

一旦解决了机器身份,Infisical 就解决 secrets 管理部分。Infisical 是一个工作负载可向其认证的 secrets manager。
就像使用任何其他身份验证方法一样,现在你可以使用 SPIFFE,通过你提供给系统(或工作负载)的 SVID,在 Infisical 中认证你的机器身份。
一旦配置了身份,你就可以在系统中从 spire-agent(spire-agent api fetch jwt)获取 JWT。这意味着你的本地部署硬件现在可以通过 JWT 向 Infisical 认证,并使用 CLI 将 secrets 作为环境变量、.env 文件交付,或者只是为你的应用程序提供一个 Token 来读取 secrets。
Infisical 是开源的,并提供多种方式让 secrets 从一个地方传到另一个地方。因此,你可以将 Infisical 配置为包装成一个脚本,该脚本仅在进程运行时执行,并确保进程完成后清理 secrets。
这些部分既解决了机器身份问题,也解决了 secrets 管理问题,让你在留在自己的硬件上的同时,获得大型云厂商那样顺畅的安全运营。
如何设置 Spire server
那么我们该如何设置呢?当然是问你最喜欢的 LLM!
开个玩笑,让我们从 server 开始。
- 从官方项目仓库拉取 spire-server 的二进制文件。你还需要用于 server 的 TPM attestor,可以从 tpm 插件仓库获取。server 本身需要两个二进制文件、一个配置文件和 systemd unit 文件。所有目录、用户和文件都需要创建。
- 我已将二进制文件放到系统中的
/usr/local/bin。
user@spire:~$ ls /usr/local/bin/
spire-server tpm_attestor_server
- 我创建了以下基本配置文件,并在需要的地方进行了注释。
## 基本 Server 配置
server {
bind_address = "ip.address"
bind_port = "8081"
trust_domain = "corrarello.net"
data_dir = "/var/lib/spire/data/server"
ca_ttl = "168h"
default_x509_svid_ttl = "48h"
socket_path = "/run/spire/private/api.sock"
}
plugins {
# spire server 需要将其状态持久化到某处。对于我的简单设置,我使用 sqlite,虽然还有其他选项可用。
DataStore "sql" {
plugin_data {
database_type = "sqlite3"
connection_string = "/var/lib/spire/data/server/datastore.sqlite3"
}
}
# 使用哪个 node attestor。在我的情况下,我使用前面提到的 TPM,其他选项也可用。
NodeAttestor "tpm" {
plugin_cmd = "/usr/local/bin/tpm_attestor_server"
plugin_checksum = "ace348084a391cc96d6be225e999539641cef373ddc40e736dc8badc6a2c99d3"
plugin_data {
ca_path = "/etc/spire/certs"
hash_path = "/etc/spire/hashes"
# 在我的示例中,我将手动把哈希添加到上面的目录中。话虽如此,你可以使用 pve 插件直接与 Proxmox 验证它们
# pve {
# enabled = true
# cluster "pve-prod" {
# hosts = ["your.pve.host", "your.other.pve.host"]
# # 在此处添加你的 Proxmox API 凭据
# user = "spire-attestor@pve"
# token_id = "spire-token"
# token_secret = "00000000-0000-0000-0000-000000000"
#
# # 如果你为 Proxmox UI 使用自签名证书,请设置为 true
# insecure_skip_verify = true
# }
# }
}
}
## KeyManager 是用于签署 JWT 和 Certificate 的插件。
KeyManager "disk" {
plugin_data {
keys_path = "/var/lib/spire/data/server/keys.json"
}
}
}
- 我还为 spire server 创建了以下 systemd unit 文件,尝试应用一些合理的安全默认值:
[Unit]
Description=SPIRE Server (SPIFFE Runtime Environment Server)
After=network.target
[Service]
Type=simple
User=spire
Group=spire
ExecStart=/usr/local/bin/spire-server run -config /etc/spire/server.conf
Restart=on-failure
RestartSec=5
RuntimeDirectory=spire
RuntimeDirectoryMode=0750
## 安全加固
CapabilityBoundingSet=
ProtectSystem=strict
ProtectHome=true
ReadWritePaths=/var/lib/spire/data/server
ReadOnlyPaths=/etc/spire /usr/local/bin/spire-server /usr/local/bin/tpm_attestor_server
PrivateTmp=true
[Install]
WantedBy=multi-user.target
- 最后,启动服务并提取 PEM bundle,spire-agent 会需要它:
user@spire:~$ sudo /usr/local/bin/spire-server bundle show -socketPath /run/spire/private/api.sock
-----BEGIN CERTIFICATE-----
[...]
-----END CERTIFICATE-----
-----BEGIN CERTIFICATE-----
[...]
-----END CERTIFICATE-----
如何设置 Spire agent
现在切换到你的 agent,你将需要:
- 来自官方项目仓库的 spire-agent。你还需要用于 agent 的 TPM attestor,可以从 tpm 插件仓库获取。还要获取
tpm_pubhash 二进制文件,因为如果你没有使用类似用于 Proxmox 的 pve 插件,就需要用它来提取哈希并在 server 上授权它。
user@agent:~$ ls -l /usr/local/bin/
total 78200
-rwxr-xr-x 1 root root 66663576 Sep 10 09:46 spire-agent
-rwxr-xr-x 1 root root 13406370 Sep 10 09:46 tpm_attestor_agent
- 你需要将 server 的 bundle 放到
/etc/spire/agent/certs/ 中的某个位置,至少如果你遵循我的配置示例的话。
- 我的示例配置相当简单,设置了一个 NodeAttestor,以及若干 workload attestor,以便在我的情况下将 SVID 缩小到 systemd unit:
agent {
data_dir = "/var/lib/spire/data/agent"
log_level = "DEBUG"
server_address = "your.spire.server"
server_port = "8081"
socket_path = "/run/spire/agent_sockets/public.sock"
trust_domain = "corrarello.net"
trust_bundle_path = "/etc/spire/agent/certs/server-trust-bundle.pem"
}
plugins {
# 1. 核心硬件 Node Attestor(通过 Kernel Resource Manager 的 vTPM)
NodeAttestor "tpm" {
plugin_cmd = "/usr/local/bin/tpm_attestor_agent"
plugin_checksum = "5e1d927e3c18cfe2c28989330349003e43287b0289cceab4f12be3aa12e05eb2"
plugin_data {
tpm_path = "/dev/tpmrm0"
}
}
# 2. 用于存储动态客户端身份的 Key Manager
KeyManager "disk" {
plugin_data {
directory = "/var/lib/spire/data/agent"
}
}
# 3. Unix 和 Systemd Attestor,用于稍后解析本地 systemd 应用身份属性
WorkloadAttestor "unix" {
}
WorkloadAttestor "systemd" {
}
}
- 你还需要一个 systemd unit 文件来运行它:
[Unit]
Description=SPIRE Agent (SPIFFE Runtime Environment Agent)
After=network.target
[Service]
Type=simple
User=spire
Group=spire
SupplementaryGroups=tss
ExecStart=/usr/local/bin/spire-agent run -config /etc/spire/agent.conf
Restart=on-failure
RestartSec=5
## 设置用于本地应用程序 socket 挂载的运行时目录
RuntimeDirectory=spire/agent_sockets
RuntimeDirectoryMode=0755
## 安全隔离加固
##ProtectSystem=strict
ProtectHome=true
PrivateTmp=true
ReadWritePaths=/var/lib/spire/data/agent
ReadOnlyPaths=/etc/spire /usr/local/bin/spire-agent /usr/local/bin/tpm_attestor_agent /dev/tpmrm0
[Install]
WantedBy=multi-user.target
如何注册节点和工作负载
- 现在希望你能够启动 agent 进程。从这个 EK 收集哈希,以便将其放入 spire server:
user@agent:~$ sudo ./get_tpm_pubhash
66972674931c5ac68e652ca609239756e7ca97013564dc0900fc5a183fb6367c
user@spire:~$ sudo touch /etc/spire/hashes/66972674931c5ac68e652ca609239756e7ca97013564dc0900fc5a183fb6367c
- 为节点和工作负载都创建条目。下面的 selector 将节点身份锚定到该 TPM 公共哈希,再把工作负载绑定到特定的
systemd 服务:
user@spire:~$ sudo /usr/local/bin/spire-server entry create -socketPath /run/spire/private/api.sock -selector tpm:pubhash:66972674931c5ac68e652ca609239756e7ca97013564dc0900fc -parentID spiffe://corrarello.net/spire/server -spiffeID spiffe://corrarello.net/node/proxmox-vm-104
user@spire:~$ sudo /usr/local/bin/spire-server entry create -socketPath /run/spire/private/api.sock -parentID "spiffe://corrarello.net/spire/agent/tpm/66972674931c5ac68e652ca609239756e7ca97013564dc0900fc" -spiffeID "spiffe://corrarello.net/ns/prod/app/trading-advisor" -selector "unix:systemd_unit_name:trading-advisor.service" -audience "infisical"
这里原命令中的 selector 哈希可能由于复制被截断,使用时建议与 get_tpm_pubhash 输出的完整值核对。
如何使用 Spire JWT 登录 Infisical
- 注意,audience 已设置为
infisical,因为在 agent 中,现在使用由 spire-server 签发的 JWT 进行身份验证。
原文这段脚本因采集原因存在截断,下面保留能辨认的关键片段:
JWT_TOKEN=SPIRE_SOCKET" \
-audience "infisical" \
-spiffeID "$SPIFFE_ID" \
| grep -oE '[A-Za-z0-9_-]+\.[A-Za-z0-9_-]+\.[A-Za-z0-9_-]+' | head -n1)
RAW_RESPONSE='\n%{http_code}' -X POST \
"{INFISICAL_IDENTITY_ID}\",\"jwt\":\"${JWT_TOKEN}\"}")
HTTP_STATUS="'\n'}"
LOGIN_RESPONSE="'\n'*}"
TPM 支持的证明胜过本地硬编码 secrets
EC2 身份验证很神奇,因为 AWS 为实例背书,所以不必把 Token 硬编码到其中。自那以后身份验证已经发展了很多,但在本地部署工作负载中,机器身份仍未解决。许多组织仍然退回到硬编码 secrets,因为自己构建信任链很难。
我帮助过不同的本地部署机器身份验证方法。它们都有助于自动化,但都有一个弱点:机器在登录之前需要一个 secret,因此硬编码凭据的问题仍然存在。
TPM 和 Spire 通过在你自己的硬件上复现云提供商在 AWS、GCP 或 Azure 中所做的事情来解决这个问题:
- 没有需要交付的 secret。机器的身份锚定在烧录到 TPM 中的密钥(或由 hypervisor 为 vTPM 生成的密钥)中,因此没有任何东西需要硬编码、复制或泄露。
- 凭据是短期的,并且会自行轮换。Spire agent 会自动续订其 SVID,并且用于登录 Infisical 的 JWT 是按需获取的,而不是存储在磁盘上。
- 身份的作用域是工作负载,而不仅仅是主机。Selector 将身份绑定到特定已证明机器上的特定
systemd 服务,因此同一台机器上的另一个进程无法复用它。
入门很简单:安装 Spire server,证明你的节点,并开始注册你的工作负载。Infisical 用 JWT 交换访问你系统所需 secrets 的权限。你的本地部署工作负载获得与大型云厂商为其自身工作负载提供的同样顺畅、密码学保障的身份验证。
原文链接:https://infisical.com/blog/machine-identity-attestation-bare-metal