Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
79 changes: 79 additions & 0 deletions .github/workflows/terraform-test.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
name: terraform test

# Runs the variable-validation suite under tests/terraform/.
# See tests/terraform/README.md for what's covered and how to run locally.

on:
pull_request:
paths:
- "**/*.tf"
- "**/*.tftest.hcl"
- "templates/TEMPLATE_terraform.tfvars"
- "scripts/installer/validation/**"
- "scripts/installer/data_external/**"
- "scripts/installer/utils/**"
- "tests/datafiles/**"
- "tests/terraform/**"
- "Makefile"
- ".github/workflows/terraform-test.yml"
push:
branches:
- master
- "release/v*"
- "chore/v26-**" # Temporary: keep visibility while the stacked v26 work is in flight.
workflow_dispatch:

jobs:
terraform-test:
name: terraform test
runs-on: ubuntu-latest
timeout-minutes: 15

steps:
- name: Checkout
uses: actions/checkout@v4

- name: Setup Terraform
uses: hashicorp/setup-terraform@v3
with:
terraform_version: 1.14.8
terraform_wrapper: false

- name: terraform fmt
run: terraform fmt -check -recursive -diff

- name: Setup tflint
uses: terraform-linters/setup-tflint@v4
with:
tflint_version: v0.61.0

- name: tflint init
run: tflint --init

- name: tflint
# TFLINT_CONFIG_FILE is required so child modules walked by --recursive
# use the root .tflint.hcl — they don't auto-discover it.
env:
TFLINT_CONFIG_FILE: ${{ github.workspace }}/.tflint.hcl
run: tflint --recursive

- name: Setup Python
uses: actions/setup-python@v5
with:
python-version: "3.12"

# tests/requirements.txt is the pytest-suite fixture; this job only needs
# stdlib Python (generate_testing_secrets.py uses no third-party imports).

- name: Terraform init (modules only)
run: terraform init -backend=false

- name: Generate test fixtures
env:
CX_SKIP_SSM: "true"
run: |
mkdir -p tests/logs
make generate_test_data

- name: Run terraform test
run: make terraform_test
31 changes: 31 additions & 0 deletions .tflint.hcl
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
config {
call_module_type = "local"
force = false
}

plugin "terraform" {
enabled = true
preset = "recommended"
}

plugin "aws" {
enabled = true
version = "0.42.0"
source = "github.com/terraform-linters/tflint-ruleset-aws"
}

# Most variables are typed-only flags by convention; description requirement
# would be noise rather than signal here.
rule "terraform_documented_variables" {
enabled = false
}
rule "terraform_documented_outputs" {
enabled = false
}

# Modules are pinned via folder name (e.g. modules/connection_strings/v1.0.0/),
# not via a `version = ` argument.
rule "terraform_module_version" {
enabled = false
}

