diff --git a/.tflint.hcl b/.tflint.hcl index 4c2685dc..07fcaad7 100644 --- a/.tflint.hcl +++ b/.tflint.hcl @@ -4,6 +4,13 @@ config { disabled_by_default = false } +plugin "aws" { + enabled = true + deep_check = false + source = "github.com/terraform-linters/tflint-ruleset-aws" + version = "0.8.0" +} + rule "terraform_deprecated_index" { enabled = true } diff --git a/examples/existing-image/README.md b/examples/existing-image/README.md index d88f9ab6..fd9ee837 100644 --- a/examples/existing-image/README.md +++ b/examples/existing-image/README.md @@ -2,7 +2,7 @@ ## About This Example -This example functions as a reference for how to use this module to install +This example functions as a reference for how to use this module to install Terraform Enterprise with a custom image (AMI) in AWS. ## Module Prerequisites diff --git a/examples/existing-image/versions.tf b/examples/existing-image/versions.tf index e7c8bf52..5733292f 100644 --- a/examples/existing-image/versions.tf +++ b/examples/existing-image/versions.tf @@ -3,7 +3,7 @@ terraform { required_providers { aws = { source = "hashicorp/aws" - version = "~> 3.38" + version = "~> 3.63" } } -} \ No newline at end of file +} diff --git a/main.tf b/main.tf index c6d6ac9e..905cc599 100644 --- a/main.tf +++ b/main.tf @@ -1,25 +1,9 @@ data "aws_region" "current" {} -data "aws_ami" "ubuntu" { - most_recent = true - - filter { - name = "name" - values = ["ubuntu/images/hvm-ssd/ubuntu-focal-20.04-amd64-server-*"] - } - - filter { - name = "virtualization-type" - values = ["hvm"] - } - - owners = ["099720109477"] # Canonical -} - resource "aws_kms_key" "tfe_key" { deletion_window_in_days = var.kms_key_deletion_window description = "AWS KMS Customer-managed key to encrypt TFE and other resources" - enable_key_rotation = false + enable_key_rotation = true is_enabled = true key_usage = "ENCRYPT_DECRYPT" @@ -36,10 +20,8 @@ resource "aws_kms_alias" "key_alias" { } locals { - active_active = var.node_count >= 2 - ami_id = local.default_ami_id ? data.aws_ami.ubuntu.id : var.ami_id - default_ami_id = var.ami_id == "" - fqdn = "${var.tfe_subdomain}.${var.domain_name}" + active_active = var.node_count >= 2 + fqdn = var.custom_fqdn != null ? var.custom_fqdn : "${var.tfe_subdomain}.${var.domain_name}" } module "object_storage" { @@ -47,6 +29,8 @@ module "object_storage" { friendly_name_prefix = var.friendly_name_prefix kms_key_arn = aws_kms_key.tfe_key.arn + logging_bucket = var.logging_bucket + logging_prefix = var.logging_prefix } module "service_accounts" { @@ -106,10 +90,13 @@ module "database" { db_backup_window = var.db_backup_window engine_version = var.postgres_engine_version friendly_name_prefix = var.friendly_name_prefix + monitoring_interval = var.db_monitoring_interval network_id = local.network_id network_private_subnet_cidrs = var.network_private_subnet_cidrs network_subnets_private = local.network_private_subnets tfe_instance_sg = module.vm.tfe_instance_sg + enabled_cloudwatch_logs = var.db_enabled_cloudwatch_logs + tags = var.db_tags } module "user_data" { @@ -152,6 +139,8 @@ module "load_balancer" { network_public_subnets = local.network_public_subnets network_private_subnets = local.network_private_subnets ssl_policy = var.ssl_policy + logging_bucket = var.logging_bucket + logging_prefix = var.logging_prefix } module "private_tcp_load_balancer" { @@ -166,6 +155,8 @@ module "private_tcp_load_balancer" { network_id = local.network_id network_private_subnets = local.network_private_subnets ssl_policy = var.ssl_policy + logging_bucket = var.logging_bucket + logging_prefix = var.logging_prefix } module "vm" { @@ -173,12 +164,13 @@ module "vm" { active_active = local.active_active aws_iam_instance_profile = module.service_accounts.aws_iam_instance_profile - ami_id = local.ami_id + ami_id = var.ami_id + ami_kms_key_arn = var.ami_kms_key_arn aws_lb = var.load_balancing_scheme == "PRIVATE_TCP" ? null : module.load_balancer[0].aws_lb_security_group + aws_lb_target_group_tfe_tg_80_arn = var.load_balancing_scheme == "PRIVATE_TCP" ? module.private_tcp_load_balancer[0].aws_lb_target_group_tfe_tg_80_arn : null aws_lb_target_group_tfe_tg_443_arn = var.load_balancing_scheme == "PRIVATE_TCP" ? module.private_tcp_load_balancer[0].aws_lb_target_group_tfe_tg_443_arn : module.load_balancer[0].aws_lb_target_group_tfe_tg_443_arn aws_lb_target_group_tfe_tg_8800_arn = var.load_balancing_scheme == "PRIVATE_TCP" ? module.private_tcp_load_balancer[0].aws_lb_target_group_tfe_tg_8800_arn : module.load_balancer[0].aws_lb_target_group_tfe_tg_8800_arn asg_tags = var.asg_tags - default_ami_id = local.default_ami_id friendly_name_prefix = var.friendly_name_prefix key_name = var.key_name instance_type = var.instance_type @@ -187,4 +179,5 @@ module "vm" { network_private_subnet_cidrs = local.network_private_subnet_cidrs node_count = var.node_count user_data_base64 = module.user_data.tfe_user_data_base64 + health_check_grace_period = var.health_check_grace_period } diff --git a/modules/application_load_balancer/main.tf b/modules/application_load_balancer/main.tf index 9a7c5301..4100369a 100644 --- a/modules/application_load_balancer/main.tf +++ b/modules/application_load_balancer/main.tf @@ -1,6 +1,7 @@ resource "aws_security_group" "tfe_lb_allow" { - name = "${var.friendly_name_prefix}-tfe-lb-allow" - vpc_id = var.network_id + name = "${var.friendly_name_prefix}-tfe-lb-allow" + description = "Managed by Terraform" + vpc_id = var.network_id } resource "aws_security_group_rule" "tfe_lb_allow_inbound_http" { @@ -35,8 +36,9 @@ resource "aws_security_group_rule" "tfe_lb_allow_inbound_dashboard" { } resource "aws_security_group" "tfe_outbound_allow" { - name = "${var.friendly_name_prefix}-tfe-outbound-allow" - vpc_id = var.network_id + name = "${var.friendly_name_prefix}-tfe-outbound-allow" + description = "Managed by Terraform" + vpc_id = var.network_id } resource "aws_security_group_rule" "tfe_outbound_allow_all" { @@ -51,10 +53,21 @@ resource "aws_security_group_rule" "tfe_outbound_allow_all" { } resource "aws_lb" "tfe_lb" { - name = "${var.friendly_name_prefix}-tfe-web-alb" - internal = (var.load_balancing_scheme == "PRIVATE") - load_balancer_type = "application" - subnets = var.load_balancing_scheme == "PRIVATE" ? var.network_private_subnets : var.network_public_subnets + #checkov:skip=CKV_AWS_150:We will expect that people might want to spin up/down infrastructure quickly. Skip checking for delete protection. + name = "${var.friendly_name_prefix}-tfe-web-alb" + internal = (var.load_balancing_scheme == "PRIVATE") + load_balancer_type = "application" + subnets = var.load_balancing_scheme == "PRIVATE" ? var.network_private_subnets : var.network_public_subnets + drop_invalid_header_fields = true + + dynamic "access_logs" { + for_each = var.logging_bucket != null ? [var.logging_bucket] : [] + content { + bucket = var.logging_bucket + prefix = var.logging_prefix + enabled = true + } + } security_groups = [ aws_security_group.tfe_lb_allow.id, diff --git a/modules/application_load_balancer/variables.tf b/modules/application_load_balancer/variables.tf index 5e0ddd89..0afac955 100644 --- a/modules/application_load_balancer/variables.tf +++ b/modules/application_load_balancer/variables.tf @@ -30,6 +30,7 @@ variable "load_balancing_scheme" { } variable "ssl_policy" { + default = "ELBSecurityPolicy-TLS-1-2-2017-01" description = "The name of the SSL policy to assign to the load balancer listeners." type = string } @@ -57,3 +58,15 @@ variable "friendly_name_prefix" { type = string description = "(Required) Friendly name prefix used for tagging and naming AWS resources." } + +variable "logging_bucket" { + type = string + description = "S3 bucket name for logging of resources to. Requires a bucket in the same region that TFE is in." + default = null +} + +variable "logging_prefix" { + type = string + description = "Optional prefix to prepend to TFE resource logs in S3 bucket" + default = null +} diff --git a/modules/application_load_balancer/versions.tf b/modules/application_load_balancer/versions.tf index 241fbd9e..5733292f 100644 --- a/modules/application_load_balancer/versions.tf +++ b/modules/application_load_balancer/versions.tf @@ -3,7 +3,7 @@ terraform { required_providers { aws = { source = "hashicorp/aws" - version = "~> 3.38" + version = "~> 3.63" } } } diff --git a/modules/database/main.tf b/modules/database/main.tf index 9ce695de..953e4a80 100644 --- a/modules/database/main.tf +++ b/modules/database/main.tf @@ -7,10 +7,12 @@ resource "aws_security_group" "postgresql" { description = "The security group of the PostgreSQL deployment for TFE." name = "${var.friendly_name_prefix}-tfe-postgresql" vpc_id = var.network_id + tags = var.tags } resource "aws_security_group_rule" "postgresql_tfe_ingress" { security_group_id = aws_security_group.postgresql.id + description = "Inbound traffic to postgresql port from TFE" type = "ingress" from_port = 5432 to_port = 5432 @@ -20,6 +22,7 @@ resource "aws_security_group_rule" "postgresql_tfe_ingress" { resource "aws_security_group_rule" "postgresql_tfe_egress" { security_group_id = aws_security_group.postgresql.id + description = "Outbound ICMP traffic from RDS" type = "egress" from_port = 0 to_port = 0 @@ -29,6 +32,7 @@ resource "aws_security_group_rule" "postgresql_tfe_egress" { resource "aws_security_group_rule" "postgresql_ingress" { security_group_id = aws_security_group.postgresql.id + description = "Inbound traffic to postgresql port from VPC" type = "ingress" from_port = 5432 to_port = 5432 @@ -38,6 +42,7 @@ resource "aws_security_group_rule" "postgresql_ingress" { resource "aws_security_group_rule" "postgresql_egress" { security_group_id = aws_security_group.postgresql.id + description = "Outbound traffic from postgresql port to VPC" type = "egress" from_port = 5432 to_port = 5432 @@ -51,6 +56,7 @@ resource "aws_db_subnet_group" "tfe" { } resource "aws_db_instance" "postgresql" { + #checkov:skip=CKV_AWS_129:We allow enabling this, but it's not on by default so skip the check. allocated_storage = 20 engine = "postgres" instance_class = var.db_size @@ -58,24 +64,28 @@ resource "aws_db_instance" "postgresql" { # no special characters allowed username = "espdtfe" - allow_major_version_upgrade = false - apply_immediately = true - auto_minor_version_upgrade = true - backup_retention_period = var.db_backup_retention - backup_window = var.db_backup_window - db_subnet_group_name = aws_db_subnet_group.tfe.name - delete_automated_backups = true - deletion_protection = false - engine_version = var.engine_version - identifier_prefix = "${var.friendly_name_prefix}-tfe" - max_allocated_storage = 0 - multi_az = true + iam_database_authentication_enabled = true + allow_major_version_upgrade = false + apply_immediately = true + auto_minor_version_upgrade = true + backup_retention_period = var.db_backup_retention + backup_window = var.db_backup_window + db_subnet_group_name = aws_db_subnet_group.tfe.name + delete_automated_backups = true + deletion_protection = false + engine_version = var.engine_version + identifier_prefix = "${var.friendly_name_prefix}-tfe" + max_allocated_storage = 0 + multi_az = true + monitoring_interval = var.monitoring_interval # no special characters allowed - name = "espdtfe" - port = 5432 - publicly_accessible = false - skip_final_snapshot = true - storage_encrypted = true - storage_type = "gp2" - vpc_security_group_ids = [aws_security_group.postgresql.id] + name = "espdtfe" + port = 5432 + publicly_accessible = false + skip_final_snapshot = true + storage_encrypted = true + storage_type = "gp2" + vpc_security_group_ids = [aws_security_group.postgresql.id] + enabled_cloudwatch_logs_exports = var.enabled_cloudwatch_logs + tags = var.tags } diff --git a/modules/database/variables.tf b/modules/database/variables.tf index ba3e9ba0..3d22c766 100644 --- a/modules/database/variables.tf +++ b/modules/database/variables.tf @@ -28,6 +28,12 @@ variable "engine_version" { description = "PostgreSQL version." } +variable "monitoring_interval" { + type = number + description = "Interval in seconds for monitoring of the RDS database. Any value other than null will enable enhanced monitoring." + default = null +} + variable "network_subnets_private" { description = <<-EOD A list of the identities of the private subnetworks in which the PostgreSQL RDS instance will be deployed. @@ -52,3 +58,20 @@ variable "network_private_subnet_cidrs" { description = "(Optional) List of private subnet CIDR ranges to create in VPC." default = ["10.0.32.0/20", "10.0.48.0/20"] } + + +variable "enabled_cloudwatch_logs" { + type = list(string) + description = "List of enabled cloudwatch log export types. From list here: https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/db_instance#enabled_cloudwatch_logs_exports" + default = null + validation { + condition = var.enabled_cloudwatch_logs != null ? can(contains(["postgresql", "upgrade"], var.enabled_cloudwatch_logs)) : true + error_message = "Allowed cloudwatch log export types don't match allowed. Must be: postgresql, upgrade." + } +} + +variable "tags" { + type = map(string) + description = "(Optional) Map of tags used only for the database instance" + default = {} +} diff --git a/modules/database/versions.tf b/modules/database/versions.tf index 4c3fe642..f42f09c9 100644 --- a/modules/database/versions.tf +++ b/modules/database/versions.tf @@ -3,7 +3,7 @@ terraform { required_providers { aws = { source = "hashicorp/aws" - version = "~> 3.38" + version = "~> 3.63" } random = { source = "hashicorp/random" diff --git a/modules/network_load_balancer/main.tf b/modules/network_load_balancer/main.tf index 5cd31dca..3196a5a8 100644 --- a/modules/network_load_balancer/main.tf +++ b/modules/network_load_balancer/main.tf @@ -1,14 +1,38 @@ resource "aws_lb" "tfe_lb" { - name = "${var.friendly_name_prefix}-tfe-nlb" - internal = true - load_balancer_type = "network" - subnets = var.network_private_subnets + #checkov:skip=CKV_AWS_150:Given we are automating this for the most part we can recover from a deleted load-balancer. Okay for now. + name = "${var.friendly_name_prefix}-tfe-nlb" + internal = true + load_balancer_type = "network" + enable_cross_zone_load_balancing = true + subnets = var.network_private_subnets + + dynamic "access_logs" { + for_each = var.logging_bucket != null ? [var.logging_bucket] : [] + content { + bucket = var.logging_bucket + prefix = var.logging_prefix + enabled = true + } + } +} + +resource "aws_lb_listener" "tfe_listener_80" { + load_balancer_arn = aws_lb.tfe_lb.arn + port = 80 + protocol = "TCP" + + default_action { + type = "forward" + target_group_arn = aws_lb_target_group.tfe_tg_80.arn + } } resource "aws_lb_listener" "tfe_listener_443" { load_balancer_arn = aws_lb.tfe_lb.arn port = 443 - protocol = "TCP" + protocol = "TLS" + ssl_policy = var.ssl_policy + certificate_arn = var.certificate_arn default_action { type = "forward" @@ -16,14 +40,29 @@ resource "aws_lb_listener" "tfe_listener_443" { } } +resource "aws_lb_target_group" "tfe_tg_80" { + name = "${var.friendly_name_prefix}-tfe-nlb-tg-80" + port = 80 + protocol = "TCP" + vpc_id = var.network_id + + health_check { + protocol = "HTTP" + path = "/_health_check" + matcher = "200-399" + } +} + resource "aws_lb_target_group" "tfe_tg_443" { - name = "${var.friendly_name_prefix}-tfe-alb-tg-443" + name = "${var.friendly_name_prefix}-tfe-nlb-tg-443" port = 443 - protocol = "TCP" + protocol = "TLS" vpc_id = var.network_id health_check { - protocol = "TCP" + protocol = "HTTPS" + path = "/_health_check" + matcher = "200-399" } } @@ -31,11 +70,10 @@ resource "aws_lb_listener" "tfe_listener_8800" { count = var.active_active ? 0 : 1 load_balancer_arn = aws_lb.tfe_lb.arn port = 8800 - protocol = "TCP" + protocol = "TLS" ssl_policy = var.ssl_policy certificate_arn = var.certificate_arn - default_action { type = "forward" target_group_arn = aws_lb_target_group.tfe_tg_8800[0].arn @@ -44,7 +82,7 @@ resource "aws_lb_listener" "tfe_listener_8800" { resource "aws_lb_target_group" "tfe_tg_8800" { count = var.active_active ? 0 : 1 - name = "${var.friendly_name_prefix}-tfe-alb-tg-8800" + name = "${var.friendly_name_prefix}-tfe-nlb-tg-8800" port = 8800 protocol = "TCP" vpc_id = var.network_id diff --git a/modules/network_load_balancer/outputs.tf b/modules/network_load_balancer/outputs.tf index f3cd06e7..d3f8e3b9 100644 --- a/modules/network_load_balancer/outputs.tf +++ b/modules/network_load_balancer/outputs.tf @@ -1,3 +1,10 @@ + +output "aws_lb_target_group_tfe_tg_80_arn" { + value = aws_lb_target_group.tfe_tg_80.arn + + description = "The Amazon Resource Name of the load balancer target group for traffic on port 80." +} + output "aws_lb_target_group_tfe_tg_443_arn" { value = aws_lb_target_group.tfe_tg_443.arn diff --git a/modules/network_load_balancer/variables.tf b/modules/network_load_balancer/variables.tf index 62f1d815..0162a074 100644 --- a/modules/network_load_balancer/variables.tf +++ b/modules/network_load_balancer/variables.tf @@ -37,3 +37,15 @@ variable "friendly_name_prefix" { type = string description = "(Required) Friendly name prefix used for tagging and naming AWS resources." } + +variable "logging_bucket" { + type = string + description = "S3 bucket name for logging of resources to. Requires a bucket in the same region that TFE is in." + default = null +} + +variable "logging_prefix" { + type = string + description = "Optional prefix to prepend to TFE resource logs in S3 bucket" + default = null +} diff --git a/modules/network_load_balancer/versions.tf b/modules/network_load_balancer/versions.tf index 241fbd9e..5733292f 100644 --- a/modules/network_load_balancer/versions.tf +++ b/modules/network_load_balancer/versions.tf @@ -3,7 +3,7 @@ terraform { required_providers { aws = { source = "hashicorp/aws" - version = "~> 3.38" + version = "~> 3.63" } } } diff --git a/modules/networking/versions.tf b/modules/networking/versions.tf index 241fbd9e..5733292f 100644 --- a/modules/networking/versions.tf +++ b/modules/networking/versions.tf @@ -3,7 +3,7 @@ terraform { required_providers { aws = { source = "hashicorp/aws" - version = "~> 3.38" + version = "~> 3.63" } } } diff --git a/modules/object_storage/main.tf b/modules/object_storage/main.tf index 9d908237..acabe639 100644 --- a/modules/object_storage/main.tf +++ b/modules/object_storage/main.tf @@ -1,4 +1,5 @@ resource "aws_s3_bucket" "tfe_data_bucket" { + #checkov:skip=CKV_AWS_144:While cross-region might make sense someday, right now it doesn't for TFE bucket = "${var.friendly_name_prefix}-tfe-data" acl = "private" @@ -15,6 +16,14 @@ resource "aws_s3_bucket" "tfe_data_bucket" { } } + dynamic "logging" { + for_each = var.logging_bucket == null ? [] : [var.logging_bucket] + content { + target_bucket = logging.value + target_prefix = var.logging_prefix + } + } + force_destroy = true } diff --git a/modules/object_storage/variables.tf b/modules/object_storage/variables.tf index 31f39bf0..05b9208d 100644 --- a/modules/object_storage/variables.tf +++ b/modules/object_storage/variables.tf @@ -7,3 +7,15 @@ variable "friendly_name_prefix" { type = string description = "(Required) Friendly name prefix used for tagging and naming AWS resources." } + +variable "logging_bucket" { + type = string + description = "S3 bucket name for logging of resources to. Requires a bucket in the same region that TFE is in." + default = null +} + +variable "logging_prefix" { + type = string + description = "Optional prefix to prepend to TFE resource logs in S3 bucket" + default = null +} diff --git a/modules/object_storage/versions.tf b/modules/object_storage/versions.tf index 241fbd9e..5733292f 100644 --- a/modules/object_storage/versions.tf +++ b/modules/object_storage/versions.tf @@ -3,7 +3,7 @@ terraform { required_providers { aws = { source = "hashicorp/aws" - version = "~> 3.38" + version = "~> 3.63" } } } diff --git a/modules/redis/main.tf b/modules/redis/main.tf index a0ecf2fe..1de68550 100644 --- a/modules/redis/main.tf +++ b/modules/redis/main.tf @@ -13,6 +13,7 @@ resource "aws_security_group" "redis" { resource "aws_security_group_rule" "redis_tfe_ingress" { count = var.active_active ? 1 : 0 security_group_id = aws_security_group.redis[0].id + description = "Allow inbound connection to the cluster from EC2/TFE" type = "ingress" from_port = var.redis_port to_port = var.redis_port @@ -23,6 +24,7 @@ resource "aws_security_group_rule" "redis_tfe_ingress" { resource "aws_security_group_rule" "redis_tfe_egress" { count = var.active_active ? 1 : 0 security_group_id = aws_security_group.redis[0].id + description = "Allow outbound ICMP for the redis cluster" type = "egress" from_port = 0 to_port = 0 @@ -33,6 +35,7 @@ resource "aws_security_group_rule" "redis_tfe_egress" { resource "aws_security_group_rule" "redis_ingress" { count = var.active_active ? 1 : 0 security_group_id = aws_security_group.redis[0].id + description = "Allow connections in to the redis port from VPC" type = "ingress" from_port = var.redis_port to_port = var.redis_port @@ -43,6 +46,7 @@ resource "aws_security_group_rule" "redis_ingress" { resource "aws_security_group_rule" "redis_egress" { count = var.active_active ? 1 : 0 security_group_id = aws_security_group.redis[0].id + description = "Egress rule for redis port communicating to the VPC" type = "egress" from_port = var.redis_port to_port = var.redis_port @@ -57,6 +61,9 @@ resource "aws_elasticache_subnet_group" "tfe" { } resource "aws_elasticache_replication_group" "redis" { + #checkov:skip=CKV_AWS_29:We allow this to be a feature flag. Don't error on it. + #checkov:skip=CKV_AWS_30:This is a feature that we're allowing the user(s) to disable or enable if they want. + #checkov:skip=CKV_AWS_31:Another feature flag that we allow the user to set. Don't error. count = var.active_active ? 1 : 0 node_type = var.cache_size number_cache_clusters = 1 diff --git a/modules/redis/variables.tf b/modules/redis/variables.tf index 2be3793b..00f2dcaf 100644 --- a/modules/redis/variables.tf +++ b/modules/redis/variables.tf @@ -5,7 +5,7 @@ variable "active_active" { variable "kms_key_arn" { description = <<-EOD - The Amazon Resource Name of the KMS key which will be used by the Redis Elasticache replication group to encrypt data + The Amazon Resource Name of the KMS key which will be used by the Redis Elasticache replication group to encrypt data at rest. EOD type = string @@ -13,7 +13,7 @@ variable "kms_key_arn" { variable "tfe_instance_sg" { description = <<-EOD - The identity of the security group attached to the TFE EC2 instance(s) which will be authorized to communicate with + The identity of the security group attached to the TFE EC2 instance(s) which will be authorized to communicate with the Redis Elasticache replication group. EOD type = string @@ -21,7 +21,7 @@ variable "tfe_instance_sg" { variable "network_id" { description = <<-EOD - The identity of the VPC in which the security group attached to the Redis Elasticache replication group will be + The identity of the VPC in which the security group attached to the Redis Elasticache replication group will be deployed. EOD type = string diff --git a/modules/redis/versions.tf b/modules/redis/versions.tf index 4c3fe642..f42f09c9 100644 --- a/modules/redis/versions.tf +++ b/modules/redis/versions.tf @@ -3,7 +3,7 @@ terraform { required_providers { aws = { source = "hashicorp/aws" - version = "~> 3.38" + version = "~> 3.63" } random = { source = "hashicorp/random" diff --git a/modules/service_accounts/outputs.tf b/modules/service_accounts/outputs.tf index 924cee4e..40e22442 100644 --- a/modules/service_accounts/outputs.tf +++ b/modules/service_accounts/outputs.tf @@ -2,7 +2,7 @@ output "aws_iam_instance_profile" { value = aws_iam_instance_profile.tfe.name description = <<-EOD - The name of the IAM instance profile to be attached to the TFE EC2 instance(s) which is authorized to access the S3 + The name of the IAM instance profile to be attached to the TFE EC2 instance(s) which is authorized to access the S3 storage buckets and EC2 autoscaling group. EOD } diff --git a/modules/service_accounts/versions.tf b/modules/service_accounts/versions.tf index 241fbd9e..5733292f 100644 --- a/modules/service_accounts/versions.tf +++ b/modules/service_accounts/versions.tf @@ -3,7 +3,7 @@ terraform { required_providers { aws = { source = "hashicorp/aws" - version = "~> 3.38" + version = "~> 3.63" } } } diff --git a/modules/user_data/main.tf b/modules/user_data/main.tf index 512cfb37..48e02dc0 100644 --- a/modules/user_data/main.tf +++ b/modules/user_data/main.tf @@ -45,6 +45,14 @@ locals { value = "production" } + hairpin_addressing = { + value = "1" + } + + run_pipeline_mode = { + value = "legacy" + } + production_type = { value = "external" } diff --git a/modules/user_data/templates/tfe_ec2.sh.tpl b/modules/user_data/templates/tfe_ec2.sh.tpl index 5e79750a..f75f3858 100644 --- a/modules/user_data/templates/tfe_ec2.sh.tpl +++ b/modules/user_data/templates/tfe_ec2.sh.tpl @@ -9,7 +9,7 @@ install_jq() { install_awscli() { curl "https://awscli.amazonaws.com/awscli-exe-linux-x86_64.zip" -o "awscliv2.zip" unzip awscliv2.zip - ./aws/install + ./aws/install --update rm -f ./awscliv2.zip rm -rf ./aws } @@ -142,7 +142,7 @@ install_tfe() { curl -o /tmp/install.sh https://get.replicated.com/docker/terraformenterprise/active-active chmod +x /tmp/install.sh - /tmp/install.sh "$${arguments[@]}" | tee -a /var/log/ptfe.log + bash /tmp/install.sh "$${arguments[@]}" | tee -a /var/log/ptfe.log } configure_tfe() { diff --git a/modules/vm/main.tf b/modules/vm/main.tf index 919c59c5..fe96e305 100644 --- a/modules/vm/main.tf +++ b/modules/vm/main.tf @@ -2,12 +2,25 @@ # TFE CLUSTER # ############### resource "aws_security_group" "tfe_instance" { - name = "${var.friendly_name_prefix}-tfe-ec2-sg" - vpc_id = var.network_id + name = "${var.friendly_name_prefix}-tfe-ec2-sg" + description = "Managed by Terraform" + vpc_id = var.network_id +} + +resource "aws_security_group_rule" "tfe_redirect_port" { + security_group_id = aws_security_group.tfe_instance.id + description = "Allow ingress HTTP traffic to TFE from the load-balancers for redirect to HTTPS" + type = "ingress" + from_port = 80 + to_port = 80 + protocol = "tcp" + source_security_group_id = var.aws_lb + cidr_blocks = var.aws_lb == null ? var.network_private_subnet_cidrs : null } resource "aws_security_group_rule" "tfe_ui" { security_group_id = aws_security_group.tfe_instance.id + description = "Allow ingress traffic to TFE from the load-balancers" type = "ingress" from_port = 443 to_port = 443 @@ -18,6 +31,7 @@ resource "aws_security_group_rule" "tfe_ui" { resource "aws_security_group_rule" "tfe_inbound" { security_group_id = aws_security_group.tfe_instance.id + description = "Allow ICMP ingress traffic to TFE" type = "ingress" from_port = 0 to_port = 0 @@ -27,6 +41,7 @@ resource "aws_security_group_rule" "tfe_inbound" { resource "aws_security_group_rule" "tfe_outbound" { security_group_id = aws_security_group.tfe_instance.id + description = "Allow egress traffic to all IPV4 addresses" type = "egress" from_port = 0 to_port = 0 @@ -37,6 +52,7 @@ resource "aws_security_group_rule" "tfe_outbound" { resource "aws_security_group_rule" "tfe_dashboard" { count = var.active_active ? 0 : 1 security_group_id = aws_security_group.tfe_instance.id + description = "Allow ingress traffic to dashboard service for TFE from the load-balancer(s)" type = "ingress" from_port = 8800 to_port = 8800 @@ -74,21 +90,37 @@ resource "aws_launch_configuration" "tfe" { } } +# Add ability for ASG to access KMS keys for encrypted images +resource "aws_iam_service_linked_role" "tfe_asg" { + aws_service_name = "autoscaling.amazonaws.com" + custom_suffix = "${var.friendly_name_prefix}-tfe-asg" +} + +resource "aws_kms_grant" "kms_grant_autoscaling" { + count = var.ami_kms_key_arn != null ? 1 : 0 + name = "kms_grant_autoscaling" + key_id = var.ami_kms_key_arn + grantee_principal = aws_iam_service_linked_role.tfe_asg.arn + operations = ["Decrypt", "ReEncryptFrom"] + retire_on_delete = true +} + resource "aws_autoscaling_group" "tfe_asg" { name = "${var.friendly_name_prefix}-tfe-asg" min_size = var.node_count max_size = var.node_count desired_capacity = var.node_count vpc_zone_identifier = var.network_subnets_private - target_group_arns = var.active_active ? [var.aws_lb_target_group_tfe_tg_443_arn] : [ + target_group_arns = var.active_active ? compact([var.aws_lb_target_group_tfe_tg_80_arn, var.aws_lb_target_group_tfe_tg_443_arn]) : [ var.aws_lb_target_group_tfe_tg_8800_arn, var.aws_lb_target_group_tfe_tg_443_arn, ] # Increases grace period for any AMI that is not the default Ubuntu # since RHEL has longer startup time - health_check_grace_period = var.default_ami_id ? 900 : 1500 + health_check_grace_period = var.health_check_grace_period health_check_type = "ELB" launch_configuration = aws_launch_configuration.tfe.name + service_linked_role_arn = aws_iam_service_linked_role.tfe_asg.arn tags = concat( [ @@ -107,6 +139,8 @@ resource "aws_autoscaling_group" "tfe_asg" { ] ) + depends_on = [aws_kms_grant.kms_grant_autoscaling] + lifecycle { create_before_destroy = true } diff --git a/modules/vm/variables.tf b/modules/vm/variables.tf index a814c2dc..c3fbd68c 100644 --- a/modules/vm/variables.tf +++ b/modules/vm/variables.tf @@ -1,8 +1,3 @@ -variable "default_ami_id" { - description = "The identity of the AMI which will be used to provision the TFE EC2 instance(s)." - type = string -} - variable "user_data_base64" { description = "A Base64 encoded user data script to be executed when launching the TFE EC2 instance(s)." type = string @@ -16,6 +11,15 @@ variable "aws_lb" { type = string } +variable "aws_lb_target_group_tfe_tg_80_arn" { + description = <<-EOD + The Amazon Resource Name of the load balancer target group for traffic on port + 80 which will be backed by the TFE EC2 autoscaling group. + EOD + default = null + type = string +} + variable "aws_lb_target_group_tfe_tg_443_arn" { description = <<-EOD The Amazon Resource Name of the load balancer target group for traffic on port @@ -64,6 +68,12 @@ variable "ami_id" { description = "AMI ID to use for TFE instances" } +variable "ami_kms_key_arn" { + type = string + description = "Optional KMS key ARN to use with the TFE ASG for encrypted AMIs" + default = null +} + variable "friendly_name_prefix" { type = string description = "(Required) Friendly name prefix used for tagging and naming AWS resources." @@ -77,7 +87,7 @@ variable "node_count" { variable "asg_tags" { type = map(string) description = <