Terraformでaws_security_groupとaws_security_group_ruleの両方でルールを設定すると競合する

前回はセキュリティグループIDをソースに指定してセキュリティグループを作成する方法について書きましたが、今回はその流れでルールを書いていたらハマった話です。

ohshige.hatenablog.com

セキュリティグループIDをソースに指定してセキュリティグループを作成するユースケースとして、AWSで踏み台サーバを用意してアプリケーションサーバにはその踏み台サーバ経由でしかsshできないようにする場合を想定していました。
例えば、そのうえで「アプリケーションサーバには特定のIPからの80番ポートでのHTTPを許可する」というルールを追加するということも考えられます。

CIDRブロックでのingressルールはaws_security_groupにそのまま記述できるので、例えば以下のように書けます。

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

resource "aws_security_group" "bastion" {
  (略)
}

resource "aws_security_group" "application" {
  name        = "application"
  description = "Security Group for application"

  // ここを新しく追加
  ingress {
    from_port   = 80
    to_port     = 80
    protocol    = "tcp"
    cidr_blocks = ["0.0.0.0/0"]
  }

  egress {
    from_port   = 0
    to_port     = 0
    protocol    = "-1"
    cidr_blocks = ["0.0.0.0/0"]
  }
}
resource "aws_security_group_rule" "application_from_bastion" {
  type                     = "ingress"
  to_port                  = 22
  protocol                 = "tcp"
  source_security_group_id = aws_security_group.bastion.id
  from_port                = 22
  security_group_id        = aws_security_group.application.id
}

これは問題なく実行でき、意図通りの設定になります。

問題はこれからで、上記のtfファイルを特に設定を書き換えることなく、 terraform plan を実行すると、変更があると言われてしまいます。

Terraform will perform the following actions:

  # aws_security_group.application will be updated in-place
  ~ resource "aws_security_group" "application" {
      (略)
    }

Plan: 0 to add, 1 to change, 0 to destroy.

そんなまさかと思いつつ、 terraform apply を実行すると、確かに変更が適用され、コンソールで確認するとaws_security_group_ruleで指定したSSHのルールが削除されています。

さらに、この状態で再度 terraform plan terraform apply を実行すると、もとに戻り、無限ループ(ずっと terraform plan で差分が出る)になります。

これを回避するためには、全てのルールをaws_security_group内かaws_security_group_ruleかどちらかに統一すれば良いです。
今回はセキュリティグループIDをソースに指定する指定方法をそのまま生かしたいので、aws_security_group_ruleに統一します。
ingressのルールを統一すれば良いので、egressはそのままでも良いです。

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

resource "aws_security_group" "bastion" {
  (略)
}

resource "aws_security_group" "application" {
  name        = "application"
  description = "Security Group for application"

  egress {
    from_port   = 0
    to_port     = 0
    protocol    = "-1"
    cidr_blocks = ["0.0.0.0/0"]
  }
}
resource "aws_security_group_rule" "application_from_bastion" {
  type                     = "ingress"
  to_port                  = 22
  protocol                 = "tcp"
  source_security_group_id = aws_security_group.bastion.id
  from_port                = 22
  security_group_id        = aws_security_group.application.id
}
// aws_security_group_ruleとして新しく追加
resource "aws_security_group_rule" "application_from_http" {
  type              = "ingress"
  to_port           = 80
  protocol          = "tcp"
  cidr_blocks       = ["0.0.0.0/0"]
  from_port         = 80
  security_group_id = aws_security_group.application.id
}

これで何回 terraform plan を実行しても謎の差分が出ることはなくなります。

一件落着です。

--

という話は公式にしっかりと書かれていました。

www.terraform.io

NOTE on Security Groups and Security Group Rules: Terraform currently provides both a standalone Security Group Rule resource (a single ingress or egress rule), and a Security Group resource with ingress and egress rules defined in-line. At this time you cannot use a Security Group with in-line rules in conjunction with any Security Group Rule resources. Doing so will cause a conflict of rule settings and will overwrite rules.

aws_security_group_ruleを使う場合とaws_security_group内のingress/egressで定義する方法がありますが、同時に使うと競合するよ」みたいな感じでしょうか。

競合するので、どちらかに統一して書きましょうというお話でした。