21855: Update URL path for RPMs
[arvados.git] / tools / salt-install / terraform / aws / vpc / main.tf
1 # Copyright (C) The Arvados Authors. All rights reserved.
2 #
3 # SPDX-License-Identifier: CC-BY-SA-3.0
4
5 terraform {
6   required_version = "~> 1.3.0"
7   required_providers {
8     aws = {
9       source = "hashicorp/aws"
10       version = "~> 4.38.0"
11     }
12   }
13 }
14
15 provider "aws" {
16   region = var.region_name
17   default_tags {
18     tags = merge(var.custom_tags, {
19       Arvados = var.cluster_name
20       Terraform = true
21     })
22   }
23 }
24
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
30
31   lifecycle {
32     precondition {
33       condition = (var.sg_id == "")
34       error_message = "vpc_id should be set if sg_id is also set"
35     }
36   }
37 }
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"
43
44   lifecycle {
45     precondition {
46       condition = (var.vpc_id == "")
47       error_message = "public_subnet_id should be set if vpc_id is also set"
48     }
49   }
50 }
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"
56
57   lifecycle {
58     precondition {
59       condition = (var.vpc_id == "")
60       error_message = "private_subnet_id should be set if vpc_id is also set"
61     }
62   }
63 }
64
65 #
66 # Additional subnet on a different AZ is required if RDS is enabled
67 #
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"
73
74   lifecycle {
75     precondition {
76       condition = (var.vpc_id == "")
77       error_message = "additional_rds_subnet_id should be set if vpc_id is also set"
78     }
79   }
80 }
81
82 #
83 # VPC S3 access
84 #
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"
89 }
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
94 }
95
96 #
97 # Internet access for Public IP instances
98 #
99 resource "aws_internet_gateway" "internet_gw" {
100   count = var.vpc_id == "" ? 1 : 0
101   vpc_id = local.arvados_vpc_id
102 }
103 resource "aws_eip" "arvados_eip" {
104   for_each = toset(local.public_hosts)
105   depends_on = [
106     aws_internet_gateway.internet_gw
107   ]
108 }
109 resource "aws_route_table" "public_subnet_rt" {
110   count = var.public_subnet_id == "" ? 1 : 0
111   vpc_id = local.arvados_vpc_id
112   route {
113     cidr_block = "0.0.0.0/0"
114     gateway_id = aws_internet_gateway.internet_gw[0].id
115   }
116 }
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
121 }
122
123 #
124 # Internet access for Private IP instances
125 #
126 resource "aws_eip" "nat_gw_eip" {
127   count = var.private_subnet_id == "" ? 1 : 0
128   depends_on = [
129     aws_internet_gateway.internet_gw[0]
130   ]
131 }
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
137 }
138 resource "aws_route_table" "private_subnet_rt" {
139   count = var.private_subnet_id == "" ? 1 : 0
140   vpc_id = local.arvados_vpc_id
141   route {
142     cidr_block = "0.0.0.0/0"
143     nat_gateway_id = aws_nat_gateway.nat_gw[0].id
144   }
145 }
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
150 }
151
152 resource "aws_security_group" "arvados_sg" {
153   name = "arvados_sg"
154   count = var.sg_id == "" ? 1 : 0
155   vpc_id = aws_vpc.arvados_vpc[0].id
156
157   lifecycle {
158     precondition {
159       condition = (var.vpc_id == "")
160       error_message = "sg_id should be set if vpc_id is set"
161     }
162   }
163
164   dynamic "ingress" {
165     for_each = local.allowed_ports
166     content {
167       description = "Ingress rule for ${ingress.key}"
168       from_port = "${ingress.value}"
169       to_port = "${ingress.value}"
170       protocol = "tcp"
171       cidr_blocks = ["0.0.0.0/0"]
172       ipv6_cidr_blocks = ["::/0"]
173     }
174   }
175   # Allows communication between nodes in the VPC
176   ingress {
177     from_port = 0
178     to_port = 0
179     protocol = "-1"
180     cidr_blocks = [ aws_vpc.arvados_vpc[0].cidr_block ]
181   }
182   # Even though AWS auto-creates an "allow all" egress rule,
183   # Terraform deletes it, so we add it explicitly.
184   egress {
185     from_port = 0
186     to_port = 0
187     protocol = "-1"
188     cidr_blocks = ["0.0.0.0/0"]
189     ipv6_cidr_blocks = ["::/0"]
190   }
191 }
192
193 #
194 # Route53 split-horizon DNS zones
195 #
196
197 # PUBLIC DNS
198 resource "aws_route53_zone" "public_zone" {
199   count = var.private_only ? 0 : 1
200   name = var.domain_name
201 }
202 resource "aws_route53_record" "public_a_record" {
203   zone_id = try(local.route53_public_zone.id, "")
204   for_each = local.public_ip
205   name = each.key
206   type = "A"
207   ttl = 300
208   records = [ each.value ]
209 }
210 resource "aws_route53_record" "main_a_record" {
211   count = var.private_only ? 0 : 1
212   zone_id = try(local.route53_public_zone.id, "")
213   name = ""
214   type = "A"
215   ttl = 300
216   records = [ local.public_ip["controller"] ]
217 }
218 resource "aws_route53_record" "public_cname_record" {
219   zone_id = try(local.route53_public_zone.id, "")
220   for_each = {
221     for i in local.cname_by_host: i.record =>
222       "${i.cname}.${var.domain_name}"
223     if var.private_only == false
224   }
225   name = each.key
226   type = "CNAME"
227   ttl = 300
228   records = [ each.value ]
229 }
230
231 # PRIVATE DNS
232 resource "aws_route53_zone" "private_zone" {
233   name = var.domain_name
234   vpc {
235     vpc_id = local.arvados_vpc_id
236   }
237 }
238 resource "aws_route53_record" "private_a_record" {
239   zone_id = aws_route53_zone.private_zone.id
240   for_each = local.private_ip
241   name = each.key
242   type = "A"
243   ttl = 300
244   records = [ each.value ]
245 }
246 resource "aws_route53_record" "private_main_a_record" {
247   zone_id = aws_route53_zone.private_zone.id
248   name = ""
249   type = "A"
250   ttl = 300
251   records = [ local.private_ip["controller"] ]
252 }
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}" }
256   name = each.key
257   type = "CNAME"
258   ttl = 300
259   records = [ each.value ]
260 }
261
262 #
263 # Route53's credentials for Let's Encrypt
264 #
265 resource "aws_iam_user" "letsencrypt" {
266   count = var.private_only ? 0 : 1
267   name = "${var.cluster_name}-letsencrypt"
268   path = "/"
269 }
270
271 resource "aws_iam_access_key" "letsencrypt" {
272   count = var.private_only ? 0 : 1
273   user = local.iam_user_letsencrypt.name
274 }
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",
281     "Statement": [{
282       "Effect": "Allow",
283       "Action": [
284         "route53:ListHostedZones",
285         "route53:GetChange"
286       ],
287       "Resource": [
288           "*"
289       ]
290     },{
291       "Effect" : "Allow",
292       "Action" : [
293         "route53:ChangeResourceRecordSets"
294       ],
295       "Resource" : [
296         "arn:aws:route53:::hostedzone/${local.route53_public_zone.id}"
297       ]
298     }]
299   })
300 }
301