
Terraform プロジェクトの静的解析ツールとして TFLint と TFLint Ruleset for terraform-provider-aws (tflint-ruleset-aws) をよく使っている💡
最近 tflint-ruleset-aws のコードを読んでいたら「Deep Checking」という機能があることを知って,試してみることにした.簡単に言うと,実際に AWS アカウントからリソース情報を取得して「plan は成功するけど apply に失敗する」という Terraform であるあるなパターンを検出できる.これは嬉しいやつかも!?
ルール一覧
Deep Checking のルール一覧は README に載っていて,表の Deep に ✔ が付いている.
👾 .tflint.hcl
Deep Checking を使うには以下のようにプラグイン設定で deep_check = true を追加すれば OK👌
plugin "terraform" { enabled = true preset = "recommended" } plugin "aws" { enabled = true deep_check = true version = "0.45.0" source = "github.com/terraform-linters/tflint-ruleset-aws" }
実際に AWS アカウントからリソース情報を取得するため認証情報も必要になる.詳しくはドキュメントに載っている📝ちなみに必要最低限必要な IAM ポリシーもドキュメントに載っていて,参照系の権限が必要になる感じだった.
{ "Version": "2012-10-17", "Statement": [ { "Effect": "Allow", "Action": [ "ec2:DescribeInstances", "ec2:DescribeSecurityGroups", "ec2:DescribeSubnets", "ec2:DescribeKeyPairs", "ec2:DescribeEgressOnlyInternetGateways", "ec2:DescribeInternetGateways", "ec2:DescribeNatGateways", "ec2:DescribeNetworkInterfaces", "ec2:DescribeRouteTables", "ec2:DescribeVpcPeeringConnections", "rds:DescribeDBSubnetGroups", "rds:DescribeOptionGroups", "rds:DescribeDBParameterGroups", "elasticache:DescribeCacheParameterGroups", "elasticache:DescribeCacheSubnetGroups", "iam:ListInstanceProfiles" ], "Resource": "*" } ] }
aws_route_invalid_gateway を試す
まずは Route Table に誤った Internet Gateway ID を指定していることを検出できる aws_route_invalid_gateway を試す.
以下のように gateway_id に igw-99999999999999999 という値を設定しておく.実際に Internet Gateway が存在しなくても plan 自体は通ってしまう.
resource "aws_route_table" "public_1a" { vpc_id = aws_vpc.main.id } resource "aws_route" "public_1a_igw" { route_table_id = aws_route_table.public_1a.id destination_cidr_block = "0.0.0.0/0" gateway_id = "igw-99999999999999999" } resource "aws_route_table" "public_1c" { vpc_id = aws_vpc.main.id } resource "aws_route" "public_1c_igw" { route_table_id = aws_route_table.public_1c.id destination_cidr_block = "0.0.0.0/0" gateway_id = "igw-99999999999999999" }
次に Deep Checking を有効化して TFLint を実行するとちゃんとエラーになる🚨
$ tflint --config .tflint.hcl 2 issue(s) found: Error: "igw-99999999999999999" is invalid internet gateway ID. (aws_route_invalid_gateway) on vpc.tf line 32: 32: gateway_id = "igw-99999999999999999" Error: "igw-99999999999999999" is invalid internet gateway ID. (aws_route_invalid_gateway) on vpc.tf line 42: 42: gateway_id = "igw-99999999999999999"
注意点としては aws_route_invalid_gateway は aws_route リソースに特化した実装になっていて(aws_route_invalid_gateway.go 参照),aws_route_table リソースだと検出できなかった.
resource "aws_route_table" "public_1a" { vpc_id = aws_vpc.main.id route { cidr_block = "0.0.0.0/0" gateway_id = "igw-99999999999999999" } } resource "aws_route_table" "public_1c" { vpc_id = aws_vpc.main.id route { cidr_block = "0.0.0.0/0" gateway_id = "igw-99999999999999999" } }
とは言え Route Table を実装するときに gateway_id をベタ書きする機会は少ないと思う.
通常は Internet Gateway も Terraform で管理しているだろうから aws_internet_gateway リソースで参照するし,もし Terraform 管理外の既存リソースだとしても,以下のように aws_internet_gateway データソースで参照すれば plan 時点でエラーが出て気付ける.
data "aws_internet_gateway" "main" { internet_gateway_id = "igw-99999999999999999" } resource "aws_route_table" "public_1a" { vpc_id = aws_vpc.main.id } resource "aws_route" "public_1a_igw" { route_table_id = aws_route_table.public_1a.id destination_cidr_block = "0.0.0.0/0" gateway_id = data.aws_internet_gateway.main.id } resource "aws_route_table" "public_1c" { vpc_id = aws_vpc.main.id } resource "aws_route" "public_1c_igw" { route_table_id = aws_route_table.public_1c.id destination_cidr_block = "0.0.0.0/0" gateway_id = data.aws_internet_gateway.main.id }
実際に実行すると Error: no matching EC2 Internet Gateway found というエラーが出る🚨これなら Deep Checking は使わなくても良さそう.
$ terraform plan (中略) ╷ │ Error: no matching EC2 Internet Gateway found │ │ with data.aws_internet_gateway.main, │ on vpc.tf line 21, in data "aws_internet_gateway" "main": │ 21: data "aws_internet_gateway" "main" { │ ╵ Operation failed: failed running terraform plan (exit 1)
aws_alb_invalid_security_group を試す
今度は ALB に誤ったセキュリティグループ ID を指定していることを検出できる aws_alb_invalid_security_group を試す.
以下のように security_groups に sg-99999999999999999 という値を設定しておく.実際にセキュリティグループが存在しなくても plan 自体は通ってしまう.
resource "aws_alb" "main" { name = "sandbox-deep-checking" internal = false load_balancer_type = "application" security_groups = [ "sg-99999999999999999" ] subnets = [ aws_subnet.public_1a.id, aws_subnet.public_1c.id ] }
次に Deep Checking を有効化して TFLint を実行するとちゃんとエラーになる🚨
tflint --config .tflint.hcl 1 issue(s) found: Error: "sg-99999999999999999" is invalid security group. (aws_alb_invalid_security_group) on test.tf line 7: 7: "sg-99999999999999999"
注意点としては aws_alb_invalid_security_group は aws_alb リソースに特化した実装になっていて(aws_alb_invalid_security_group.go 参照),aws_lb リソースだと検出できなかった.ドキュメントには以下のように書いてあって,僕自身は普段 aws_lb を使うので,最初コードを書いたときに検出できなくてハマった😇
aws_alb is known as aws_lb. The functionality is identical.
とは言え ALB を実装するときに security_groups をベタ書きする機会は少ないと思う.そこは aws_route_invalid_gateway と同じ感想だった \( 'ω')/
まとめ
tflint-ruleset-aws の「Deep Checking」という機能を知って,今回は aws_route_invalid_gateway と aws_alb_invalid_security_group という2つのルールを試してみた❗️
あくまで個人的な予想だけど,Deep Checking の使いどころとしては「歴史的経緯があって id ベタ書きで必要最低限の Terraform コードを書かないといけないのに id を間違えて plan で気付けず apply で失敗する問題」を解消したいとき😀?他の Deep Checking も試してみたいと思う.
あと期待通りに検出できないときはコードを読んで対象リソースを確認すると良いと思う.今回で言うと aws_route と aws_route_table・aws_alb と aws_lb などの違いがあって少しハマってしまった.