- OpenTofu 语言
- 状态
- 状态和计划加密
状态和计划加密
OpenTofu 支持对静态状态和计划文件进行加密,无论是在本地存储还是使用后端时。此外,您还可以将加密与 terraform_remote_state
数据源一起使用。此页面说明如何设置加密以及哪种加密方法适合哪种用例。
一般指南和陷阱(请阅读)
启用加密后,您的状态和计划文件在没有相应的加密密钥的情况下将无法恢复。请务必在启用加密之前仔细阅读本节。
加密可以防止什么?
启用加密后,OpenTofu 将静态加密状态数据。如果攻击者获取了您的状态文件,他们应该无法读取它并使用状态文件中包含的敏感值(例如访问密钥)。
但是,加密不能防止数据丢失(您的状态文件损坏),也不能防止重放攻击(攻击者使用旧的状态或计划文件并诱骗您运行它)。此外,OpenTofu 不会也不会阻止状态文件中敏感的值被运行 tofu
命令的人员访问。
我需要采取哪些预防措施?
启用加密后,请考虑谁需要直接访问您的状态文件。如果您有多于极少数的人员需要访问,您可能需要考虑从持续集成系统运行生产 plan
和 apply
运行,以保护加密密钥和状态中的敏感值。
您还需要根据您的安全要求决定要使用哪种密钥。您可以选择静态密码短语,也可以选择密钥管理系统。如果您选择密钥管理系统,则必须为某些加密方法配置自动密钥轮换。这对于您选择的加密算法有可能达到“密钥饱和”点(即接近密钥的最大安全使用限制)的情况尤其重要,例如 AES-GCM。您可以在下面的 加密方法 部分中找到有关此方面的更多信息。
最后,在启用加密之前,请演练您的灾难恢复计划并创建未加密状态文件的临时备份。此外,请确保您已备份密钥。启用加密后,OpenTofu 在没有正确密钥的情况下无法读取您的状态文件。
从未加密的状态/计划迁移
如果您有一个预先存在的状态文件并希望启用加密,则仅仅启用加密是不够的,因为 OpenTofu 将拒绝读取纯文本数据。这是一种保护机制,可以防止 OpenTofu 读取被操纵的、未加密的数据。有关详细的迁移说明,请参阅下面的 初始设置 部分。
兼容性保证
密码学研究可以迅速改变现有技术。我们将支持所有已记录的密钥提供程序和方法 +1 个次要版本,但可能会在任何次要版本中引入相同密钥提供程序和方法的新版本(例如 aes_gcm_v2
),或新的密钥提供程序和方法。如果我们弃用密钥提供程序或方法,您将在运行 tofu plan
或 tofu apply
时在控制台上收到警告。如果您收到此类警告,请在升级到下一个版本之前切换。
配置
您可以通过在 OpenTofu 代码中指定配置或使用 TF_ENCRYPTION
环境变量来配置 OpenTofu 中的加密。这两种解决方案是等效的,如果您同时使用两者,OpenTofu 将合并这两个配置,并使用环境配置覆盖任何基于代码的设置。
基本配置结构如下所示
- 代码
- 环境(Linux/UNIX shell)
- 环境(Powershell)
terraform {
encryption {
key_provider "some_key_provider" "some_name" {
# Key provider options here
}
method "some_method" "some_method_name" {
# Method options here
keys = key_provider.some_key_provider.some_name
}
state {
# Encryption/decryption for state data
method = method.some_method.some_method_name
}
plan {
# Encryption/decryption for plan data
method = method.some_method.some_method_name
}
remote_state_data_sources {
# See below
}
}
}
TF_ENCRYPTION=$(cat <<EOF
key_provider "some_key_provider" "some_name" {
# Key provider options here
}
method "some_method" "some_method_name" {
# Method options here
keys = key_provider.some_key_provider.some_name
}
state {
# Encryption/decryption for state data
method = method.some_method.some_method_name
}
plan {
# Encryption/decryption for plan data
method = method.some_method.some_method_name
}
remote_state_data_sources {
# See below
}
EOF)
$Env:TF_ENCRYPTION = @"
key_provider "some_key_provider" "some_name" {
# Key provider options here
}
method "some_method" "some_method_name" {
# Method options here
keys = key_provider.some_key_provider.some_name
}
state {
# Encryption/decryption for state data
method = method.some_method.some_method_name
}
plan {
# Encryption/decryption for plan data
method = method.some_method.some_method_name
}
remote_state_data_sources {
# See below
}
"@
数据加密后,请勿重命名配置中的密钥提供程序和方法!后端中存储的加密数据包含与其特定名称相关的元数据。相反,请使用 回退块 处理密钥提供程序的更改。
您可以使用 JSON 配置语法 而不是 HCL 进行加密配置。
如果您使用环境配置,则可以包含以下代码配置以防止在没有环境变量的情况下写入未加密的数据
terraform {
encryption {
state {
enforced = true
}
plan {
enforced = true
}
}
}
密钥和方法切换
在某些情况下,您可能希望更改加密配置。这可能包括重命名密钥提供程序或方法、更改密钥提供程序的密码或切换密钥管理系统。如果您在fallback
块中提供旧配置,OpenTofu 支持自动轮换您的加密配置。
terraform {
encryption {
# Methods and key providers here.
state {
method = method.some_method.new_method
fallback {
method = method.some_method.old_method
}
}
plan {
method = method.some_method.new_method
fallback {
method = method.some_method.old_method
}
}
}
}
如果 OpenTofu 无法使用新方法**读取**您的状态或计划文件,它将自动尝试回退方法。当 OpenTofu **保存**您的状态或计划文件时,它将始终使用新方法,而不是回退方法。
初始设置
新项目
如果您正在设置一个新项目并且还没有状态文件,此示例配置将帮助您开始使用基于密码的加密。
variable "passphrase" {
# Change passphrase to be at least 16 characters long:
default = "changeme!"
}
terraform {
encryption {
## Step 1: Add the desired key provider:
key_provider "pbkdf2" "mykey" {
passphrase = var.passphrase
}
## Step 2: Set up your encryption method:
method "aes_gcm" "new_method" {
keys = key_provider.pbkdf2.mykey
}
state {
## Step 3: Link the desired encryption method:
method = method.aes_gcm.new_method
## Step 4: Run "tofu apply".
## Step 5: Consider adding the "enforced" option:
# enforced = true
}
## Step 6: Repeat steps 3-5 for plan{} if needed.
}
}
现有项目
当您首次在现有项目上配置加密时,您的状态和计划文件未加密。默认情况下,OpenTofu 拒绝读取它们,因为它们可能已被篡改。要启用读取未加密的数据,您必须指定unencrypted
方法。
variable "passphrase" {
# Change passphrase to be at least 16 characters long:
default = "changeme!"
}
terraform {
encryption {
## Step 1: Add the unencrypted method:
method "unencrypted" "migrate" {}
## Step 2: Add the desired key provider:
key_provider "pbkdf2" "mykey" {
passphrase = var.passphrase
}
## Step 3: Add the desired encryption method:
method "aes_gcm" "new_method" {
keys = key_provider.pbkdf2.mykey
}
state {
## Step 4: Link the desired encryption method:
method = method.aes_gcm.new_method
## Step 5: Add the "fallback" block referencing the
## "unencrypted" method.
fallback {
method = method.unencrypted.migrate
}
## Step 6: Run "tofu apply".
## Step 7: Remove the "fallback" block above and
## consider adding the "enforced" option:
# enforced = true
}
## Step 8: Repeat steps 4-8 for plan{} if needed.
}
}
变量和局部变量可以在配置中使用,但不能包含对状态或提供程序定义函数中任何数据的引用。所有值都必须能够在状态可用之前,在tofu init
期间解析。
回滚加密
与上述初始设置类似,也可以通过使用unencrypted
方法迁移到未加密的状态和计划文件,如下所示。
terraform {
encryption {
## Step 1: Leave the original encryption method unchanged:
method "some_method" "old_method" {
## Parameters for the old method here.
}
# Step 2: Add the unencrypted method here:
method "unencrypted" "migrate" {}
state {
## Step 3: Disable or remove the "enforced" option:
enforced = false
## Step 4: Move the original encryption method into the "fallback" block:
fallback {
method = method.some_method.old_method
}
## Step 5: Reference the unencrypted method as your primary "encryption" method.
method = method.unencrypted.migrate
}
## Step 6: Run "tofu apply".
## Step 7: Remove the "state" block once the migration is complete.
## Step 8: Repeat steps 3-7 for plan{} if needed.
}
}
在完成迁移之前,请勿删除或修改原始加密方法。
远程状态数据源
您还可以为使用terraform_remote_state
数据源的项目配置加密设置。这可以与您的主要配置相同,但您也可以定义一组单独的密钥和方法。配置语法如下所示。
terraform {
encryption {
# Key provider and method configuration here
remote_state_data_sources {
default {
method = method.my_method.my_name
}
remote_state_data_source "my_state" {
method = method.my_method.my_other_name
}
}
}
}
data "terraform_remote_state" "my_state" {
# ...
}
对于特定的远程状态,您可以使用以下语法。
myname
针对具有给定名称的主项目中的数据源。mymodule.myname
针对指定模块中具有给定名称的数据源。mymodule.myname[0]
针对指定模块中具有给定名称的第一个数据源。
密钥提供程序
PBKDF2
PBKDF2 密钥提供程序允许您使用长密码来生成用于加密方法(例如 AES-GCM)的密钥。您可以按如下方式配置它。
terraform {
encryption {
key_provider "pbkdf2" "foo" {
# Specify a long / complex passphrase (min. 16 characters)
passphrase = "correct-horse-battery-staple"
# Adjust the key length to the encryption method (default: 32)
key_length = 32
# Specify the number of iterations (min. 200.000, default: 600.000)
iterations = 600000
# Specify the salt length in bytes (default: 32)
salt_length = 32
# Specify the hash function (sha256 or sha512, default: sha512)
hash_function = "sha512"
}
}
}
选项 | 描述 | 最小值。 | 默认值 |
---|---|---|---|
passphrase (必需) | 输入一个长而复杂的密码。 | 16 个字符。 | - |
key_length | 要生成为密钥的字节数。 | 1 | 32 |
iterations | 迭代次数。有关建议,请参阅此文档。 | 200.000 | 600.000 |
salt_length | 密钥派生的盐的长度。 | 1 | 32 |
hash_function | 指定sha256 或sha512 作为哈希函数。不支持sha1 。 | N/A | sha512 |
AWS KMS
此密钥提供程序使用Amazon Web Servers 密钥管理服务生成密钥。身份验证选项与S3 后端相同,不包括任何已弃用的选项。此外,请提供以下选项。
选项 | 描述 | 最小值。 | 默认值 |
---|---|---|---|
kms_key_id | AWS KMS 的密钥 ID。. | 1 | - |
key_spec | AWS KMS 的密钥规范。将其调整到您的加密方法(例如AES_256 )。 | 1 | - |
以下示例说明了最小配置。
terraform {
encryption {
key_provider "aws_kms" "basic" {
kms_key_id = "a4f791e1-0d46-4c8e-b489-917e0bec05ef"
region = "us-east-1"
key_spec = "AES_256"
}
}
}
GCP KMS
此密钥提供程序使用Google Cloud 密钥管理服务生成密钥。身份验证选项与GCS 后端相同,不包括任何已弃用的选项。此外,请提供以下选项。
选项 | 描述 | 最小值。 | 默认值 |
---|---|---|---|
kms_encryption_key (必需) | GCP KMS 的密钥 ID。. | N/A | - |
key_length (必需) | 要生成为密钥的字节数。必须在1 到1024 字节的范围内。 | 1 | - |
以下示例说明了最小配置。
terraform {
encryption {
key_provider "gcp_kms" "basic" {
kms_encryption_key = "projects/local-vehicle-id/locations/global/keyRings/ringid/cryptoKeys/keyid"
key_length = 32
}
}
}
OpenBao(实验性)
此密钥提供程序使用OpenBao Transit Secret Engine生成数据密钥。您可以按如下方式配置它。
选项 | 描述 | 最小值。 | 默认值 |
---|---|---|---|
key_name (必需) | 用于加密/解密数据密钥的传输加密密钥的名称。在您的 OpenBao 服务器中预先配置它。 | N/A | - |
token | 访问 OpenBao API 时使用的授权令牌。OpenTofu 也可以从BAO_TOKEN 环境变量中读取它。 | N/A | - |
address | 访问 API 的 OpenBao 服务器地址。OpenTofu 也可以从BAO_ADDR 环境变量中读取它。您的系统必须信任服务器的 TLS 证书。 | N/A | https://127.0.0.1:8200 |
transit_engine_path | 在 OpenBao 中启用 Transit Secret Engine 的路径。如果您更改了传输引擎路径,请自定义此路径。 | N/A | /transit |
key_length | 要生成为密钥的字节数。可用选项为16 、32 或64 字节。 | 16 | 32 |
以下示例说明了可能的配置。
terraform {
encryption {
key_provider "openbao" "my_bao" {
# Required. Name of the transit encryption key
# to use to encrypt/decrypt the data key.
key_name = "test-key"
# Optional. Authorization Token to use when accessing OpenBao API.
# You can also set this in the BAO_TOKEN environment variable.
token = "s.Fg8wA4nDrP08TirpjEXkrTmt"
# Optional. OpenBao server address to access the API on.
# You can also set this using the BAO_ADDR environment variable.
address = "http://127.0.0.1:8200"
# Optional. You can customize this if you mounted the
# transit engine on a different path. Default: /transit
transit_engine_path = "/my-org/transit"
# Optional. Number of bytes to generate as a key. Default: 32
key_length = 16
}
}
}
OpenBao 密钥提供程序目前处于实验阶段,因为在 OpenTofu 发布时没有可用的稳定版 OpenBao。
OpenBao 密钥提供程序与 HashiCorp Vault 的最后一个 MPL 许可版本 (1.14) 兼容,但不支持随后的 BUSL 许可版本。
方法
AES-GCM
目前唯一支持的加密方法是 AES-GCM。您可以通过以下方式配置它。
terraform {
encryption {
# Key provider configuration here
method "aes_gcm" "yourname" {
keys = key_provider.yourkeyprovider.yourname
}
}
}
AES-GCM 方法需要 16、24 或 32 字节的密钥。请配置您的密钥提供程序以提供完全此长度的密钥。
AES-GCM 是一种安全且符合行业标准的加密算法,但会受到“密钥饱和”的影响。为了配置安全的设置,您应该要么使用具有长而复杂密码的密钥派生密钥提供程序(例如 PBKDF2),要么使用定期自动轮换密钥的密钥管理系统。使用短而静态的密钥将降低您的加密安全性。
未加密
unencrypted
方法用于提供到加密和从加密的显式迁移路径。它不需要任何配置,并且可以在上面的初始设置块中看到其使用。