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"
68 resource "aws_vpc_endpoint" "s3" {
69 count = var.vpc_id == "" ? 1 : 0
70 vpc_id = local.arvados_vpc_id
71 service_name = "com.amazonaws.${var.region_name}.s3"
73 resource "aws_vpc_endpoint_route_table_association" "compute_s3_route" {
74 count = var.private_subnet_id == "" ? 1 : 0
75 vpc_endpoint_id = aws_vpc_endpoint.s3[0].id
76 route_table_id = aws_route_table.private_subnet_rt[0].id
80 # Internet access for Public IP instances
82 resource "aws_internet_gateway" "internet_gw" {
83 count = var.vpc_id == "" ? 1 : 0
84 vpc_id = local.arvados_vpc_id
86 resource "aws_eip" "arvados_eip" {
87 for_each = toset(local.public_hosts)
89 aws_internet_gateway.internet_gw
92 resource "aws_route_table" "public_subnet_rt" {
93 count = var.public_subnet_id == "" ? 1 : 0
94 vpc_id = local.arvados_vpc_id
96 cidr_block = "0.0.0.0/0"
97 gateway_id = aws_internet_gateway.internet_gw[0].id
100 resource "aws_route_table_association" "public_subnet_assoc" {
101 count = var.public_subnet_id == "" ? 1 : 0
102 subnet_id = aws_subnet.public_subnet[0].id
103 route_table_id = aws_route_table.public_subnet_rt[0].id
107 # Internet access for Private IP instances
109 resource "aws_eip" "nat_gw_eip" {
110 count = var.private_subnet_id == "" ? 1 : 0
112 aws_internet_gateway.internet_gw[0]
115 resource "aws_nat_gateway" "nat_gw" {
116 count = var.private_subnet_id == "" ? 1 : 0
117 # A NAT gateway should be placed on a subnet with an internet gateway
118 subnet_id = aws_subnet.public_subnet[0].id
119 allocation_id = aws_eip.nat_gw_eip[0].id
121 resource "aws_route_table" "private_subnet_rt" {
122 count = var.private_subnet_id == "" ? 1 : 0
123 vpc_id = local.arvados_vpc_id
125 cidr_block = "0.0.0.0/0"
126 nat_gateway_id = aws_nat_gateway.nat_gw[0].id
129 resource "aws_route_table_association" "private_subnet_assoc" {
130 count = var.private_subnet_id == "" ? 1 : 0
131 subnet_id = aws_subnet.private_subnet[0].id
132 route_table_id = aws_route_table.private_subnet_rt[0].id
135 resource "aws_security_group" "arvados_sg" {
137 count = var.sg_id == "" ? 1 : 0
138 vpc_id = aws_vpc.arvados_vpc[0].id
142 condition = (var.vpc_id == "")
143 error_message = "sg_id should be set if vpc_id is set"
148 for_each = local.allowed_ports
150 description = "Ingress rule for ${ingress.key}"
151 from_port = "${ingress.value}"
152 to_port = "${ingress.value}"
154 cidr_blocks = ["0.0.0.0/0"]
155 ipv6_cidr_blocks = ["::/0"]
158 # Allows communication between nodes in the VPC
163 cidr_blocks = [ aws_vpc.arvados_vpc[0].cidr_block ]
165 # Even though AWS auto-creates an "allow all" egress rule,
166 # Terraform deletes it, so we add it explicitly.
171 cidr_blocks = ["0.0.0.0/0"]
172 ipv6_cidr_blocks = ["::/0"]
177 # Route53 split-horizon DNS zones
181 resource "aws_route53_zone" "public_zone" {
182 count = var.private_only ? 0 : 1
183 name = var.domain_name
185 resource "aws_route53_record" "public_a_record" {
186 zone_id = try(local.route53_public_zone.id, "")
187 for_each = local.public_ip
191 records = [ each.value ]
193 resource "aws_route53_record" "main_a_record" {
194 count = var.private_only ? 0 : 1
195 zone_id = try(local.route53_public_zone.id, "")
199 records = [ local.public_ip["controller"] ]
201 resource "aws_route53_record" "public_cname_record" {
202 zone_id = try(local.route53_public_zone.id, "")
204 for i in local.cname_by_host: i.record =>
205 "${i.cname}.${var.domain_name}"
206 if var.private_only == false
211 records = [ each.value ]
215 resource "aws_route53_zone" "private_zone" {
216 name = var.domain_name
218 vpc_id = local.arvados_vpc_id
221 resource "aws_route53_record" "private_a_record" {
222 zone_id = aws_route53_zone.private_zone.id
223 for_each = local.private_ip
227 records = [ each.value ]
229 resource "aws_route53_record" "private_main_a_record" {
230 zone_id = aws_route53_zone.private_zone.id
234 records = [ local.private_ip["controller"] ]
236 resource "aws_route53_record" "private_cname_record" {
237 zone_id = aws_route53_zone.private_zone.id
238 for_each = {for i in local.cname_by_host: i.record => "${i.cname}.${var.domain_name}" }
242 records = [ each.value ]
246 # Route53's credentials for Let's Encrypt
248 resource "aws_iam_user" "letsencrypt" {
249 count = var.private_only ? 0 : 1
250 name = "${var.cluster_name}-letsencrypt"
254 resource "aws_iam_access_key" "letsencrypt" {
255 count = var.private_only ? 0 : 1
256 user = local.iam_user_letsencrypt.name
258 resource "aws_iam_user_policy" "letsencrypt_iam_policy" {
259 count = var.private_only ? 0 : 1
260 name = "${var.cluster_name}-letsencrypt_iam_policy"
261 user = local.iam_user_letsencrypt.name
262 policy = jsonencode({
263 "Version": "2012-10-17",
267 "route53:ListHostedZones",
276 "route53:ChangeResourceRecordSets"
279 "arn:aws:route53:::hostedzone/${local.route53_public_zone.id}"