Edited at

terraform で aws_security_group の更新を安全に行う

More than 1 year has passed since last update.


ダメなパターン

これを

resource "aws_security_group" "test" {

name = "test"
vpc_id = "${aws_vpc.test.id}"
ingress {
from_port = 22
to_port = 22
protocol = "tcp"
cidr_blocks = ["x.x.x.x/32"]
}
}

こう変えると

 resource "aws_security_group" "test" {

name = "test"
vpc_id = "${aws_vpc.test.id}"
ingress {
from_port = 22
to_port = 22
protocol = "tcp"
- cidr_blocks = ["x.x.x.x/32"]
+ cidr_blocks = ["x.x.x.x/32","y.y.y.y/32"]
}
}

ログが次のように出て、なんか diff が怪しい。1回 ingress を全部消して、再度全部作ってるような。

aws_security_group.test: Modifying...

ingress.2293169953.cidr_blocks.#: "1" => "0"
2016/10/27 18:33:14 [DEBUG] apply: aws_security_group.test: executing Apply
ingress.2293169953.cidr_blocks.0: "x.x.x.x/32" => ""
ingress.2293169953.from_port: "22" => "0"
ingress.2293169953.protocol: "tcp" => ""
ingress.2293169953.security_groups.#: "0" => "0"
ingress.2293169953.self: "0" => "0"
ingress.2293169953.to_port: "22" => "0"
ingress.3838495408.cidr_blocks.#: "0" => "2"
ingress.3838495408.cidr_blocks.0: "" => "x.x.x.x/32"
ingress.3838495408.cidr_blocks.1: "" => "y.y.y.y/32"
ingress.3838495408.from_port: "" => "22"
ingress.3838495408.protocol: "" => "tcp"
ingress.3838495408.security_groups.#: "0" => "0"
ingress.3838495408.self: "" => "0"
ingress.3838495408.to_port: "" => "22"

TF_LOG=DEBUG をつけて実行すると以下のようにログが出て、Revoke & Authorize している感じ。

2016/10/27 18:33:15 [DEBUG] terraform-provider-aws: 2016/10/27 18:33:15 [DEBUG] Revoking security group {

2016/10/27 18:33:15 [DEBUG] terraform-provider-aws: Description: "Managed by Terraform",
2016/10/27 18:33:15 [DEBUG] terraform-provider-aws: GroupName: "test",
2016/10/27 18:33:15 [DEBUG] terraform-provider-aws: IpPermissions: [{
2016/10/27 18:33:15 [DEBUG] terraform-provider-aws: FromPort: 22,
2016/10/27 18:33:15 [DEBUG] terraform-provider-aws: IpProtocol: "tcp",
2016/10/27 18:33:15 [DEBUG] terraform-provider-aws: IpRanges: [{
2016/10/27 18:33:15 [DEBUG] terraform-provider-aws: CidrIp: "x.x.x.x/32"
2016/10/27 18:33:15 [DEBUG] terraform-provider-aws: }],
2016/10/27 18:33:15 [DEBUG] terraform-provider-aws: ToPort: 22
2016/10/27 18:33:15 [DEBUG] terraform-provider-aws: }],
2016/10/27 18:33:15 [DEBUG] terraform-provider-aws: } ingress rule: []*ec2.IpPermission{{
2016/10/27 18:33:15 [DEBUG] terraform-provider-aws: FromPort: 22,
2016/10/27 18:33:15 [DEBUG] terraform-provider-aws: IpProtocol: "tcp",
2016/10/27 18:33:15 [DEBUG] terraform-provider-aws: IpRanges: [{
2016/10/27 18:33:15 [DEBUG] terraform-provider-aws: CidrIp: "x.x.x.x/32"
2016/10/27 18:33:15 [DEBUG] terraform-provider-aws: }],
2016/10/27 18:33:15 [DEBUG] terraform-provider-aws: ToPort: 22
2016/10/27 18:33:15 [DEBUG] terraform-provider-aws: }}
2016/10/27 18:33:15 [DEBUG] terraform-provider-aws: 2016/10/27 18:33:15 [DEBUG] Authorizing security group {
2016/10/27 18:33:15 [DEBUG] terraform-provider-aws: Description: "Managed by Terraform",
2016/10/27 18:33:15 [DEBUG] terraform-provider-aws: GroupName: "test",
2016/10/27 18:33:15 [DEBUG] terraform-provider-aws: IpPermissions: [{
2016/10/27 18:33:15 [DEBUG] terraform-provider-aws: FromPort: 22,
2016/10/27 18:33:15 [DEBUG] terraform-provider-aws: IpProtocol: "tcp",
2016/10/27 18:33:15 [DEBUG] terraform-provider-aws: IpRanges: [{
2016/10/27 18:33:15 [DEBUG] terraform-provider-aws: CidrIp: "x.x.x.x/32"
2016/10/27 18:33:15 [DEBUG] terraform-provider-aws: }],
2016/10/27 18:33:15 [DEBUG] terraform-provider-aws: ToPort: 22
2016/10/27 18:33:15 [DEBUG] terraform-provider-aws: }],
2016/10/27 18:33:15 [DEBUG] terraform-provider-aws: } ingress rule: []*ec2.IpPermission{{
2016/10/27 18:33:15 [DEBUG] terraform-provider-aws: FromPort: 22,
2016/10/27 18:33:15 [DEBUG] terraform-provider-aws: IpProtocol: "tcp",
2016/10/27 18:33:15 [DEBUG] terraform-provider-aws: IpRanges: [{
2016/10/27 18:33:15 [DEBUG] terraform-provider-aws: CidrIp: "x.x.x.x/32"
2016/10/27 18:33:15 [DEBUG] terraform-provider-aws: },{
2016/10/27 18:33:15 [DEBUG] terraform-provider-aws: CidrIp: "y.y.y.y/32"
2016/10/27 18:33:15 [DEBUG] terraform-provider-aws: }],
2016/10/27 18:33:15 [DEBUG] terraform-provider-aws: ToPort: 22
2016/10/27 18:33:15 [DEBUG] terraform-provider-aws: }}

