kakakakakku blog

Weekly Tech Blog: Keep Learning!

tflint-ruleset-aws の Deep Checking で AWS アカウントのリソースを考慮した静的解析を実行する

Terraform プロジェクトの静的解析ツールとして TFLintTFLint Ruleset for terraform-provider-aws (tflint-ruleset-aws) をよく使っている💡

最近 tflint-ruleset-aws のコードを読んでいたら「Deep Checking」という機能があることを知って,試してみることにした.簡単に言うと,実際に AWS アカウントからリソース情報を取得して「plan は成功するけど apply に失敗する」という Terraform であるあるなパターンを検出できる.これは嬉しいやつかも!?

github.com

ルール一覧

Deep Checking のルール一覧は README に載っていて,表の Deep に ✔ が付いている.

github.com

👾 .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_idigw-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_gatewayaws_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_groupssg-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_groupaws_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_gatewayaws_alb_invalid_security_group という2つのルールを試してみた❗️

あくまで個人的な予想だけど,Deep Checking の使いどころとしては「歴史的経緯があって id ベタ書きで必要最低限の Terraform コードを書かないといけないのに id を間違えて plan で気付けず apply で失敗する問題」を解消したいとき😀?他の Deep Checking も試してみたいと思う.

あと期待通りに検出できないときはコードを読んで対象リソースを確認すると良いと思う.今回で言うと aws_routeaws_route_tableaws_albaws_lb などの違いがあって少しハマってしまった.