← All posts

把你的 Terraform state 移出公网(还不用搭 VPN)

Authagonal·June 22, 2026
terraformsecurityzero-trustdevopsazure

你的 Terraform state 文件是整个云环境里最敏感的一份产物。它是你运行的每一个资源的完整地图,而且取决于你用的 provider,它还会以明文形式保存密钥:连接字符串、自动生成的密码、密钥。默认情况下,这份文件存放在一个带公网端点的云存储账户里,把守它的只有一把访问密钥而已。一旦这把密钥泄露,攻击者根本不必费力枚举你的基础设施。你已经把图纸亲手递给他们了。

我们卖的是身份认证。一个连自己王国的钥匙都丢在公网上的认证供应商,根本没资格替你保管钥匙。所以我们的生产 state 账户压根没有任何公网入口。走到这一步要踩过三个坑,每一个我们都先摸清了它的形状,才把它做对。

坑一:先有鸡还是先有蛋

远程 state 需要一个在 Terraform 能运行之前就已经存在的后端。但这个后端本身就是基础设施,而你又希望由 Terraform 来管理它。你没法用那个存储账户去存放那个尚不存在的存储账户的 state。

出路是一次有意为之的两阶段引导。第一阶段以本地 state 运行,只创建那块地基:存放 state 的存储账户、它将栖身的网络,以及访问路径。第二阶段把后端配置块从本地切换为远程,把刚刚生成的 state 文件迁移上去,存进它自己刚刚创建好的那个账户。从那一刻起,这层引导基础设施就和其它一切一样,远程地自我管理。这几分钟感觉像是站在一架自己还在搭的梯子上,但搭完之后就一劳永逸了。

坑二:可达性难题,以及那笔 VPN 网关税

把账户改成私有只需一行配置:关掉公网访问,在它前面放一个私有端点。现在这个存储账户只能从你的虚拟网络内部访问到。可这恰恰就是问题所在,因为最经常需要访问它的那个东西——你的 CI 流水线——并不在你的虚拟网络里。你自己也不在。

教科书式的答案是搭一套 provider 的 VPN:一个托管的 VPN 网关,或者一台堡垒机,又或者带客户端证书的点到站点。任何做过这事的人都清楚那笔税:网关又贵又慢,要花时间预配;点到站点意味着不断签发和轮换客户端证书;每来一个新运维都得走一遍配置仪式;堡垒机则是又一台要打补丁、要掏钱养着的机器。这是一大堆常驻基础设施,而它们存在的全部意义不过是“让受信任的人够得着一个私有的东西”。

我们把这一切都跳过了。我们没用 VPN 网关,而是在 VNet 内部以容器形式跑了一个小小的零信任连接器——也就是 Tailscale、Twingate 和 Cloudflare Access 所属的那一类。它加入一张感知身份的网格。授权的人和 CI 流水线都通过这张网格抵达私有端点,按身份逐一认证,访问权限被精确收窄到唯一那个需要被访问的资源。没有网关,没有公网 IP,没有要轮换的证书,没有跳板机。CI 在一次运行期间建立连接,跑完就拆掉。可达性难题就这样消失了,却没有那张常驻基础设施的账单。

坑三:那个没法一步到位的封锁

最容易冒出来的本能想法,是从一开始就把账户声明为私有,就在创建它的同一次 Terraform apply 里搞定。这么干你会在私有路径还没建好之前就先剪断了脐带:这次 apply 需要通过公网端点写入 state,可私有端点和 DNS 都还没接好,于是你把 Terraform、连同你自己,一起锁在了你正要创建的那个账户外面。我们在触发之前就把这一串推演想清楚了,这正是那种把演练装进脑子里、而不是丢进终端里去跑的少有时刻。

所以这道封锁是一个有意拆出来的独立步骤,只在网络、私有端点和连接器全部就位并验证无误之后才执行。一条命令把公网访问翻成禁用。它一落地,公网端点就消失了,旧的 IP 允许列表也变得无关紧要——因为已经没有任何公网入口可供放行任何东西了——而往后每一次运行都通过网格去访问 state。我们用唯一算数的方式做了验证:确认这个账户现在能从私有路径响应,并且拒绝公网路径。

另一半:根本没有密码可偷

关掉公网端点,等于拆掉了那扇网络门。与之配套的一招,是确保门后也没有一把常驻的钥匙。我们的流水线不持有任何云端凭据。它用工作负载身份联合来认证:CI 系统出示一个短时效的 OIDC token,云端只为某一个特定仓库信任这个 token,然后发回一份几分钟内就过期的访问权限。没有躺在某个保险库里等着泄露的服务主体密钥,因为压根就不存在这样一把密钥。

state 的访问也遵循同一条规则。Terraform 读写 state blob 用的是一个绑定到该身份的短时效目录 token,而不是存储账户的访问密钥。于是攻击者最想要的两样东西——一条进入的路,外加一份能用来进入的凭据——到头来变成了一个仅限私有访问的端点,以及一个在他们读到它时就已经在过期的 token。没有任何静态的东西可供顺走。

何必费这个劲

state 文件泄露,通常不是因为有人攻破了加密。它们泄露,是因为账户是公开的,一把密钥落进了某条日志、某个 fork 或某台笔记本,而再没别的东西挡在路上。把公网端点拿掉,就把整整一类“密钥泄露了”从威胁模型里清了出去。这把密钥若不同时也在网格上,就一文不值。

这和我们打造产品时秉持的是同一条原则:别把安全功能锁在投入或套餐门槛背后,直接做就是了,因为另一条路通向的,是凌晨两点让你追悔莫及的那种东西。我们交付给客户的每一项安全功能——SSO 和 SAML、SCIM、MFA、强制 webhook、审计导出——在每个套餐上都默认开启,绝不留作向企业版加价的诱饵。看看都包含了什么