本文介绍了如何将 Infisical 作为 OpenTofu 的外部密钥管理方案,解决 OpenTofu 原生不支持 secrets management 的问题。
plan、apply 和 state 存储中的凭据。.tfvars 文件、环境变量和硬编码值会泄漏到 Git 历史、CI 日志和崩溃报告中。即使是 OpenTofu 的原生 state 加密,也只能保护 state 文件静态存储时的 secret,而不能在执行期间或日志中保护它们。.tfvars 文件,没有共享静态密钥,也没有自定义 glue code。plan/apply 期间获取 secret,而不会将其持久化到 state 文件中。在 HashiCorp 更改许可后,OpenTofu 作为 Terraform 的社区开源分支出现。它已经成为那些希望进行基础设施 provisioning、同时避免 vendor lock in 的团队默认使用的 IaC 工具。但和 Terraform 一样,OpenTofu 不包含内置的 secrets management。它的架构假定你会自行引入解决方案。
这是真实的运维缺口,而不是理论问题。secret 会在每次 OpenTofu 运行中的三个时点暴露出来:
tofu plan 会从变量、.tfvars 文件或环境变量中加载凭据,以验证你的配置。如果某个变量没有标记为 sensitive,其值可能会出现在控制台输出和日志中。tofu apply 会使用这些凭据与云 provider 进行认证、配置服务,并将值传递给 API。这里的配置错误会在 API 调用和构建日志中暴露凭据。大多数团队会从两种方法之一开始:.tfvars 文件或环境变量。两者都有广为人知的弱点。提交到 Git 的 .tfvars 文件会将凭据永久写入版本历史。环境变量更好一些,但它们仍然可能被其他进程检查、被写入崩溃报告,或者被粗心的 echo 语句打印到构建日志中。
经历过这些问题的团队通常会转向 HashiCorp Vault。但 Vault 也带来了自身的复杂性:基于 Raft 的集群、专门的运维团队、自定义 provider 配置,以及足够陡峭的学习曲线,以至于一些组织会在部署过程中放弃。
Infisical 采取了不同的方法。它是一个运行在 Postgres 上的开源 secrets management 平台(而不是专有基础设施),自带工作流和 UI,并作为原生 provider 与 OpenTofu 集成。本指南的目标是准确展示这种集成如何工作、代码是什么样子,以及如何将其用于生产环境。
Infisical 维护了一个可同时与 Terraform 和 OpenTofu 配合使用的 Terraform provider。你在配置中声明它,使用 machine identity 进行认证,并在运行时获取 secret。没有 .tfvars 文件,没有手动加密,也没有自定义脚本。
下面是从零开始的设置方式。
将 Infisical provider 添加到你的 required_providers 块中:
terraform {
required_providers {
infisical = {
source = "infisical/infisical"
}
}
}
这会告诉 OpenTofu 从 registry 下载 Infisical provider。这里为了简化说明没有展示版本固定,但在生产环境中,你应该固定到特定版本,以避免意外变更。
Infisical 使用 machine identity 来认证非人类客户端。你有两个选项:OIDC Auth(推荐用于基于云的 CI/CD)和 Universal Auth(用于不支持 OIDC 的环境)。
如果你的 CI/CD 运行在 GitHub Actions、GitLab CI、AWS、GCP 或 Azure 上,请使用 OIDC。这消除了在 pipeline 中存储任何长期有效 Infisical 凭据的需要。你的 CI 平台会签发一个短期 token,而 Infisical 会直接验证它:
provider "infisical" {
host = "https://app.infisical.com" # Infisical Cloud 默认可省略
auth = {
oidc = {
identity_id = "your-machine-identity-id"
}
}
}
identity_id 是你在 Infisical 控制台中创建的 machine identity。当 OpenTofu 在 CI pipeline 中运行时,provider 会将平台的 OIDC token 交换为一个短期 Infisical access token。没有需要轮换的 client secret,也没有会泄漏的静态凭据。
为什么这很重要:OIDC 解决了引导问题。没有它,你就需要把 Infisical 凭据存放在某处来访问 Infisical,而这正是你试图解决的“用 secret 去访问 secret”的问题。OIDC 让你的云 provider 直接为 runner 的身份背书。
对于不支持 OIDC 的环境(本地 CI runner、自定义构建系统、本地开发),使用带有 client ID 和 secret 的 Universal Auth:
provider "infisical" {
host = "https://your-infisical-instance.com" # 自托管必填
auth = {
universal = {
client_id = var.infisical_client_id
client_secret = var.infisical_client_secret
}
}
}
在 CI/CD 中,将这些值作为 pipeline secrets 传入(GitHub Actions secrets、GitLab CI variables 等),绝不要使用 .tfvars 或硬编码值。Infisical client secret 应该被限定到尽可能窄的权限范围,并按计划轮换。
警告: 这种方法仍然需要在你的 pipeline 中某处存储凭据。使用短期 token 和最小权限范围来限制影响面。如果你的平台支持 OIDC,请优先使用它。
Infisical 提供两种在 OpenTofu 中访问 secret 的方式,正确选择取决于你的 OpenTofu 版本和安全需求。
| Ephemeral Resource | Data Source | CLI Injection | Env Variables | |
|---|---|---|---|---|
| state 中有 secret? | 否 | 是 | 否 | 取决于情况 |
| 需要的版本 | OpenTofu v1.11+ | 任意 | 任意 | 任意 |
| 中心化 RBAC | 是 | 是 | 部分 | 否 |
| 审计追踪 | 是 | 是 | 是 | 否 |
| 复杂度 | 低 | 低 | 中 | 低 |
| 最适合 | 生产环境、敏感凭据 | 旧版本、非敏感配置 | 包装脚本、迁移 | 仅限本地开发 |
Ephemeral resource 是首选方法。它们在运行时获取 secret,并且不会将其写入 state 文件。这对于数据库密码和 API key 这类高度敏感的凭据至关重要:
## 获取数据库凭据而不持久化到 state
ephemeral "infisical_secret" "db_credentials" {
name = "DB_CREDENTIALS"
env_slug = "prod"
workspace_id = var.infisical_workspace_id
folder_path = "/database"
}
## 用它们来配置下游 provider
provider "postgresql" {
host = data.aws_db_instance.main.address
port = data.aws_db_instance.main.port
username = jsondecode(ephemeral.infisical_secret.db_credentials.value)["username"]
password = jsondecode(ephemeral.infisical_secret.db_credentials.value)["password"]
}
在 tofu apply 完成后,这些凭据就消失了。它们只在执行期间存在于内存中,绝不会作为 state 文件的一部分写入磁盘。
版本说明: Ephemeral resource 需要 OpenTofu v1.11 或更高版本。如果你使用的是旧版本,请使用 data source(选项 2)并加密你的 state backend。
对于使用较旧 OpenTofu 版本的团队,或者对于非敏感配置值,标准 data source 可以正常工作:
## 获取某个 folder 中的所有 secret
data "infisical_secrets" "api_secrets" {
env_slug = "dev"
workspace_id = var.infisical_workspace_id
folder_path = "/api"
}
## 引用单个值
resource "aws_db_instance" "main" {
engine = "postgres"
instance_class = "db.t3.medium"
username = data.infisical_secrets.api_secrets.secrets["DB_USER"].value
password = data.infisical_secrets.api_secrets.secrets["DB_PASS"].value
}
警告: Data source 的值会被存储在 state 文件中。如果你使用这种方法,请确保你的 state backend 已加密(例如带有 server side encryption 的 S3、默认加密的 GCS,或者使用 OpenTofu 的原生 state 加密),并且有访问控制。
下面是一个完整的、面向生产环境的示例,展示如何使用由 Infisical 管理的凭据来 provision 一个 AWS RDS instance。这使用了 ephemeral resource 和 OIDC authentication。
infra/
main.tf # Provider 声明和 data source
database.tf # RDS resource 定义
variables.tf # 输入变量(这里没有 secret)
outputs.tf # 仅输出非敏感内容
backend.tf # 远程 state 配置
terraform {
backend "s3" {
bucket = "myco-terraform-state"
key = "prod/database/terraform.tfstate"
region = "us-east-1"
encrypt = true
dynamodb_table = "terraform-locks"
}
}
variable "infisical_workspace_id" {
description = "用于此项目的 Infisical workspace ID"
type = string
}
variable "environment" {
description = "部署 environment(dev、staging、prod)"
type = string
default = "prod"
}
variable "infisical_identity_id" {
description = "用于 OIDC auth 的 machine identity ID"
type = string
}
terraform {
required_providers {
infisical = {
source = "infisical/infisical"
version = "~> 0.16" # 检查 registry 以获取最新版本
}
aws = {
source = "hashicorp/aws"
version = "~> 5.0"
}
}
}
provider "infisical" {
auth = {
oidc = {
identity_id = var.infisical_identity_id
}
}
}
provider "aws" {
region = "us-east-1"
}
## 以 ephemeral 方式获取数据库凭据
ephemeral "infisical_secret" "db_creds" {
name = "RDS_CREDENTIALS"
env_slug = var.environment
workspace_id = var.infisical_workspace_id
folder_path = "/database"
}
resource "aws_db_instance" "main" {
identifier = "myapp-${var.environment}"
engine = "postgres"
engine_version = "15.4"
instance_class = "db.t3.medium"
allocated_storage = 50
max_allocated_storage = 200
db_name = "myapp"
username = jsondecode(ephemeral.infisical_secret.db_creds.value)["username"]
password = jsondecode(ephemeral.infisical_secret.db_creds.value)["password"]
vpc_security_group_ids = [aws_security_group.db.id]
db_subnet_group_name = aws_db_subnet_group.db.name
storage_encrypted = true
skip_final_snapshot = false
tags = {
Environment = var.environment
ManagedBy = "opentofu"
}
}
注意,配置文件、变量中,以及(由于 ephemeral resource 的原因)state 文件里都不会出现任何凭据。这些凭据只会在 tofu apply 执行期间存在于内存中。审查这段代码 pull request 的工程师看不到任何 secret,拥有 state bucket 访问权限的人也看不到。
下面是一个使用 OIDC authentication 运行上述配置的 GitHub Actions workflow。访问 Infisical 不需要存储任何 secret:
name: 部署数据库基础设施
on:
push:
branches: [main]
paths: ["infra/database/**"]
permissions:
id-token: write # OIDC 所需
contents: read
jobs:
deploy:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Setup OpenTofu
uses: opentofu/setup-opentofu@v1
with:
tofu_version: "1.11.0"
- name: Configure AWS Credentials
uses: aws-actions/configure-aws-credentials@v4
with:
role-to-assume: arn:aws:iam::123456789:role/opentofu-deploy
aws-region: us-east-1
- name: OpenTofu Init
run: tofu init
working-directory: infra/database
- name: OpenTofu Plan
run: tofu plan -out=tfplan
working-directory: infra/database
env:
TF_VAR_infisical_workspace_id: ${{ vars.INFISICAL_WORKSPACE_ID }}
TF_VAR_infisical_identity_id: ${{ vars.INFISICAL_IDENTITY_ID }}
TF_VAR_environment: prod
- name: OpenTofu Apply
run: tofu apply tfplan
working-directory: infra/database
这里的关键细节是 permissions 块:id-token: write 使 GitHub Actions runner 能够签发 OIDC token,Infisical provider 会用它来交换访问权限。INFISICAL_WORKSPACE_ID 和 INFISICAL_IDENTITY_ID 是非敏感标识符,因此作为 GitHub Actions variables(而不是 secrets)存储,因为它们本身并不授予访问权限。
许多 OpenTofu 用户之前使用 Terraform 和 HashiCorp Vault。如果你正在评估是继续使用 Vault,还是采用 Infisical,下面是实际差异的具体表现。
Vault 的 Terraform provider 可以工作,但其周边基础设施相当庞大。你需要一个正在运行的 Vault 集群(通常使用 Raft consensus 部署,这至少需要三个节点才能实现高可用)。
团队中需要有人理解 Vault 的 seal/unseal 流程、token 生命周期和 policy language。Terraform provider 配置看起来很简单,但其背后是一个大多数组织会配备一到三名专职工程师来维护的系统。
除了运维,Vault 还是一组构建模块。它提供 key value 存储和 API。像 secret rotation、approval gates、environment 分离和 audit dashboards 这样的工作流,往往都需要你自己构建,经常还要结合自定义脚本、GitOps 流程和基于 YAML 的变更管理。
阅读更多:HashiCorp Vault Alternatives
Infisical 的 provider 集成在功能上是类似的(声明 provider、获取 secret、在 resource 中使用它们),但其背后的支持基础设施不同。它运行在 Postgres 上,而这也是大多数团队已经知道如何运维和扩展的系统。没有 Raft cluster 需要管理,没有 seal/unseal 仪式,也没有专有 consensus protocol。
更重要的是,Vault 留给你自己处理的那些工作流(基于 environment 的 secret 组织、approval workflow、access request flow 和 audit logging)都是内置的。这就是在一个工具包上构建,和直接使用一个产品之间的区别。
正在从 Vault 迁移?如果你现在正在运行 Vault,也不必一次性全部切换。Infisical 可以在迁移过程中与 Vault 并行运行,你可以按项目逐步迁移。该 provider 是自包含的,因此在过渡期间,你可以在同一个 OpenTofu 配置中同时使用两者。
阅读更多:Infisical vs HashiCorp Vault
上面展示的集成覆盖了最常见的用例:在 OpenTofu 运行中注入 secret。但 secrets management 只是更广泛运维挑战中的一部分。
对于大规模管理基础设施的团队,相关问题还包括 certificate 生命周期管理(签发、续期和发现 TLS certificates;随着证书有效期到 2029 年缩短为 47 天,这一需求变得越来越紧迫)、用于 SSH sessions 的 privileged access management,以及跨仓库的 secrets scanning,用来在泄漏的凭据演变成事故之前将其捕获。
Infisical 将这些都作为单一平台的一部分来处理。是否需要这种广度,取决于你当前的技术栈。如果你已经为 secrets、certificates 和 privileged access 运行着独立工具,整合可以减少 vendor 数量,并消除它们之间的自定义编排。如果你今天只是在解决 secret 问题,知道这个平台可以随着你的需求扩展也很有帮助。
深入了解:Infisical integration guide for Terraform 覆盖了更多 provider 选项和配置细节。由于 OpenTofu 是一个 fork,这些文档可以直接适用。
这些建议专门针对将 Infisical 与 OpenTofu 一起运行,而不是通用的 secrets management 建议。
required_providers 中固定到特定版本,并有意识地升级,先在 staging workspace 中测试。tofu plan 期间无法连接到服务器,plan 将失败。在 CI/CD 中,确保你的 pipeline 将其清晰地呈现为错误,而不是带着空值继续执行。可以考虑在运行 tofu init 之前添加健康检查步骤。/project-name/environment/component 这样的模式,可以在不过度共享访问权限的情况下保持 secret 易于发现。client_secret 视为其他任何凭据:按计划轮换,使用 Infisical 内置的轮换策略,并确保你的 CI/CD pipeline 会自动获取新值。tofu apply 的 CI/CD pipeline 只需要对 secret 的读取权限。将写入权限保留给 secrets management workflow 使用的另一个 identity。这可以限制受损 pipeline 能造成的影响。Infisical 与 OpenTofu 之间的集成,就是一个 provider 声明、一种认证方法和一次 secret 获取。本指南中的代码示例是可用于生产环境的模式,你可以将其适配到自己的基础设施中。
如果你目前通过 .tfvars 文件、环境变量,或者一个已经超出团队维护能力的 Vault 方案来管理 secret,那么这是一条实用的前进路径。从一个项目开始,在 CI/CD 中验证 workflow,然后再逐步扩展。
Infisical 文档 涵盖了完整的 provider API,包括这里未展示的选项。如需迁移方面的问题或帮助,Infisical community 活跃且响应迅速。
- 原文链接: infisical.com/blog/opent...
- 登链社区 AI 助手,为大家转译优秀英文文章,如有翻译不通的地方,还请包涵~
如果觉得我的文章对您有用,请随意打赏。你的支持将鼓励我继续创作!