【Terraform】moduleやlocal変数を使用してみる。ついでにtfsecも使用してみる。
お久しぶり(?)です!引っ越しが落ち着き、いろんな人をうちに招待しまくっていてブログをさぼってしまいました。お許しください。
本日は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運用の一つの選択肢として参考にしていただければとおもいます!
ここまで読んでいただきありがとうございました!