開発者ドキュメント

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

序章

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

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

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

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

前提条件

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

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

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

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

  1. 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ログでこれを受け取ります。

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

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

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

Output
Terraform 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 プロンプトが表示されたら。 出力は次のようになります。

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

検閲するには、を設定する必要があります sensitive の属性 droplet_ip_address に出力 true.

開ける droplets.tf 編集用:

  1. 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
}

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

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

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

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

Output
digitalocean_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 編集用:

  1. 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
}

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

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

  1. nano droplets.tf

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

terraform-sensitive / droplets.tf
output "dotoken" {
  value = var.do_token
}

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

  1. 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 初期化されたすべてのディレクトリのルートにあります。 次のコマンドを実行すると、その内容を表示できます。

  1. 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 ローカルストレージの場合、 pg Postgresデータベースの場合、および s3 Spaceへの接続に使用するS3互換ストレージの場合。

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

  1. 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
}

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

The endpoint パラメータは、スペースが配置されている場所をTerraformに通知します。 bucket 接続する正確なスペースを定義します。 The skip_region_validationskip_credentials_validation DigitalOceanSpacesに適用できない検証を無効にします。 ご了承ください region 適合値(など)に設定する必要があります us-west-1)、スペースへの参照はありません。

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

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

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

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

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

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

Output
Initializing 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 プロンプトが表示されたら。 残りの出力は次のようになります。

Output
Successfully 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.

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

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

  1. cat terraform.tfstate

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

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

開ける droplets.tf 編集用:

  1. 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 以前からの出力。 ファイルを保存して閉じ、次のコマンドを実行してプロジェクトを適用します。

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

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

Output
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 = "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

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

使用する tfmask CI/CD環境で

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

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

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

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

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

  1. sudo chmod +x /usr/bin/tfmask

tfmask の出力に取り組んでいます terraform planterraform apply 指定したRegEx式と名前が一致するすべての変数の値をマスクします。 環境変数を使用します TFMASK_VALUES_REGEXTFMASK_CHAR 正規表現と、実際の値を置き換える文字を指定します。

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

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

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

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

  1. 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 正規表現に従って変数を打ち切ります。

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

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

Output
digitalocean_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 の値を検閲しました name, ipv4_address、 と ipv4_address_private で指定した文字を使用する TFMASK_CHAR 正規表現と一致するため、環境変数。

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

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

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

結論

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

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

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

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

モバイルバージョンを終了