📛

OpenTofuでStateファイルの暗号化が実装されました

2024/03/29に公開
2

序論

以前紹介したOpenTofuが2024年1月からGAされています。[1]
GAされた時点ではTerraform 1.6系に追随する形でOpenTofu1.6としてリリースされましたが、今後予定されるOpenTofu1.7からは独自の機能がいくつか実装されることになります。その中でも一番の目玉はStateファイルの暗号化でしょうか。
今まで平文で保存してきたStateファイルを暗号化して保存できるようになり、セキュリティが向上しました。プレリリースされたばかりですが、1.7で追加されたOpenTofuを触ってみこうと思います。

対象読者

  • Terraformについての基礎知識を持っている
  • Stateファイルの取り扱いについて課題を持っている
  • OpenTofuの独自機能について知りたい

OpenTofu 1.7について

前述した通りOpenTofu1.7.0は現在α版としてプレリリースされています。[2]

https://opentofu.org/blog/help-us-test-opentofu-1-7-0-alpha1/

本番環境での利用は推奨されておらずあくまでも非本番環境で使い、フィードバックを募集している段階となっています。
各種パッケージマネージャーからもインストールできず、GitHub Releaseページからダウンロードすることのみ利用できます。

追加機能について

1.7では新機能や新しい関数などTerraformにはないものが組み込まれています。

主な追加機能

  • Stateファイルの暗号化
  • Removedブロック
  • 独自の組み込み関数

Stateファイルの暗号化は1.7の大きな目玉として注目されており、すでに公式ドキュメントも充実しています。

https://1-7-0-alpha1.opentofu.pages.dev/docs/language/state/encryption/

新規に作られるStateファイル以外にも作成済みの暗号化されていないStateファイルも暗号化されたStateファイルに移行できる手順も用意されています。
まずは新規に暗号化されたStateファイルを作成してみます。

Stateファイル暗号化

暗号化に使用する鍵は現時点では2つの手段があります。
自身でパスフレーズを設定して、暗号鍵を生成するPBKDF2[3]か、AWSの鍵管理マネージドサービスであるAES KMS[4]です。
また2024年3月時点でサポートされている暗号化方式はAES-GCM[5]のみになります。

ではStateファイルを暗号化するコードを書いていきます。

OpenTofuの階層構造はシンプルにしており、Amazon S3を作成するだけの構造にしています。

$ tree
├── encrypt.tf ← 暗号化設定ファイル
├── init.tf
└── s3.tf
init.tf
#AWSプロバイダーインストール
terraform {
  required_providers {
    aws = {
      source  = "hashicorp/aws"
      version = "~> 5.0"
    }
  }
}
provider "aws" {
  region = "ap-northeast-1"
}
s3.tf
#S3作成ファイル
resource "aws_s3_bucket" "example" {
  bucket = "yuta-opentofu-test-bucket"
}

PBKDF2の場合

PBKDF2の場合、パスフレーズをHCLコード内に記載します。

encrypt.tf
terraform {
  encryption {
    key_provider "pbkdf2" "passphrase" {
      passphrase = "correct-horse-battery-staple" #最低でも16文字以上
    }
    method "aes_gcm" "my_method" {
      keys = key_provider.pbkdf2.passphrase
    }
    state {
      method = method.aes_gcm.my_method
    }
  }
}

tofu applyを実行し生成されたterraform.tfstateを見ると中身が暗号化されております。

terraform.tfstate
{
  "meta":{
    "key_provider.pbkdf2.basic":"~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~"
  },
  "encrypted_data":"~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~",
  "encryption_version":"v0"
}

tofu state show ~でリソースの中身は確認できます。

