TerraformでAWSのセキュリティグループのルールを作成する方法の比較と注意点

2023.07.12

aws providerを使用して、セキュリティグループのルールを作成できるリソースは以下の3つがあります。

  • aws_security_group
  • aws_security_group_rule
  • aws_vpc_security_group_[ingress|egress]_rule

どう違うのか気になったので、ブログにしてみました。

結論

  • aws_vpc_security_group_[ingress|egress]_ruleを使うのがおすすめ
  • ルールの定義方法は混在させない
    • ルールの競合によって設定が上書きされるリスクがある

ルール単位のタグ付けやimportが可能なため、aws_vpc_security_group_[ingress|egress]_ruleを使うのがおすすめです。

同様のことは、「aws_security_group_rule」でも可能ですが、タグ付けに関してはリソースとルールが1対1でマッピングしていない場合、うまく動作しないことがあります。(後述)

リソース ルール単位のタグ付け ルール単位のimport
aws_security_group - -
aws_security_group_rule
aws_vpc_security_group_[ingress|egress]_rule

比較してみる

それぞれのResourceで以下のセキュリティグループを定義して、比較してみます。

タイプ プロトコル ソースCIDR タグ
ingress HTTP VPC CIDR
ingress HTTPS VPC CIDR Name = Hello World
(テスト用のタグ)
egress all all

前提

比較時は、以下のバージョンを利用しています。

  • terraform: 1.5.0
  • aws-provider: 5.7.0

aws_security_group

aws_security_group | Resources | hashicorp/aws | Terraform Registry

サンプルコード

main.tf

provider "aws" {}

resource "aws_vpc" "this" {
  cidr_block = "10.0.0.0/16"
}

