19215: Adds initial version of terraform code for multi-host deploy in AWS.
authorLucas Di Pentima <lucas.dipentima@curii.com>
Wed, 23 Nov 2022 01:29:27 +0000 (22:29 -0300)
committerLucas Di Pentima <lucas.dipentima@curii.com>
Fri, 25 Nov 2022 14:00:35 +0000 (11:00 -0300)
It's separated in 3 sections: vpc, data-storage & services. This is to limit
the 'blast radius' of a potential error when applying changes, as recommended
in many places.
Each state should be applied in the order described above, and their outputs
feed the following states with important data.
The shared 'terraform.tfvars' file allows the operator to customize their
deployment.

Arvados-DCO-1.1-Signed-off-by: Lucas Di Pentima <lucas.dipentima@curii.com>

23 files changed:
tools/salt-install/terraform/aws/.gitignore [new file with mode: 0644]
tools/salt-install/terraform/aws/assumerolepolicy.json [new file with mode: 0644]
tools/salt-install/terraform/aws/data-storage/.terraform.lock.hcl [new file with mode: 0644]
tools/salt-install/terraform/aws/data-storage/data.tf [new file with mode: 0644]
tools/salt-install/terraform/aws/data-storage/locals.tf [new file with mode: 0644]
tools/salt-install/terraform/aws/data-storage/main.tf [new file with mode: 0644]
tools/salt-install/terraform/aws/data-storage/outputs.tf [new file with mode: 0644]
tools/salt-install/terraform/aws/data-storage/terraform.tfvars [new file with mode: 0644]
tools/salt-install/terraform/aws/data-storage/variables.tf [new file with mode: 0644]
tools/salt-install/terraform/aws/services/.terraform.lock.hcl [new file with mode: 0644]
tools/salt-install/terraform/aws/services/data.tf [new file with mode: 0644]
tools/salt-install/terraform/aws/services/locals.tf [new file with mode: 0644]
tools/salt-install/terraform/aws/services/main.tf [new file with mode: 0644]
tools/salt-install/terraform/aws/services/outputs.tf [new file with mode: 0644]
tools/salt-install/terraform/aws/services/terraform.tfvars [new file with mode: 0644]
tools/salt-install/terraform/aws/services/user_data.sh [new file with mode: 0644]
tools/salt-install/terraform/aws/services/variables.tf [new file with mode: 0644]
tools/salt-install/terraform/aws/vpc/.terraform.lock.hcl [new file with mode: 0644]
tools/salt-install/terraform/aws/vpc/locals.tf [new file with mode: 0644]
tools/salt-install/terraform/aws/vpc/main.tf [new file with mode: 0644]
tools/salt-install/terraform/aws/vpc/outputs.tf [new file with mode: 0644]
tools/salt-install/terraform/aws/vpc/terraform.tfvars [new file with mode: 0644]
tools/salt-install/terraform/aws/vpc/variables.tf [new file with mode: 0644]