$ tofu state show aws_s3_bucket.example
#見やすさ優先で一部省略しています
# aws_s3_bucket.example:
resource "aws_s3_bucket" "example" {
    arn                         = "arn:aws:s3:::yuta-opentofu-test-bucket"
    bucket                      = "yuta-opentofu-test-bucket"
    bucket_domain_name          = "yuta-opentofu-test-bucket.s3.amazonaws.com"
    bucket_regional_domain_name = "yuta-opentofu-test-bucket.s3.ap-northeast-1.amazonaws.com"
    force_destroy               = false
    id                          = "yuta-opentofu-test-bucket"
    object_lock_enabled         = false
    region                      = "ap-northeast-1"
    request_payer               = "BucketOwner"
    tags_all                    = {}

    server_side_encryption_configuration {
        rule {
            bucket_key_enabled = false

            apply_server_side_encryption_by_default {
                sse_algorithm = "AES256"
            }
        }
    }
}

AWS KMSの場合

AWS KMSを使った暗号化について紹介します。KMSを使う場合カスタマー管理型のキーを作成し、そのキーを指定します。

カスタマーキー作成

key_provider識別子と中のプロパティが変わりますので注意です。

encrypt.tf
terraform {
  encryption {
    key_provider "aws_kms" "basic" {
      kms_key_id = "<your-own-customer-managed-key-id>"
      region     = "ap-northeast-1"
      key_spec   = "AES_256"
    }
    method "aes_gcm" "my_method" {
      keys = key_provider.aws_kms.basic
    }
    state {
      method = method.aes_gcm.my_method
    }
  }
}
terraform.tfstate
{
  "meta":{
    "key_provider.aws_kms.basic":"~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~"
  },
  "encrypted_data":"~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~",
  "encryption_version":"v0"
}

既存Stateファイルの暗号化

すでに平文で保存されているStateファイルを暗号化する方法もOpenTofuは提供しています。
先ほどのencrypt.tfstateブロックに以下を追加します。

encrypt.tf
terraform {
  encryption {
    key_provider "aws_kms" "basic" {
      kms_key_id = "<your-own-customer-managed-key-id>"
      region     = "ap-northeast-1"
      key_spec   = "AES_256"
    }
    method "aes_gcm" "my_method" {
      keys = key_provider.aws_kms.basic
    }
    state {
      method = method.aes_gcm.my_method
      #fallbackを追加
      fallback {}
    }
  }
}

追加後にtofu applyを実行すると暗号化されます。
またfallbackブロックの中にmethodプロパティを入れると暗号化されたStateファイルを復号化できます。

encrypt.tf
terraform {
  encryption {
    key_provider "aws_kms" "basic" {
      kms_key_id = "<your-own-customer-managed-key-id>"
      region     = "ap-northeast-1"
      key_spec   = "AES_256"
    }
    method "aes_gcm" "my_method" {
      keys = key_provider.aws_kms.basic
    }
    state {
      fallback {
        #fallbackで囲む
        method = method.aes_gcm.my_method
      }
    }
  }
}

Removed block

RemovedブロックはOpenTofuで作成されたリソースをStateファイルから除外してくれます。

Terraformにもterraform state rm ~でStateファイルからリソースを除外してくれるサブコマンドはあります。
ですが、terraform state rm ~は1つのリソースしか削除できず複数のリソースを削除してくれません。

$ terraform state list ← Stateファイルに複数リソースを管理している。
aws_s3_bucket.example
local_file.test

$ terraform state rm  aws_s3_bucket.example local_file.text                                                                                    
Removed aws_s3_bucket.example
Successfully removed 1 resource instance(s). ← 最初のリソースしか削除してくれない。

$ terraform state list
local_file.test

Removedブロックを使えば複数のリソースを一括で除外してくれます。

Removedブロック使い方

手順は以下の通りです。

  1. 設定ファイルからリソース記述を削除する
  2. Stateファイルから除外したいリソースをremovedブロックで囲む

例として以下2つのリソースを作成していたとします。

resources.tf
resource "aws_s3_bucket" "example" {
  bucket = "yuta-opentofu-test-bucket"
}

