1 # Copyright (C) The Arvados Authors. All rights reserved.
3 # SPDX-License-Identifier: CC-BY-SA-3.0
6 required_version = "~> 1.3.0"
9 source = "hashicorp/aws"
16 region = var.region_name
18 tags = merge(var.custom_tags, {
19 Arvados = var.cluster_name
25 resource "aws_vpc" "arvados_vpc" {
26 count = var.vpc_id == "" ? 1 : 0
27 cidr_block = "10.1.0.0/16"
28 enable_dns_hostnames = true
29 enable_dns_support = true
33 condition = (var.sg_id == "")
34 error_message = "vpc_id should be set if sg_id is also set"
38 resource "aws_subnet" "public_subnet" {
39 count = var.public_subnet_id == "" ? 1 : 0
40 vpc_id = local.arvados_vpc_id
41 availability_zone = local.availability_zone
42 cidr_block = "10.1.1.0/24"
46 condition = (var.vpc_id == "")
47 error_message = "public_subnet_id should be set if vpc_id is also set"
51 resource "aws_subnet" "private_subnet" {
52 count = var.private_subnet_id == "" ? 1 : 0
53 vpc_id = local.arvados_vpc_id
54 availability_zone = local.availability_zone
55 cidr_block = "10.1.2.0/24"
59 condition = (var.vpc_id == "")
60 error_message = "private_subnet_id should be set if vpc_id is also set"
66 # Additional subnet on a different AZ is required if RDS is enabled
68 resource "aws_subnet" "additional_rds_subnet" {
69 count = (var.additional_rds_subnet_id == "" && local.use_rds) ? 1 : 0
70 vpc_id = local.arvados_vpc_id
71 availability_zone = data.aws_availability_zones.available.names[1]
72 cidr_block = "10.1.3.0/24"
76 condition = (var.vpc_id == "")
77 error_message = "additional_rds_subnet_id should be set if vpc_id is also set"
85 resource "aws_vpc_endpoint" "s3" {
86 count = var.vpc_id == "" ? 1 : 0
87 vpc_id = local.arvados_vpc_id
88 service_name = "com.amazonaws.${var.region_name}.s3"
90 resource "aws_vpc_endpoint_route_table_association" "compute_s3_route" {
91 count = var.private_subnet_id == "" ? 1 : 0
92 vpc_endpoint_id = aws_vpc_endpoint.s3[0].id
93 route_table_id = aws_route_table.private_subnet_rt[0].id
97 # Internet access for Public IP instances
99 resource "aws_internet_gateway" "internet_gw" {
100 count = var.vpc_id == "" ? 1 : 0
101 vpc_id = local.arvados_vpc_id
103 resource "aws_eip" "arvados_eip" {
104 for_each = toset(local.public_hosts)
106 aws_internet_gateway.internet_gw
109 resource "aws_route_table" "public_subnet_rt" {
110 count = var.public_subnet_id == "" ? 1 : 0
111 vpc_id = local.arvados_vpc_id
113 cidr_block = "0.0.0.0/0"
114 gateway_id = aws_internet_gateway.internet_gw[0].id
117 resource "aws_route_table_association" "public_subnet_assoc" {
118 count = var.public_subnet_id == "" ? 1 : 0
119 subnet_id = aws_subnet.public_subnet[0].id
120 route_table_id = aws_route_table.public_subnet_rt[0].id
124 # Internet access for Private IP instances
126 resource "aws_eip" "nat_gw_eip" {
127 count = var.private_subnet_id == "" ? 1 : 0
129 aws_internet_gateway.internet_gw[0]
132 resource "aws_nat_gateway" "nat_gw" {
133 count = var.private_subnet_id == "" ? 1 : 0
134 # A NAT gateway should be placed on a subnet with an internet gateway
135 subnet_id = aws_subnet.public_subnet[0].id
136 allocation_id = aws_eip.nat_gw_eip[0].id
138 resource "aws_route_table" "private_subnet_rt" {
139 count = var.private_subnet_id == "" ? 1 : 0
140 vpc_id = local.arvados_vpc_id
142 cidr_block = "0.0.0.0/0"
143 nat_gateway_id = aws_nat_gateway.nat_gw[0].id
146 resource "aws_route_table_association" "private_subnet_assoc" {
147 count = var.private_subnet_id == "" ? 1 : 0
148 subnet_id = aws_subnet.private_subnet[0].id
149 route_table_id = aws_route_table.private_subnet_rt[0].id
152 resource "aws_security_group" "arvados_sg" {
154 count = var.sg_id == "" ? 1 : 0
155 vpc_id = aws_vpc.arvados_vpc[0].id
159 condition = (var.vpc_id == "")
160 error_message = "sg_id should be set if vpc_id is set"
165 for_each = local.allowed_ports
167 description = "Ingress rule for ${ingress.key}"
168 from_port = "${ingress.value}"
169 to_port = "${ingress.value}"
171 cidr_blocks = ["0.0.0.0/0"]
172 ipv6_cidr_blocks = ["::/0"]
175 # Allows communication between nodes in the VPC
180 cidr_blocks = [ aws_vpc.arvados_vpc[0].cidr_block ]
182 # Even though AWS auto-creates an "allow all" egress rule,
183 # Terraform deletes it, so we add it explicitly.
188 cidr_blocks = ["0.0.0.0/0"]
189 ipv6_cidr_blocks = ["::/0"]
194 # Route53 split-horizon DNS zones
198 resource "aws_route53_zone" "public_zone" {
199 count = var.private_only ? 0 : 1
200 name = var.domain_name
202 resource "aws_route53_record" "public_a_record" {
203 zone_id = try(local.route53_public_zone.id, "")
204 for_each = local.public_ip
208 records = [ each.value ]
210 resource "aws_route53_record" "main_a_record" {
211 count = var.private_only ? 0 : 1
212 zone_id = try(local.route53_public_zone.id, "")
216 records = [ local.public_ip["controller"] ]
218 resource "aws_route53_record" "public_cname_record" {
219 zone_id = try(local.route53_public_zone.id, "")
221 for i in local.cname_by_host: i.record =>
222 "${i.cname}.${var.domain_name}"
223 if var.private_only == false
228 records = [ each.value ]
232 resource "aws_route53_zone" "private_zone" {
233 name = var.domain_name
235 vpc_id = local.arvados_vpc_id
238 resource "aws_route53_record" "private_a_record" {
239 zone_id = aws_route53_zone.private_zone.id
240 for_each = local.private_ip
244 records = [ each.value ]
246 resource "aws_route53_record" "private_main_a_record" {
247 zone_id = aws_route53_zone.private_zone.id
251 records = [ local.private_ip["controller"] ]
253 resource "aws_route53_record" "private_cname_record" {
254 zone_id = aws_route53_zone.private_zone.id
255 for_each = {for i in local.cname_by_host: i.record => "${i.cname}.${var.domain_name}" }
259 records = [ each.value ]
263 # Route53's credentials for Let's Encrypt
265 resource "aws_iam_user" "letsencrypt" {
266 count = var.private_only ? 0 : 1
267 name = "${var.cluster_name}-letsencrypt"
271 resource "aws_iam_access_key" "letsencrypt" {
272 count = var.private_only ? 0 : 1
273 user = local.iam_user_letsencrypt.name
275 resource "aws_iam_user_policy" "letsencrypt_iam_policy" {
276 count = var.private_only ? 0 : 1
277 name = "${var.cluster_name}-letsencrypt_iam_policy"
278 user = local.iam_user_letsencrypt.name
279 policy = jsonencode({
280 "Version": "2012-10-17",
284 "route53:ListHostedZones",
293 "route53:ChangeResourceRecordSets"
296 "arn:aws:route53:::hostedzone/${local.route53_public_zone.id}"