Terraformで機密データを保護する方法

提供:Dev Guides
移動先:案内検索

著者は、 Write for DOnations プログラムの一環として、 Free and Open SourceFundを選択して寄付を受け取りました。

序章

Terraformは、クラウドでインフラストラクチャをプロビジョニングするための自動化を提供します。 これを行うために、Terraformはクラウドプロバイダー(および他のプロバイダー)で認証を行い、リソースをデプロイして計画されたアクションを実行します。 ただし、Terraformが認証に必要とする情報は非常に貴重であり、一般に機密情報であり、サービスへのアクセスをロック解除するため、常に秘密にしておく必要があります。 たとえば、データベースユーザーのAPIキーまたはパスワードを機密データと見なすことができます。

悪意のある第三者が機密情報を取得した場合、既知の信頼できるユーザーとして自分自身を提示することにより、セキュリティシステムを侵害する可能性があります。 次に、取得したキーの範囲内で利用可能なリソースとサービスを変更、削除、および置換できるようになります。 これを防ぐには、プロジェクトを適切に保護し、すべてのプロジェクトシークレットを格納する状態ファイルを保護することが不可欠です。

デフォルトでは、Terraformは状態ファイルを暗号化されていないJSONの形式でローカルに保存し、プロジェクトファイルにアクセスできるすべての人がシークレットを読み取れるようにします。 これに対する解決策はディスク上のファイルへのアクセスを制限することですが、別のオプションは、 DigitalOceanSpacesなどのデータを自動的に暗号化するバックエンドに状態をリモートで保存することです。

このチュートリアルでは、実行中に機密データを出力に隠し、保存されているデータを暗号化する安全なクラウドオブジェクトストレージに状態を保存します。 このチュートリアルでは、クラウドオブジェクトストレージとしてDigitalOceanSpacesを使用します。 また、変数を機密としてマークする方法を学び、 tfmask を調べます。これは、Terraform実行ログ出力の値を動的に打ち切るGoで記述されたオープンソースプログラムです。

前提条件

  • DigitalOceanパーソナルアクセストークン。DigitalOceanコントロールパネルから作成できます。 手順については、DigitalOcean製品ドキュメントパーソナルアクセストークンの作成方法を参照してください。
  • ローカルマシンにインストールされたTerraformと、DigitalOceanプロバイダーでセットアップされたプロジェクト。 DigitalOcean チュートリアルでTerraformを使用する方法のステップ1およびステップ2を完了し、代わりにプロジェクトフォルダーにterraform-sensitiveという名前を付けてください。 loadbalanceの。 ステップ2の間、pvt_key変数とSSHキーリソースを含めないでください。
  • APIキー(アクセスとシークレット)を備えたDigitalOceanスペース。 DigitalOceanスペースとAPIキーを作成する方法については、チュートリアルDigitalOceanスペースとAPIキーを作成する方法を参照してください。

注:このチュートリアルは、Terraform1.0.2で特別にテストされています。


出力をsensitiveとしてマークする

このステップでは、sensitiveパラメーターをtrueに設定して、コード内の出力を非表示にします。 これは、シークレット値が無期限に保存しているTerraform出力の一部である場合、または分析のためにチームを超えて出力ログを共有する必要がある場合に役立ちます。

前提条件の一部として作成したterraform-sensitiveディレクトリにいると仮定して、ドロップレットとそのIPアドレスを示す出力を定義します。 droplets.tfという名前のファイルに保存するので、次のコマンドを実行して作成し、編集用に開きます。

nano droplets.tf

次の行を追加します。

terraform-sensitive / droplets.tf

resource "digitalocean_droplet" "web" {
  image  = "ubuntu-20-04-x64"
  name   = "web-1"
  region = "fra1"
  size   = "s-1vcpu-1gb"
}

output "droplet_ip_address" {
  value = digitalocean_droplet.web.ipv4_address
}