diff --git a/tools/salt-install/terraform/aws/.gitignore b/tools/salt-install/terraform/aws/.gitignore
new file mode 100644 (file)
index 0000000..5454e9c
--- /dev/null
@@ -0,0 +1,3 @@
+**/.terraform
+**/terraform.tfstate*
+**/.infracost
diff --git a/tools/salt-install/terraform/aws/assumerolepolicy.json b/tools/salt-install/terraform/aws/assumerolepolicy.json
new file mode 100644 (file)
index 0000000..8b9ed69
--- /dev/null
@@ -0,0 +1,13 @@
+{
+    "Version": "2012-10-17",
+    "Statement": [
+      {
+        "Action": "sts:AssumeRole",
+        "Principal": {
+          "Service": "ec2.amazonaws.com"
+        },
+        "Effect": "Allow",
+        "Sid": ""
+      }
+    ]
+}
\ No newline at end of file
diff --git a/tools/salt-install/terraform/aws/data-storage/.terraform.lock.hcl b/tools/salt-install/terraform/aws/data-storage/.terraform.lock.hcl
new file mode 100644 (file)
index 0000000..473eebc
--- /dev/null
@@ -0,0 +1,42 @@
+# This file is maintained automatically by "terraform init".
+# Manual edits may be lost in future updates.
+
+provider "registry.terraform.io/hashicorp/aws" {
+  version = "4.38.0"
+  hashes = [
+    "h1:LympybKZJE3L0H12nMmDnFH1iexD9S2GqZbDMo4fuPI=",
+    "h1:bhDPZioOF9Uz9mavezCHfYbD5YJ3fEPsixLpcWgV/kU=",
+    "zh:0ae61458acf7acecf47f7a02e08da1f7adeee9532e053c0d80432f16197e4799",
+    "zh:1ece9bcef41ffc75e0955419d7f8b1708ab7ffe4518bc9a2afe3bc5c79a9e79b",
+    "zh:302065a7c3ae798345b92a465b650b025d9c4e9abc3e78421ecc69a17b8c3d6a",
+    "zh:52d61f6a3ed6726b821a78f1fb78df818cf24a4d2378cc16afded297b37d4b7b",
+    "zh:6c365ed0cae031acdbcca04560997589a94629269cb456d468cbe51a3a020386",
+    "zh:70987a51d782f3458f124efea320157a48453864c420421051c56d41e463a948",
+    "zh:8b5a5f30240c67e596a89ccd76aa81133e6ae253c8a06a932b8901ef2b4a7486",
+    "zh:9b12af85486a96aedd8d7984b0ff811a4b42e3d88dad1a3fb4c0b580d04fa425",
+    "zh:d672167515ece7c2db4663faf180dfb6cfc6dbf5e149f868d05c39bb54b9ca03",
+    "zh:df1bc9926674b2e1246c9ebffd8bf8c4e380f50910a7f0b3ded957e8768ae27a",
+    "zh:e304b6e2bd66e7992326aa0446152547eb97e8f77d00bc1a9096022ac37e5d71",
+    "zh:f033690f11446af1383ad74149f429fae19e2784af5e151a22f46965dff21b29",
+  ]
+}
+
+provider "registry.terraform.io/hashicorp/external" {
+  version = "2.2.2"
+  hashes = [
+    "h1:VUkgcWvCliS0HO4kt7oEQhFD2gcx/59XpwMqxfCU1kE=",
+    "h1:e7RpnZ2PbJEEPnfsg7V0FNwbfSk0/Z3FdrLsXINBmDY=",
+    "zh:0b84ab0af2e28606e9c0c1289343949339221c3ab126616b831ddb5aaef5f5ca",
+    "zh:10cf5c9b9524ca2e4302bf02368dc6aac29fb50aeaa6f7758cce9aa36ae87a28",
+    "zh:56a016ee871c8501acb3f2ee3b51592ad7c3871a1757b098838349b17762ba6b",
+    "zh:719d6ef39c50e4cffc67aa67d74d195adaf42afcf62beab132dafdb500347d39",
+    "zh:78d5eefdd9e494defcb3c68d282b8f96630502cac21d1ea161f53cfe9bb483b3",
+    "zh:7fbfc4d37435ac2f717b0316f872f558f608596b389b895fcb549f118462d327",
+    "zh:8ac71408204db606ce63fe8f9aeaf1ddc7751d57d586ec421e62d440c402e955",
+    "zh:a4cacdb06f114454b6ed0033add28006afa3f65a0ea7a43befe45fc82e6809fb",
+    "zh:bb5ce3132b52ae32b6cc005bc9f7627b95259b9ffe556de4dad60d47d47f21f0",
+    "zh:bb60d2976f125ffd232a7ccb4b3f81e7109578b23c9c6179f13a11d125dca82a",
+    "zh:f9540ecd2e056d6e71b9ea5f5a5cf8f63dd5c25394b9db831083a9d4ea99b372",
+    "zh:ffd998b55b8a64d4335a090b6956b4bf8855b290f7554dd38db3302de9c41809",
+  ]
+}
diff --git a/tools/salt-install/terraform/aws/data-storage/data.tf b/tools/salt-install/terraform/aws/data-storage/data.tf
new file mode 100644 (file)
index 0000000..17fd9d3
--- /dev/null
@@ -0,0 +1,10 @@
+# Copyright (C) The Arvados Authors. All rights reserved.
+#
+# SPDX-License-Identifier: CC-BY-SA-3.0
+
+data "terraform_remote_state" "vpc" {
+  backend = "local"
+  config = {
+    path = "../vpc/terraform.tfstate"
+  }
+}
diff --git a/tools/salt-install/terraform/aws/data-storage/locals.tf b/tools/salt-install/terraform/aws/data-storage/locals.tf
new file mode 100644 (file)
index 0000000..5b9f689
--- /dev/null
@@ -0,0 +1,8 @@
+# Copyright (C) The Arvados Authors. All rights reserved.
+#
+# SPDX-License-Identifier: CC-BY-SA-3.0
+
+locals {
+  region_name = data.terraform_remote_state.vpc.outputs.region_name
+  cluster_name = data.terraform_remote_state.vpc.outputs.cluster_name
+}
diff --git a/tools/salt-install/terraform/aws/data-storage/main.tf b/tools/salt-install/terraform/aws/data-storage/main.tf
new file mode 100644 (file)
index 0000000..d4a3a7d
--- /dev/null
@@ -0,0 +1,69 @@
+# Copyright (C) The Arvados Authors. All rights reserved.
+#
+# SPDX-License-Identifier: CC-BY-SA-3.0
+
+terraform {
+  required_providers {
+    aws = {
+      source = "hashicorp/aws"
+    }
+  }
+}
+
+provider "aws" {
+  region = local.region_name
+  default_tags {
+    tags = {
+      Arvados = local.cluster_name
+    }
+  }
+}
+
+# S3 bucket and access resources for Keep blocks
+resource "aws_s3_bucket" "keep_volume" {
+  bucket = "${local.cluster_name}-nyw5e-000000000000000-volume"
+}
+
+resource "aws_s3_bucket_acl" "keep_volume_acl" {
+  bucket = aws_s3_bucket.keep_volume.id
+  acl = "private"
+}
+
+# Avoid direct public access to Keep blocks
+resource "aws_s3_bucket_public_access_block" "keep_volume_public_access" {
+  bucket = aws_s3_bucket.keep_volume.id
+
+  block_public_acls   = true
+  block_public_policy = true
+  ignore_public_acls  = true
+}
+
+resource "aws_iam_role" "keepstore_iam_role" {
+  name = "${local.cluster_name}-keepstore-00-iam-role"
+  assume_role_policy = "${file("../assumerolepolicy.json")}"
+}
+
+resource "aws_iam_policy" "s3_full_access" {
+  name = "${local.cluster_name}_s3_full_access"
+  policy = jsonencode({
+    Version: "2012-10-17",
+    Id: "arvados-keepstore policy",
+    Statement: [{
+      Effect: "Allow",
+      Action: [
+        "s3:*",
+      ],
+      Resource: [
+        "arn:aws:s3:::${local.cluster_name}-nyw5e-000000000000000-volume",
+        "arn:aws:s3:::${local.cluster_name}-nyw5e-000000000000000-volume/*"
+      ]
+    }]
+  })
+}
+
+resource "aws_iam_policy_attachment" "s3_full_access_policy_attachment" {
+  name = "${local.cluster_name}_s3_full_access_attachment"
+  roles = [ aws_iam_role.keepstore_iam_role.name ]
+  policy_arn = aws_iam_policy.s3_full_access.arn
+}
+
diff --git a/tools/salt-install/terraform/aws/data-storage/outputs.tf b/tools/salt-install/terraform/aws/data-storage/outputs.tf
new file mode 100644 (file)
index 0000000..6298f92
--- /dev/null
@@ -0,0 +1,11 @@
+# Copyright (C) The Arvados Authors. All rights reserved.
+#
+# SPDX-License-Identifier: CC-BY-SA-3.0
+
+output "keepstore_iam_role_name" {
+  value = aws_iam_role.keepstore_iam_role.name
+}
+
+output "use_external_db" {
+  value = var.use_external_db
+}
\ No newline at end of file
diff --git a/tools/salt-install/terraform/aws/data-storage/terraform.tfvars b/tools/salt-install/terraform/aws/data-storage/terraform.tfvars
new file mode 100644 (file)
index 0000000..6515339
--- /dev/null
@@ -0,0 +1,7 @@
+# Copyright (C) The Arvados Authors. All rights reserved.
+#
+# SPDX-License-Identifier: CC-BY-SA-3.0
+
+# Set to true if the database server won't be running in any service instance.
+# Default: false
+# use_external_db = true
diff --git a/tools/salt-install/terraform/aws/data-storage/variables.tf b/tools/salt-install/terraform/aws/data-storage/variables.tf
new file mode 100644 (file)
index 0000000..45e1531
--- /dev/null
@@ -0,0 +1,15 @@
+# Copyright (C) The Arvados Authors. All rights reserved.
+#
+# SPDX-License-Identifier: CC-BY-SA-3.0
+
+variable "use_external_db" {
+  description = "Enable this if the database service won't be installed on these instances"
+  type = bool
+  default = false
+}
+
+variable "keep_cluster_data" {
+  description = "Avoids state (database & keep blocks) to be destroyed. Needed for production clusters"
+  type = bool
+  default = false
+}
diff --git a/tools/salt-install/terraform/aws/services/.terraform.lock.hcl b/tools/salt-install/terraform/aws/services/.terraform.lock.hcl
new file mode 100644 (file)
index 0000000..63116a5
--- /dev/null
@@ -0,0 +1,22 @@
+# This file is maintained automatically by "terraform init".
+# Manual edits may be lost in future updates.
+
+provider "registry.terraform.io/hashicorp/aws" {
+  version = "4.38.0"
+  hashes = [
+    "h1:LympybKZJE3L0H12nMmDnFH1iexD9S2GqZbDMo4fuPI=",
+    "h1:bhDPZioOF9Uz9mavezCHfYbD5YJ3fEPsixLpcWgV/kU=",
+    "zh:0ae61458acf7acecf47f7a02e08da1f7adeee9532e053c0d80432f16197e4799",
+    "zh:1ece9bcef41ffc75e0955419d7f8b1708ab7ffe4518bc9a2afe3bc5c79a9e79b",
+    "zh:302065a7c3ae798345b92a465b650b025d9c4e9abc3e78421ecc69a17b8c3d6a",
+    "zh:52d61f6a3ed6726b821a78f1fb78df818cf24a4d2378cc16afded297b37d4b7b",
+    "zh:6c365ed0cae031acdbcca04560997589a94629269cb456d468cbe51a3a020386",
+    "zh:70987a51d782f3458f124efea320157a48453864c420421051c56d41e463a948",
+    "zh:8b5a5f30240c67e596a89ccd76aa81133e6ae253c8a06a932b8901ef2b4a7486",
+    "zh:9b12af85486a96aedd8d7984b0ff811a4b42e3d88dad1a3fb4c0b580d04fa425",
+    "zh:d672167515ece7c2db4663faf180dfb6cfc6dbf5e149f868d05c39bb54b9ca03",
+    "zh:df1bc9926674b2e1246c9ebffd8bf8c4e380f50910a7f0b3ded957e8768ae27a",
+    "zh:e304b6e2bd66e7992326aa0446152547eb97e8f77d00bc1a9096022ac37e5d71",
+    "zh:f033690f11446af1383ad74149f429fae19e2784af5e151a22f46965dff21b29",
+  ]
+}
diff --git a/tools/salt-install/terraform/aws/services/data.tf b/tools/salt-install/terraform/aws/services/data.tf
new file mode 100644 (file)
index 0000000..3587f25
--- /dev/null
@@ -0,0 +1,31 @@
+# Copyright (C) The Arvados Authors. All rights reserved.
+#
+# SPDX-License-Identifier: CC-BY-SA-3.0
+
+data "terraform_remote_state" "vpc" {
+  backend = "local"
+  config = {
+    path = "../vpc/terraform.tfstate"
+  }
+}
+
+data "terraform_remote_state" "data-storage" {
+  backend = "local"
+  config = {
+    path = "../data-storage/terraform.tfstate"
+  }
+}
+
+# https://wiki.debian.org/Cloud/AmazonEC2Image/Bullseye
+data "aws_ami" "debian-11" {
+  most_recent = true
+  owners = ["136693071363"]
+  filter {
+    name   = "name"
+    values = ["debian-11-amd64-*"]
+  }
+  filter {
+    name   = "virtualization-type"
+    values = ["hvm"]
+  }
+}
\ No newline at end of file
diff --git a/tools/salt-install/terraform/aws/services/locals.tf b/tools/salt-install/terraform/aws/services/locals.tf
new file mode 100644 (file)
index 0000000..80dc337
--- /dev/null
@@ -0,0 +1,12 @@
+# Copyright (C) The Arvados Authors. All rights reserved.
+#
+# SPDX-License-Identifier: CC-BY-SA-3.0
+
+locals {
+  region_name = data.terraform_remote_state.vpc.outputs.region_name
+  cluster_name = data.terraform_remote_state.vpc.outputs.cluster_name
+  use_external_db = data.terraform_remote_state.data-storage.outputs.use_external_db
+  public_ip = data.terraform_remote_state.vpc.outputs.public_ip
+  private_ip = data.terraform_remote_state.vpc.outputs.private_ip
+  hostnames = [ for hostname, eip_id in data.terraform_remote_state.vpc.outputs.eip_id: hostname ]
+}
diff --git a/tools/salt-install/terraform/aws/services/main.tf b/tools/salt-install/terraform/aws/services/main.tf
new file mode 100644 (file)
index 0000000..911311f
--- /dev/null
@@ -0,0 +1,109 @@
+# Copyright (C) The Arvados Authors. All rights reserved.
+#
+# SPDX-License-Identifier: CC-BY-SA-3.0
+
+terraform {
+  required_providers {
+    aws = {
+      source = "hashicorp/aws"
+    }
+  }
+}
+
+provider "aws" {
+  region = local.region_name
+  default_tags {
+    tags = {
+      Arvados = local.cluster_name
+    }
+  }
+}
+
+locals {
+  pubkey_path = pathexpand(var.pubkey_path)
+  pubkey_name = "arvados-deployer-key"
+}
+resource "aws_key_pair" "deployer" {
+  key_name = local.pubkey_name
+  public_key = file(local.pubkey_path)
+}
+
+resource "aws_iam_instance_profile" "keepstore_instance_profile" {
+  name = "${local.cluster_name}-keepstore-00-iam-role"
+  role = data.terraform_remote_state.data-storage.outputs.keepstore_iam_role_name
+}
+
+resource "aws_iam_instance_profile" "dispatcher_instance_profile" {
+  name = "${local.cluster_name}_dispatcher_instance_profile"
+  role = aws_iam_role.cloud_dispatcher_iam_role.name
+}
+
+resource "aws_instance" "arvados_service" {
+  for_each = toset(local.hostnames)
+  ami = data.aws_ami.debian-11.image_id
+  instance_type = var.default_instance_type
+  key_name = local.pubkey_name
+  user_data = templatefile("user_data.sh", {
+    "hostname": each.value
+  })
+  private_ip = local.private_ip[each.value]
+  subnet_id = data.terraform_remote_state.vpc.outputs.arvados_subnet_id
+  vpc_security_group_ids = [ data.terraform_remote_state.vpc.outputs.arvados_sg_id ]
+  # This should be done in a more readable way
+  iam_instance_profile = each.value == "controller" ? aws_iam_instance_profile.dispatcher_instance_profile.name : length(regexall("^keep[0-9]+", each.value)) > 0 ? aws_iam_instance_profile.keepstore_instance_profile.name : ""
+  tags = {
+    Name = "arvados_service_${each.value}"
+  }
+  root_block_device {
+    volume_type = "gp3"
+    volume_size = (each.value == "controller" && !local.use_external_db) ? 70 : 20
+  }
+
+  lifecycle {
+    ignore_changes = [
+      # Avoids recreating the instance when the latest AMI changes.
+      # Use 'terraform taint' or 'terraform apply -replace' to force
+      # an AMI change.
+      ami,
+    ]
+  }
+}
+
+resource "aws_iam_policy" "cloud_dispatcher_ec2_access" {
+  name = "${local.cluster_name}_cloud_dispatcher_ec2_access"
+  policy = jsonencode({
+    Version: "2012-10-17",
+    Id: "arvados-dispatch-cloud policy",
+    Statement: [{
+      Effect: "Allow",
+      Action: [
+        "iam:PassRole",
+        "ec2:DescribeKeyPairs",
+        "ec2:ImportKeyPair",
+        "ec2:RunInstances",
+        "ec2:DescribeInstances",
+        "ec2:CreateTags",
+        "ec2:TerminateInstances"
+      ],
+      Resource: "*"
+    }]
+  })
+}
+
+resource "aws_iam_role" "cloud_dispatcher_iam_role" {
+  name = "${local.cluster_name}-dispatcher-00-iam-role"
+  assume_role_policy = "${file("../assumerolepolicy.json")}"
+}
+
+resource "aws_iam_policy_attachment" "cloud_dispatcher_ec2_access_attachment" {
+  name = "${local.cluster_name}_cloud_dispatcher_ec2_access_attachment"
+  roles = [ aws_iam_role.cloud_dispatcher_iam_role.name ]
+  policy_arn = aws_iam_policy.cloud_dispatcher_ec2_access.arn
+}
+
+resource "aws_eip_association" "eip_assoc" {
+  for_each = toset(local.hostnames)
+  instance_id = aws_instance.arvados_service[each.value].id
+  allocation_id = data.terraform_remote_state.vpc.outputs.eip_id[each.value]
+  # private_ip_address = local.private_ip[each.value]
+}
diff --git a/tools/salt-install/terraform/aws/services/outputs.tf b/tools/salt-install/terraform/aws/services/outputs.tf
new file mode 100644 (file)
index 0000000..96af405
--- /dev/null
@@ -0,0 +1,48 @@
+# Copyright (C) The Arvados Authors. All rights reserved.
+#
+# SPDX-License-Identifier: CC-BY-SA-3.0
+
+output "vpc_id" {
+  value = data.terraform_remote_state.vpc.outputs.arvados_vpc_id
+}
+
+output "vpc_cidr" {
+  value = data.terraform_remote_state.vpc.outputs.arvados_vpc_cidr
+}
+
+output "subnet_id" {
+  value = data.terraform_remote_state.vpc.outputs.arvados_subnet_id
+}
+
+output "arvados_sg_id" {
+  value = data.terraform_remote_state.vpc.outputs.arvados_sg_id
+}
+
+output "public_ip" {
+  value = local.public_ip
+}
+
+output "private_ip" {
+  value = local.private_ip
+}
+
+output "route53_dns_ns" {
+  value = data.terraform_remote_state.vpc.outputs.route53_dns_ns
+}
+
+output "letsencrypt_iam_access_key_id" {
+  value = data.terraform_remote_state.vpc.outputs.letsencrypt_iam_access_key_id
+}
+
+output "letsencrypt_iam_secret_access_key" {
+  value = data.terraform_remote_state.vpc.outputs.letsencrypt_iam_secret_access_key
+  sensitive = true
+}
+
+output "cluster_name" {
+  value = data.terraform_remote_state.vpc.outputs.cluster_name
+}
+
+output "domain_name" {
+  value = data.terraform_remote_state.vpc.outputs.domain_name
+}
diff --git a/tools/salt-install/terraform/aws/services/terraform.tfvars b/tools/salt-install/terraform/aws/services/terraform.tfvars
new file mode 100644 (file)
index 0000000..374ecbe
--- /dev/null
@@ -0,0 +1,9 @@
+# Copyright (C) The Arvados Authors. All rights reserved.
+#
+# SPDX-License-Identifier: CC-BY-SA-3.0
+
+# Set to a specific SSH public key path. Default: ~/.ssh/id_rsa.pub
+# pubkey_path = /path/to/pub.key
+
+# Set the instance type for your hosts. Default: m5a.large
+# default_instance_type = "t2.micro"
diff --git a/tools/salt-install/terraform/aws/services/user_data.sh b/tools/salt-install/terraform/aws/services/user_data.sh
new file mode 100644 (file)
index 0000000..6c5b574
--- /dev/null
@@ -0,0 +1,19 @@
+#!/bin/sh
+
+# Copyright (C) The Arvados Authors. All rights reserved.
+#
+# SPDX-License-Identifier: CC-BY-SA-3.0
+
+hostname ${hostname}
+echo ${hostname} > /etc/hostname
+
+# Retry just in case internet access is not yet ready
+while true; do
+  apt-get -o Acquire::ForceIPv4=true update
+  ERR=$?
+  if [ "$${ERR}" = "0" ]; then
+    break
+  fi
+done
+
+apt-get -o Acquire::ForceIPv4=true install -y git curl
diff --git a/tools/salt-install/terraform/aws/services/variables.tf b/tools/salt-install/terraform/aws/services/variables.tf
new file mode 100644 (file)
index 0000000..89b1886
--- /dev/null
@@ -0,0 +1,15 @@
+# Copyright (C) The Arvados Authors. All rights reserved.
+#
+# SPDX-License-Identifier: CC-BY-SA-3.0
+
+variable "default_instance_type" {
+  description = "The default EC2 instance type to use on the nodes"
+  type = string
+  default = "m5a.large"
+}
+
+variable "pubkey_path" {
+  description = "Path to the file containing the public SSH key"
+  type = string
+  default = "~/.ssh/id_rsa.pub"
+}
diff --git a/tools/salt-install/terraform/aws/vpc/.terraform.lock.hcl b/tools/salt-install/terraform/aws/vpc/.terraform.lock.hcl
new file mode 100644 (file)
index 0000000..63116a5
--- /dev/null
@@ -0,0 +1,22 @@
+# This file is maintained automatically by "terraform init".
+# Manual edits may be lost in future updates.
+
+provider "registry.terraform.io/hashicorp/aws" {
+  version = "4.38.0"
+  hashes = [
+    "h1:LympybKZJE3L0H12nMmDnFH1iexD9S2GqZbDMo4fuPI=",
+    "h1:bhDPZioOF9Uz9mavezCHfYbD5YJ3fEPsixLpcWgV/kU=",
+    "zh:0ae61458acf7acecf47f7a02e08da1f7adeee9532e053c0d80432f16197e4799",
+    "zh:1ece9bcef41ffc75e0955419d7f8b1708ab7ffe4518bc9a2afe3bc5c79a9e79b",
+    "zh:302065a7c3ae798345b92a465b650b025d9c4e9abc3e78421ecc69a17b8c3d6a",
+    "zh:52d61f6a3ed6726b821a78f1fb78df818cf24a4d2378cc16afded297b37d4b7b",
+    "zh:6c365ed0cae031acdbcca04560997589a94629269cb456d468cbe51a3a020386",
+    "zh:70987a51d782f3458f124efea320157a48453864c420421051c56d41e463a948",
+    "zh:8b5a5f30240c67e596a89ccd76aa81133e6ae253c8a06a932b8901ef2b4a7486",
+    "zh:9b12af85486a96aedd8d7984b0ff811a4b42e3d88dad1a3fb4c0b580d04fa425",
+    "zh:d672167515ece7c2db4663faf180dfb6cfc6dbf5e149f868d05c39bb54b9ca03",
+    "zh:df1bc9926674b2e1246c9ebffd8bf8c4e380f50910a7f0b3ded957e8768ae27a",
+    "zh:e304b6e2bd66e7992326aa0446152547eb97e8f77d00bc1a9096022ac37e5d71",
+    "zh:f033690f11446af1383ad74149f429fae19e2784af5e151a22f46965dff21b29",
+  ]
+}
diff --git a/tools/salt-install/terraform/aws/vpc/locals.tf b/tools/salt-install/terraform/aws/vpc/locals.tf
new file mode 100644 (file)
index 0000000..e8864c5
--- /dev/null
@@ -0,0 +1,36 @@
+# Copyright (C) The Arvados Authors. All rights reserved.
+#
+# SPDX-License-Identifier: CC-BY-SA-3.0
+
+locals {
+  allowed_ports = {
+    http: "80",
+    https: "443",
+    ssh: "22",
+  }
+  hostnames = [ "controller", "workbench", "keep0", "keep1", "keepproxy", "shell" ]
+  arvados_dns_zone = "${var.cluster_name}.${var.domain_name}"
+  public_ip = { for k, v in aws_eip.arvados_eip: k => v.public_ip }
+  private_ip = {
+    "controller": "10.1.1.1",
+    "workbench": "10.1.1.5",
+    "keepproxy": "10.1.1.2",
+    "shell": "10.1.1.7",
+    "keep0": "10.1.1.3",
+    "keep1": "10.1.1.4"
+  }
+  aliases = {
+    controller: ["ws"]
+    workbench: ["workbench2", "webshell"]
+    keepproxy: ["keep", "download", "*.collections"]
+  }
+  cname_by_host = flatten([
+    for host, aliases in local.aliases : [
+      for alias in aliases : {
+        record = alias
+        cname = host
+      }
+    ]
+  ])
+}
+
diff --git a/tools/salt-install/terraform/aws/vpc/main.tf b/tools/salt-install/terraform/aws/vpc/main.tf
new file mode 100644 (file)
index 0000000..4581d5b
--- /dev/null
@@ -0,0 +1,200 @@
+# Copyright (C) The Arvados Authors. All rights reserved.
+#
+# SPDX-License-Identifier: CC-BY-SA-3.0
+
+terraform {
+  required_providers {
+    aws = {
+      source = "hashicorp/aws"
+    }
+  }
+}
+
+provider "aws" {
+  region = var.region_name
+  default_tags {
+    tags = {
+      Arvados = var.cluster_name
+    }
+  }
+}
+
+resource "aws_vpc" "arvados_vpc" {
+  cidr_block = "10.1.0.0/16"
+  enable_dns_hostnames = true
+  enable_dns_support = true
+}
+resource "aws_subnet" "arvados_subnet" {
+  vpc_id = aws_vpc.arvados_vpc.id
+  availability_zone = "${var.region_name}a"
+  cidr_block = aws_vpc.arvados_vpc.cidr_block
+}
+
+#
+# VPC S3 access
+#
+resource "aws_vpc_endpoint" "s3" {
+  vpc_id = aws_vpc.arvados_vpc.id
+  service_name = "com.amazonaws.${var.region_name}.s3"
+}
+resource "aws_vpc_endpoint_route_table_association" "s3_route" {
+  vpc_endpoint_id = aws_vpc_endpoint.s3.id
+  route_table_id = aws_route_table.arvados_rt.id
+}
+
+#
+# VPC Internet access
+#
+resource "aws_internet_gateway" "arvados_gw" {
+  vpc_id = aws_vpc.arvados_vpc.id
+}
+resource "aws_eip" "arvados_eip" {
+  for_each = toset(local.hostnames)
+  depends_on = [
+    aws_internet_gateway.arvados_gw
+  ]
+}
+resource "aws_route_table" "arvados_rt" {
+  vpc_id = aws_vpc.arvados_vpc.id
+  route {
+    cidr_block = "0.0.0.0/0"
+    gateway_id = aws_internet_gateway.arvados_gw.id
+  }
+}
+resource "aws_route_table_association" "arvados_subnet_assoc" {
+  subnet_id = aws_subnet.arvados_subnet.id
+  route_table_id = aws_route_table.arvados_rt.id
+}
+resource "aws_security_group" "arvados_sg" {
+  name = "arvados_sg"
+  vpc_id = aws_vpc.arvados_vpc.id
+
+  dynamic "ingress" {
+    for_each = local.allowed_ports
+    content {
+      description = "Ingress rule for ${ingress.key}"
+      from_port = "${ingress.value}"
+      to_port = "${ingress.value}"
+      protocol = "tcp"
+      cidr_blocks = ["0.0.0.0/0"]
+      ipv6_cidr_blocks = ["::/0"]
+    }
+  }
+  # Allows communication between nodes in the VPC
+  ingress {
+    from_port = 0
+    to_port = 0
+    protocol = "-1"
+    cidr_blocks = [ aws_vpc.arvados_vpc.cidr_block ]
+  }
+  # Even though AWS auto-creates an "allow all" egress rule,
+  # Terraform deletes it, so we add it explicitly.
+  egress {
+    from_port = 0
+    to_port = 0
+    protocol = "-1"
+    cidr_blocks = ["0.0.0.0/0"]
+    ipv6_cidr_blocks = ["::/0"]
+  }
+}
+
+#
+# Route53 split-horizon DNS zones
+#
+
+# PUBLIC DNS
+resource "aws_route53_zone" "public_zone" {
+  name = local.arvados_dns_zone
+}
+resource "aws_route53_record" "public_a_record" {
+  zone_id = aws_route53_zone.public_zone.id
+  for_each = local.public_ip
+  name = each.key
+  type = "A"
+  ttl = 300
+  records = [ each.value ]
+}
+resource "aws_route53_record" "main_a_record" {
+  zone_id = aws_route53_zone.public_zone.id
+  name = ""
+  type = "A"
+  ttl = 300
+  records = [ local.public_ip["controller"] ]
+}
+resource "aws_route53_record" "public_cname_record" {
+  zone_id = aws_route53_zone.public_zone.id
+  for_each = {for i in local.cname_by_host: i.record => "${i.cname}.${local.arvados_dns_zone}" }
+  name = each.key
+  type = "CNAME"
+  ttl = 300
+  records = [ each.value ]
+}
+
+# PRIVATE DNS
+resource "aws_route53_zone" "private_zone" {
+  name = local.arvados_dns_zone
+  vpc {
+    vpc_id = aws_vpc.arvados_vpc.id
+  }
+}
+resource "aws_route53_record" "private_a_record" {
+  zone_id = aws_route53_zone.private_zone.id
+  for_each = local.private_ip
+  name = each.key
+  type = "A"
+  ttl = 300
+  records = [ each.value ]
+}
+resource "aws_route53_record" "private_main_a_record" {
+  zone_id = aws_route53_zone.private_zone.id
+  name = ""
+  type = "A"
+  ttl = 300
+  records = [ local.private_ip["controller"] ]
+}
+resource "aws_route53_record" "private_cname_record" {
+  zone_id = aws_route53_zone.private_zone.id
+  for_each = {for i in local.cname_by_host: i.record => "${i.cname}.${local.arvados_dns_zone}" }
+  name = each.key
+  type = "CNAME"
+  ttl = 300
+  records = [ each.value ]
+}
+
+#
+# Route53's credentials for Let's Encrypt
+#
+resource "aws_iam_user" "letsencrypt" {
+  name = "${var.cluster_name}-letsencrypt"
+  path = "/"
+}
+
+resource "aws_iam_access_key" "letsencrypt" {
+  user = aws_iam_user.letsencrypt.name
+}
+resource "aws_iam_user_policy" "letsencrypt_iam_policy" {
+  name = "${var.cluster_name}-letsencrypt_iam_policy"
+  user = aws_iam_user.letsencrypt.name
+  policy = jsonencode({
+    "Version": "2012-10-17",
+    "Statement": [{
+      "Effect": "Allow",
+      "Action": [
+        "route53:ListHostedZones",
+        "route53:GetChange"
+      ],
+      "Resource": [
+          "*"
+      ]
+    },{
+      "Effect" : "Allow",
+      "Action" : [
+        "route53:ChangeResourceRecordSets"
+      ],
+      "Resource" : [
+        "arn:aws:route53:::hostedzone/${aws_route53_zone.public_zone.id}"
+      ]
+    }]
+  })
+}
+
diff --git a/tools/salt-install/terraform/aws/vpc/outputs.tf b/tools/salt-install/terraform/aws/vpc/outputs.tf
new file mode 100644 (file)
index 0000000..4ae90a5
--- /dev/null
@@ -0,0 +1,55 @@
+# Copyright (C) The Arvados Authors. All rights reserved.
+#
+# SPDX-License-Identifier: CC-BY-SA-3.0
+
+output "arvados_vpc_id" {
+  value = aws_vpc.arvados_vpc.id
+}
+output "arvados_vpc_cidr" {
+  value = aws_vpc.arvados_vpc.cidr_block
+}
+
+output "arvados_subnet_id" {
+  value = aws_subnet.arvados_subnet.id
+}
+
+output "arvados_sg_id" {
+  value = aws_security_group.arvados_sg.id
+}
+
+output "eip_id" {
+  value = { for k, v in aws_eip.arvados_eip: k => v.id }
+}
+
+output "public_ip" {
+  value = local.public_ip
+}
+
+output "private_ip" {
+  value = local.private_ip
+}
+
+output "route53_dns_ns" {
+  value = aws_route53_zone.public_zone.name_servers
+}
+
+output "letsencrypt_iam_access_key_id" {
+  value = aws_iam_access_key.letsencrypt.id
+}
+
+output "letsencrypt_iam_secret_access_key" {
+  value = aws_iam_access_key.letsencrypt.secret
+  sensitive = true
+}
+
+output "region_name" {
+  value = var.region_name
+}
+
+output "cluster_name" {
+  value = var.cluster_name
+}
+
+output "domain_name" {
+  value = var.domain_name
+}
diff --git a/tools/salt-install/terraform/aws/vpc/terraform.tfvars b/tools/salt-install/terraform/aws/vpc/terraform.tfvars
new file mode 100644 (file)
index 0000000..cac62ed
--- /dev/null
@@ -0,0 +1,7 @@
+# Copyright (C) The Arvados Authors. All rights reserved.
+#
+# SPDX-License-Identifier: CC-BY-SA-3.0
+
+region_name = "us-east-1"
+# cluster_name = "xarv1"
+# domain_name = "example.com"
diff --git a/tools/salt-install/terraform/aws/vpc/variables.tf b/tools/salt-install/terraform/aws/vpc/variables.tf
new file mode 100644 (file)
index 0000000..4237c56
--- /dev/null
@@ -0,0 +1,22 @@
+# Copyright (C) The Arvados Authors. All rights reserved.
+#
+# SPDX-License-Identifier: CC-BY-SA-3.0
+
+variable "region_name" {
+  description = "Name of the AWS Region where to install Arvados"
+  type = string
+}
+
+variable "cluster_name" {
+  description = "A 5-char alphanum identifier for your Arvados cluster"
+  type = string
+  validation {
+    condition = length(var.cluster_name) == 5
+    error_message = "cluster_name should be 5 chars long."
+  }
+}
+
+variable "domain_name" {
+  description = "The domain name under which your Arvados cluster will be hosted"
+  type = string
+}
\ No newline at end of file