485 lines
16 KiB
Terraform
485 lines
16 KiB
Terraform
data "aws_vpc" "default" {
|
|
count = var.vpc_id == null ? 1 : 0
|
|
default = true
|
|
}
|
|
|
|
data "aws_subnets" "default" {
|
|
count = var.subnet_id == null ? 1 : 0
|
|
|
|
filter {
|
|
name = "vpc-id"
|
|
values = [local.vpc_id]
|
|
}
|
|
}
|
|
|
|
data "aws_subnet" "selected" {
|
|
id = local.subnet_id
|
|
}
|
|
|
|
data "aws_ssm_parameter" "ami" {
|
|
count = var.ami_id == null && var.ami_ssm_parameter_name != null ? 1 : 0
|
|
name = var.ami_ssm_parameter_name
|
|
}
|
|
|
|
data "terraform_remote_state" "nix_cache" {
|
|
count = var.nix_cache_enable ? 1 : 0
|
|
backend = "s3"
|
|
|
|
config = {
|
|
bucket = var.nix_cache_state_bucket
|
|
key = var.nix_cache_state_key
|
|
region = var.nix_cache_state_region
|
|
}
|
|
}
|
|
|
|
data "external" "nix_cache_public_key" {
|
|
count = var.nix_cache_enable && var.nix_cache_public_key == null ? 1 : 0
|
|
program = ["${path.module}/read-optional-ssm.sh"]
|
|
|
|
query = {
|
|
name = data.terraform_remote_state.nix_cache[0].outputs.public_key_parameter_name
|
|
}
|
|
}
|
|
|
|
resource "random_id" "central" {
|
|
byte_length = 4
|
|
|
|
lifecycle {
|
|
precondition {
|
|
condition = !var.nix_cache_enable || var.nix_cache_bootstrap_optional || var.nix_cache_public_key != null || try(data.external.nix_cache_public_key[0].result.value != "", false)
|
|
error_message = "nix_cache_enable is true, but the nix-cache public key is not available. Apply tofu/nix-cache and wait for it to publish /quixos/nix-cache/public-key, set nix_cache_public_key explicitly, or set nix_cache_bootstrap_optional=true for the first bootstrap apply."
|
|
}
|
|
}
|
|
}
|
|
|
|
locals {
|
|
vpc_id = var.vpc_id == null ? data.aws_vpc.default[0].id : var.vpc_id
|
|
subnet_id = var.subnet_id == null ? data.aws_subnets.default[0].ids[0] : var.subnet_id
|
|
resource_name = "${var.name}-${random_id.central.hex}"
|
|
bucket_name = var.runtime_state_bucket_name == null ? "${local.resource_name}-runtime-state" : var.runtime_state_bucket_name
|
|
base_domain = trimsuffix(var.base_domain, ".")
|
|
app_server_domain = var.app_server_domain == null ? local.base_domain : trimsuffix(var.app_server_domain, ".")
|
|
app_wildcard_domain = "app.${local.base_domain}"
|
|
dev_app_wildcard_domain = "dev.app.${local.base_domain}"
|
|
central_ami_id = var.ami_id == null && var.ami_ssm_parameter_name != null ? data.aws_ssm_parameter.ami[0].value : var.ami_id
|
|
app_server_ami_id = var.app_server_ami_id == null ? local.central_ami_id : var.app_server_ami_id
|
|
app_server_enabled = var.app_server_ami_id != null || var.ami_id != null || var.ami_ssm_parameter_name != null
|
|
app_server_flake_uri = "git+${var.app_server_repo_url}?dir=services/app-server&${var.app_server_ref_is_rev ? "rev" : "ref"}=${urlencode(var.app_server_ref)}"
|
|
app_server_persist_device = "/dev/disk/by-id/nvme-Amazon_Elastic_Block_Store_${replace(aws_ebs_volume.persist.id, "-", "")}"
|
|
nix_cache_outputs = var.nix_cache_enable ? data.terraform_remote_state.nix_cache[0].outputs : null
|
|
nix_cache_hosts_parts = var.nix_cache_enable ? split(" ", local.nix_cache_outputs.hosts_entry) : []
|
|
nix_cache_host_address = var.nix_cache_enable ? local.nix_cache_hosts_parts[0] : ""
|
|
nix_cache_domain = var.nix_cache_enable ? local.nix_cache_outputs.domain : ""
|
|
nix_cache_endpoint = var.nix_cache_enable ? "http://${local.nix_cache_outputs.domain}" : ""
|
|
nix_cache_substituter = var.nix_cache_enable ? local.nix_cache_outputs.cache_url : ""
|
|
nix_cache_cache_name = var.nix_cache_enable ? trimprefix(local.nix_cache_outputs.cache_url, "http://${local.nix_cache_outputs.domain}/") : ""
|
|
nix_cache_public_key = var.nix_cache_enable ? (var.nix_cache_public_key != null ? var.nix_cache_public_key : data.external.nix_cache_public_key[0].result.value) : ""
|
|
nix_cache_push_parameter = var.nix_cache_enable ? local.nix_cache_outputs.central_push_token_parameter_name : ""
|
|
nix_cache_ready = var.nix_cache_enable && local.nix_cache_host_address != "" && local.nix_cache_public_key != ""
|
|
platform_vpc_id = var.platform_vpc_enable ? aws_vpc.platform[0].id : local.vpc_id
|
|
platform_public_subnet_id = var.platform_vpc_enable ? aws_subnet.central_public[0].id : local.subnet_id
|
|
platform_runtime_subnet_id = var.platform_vpc_enable ? aws_subnet.runtime_public[0].id : local.subnet_id
|
|
tags = {
|
|
Name = local.resource_name
|
|
Project = var.name
|
|
Role = "central-infrastructure"
|
|
"quixos:managed-by" = "tofu/central"
|
|
"quixos:project" = var.name
|
|
}
|
|
}
|
|
|
|
resource "aws_vpc" "platform" {
|
|
count = var.platform_vpc_enable ? 1 : 0
|
|
cidr_block = var.platform_vpc_cidr
|
|
assign_generated_ipv6_cidr_block = true
|
|
enable_dns_hostnames = true
|
|
enable_dns_support = true
|
|
|
|
tags = merge(local.tags, {
|
|
Name = "${local.resource_name}-vpc"
|
|
Role = "platform-vpc"
|
|
})
|
|
}
|
|
|
|
resource "aws_internet_gateway" "platform" {
|
|
count = var.platform_vpc_enable ? 1 : 0
|
|
vpc_id = aws_vpc.platform[0].id
|
|
|
|
tags = merge(local.tags, {
|
|
Name = "${local.resource_name}-igw"
|
|
Role = "platform-internet-gateway"
|
|
})
|
|
}
|
|
|
|
resource "aws_subnet" "central_public" {
|
|
count = var.platform_vpc_enable ? 1 : 0
|
|
vpc_id = aws_vpc.platform[0].id
|
|
availability_zone = data.aws_subnet.selected.availability_zone
|
|
cidr_block = var.platform_public_subnet_cidr
|
|
ipv6_cidr_block = cidrsubnet(aws_vpc.platform[0].ipv6_cidr_block, 8, 0)
|
|
assign_ipv6_address_on_creation = true
|
|
map_public_ip_on_launch = true
|
|
|
|
tags = merge(local.tags, {
|
|
Name = "${local.resource_name}-central-public"
|
|
Role = "central-public-subnet"
|
|
})
|
|
}
|
|
|
|
resource "aws_subnet" "runtime_public" {
|
|
count = var.platform_vpc_enable ? 1 : 0
|
|
vpc_id = aws_vpc.platform[0].id
|
|
availability_zone = data.aws_subnet.selected.availability_zone
|
|
cidr_block = var.platform_runtime_subnet_cidr
|
|
ipv6_cidr_block = cidrsubnet(aws_vpc.platform[0].ipv6_cidr_block, 8, 1)
|
|
assign_ipv6_address_on_creation = true
|
|
map_public_ip_on_launch = true
|
|
|
|
tags = merge(local.tags, {
|
|
Name = "${local.resource_name}-runtime-public"
|
|
Role = "runtime-public-egress-subnet"
|
|
})
|
|
}
|
|
|
|
resource "aws_route_table" "platform_public" {
|
|
count = var.platform_vpc_enable ? 1 : 0
|
|
vpc_id = aws_vpc.platform[0].id
|
|
|
|
route {
|
|
cidr_block = "0.0.0.0/0"
|
|
gateway_id = aws_internet_gateway.platform[0].id
|
|
}
|
|
|
|
route {
|
|
ipv6_cidr_block = "::/0"
|
|
gateway_id = aws_internet_gateway.platform[0].id
|
|
}
|
|
|
|
tags = merge(local.tags, {
|
|
Name = "${local.resource_name}-public-rt"
|
|
Role = "platform-public-route-table"
|
|
})
|
|
}
|
|
|
|
resource "aws_route_table_association" "central_public" {
|
|
count = var.platform_vpc_enable ? 1 : 0
|
|
subnet_id = aws_subnet.central_public[0].id
|
|
route_table_id = aws_route_table.platform_public[0].id
|
|
}
|
|
|
|
resource "aws_route_table_association" "runtime_public" {
|
|
count = var.platform_vpc_enable ? 1 : 0
|
|
subnet_id = aws_subnet.runtime_public[0].id
|
|
route_table_id = aws_route_table.platform_public[0].id
|
|
}
|
|
|
|
resource "aws_s3_bucket" "runtime_state" {
|
|
bucket = local.bucket_name
|
|
force_destroy = var.runtime_state_bucket_force_destroy
|
|
|
|
tags = merge(local.tags, {
|
|
Name = local.bucket_name
|
|
Role = "runtime-deployment-state"
|
|
})
|
|
}
|
|
|
|
resource "aws_s3_bucket_public_access_block" "runtime_state" {
|
|
bucket = aws_s3_bucket.runtime_state.id
|
|
|
|
block_public_acls = true
|
|
block_public_policy = true
|
|
ignore_public_acls = true
|
|
restrict_public_buckets = true
|
|
}
|
|
|
|
resource "aws_s3_bucket_server_side_encryption_configuration" "runtime_state" {
|
|
bucket = aws_s3_bucket.runtime_state.id
|
|
|
|
rule {
|
|
apply_server_side_encryption_by_default {
|
|
sse_algorithm = "AES256"
|
|
}
|
|
}
|
|
}
|
|
|
|
resource "aws_s3_bucket_versioning" "runtime_state" {
|
|
bucket = aws_s3_bucket.runtime_state.id
|
|
|
|
versioning_configuration {
|
|
status = "Enabled"
|
|
}
|
|
}
|
|
|
|
resource "aws_ebs_volume" "persist" {
|
|
availability_zone = data.aws_subnet.selected.availability_zone
|
|
size = var.volume_size_gb
|
|
snapshot_id = var.snapshot_id
|
|
type = var.volume_type
|
|
encrypted = true
|
|
|
|
tags = merge(local.tags, {
|
|
Name = "${local.resource_name}-volume"
|
|
Role = "persistent-state"
|
|
})
|
|
}
|
|
|
|
resource "aws_security_group" "app_server" {
|
|
count = local.app_server_enabled ? 1 : 0
|
|
name = "${local.resource_name}-central-proxy"
|
|
description = "Quixos central app-server ingress"
|
|
vpc_id = local.platform_vpc_id
|
|
|
|
tags = merge(local.tags, {
|
|
Name = "${local.resource_name}-central-proxy-sg"
|
|
Role = "central-app-server-security-group"
|
|
})
|
|
}
|
|
|
|
resource "aws_vpc_security_group_ingress_rule" "app_server_http_ipv4" {
|
|
count = local.app_server_enabled ? 1 : 0
|
|
security_group_id = aws_security_group.app_server[0].id
|
|
description = "HTTP"
|
|
cidr_ipv4 = "0.0.0.0/0"
|
|
from_port = 80
|
|
ip_protocol = "tcp"
|
|
to_port = 80
|
|
}
|
|
|
|
resource "aws_vpc_security_group_ingress_rule" "app_server_http_ipv6" {
|
|
count = local.app_server_enabled ? 1 : 0
|
|
security_group_id = aws_security_group.app_server[0].id
|
|
description = "HTTP"
|
|
cidr_ipv6 = "::/0"
|
|
from_port = 80
|
|
ip_protocol = "tcp"
|
|
to_port = 80
|
|
}
|
|
|
|
resource "aws_vpc_security_group_ingress_rule" "app_server_https_ipv4" {
|
|
count = local.app_server_enabled ? 1 : 0
|
|
security_group_id = aws_security_group.app_server[0].id
|
|
description = "HTTPS"
|
|
cidr_ipv4 = "0.0.0.0/0"
|
|
from_port = 443
|
|
ip_protocol = "tcp"
|
|
to_port = 443
|
|
}
|
|
|
|
resource "aws_vpc_security_group_ingress_rule" "app_server_https_ipv6" {
|
|
count = local.app_server_enabled ? 1 : 0
|
|
security_group_id = aws_security_group.app_server[0].id
|
|
description = "HTTPS"
|
|
cidr_ipv6 = "::/0"
|
|
from_port = 443
|
|
ip_protocol = "tcp"
|
|
to_port = 443
|
|
}
|
|
|
|
resource "aws_vpc_security_group_egress_rule" "app_server_ipv4_all" {
|
|
count = local.app_server_enabled ? 1 : 0
|
|
security_group_id = aws_security_group.app_server[0].id
|
|
description = "Outbound IPv4"
|
|
cidr_ipv4 = "0.0.0.0/0"
|
|
ip_protocol = "-1"
|
|
}
|
|
|
|
resource "aws_vpc_security_group_egress_rule" "app_server_runtime_http_ipv6" {
|
|
count = local.app_server_enabled && var.platform_vpc_enable ? 1 : 0
|
|
security_group_id = aws_security_group.app_server[0].id
|
|
description = "Runtime HTTP over platform IPv6"
|
|
cidr_ipv6 = aws_vpc.platform[0].ipv6_cidr_block
|
|
from_port = 80
|
|
ip_protocol = "tcp"
|
|
to_port = 80
|
|
}
|
|
|
|
resource "aws_vpc_security_group_egress_rule" "app_server_runtime_https_ipv6" {
|
|
count = local.app_server_enabled && var.platform_vpc_enable ? 1 : 0
|
|
security_group_id = aws_security_group.app_server[0].id
|
|
description = "Runtime HTTPS over platform IPv6"
|
|
cidr_ipv6 = aws_vpc.platform[0].ipv6_cidr_block
|
|
from_port = 443
|
|
ip_protocol = "tcp"
|
|
to_port = 443
|
|
}
|
|
|
|
resource "tls_private_key" "proxy_grants" {
|
|
algorithm = "ECDSA"
|
|
ecdsa_curve = "P256"
|
|
}
|
|
|
|
resource "aws_iam_role" "app_server" {
|
|
count = local.app_server_enabled ? 1 : 0
|
|
name = "${local.resource_name}-app-server"
|
|
|
|
assume_role_policy = jsonencode({
|
|
Version = "2012-10-17"
|
|
Statement = [
|
|
{
|
|
Action = "sts:AssumeRole"
|
|
Effect = "Allow"
|
|
Principal = {
|
|
Service = "ec2.amazonaws.com"
|
|
}
|
|
}
|
|
]
|
|
})
|
|
|
|
tags = local.tags
|
|
}
|
|
|
|
resource "aws_iam_role_policy_attachment" "app_server_ssm" {
|
|
count = local.app_server_enabled ? 1 : 0
|
|
role = aws_iam_role.app_server[0].name
|
|
policy_arn = "arn:aws:iam::aws:policy/AmazonSSMManagedInstanceCore"
|
|
}
|
|
|
|
resource "aws_iam_role_policy" "app_server_route53" {
|
|
count = local.app_server_enabled && var.route53_zone_id != null ? 1 : 0
|
|
name = "${local.resource_name}-route53"
|
|
role = aws_iam_role.app_server[0].name
|
|
|
|
policy = jsonencode({
|
|
Version = "2012-10-17"
|
|
Statement = [
|
|
{
|
|
Effect = "Allow"
|
|
Action = [
|
|
"route53:ChangeResourceRecordSets",
|
|
"route53:ListResourceRecordSets"
|
|
]
|
|
Resource = "arn:aws:route53:::hostedzone/${var.route53_zone_id}"
|
|
},
|
|
{
|
|
Effect = "Allow"
|
|
Action = [
|
|
"route53:GetChange",
|
|
"route53:ListHostedZonesByName"
|
|
]
|
|
Resource = "*"
|
|
}
|
|
]
|
|
})
|
|
}
|
|
|
|
resource "aws_iam_role_policy" "app_server_nix_cache" {
|
|
count = local.app_server_enabled && local.nix_cache_ready ? 1 : 0
|
|
name = "${local.resource_name}-nix-cache"
|
|
role = aws_iam_role.app_server[0].name
|
|
|
|
policy = jsonencode({
|
|
Version = "2012-10-17"
|
|
Statement = [
|
|
{
|
|
Effect = "Allow"
|
|
Action = [
|
|
"ssm:GetParameter"
|
|
]
|
|
Resource = "arn:aws:ssm:${var.aws_region}:*:parameter${local.nix_cache_push_parameter}"
|
|
},
|
|
{
|
|
Effect = "Allow"
|
|
Action = "kms:Decrypt"
|
|
Resource = "*"
|
|
}
|
|
]
|
|
})
|
|
}
|
|
|
|
resource "aws_iam_instance_profile" "app_server" {
|
|
count = local.app_server_enabled ? 1 : 0
|
|
name = "${local.resource_name}-app-server"
|
|
role = aws_iam_role.app_server[0].name
|
|
}
|
|
|
|
resource "aws_instance" "app_server" {
|
|
count = local.app_server_enabled ? 1 : 0
|
|
ami = local.app_server_ami_id
|
|
instance_type = var.app_server_instance_type
|
|
subnet_id = local.platform_public_subnet_id
|
|
vpc_security_group_ids = [aws_security_group.app_server[0].id]
|
|
iam_instance_profile = aws_iam_instance_profile.app_server[0].name
|
|
associate_public_ip_address = true
|
|
ipv6_address_count = var.platform_vpc_enable ? 1 : null
|
|
user_data_replace_on_change = true
|
|
|
|
user_data = templatefile("${path.module}/user-data-flake.nix.tftpl", {
|
|
app_server_flake_uri = local.app_server_flake_uri
|
|
app_wildcard_domain = local.app_wildcard_domain
|
|
dev_app_wildcard_domain = local.dev_app_wildcard_domain
|
|
domain = local.app_server_domain
|
|
persist_device = local.app_server_persist_device
|
|
nix_cache_enable = local.nix_cache_ready
|
|
nix_cache_domain = local.nix_cache_domain
|
|
nix_cache_host_address = local.nix_cache_host_address
|
|
nix_cache_endpoint = local.nix_cache_endpoint
|
|
nix_cache_substituter = local.nix_cache_substituter
|
|
nix_cache_public_key = local.nix_cache_public_key
|
|
nix_cache_cache_name = local.nix_cache_cache_name
|
|
nix_cache_push_parameter = local.nix_cache_push_parameter
|
|
proxy_grant_public_key_pem = jsonencode(tls_private_key.proxy_grants.public_key_pem)
|
|
route53_zone_id = var.route53_zone_id
|
|
system = var.app_server_system
|
|
})
|
|
|
|
root_block_device {
|
|
volume_size = var.app_server_root_volume_size_gb
|
|
volume_type = "gp3"
|
|
}
|
|
|
|
tags = merge(local.tags, {
|
|
Name = "${local.resource_name}-app-server"
|
|
Role = "central-app-server"
|
|
})
|
|
}
|
|
|
|
resource "aws_volume_attachment" "app_server_persist" {
|
|
count = local.app_server_enabled ? 1 : 0
|
|
device_name = "/dev/sdh"
|
|
volume_id = aws_ebs_volume.persist.id
|
|
instance_id = aws_instance.app_server[0].id
|
|
}
|
|
|
|
resource "aws_eip" "app_server" {
|
|
count = local.app_server_enabled ? 1 : 0
|
|
domain = "vpc"
|
|
|
|
tags = merge(local.tags, {
|
|
Name = "${local.resource_name}-app-server-eip"
|
|
Role = "central-app-server-public-ip"
|
|
})
|
|
}
|
|
|
|
resource "aws_eip_association" "app_server" {
|
|
count = local.app_server_enabled ? 1 : 0
|
|
instance_id = aws_instance.app_server[0].id
|
|
allocation_id = aws_eip.app_server[0].id
|
|
}
|
|
|
|
resource "aws_route53_record" "app_server" {
|
|
count = local.app_server_enabled && var.route53_zone_id != null ? 1 : 0
|
|
zone_id = var.route53_zone_id
|
|
name = local.app_server_domain
|
|
type = "A"
|
|
ttl = 60
|
|
records = [aws_eip.app_server[0].public_ip]
|
|
}
|
|
|
|
resource "aws_route53_record" "app_wildcard" {
|
|
count = local.app_server_enabled && var.route53_zone_id != null ? 1 : 0
|
|
zone_id = var.route53_zone_id
|
|
name = "*.${local.app_wildcard_domain}"
|
|
type = "A"
|
|
ttl = 60
|
|
records = [aws_eip.app_server[0].public_ip]
|
|
}
|
|
|
|
resource "aws_route53_record" "dev_app_wildcard" {
|
|
count = local.app_server_enabled && var.route53_zone_id != null ? 1 : 0
|
|
zone_id = var.route53_zone_id
|
|
name = "*.${local.dev_app_wildcard_domain}"
|
|
type = "A"
|
|
ttl = 60
|
|
records = [aws_eip.app_server[0].public_ip]
|
|
}
|