概要
PublicSubnetとPrivateSubnetを切り分けてよりセキュアに構築する方法。
PrivateSubnetのインスタンスにSSHアクセスする場合は別途VPNを立てる形になると思いますが、今回は割愛させていただきます。
環境
- Terraform 0.6.3
構成図とネットワークフロー
inbound request
外部から内部へ入ってくるリクエストは以下の流れです。
青がリクエスト、赤がレスポンスです。
通常のクライアントからのリクエストのフローです。
outbound request
内部から外部へでていくリクエストは以下の流れです。
青がリクエスト、赤がレスポンスです。
private subnetから外部APIを叩きたい時などのフローですね。
コード
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を使用しているためややソースが異なりますが、実行時のインフラ構成は同じです。