このコードは、web-1というドロップレットをfra1リージョンにデプロイし、1GBのRAMと1つのCPUコアでUbuntu20.04を実行します。 ここでは、droplet_ip_address出力に値を指定しました。これは、Terraformログで受信されます。

このドロップレットをデプロイするには、次のコマンドを実行してコードを実行します。

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

Terraformが実行するアクションは次のとおりです。

OutputTerraform used the selected providers to generate the following execution plan. Resource actions are indicated with the
following symbols:
  + create

Terraform will perform the following actions:

  # digitalocean_droplet.web will be created
  + resource "digitalocean_droplet" "web" {
      + backups              = false
      + created_at           = (known after apply)
      + disk                 = (known after apply)
      + id                   = (known after apply)
      + image                = "ubuntu-20-04-x64"
      + ipv4_address         = (known after apply)
      + ipv4_address_private = (known after apply)
      + ipv6                 = false
      + ipv6_address         = (known after apply)
      + ipv6_address_private = (known after apply)
      + locked               = (known after apply)
      + memory               = (known after apply)
      + monitoring           = false
      + name                 = "web-1"
      + price_hourly         = (known after apply)
      + price_monthly        = (known after apply)
      + private_networking   = (known after apply)
      + region               = "fra1"
      + resize_disk          = true
      + size                 = "s-1vcpu-1gb"
      + status               = (known after apply)
      + urn                  = (known after apply)
      + vcpus                = (known after apply)
      + volume_ids           = (known after apply)
      + vpc_uuid             = (known after apply)
    }

Plan: 1 to add, 0 to change, 0 to destroy.
...

プロンプトが表示されたら、yesと入力します。 出力は次のようになります。

Outputdigitalocean_droplet.web: Creating...
...
digitalocean_droplet.web: Creation complete after 40s [id=216255733]

Apply complete! Resources: 1 added, 0 changed, 0 destroyed.

Outputs:

droplet_ip_address = your_droplet_ip_address

IPアドレスが出力に含まれていることがわかります。 この出力を他のユーザーと共有している場合、または自動展開プロセスのために公開される場合は、出力でこのデータを非表示にするアクションを実行することが重要です。

打ち切りするには、droplet_ip_address出力のsensitive属性をtrueに設定する必要があります。

droplets.tfを開いて編集します。

nano droplets.tf

強調表示された行を追加します。

terraform-sensitive / droplets.tf

resource "digitalocean_droplet" "web" {
  image  = "ubuntu-20-04-x64"
  name   = "web-1"
  region = "fra1"
  size   = "s-1vcpu-1gb"
}

output "droplet_ip_address" {
  value = digitalocean_droplet.web.ipv4_address
  sensitive = true
}

完了したら、ファイルを保存して閉じます。

次のコマンドを実行して、プロジェクトを再度適用します。

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

出力は次のようになります。

Outputdigitalocean_droplet.web: Refreshing state... [id=216255733]
...
Apply complete! Resources: 0 added, 0 changed, 0 destroyed.

Outputs:

droplet_ip_address = <sensitive>

これで、IPアドレス(出力の値)を明示的に打ち切りました。 出力の打ち切りは、Terraformログがパブリックスペースにある場合、またはログを非表示のままにしておきたいがコードから削除しない場合に役立ちます。 また、パスワードとAPIトークンは機密情報でもあるため、それらを含む出力を検閲することもできます。

これで、定義された出力の値をsensitiveとしてマークすることにより、非表示になりました。 これで、変数を機密としてマークする方法がわかります。

変数をsensitiveとしてマークする

出力と同様に、変数も機密としてマークすることができます。 定義されている変数は1つ(do_token)しかないため、provider.tfを開いて編集します。

nano provider.tf

do_token変数を次のように変更します。

terraform-sensitive / Provider.tf

terraform {
  required_providers {
    digitalocean = {
      source = "digitalocean/digitalocean"
      version = "~> 2.0"
    }
  }
}

