Terraform读取SOPS文件时替换foreach键的.为/适配Vault路径
解决Terraform读取SOPS加密YAML创建Vault KV v2资源的层级适配问题
我读取一个YAML格式的SOPS加密文件,Terraform会自动将YAML的层级结构转换为以.连接的扁平键值对,现在需要基于这些数据创建Vault KV v2资源,但遇到两个问题:
name字段使用.分隔,不符合Vault用/划分子目录的规范;data_json中的键名也是.连接的格式,而非原始层级的键名。
现有代码
data "sops_file" "account_secrets" { source_file = "${path.module}/secrets/secrets.yaml" input_type = "yaml" } resource "vault_kv_secret_v2" "secrets" { for_each = nonsensitive(data.sops_file.account_secrets.data) mount = "test" name = each.key cas = 1 data_json = jsonencode( { (each.key) = (each.value), } ) custom_metadata { max_versions = 5 #var.max_versions != "" ? 5 : var.max_versions } }
SOPS加密YAML示例
AAAAA: AAAAAAAAA: SECRET BBBBBBBBB: SECRET CCCCCCCCC: SECRET DDDDDDDDD: SECRET BBBBB: EEEEEEEEE: FFFFFFFFF: SECRET CCCC: DDDDD: EEEEEE: FFFFFF: DDDDDD: SECRET IIIIIII: SECRET IIIIIIII: RRRRRRRR: SECRET UUUUUUU: OOOOOOOO: SECRET RRRRRRRR: SECRET
当前问题
执行计划生成的资源存在两处不符合预期:
name字段为.连接的字符串,如ccccc.ddddddd.eeeeeee.ffffffff.dddddddd.iiiiii,不符合Vault的目录路径规范;data_json中的键名与name一致,为.连接格式,而非原始层级中的最终节点键名。
我曾尝试用replace函数修改键名,但写法错误触发类型错误:
data_json = jsonencode( { replace("/", each.key) = (each.value), } )
解决方案
根据不同的存储需求,提供两种方案:
方案1:每个叶子节点单独创建Vault Secret
将每个扁平键值对转换为独立的Vault Secret,路径用/分隔,data中仅存储叶子节点的键值对:
resource "vault_kv_secret_v2" "secrets" { for_each = nonsensitive(data.sops_file.account_secrets.data) mount = "test" # 将键中的`.`替换为`/`,符合Vault路径规范 name = replace(each.key, ".", "/") cas = 1 data_json = jsonencode({ # 提取键的最后一部分作为data中的键名 (element(reverse(split(".", each.key)), 0)) = each.value }) custom_metadata { max_versions = 5 #var.max_versions != "" ? 5 : var.max_versions } }
执行后,示例中的AAAAA.AAAAAAAAA会生成:
name为AAAAA/AAAAAAAAAdata_json为{"AAAAAAAAA": "SECRET"}
方案2:还原原始嵌套结构,按顶层节点创建Secret
将扁平的键值对还原为原始YAML的嵌套结构,每个顶层节点对应一个Vault Secret,存储整个子层级的数据:
locals { flat_secrets = nonsensitive(data.sops_file.account_secrets.data) # 递归函数:将扁平键值对还原为嵌套map build_nested_map = func(flat_map) { result = {} # 处理单层键 for key, value in flat_map : key => value if length(split(".", key)) == 1 # 处理多层键,递归合并到嵌套结构中 for key, value in flat_map : let parts = split(".", key) let first_part = parts[0] let remaining_key = join(".", slice(parts, 1, length(parts))) : first_part => merge( try(result[first_part], {}), local.build_nested_map({ remaining_key = value }) ) if length(split(".", key)) > 1 return result } # 生成嵌套结构的secret数据 nested_secrets = local.build_nested_map(local.flat_secrets) } resource "vault_kv_secret_v2" "secrets" { for_each = local.nested_secrets mount = "test" name = each.key cas = 1 data_json = jsonencode(each.value) custom_metadata { max_versions = 5 #var.max_versions != "" ? 5 : var.max_versions } }
执行后,示例中的AAAAA顶层节点会生成:
name为AAAAAdata_json为{"AAAAAAAAA": "SECRET", "BBBBBBBBB": "SECRET", "CCCCCCCCC": "SECRET", "DDDDDDDDD": "SECRET"}
关键说明
- 方案1适合需要将每个敏感值独立存储、精细化权限控制的场景;
- 方案2更贴合原始YAML的层级结构,适合按业务模块批量存储的场景;
replace函数的正确用法是replace(原字符串, 要替换的子串, 替换后的子串),之前的错误写法颠倒了参数顺序。
内容的提问来源于stack exchange,提问作者PsySkeletor
相关产品推荐
相关产品推荐

