OpenTofuでStateファイルの暗号化が実装されました
序論
以前紹介した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]
本番環境での利用は推奨されておらずあくまでも非本番環境で使い、フィードバックを募集している段階となっています。
各種パッケージマネージャーからもインストールできず、GitHub Releaseページからダウンロードすることのみ利用できます。
追加機能について
1.7では新機能や新しい関数などTerraformにはないものが組み込まれています。
主な追加機能
- Stateファイルの暗号化
- Removedブロック
- 独自の組み込み関数
Stateファイルの暗号化は1.7の大きな目玉として注目されており、すでに公式ドキュメントも充実しています。
新規に作られる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
#AWSプロバイダーインストール
terraform {
required_providers {
aws = {
source = "hashicorp/aws"
version = "~> 5.0"
}
}
}
provider "aws" {
region = "ap-northeast-1"
}
#S3作成ファイル
resource "aws_s3_bucket" "example" {
bucket = "yuta-opentofu-test-bucket"
}
PBKDF2の場合
PBKDF2の場合、パスフレーズをHCLコード内に記載します。
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
を見ると中身が暗号化されております。
{
"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
識別子と中のプロパティが変わりますので注意です。
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
}
}
}
{
"meta":{
"key_provider.aws_kms.basic":"~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~"
},
"encrypted_data":"~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~",
"encryption_version":"v0"
}
既存Stateファイルの暗号化
すでに平文で保存されているStateファイルを暗号化する方法もOpenTofuは提供しています。
先ほどのencrypt.tf
のstate
ブロックに以下を追加します。
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ファイルを復号化できます。
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ブロック使い方
手順は以下の通りです。
- 設定ファイルからリソース記述を削除する
- Stateファイルから除外したいリソースを
removed
ブロックで囲む
例として以下2つのリソースを作成していたとします。
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 {
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
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
templatestring
関数はtemplatefile
関数[6]に似た関数で、プレースホルダーを指定されたテンプレート変数のセットからの値で置き換えることにより、テンプレートとしての文字列レンダリングを可能にします。
output "result" {
value = templatestring("Hello, $${name}!", { name = "Alice" })
}
$ tofu apply
Outputs:
result = "Hello, Alice!"
所感
OpenTofuの新機能について紹介しました。
ver1.7はα版リリースのためまだまだ検証する段階ですが、Stateファイル暗号化機能はTerraformにはない独自の強みを持った機能だと思います。
正式版も来月くらいにはリリースされると思いますので、また改めて検証してみたいと思います。
参考文献
Discussion
「暗号化方式はAWS-GCM[5]のみになります」ってあったので何だそれはっておもってマジしらべてしまった。ちゃんと出典あって助かります。
誤字失礼しました。修正しておきます