variable "do_token" {
  sensitive = true
}

provider "digitalocean" {
  token = var.do_token
}

完了したら、ファイルを保存して閉じます。 do_token変数は機密と見なされるようになりました。

機密変数の出力を試みるには、droplets.tfで新しい出力を定義します。

nano droplets.tf

最後に次の行を追加します。

terraform-sensitive / droplets.tf

output "dotoken" {
  value = var.do_token
}

ファイルを保存して閉じます。 次に、以下を実行して構成を適用してみてください。

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

次のようなエラーメッセージが表示されます。

Output╷
│ Error: Output refers to sensitive values
│
│   on droplets.tf line 13:
│   13: output "dotoken" {
│
│ To reduce the risk of accidentally exporting sensitive data that was intended to be only internal, Terraform requires
│ that any root module output containing sensitive data be explicitly marked as sensitive, to confirm your intent.
│
│ If you do intend to export this data, annotate the output value as sensitive by adding the following argument:
│     sensitive = true
╵

このエラーは、情報漏えいを防ぐために、機密性の高い変数を非機密性の出力に表示できないことを意味します。 ただし、次のように、出力値をnonsensitiveとしてラップすることにより、それらを強制的に表示することができます。

terraform-sensitive / droplets.tf

...

output "dotoken" {
  value = nonsensitive(var.do_token)
}

nonsensitiveは、変数の感度設定をリセットして、表示できるようにします。 これは慎重に使用する必要があり、出力が機密変数の不可逆導関数である場合にのみ使用してください。

これで、変数を機密としてマークする方法と、その設定をオーバーライドする方法を見てきました。 次のステップでは、プロジェクトの状態をローカルではなく暗号化されたクラウドに保存するようにTerraformを構成します。

暗号化されたリモートバックエンドに状態を保存する

状態ファイルには、すべての内部関係やシークレットなど、展開されたインフラストラクチャに関するすべての情報が格納されます。 デフォルトでは、ディスク上にローカルにプレーンテキストで保存されます。 クラウドにリモートで保存すると、より高いレベルのセキュリティが提供されます。 クラウドストレージサービスが保存時に暗号化をサポートしている場合、状態ファイルは常に暗号化された状態で保存されるため、潜在的な攻撃者はそこから情報を収集できません。 リモートで暗号化された状態ファイルを保存することは、出力をsensitiveとしてマークすることとは異なります。このように、すべてのシークレットはクラウドに安全に保存されます。これにより、Terraformがデータを保存する方法のみが変更され、表示されるときは変更されません。

次に、状態ファイルをDigitalOceanSpaceに保存するようにプロジェクトを構成します。 その結果、保存時に暗号化され、転送中にTLSで保護されます。

デフォルトでは、Terraform状態ファイルはterraform.tfstateと呼ばれ、初期化されたすべてのディレクトリのルートにあります。 次のコマンドを実行すると、その内容を表示できます。

cat terraform.tfstate

ファイルの内容は次のようになります。

terraform-sensitive / terraform.tfstate

{
  "version": 4,
  "terraform_version": "1.0.2",
  "serial": 3,
  "lineage": "16362bdb-2ff3-8ac7-49cc-260f3261d8eb",
  "outputs": {
    "droplet_ip_address": {
      "value": "...",
      "type": "string",
      "sensitive": true
    }
  },
  "resources": [
    {
      "mode": "managed",
      "type": "digitalocean_droplet",
      "name": "web",
      "provider": "provider[\"registry.terraform.io/digitalocean/digitalocean\"]",
      "instances": [
        {
          "schema_version": 1,
          "attributes": {
            "backups": false,
            "created_at": "2021-07-11T06:16:51Z",
            "disk": 25,
            "id": "254368889",
            "image": "ubuntu-20-04-x64",
            "ipv4_address": "...",
            "ipv4_address_private": "10.135.0.3",
            "ipv6": false,
            "ipv6_address": "",
            "locked": false,
            "memory": 1024,
            "monitoring": false,
            "name": "web-1",
            "price_hourly": 0.00744,
            "price_monthly": 5,
            "private_networking": true,
            "region": "fra1",
            "resize_disk": true,
            "size": "s-1vcpu-1gb",
            "ssh_keys": null,
            "status": "active",
            "tags": [],
            "urn": "do:droplet:254368889",
            "user_data": null,
            "vcpus": 1,
            "volume_ids": [],
            "vpc_uuid": "fc52519c-dc84-11e8-8b13-3cfdfea9f160"
          },
          "sensitive_attributes": [],
          "private": "..."
        }
      ]
    }
  ]
}

状態ファイルには、デプロイしたすべてのリソースと、すべての出力およびそれらの計算値が含まれています。 このファイルへのアクセスを取得することは、展開されたインフラストラクチャ全体を危険にさらすのに十分です。 これを防ぐために、暗号化してクラウドに保存できます。

Terraformは、状態の保存および取得メカニズムである複数のバックエンドをサポートします。 例:ローカルストレージの場合はlocal、Postgresデータベースの場合はpg、Spaceへの接続に使用するS3互換ストレージの場合はs3

バックエンド構成は、現在provider.tfにあるメインのterraformブロックで指定されます。 次のコマンドを実行して、編集用に開きます。

nano provider.tf

次の行を追加します。

terraform-sensitive / Provider.tf

terraform {
  required_providers {
    digitalocean = {
      source = "digitalocean/digitalocean"
      version = "~> 2.0"
    }
  }

  backend "s3" {
    key      = "state/terraform.tfstate"
    bucket   = "your_space_name"
    region   = "us-west-1"
    endpoint = "https://spaces_endpoint"
    skip_region_validation      = true
    skip_credentials_validation = true
    skip_metadata_api_check     = true
  }
}

variable "do_token" {}

provider "digitalocean" {
  token = var.do_token
}

s3バックエンドブロックは、最初にkeyを指定します。これは、スペース上のTerraform状態ファイルの場所です。 state/terraform.tfstateを渡すということは、stateディレクトリの下にterraform.tfstateとして保存することを意味します。

endpointパラメーターは、スペースが配置されている場所をTerraformに通知し、bucketは接続する正確なスペースを定義します。 skip_region_validationおよびskip_credentials_validationは、DigitalOceanSpacesに適用できない検証を無効にします。 regionは、スペースを参照しない適合値(us-west-1など)に設定する必要があることに注意してください。

バケット名と、スペースの設定タブにあるリージョンを含むスペースエンドポイントを忘れずに入力してください。 do_token変数が機密としてマークされなくなったことに注意してください。 endpointのカスタマイズが完了したら、ファイルを保存して閉じます。

次に、スペースのアクセスキーと秘密キーを環境変数に入れて、後で参照できるようにします。 次のコマンドを実行し、強調表示されたプレースホルダーをキー値に置き換えます。

export SPACE_ACCESS_KEY="your_space_access_key"
export SPACE_SECRET_KEY="your_space_secret_key"

次に、以下を実行して、スペースをバックエンドとして使用するようにTerraformを構成します。

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

-backend-config引数は、実行時にバックエンドパラメータを設定する方法を提供します。これは、ここでスペースキーを設定するために使用しています。 既存の状態をクラウドにコピーするか、新たに開始するかを尋ねられます。

OutputInitializing the backend...
Do you want to copy existing state to the new backend?
  Pre-existing state was found while migrating the previous "local" backend to the
  newly configured "s3" backend. No existing state was found in the newly
  configured "s3" backend. Do you want to copy this state to the new "s3"
  backend? Enter "yes" to copy and "no" to start with an empty state.

プロンプトが表示されたら、yesと入力します。 残りの出力は次のようになります。

OutputSuccessfully configured the backend "s3"! Terraform will automatically
use this backend unless the backend configuration changes.

Initializing provider plugins...
- Reusing previous version of digitalocean/digitalocean from the dependency lock file
- Using previously-installed digitalocean/digitalocean v2.10.1

Terraform has been successfully initialized!

You may now begin working with Terraform. Try running "terraform plan" to see
any changes that are required for your infrastructure. All Terraform commands
should now work.

If you ever set or change modules or backend configuration for Terraform,
rerun this command to reinitialize your working directory. If you forget, other
commands will detect it and remind you to do so if necessary.

これで、プロジェクトはその状態をスペースに保存します。 エラーが発生した場合は、正しいキー、エンドポイント、およびバケット名が指定されていることを再確認してください。

プロジェクトは現在、スペースに状態を保存しています。 ローカル状態ファイルが空になりました。これは、その内容を表示することで確認できます。

cat terraform.tfstate

期待どおり、出力はありません。

ドロップレット定義を変更して適用し、状態がまだ正しく管理されていることを確認できます。

droplets.tfを開いて編集します。

nano droplets.tf

強調表示された行を変更します。

terraform-sensitive / droplets.tf

resource "digitalocean_droplet" "web" {
  image  = "ubuntu-20-04-x64"
  name   = "test-droplet"
  region = "fra1"
  size   = "s-1vcpu-1gb"
}

output "droplet_ip_address" {
  value = digitalocean_droplet.web.ipv4_address
  sensitive = false
}

以前のdotoken出力を削除できます。 ファイルを保存して閉じ、次のコマンドを実行してプロジェクトを適用します。

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

出力は次のようになります。

OutputTerraform used the selected providers to generate the following execution plan. Resource actions are indicated with the following symbols:
  ~ update in-place

Terraform will perform the following actions:

  # digitalocean_droplet.web will be updated in-place
  ~ resource "digitalocean_droplet" "web" {
        id                   = "254368889"
      ~ name                 = "web-1" -> "test-droplet"
        tags                 = []
        # (21 unchanged attributes hidden)
    }

Plan: 0 to add, 1 to change, 0 to destroy.
...

プロンプトが表示されたらyesと入力すると、Terraformは新しい構成を既存のドロップレットに適用します。これは、状態が保存されているスペースと正しく通信していることを意味します。

Output...
digitalocean_droplet.web: Modifying... [id=216419273]
digitalocean_droplet.web: Still modifying... [id=216419273, 10s elapsed]
digitalocean_droplet.web: Modifications complete after 12s [id=216419273]

Apply complete! Resources: 0 added, 1 changed, 0 destroyed.

Outputs:

droplet_ip_address = your_droplet_ip_address

クラウドで暗号化された状態をDigitalOceanSpaceに保存するように、プロジェクトのs3バックエンドを構成しました。 次のステップでは、tfmaskを使用します。これは、Terraformログ内のすべての機密出力と情報を動的に検閲するツールです。

CI/CD環境でのtfmaskの使用

このセクションでは、tfmaskをダウンロードし、それを使用して、コマンドの実行時にTerraformが生成する出力ログ全体から機密データを動的に検閲します。 指定したRegEx式と値が一致する変数とパラメーターを打ち切ります。

パラメータ名と変数名がパターンに従っている場合(たとえば、passwordまたはsecretという単語が含まれている場合)、動的に一致するパラメータと変数の名前が可能です。 出力を機密としてマークするよりもtfmaskを使用する利点は、実行中にTerraformが出力するリソース宣言の一致する部分も検閲することです。 自動化されたCI/CD環境のように、実行ログが公開される可能性がある場合は、それらを非表示にする必要があります。自動CI / CD環境では、実行ログが公開されることがよくあります。

tfmaskのコンパイル済みバイナリは、GitHubのリリースページで入手できます。 Linuxの場合、次のコマンドを実行してダウンロードします。

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

次のコマンドを実行して、実行可能としてマークします。

sudo chmod +x /usr/bin/tfmask

tfmaskは、指定した RegEx 式と名前が一致するすべての変数の値をマスクすることにより、terraform planおよびterraform applyの出力で機能します。 環境変数TFMASK_VALUES_REGEXおよびTFMASK_CHARを使用して、正規表現と、実際の値を置き換える文字を指定します。

次に、tfmaskを使用して、Terraformが展開するドロップレットのnameipv4_addressを検閲します。 まず、以下を実行して、前述の環境変数を設定する必要があります。

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

この正規表現は、ipv4_addressまたはnameで始まるすべての文字列(およびそれら自体)と一致し、大文字と小文字は区別されません。

Terraformプランをドロップレットのアクションにするには、その定義を変更します。

nano droplets.tf

ドロップレットの名前を変更します。

terraform-sensitive / droplets.tf

resource "digitalocean_droplet" "web" {
  image  = "ubuntu-20-04-x64"
  name   = "web"
  region = "fra1"
  size   = "s-1vcpu-1gb"
}

output "droplet_ip_address" {
  value = digitalocean_droplet.web.ipv4_address
  sensitive = false
}

ファイルを保存して閉じます。

ドロップレットの属性を変更したため、Terraformはその完全な定義を出力に表示します。 構成を計画しますが、それをtfmaskにパイプして、正規表現に従って変数を打ち切ります。

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

次のような出力が表示されます。

Outputdigitalocean_droplet.web: Refreshing state... [id=216419273]

Terraform used the selected providers to generate the following execution
plan. Resource actions are indicated with the following symbols:
  ~ update in-place

Terraform will perform the following actions:

  # digitalocean_droplet.web will be updated in-place
  ~ resource "digitalocean_droplet" "web" {
        id                   = "254368889"
      ~ name                 = "**********************************"
        tags                 = []
        # (21 unchanged attributes hidden)
    }

Plan: 0 to add, 1 to change, 0 to destroy.
...

tfmaskは、TFMASK_CHAR環境変数で指定した文字を使用して、nameipv4_address、およびipv4_address_privateの値を打ち切りました。正規表現と一致するためです。

Terraformログでの値の打ち切りのこの方法は、ログが公開されている可能性があるCI/CDに非常に役立ちます。 tfmaskの利点は、(正規表現を使用して)検閲する変数を完全に制御できることです。 検閲するキーワードを指定することもできます。現在は存在しない可能性がありますが、将来使用する予定です。

次のコマンドを実行し、プロンプトが表示されたらyesと入力すると、デプロイされたリソースを破棄できます。

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

結論

この記事では、Terraformプロジェクトで機密データを非表示にして保護するためのいくつかの方法を使用しました。 sensitiveを使用して出力と変数から値を非表示にする最初の方法は、ログにのみアクセスできる場合に役立ちますが、値自体はディスクに保存された状態のままでかまいません。

これを改善するために、DigitalOceanSpacesで実現した状態ファイルをリモートで保存することを選択できます。 これにより、保存時に暗号化を利用できます。 また、terraform planおよびterraform applyで、正規表現を使用して照合された変数の値を打ち切るツールであるtfmaskを使用しました。

Hashicorp Vault をチェックして、シークレットとシークレットデータを保存することもできます。 Terraformと統合して、リソース定義にシークレットを挿入できるため、プロジェクトを既存のVaultワークフローに接続できます。 DigitalOceanでPackerとTerraformを使用してHashicorpVaultサーバーを構築する方法に関するチュートリアルを確認することをお勧めします。

このチュートリアルは、Terraformシリーズでインフラストラクチャを管理する方法の一部です。 このシリーズでは、Terraformの初めてのインストールから複雑なプロジェクトの管理まで、Terraformの多くのトピックを取り上げています。