【Terraform】moduleやlocal変数を使用してみる。ついでにtfsecも使用してみる。

10月 11, 2023

お久しぶり(?)です!引っ越しが落ち着き、いろんな人をうちに招待しまくっていてブログをさぼってしまいました。お許しください。


本日はterraformのmoduleやlocal変数をしようしてAWSリソースを作成してみようと思います。ついでにtfsecを使用したterraformの静的セキュリティスキャンもやってみます。


別々の記事にしようかと思ったのですが、それぞれのボリュームがあんまりなかったので今回は豪華二本立てでお送りしたいと思います。

今回作成するリソース

今回作成するのはVPCとセキュリティグループです。セキュリティ脆弱性を手っ取り早く再現できそうだったのでセキュリティグループを選びました。セキュリティグループの作成にはVPCが必要なのでついでに作成してやります。

ディレクトリ構造

.
├── backend.tf
├── locals.tf
├── module.tf
└── network
    ├── network.tf
    └── variables.tf

ではそれぞれみていきましょう。

terraform init

まずはterraformを使用するための準備を行いましょう。backend.tfを用意します。

terraform {
  backend "s3" {
    bucket  = "tfstateファイルを配置するS3バケット名"
    encrypt = true
    key     = "tfsec/default.tfstate"
    region  = "ap-northeast-1"
  }
}

provider "aws" {
  region = "ap-northeast-1"
}

terraformはtfstateファイルで現在のリソース状態を管理します。ローカルにもこのファイルをおいておくことは可能なのですがS3に配置すると複数のクライアントから同じstateを参照できるので基本的にS3においておくべきです。


あらかじめS3バケットを用意しておき、上記ファイルに指定してください。keyにはS3バケット内のどこにファイルを置いておくかを指定します。上記の場合、"terraform init"コマンドを実行した際、S3バケットにtfsecフォルダとdefault.tfstateファイルが作成されます。


このdefault.tfstateにterraformで作成されたリソース情報が保存されていきます。


S3バケットとbackend.tfが用意できましたら、initコマンドでinitializeしましょう!

# デフォルトのクレデンシャル
$ terraform terraform init
# デフォルト以外ののクレデンシャル
$ AWS_PROFILE=任意のProfile terraform init

これで準備は完了です!

リソースを作成するtfファイルを用意する

次にVPCとセキュリティグループを作成するtfファイルを作成していきます。


networkディレクトリを作成し、network.tfを作成しましょう。

resource "aws_vpc" "sample" {
  cidr_block           = var.vpc_cidr
  instance_tenancy     = "default"
  enable_dns_hostnames = true
  tags = {
    Name = var.vpc_name
  }
}

resource "aws_security_group" "sample" {
  name   = var.sg_name
  vpc_id = aws_vpc.sample.id

  ingress {
    description = "TLS from VPC"
    from_port   = 443
    to_port     = 443
    protocol    = "tcp"
    cidr_blocks = [aws_vpc.sample.cidr_block]
  }

  egress {
    description = "Anywhere"
    from_port   = 0
    to_port     = 0
    protocol    = "-1"
    cidr_blocks = ["0.0.0.0/0"]
  }
}

VPCのCIDRブロックやリソース名など、様々な値を入れる可能性がある部分は変数化"var.~"しておきます。この時点では特定の値は入れず、あえて変数を通して後から入れることができるようにしておくことで非常に柔軟性のある運用を可能にできます。

次に変数を指定しておきましょう。上記はあくまで変数の値を受け取れるようにしているにすぎず、値を入れる変数はまだ定義されていないのです。同じディレクトリにvariables.tfを作成していきましょう。

variable "vpc_cidr" {}
variable "vpc_name" {}
variable "sg_name" {}

これで変数の準備も完了です。それではmoduleを定義していきましょう。

moduleを定義する

変数を定義しましたが見ての通り中身が何も入っていません。もちろん変数に値を直接代入することも可能ですが、使い回しを可能にするためmodule化しておきましょう。backend.tfと同じ階層に戻り、module.tfを作成していきます。

module "network" {
  source   = "./network"
  vpc_cidr = local.vpc_cidr
  vpc_name = local.vpc_name
  sg_name  = local.sg_name
}

このようにしておくことで複数moduleを作成して環境ごとに異なる変数をいれてリソース作成なんかもできたりします。

ところで、module化をしてもなお、まだ変数の値が"local.~"となっていますね。もちろんmodule内の変数に直接値を指定することも可能ですが、moduleファイルが増えるたびにどこになんの変数とその値が指定されているか分からなくなります。

