※この記事はミクシィグループ Advent Calendar 2021の11日目の記事です
先日弊プロジェクトで作成している Terraform コードを、最新の 1.0.11 にアップグレードしました。
その際に、バージョンを上げようとすると謎の差分が発生し、どういうこっちゃ?ということで調べた時のメモ。
要約
- Terraform コードを 0.14.11 -> 1.0.11 にアップグレードしようと plan を実行したところ、謎の差分が発生
- 調べると、 AWS Security Manager を使用して設定した値で差分が発生している
- どうも 0.15 以降は provider_sensitive_attrs の機能があり、sensitive な値を変数に入れて使うと、使っている resource の値も sensitive 扱いになってくれるらしい
- sensitive の扱いが変更されると、物によっては Terraform はそれを resource の差分として認識する(っぽい)
- というわけでアップグレードに伴って変更される resource はなかったので、みんな幸せ
発端
事の起こりは Terraform をアップグレードしようと Terraform の required_version を変更しただけの簡単な PR を作成した時に発生。
CodeBuild で実行した terraform plan 結果に謎の差分が発生。
差分の発生箇所は AWS WAF で設定している rule。
そして肝心の差分の内容は、
~ rule {
# At least one attribute in this block is (or was) sensitive,
# so its contents will not be displayed.
}
sensitive だから差分は見せられないときてる。
さて困った
調査
なんとか差分を調べる方法がないかと思ってググってみると、以下のフォーラムの投稿が見つかる。
https://discuss.hashicorp.com/t/how-to-show-sensitive-values/24076
それによると、下記のコマンドを実行すれば詳細な plan の実行計画を json として吐き出すことができて、差分を確認できるとのこと。
terraform plan -out=tfplan
terraform show -json tfplan
json の中身は以下の公式のドキュメントで詳しく説明されている。
https://www.terraform.io/docs/internals/json-format.html
これによると、 terraform plan で出力した時の構成は大まかには以下の通り。
- 現在 Terraform で管理している resources の状態(Values Representation)
- 現在の Terraform の設定(Configuration Representation)
- 今回の変更点(Change Representation)
この中の Change Representation の中に、before / after という項目でそれぞれ変更点が詳細に記述される。
今回の差分はここの before と after を見比べれば良さそう。
なので、before と after を抜き出して diff をとってみる。
14c14
< "before": {
---
> "after": {
37c37
< "custom_request_handling": []
---
> "custom_request_handling": null
116c116
< "before_sensitive": {
---
> "after_sensitive": {
132,134c132
< {
< "custom_request_handling": []
< }
---
> {}
187c185
< },
---
> }
203c201
< "before": {
---
> "after": {
305c303,304
< "before_sensitive": {
---
> "after_unknown": {},
> "after_sensitive": {
316,368c315
< "rule": [
< {
< "action": [
< {
< "allow": [
< {}
< ],
< "block": [],
< "count": []
< }
< ],
< "override_action": [],
< "statement": [
< {
< "and_statement": [],
< "byte_match_statement": [
< {
< "field_to_match": [
< {
< "all_query_arguments": [],
< "body": [],
< "method": [],
< "query_string": [],
< "single_header": [
< {}
< ],
< "single_query_argument": [],
< "uri_path": []
< }
< ],
< "text_transformation": [
< {}
< ]
< }
< ],
< "geo_match_statement": [],
< "ip_set_reference_statement": [],
< "managed_rule_group_statement": [],
< "not_statement": [],
< "or_statement": [],
< "rate_based_statement": [],
< "regex_pattern_set_reference_statement": [],
< "rule_group_reference_statement": [],
< "size_constraint_statement": [],
< "sqli_match_statement": [],
< "xss_match_statement": []
< }
< ],
< "visibility_config": [
< {}
< ]
< }
< ],
---
> "rule": true,
374c321
< },
---
> }
これをみると、before_sensitive にある WAF の rule がまるっと after_sensitive で true に置き換わっていることがわかる。
before/after_sensive については、
// "before_sensitive" and "after_sensitive" are object values with similar
// structure to "before" and "after", but with all sensitive leaf values
// replaced with true, and all non-sensitive leaf values omitted.
とあり、どうやら sensitive 扱いになるパラメータは、すべて true という値になる模様。
つまり、AWS WAF の rule がまるっと sensitive 扱いされたということがわかる。
はて、 rule に sensitive なデータなんかあったっけ…? と思ったのだが、ここで rule に、AWS Secret Manager で取得した値を入れていたことを思い出す。
今回のシステムは、固定の値をリクエストヘッダーにもつシステムからのみを許可する WAF の rule を設定しており、その固定の値を Secret Manager で管理していたのでした。
0.15 で導入された provider_sensitive_attrs
となると、アップグレードに伴って sensitive value の扱いが変わったのかしら?と思って調べると、 0.15 で provider_sensitive_attrs というのが GA になったことがわかる。
https://dev.classmethod.jp/articles/terraform-015/#toc-4
provider_sensitive_attrs というのは、ざっくりいうと sensitive な値を使っている resource も同様に sensitive 扱いにするよ!という機能。
元々は sensitive な値を変数に入れて使いまわしていたりすると、 terraform plan とかで sensitive の値がプレーンテキストとして表示されちゃうという問題があり、それを解決するために導入された機能とのこと。
Security Manager の値は元々 sensitive 扱いなので、これにより WAF の rule がまるっと sensitive 扱いにされたことがわかる。
resource の変更?
実は、通常は sensitive 扱いの変更だけなら、下記のような The value is unchanged.
表示が出るようになっているらしい。
# Warning: this block will be marked as sensitive and will not
# display in UI output after applying this change. The value is unchanged. <= ココ!
~ rule {
# At least one attribute in this block is (or was) sensitive,
# so its contents will not be displayed.
}
この文字が出ていれば値は変更されないことがわかるのだが、今回はこれが表示されていなかった。
どうも resource によっては The value is unchanged.
の文字が表示されないことがあるっぽい?
結局どうしたか
The value is unchanged.
の表示がない上に、今回の環境が production 環境だったので、エイヤッと apply するのはさすがにちょっと怖かった。
そのため、今回は問題となる WAF と Secret Manager をだけを作成する簡単な Terraform を作成し、実際にどうなるか検証した。
検証
まずは 0.14.11 で作成。
terraform {
required_version = "~> 0.14.11"
}
provider "aws" {
profile = "test"
region = "ap-northeast-1"
}
provider "aws" {
profile = "test"
region = "us-east-1"
alias = "virginia"
}
data "aws_secretsmanager_secret" "secret" {
name = "test"
}
data "aws_secretsmanager_secret_version" "secret" {
secret_id = data.aws_secretsmanager_secret.secret.id
}
resource "aws_wafv2_web_acl" "waf" {
provider = aws.virginia
name = "test-waf-allow-header"
scope = "CLOUDFRONT"
description = "test"
tags = {}
visibility_config {
cloudwatch_metrics_enabled = false
metric_name = "test-waf-allow-header"
sampled_requests_enabled = false
}
default_action {
block {}
}
rule {
name = "test-waf-rule-allow-header"
priority = 1
visibility_config {
cloudwatch_metrics_enabled = false
metric_name = "test-waf-rule-allow-header"
sampled_requests_enabled = false
}
action {
allow {}
}
statement {
byte_match_statement {
field_to_match {
single_header {
name = "x-cdn-key"
}
}
positional_constraint = "EXACTLY"
search_string = jsondecode(data.aws_secretsmanager_secret_version.secret.secret_string)["x-cdn-key"]
text_transformation {
priority = 0
type = "NONE"
}
}
}
}
}
作った main.tf を apply。
% terraform apply
An execution plan has been generated and is shown below.
Resource actions are indicated with the following symbols:
+ create
Terraform will perform the following actions:
# aws_wafv2_web_acl.waf will be created
+ resource "aws_wafv2_web_acl" "waf" {
+ arn = (known after apply)
+ capacity = (known after apply)
+ description = "test"
+ id = (known after apply)
+ lock_token = (known after apply)
+ name = "test-waf-allow-header"
+ scope = "CLOUDFRONT"
+ tags_all = (known after apply)
+ default_action {
+ block {
}
}
+ rule {
+ name = "test-waf-rule-allow-header"
+ priority = 1
+ action {
+ allow {
}
}
+ statement {
+ byte_match_statement {
+ positional_constraint = "EXACTLY"
+ search_string = "hogehoge"
+ field_to_match {
+ single_header {
+ name = "x-cdn-key"
}
}
+ text_transformation {
+ priority = 0
+ type = "NONE"
}
}
}
+ visibility_config {
+ cloudwatch_metrics_enabled = false
+ metric_name = "test-waf-rule-allow-header"
+ sampled_requests_enabled = false
}
}
+ visibility_config {
+ cloudwatch_metrics_enabled = false
+ metric_name = "test-waf-allow-header"
+ sampled_requests_enabled = false
}
}
Plan: 1 to add, 0 to change, 0 to destroy.
Do you want to perform these actions?
Terraform will perform the actions described above.
Only 'yes' will be accepted to approve.
Enter a value: yes
aws_wafv2_web_acl.waf: Creating...
aws_wafv2_web_acl.waf: Creation complete after 2s [id=ebd18307-4665-4bfa-8807-a53ae9df4779]
Apply complete! Resources: 1 added, 0 changed, 0 destroyed.
この時に WAF に設定された sensitive な値は以下。
+ search_string = "hogehoge"
required_version を変更する。
diff --git a/main.tf b/main.tf
index 699ddc8..e8af984 100644
--- a/main.tf
+++ b/main.tf
@@ -1,5 +1,5 @@
terraform {
- required_version = "~> 0.14.11"
+ required_version = "~> 1.0.11"
}
provider "aws" {
そして terraform plan。
% terraform plan
aws_wafv2_web_acl.waf: Refreshing state... [id=ebd18307-4665-4bfa-8807-a53ae9df4779]
Note: Objects have changed outside of Terraform
Terraform detected the following changes made outside of Terraform since the last "terraform apply":
# aws_wafv2_web_acl.waf has been changed
~ resource "aws_wafv2_web_acl" "waf" {
id = "ebd18307-4665-4bfa-8807-a53ae9df4779"
name = "test-waf-allow-header"
+ tags = {}
# (6 unchanged attributes hidden)
# (3 unchanged blocks hidden)
}
Unless you have made equivalent changes to your configuration, or ignored the relevant attributes
using ignore_changes, the following plan may include actions to undo or respond to these changes.
────────────────────────────────────────────────────────────────────────────────────────────────────
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:
# aws_wafv2_web_acl.waf will be updated in-place
~ resource "aws_wafv2_web_acl" "waf" {
id = "ebd18307-4665-4bfa-8807-a53ae9df4779"
name = "test-waf-allow-header"
tags = {}
# (6 unchanged attributes hidden)
# Warning: this block will be marked as sensitive and will not
# display in UI output after applying this change.
~ rule {
# At least one attribute in this block is (or was) sensitive,
# so its contents will not be displayed.
}
# (2 unchanged blocks hidden)
}
Plan: 0 to add, 1 to change, 0 to destroy.
────────────────────────────────────────────────────────────────────────────────────────────────────
Note: You didn't use the -out option to save this plan, so Terraform can't guarantee to take exactly
these actions if you run "terraform apply" now.
やはり The value is unchanged.
の表記はない。
このまま apply すると値はどうなるか確認する。
% terraform apply [ main ]
aws_wafv2_web_acl.waf: Refreshing state... [id=ebd18307-4665-4bfa-8807-a53ae9df4779]
Note: Objects have changed outside of Terraform
Terraform detected the following changes made outside of Terraform since the last "terraform apply":
# aws_wafv2_web_acl.waf has been changed
~ resource "aws_wafv2_web_acl" "waf" {
id = "ebd18307-4665-4bfa-8807-a53ae9df4779"
name = "test-waf-allow-header"
+ tags = {}
# (6 unchanged attributes hidden)
# (3 unchanged blocks hidden)
}
Unless you have made equivalent changes to your configuration, or ignored the relevant attributes
using ignore_changes, the following plan may include actions to undo or respond to these changes.
────────────────────────────────────────────────────────────────────────────────────────────────────
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:
# aws_wafv2_web_acl.waf will be updated in-place
~ resource "aws_wafv2_web_acl" "waf" {
id = "ebd18307-4665-4bfa-8807-a53ae9df4779"
name = "test-waf-allow-header"
tags = {}
# (6 unchanged attributes hidden)
# Warning: this block will be marked as sensitive and will not
# display in UI output after applying this change.
~ rule {
# At least one attribute in this block is (or was) sensitive,
# so its contents will not be displayed.
}
# (2 unchanged blocks hidden)
}
Plan: 0 to add, 1 to change, 0 to destroy.
Do you want to perform these actions?
Terraform will perform the actions described above.
Only 'yes' will be accepted to approve.
Enter a value: yes
aws_wafv2_web_acl.waf: Modifying... [id=ebd18307-4665-4bfa-8807-a53ae9df4779]
aws_wafv2_web_acl.waf: Modifications complete after 0s [id=ebd18307-4665-4bfa-8807-a53ae9df4779]
Apply complete! Resources: 0 added, 1 changed, 0 destroyed.
AWS コンソール上で WAF に設定された値を見ると、変更はなさそう。
CloudTrail 上も特に何も記録されておらず、AWS 上の変更は何もされていなさそう。
以上より問題ないと判断して、applyを実行しました。
特に問題は発生しなかったので、良かったですね。
おわりに
この記事が書いているうちに Terraform 1.1.0 が出ちゃったので、またアップグレードしないといけない