resource "local_file" "test" {
  content  = "Hello world!"
  filename = "test.txt"
}

resources.tfファイルを削除し、removed.tfに除外リソースを指定します。

removed.tf
removed {
  from = local_file.test
}
removed {
  from = aws_s3_bucket.example
}

tofu planを実行するとStateファイルから除外しますが、リソースを削除しないメッセージが表示されます。

$ tofu plan

OpenTofu used the selected providers to generate the following execution plan. Resource actions are indicated with the following symbols:
  . forget

saOpenTofu will perform the following actions:

  # aws_s3_bucket.example will be removed from the OpenTofu state but will not be destroyed ← Stateファイルから除外するがリソースを削除しない。
  . resource "aws_s3_bucket" "example" {
    arn                         = "arn:aws:s3:::yuta-opentofu-test-bucket"
    bucket                      = "yuta-opentofu-test-bucket"
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
  # local_file.test will be removed from the OpenTofu state but will not be destroyed ← Stateファイルから除外するがリソースを削除しない。
  . resource "local_file" "test" {
    content              = "Hello world!"

tofu applyを実行してもリソースは残り続けます。

$ tofu apply


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

Do you want to perform these actions?
  OpenTofu will perform the actions described above.
  Only 'yes' will be accepted to approve.

  Enter a value: yes

Apply complete! Resources: 0 added, 0 changed, 0 destroyed. ← リソースは削除されない。

その他の追加機能

おまけとしてOpenTofuオリジナルの組み込み関数として追加されたいくつかを紹介します。

cidrcontains

https://1-7-0-alpha1.opentofu.pages.dev/docs/language/functions/cidrcontains/

cidrcontains関数は定義されたIPアドレスレンジの中に指定したIPアドレスもしくはアドレスレンジが含まれているかを判定してくれます。
tofu consoleコマンドでインタラクティブモードにした状態で、関数を使えばtrueかfalseを返してくれます。

$ tofu console
> cidrcontains("192.168.2.0/20", "192.168.2.1")
true
> cidrcontains("192.168.2.0/20", "192.168.2.0/22")
true
> cidrcontains("192.168.2.0/20", "192.126.2.1")
false
> cidrcontains("192.168.2.0/20", "192.126.2.0/18")
false
> cidrcontains("fe80::/48", "fe80::1")
true
> cidrcontains("fe80::/48", "fe81::1")
false

templatestring

https://1-7-0-alpha1.opentofu.pages.dev/docs/language/functions/templatestring/

templatestring関数はtemplatefile関数[6]に似た関数で、プレースホルダーを指定されたテンプレート変数のセットからの値で置き換えることにより、テンプレートとしての文字列レンダリングを可能にします。

output.tf
output "result" {
  value = templatestring("Hello, $${name}!", { name = "Alice" })
}
$ tofu apply

Outputs:

result = "Hello, Alice!"

所感

OpenTofuの新機能について紹介しました。
ver1.7はα版リリースのためまだまだ検証する段階ですが、Stateファイル暗号化機能はTerraformにはない独自の強みを持った機能だと思います。

正式版も来月くらいにはリリースされると思いますので、また改めて検証してみたいと思います。

参考文献

https://zenn.dev/yukionodera/articles/opentofu-ga-v1-6

脚注
  1. https://opentofu.org/blog/opentofu-is-going-ga/ ↩︎

  2. 2024年3月時点 ↩︎

  3. https://ja.wikipedia.org/wiki/PBKDF2 ↩︎

  4. https://aws.amazon.com/jp/kms/ ↩︎

  5. https://www.mbsd.jp/research/20200901/aes-gcm/ ↩︎

  6. https://developer.hashicorp.com/terraform/language/functions/templatefile ↩︎

GitHubで編集を提案

Discussion

ar1ar1

「暗号化方式はAWS-GCM[5]のみになります」ってあったので何だそれはっておもってマジしらべてしまった。ちゃんと出典あって助かります。