Terraformセキュリティベストプラクティス

By 清水 孝郎 - MARCH 22, 2023

SHARE:

Terraformセキュリティベストプラクティス

本文の内容は、2023年3月21にNIGEL DOUGLAS が投稿したブログ(https://sysdig.com/blog/terraform-security-best-practices)を元に日本語に翻訳・再構成した内容となっております。

コードとしてのインフラストラクチャ ー(IaC) を使用する場合、Terraform はデファクトのツールです。 リソース プロバイダーに関係なく、組織はそれらすべてを同時に操作できます。 コンフィギュレーションエラーがインフラストラクチャー全体に影響を与える可能性があるため、疑いの余地のない側面の 1 つは Terraform のセキュリティです。

この記事では、Terraformを使用するメリットを説明し、いくつかのセキュリティベストプラクティスを参照しながら、Terraformを安全な方法で使用するためのガイダンスを提供したいと思います。

  • Terraformの構成にセキュリティの脆弱性がないか監査し、セキュリティコントロールを実装する
  • Terraformのセキュリティにおけるアクセス資格の管理
  • Terraformモジュールの使用におけるセキュリティのベストプラクティス
  • TerraformモジュールのDIY

さっそく始めてみましょう!

Terraformとは?

Terraformは、インフラストラクチャーを安全かつ予測可能に作成、変更、破棄することができるオープンソースのInfrastructure as Codeソフトウェアツールです。Terraformを使用してプロビジョニングされたクラウドリソースのアクセス認証情報を安全に管理するという継続的な課題に対処することができます。

簡単な例を見るために、公式ページのgeting startedに従って、nginxコンテナをデプロイしてみます。

$> cat main.tf
terraform {
  required_providers {
    docker = {
      source  = "kreuzwerker/docker"
      version = "~> 2.13.0"
    }
  }
}
provider "docker" {}
resource "docker_image" "nginx" {
  name         = "nginx:latest"
  keep_locally = false
}
resource "docker_container" "nginx" {
  image = docker_image.nginx.latest
  name  = "tutorial"
  ports {
    internal = 80
    external = 8000
  }
}Code language: JavaScript (javascript)

terraform initでプロジェクトを初期化し、terraform applyでnginxサーバーコンテナをプロビジョニングし(すべてがうまくいっているか確認することを忘れずに)、terraform destroyでnginx Webサーバーを破棄することができました。

Terraformは、クラウドプロバイダーと認証し、ユーザーの代わりにリソースをプロビジョニングするために、アクセスキーとシークレットキーに依存します。この例では、認証は必要ありませんでしたが、ほとんどのプロバイダーは何らかの方法で認証情報を要求します。認証情報を安全に保管しないと、不正アクセスやデータ漏洩などのセキュリティ上の脆弱性が発生する可能性があります。注意すべき場所として、Terraformのステートファイルにクレデンシャルを保存していることが挙げられます。

Terraformのステートファイルは、Terraformが特定のインフラストラクチャーで作成したリソースを追跡するために使用するファイルです。これらのステートファイルは通常、Terraformを実行しているコンピュータにローカルに保存されますが、Terraform CloudやS3などのバックエンドにリモートで保存することも可能です。

ステートファイルには、Terraformが作成または変更したすべてのリソースを含む、特定の時点におけるインフラストラクチャーのスナップショットが含まれています。これには、リソースのID、現在の状態、その他Terraformがリソースを管理するために必要なメタデータのような詳細が含まれます。

Terraformについてもっと知りたい方は、Terraformとは?の記事をご覧ください。

Terraformのマニフェストファイルを監査する

Terraformは、インフラストラクチャーの現在の状態を判断し、そのインフラストラクチャーへの変更を計画するためにステートファイルを使用します。Terraformの構成に変更を加え、その変更を適用すると、Terraformは新しい構成と既存のステートファイルを比較し、新しい構成に合わせるためにインフラストラクチャーにどんな変更を加える必要があるかを決定します。

Auditing your Terraform Manifest Files

クラウドインフラストラクチャーにおける潜在的なセキュリティリスクを検出し軽減する上で、Terraformマニフェストファイルをスキャンすることのメリットは大きいです。もし1つしかできないのであれば、Terraformのファイルを徹底的にスキャンするようにしてください。ステートファイルはTerraformの運用に非常に重要なため、慎重に扱うことが重要です。特にリモートバックエンドを使用している場合は、常にステートファイルをバックアップし、安全に保存されていることを確認する必要があります。また、ステートファイルを手動で修正すると、ステートファイルと実際のインフラストラクチャーとの間に不整合が生じる可能性があるため、注意が必要です。

SCARLETEEL の場合、Terraform マニフェストをスキャンすると、S3 バケットで公開されたアクセス キーとシークレットを検出できた可能性があります。 以下の図に示されているように、攻撃者が 2 つ目の AWS アカウントにアクセスする機会を得る前に、早期に検出されていれば、インシデントを防止するか、少なくとも封じ込めることができた可能性があります。

SCARLETEEL diagram

そこで、Terraformの資産定義をスキャンすることで得られる具体的なメリットをいくつかご紹介します。

機密情報の特定

Terraform宣言ファイルをスキャンすることで、アクセスキー、シークレット、パスワード、トークンなど、ステートファイルから偶然に露出する可能性のある機密情報を特定することができます。SCARLETEEL インシデントで見られたように、攻撃者はこの情報を使ってクラウドインフラストラクチャーに不正にアクセスし、組織間で横移動することができます。

ネットワークの誤設定

VPCやAWS Security Groupを設定してクラウドへのアクセスを設定するように、Terraformのファイルも正しい設定を考慮し、攻撃対象領域をできるだけ少なくする必要があります。

変更点の可視化

Terraformのマニフェストファイルは、リソースがいつ作成、更新、破棄されたかを含む、インフラストラクチャーに加えられた変更の履歴を提供します。ステートファイルをスキャンすることで、変更を追跡し、異常を特定し、セキュリティインシデントに迅速に対応することができます。

コンプライアンスとガバナンス

.tfファイルをスキャンすることで、クラウドインフラストラクチャーがPCI DSSHIPAASOC 2などの規制およびガバナンス要件に準拠していることを確認できます。潜在的なセキュリティリスクを検出することで、是正措置を講じ、コンプライアンス違反を防止することができます。

脆弱性発見の自動化

Terraformfileのスキャンを自動化することで、セキュリティリスクをリアルタイムで検知・軽減することができます。スキャンをDevOpsパイプラインにインテグレーションすることで、開発サイクルの早い段階で脆弱性を発見し、本番環境に到達するのを防ぐことができます。

全体として、Terraform定義ファイルのスキャンは、Terraformを使用してクラウドインフラストラクチャーを管理する組織にとって、必須のセキュリティ慣行です。潜在的なセキュリティリスクを特定・軽減し、クラウド環境のセキュリティとコンプライアンスを確保することができるのです。

Terraformスキャンツール

Terraformスキャンツールは、Terraformコードの設定ミスやセキュリティ問題、脆弱性を見つけるのに役立ちます。これらのツールは、本番環境にデプロイされる前に、ユーザーが問題を特定し、修正することを支援するように設計されています。以下は、人気のあるTerraformスキャンツールです。

  1. Terrascan – Terrascanは、Terraformのコードをスキャンしてセキュリティ問題を解決するオープンソースの静的解析ツールです。ポリシーアズコードの機能を提供し、複数のクラウドプロバイダーをサポートしています。
  2. Checkov – Checkovは、Terraformのコードをスキャンして、セキュリティ上の問題やベストプラクティス違反を検出するオープンソースのツールです。ビルトインポリシーの包括的なライブラリを持ち、高度なカスタマイズが可能です。
  3. KICS – Keeping Infrastructure as Code Secure (KICS)は、Terraformのコードをスキャンして、セキュリティ問題、コンプライアンス違反、インフラストラクチャーの設定ミスを検出するオープンソースツールです。複数のクラウドプロバイダーをサポートし、高度なカスタマイズが可能です。

本ブログでは、Terraformのセキュリティ問題をスキャンするためにTerrascanがどのように使用されるかを説明します。

静的コード解析を用いて、Terraformのコードや設定をスキャンし、セキュリティ上の問題や脆弱性を発見します。Terrascanはスタンドアローンツールとして、またはCI/CDパイプラインにインテグレーションして、ビルドプロセスの一部としてTerraformコードを自動的にスキャンすることができます。では、Terrascanを使用してどのように脆弱性をスキャンするのかを紹介します。

1. TerrascanでTerraformのコードをスキャンする

Terrascanの設定ファイルを初期化したら、terrascan scanコマンドでTerraformのコードをスキャンして、セキュリティ上の問題を確認します。

terrascan scan -f /path/to/terraform/code

2.Terrascanスキャン結果のレビュー

スキャンが完了すると、TerrascanはあなたのTerraformコードで検出されたセキュリティ問題のリストを出力するはずです。各問題には、問題の説明、問題が検出されたTerraformコード内の場所、重大度評価が含まれます。以下は、Terrascanスキャンによる出力の例です。

=== Results Summary ===
Pass: 0, Fail: 1, Skip: 0
File: /path/to/terraform/code/main.tf
Line: 15 Rule ID: AWS_001
Rule Description: Ensure no hard-coded secrets exist in Terraform configurations
Severity: HIGHCode language: JavaScript (javascript)
  • この例では、TerrascanはTerraformコードのmain.tfファイルで重大性の高いセキュリティ問題を検出しました。
  • この問題は、Terraformの設定にハードコードされたシークレットに関連しており、深刻なセキュリティリスクとなる可能性があります。

Terraformスキャンツールを使用することで、本番環境にデプロイされる前に潜在的なセキュリティ問題や脆弱性を特定し、セキュリティ向上に役立てることができます。これにより、セキュリティ侵害を防止し、インフラストラクチャーが安全でコンプライアンスに準拠した方法で構成されていることを確認することができます。さらに、これらのツールは、組織全体でセキュリティポリシーとベストプラクティスを実施し、人的ミスのリスクを低減するのに役立ちます。

Terraform Securityにおけるアクセス資格の管理

Terraformを使えば簡単にクラウドリソースを構成できますが、クラウドプロバイダーと認証するためのクレデンシャルが必要です。これらの認証情報をどのように管理するかは、インフラストラクチャーのセキュリティにとって重要です。ベストプラクティスの1つは、安全な認証情報管理システムを使用して認証情報を保存することです。

認証情報を平文で保存したり、Terraformのコードにハードコーディングしたりすることは避けることが肝要です。

Terraform ステートにシークレットを保存してはいけない

Terraformのステートファイルには、パスワードやAPIキーなどの秘密は含まれていないはずです。代わりに、Terraformの入力変数や外部データソースを使用して、機密情報をモジュールに渡します。こうすることで、ステートファイル内でシークレットが公開されないようにすることができます。例えば、シークレットを含まないTerraformのステートファイルを以下に示します。

# Example Terraform state file
# This file tracks the state of our AWS EC2 instances.
terraform {  
 required_version = ">= 0.12"
}
provider "aws" {  
 region = "eu-west-1"
}
resource "aws_instance" "example" {  
 ami = "ami-0c55b159cbfafe1f0"  
 instance_type = "t2.micro"  
 tags = {    
  Name = "example-instance"  
 }
}
output "public_ip" {  
 value = aws_instance.example.public_ip
}Code language: PHP (php)

上記の例では、ステートファイルにはシークレットや機密情報は保存されていません。

  • このファイルでは、1つのAWS EC2インスタンスを定義し、aws_instanceリソースタイプを使用して作成します。
  • providerブロックは使用するAWSリージョンを指定し、outputブロックはインスタンスのパブリックIPアドレスを表示する出力変数を定義しています。

AWSアクセスキーやシークレットキーなどの機密情報が必要な場合は、ステートファイルに格納するのではなく、入力変数や外部データソースを使用してモジュールに渡します。

シークレットをプレーンテキストで保存しない

Terraformのマニフェストには、決してシークレットをプレーンテキストで保存しないでください。

先にも述べたように、環境変数や入力変数を使ってTerraformモジュールにシークレットを渡すことができます。例えば、環境変数を使ってTerraformにシークレットを渡すスクリプトを以下に示します。

provider "aws" {  
 region = "eu-west-1"
}
resource "aws_db_instance" "example" {  
 engine            = "mysql"  
 instance_class    = "db.t2.micro"  
 allocated_storage = 10  
 name              = "exampledb"  
 username          = "exampleuser"  
 password          = "${var.db_password}"
}
variable "db_password" {}Code language: JavaScript (javascript)

db_passwordをTerraformマニフェスト内にプレーンテキストで保存する代わりに、Terraformコマンドを実行するときに環境変数として渡すことができます。

export TF_VAR_db_password="supersecret"Code language: JavaScript (javascript)

環境変数を渡したら、あとはTerraformスクリプトを実行するだけです。

terraform apply

変数db_passwordには、aws_db_instanceリソースに渡される環境変数TF_VAR_db_passwordの値が入力されます。パスワードはTerraformマニフェストにプレーンテキストで保存されないので、より安全です。また、入力変数を使ってTerraformモジュールにシークレットを渡すこともできます。以下は、入力変数を使ってTerraformモジュールにシークレットを渡す場合の例です。

provider "aws" {  
 region = "eu-west-1"
}
resource "aws_db_instance" "example" {  
 engine               = "mysql"  
 instance_class       = "db.t2.micro"  
 allocated_storage    = 10  
 name                 = "exampledb"  
 username             = "exampleuser"  
 password             = "${var.db_password}"
}
variable "db_password" {  
 type = "string"
}Code language: JavaScript (javascript)

そして、terraform applyコマンドを実行する際に、このdb_password変数をTerraformモジュールに渡すことができます。

terraform apply -var "db_password=supersecret"Code language: JavaScript (javascript)

このアプローチでは、Terraformマニフェストにプレーンテキストで保存することなく、Terraformモジュールにシークレットを渡すことができます。

より良いアプローチは、HashiCorp VaultAWS Secrets Managerのような安全なクレデンシャル管理システムを使用することです。これらのツールは、シークレットを安全に保管・管理する方法を提供し、アクセス制御、暗号化、監査ログを提供します。まずは、Hashicorp Vaultのシークレットエンジンを作成することから始めるとよいでしょう。

resource "vault_mount" "secret" {  
 path = "secret"  
 type = "kv"
}
resource "vault_generic_secret" "aws_credentials" {  
 path = "secret/aws/credentials"  
 data_json = <<JSON  {    
  "access_key": "${var.aws_access_key}",
  "secret_key": "${var.aws_secret_key}"  
 }  JSON
}Code language: JavaScript (javascript)

上記のコードでは、KV secrets engine mountと、AWSの認証情報を保存するgeneric secretを作成しています。

var.aws_access_keyvar.aws_secret_keyを対応する環境変数に置き換えるか、別の方法でこれらの値をTerraformに安全に渡せるようにしましょう。シークレットエンジンを設定したら、以下のポリシーでHashicorp VaultからそれらのAWS認証情報を安全に取得することができます。

data "vault_generic_secret" "aws_credentials" {  
 path = "secret/aws/credentials"
}
provider "aws" {  
 access_key = data.vault_generic_secret.aws_credentials.data.access_key  
 secret_key = data.vault_generic_secret.aws_credentials.data.secret_key  
 region     = "eu-west-1"
}Code language: JavaScript (javascript)

上記のコードは、HashiCorp VaultからAWS認証情報を取得し、最初のスクリプトで設定した ‘aws’ プロバイダーのプロバイダー構成として設定します。これで、AWS認証情報をハードコードしたり平文で保存したりすることなく、Terraformを使ってAWSリソースを構成できるようになりました。

注意:これはあくまでスクリプトの例です。特定の要件や使用しているクレデンシャル管理システムに基づいて修正する必要があります。さらに、シークレットが保護され監視されるように、資格情報管理システムのアクセス制御と監査ログを構成する必要があります。

キーの頻繁なローテーション

安全なクレデンシャル管理システムを導入した後は、キーを頻繁にローテーションすることが重要です。

キーのローテーションとは、定期的に新しいアクセスキーを生成し、古いアクセスキーを失効させることです。この方法によって、キーの1つが危険にさらされたとしても、長くは有効でないことが保証されます。

Hashicorp VaultやAWS Secrets Managerのようなツールを使用して、キーのローテーションプロセスを自動化することもできます。このプロセスを自動化することで、セキュリティ侵害の主な原因の1つである人的ミスを回避することができます。すでにHashicorp Vaultを使用していたので、システムのセキュリティを維持するためにTerraformキーが頻繁にローテーションされるようにすべき、次のVault Policyの例を作成します。

path "secret/data/terraform/*" {  
 capabilities = ["read", "create", "update", "delete", "list"]  
 allowed_parameters = {    
  "max_versions": ["10"]    
  "force":      ["true"]  
 }
}
path "sys/leases/renew" {  
 capabilities = ["create", "update"]
}
path "sys/leases/revoke" {  
 capabilities = ["update"]
}Code language: JavaScript (javascript)

このポリシーには3つのステートメントがあり、Terraformのキーを管理するために最低限必要な権限を提供しています。

  1. 最初のステートメントでは、secret/data/terraform/パス配下のシークレットの読み取り、作成、更新、削除、リストアップをユーザーに許可しています。このパスには、Terraformが他のリソースにアクセスするために使用するキーが含まれているはずです。max_versionsパラメータは保存できるキーのバージョン数を制限し、forceパラメータはすでにバージョン数の上限に達していても新しいキーが生成されるようにする。
  2. 2番目のステートメントでは、ユーザーがシークレットのリースを更新することを許可しています。これは、キーが頻繁にローテーションされることを保証するために重要です。
  3. 3番目のステートメントは、ユーザーがシークレットのリースを取り消すことを可能にします。これは、キーが危険にさらされ、直ちに失効させる必要がある場合に便利です。

注意:これはあくまでポリシーの例であり、特定の要件とTerraformで管理しているリソースに基づいて変更する必要があります。また、組織のセキュリティポリシーに基づき、Vaultが自動的にキーをローテーションするよう設定する必要があります。

最小特権のアクセスポリシー

最後に、Terraformインフラストラクチャーを構成する際に、最小特権の原則を実装することは非常に重要です。

最小特権の原則とは、特定のリソースが正しく機能するために必要な最小レベルのアクセス権を付与することを意味します。このアプローチにより、攻撃者がインフラストラクチャーにアクセスした場合に与える可能性のある損害を最小限に抑えることができます。

Terraformでは、適切なIAMロール、ポリシー、パーミッションを定義することで最小特権のアクセスポリシーを実装することができ、IAMの誤設定も減らすことができます。また、セキュリティのベストプラクティスを考慮して設計されたTerraformモジュールを使用することができます。例えば、以下のポリシーは、指定されたユーザーまたはグループに対して、Terraformリソースへの最小特権アクセスを提供します。

{    
 "Version": "2012-10-17",    
 "Statement": [        
  {            
   "Effect": "Allow",            
   "Action": [                
    "terraform:plan",                
    "terraform:apply",                
    "terraform:destroy"            
   ],            
   "Resource": "arn:aws:terraform:::<your-terraform-workspace>"        
  },        
  {            
   "Effect": "Allow",            
   "Action": [                
    "terraform:state-push",                
    "terraform:state-pull"            
   ],            
   "Resource": "arn:aws:terraform:::<your-terraform-state-bucket>"        
  },        
  {            
   "Effect": "Allow",            
   "Action": "terraform:state-list",            
   "Resource": "arn:aws:terraform:::<your-terraform-workspace>/*"        
  }    
 ]
}Code language: JSON / JSON with Comments (json)

上記のポリシーには3つの記述があり、Terraformリソースを管理するために最低限必要な権限を提供しています。

  1. 最初のステートメントでは、指定されたユーザーまたはグループが、指定されたTerraformワークスペース内のリソースに対してterraform:plan, terraform:apply, terraform:destroy アクションを実行することを許可します。<your-terraform-workspace>をTerraformワークスペースのAmazon Resource Names(ARN)に置き換える必要があります。
  2. 2番目のステートメントは、指定されたユーザーまたはグループが、指定されたTerraform state bucketに対してterraform:state-pushおよびterraform:state-pullアクションを実行できるようにします。ここでも<your-terraform-state-bucket>をTerraformのステートバケットのARNに置き換える必要があります。
  3. 3つ目のステートメントは、指定したユーザーまたはグループが、指定したTerraformワークスペース内のすべてのリソースに対してterraform:state-listアクションを実行することを許可します。いつものように、<your-terraform-workspace>をTerraformワークスペースのARNに置き換えることを忘れないでください。

注意:これはあくまでポリシーの例であり、特定の環境とユースケースに合わせてARNを変更する必要があります。さらに、特定の要件とTerraformで管理しているリソースに基づいて、ポリシーを見直し、調整する必要があります。

Terraformモジュールの使用

Terraformモジュールは、インフラストラクチャーコードを整理して再利用するための強力な方法です。しかし、他のコードと同様に、モジュールは適切に使用されないとサプライチェーンのセキュリティリスクをもたらす可能性があります。このセクションでは、コードとして安全なインフラストラクチャーを確保するためにTerraformモジュールを使用するためのベストプラクティスを紹介しながら、Terraformのセキュリティについて解説していきます。

盲目的に信用せず、作成されるインフラストラクチャーやセキュリティグループなどをダブルチェックし、常に ‘plan’ を優先すること

Terraformモジュールは時間と労力を大幅に節約できますが、盲目的に信頼すべきではありません。インフラストラクチャーに変更を加える前に、必ずコードとplanを確認してください。これには、Terraformに設定ミスや脆弱性がないかスキャンすることや、作成されるセキュリティグループやその他のリソースがセキュリティ要件を満たしているかどうか確認することも含まれます。

Terraformの‘plan’コマンドを使用して、変更を適用する前に確認します。

‘plan’コマンドは、設定を適用した際にTerraformが行うことを示す実行計画を生成するために使用されます。これにより、実際に適用する前に変更内容を確認し、期待に応えてくれることを確認することができます。プランを生成する最もシンプルなコマンドは、以下の通りです。

terraform plan

Terraformは設定ファイルを分析し、あなたが変更を適用したときに何をするかというplanを生成します。出力は以下のような感じになります。

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

Changes to be added:  
 + aws_security_group.web      
  id:                                <computed>      
  name:                              "web"      
  ...
 + aws_instance.web      
  id:                                <computed>      
  ami:                               "ami-0c55b159cbfafe1f0"      
  ...
Changes to be destroyed:  
 - aws_instance.db      
  id:                                "i-0123456789abcdef0"      
  ...Code language: CSS (css)

それぞれのケースで、出力を見直して、変更が期待通りのものであることを確認します。この例では、Terraformは新しいセキュリティグループと新しいEC2インスタンスを作成し、既存のEC2インスタンスを破棄します。planに問題がなければ、applyコマンドを使用して変更を適用できます。

terraform apply

変更を適用する前にplanを見直すことで、予期せぬ変更やエラーをキャッチし、インフラストラクチャーに起こりうる問題を回避することができます。このセクションの冒頭で述べたように、インフラストラクチャーを盲目的に信用してはいけません。planがあるからこそ、正確なセキュリティ仕様を適用することができるのです。

モジュールを常にアップデートする

モジュールは、最新のセキュリティパッチとベストプラクティスを適用しておく必要があります。

常にアップデートを確認し、必要に応じて適用してください。これにより、インフラストラクチャーが安全であり、最新のセキュリティ標準に対応していることを確認できます。Terraformには、モジュールを最新のTerraformセキュリティパッチとベストプラクティスに保つための組み込みコマンドはありませんが、しかし、モジュール管理とバージョン管理に役立つサードパーティツールはいくつかあります。

そのようなツールの1つがTerraform自身の公式モジュールレジストリで、これは一般的なインフラストラクチャーのニーズに対応したビルド済みモジュールのキュレーションコレクションです。レジストリのモジュールはTerraformのコミュニティによって管理されており、最新のセキュリティパッチやベストプラクティスに対応できるよう定期的に更新されています。

レジストリのモジュールを利用するには、Terraformの設定ファイルにそのソースURLを記載します。例えば、以下のようになります。

module "my_module" {  
 source = "terraform-aws-modules/s3-bucket/aws"  
 ...
}Code language: JavaScript (javascript)

モジュールをレジストリに登録されている最新版に更新するには、terraform get コマンドに -update フラグを指定します。

terraform get -updateCode language: JavaScript (javascript)

上記の ‘terraform get -update’ コマンドは、Terraformの構成に含まれるすべてのモジュールを、レジストリで利用可能な最新バージョンに更新します。モジュールレジストリだけでなく、Terraform CloudAtlantisといったサードパーティ製のツールもあり、以下のようなより高度なモジュール管理・バージョン管理機能が提供されています。

  • 自動アップデート
  • バージョンロック
  • コラボレーションツール

これらのツールは、モジュールを最新の状態に保ち、インフラストラクチャーが安全で最新のセキュリティ標準に準拠していることを確認するのに役立ちます。

ステートファイルをローカルに保存せず、後で引き出せるような場所に暗号化して保存してください

Terraformのステートファイルには、リソースIDやシークレットなど、インフラストラクチャーに関する機密情報が含まれています。それらをローカルに保存したり、バージョン管理システムで公開したりしないでください。代わりに、必要なときに後で引き出せるような安全な場所に保存してください。また、不正なアクセスから保護するために、ステートファイルを暗号化する必要があります。たとえば、S3 バケットのリモートステートストレージを使用して、これらのステートファイルをローカルまたはバージョン管理システムに保存しないようにする以下の Terraform スクリプトを作成しました。

terraform {  
 backend "s3" {    
  bucket         = "my-terraform-state-bucket"    
  key            = "my-terraform-state.tfstate"    
  region         = "eu-west-1"    
  dynamodb_table = "my-terraform-state-lock"    
  encrypt        = true  
 }
}
resource "aws_instance" "example" {  
 ami           = "ami-0c55b159cbfafe1f0"  
 instance_type = "t2.micro"  
 ...
}Code language: JavaScript (javascript)

この例では、ファイルの先頭にあるterraformブロックで、ステートファイルをmy-terraform-state-bucketというS3バケットに、my-terraform-state.tfstateというキーで保存するよう指定しています。

  • regionパラメータは、S3バケットが配置されているAWSリージョンを指定します。
  • dynamodb_tableパラメーターは、ロックに使用するDynamoDBテーブルの名前を指定します。
  • encryptパラメータは、S3に保存する前にステートファイルを暗号化するようTerraformに指示します。
  • aws_instanceリソースブロックは、作成するインフラストラクチャーリソースを指定しますが、機密情報は含まれません。

terraform applyを実行すると、Terraformはリソースを作成し、S3バケットにステートファイルを保存します。
以降のterraformコマンドを実行すると、Terraformはローカルファイルではなく、S3バケット内のリモートステートファイルを使用します。

ステートファイルをS3バケットのような安全な場所に保存することで、ローカルやバージョン管理システムには保存されないようにすることができます。また、ステートファイルを暗号化することで、不正なアクセスから保護することができます。

Terraformのステートを手動で変更しない

Terraformのステートファイルを手動で修正すると、問題が発生したり、セキュリティリスクが発生したりすることがあります。

ステートファイルの管理には必ずTerraformのコマンドを使用してください。ステートファイルを変更する必要がある場合は、Terraformのimportコマンドを使用してリソースをステートにインポートしてください。こうすることで、ステートファイルと実際のインフラストラクチャーとの整合性を保つことができます。

AWSで稼働しているEC2インスタンスがあり、Terraformで管理を始めたいと仮定してみます。まず、既存のリソースを記述する新しいTerraform設定ファイルを作成する必要があります。以下は簡単な例です。

# Example Terraform configuration file to import an existing EC2 instance

provider "aws" {  
 region = "eu-west-1"
}
resource "aws_instance" "example" {  
# We'll fill in these details in the next step
}Code language: PHP (php)

Terraform importコマンドに続いて、Terraformの設定ファイルにリソースの種類、リソースの一意識別子、リソースの名前を指定して実行します。

terraform import aws_instance.example i-0123456789abcdefgCode language: JavaScript (javascript)

上記の例では、i-0123456789abcdefgがAWSのEC2インスタンスの固有識別子、aws_instance.exampleがTerraformの設定ファイル内のリソース名となっています。terraform importコマンドを実行すると、今度はTerraformが新しいステートファイルを作成し、そこに既存のリソースをインポートします。あとは、terraform planterraform applyといったTerraformの通常のコマンドを使って、今後のリソースの管理を行うことになります。

EC2のセキュリティについてもっと知りたい方は、Securing SSH on EC2Amazon EC2のセキュリティを確保する方法を忘れずにチェックしてください。

Terraformモジュールの作成

Terraformモジュールは、インフラストラクチャーコードを整理して再利用する方法を提供します。Terraformモジュールを作成する際には、コードとして安全なインフラストラクチャーを確保するために、Terraformセキュリティのベストプラクティスに従うことが重要です。

Creating Terraform modules

このセクションでは、Terraformモジュールを作成するためのベストプラクティスをいくつか紹介します。

Terraformのマニフェストを保存するためにgitを使用する

TerraformマニフェストをGitのようなバージョン管理システムに保存します。これにより、変更の追跡、変更の差し戻し、他の人との共同作業が容易になります。変更が追跡され監査可能であることを保証するために、Terraformモジュールには常にバージョン管理を使用しましょう。

Terraformマニフェストを保存する方法を理解するために、カレントディレクトリでGitを初期化してみましょう。

git init

main.tfのような新しいTerraform設定ファイルを作成し、そこにいくつかのリソースを追加する必要があります。そして、そのファイルをGitに追加し、変更をコミットすることができます。

git add main.tfgit commit -m "Initial commit"Code language: JavaScript (javascript)

Terraformの設定ファイルを少し変更し、Gitに追加してコミットします。

git add main.tfgit commit -m "Added new resource"Code language: JavaScript (javascript)

Terraformの設定ファイルを以前のバージョンに戻す必要がある場合、Gitを使って前のコミットをチェックアウトすることができます。

git checkout HEAD~1 main.tfCode language: CSS (css)

このコマンドは、前回のコミットからmain.tfのバージョンをチェックアウトします。Git を使って Terraform プロジェクトで他の人と共同作業をすることもできます。たとえば、自分の変更をリモートの Git リポジトリにプッシュして、他の人がそれをクローンして作業できるようにすることができます。

git remote add origin <remote-repository-url>git push -u origin masterCode language: HTML, XML (xml)

Gitを使うことで、以下のことが可能になります。

  • Terraformマニフェストの変更点を追跡し、誰が特定のリソースを変更したのか、最後に何かが変更されたのはいつなのかを知ることができます。
  • 障害が発生した場合、変更を元に戻すことができ、以前の作業バージョンに戻ることができます。
  • Pull Requests を使って、より簡単に他の人とコラボレーションして、変更について議論することができます。

Terraformの署名

Terraformの署名は、ユーザーがシークレットキーでTerraformマニフェストに署名することを可能にするクールな機能です。

これは、ユーザーが自分のTerraformマニフェストが改ざんされていないことを確認するのに役立つので、Terraformのセキュリティを大きく向上させるでしょう。残念ながら、TerraformにはTerraformマニフェストにシークレットキーで署名する機能が組み込まれていません。しかし、GPGやHashicorpのPlugin Signingツールなどの外部ツールを使って、Terraformマニフェストに署名し、その整合性を確認することができます。例えば、GPGでTerraformマニフェストに署名し、その整合性を検証するスクリプトを以下に示します。

#!/bin/bash

# Set the path to your Terraform code
TERRAFORM_PATH="./my-terraform-code"

# Set the path to your GPG private key
GPG_PRIVATE_KEY_PATH="/path/to/your/gpg/private/key"

# Set the GPG key ID
GPG_KEY_ID="your-gpg-key-id"

# Set the GPG key passphrase
GPG_KEY_PASSPHRASE="your-gpg-key-passphrase"

# Set the output file for the signed Terraform manifest
SIGNED_MANIFEST_FILE="./signed-terraform-manifest.tf"

# Set the output file for the verified Terraform manifest
VERIFIED_MANIFEST_FILE="./verified-terraform-manifest.tf"

# Sign the Terraform manifest with GPG
gpg --yes --batch --passphrase="$GPG_KEY_PASSPHRASE" --local-user="$GPG_KEY_ID" 
--output="$SIGNED_MANIFEST_FILE" --sign "$TERRAFORM_PATH"

# Verify the integrity of the signed Terraform manifest with GPG
gpg --verify "$SIGNED_MANIFEST_FILE"

# Extract the signed Terraform manifest from the GPG signature
gpg --output="$VERIFIED_MANIFEST_FILE" --decrypt "$SIGNED_MANIFEST_FILE"Code language: PHP (php)

いつものように、上記のスクリプトを実行し、シークレットキーを使用してGPGでTerraformマニフェストに署名し、署名されたマニフェストの完全性を検証し、さらに使用するために署名されたマニフェストを抽出する必要があります。

GPGでTerraformマニフェストに署名し、その完全性を検証することで、Terraformマニフェストが改ざんされておらず、最初に署名されたときと同じであることを確認することができます。これにより、インフラストラクチャーコードのセキュリティを向上させ、不正な変更のリスクを低減することができます。

全プロセスの自動化

Terraformモジュールの作成、テスト、デプロイの全プロセスを自動化しましょう。GitLab CI/CDやJenkinsのようなツールを使ってプロセスを自動化することで、インフラストラクチャーコードの管理が容易になり、安全性を確保することができます。

例えば、ある大手金融サービス会社では、インフラストラクチャーチームがクラウドインフラの管理に使用するTerraformモジュールを多数保有していました。しかし、これらのモジュールを手動で管理・デプロイすることで、エラーや不整合を引き起こすという課題に直面していました。

そこで、GitLab CI/CDを導入し、Terraformモジュールの作成、テスト、デプロイを自動化することにしました。以下は、彼らがGitLab CI/CDパイプラインをどのように構成したかの一例です。

# .gitlab-ci.yml

stages:  
 - build  
 - test  
 - deploy

# Build stage: Compile and package the Terraform code
build:  
 stage: build  
 script:    
  - terraform init -backend-config="bucket=${TF_STATE_BUCKET}" ./path/to/terraform/module    
  - terraform validate ./path/to/terraform/module    
  - terraform fmt -check ./path/to/terraform/module    
  - terraform plan -out=tfplan ./path/to/terraform/module

# Test stage: Run automated tests on the Terraform code
test:  
 stage: test  
 script:    
  - terraform init -backend-config="bucket=${TF_STATE_BUCKET}" ./path/to/terraform/module    
  - terraform validate ./path/to/terraform/module    
  - terraform fmt -check ./path/to/terraform/module    
  - terraform plan -out=tfplan ./path/to/terraform/module    
  - terraform apply -auto-approve tfplan
# Deploy stage: Apply the Terraform code to the target environment
deploy:  
 stage: deploy  
 script:    
  - terraform init -backend-config="bucket=${TF_STATE_BUCKET}" ./path/to/terraform/module    
  - terraform validate ./path/to/terraform/module    
  - terraform fmt -check ./path/to/terraform/module    
  - terraform apply -auto-approve ./path/to/terraform/moduleCode language: PHP (php)

この例では、パイプラインはビルド、テスト、デプロイの3つのステージで構成されています。

  • ビルドステージでは、Terraformのコードがコンパイルされパッケージ化され、planが生成されます。
  • テストステージでは、Terraformのコードに対して、コードの検証、正しいフォーマット、テスト環境へのplanの適用などの自動テストが実行されます。
  • 最後に、デプロイ段階では、Terraformコードをターゲット環境に適用し、インフラストラクチャーがコードに従って構成されていることを確認します。

セキュリティの観点から、GitLab CI/CDを通じてTerraformモジュールの作成、テスト、デプロイのプロセスを自動化することで、インフラストラクチャーコードのセキュリティを向上させ、不正な変更のリスクを低減させることができます。

outputを乱用しない

シークレットのような機密情報を保存するために出力変数を乱用しないでください。

機密情報を保存するには、VaultやAWS Secrets Managerのようなシークレット管理ツールを使用します。以下のスクリプトでは、Terraformで使用するシークレットの保存と取得に、Hashicorp Vaultを使用します。

# main.tf
provider "vault" {  
 address = "https://vault.example.com"  
 token   = var.vault_token
}
resource "vault_generic_secret" "my_secrets" {  
 path = "secret/myapp"
 data_json = jsonencode({    
  db_username = vault_generic_secret.my_secrets.data["db_username"]    
  db_password = vault_generic_secret.my_secrets.data["db_password"]    
  api_key     = vault_generic_secret.my_secrets.data["api_key"]  
 })
}
data "vault_generic_secret" "my_secrets" {  
 path = "secret/myapp"
 depends_on = [vault_generic_secret.my_secrets]
}

# Use the secrets in your Terraform code
resource "aws_instance" "nigel_instance" {  
 ami           = "ami-0c55b159cbfafe1f0"  
 instance_type = "t2.micro"  
 key_name      = var.key_name
 tags = {    
  Name = "nigel-instance"  
 }
 connection {    
  user        = data.vault_generic_secret.my_secrets.data["db_username"]    
  private_key = file(var.key_path)    
  timeout     = "2m"  
 }
 provisioner "remote-exec" {    
  inline = [      
   "echo ${data.vault_generic_secret.my_secrets.data["api_key"]} > /tmp/api_key",      
   "sudo apt-get update",      "sudo apt-get install -y nginx",      
   "sudo service nginx start"    
  ]  
 }
}Code language: PHP (php)

この場合、vault_generic_secret リソースを使用してシークレットを Vault に保存し、data.vault_generic_secret データ ソースを使用してそれらのシークレットを取得し、Terraform コードで使用できます。

Hashicorp VaultやAWS Secrets Managerのようなシークレット管理ツールを使って機密情報を保存することで、出力変数やTerraformコードの他の部分にシークレットを保存する必要性を簡単に回避でき、したがってそれらの機密情報/シークレットから不正なアクセスや暴露の関連リスクを低減できます。

“pet” インフラストラクチャーにprevent-destroyを使用する

prevent-destroy  オプションを使用して、 “pet” インフラストラクチャーリソースが誤って削除されるのを防ぎます。 セキュリティの観点からは、prevent_destroy ライフサイクル メタ引数は、気まぐれに作成された “pet” インフラストラクチャーリソースが誤って削除されるのを防ぐように設計されているため、コストのかかるミスを回避し、インフラストラクチャーの安定性と一貫性を確保するのに役立ちます。

# main.tf
resource "aws_instance" "nigel_instance" {  
 ami           = "ami-0c55b159cbfafe1f0"  
 instance_type = "t2.micro"  
 key_name      = var.key_name
 lifecycle {    
  prevent_destroy = true  
 }
 tags = {    
  Name = "nigel_instance"    
  Environment = "staging"    
  Pet = "true" # LOL  
 }
}Code language: PHP (php)

“nigel_instance” リソースが誤って削除されるのを防ぐために、prevent-destroy を使用する予定です。 prevent_destroy を true に設定すると、Terraform は、terraform destroy コマンドまたは Terraform Web UI によってリソースが削除されるのを防ぎます。 破壊防止を使用することで、このラボ/ステージング環境の重要性を強調するだけでなく、本番環境でなくても、安定性と一貫性を維持できるようにします。 そのため、ダウンタイムの原因となる同僚による誤った削除のリスクを軽減しています。

Terraform を使用してインフラストラクチャーを作成する場合、インフラストラクチャーが安全であることを確認することが重要です。

まとめ

Terraformはインフラストラクチャー・アズ・コードを実現する強力なツールですが、使用する際にはセキュリティを優先することが極めて重要です。

本ガイドで紹介したTerraformのセキュリティベストプラクティスに従うことで、セキュリティ侵害のリスクを最小化し、インフラストラクチャーを安全に保つことができます。要約すると、以下の通りです。

  • Terraformのファイルをスキャンして、設定ミスや脆弱性を発見する。
  • 安全なクレデンシャル管理システムを使用し、Terraformファイル内に機密を保存しない。
  • 最小権限アクセスポリシーを導入する。
  • クラウドプロバイダーにも同じセキュリティプラクティスを適用する。

DevOpsエンジニア、セキュリティアナリスト、クラウドアーキテクトのいずれであっても、これらのガイドラインにより、クラウドネイティブインフラストラクチャーを簡単に管理し、セキュリティを確保することができます。