40 changes: 25 additions & 15 deletions 000_main.tf
Original file line number Diff line number Diff line change
Expand Up @@ -2,13 +2,31 @@
## Provider & Backend
## ------------------------------------------------------------------------------------
terraform {
required_version = ">= 1.1.0"
# Bumped from 1.1.0 -> 1.7.0: terraform test override_data blocks (used by tests/terraform/)
# are only supported on >= 1.7.
required_version = ">= 1.7.0"

required_providers {
aws = {
source = "hashicorp/aws"
version = "~> 5.12.0"
}
tls = {
source = "hashicorp/tls"
version = "~> 4.0"
}
null = {
source = "hashicorp/null"
version = "~> 3.2"
}
random = {
source = "hashicorp/random"
version = "~> 3.6"
}
external = {
source = "hashicorp/external"
version = "~> 2.3"
}
}

backend "local" {
Expand Down Expand Up @@ -71,15 +89,10 @@ locals {
# NLB health checks originate from NLB nodes within the VPC — not from external IPs in sg_ingress_cidrs.
# Without the VPC CIDR in the EC2 security group, health checks are blocked, the target shows unhealthy,
# and the NLB stops forwarding real SSH traffic even though connect-proxy is running correctly.
vpc_cidr_block = var.flag_create_new_vpc == true ? var.vpc_new_cidr_range : data.aws_vpc.preexisting[0].cidr_block
vpc_cidr_block = var.flag_create_new_vpc == true ? var.vpc_new_cidr_range : data.aws_vpc.preexisting[0].cidr_block

flag_map_public_ip_on_launch = var.flag_map_public_ip_on_launch == true || var.flag_make_instance_public == true ? true : false

# SSM
# ---------------------------------------------------------------------------------------
# Load bootstrapped secrets and define target for TF-generated SSM values. Magical - don't know why it works but it does.
ssm_root = "/config/${var.app_name}"

tower_secrets = jsondecode(data.aws_ssm_parameter.tower_secrets.value)
tower_secret_keys = nonsensitive(toset([for k, v in local.tower_secrets : k]))

Expand Down Expand Up @@ -135,8 +148,8 @@ locals {
# Studios SSH — see 002_security_groups.tf for why two separate rules are needed
# (one for direct EC2 access, one for NLB path; NLBs don't have security groups
# so both use CIDR-based rules rather than source_security_group_id)
sg_ec2_noalb_ssh = try([module.sg_ec2_noalb_ssh[0].security_group_id], [])
sg_from_nlb_ssh = try([module.sg_from_nlb_ssh[0].security_group_id], [])
sg_ec2_noalb_ssh = try([module.sg_ec2_noalb_ssh[0].security_group_id], [])
sg_from_nlb_ssh = try([module.sg_from_nlb_ssh[0].security_group_id], [])

sg_ec2_final = concat(
local.sg_ec2_core,
Expand All @@ -150,7 +163,6 @@ locals {
local.sg_from_nlb_ssh,

)
ec2_sg_final_raw = join(",", [for sg in local.sg_ec2_final : jsonencode(sg)]) # Needed?


# ALB - Determine which CIDR Blocks to attach to allowed ports
Expand Down Expand Up @@ -209,8 +221,7 @@ locals {
# Miscellaneous
# ---------------------------------------------------------------------------------------
# These are needed to handle templatefile rendering to Bash echoing to file craziness.
dollar = "$"
singlequote = "'"
dollar = "$"


# Ansible
Expand Down Expand Up @@ -244,8 +255,7 @@ module "connection_strings" {
source = "./modules/connection_strings/v1.0.0"

# Feature Flags
flag_create_load_balancer = var.flag_create_load_balancer
flag_do_not_use_https = var.flag_do_not_use_https
flag_do_not_use_https = var.flag_do_not_use_https

flag_create_external_db = var.flag_create_external_db
flag_use_existing_external_db = var.flag_use_existing_external_db
Expand All @@ -270,7 +280,7 @@ module "connection_strings" {

# Studios Configuration
flag_enable_data_studio = var.flag_enable_data_studio
flag_enable_data_studio_ssh = var.flag_enable_data_studio_ssh
flag_enable_data_studio_ssh = var.flag_enable_data_studio_ssh
flag_studio_enable_path_routing = var.flag_studio_enable_path_routing
data_studio_path_routing_url = var.flag_studio_enable_path_routing ? var.data_studio_path_routing_url : ""

Expand Down
2 changes: 1 addition & 1 deletion 002_security_groups.tf
Original file line number Diff line number Diff line change
Expand Up @@ -109,7 +109,7 @@ module "sg_ec2_noalb_connect" {
to_port = 9090
protocol = "tcp"
description = "Connect-Proxy"
cidr_blocks = "${join(",", var.sg_ingress_cidrs)}"
cidr_blocks = join(",", var.sg_ingress_cidrs)
}
]
# number_of_computed_ingress_with_source_security_group_id = 1
Expand Down
3 changes: 1 addition & 2 deletions 004_iam.tf
Original file line number Diff line number Diff line change
Expand Up @@ -14,11 +14,10 @@ locals {
tag_key = local.global_prefix,
aws_region = var.aws_region,
aws_account = var.aws_account,
app_name = var.app_name,
ssm_key_arn = data.aws_kms_alias.default_ssm.arn,

flag_allow_aws_instance_credentials = var.flag_allow_aws_instance_credentials,
tower_aws_role = local.seqerakit_secrets["TOWER_AWS_ROLE"]["value"],
tower_aws_role = local.seqerakit_secrets["TOWER_AWS_ROLE"]["value"],
}
)

Expand Down
40 changes: 20 additions & 20 deletions 005_parameter_store.tf
Original file line number Diff line number Diff line change
Expand Up @@ -26,22 +26,22 @@ data "aws_ssm_parameter" "wave_lite_secrets" {
# Generate individual SSM Parameters
# ------------------------------------------------
resource "aws_ssm_parameter" "client_supplied_secrets_tower" {
for_each = local.tower_secret_keys
name = nonsensitive(local.tower_secrets[each.key]["ssm_key"])
value = local.tower_secrets[each.key]["value"]
type = "SecureString"
overwrite = var.flag_overwrite_ssm_keys
for_each = local.tower_secret_keys
name = nonsensitive(local.tower_secrets[each.key]["ssm_key"])
value = local.tower_secrets[each.key]["value"]
type = "SecureString"
overwrite = var.flag_overwrite_ssm_keys
}


resource "aws_ssm_parameter" "client_supplied_secrets_seqerakit" {
# for_each = local.seqerakit_secret_keys

for_each = var.flag_run_seqerakit == true ? local.seqerakit_secret_keys : []
name = nonsensitive(local.seqerakit_secrets[each.key]["ssm_key"])
value = local.seqerakit_secrets[each.key]["value"]
type = "SecureString"
overwrite = var.flag_overwrite_ssm_keys
for_each = var.flag_run_seqerakit == true ? local.seqerakit_secret_keys : []
name = nonsensitive(local.seqerakit_secrets[each.key]["ssm_key"])
value = local.seqerakit_secrets[each.key]["value"]
type = "SecureString"
overwrite = var.flag_overwrite_ssm_keys
}


Expand All @@ -50,19 +50,19 @@ resource "aws_ssm_parameter" "client_supplied_secrets_groundswell" {
# count = var.flag_enable_groundswell == true ? 1 : 0
# for_each = local.groundswell_secret_keys

for_each = var.flag_enable_groundswell == true ? local.groundswell_secret_keys : []
name = nonsensitive(local.groundswell_secrets[each.key]["ssm_key"])
value = local.groundswell_secrets[each.key]["value"]
type = "SecureString"
overwrite = var.flag_overwrite_ssm_keys
for_each = var.flag_enable_groundswell == true ? local.groundswell_secret_keys : []
name = nonsensitive(local.groundswell_secrets[each.key]["ssm_key"])
value = local.groundswell_secrets[each.key]["value"]
type = "SecureString"
overwrite = var.flag_overwrite_ssm_keys
}


resource "aws_ssm_parameter" "client_supplied_secrets_wave_lite" {

for_each = var.flag_use_wave_lite == true ? local.wave_lite_secret_keys : []
name = nonsensitive(local.wave_lite_secrets[each.key]["ssm_key"])
value = local.wave_lite_secrets[each.key]["value"]
type = "SecureString"
overwrite = var.flag_overwrite_ssm_keys
for_each = var.flag_use_wave_lite == true ? local.wave_lite_secret_keys : []
name = nonsensitive(local.wave_lite_secrets[each.key]["ssm_key"])
value = local.wave_lite_secrets[each.key]["value"]
type = "SecureString"
overwrite = var.flag_overwrite_ssm_keys
}
10 changes: 5 additions & 5 deletions 008_route53.tf
Original file line number Diff line number Diff line change
Expand Up @@ -66,7 +66,7 @@ resource "aws_route53_record" "alb_connect" {
count = local.dns_create_alb_record == true ? 1 : 0

zone_id = local.dns_zone_id
name = var.flag_studio_enable_path_routing ? module.connection_strings.tower_connect_dns : module.connection_strings.tower_connect_wildcard_dns
name = var.flag_studio_enable_path_routing ? module.connection_strings.tower_connect_dns : module.connection_strings.tower_connect_wildcard_dns

type = "A"

Expand All @@ -82,8 +82,8 @@ resource "aws_route53_record" "ec2_connect" {
count = local.dns_create_ec2_record == true ? 1 : 0

zone_id = local.dns_zone_id
name = var.flag_studio_enable_path_routing ? module.connection_strings.tower_connect_dns : module.connection_strings.tower_connect_wildcard_dns
type = "A"
name = var.flag_studio_enable_path_routing ? module.connection_strings.tower_connect_dns : module.connection_strings.tower_connect_wildcard_dns
type = "A"

ttl = "5"
records = [local.dns_instance_ip]
Expand Down Expand Up @@ -124,8 +124,8 @@ resource "aws_route53_record" "alb_wave" {
count = local.dns_create_alb_record == true && var.flag_use_wave_lite == true ? 1 : 0

zone_id = local.dns_zone_id
name = module.connection_strings.tower_wave_dns
type = "A"
name = module.connection_strings.tower_wave_dns
type = "A"

alias {
name = module.alb[0].lb_dns_name
Expand Down
23 changes: 11 additions & 12 deletions 009_define_file_templates.tf
Original file line number Diff line number Diff line change
Expand Up @@ -44,12 +44,12 @@ locals {
flag_limit_data_studio_to_some_workspaces = var.flag_limit_data_studio_to_some_workspaces,
data_studio_eligible_workspaces = var.data_studio_eligible_workspaces,

flag_enable_data_studio_ssh = var.flag_enable_data_studio_ssh,
data_studio_ssh_address = module.connection_strings.tower_connect_ssh_url,
flag_limit_data_studio_ssh_to_some_workspaces = var.flag_limit_data_studio_ssh_to_some_workspaces,
data_studio_ssh_eligible_workspaces = var.data_studio_ssh_eligible_workspaces,
connect_ssh_fingerprint = tls_private_key.connect_ssh_host_key.public_key_fingerprint_sha256,
flag_enable_data_studio_ssh = var.flag_enable_data_studio_ssh,
data_studio_ssh_address = module.connection_strings.tower_connect_ssh_url,
flag_limit_data_studio_ssh_to_some_workspaces = var.flag_limit_data_studio_ssh_to_some_workspaces,
data_studio_ssh_eligible_workspaces = var.data_studio_ssh_eligible_workspaces,
connect_ssh_fingerprint = tls_private_key.connect_ssh_host_key.public_key_fingerprint_sha256,

data_studio_options = var.data_studio_options,
flag_studio_enable_path_routing = var.flag_studio_enable_path_routing,

Expand Down Expand Up @@ -106,7 +106,6 @@ locals {
swell_db_user = local.groundswell_secrets["SWELL_DB_USER"]["value"],
swell_db_password = local.groundswell_secrets["SWELL_DB_PASSWORD"]["value"],
swell_database_name = var.swell_database_name,
db_database_name = var.db_database_name,
}
)

Expand All @@ -128,10 +127,10 @@ locals {

data_studios_env = templatefile("assets/src/tower_config/data-studios.env.tpl",
{
flag_enable_data_studio = var.flag_enable_data_studio,
tower_server_url = module.connection_strings.tower_server_url,
tower_redis_url = module.connection_strings.tower_connect_redis_url,
tower_connect_server_url = module.connection_strings.tower_connect_server_url,
flag_enable_data_studio = var.flag_enable_data_studio,
tower_server_url = module.connection_strings.tower_server_url,
tower_redis_url = module.connection_strings.tower_connect_redis_url,
tower_connect_server_url = module.connection_strings.tower_connect_server_url,
flag_enable_data_studio_ssh = var.flag_enable_data_studio_ssh,
connect_ssh_key_path = "/data/ssh-host-key",
}
Expand Down Expand Up @@ -422,7 +421,7 @@ resource "tls_private_key" "connect_pem" {
}

resource "tls_private_key" "connect_ssh_host_key" {
algorithm = "ED25519"
algorithm = "ED25519"
}


Loading
Loading