【Terraform】AWS Security Group構築用のmoduleを作成してみた

# はじめに
セキュリティグループって作るの面倒ですよね。
開発・検証・本番と環境が増えるごとにより面倒に。。

インフラ構築をコード化出来たらなー、ということで Terraform の module を作ってみました。
どうせ作ったなら皆さんの参考になればということで共有します。

バージョンは以下の通り。
このバージョン以外での動作は確認してません。

```terraform
terraform {
  required_version = "1.5.0"
  required_providers {
    aws = {
      source  = "hashicorp/aws"
      version = "5.3.0"
    }
  }
}
```
GitHubにソースコードを上げておりますので、もしよろしければ参考に。
⇒ [こちらをクリック](https://github.com/onishi-app/terraform)

参考にしていただくためにGitHubに残しております。
いかなる損失が発生しても一切責任は負いませんのでよろしくお願いしますね。

# module
セキュリティグループ構築用の module について軽く説明します。
ややこしいことは何もやってないです。

一部抜粋して説明していきます。

## variables.tf

まずは変数ファイルから。ものすごくシンプル・・・！

```terraform:module/security_group/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" {    ※１
  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 = "セキュリティグループルールのマップ"
}
```

### ※１
`variable "rule_map" {・・・}`

変数の中で一番ややこしいのがこれ。
map 型で記述したセキュリティグループのルールをこの変数に代入します。
map についての公式ページは [こちら](https://developer.hashicorp.com/terraform/language/functions/map)

変数 rule_map の型は `map(object({・・・}))` なので、
map の value の部分を object型 にする必要があります。
なんか説明だとややこしいので後ほどコードを用いて説明します。。

## outputs.tf
特に難しいことはしてないです。
セキュリティグループのIDと名前を返り値にしてます。

```terraform:module/security_group/outputs.tf
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 リソースが少しややこしいです。

```terraform:module/security_group/main.tf
#
# 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     ※１
  ipv6_cidr_blocks         = each.value.ipv6_cidr_blocks
  source_security_group_id = each.value.source_security_group_id
  description              = each.value.description
}
```

### ※１
`cidr_blocks`、`ipv6_cidr_blocks`、`source_security_group_id`のうち、
設定するパラメータは一つのみとなります。

つまり、`cidr_blocks`に`10.0.0.0/16`などを指定した場合は、残りの２つのパラメータは設定できません。
※ 複数個を同時に設定しようとするとエラーになります。

上記３つのパラメータは、**送信元**を指定するパラメータなので当然ですね。

# module 呼び出し元
module 呼び出し元は、今回サンプルとして２種類のセキュリティグループを作成してます。
１つ目は、IPv4からの通信を許可する場合のセキュリティグループ。
２つ目は、セキュリティグループからの通信許可やアウトバウンドルールを設定したセキュリティグループ。

まずは １つ目の sample-sg-1 から。

```terraform:network.tf
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"]    ※１
      ipv6_cidr_blocks         = null      ※２
      source_security_group_id = null      ※３
      description              = "allow http"
    }
  }
}
```

### ※１
`cidr_blocks = ["0.0.0.0/0"]`

list(string)型で渡す必要があるので、上記のような記述方法になります。

### ※２
`ipv6_cidr_blocks = null`

main.tf の※１で説明した通り、３つのパラメータの内の１つしか設定できないため、
設定対象ではないパラメータについては null を渡します。

### ※３
`source_security_group_id = null`

network.tf の ※２と同様

<hr>

続いては２つ目のセキュリティグループです。

```terraform:network.tf
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     ※１
      to_port                  = 0
      protocol                 = -1    ※２
      cidr_blocks              = null
      ipv6_cidr_blocks         = null
      source_security_group_id = module.sample_sg_1.sg_id    ※３
      description              = "allow all traffic from ${module.sample_sg_1.sg_name}"   ※４
    }
    to_sample = {
      type                     = "egress"    ※５
      from_port                = 0
      to_port                  = 65535       ※６
      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}"
    }
  }
}
```

### ※１
`from_port = 0`

ポート範囲を**すべて**にする場合は、from_port と to_port を`0`にします。

### ※２
`protocol = -1`

プロトコルを**すべて**にする場合は、protocol を`-1`にします。

### ※３
`source_security_group_id = module.sample_sg_1.sg_id`

送信元にセキュリティグループIDを設定しています。
sample_sg_1 の sg_id（セキュリティグループID）を指定。

しつこいですがこの場合、`cidr_blocks`と`ipv6_cidr_blocks`は設定できないので null

### ※４
`description = "allow all traffic from ${module.sample_sg_1.sg_name}"`

module.sample_sg_1.sg_name でセキュリティグループ名を取得
※３と似たような感じです。

### ※５
`type = "egress"`

egress はアウトバウンドルール

### ※６
`to_port = 65535`

from_port と to_port が異なる数字の場合は、その範囲のポート番号が許可されます。
from_port = 0 、 to_port = 65535 の場合は`0 - 65535`

# AWS リソース
一つ目のセキュリティグループ。

インバウンドルールは 0.0.0.0/0 からの HTTP:80 の通信を許可しているので想定通り。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/797483/cd520141-aeba-3f40-1429-23be3dfcff70.png)

アウトバウンドルールは設定していないので空でOK
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/797483/e10f5e35-b090-9e47-d9ac-ac473e78a5cc.png)


二つ目のセキュリティグループ。

インバウンドルールは、sample_sg_1 のセキュリティグループからのすべての通信を許可。
ソース ・ 説明 ともに上手く参照して設定できている。

マネコンから一つずつ作成した場合、送信元のセキュリティグループIDが変わるたびに
インバウンドルールのソースを設定しなおさないといけないが、Terraformだとすべて良きようにやってくれるので凄く楽です。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/797483/a5380fe3-d061-ea6a-df47-bd7d5171fd84.png)

アウトバウンドルールは、しっかりポートを範囲指定出来ているのでOKそう。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/797483/81866471-8d25-4d52-ebca-628caac408ba.png)

# さいごに

最後までご覧いただきありがとうございました。
一度 module を作ってしまえばその後はとても楽なので、ぜひ皆さんも module を活用しましょう！

特にセキュリティグループやIAMロールなどは数が構築する数が多いのでコード化するとかなり楽です。
それにコード化されたインフラは冪等性があるのでオススメ。

今後もGitHubは更新していく予定なので役に立ったら記事にコメントでもしてくれると嬉しくて泣きます。
module の変更案などもコメントしてください。

では、良いTerraformライフをお送りください。