ソースコードを読むと https://github.com/hashicorp/terraform/blob/11b3b7cf29fa4120974d293458beac47e1eedd32/builtin/providers/aws/resource_aws_security_group.go#L552-L559

        // TODO: We need to handle partial state better in the in-between

// in this update.

// TODO: It'd be nicer to authorize before removing, but then we have
// to deal with complicated unrolling to get individual CIDR blocks
// to avoid authorizing already authorized sources. Removing before
// adding is easier here, and Terraform should be fast enough to
// not have service issues.

とあり、TODO になっている。Terraform は充分に早いからサービスISSUEは起こらないんじゃないかな(意訳)とのことだが ...


安全安心なパターン

aws_security_group_rule を使う

 resource "aws_security_group" "test" {

name = "test"
vpc_id = "${aws_vpc.test.id}"
}

resource "aws_security_group_rule" "test_rule_1" {
security_group_id = "${aws_security_group.test.id}"
type = "ingress"
from_port = 22
to_port = 22
protocol = "tcp"
cidr_blocks = ["x.x.x.x/32"]
}

+resource "aws_security_group_rule" "test_rule_2" {
+ security_group_id = "${aws_security_group.test.id}"
+ type = "ingress"
+ from_port = 22
+ to_port = 22
+ protocol = "tcp"
+ cidr_blocks = ["y.y.y.y/32"]
+}

これなら追加だけになる。ログは以下。

2016/10/27 18:40:29 [DEBUG] apply: aws_security_group_rule.test_rule2: executing Apply

aws_security_group_rule.test: Creating...
cidr_blocks.#: "" => "1"
cidr_blocks.0: "" => "y.y.y.y/32"
from_port: "" => "22"
protocol: "" => "tcp"
self: "" => "0"

2016/10/27 18:40:30 [DEBUG] terraform-provider-aws: 2016/10/27 18:40:30 [DEBUG] Authorizing security group sg-66d6231b Ingress rule: {
2016/10/27 18:40:30 [DEBUG] terraform-provider-aws: FromPort: 22,
2016/10/27 18:40:30 [DEBUG] terraform-provider-aws: IpProtocol: "tcp",
2016/10/27 18:40:30 [DEBUG] terraform-provider-aws: IpRanges: [{
2016/10/27 18:40:30 [DEBUG] terraform-provider-aws: CidrIp: "y.y.y.y/32"
2016/10/27 18:40:30 [DEBUG] terraform-provider-aws: }],
2016/10/27 18:40:30 [DEBUG] terraform-provider-aws: ToPort: 22
2016/10/27 18:40:30 [DEBUG] terraform-provider-aws: }
2016/10/27 18:40:30 [DEBUG] terraform-provider-aws: 2016/10/27 18:40:30 [DEBUG] Found rule for Security Group Rule (sgrule-3952629376): {
2016/10/27 18:40:30 [DEBUG] terraform-provider-aws: FromPort: 22,
2016/10/27 18:40:30 [DEBUG] terraform-provider-aws: IpProtocol: "tcp",
2016/10/27 18:40:30 [DEBUG] terraform-provider-aws: IpRanges: [{
2016/10/27 18:40:30 [DEBUG] terraform-provider-aws: CidrIp: "x.x.x.x/32"
2016/10/27 18:40:30 [DEBUG] terraform-provider-aws: },{
2016/10/27 18:40:30 [DEBUG] terraform-provider-aws: CidrIp: "y.y.y.y/32"
2016/10/27 18:40:30 [DEBUG] terraform-provider-aws: }],
2016/10/27 18:40:30 [DEBUG] terraform-provider-aws: ToPort: 22
2016/10/27 18:40:30 [DEBUG] terraform-provider-aws: }

注意点は、from_port, to_port が同じだからと cidr_blocks の配列要素に追加しようとすると、destory & add になってしまう点。つまり、以下はダメ。aws_security_group_rule を別途用意すべき。

 resource "aws_security_group" "test" {

name = "test"
vpc_id = "${aws_vpc.test.id}"
}

resource "aws_security_group_rule" "test_rule_1" {
security_group_id = "${aws_security_group.test.id}"
type = "ingress"
from_port = 22
to_port = 22
protocol = "tcp"
- cidr_blocks = ["x.x.x.x/32"]
+ cidr_blocks = ["x.x.x.x/32","y.y.y.y/32"]
}


セキュリティグループの瞬断について考察 (追記)

セキュリティグループの ingress または egress からエントリを削除すると新規接続が弾かれるようになるが、すでに開いている接続が切られるわけではないことを確認した。

なので、「ダメなパターン」でも Revoke してから Authorize するまでの短い時間に新規接続が来たorをする場合にのみ問題となる。TCPであれば 少し待って(Linuxならデフォルト1秒)再接続するので、この少しを許容できるかという点と、UDPの送信失敗を許容できるかという点が論点になるだろう。

まぁ、aws_security_group_rule を使っておくほうが安全というのは間違いない。