resource "aws_security_group" "this" {
  vpc_id = aws_vpc.this.id
  name   = "example-1"

  ingress {
    description = "HTTP from VPC"
    from_port   = 80
    to_port     = 80
    protocol    = "tcp"
    cidr_blocks = [aws_vpc.this.cidr_block]
  }
  ingress {
    description = "HTTPS from VPC"
    from_port   = 443
    to_port     = 443
    protocol    = "tcp"
    cidr_blocks = [aws_vpc.this.cidr_block]
  }

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

サンプルコードのようにingressを複数書くことで、複数のルールを定義できます。

ルール単位のタグ付け

できません。セキュリティグループ単位でタグを付けることは可能です。

しかし、ingressやegressにはtag定義用の引数が無いためルール単位のタグ付けはできませんでした。

ルール単位のimport

できません。以下のように、セキュリティグループIDを渡してセキュリティグループ単位でimportを行うことは可能です。

$ terraform import aws_security_group.example sg-XXXXX

Terraformで作成したセキュリティグループに手動でルール追加したケースで、ルールをimportしたい場合、上記のようにセキュリティグループごとimportするとエラーになります。

すでに、同一のリソース名がStateファイルに追加されているためです。

一度、セキュリティグループもTerraform管理外から外してimportし直す必要がありそうです。

aws_security_group_rule

aws_security_group | Resources | hashicorp/aws | Terraform Registry

サンプルコード

provider "aws" {}

resource "aws_vpc" "this" {
  cidr_block = "10.0.0.0/16"
}

resource "aws_security_group" "this" {
  vpc_id = aws_vpc.this.id
  name   = "example-2"
}

resource "aws_security_group_rule" "ingress_allow_http" {
  type              = "ingress"
  from_port         = 80
  to_port           = 80
  protocol          = "tcp"
  cidr_blocks       = [aws_vpc.this.cidr_block]
  security_group_id = aws_security_group.this.id
}

resource "aws_security_group_rule" "ingress_allow_https" {
  type              = "ingress"
  from_port         = 443
  to_port           = 443
  protocol          = "tcp"
  cidr_blocks       = [aws_vpc.this.cidr_block]
  security_group_id = aws_security_group.this.id
}

resource "aws_security_group_rule" "egress_allow_all" {
  type              = "egress"
  from_port         = 0
  to_port           = 0
  protocol          = "-1"
  cidr_blocks       = ["0.0.0.0/0"]
  security_group_id = aws_security_group.this.id
}

resource "aws_ec2_tag" "test" {
  resource_id = aws_security_group_rule.ingress_allow_http.security_group_rule_id
  key         = "Name"
  value       = "Hello World"
}

ルール単位のタグ付け

できます。リソースの引数にはタグが無いのですが、rule_idを属性として持っています。

そのため、aws_ec2_tagを利用してルール単位でタグを付けることができます。

注意点として、cidr_blocksで複数のCIDRを指定するとルールも作成されます。

1つのリソースで複数のルールを作れるため、リソースとルールで1対1のマッピングが成り立たなくなります。

この際、リソースの属性にあるrule_idは空になってしまいます。そのため、タグ付けもうまくできません。

aws_security_group_ruleを使ってルール単位のタグ付けを行う場合は、1リソース1ルールになるように作成する必要があります。

ルール単位のimport

できます。rule_idを使ってimportすることはできないため、ポートやIPアドレスなど指定する必要があります。 (後述の「aws_vpc_security_group_ingress_rule/aws_vpc_security_group_egress_rule」では、rule_idを使ってimportが可能です)

terraform import aws_security_group_rule.egress_allow_all sg-XXXXXX_ingress_-1_0_0_0.0.0.0/0

aws_vpc_security_group_[ingress|egress]_rule

aws_vpc_security_group_ingress_rule | Resources | hashicorp/aws | Terraform Registry aws_vpc_security_group_egress_rule | Resources | hashicorp/aws | Terraform Registry

AWS Provider v4.56.0で追加されたリソースです。

aws_security_group_ruleがあるのに、なぜ追加されたの?」と思う方もいるかもしれません。

正確な経緯は以下を確認するとよさそうです。また、公式ドキュメントにも説明があります。

ここでは、ざっくり説明します。

2021年7月にセキュリティグループの各ルールにリソース識別子(ルールID)がつくようになりタグ付けが可能になりました。

aws_security_group_ruleは上記のアップデート前から存在するリソースです。

そのため、ルールIDやタグ付けがうまくできないケースもあります。

例えば、cidr_blocksで複数のCIDRを指定すれば、その分だけルールが作られます。

結果、リソースとルールが1対1でマッピングしないことがあります。

その状態で、rule_idをoutputすると空になります。rule_idを指定したタグ付けもできません。

resource "aws_security_group_rule" "ingress_allow_http" {
  type              = "ingress"
  from_port         = 80
  to_port           = 80
  protocol          = "tcp"
  # 複数のルールが作成される
  cidr_blocks       = [aws_vpc.this.cidr_block, "10.0.1.0/24"]
  security_group_id = aws_security_group.this.id
}

# ルールIDは空
output "test" {
  value = aws_security_group_rule.ingress_allow_http.security_group_rule_id
}

上記の問題を解消するために、「aws_vpc_security_group_[ingress|egress]_rule」が追加されました。

サンプルコード

provider "aws" {}

resource "aws_vpc" "this" {
  cidr_block = "10.0.0.0/16"
}

resource "aws_security_group" "this" {
  vpc_id = aws_vpc.this.id
  name   = "example-3"
}

resource "aws_vpc_security_group_ingress_rule" "allow_http" {
  from_port         = 80
  to_port           = 80
  ip_protocol       = "tcp"
  cidr_ipv4         = aws_vpc.this.cidr_block
  security_group_id = aws_security_group.this.id
  tags = {
    Name = "Hello World"
  }
}

resource "aws_vpc_security_group_ingress_rule" "allow_https" {
  from_port         = 443
  to_port           = 443
  ip_protocol       = "tcp"
  cidr_ipv4         = aws_vpc.this.cidr_block
  security_group_id = aws_security_group.this.id
}

resource "aws_vpc_security_group_egress_rule" "allow_all" {
  ip_protocol       = "-1"
  cidr_ipv4         = "0.0.0.0/0"
  security_group_id = aws_security_group.this.id
}

aws_security_group_ruleと似ていますが、異なる部分もあります。

  • ingressとegressでリソースが別れている
  • cidr_blockの渡し方が配列ではなく、文字列になっている
  • ip_protocolで「-1」を定義した場合、to_portとfrom_portを定義できなくなっている

ルール単位のタグ付け

できます。リソースに引数としてtagsがあります。

ルール単位のimport

できます。以下のようにrule_id単位でimportが可能です。

$ terraform import aws_vpc_security_group_egress_rule.allow_all sgr-XXXXXXX

おわりに

Terraformでセキュリティグループのルール作成する方法の比較でした。

混在させなければ、どれを使っても基本的には問題なく使えると思います。

しかし、Terraformでセキュリティグループ作ってルールだけ手動で作成してしまった。

ルールだけimportしたいケースでは、aws_vpc_security_group_[ingress|egress]_ruleを使用していると楽に対応できます。

既存のコードを直していくのは大変だと思いますが、既存影響なく新規作成する分はaws_vpc_security_group_[ingress|egress]_ruleを使っていきたいと思いました。

以上、AWS事業本部の佐藤(@chari7311)でした。