はじめに
セキュリティグループって作るの面倒ですよね。
開発・検証・本番と環境が増えるごとにより面倒に。。
インフラ構築をコード化出来たらなー、ということで Terraform の module を作ってみました。
どうせ作ったなら皆さんの参考になればということで共有します。
バージョンは以下の通り。
このバージョン以外での動作は確認してません。
terraform {
required_version = "1.5.0"
required_providers {
aws = {
source = "hashicorp/aws"
version = "5.3.0"
}
}
}
GitHubにソースコードを上げておりますので、もしよろしければ参考に。
⇒ こちらをクリック
参考にしていただくためにGitHubに残しております。
いかなる損失が発生しても一切責任は負いませんのでよろしくお願いしますね。
module
セキュリティグループ構築用の module について軽く説明します。
ややこしいことは何もやってないです。
一部抜粋して説明していきます。
variables.tf
まずは変数ファイルから。ものすごくシンプル・・・!
#
# aws_security_group
#
variable "description" {
type = string
default = ""
description = "セキュリティグループの説明"
}
variable "vpc_id" {
type = string
default = null
description = "セキュリティグループを作成するVPCのID。デフォルトはリージョンのデフォルトVPC"
}
#
# aws_security_group_rule
#
variable "rule_map" { ※1
type = map(object({
type = string # ingress or egress
from_port = number # 開始ポート
to_port = number # 終了ポート
protocol = string # プロトコル
cidr_blocks = list(string) # IPv4 CIDRブロックのリスト
ipv6_cidr_blocks = list(string) # IPv6 CIDRブロックのリスト
source_security_group_id = string # 送信元セキュリティグループID
description = string # ルールの説明
}))
default = {}
description = "セキュリティグループルールのマップ"
}
※1
variable "rule_map" {・・・}
変数の中で一番ややこしいのがこれ。
map 型で記述したセキュリティグループのルールをこの変数に代入します。
map についての公式ページは こちら
変数 rule_map の型は map(object({・・・}))
なので、
map の value の部分を object型 にする必要があります。
なんか説明だとややこしいので後ほどコードを用いて説明します。。
outputs.tf
特に難しいことはしてないです。
セキュリティグループのIDと名前を返り値にしてます。
output "sg_id" {
value = aws_security_group.this.id
description = "セキュリティグループのID"
}
output "sg_name" {
value = aws_security_group.this.name
description = "セキュリティグループ名"
}
main.tf
aws_security_group_rule リソースが少しややこしいです。
#
# Security Group
#
resource "aws_security_group" "this" {
name = local.sg_name
description = var.description
vpc_id = var.vpc_id
tags = {
Name = local.sg_name
}
}
#
# Security Group Rule
#
resource "aws_security_group_rule" "this" {
for_each = var.rule_map
security_group_id = aws_security_group.this.id
type = each.value.type
from_port = each.value.from_port
to_port = each.value.to_port
protocol = each.value.protocol
cidr_blocks = each.value.cidr_blocks ※1
ipv6_cidr_blocks = each.value.ipv6_cidr_blocks
source_security_group_id = each.value.source_security_group_id
description = each.value.description
}
※1
cidr_blocks
、ipv6_cidr_blocks
、source_security_group_id
のうち、
設定するパラメータは一つのみとなります。
つまり、cidr_blocks
に10.0.0.0/16
などを指定した場合は、残りの2つのパラメータは設定できません。
※ 複数個を同時に設定しようとするとエラーになります。
上記3つのパラメータは、送信元を指定するパラメータなので当然ですね。
module 呼び出し元
module 呼び出し元は、今回サンプルとして2種類のセキュリティグループを作成してます。
1つ目は、IPv4からの通信を許可する場合のセキュリティグループ。
2つ目は、セキュリティグループからの通信許可やアウトバウンドルールを設定したセキュリティグループ。
まずは 1つ目の sample-sg-1 から。
module "sample_sg_1" {
source = "./modules/security_group"
prefix = local.prefix
subname = "sample-sg-1"
# Security Group
description = "sample security group 1"
# security Group Rule
rule_map = {
http = {
type = "ingress"
from_port = 80
to_port = 80
protocol = "TCP"
cidr_blocks = ["0.0.0.0/0"] ※1
ipv6_cidr_blocks = null ※2
source_security_group_id = null ※3
description = "allow http"
}
}
}
※1
cidr_blocks = ["0.0.0.0/0"]
list(string)型で渡す必要があるので、上記のような記述方法になります。
※2
ipv6_cidr_blocks = null
main.tf の※1で説明した通り、3つのパラメータの内の1つしか設定できないため、
設定対象ではないパラメータについては null を渡します。
※3
source_security_group_id = null
network.tf の ※2と同様
続いては2つ目のセキュリティグループです。
module "sample_sg_2" {
source = "./modules/security_group"
prefix = local.prefix
subname = "sample-sg-2"
# Security Group
description = "sample security group 2"
# Security Group Rule
rule_map = {
from_sample = {
type = "ingress"
from_port = 0 ※1
to_port = 0
protocol = -1 ※2
cidr_blocks = null
ipv6_cidr_blocks = null
source_security_group_id = module.sample_sg_1.sg_id ※3
description = "allow all traffic from ${module.sample_sg_1.sg_name}" ※4
}
to_sample = {
type = "egress" ※5
from_port = 0
to_port = 65535 ※6
protocol = "TCP"
cidr_blocks = null
ipv6_cidr_blocks = null
source_security_group_id = module.sample_sg_1.sg_id
description = "allow tcp to ${module.sample_sg_1.sg_name}"
}
}
}
※1
from_port = 0
ポート範囲をすべてにする場合は、from_port と to_port を0
にします。
※2
protocol = -1
プロトコルをすべてにする場合は、protocol を-1
にします。
※3
source_security_group_id = module.sample_sg_1.sg_id
送信元にセキュリティグループIDを設定しています。
sample_sg_1 の sg_id(セキュリティグループID)を指定。
しつこいですがこの場合、cidr_blocks
とipv6_cidr_blocks
は設定できないので null
※4
description = "allow all traffic from ${module.sample_sg_1.sg_name}"
module.sample_sg_1.sg_name でセキュリティグループ名を取得
※3と似たような感じです。
※5
type = "egress"
egress はアウトバウンドルール
※6
to_port = 65535
from_port と to_port が異なる数字の場合は、その範囲のポート番号が許可されます。
from_port = 0 、 to_port = 65535 の場合は0 - 65535
AWS リソース
一つ目のセキュリティグループ。
インバウンドルールは 0.0.0.0/0 からの HTTP:80 の通信を許可しているので想定通り。
二つ目のセキュリティグループ。
インバウンドルールは、sample_sg_1 のセキュリティグループからのすべての通信を許可。
ソース ・ 説明 ともに上手く参照して設定できている。
マネコンから一つずつ作成した場合、送信元のセキュリティグループIDが変わるたびに
インバウンドルールのソースを設定しなおさないといけないが、Terraformだとすべて良きようにやってくれるので凄く楽です。
アウトバウンドルールは、しっかりポートを範囲指定出来ているのでOKそう。
さいごに
最後までご覧いただきありがとうございました。
一度 module を作ってしまえばその後はとても楽なので、ぜひ皆さんも module を活用しましょう!
特にセキュリティグループやIAMロールなどは数が構築する数が多いのでコード化するとかなり楽です。
それにコード化されたインフラは冪等性があるのでオススメ。
今後もGitHubは更新していく予定なので役に立ったら記事にコメントでもしてくれると嬉しくて泣きます。
module の変更案などもコメントしてください。
では、良いTerraformライフをお送りください。