如何在 Terraform 中保护敏感数据

作者选择了 自由和开源基金作为 写给捐款计划的一部分接受捐款。

介绍

Terraform 提供自动化以在云中提供您的基础设施。为了做到这一点,Terraform 与云提供商(以及其他提供商)进行身份验证,以部署资源并执行计划中的操作。然而,Terraform 需要进行身份验证的信息非常有价值,一般来说,是敏感信息,您应该始终保密,因为它可以解锁访问您的服务。

如果恶意的第三方获取敏感信息,他们将能够通过将自己列为已知的可信用户来侵犯安全系统,反过来,他们将能够修改,删除和替换在获得的密钥范围内可用的资源和服务。

默认情况下,Terraform 会以未加密的 JSON 形式本地存储状态文件,允许任何有访问项目文件的人读取秘密,而解决方案是限制存取磁盘上的文件,另一种选择是将状态远程存储到自动加密数据的后端,例如 DigitalOcean Spaces

在本教程中,您将隐藏在执行过程中输出中的敏感数据,并将状态存储在安全的云对象存储中,该存储在休息时会加密数据. 在本教程中,您将使用DigitalOcean Spaces作为云对象存储。

前提条件

通过DigitalOcean控制面板创建的DigitalOcean个人访问代币. 您可以在DigitalOcean产品文档中找到指示, How to Create a Personal Access Token

  • Terraform 安装在您的本地机器上,并与DigitalOcean提供商一起创建一个项目。 完成 How To Use Terraform with DigitalOcean教程的 Step 1Step 2,并确保您将项目文件夹命名为 terraform-sensitive,而不是 load balance。 在 Step 2中,不要包括 pvt_key变量和 SSH 密钥资源。 MK1BR DigitalOcean Space 与 API 密钥(访问和秘密

<$>[注] 注: 本教程已专门用Terraform 1.0.2进行测试。

标记输出为敏感

在此步骤中,您将将输出隐藏在代码中,将其敏感参数设置为。当秘密值是您无限期存储的Terraform输出的一部分时,或如果需要在您的团队之外共享输出日志以进行分析时,这是有用的。

假设您位于您作为前提的一部分创建的terraform-sensitive目录中,您将定义一个 Droplet 和一个显示其 IP 地址的输出。

1nano droplets.tf

添加以下几行:

 1[label terraform-sensitive/droplets.tf]
 2resource "digitalocean_droplet" "web" {
 3  image  = "ubuntu-20-04-x64"
 4  name   = "web-1"
 5  region = "fra1"
 6  size   = "s-1vcpu-1gb"
 7}
 8
 9output "droplet_ip_address" {
10  value = digitalocean_droplet.web.ipv4_address
11}

此代码将部署一个名为web-1的Droplet在fra1区域,运行Ubuntu 20.04在1GB RAM和一个CPU核心上。

要部署此 Droplet,请通过运行以下命令执行代码:

1terraform apply -var "do_token=${DO_PAT}"

Terraform将采取的行动将如下:

 1[secondary_label Output]
 2Terraform used the selected providers to generate the following execution plan. Resource actions are indicated with the
 3following symbols:
 4  + create
 5
 6Terraform will perform the following actions:
 7
 8  # digitalocean_droplet.web will be created
 9  + resource "digitalocean_droplet" "web" {
10      + backups              = false
11      + created_at           = (known after apply)
12      + disk                 = (known after apply)
13      + id                   = (known after apply)
14      + image                = "ubuntu-20-04-x64"
15      + ipv4_address         = (known after apply)
16      + ipv4_address_private = (known after apply)
17      + ipv6                 = false
18      + ipv6_address         = (known after apply)
19      + ipv6_address_private = (known after apply)
20      + locked               = (known after apply)
21      + memory               = (known after apply)
22      + monitoring           = false
23      + name                 = "web-1"
24      + price_hourly         = (known after apply)
25      + price_monthly        = (known after apply)
26      + private_networking   = (known after apply)
27      + region               = "fra1"
28      + resize_disk          = true
29      + size                 = "s-1vcpu-1gb"
30      + status               = (known after apply)
31      + urn                  = (known after apply)
32      + vcpus                = (known after apply)
33      + volume_ids           = (known after apply)
34      + vpc_uuid             = (known after apply)
35    }
36
37Plan: 1 to add, 0 to change, 0 to destroy.
38...

当提示时输入时,输出将看起来像这样:

 1[secondary_label Output]
 2digitalocean_droplet.web: Creating...
 3...
 4digitalocean_droplet.web: Creation complete after 40s [id=216255733]
 5
 6Apply complete! Resources: 1 added, 0 changed, 0 destroyed.
 7
 8Outputs:
 9
10droplet_ip_address = your_droplet_ip_address

如果您正在与其他人共享此输出,或者由于自动部署流程而将其公开可用,则重要的是采取行动将此数据隐藏在输出中。

要审查它,您需要将droplet_ip_address输出的敏感属性设置为true

打开droplets.tf来编辑:

1nano droplets.tf

添加突出的线条:

 1[label terraform-sensitive/droplets.tf]
 2resource "digitalocean_droplet" "web" {
 3  image  = "ubuntu-20-04-x64"
 4  name   = "web-1"
 5  region = "fra1"
 6  size   = "s-1vcpu-1gb"
 7}
 8
 9output "droplet_ip_address" {
10  value = digitalocean_droplet.web.ipv4_address
11  sensitive = true
12}

保存并关闭文件,当你完成。

通过运行重新应用该项目:

1terraform apply -var "do_token=${DO_PAT}"

产量将是:

1[secondary_label Output]
2digitalocean_droplet.web: Refreshing state... [id=216255733]
3...
4Apply complete! Resources: 0 added, 0 changed, 0 destroyed.
5
6Outputs:
7
8droplet_ip_address = <sensitive>

你现在已经明确地审查了IP地址 - 输出值。审查输出在Terraform日志在公共空间时有用,或者当你希望它们保持隐藏时,但不要从代码中删除它们时,你还需要审查含有密码和API代码的输出,因为它们也是敏感信息。

您现在通过将它们标记为敏感来隐藏定义的输出值,您现在将看到如何将变量标记为敏感。

标记变量为敏感

类似于输出,变量也可以被标记为敏感,因为您只有一个定义的变量( do_token),打开 provider.tf 来编辑:

1nano provider.tf

更改do_token变量以看起来像这样:

 1[label terraform-sensitive/provider.tf]
 2terraform {
 3  required_providers {
 4    digitalocean = {
 5      source = "digitalocean/digitalocean"
 6      version = "~> 2.0"
 7    }
 8  }
 9}
10
11variable "do_token" {
12  sensitive = true
13}
14
15provider "digitalocean" {
16  token = var.do_token
17}

完成后,保存并关闭文件. do_token 变量现在被认为是敏感的。

若要尝试输出敏感变量,您将在droplets.tf中定义一个新的输出:

1nano droplets.tf

请在结尾添加以下几行:

1[label terraform-sensitive/droplets.tf]
2output "dotoken" {
3  value = var.do_token
4}

保存并关闭文件. 然后尝试通过运行应用配置:

1terraform apply -var "do_token=${DO_PAT}"

您将收到类似于此的错误消息:

 1[secondary_label Output]
 2
 3 Error: Output refers to sensitive values
 4
 5   on droplets.tf line 13:
 6   13: output "dotoken" {
 7
 8 To reduce the risk of accidentally exporting sensitive data that was intended to be only internal, Terraform requires
 9 that any root module output containing sensitive data be explicitly marked as sensitive, to confirm your intent.
10
11 If you do intend to export this data, annotate the output value as sensitive by adding the following argument:
12     sensitive = true
13

此错误意味着在非敏感输出中无法显示敏感变量,以防止信息泄露,但您可以通过将输出值包装为不敏感来强制显示它们,如下:

1[label terraform-sensitive/droplets.tf]
2...
3
4output "dotoken" {
5  value = nonsensitive(var.do_token)
6}

「非敏感」重置變數的敏感性偏好,允許顯示。

您现在已经看到如何将变量标记为敏感,以及如何忽略此偏好。在下一步,您将配置Terraform以将项目的状态存储在加密云中,而不是本地。

在加密的远程后端中存储状态

状态文件存储了有关您部署的基础设施的所有信息,包括其所有内部关系和秘密。默认情况下,它被存储在单文本中,本地存储在磁盘上。在云中远程存储它提供了更高级别的安全性。如果云存储服务支持在休息时进行加密,则它会随时将状态文件存储在加密状态中,以便潜在的攻击者无法从中收集信息。

现在,您将配置您的项目以将状态文件存储在DigitalOcean空间中,从而在休息时进行加密,并在过境时使用 TLS进行保护。

默认情况下,Terraform状态文件被称为terraform.tfstate,位于每个初始化目录的根部。

1cat terraform.tfstate

文件的内容将类似于此:

 1[label terraform-sensitive/terraform.tfstate]
 2{
 3  "version": 4,
 4  "terraform_version": "1.0.2",
 5  "serial": 3,
 6  "lineage": "16362bdb-2ff3-8ac7-49cc-260f3261d8eb",
 7  "outputs": {
 8    "droplet_ip_address": {
 9      "value": "...",
10      "type": "string",
11      "sensitive": true
12    }
13  },
14  "resources": [
15    {
16      "mode": "managed",
17      "type": "digitalocean_droplet",
18      "name": "web",
19      "provider": "provider[\"registry.terraform.io/digitalocean/digitalocean\"]",
20      "instances": [
21        {
22          "schema_version": 1,
23          "attributes": {
24            "backups": false,
25            "created_at": "2021-07-11T06:16:51Z",
26            "disk": 25,
27            "id": "254368889",
28            "image": "ubuntu-20-04-x64",
29            "ipv4_address": "...",
30            "ipv4_address_private": "10.135.0.3",
31            "ipv6": false,
32            "ipv6_address": "",
33            "locked": false,
34            "memory": 1024,
35            "monitoring": false,
36            "name": "web-1",
37            "price_hourly": 0.00744,
38            "price_monthly": 5,
39            "private_networking": true,
40            "region": "fra1",
41            "resize_disk": true,
42            "size": "s-1vcpu-1gb",
43            "ssh_keys": null,
44            "status": "active",
45            "tags": [],
46            "urn": "do:droplet:254368889",
47            "user_data": null,
48            "vcpus": 1,
49            "volume_ids": [],
50            "vpc_uuid": "fc52519c-dc84-11e8-8b13-3cfdfea9f160"
51          },
52          "sensitive_attributes": [],
53          "private": "..."
54        }
55      ]
56    }
57  ]
58}

状态文件包含您部署的所有资源,以及所有输出和计算值。获取该文件的访问足以破坏整个部署的基础设施。

Terraform 支持多个后端,这些是状态的存储和检索机制. 例如:本地存储的本地,Postgres 数据库的pg,和 S3 兼容的存储的s3,您将使用它来连接到您的空间。

后端配置在主terraform块下指定,目前位于provider.tf

1nano provider.tf

添加以下几行:

 1[label terraform-sensitive/provider.tf]
 2terraform {
 3  required_providers {
 4    digitalocean = {
 5      source = "digitalocean/digitalocean"
 6      version = "~> 2.0"
 7    }
 8  }
 9
10  backend "s3" {
11    key      = "state/terraform.tfstate"
12    bucket   = "your_space_name"
13    region   = "us-west-1"
14    endpoint = "https://spaces_endpoint"
15    skip_region_validation      = true
16    skip_credentials_validation = true
17    skip_metadata_api_check     = true
18  }
19}
20
21variable "do_token" {}
22
23provider "digitalocean" {
24  token = var.do_token
25}

s3后端块首先指定了密钥,这是空间上的Terraform状态文件的位置。通过state/terraform.tfstate意味着您将在state目录下将其存储为terraform.tfstate

终点参数告诉Terraform空间所在位置,而定义了要连接的确切空间。skip_region_validationskip_credentials_validation禁用不适用于DigitalOcean Spaces的验证。

请记住输入您的桶名和空间终端点,包括区域,您可以在空间设置**选项卡中找到。 请注意,do_token变量不再被标记为敏感。

接下来,将您的空间的访问和秘密密钥放入环境变量中,以便您可以稍后引用它们。

1export SPACE_ACCESS_KEY="your_space_access_key"
2export SPACE_SECRET_KEY="your_space_secret_key"

然后,通过运行配置Terraform以使用空间作为其后端:

1terraform init -backend-config "access_key=$SPACE_ACCESS_KEY" -backend-config "secret_key=$SPACE_SECRET_KEY"

backend-config参数提供了在运行时设置后端参数的方法,您在这里使用这些参数来设置空间键,您将被问及是否要将现有状态复制到云中,或重新开始:

1[secondary_label Output]
2Initializing the backend...
3Do you want to copy existing state to the new backend?
4  Pre-existing state was found while migrating the previous "local" backend to the
5  newly configured "s3" backend. No existing state was found in the newly
6  configured "s3" backend. Do you want to copy this state to the new "s3"
7  backend? Enter "yes" to copy and "no" to start with an empty state.

当提示时输入时,其余的输出将看起来如下:

 1[secondary_label Output]
 2Successfully configured the backend "s3"! Terraform will automatically
 3use this backend unless the backend configuration changes.
 4
 5Initializing provider plugins...
 6- Reusing previous version of digitalocean/digitalocean from the dependency lock file
 7- Using previously-installed digitalocean/digitalocean v2.10.1
 8
 9Terraform has been successfully initialized!
10
11You may now begin working with Terraform. Try running "terraform plan" to see
12any changes that are required for your infrastructure. All Terraform commands
13should now work.
14
15If you ever set or change modules or backend configuration for Terraform,
16rerun this command to reinitialize your working directory. If you forget, other
17commands will detect it and remind you to do so if necessary.

您的项目现在将其状态存储在您的空间中. 如果您收到错误,请双重检查您是否已提供正确的密钥、终点和桶名称。

您的项目现在在您的空间中存储状态。本地状态文件已被清空,您可以通过显示其内容来检查:

1cat terraform.tfstate

如预期,不会有产量。

您可以尝试修改 Droplet 定义并应用它来检查状态是否仍在正确管理。

打开droplets.tf来编辑:

1nano droplets.tf

更改突出的线条:

 1[label terraform-sensitive/droplets.tf]
 2resource "digitalocean_droplet" "web" {
 3  image  = "ubuntu-20-04-x64"
 4  name   = "test-droplet"
 5  region = "fra1"
 6  size   = "s-1vcpu-1gb"
 7}
 8
 9output "droplet_ip_address" {
10  value = digitalocean_droplet.web.ipv4_address
11  sensitive = false
12}

您可以删除以前的dotoken输出,保存并关闭文件,然后通过运行应用该项目:

1terraform apply -var "do_token=${DO_PAT}"

结果将类似于以下:

 1[secondary_label Output]
 2Terraform used the selected providers to generate the following execution plan. Resource actions are indicated with the following symbols:
 3  ~ update in-place
 4
 5Terraform will perform the following actions:
 6
 7  # digitalocean_droplet.web will be updated in-place
 8  ~ resource "digitalocean_droplet" "web" {
 9        id                   = "254368889"
10      ~ name                 = "web-1" -> "test-droplet"
11        tags                 = []
12        # (21 unchanged attributes hidden)
13    }
14
15Plan: 0 to add, 1 to change, 0 to destroy.
16...

提示时输入,Terraform将将新配置应用于现有的Droplet,这意味着它正在正确地与其状态存储的空间进行通信:

 1[secondary_label Output]
 2...
 3digitalocean_droplet.web: Modifying... [id=216419273]
 4digitalocean_droplet.web: Still modifying... [id=216419273, 10s elapsed]
 5digitalocean_droplet.web: Modifications complete after 12s [id=216419273]
 6
 7Apply complete! Resources: 0 added, 1 changed, 0 destroyed.
 8
 9Outputs:
10
11droplet_ip_address = your_droplet_ip_address

您已经为您的项目配置了s3后端,以便您在DigitalOcean Space中存储在云中加密的状态。

在 CI/CD 环境中使用tfmask

在本节中,您将下载tfmask,并使用它来动态审查Terraform在执行命令时生成的整个输出日志中的敏感数据,它将审查您提供的 RegEx表达式匹配值的变量和参数。

动态匹配参数和变量名称是可能的,当它们遵循一个模式(例如,含有密码秘密字)。使用tfmask比标记输出为敏感的优点是,它还审查了Terraform在执行时打印的资源声明的匹配部分。

「tfmask」的编译二进制可在其GitHub上的 发布页面下载,对于Linux,请运行以下命令来下载:

1sudo curl -L https://github.com/cloudposse/tfmask/releases/download/0.7.0/tfmask_linux_amd64 -o /usr/bin/tfmask

通过运行将其标记为可执行:

1sudo chmod +x /usr/bin/tfmask

tfmask 通过隐藏所有变量名称与您指定的 RegEx表达式相匹配的值来处理 terraform 计划terraform 应用 的输出。

您现在将使用tfmask来审查 Terraform 将部署的 Droplet 的名称ipv4_地址

1export TFMASK_CHAR="*"
2export TFMASK_VALUES_REGEX="(?i)^.*(ipv4_address|name).*$"

此 regex 表达式将匹配所有从ipv4_addressname开始的字符串(以及它们本身),并且不会具有案例敏感性。

要让 Terraform 计划对您的 Droplet 采取行动,请更改其定义:

1nano droplets.tf

更改了Droplet的名称:

 1[label terraform-sensitive/droplets.tf]
 2resource "digitalocean_droplet" "web" {
 3  image  = "ubuntu-20-04-x64"
 4  name   = "web"
 5  region = "fra1"
 6  size   = "s-1vcpu-1gb"
 7}
 8
 9output "droplet_ip_address" {
10  value = digitalocean_droplet.web.ipv4_address
11  sensitive = false
12}

保存并关闭文件。

由于您已更改了Droplet的属性,所以Terraform在输出中会显示其完整的定义,计划配置,但按regex表达式将其导管到tfmask来审查变量:

1terraform plan -var "do_token=${DO_PAT}" | tfmask

您将收到类似于以下的输出:

 1[secondary_label Output]
 2digitalocean_droplet.web: Refreshing state... [id=216419273]
 3
 4Terraform used the selected providers to generate the following execution
 5plan. Resource actions are indicated with the following symbols:
 6  ~ update in-place
 7
 8Terraform will perform the following actions:
 9
10  # digitalocean_droplet.web will be updated in-place
11  ~ resource "digitalocean_droplet" "web" {
12        id                   = "254368889"
13      ~ name                 = "**********************************"
14        tags                 = []
15        # (21 unchanged attributes hidden)
16    }
17
18Plan: 0 to add, 1 to change, 0 to destroy.
19...

请注意,tfmask 已使用您在TFMASK_CHAR环境变量中指定的字符对名称ipv4_addressipv4_address_private的值进行审查,因为它们匹配了 regex 表达式。

这种在Terraform日志中的价值审查方式对CI/CD非常有用,在那里日志可能公开可用。tfmask的优点是你可以完全控制哪些变量要审查(使用regex表达式)。

您可以通过运行以下命令并在提示时输入来摧毁部署的资源:

1terraform destroy -var "do_token=${DO_PAT}"

结论

在本文中,您已经研究了一些方法来隐藏和保护您的Terraform项目中的敏感数据,第一个方法是使用敏感来隐藏输出和变量中的值,当只有日志可访问时,但这些值本身可以保持存储在磁盘上的状态。

为了解决这一问题,你可以选择远程存储状态文件,这是你用DigitalOcean Spaces实现的。这允许你在休息时使用加密。你还使用了tfmask,一种工具在terraform计划terraform应用期间审查变量值 - 使用regex表达式进行匹配。

您还可以检查Hashicorp Vault以存储秘密和秘密数据。它可以与Terraform集成到(https://learn.hashicorp.com/tutorials/terraform/secrets-vault)在资源定义中,因此您将能够将您的项目连接到现有的Vault工作流程。

本教程是《如何使用Terraform管理基础设施》系列的一部分,该系列涵盖了许多Terraform主题,从首次安装Terraform到管理复杂项目。

Published At
Categories with 技术
comments powered by Disqus