変数の値はどこか一つのファイルで統括管理できるとうれしいですね、ここで登場するのがlocal変数です。次にlocal変数を定義してみましょう!

local変数を定義する

それではlocal変数を定義してみましょう。locals.tfを作成します。

locals {
  name_prefix = "sample"
  vpc_cidr    = "10.10.0.0/16"
  vpc_name    = "${local.name_prefix}-vpc"
  sg_name     = "${local.name_prefix}-sg"
}

このようにlocal変数を定義しておくことで、各ファイルからここに定義されている変数を通して値を参照することが可能になります!

これで"変数の値を変更するときはlocals.tfを確認すれば良い"という運用が可能になります!また、同じリソース構成を別環境に増やしたいときはmoduleファイルを増やせばいいですね!リソースを作成するファイルはもはや使いまわされるだけの存在となりました!素敵!


あとはplanやapply、destroyして実際にうまくリソースが作成されるか確認、作成、削除を行ってみてください!

番外編!tfsecを利用して静的セキュリティスキャン

ところで、確かにリソースは問題なく作成されましたがセキュリティ上の問題はないのでしょうか?リソースを作成してからAWSのサービスを利用してセキュリティチェックをかけるのも面倒ですし、理想を言えばリソースが実際に作成される前にセキュリティスキャンをしておきたいですね!


こんな時に使えるのがaquasecurity社から提供されているtfsecです!使い方はとっても簡単です。今回作成したものを利用して試しにスキャンしてみましょう!


例えばMacの場合はbrewでインストールするのみです!

$ brew install tfsec

あとはコマンド実行すればスキャンされます!お手軽!

$ tfsec

結果発表ー!

Result #1 CRITICAL Security group rule allows egress to multiple public internet addresses. 
────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────
  network/network.tf:27
   via module.tf:1-6 (module.network)
────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────
   10    resource "aws_security_group" "sample" {
   ..  
   27  [     cidr_blocks = ["0.0.0.0/0"]
   ..  
   29    }
────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────
          ID aws-ec2-no-public-egress-sgr
      Impact Your port is egressing data to the internet
  Resolution Set a more restrictive cidr range

  More Information
  - https://aquasecurity.github.io/tfsec/v1.27.1/checks/aws/ec2/no-public-egress-sgr/
  - https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/security_group
────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────


Result #2 LOW Security group explicitly uses the default description. 
────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────
  network/network.tf:10-29
   via module.tf:1-6 (module.network)
────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────
   10  ┌ resource "aws_security_group" "sample" {
   11  │   name   = var.sg_name
   12  │   vpc_id = aws_vpc.sample.id
   13  │ 
   14  │   ingress {
   15  │     description = "TLS from VPC"
   16  │     from_port   = 443
   17  │     to_port     = 443
   18  └     protocol    = "tcp"
   ..  
────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────
          ID aws-ec2-add-description-to-security-group
      Impact Descriptions provide context for the firewall rule reasons
  Resolution Add descriptions for all security groups

  More Information
  - https://aquasecurity.github.io/tfsec/v1.27.1/checks/aws/ec2/add-description-to-security-group/
  - https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/security_group
  - https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/security_group_rule
────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────


  timings
  ──────────────────────────────────────────
  disk i/o             140.991µs
  parsing              4.138317ms
  adaptation           290.423µs
  checks               39.216651ms
  total                43.786382ms

  counts
  ──────────────────────────────────────────
  modules downloaded   0
  modules processed    2
  blocks processed     9
  files read           5

  results
  ──────────────────────────────────────────
  passed               3
  ignored              0
  critical             1
  high                 0
  medium               0
  low                  1

  3 passed, 2 potential problem(s) detected.

criticalが1件、lowが1件出ましたね!内容を見てみるとやっぱりセキュリティグループが原因みたいで、アウトバウンドがフルオープンになっていることとデフォルトの説明文が使用されているという2件でした。


もちろん検出される内容においても、対応するかどうかは組織の判断になるかとおもいますがかなり親切なセキュリティが行われていることがわかります!


CIに組み込むことでよりセキュリティリスクの低いインフラ構成を実現できそうですね!

まとめ

今回はmoduleやlocal変数を用いてリソースを作成しつつ、tfsecを利用して静的セキュリティスキャンを行ってみました!


terraform運用の一つの選択肢として参考にしていただければとおもいます!


ここまで読んでいただきありがとうございました!

Terraform

Posted by CY