Carpe Diem

備忘録

TerraformでAWSのセキュアな構成を構築

概要

PublicSubnetとPrivateSubnetを切り分けてよりセキュアに構築する方法。
PrivateSubnetのインスタンスSSHアクセスする場合は別途VPNを立てる形になると思いますが、今回は割愛させていただきます。

環境

  • Terraform 0.6.3

構成図とネットワークフロー

inbound request

外部から内部へ入ってくるリクエストは以下の流れです。
青がリクエスト、赤がレスポンスです。
通常のクライアントからのリクエストのフローです。

f:id:quoll00:20171212100816p:plain

outbound request

内部から外部へでていくリクエストは以下の流れです。
青がリクエスト、赤がレスポンスです。
private subnetから外部APIを叩きたい時などのフローですね。

f:id:quoll00:20171212100826p:plain

コード

VPC

resource "aws_vpc" "vpc" {
    cidr_block = "10.0.0.0/16"
    tags {
        Name = "vpc"
    }
}

InternetGateway

resource "aws_internet_gateway" "igw" {
    vpc_id = "${aws_vpc.vpc.id}"
    tags {
        Name = "igw"
    }
}

Route Table

1つめはIGWに送信するPublicSubnet用。
2つめはNATへ送信するPrivateSubnet用。
Terraformでは内部通信用の設定10.0.0.0/16->localは自動でつくのでこちらでは指定不要。
それぞれデフォルトゲートウェイを設定します。

resource "aws_route_table" "public_rt" {
    vpc_id = "${aws_vpc.vpc.id}"
    route {
        cidr_block = "0.0.0.0/0"
        gateway_id = "${aws_internet_gateway.igw.id}"
    }
    tags {
        Name = "public-rt"
    }
}

resource "aws_route_table" "nat_rt" {
    vpc_id = "${aws_vpc.vpc.id}"
    route {
        cidr_block = "0.0.0.0/0"
        instance_id = "${aws_instance.nat.id}"
    }
    tags {
        Name = "nat-rt"
    }
}

Subnet

1つめはPublicSubnet。
2つめはPrivateSubnet。

resource "aws_subnet" "public_1a" {
    vpc_id = "${aws_vpc.vpc.id}"
    cidr_block = "10.0.10.0/24"
    availability_zone = "ap-northeast-1a"
    map_public_ip_on_launch = "1"
    tags {
        Name = "public-1a"
    }
}

resource "aws_route_table_association" "public_1a" {
    subnet_id = "${aws_subnet.public_1a.id}"
    route_table_id = "${aws_route_table.public_rt.id}"
}

resource "aws_subnet" "private_1a" {
    vpc_id = "${aws_vpc.vpc.id}"
    cidr_block = "10.0.100.0/24"
    availability_zone = "ap-northeast-1a"
    map_public_ip_on_launch = "0"
    tags {
        Name = "private-1a"
    }
}

resource "aws_route_table_association" "private_1a" {
    subnet_id = "${aws_subnet.private_1a.id}"
    route_table_id = "${aws_route_table.nat_rt.id}"
}

Security Group

1つめは内部通信用。Self referenceにしているので、このSGを持つインスタンスやELBは相互通信可能になる。
2つめはNAT用。内部からしかインバウンドを許可しない。
3つめはHTTP用。外部から80ポートへのアクセスを許可。

resource "aws_security_group" "internal" {
    vpc_id = "${aws_vpc.vpc.id}"
    name = "internal"
    description = "Allow internal traffic"
    ingress {
        from_port = 0
        to_port = 65535
        protocol = "tcp"
        self = true
    }
    egress {
        from_port = 0
        to_port = 0
        protocol = "-1"
        cidr_blocks = ["0.0.0.0/0"]
    }
    tags {
        Name = "internal"
    }
}

resource "aws_security_group" "nat" {
    vpc_id = "${aws_vpc.vpc.id}"
    name = "nat"
    description = "Allow internal inbound traffic"
    ingress {
        from_port = 0
        to_port = 65535
        protocol = "tcp"
        cidr_blocks = ["${aws_vpc.vpc.cidr_block}"]
    }
    ingress {
        from_port = 0
        to_port = 65535
        protocol = "udp"
        cidr_blocks = ["${aws_vpc.vpc.cidr_block}"]
    }
    egress {
        from_port = 0
        to_port = 0
        protocol = "-1"
        cidr_blocks = ["0.0.0.0/0"]
    }
    tags {
        Name = "nat"
    }
}

resource "aws_security_group" "http" {
    vpc_id = "${aws_vpc.vpc.id}"
    name = "http"
    description = "Allow http inbound traffic"
    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"]
    }
    tags {
        Name = "http"
    }
}

NAT

AMIはNAT用のものを使用。自分でも作れるが通常のインスタンスのAMIとは設定が少し異なるので注意。
またsource_dest_check = falseにすること。
構成図の通りSubnetはPublicに。

resource "aws_instance" "nat" {
    ami = "${var.amis.nat}"
    instance_type = "t2.micro"
    key_name = "${var.key_name}"
    vpc_security_group_ids = ["${aws_security_group.nat.id}"]
    subnet_id = "${aws_subnet.public_1a.id}"
    associate_public_ip_address = true
    source_dest_check = false
    tags {
        Name = "nat"
        Environment = "Common"
        Role = "NAT"
    }
}

Instance

PrivateSubnetに置く。 ELBと通信できるようSGにinternalをつける。

resource "aws_instance" "dev_api" {
    ami = "${var.amis.api}"
    instance_type = "t2.micro"
    key_name = "${var.key_name}"
    vpc_security_group_ids = ["${aws_security_group.internal.id}"]
    subnet_id = "${aws_subnet.private_1a.id}"
    tags {
        Environment = "Development"
        Name = "dev-api"
        Role = "API"
    }
}

ELB

PrivateSubnetのインスタンスと通信できるようSGにinternalをつける。

resource "aws_elb" "dev_elb" {
    name = "dev-elb"
    subnets = [
        "${aws_subnet.public_1a.id}",
    ]
    security_groups = [
        "${aws_security_group.internal.id}",
        "${aws_security_group.http.id}",
    ]
    listener {
        instance_port = 80
        instance_protocol = "http"
        lb_port = 80
        lb_protocol = "http"
    }
    health_check {
        healthy_threshold = 2
        unhealthy_threshold = 2
        timeout = 5
        target = "HTTP:80/"
        interval = 30
    }
    instances = ["${aws_instance.dev_api.id}"]
    cross_zone_load_balancing = true
    idle_timeout = 400
    connection_draining = true
    connection_draining_timeout = 400
    tags {
        Environment = "Development"
        Name = "dev-elb"
        Role = "ELB"
    }
}

完成物

moduleを使用しているためややソースが異なりますが、実行時のインフラ構成は同じです。

github.com

ソース