Repository: aws-games/cloud-game-development-toolkit
Pull request #448: TeamCity Module
Author: login: cwwalb
Created: 2025-01-17T16:51:50Z | Merged: 2025-03-13T21:04:17Z

**Feature request:**  TeamCity Server on ECS Fargate #12


## Summary
TeamCity server creation with mounted EFS and Aurora Serverless V2 PostgreSQL configuration. Load balancing, access logging, and health checks configured. Allows configuration of database resources with connection strings as well as providing self managed Amazon EFS file system and access points.

### Changes

> Initial module creation with VPC and docker image running TeamCity server with mounted EFS for data and logs. Aurora Serverless V2 running PostgreSQL engine backing the TeamCity server. Resource creation is configurable, and checkov requirements are addressed.

### User experience

> Using this module, a user will be able to deploy TeamCity Server hosted on an Amazon ECS container managed by AWS Fargate. The TeamCity Server will have an attached Amazon EFS file system and Amazon Aurora Serverless v2 PostgreSQL database. The user will have the option to create these resources by deploying this module from the simple example, or configure this module to use their own application load balancer, ECS cluster, database via a database connection string, or use their own managed Amazon EFS file system.

## Checklist

If your change doesn't seem to apply, please leave them unchecked.

* [ ] I have performed a self-review of this change
* [ ] Changes have been tested
* [ ] Changes are documented

<details>
<summary>Is this a breaking change?</summary>

</details>

## Acknowledgment

By submitting this pull request, I confirm that you can use, modify, copy, and redistribute this contribution, under the terms of your choice.

**Disclaimer**: We value your time and bandwidth. As such, any pull requests created might not be successful.


Diff:
diff --git a/modules/teamcity/README.md b/modules/teamcity/README.md
new file mode 100644
index 000000000..3f61a6b94
--- /dev/null
+++ b/modules/teamcity/README.md
@@ -0,0 +1,107 @@
+# TeamCity
+
+
+<!-- BEGIN_TF_DOCS -->
+## Requirements
+
+| Name | Version |
+|------|---------|
+| <a name="requirement_terraform"></a> [terraform](#requirement\_terraform) | >= 1.9 |
+| <a name="requirement_aws"></a> [aws](#requirement\_aws) | 5.89.0 |
+| <a name="requirement_random"></a> [random](#requirement\_random) | 3.5.1 |
+
+## Providers
+
+| Name | Version |
+|------|---------|
+| <a name="provider_aws"></a> [aws](#provider\_aws) | 5.89.0 |
+| <a name="provider_random"></a> [random](#provider\_random) | 3.5.1 |
+
+## Modules
+
+No modules.
+
+## Resources
+
+| Name | Type |
+|------|------|
+| [aws_cloudwatch_log_group.teamcity_log_group](https://registry.terraform.io/providers/hashicorp/aws/5.89.0/docs/resources/cloudwatch_log_group) | resource |
+| [aws_db_subnet_group.teamcity_db_subnet_group](https://registry.terraform.io/providers/hashicorp/aws/5.89.0/docs/resources/db_subnet_group) | resource |
+| [aws_ecs_cluster.teamcity_cluster](https://registry.terraform.io/providers/hashicorp/aws/5.89.0/docs/resources/ecs_cluster) | resource |
+| [aws_ecs_service.teamcity](https://registry.terraform.io/providers/hashicorp/aws/5.89.0/docs/resources/ecs_service) | resource |
+| [aws_ecs_task_definition.teamcity_task_definition](https://registry.terraform.io/providers/hashicorp/aws/5.89.0/docs/resources/ecs_task_definition) | resource |
+| [aws_efs_access_point.teamcity_efs_data_access_point](https://registry.terraform.io/providers/hashicorp/aws/5.89.0/docs/resources/efs_access_point) | resource |
+| [aws_efs_file_system.teamcity_efs_file_system](https://registry.terraform.io/providers/hashicorp/aws/5.89.0/docs/resources/efs_file_system) | resource |
+| [aws_efs_mount_target.teamcity_efs_mount_target](https://registry.terraform.io/providers/hashicorp/aws/5.89.0/docs/resources/efs_mount_target) | resource |
+| [aws_iam_policy.teamcity_default_policy](https://registry.terraform.io/providers/hashicorp/aws/5.89.0/docs/resources/iam_policy) | resource |
+| [aws_iam_role.teamcity_default_role](https://registry.terraform.io/providers/hashicorp/aws/5.89.0/docs/resources/iam_role) | resource |
+| [aws_iam_role.teamcity_task_execution_role](https://registry.terraform.io/providers/hashicorp/aws/5.89.0/docs/resources/iam_role) | resource |
+| [aws_lb.teamcity_external_lb](https://registry.terraform.io/providers/hashicorp/aws/5.89.0/docs/resources/lb) | resource |
+| [aws_lb_listener.teamcity_listener](https://registry.terraform.io/providers/hashicorp/aws/5.89.0/docs/resources/lb_listener) | resource |
+| [aws_lb_target_group.teamcity_target_group](https://registry.terraform.io/providers/hashicorp/aws/5.89.0/docs/resources/lb_target_group) | resource |
+| [aws_rds_cluster.teamcity_db_cluster](https://registry.terraform.io/providers/hashicorp/aws/5.89.0/docs/resources/rds_cluster) | resource |
+| [aws_rds_cluster_instance.teamcity_db_cluster_instance](https://registry.terraform.io/providers/hashicorp/aws/5.89.0/docs/resources/rds_cluster_instance) | resource |
+| [aws_s3_bucket.teamcity_alb_access_logs_bucket](https://registry.terraform.io/providers/hashicorp/aws/5.89.0/docs/resources/s3_bucket) | resource |
+| [aws_s3_bucket_lifecycle_configuration.access_logs_bucket_lifecycle_configuration](https://registry.terraform.io/providers/hashicorp/aws/5.89.0/docs/resources/s3_bucket_lifecycle_configuration) | resource |
+| [aws_s3_bucket_policy.alb_access_logs_bucket_policy](https://registry.terraform.io/providers/hashicorp/aws/5.89.0/docs/resources/s3_bucket_policy) | resource |
+| [aws_s3_bucket_public_access_block.access_logs_bucket_public_block](https://registry.terraform.io/providers/hashicorp/aws/5.89.0/docs/resources/s3_bucket_public_access_block) | resource |
+| [aws_security_group.teamcity_alb_sg](https://registry.terraform.io/providers/hashicorp/aws/5.89.0/docs/resources/security_group) | resource |
+| [aws_security_group.teamcity_db_sg](https://registry.terraform.io/providers/hashicorp/aws/5.89.0/docs/resources/security_group) | resource |
+| [aws_security_group.teamcity_efs_sg](https://registry.terraform.io/providers/hashicorp/aws/5.89.0/docs/resources/security_group) | resource |
+| [aws_security_group.teamcity_service_sg](https://registry.terraform.io/providers/hashicorp/aws/5.89.0/docs/resources/security_group) | resource |
+| [aws_vpc_security_group_egress_rule.alb_outbound_service](https://registry.terraform.io/providers/hashicorp/aws/5.89.0/docs/resources/vpc_security_group_egress_rule) | resource |
+| [aws_vpc_security_group_egress_rule.service_outbound_internet](https://registry.terraform.io/providers/hashicorp/aws/5.89.0/docs/resources/vpc_security_group_egress_rule) | resource |
+| [aws_vpc_security_group_ingress_rule.service_db](https://registry.terraform.io/providers/hashicorp/aws/5.89.0/docs/resources/vpc_security_group_ingress_rule) | resource |
+| [aws_vpc_security_group_ingress_rule.service_efs](https://registry.terraform.io/providers/hashicorp/aws/5.89.0/docs/resources/vpc_security_group_ingress_rule) | resource |
+| [aws_vpc_security_group_ingress_rule.service_inbound_alb](https://registry.terraform.io/providers/hashicorp/aws/5.89.0/docs/resources/vpc_security_group_ingress_rule) | resource |
+| [random_string.teamcity_alb_access_logs_bucket_suffix](https://registry.terraform.io/providers/hashicorp/random/3.5.1/docs/resources/string) | resource |
+| [aws_ecs_cluster.teamcity_cluster](https://registry.terraform.io/providers/hashicorp/aws/5.89.0/docs/data-sources/ecs_cluster) | data source |
+| [aws_efs_file_system.efs_file_system](https://registry.terraform.io/providers/hashicorp/aws/5.89.0/docs/data-sources/efs_file_system) | data source |
+| [aws_elb_service_account.main](https://registry.terraform.io/providers/hashicorp/aws/5.89.0/docs/data-sources/elb_service_account) | data source |
+| [aws_iam_policy_document.access_logs_bucket_alb_write](https://registry.terraform.io/providers/hashicorp/aws/5.89.0/docs/data-sources/iam_policy_document) | data source |
+| [aws_iam_policy_document.ecs_tasks_trust_relationship](https://registry.terraform.io/providers/hashicorp/aws/5.89.0/docs/data-sources/iam_policy_document) | data source |
+| [aws_iam_policy_document.teamcity_default_policy](https://registry.terraform.io/providers/hashicorp/aws/5.89.0/docs/data-sources/iam_policy_document) | data source |
+| [aws_region.current](https://registry.terraform.io/providers/hashicorp/aws/5.89.0/docs/data-sources/region) | data source |
+
+## Inputs
+
+| Name | Description | Type | Default | Required |
+|------|-------------|------|---------|:--------:|
+| <a name="input_alb_certificate_arn"></a> [alb\_certificate\_arn](#input\_alb\_certificate\_arn) | The ARN of the SSL certificate to use for the ALB | `string` | `null` | no |
+| <a name="input_alb_subnets"></a> [alb\_subnets](#input\_alb\_subnets) | The subnets in which the ALB will be deployed | `list(string)` | `[]` | no |
+| <a name="input_aurora_instance_count"></a> [aurora\_instance\_count](#input\_aurora\_instance\_count) | Number of instances to provision for the Aurora cluster | `number` | `2` | no |
+| <a name="input_aurora_skip_final_snapshot"></a> [aurora\_skip\_final\_snapshot](#input\_aurora\_skip\_final\_snapshot) | Flag for whether a final snapshot should be created when the cluster is destroyed. | `bool` | `true` | no |
+| <a name="input_cluster_name"></a> [cluster\_name](#input\_cluster\_name) | The name of the ECS cluster to deploy TeamCity to. | `string` | `null` | no |
+| <a name="input_container_cpu"></a> [container\_cpu](#input\_container\_cpu) | The number of CPU units to allocate to the TeamCity server container | `number` | `1024` | no |
+| <a name="input_container_memory"></a> [container\_memory](#input\_container\_memory) | The number of MB of memory to allocate to the TeamCity server container | `number` | `4096` | no |
+| <a name="input_container_name"></a> [container\_name](#input\_container\_name) | The name of the TeamCity server container | `string` | `"teamcity"` | no |
+| <a name="input_container_port"></a> [container\_port](#input\_container\_port) | The port on which the TeamCity server container listens | `number` | `8111` | no |
+| <a name="input_create_external_alb"></a> [create\_external\_alb](#input\_create\_external\_alb) | Set this flag to true to create an external load balancer for TeamCity. | `bool` | `true` | no |
+| <a name="input_database_connection_string"></a> [database\_connection\_string](#input\_database\_connection\_string) | The database connection string for TeamCity | `string` | `null` | no |
+| <a name="input_database_master_password"></a> [database\_master\_password](#input\_database\_master\_password) | The master password for the database | `string` | `null` | no |
+| <a name="input_database_master_username"></a> [database\_master\_username](#input\_database\_master\_username) | The master username for the database | `string` | `null` | no |
+| <a name="input_debug"></a> [debug](#input\_debug) | Set this flag to enable ECS execute permissions on the TeamCity server container and force new service deployments on Terraform apply. | `bool` | `false` | no |
+| <a name="input_desired_container_count"></a> [desired\_container\_count](#input\_desired\_container\_count) | The desired number of containers running TeamCity server. | `number` | `1` | no |
+| <a name="input_efs_access_point_id"></a> [efs\_access\_point\_id](#input\_efs\_access\_point\_id) | The ID of the EFS access point to use for the TeamCity data volume. | `string` | `null` | no |
+| <a name="input_efs_encryption_enabled"></a> [efs\_encryption\_enabled](#input\_efs\_encryption\_enabled) | Set this flag to true to enable EFS encryption. | `bool` | `true` | no |
+| <a name="input_efs_id"></a> [efs\_id](#input\_efs\_id) | The ID of the EFS file system to use for the TeamCity service. | `string` | `null` | no |
+| <a name="input_enable_teamcity_alb_access_logs"></a> [enable\_teamcity\_alb\_access\_logs](#input\_enable\_teamcity\_alb\_access\_logs) | Enables access logging for the TeamCity ALB. Defaults to true. | `bool` | `true` | no |
+| <a name="input_enable_teamcity_alb_deletion_protection"></a> [enable\_teamcity\_alb\_deletion\_protection](#input\_enable\_teamcity\_alb\_deletion\_protection) | Enables deletion protection for the TeamCity ALB. Defaults to true. | `bool` | `false` | no |
+| <a name="input_environment"></a> [environment](#input\_environment) | The current environment (e.g. dev, prod, etc.) | `string` | `"dev"` | no |
+| <a name="input_name"></a> [name](#input\_name) | The name applied to resources in the TeamCity module | `string` | `"teamcity"` | no |
+| <a name="input_service_subnets"></a> [service\_subnets](#input\_service\_subnets) | The subnets in which the TeamCity server service will be deployed | `list(string)` | n/a | yes |
+| <a name="input_tags"></a> [tags](#input\_tags) | Tags to apply to resources. | `map(any)` | <pre>{<br/>  "iac-management": "CGD-Toolkit",<br/>  "iac-module": "TeamCity",<br/>  "iac-provider": "Terraform"<br/>}</pre> | no |
+| <a name="input_teamcity_alb_access_logs_bucket"></a> [teamcity\_alb\_access\_logs\_bucket](#input\_teamcity\_alb\_access\_logs\_bucket) | ID of the S3 bucket for TeamCity ALB access log storage. If access logging is enabled and this is null the module creates a bucket. | `string` | `null` | no |
+| <a name="input_teamcity_alb_access_logs_prefix"></a> [teamcity\_alb\_access\_logs\_prefix](#input\_teamcity\_alb\_access\_logs\_prefix) | Log prefix for TeamCity ALB access logs. If null the project prefix and module name are used. | `string` | `null` | no |
+| <a name="input_teamcity_cloudwatch_log_retention_in_days"></a> [teamcity\_cloudwatch\_log\_retention\_in\_days](#input\_teamcity\_cloudwatch\_log\_retention\_in\_days) | The log retention in days of the cloudwatch log group for TeamCity. | `string` | `365` | no |
+| <a name="input_teamcity_efs_performance_mode"></a> [teamcity\_efs\_performance\_mode](#input\_teamcity\_efs\_performance\_mode) | The performance mode of the EFS file system used by the TeamCity service. Defaults to general purpose. | `string` | `"generalPurpose"` | no |
+| <a name="input_teamcity_efs_throughput_mode"></a> [teamcity\_efs\_throughput\_mode](#input\_teamcity\_efs\_throughput\_mode) | The throughput mode of the EFS file system used by the TeamCity service. Defaults to bursting. | `string` | `"bursting"` | no |
+| <a name="input_vpc_id"></a> [vpc\_id](#input\_vpc\_id) | The ID of the VPC in which the service will be deployed | `string` | n/a | yes |
+
+## Outputs
+
+| Name | Description |
+|------|-------------|
+| <a name="output_external_alb_dns_name"></a> [external\_alb\_dns\_name](#output\_external\_alb\_dns\_name) | DNS endpoint of Application Load Balancer (ALB) |
+| <a name="output_external_alb_zone_id"></a> [external\_alb\_zone\_id](#output\_external\_alb\_zone\_id) | Zone ID for internet facing load balancer |
+<!-- END_TF_DOCS -->
diff --git a/modules/teamcity/examples/simple/dns.tf b/modules/teamcity/examples/simple/dns.tf
new file mode 100644
index 000000000..cc1eac950
--- /dev/null
+++ b/modules/teamcity/examples/simple/dns.tf
@@ -0,0 +1,63 @@
+variable "root_domain_name" {
+  type        = string
+  description = "The root domain name for the Hosted Zone where the TeamCity record should be created."
+}
+
+##########################################
+# Route53 Hosted Zone for Root
+##########################################
+data "aws_route53_zone" "root" {
+  name         = var.root_domain_name
+  private_zone = false
+}
+
+# Create a record in the Hosted Zone for the TeamCity server
+resource "aws_route53_record" "teamcity" {
+  zone_id = data.aws_route53_zone.root.id
+  name    = "teamcity.${data.aws_route53_zone.root.name}"
+  type    = "A"
+
+  alias {
+    name                   = module.teamcity.external_alb_dns_name
+    zone_id                = module.teamcity.external_alb_zone_id
+    evaluate_target_health = false
+  }
+}
+
+# Create a certificate for the TeamCity server
+resource "aws_acm_certificate" "teamcity" {
+  domain_name       = "teamcity.${data.aws_route53_zone.root.name}"
+  validation_method = "DNS"
+
+  tags = {
+    Environment = "test"
+  }
+  lifecycle {
+    create_before_destroy = true
+  }
+}
+
+resource "aws_route53_record" "teamcity_cert" {
+  for_each = {
+    for dvo in aws_acm_certificate.teamcity.domain_validation_options : dvo.domain_name => {
+      name   = dvo.resource_record_name
+      record = dvo.resource_record_value
+      type   = dvo.resource_record_type
+    }
+  }
+
+  allow_overwrite = true
+  name            = each.value.name
+  records         = [each.value.record]
+  ttl             = 60
+  type            = each.value.type
+  zone_id         = data.aws_route53_zone.root.id
+}
+
+resource "aws_acm_certificate_validation" "teamcity" {
+  timeouts {
+    create = "15m"
+  }
+  certificate_arn         = aws_acm_certificate.teamcity.arn
+  validation_record_fqdns = [for record in aws_route53_record.teamcity_cert : record.fqdn]
+}
diff --git a/modules/teamcity/examples/simple/local.tf b/modules/teamcity/examples/simple/local.tf
new file mode 100644
index 000000000..297196e91
--- /dev/null
+++ b/modules/teamcity/examples/simple/local.tf
@@ -0,0 +1,10 @@
+data "aws_availability_zones" "available" {}
+
+locals {
+  vpc_cidr_block       = "10.0.0.0/16"
+  public_subnet_cidrs  = ["10.0.1.0/24", "10.0.2.0/24"]
+  private_subnet_cidrs = ["10.0.3.0/24", "10.0.4.0/24"]
+  azs                  = slice(data.aws_availability_zones.available.names, 0, 2)
+  tags                 = {}
+
+}
diff --git a/modules/teamcity/examples/simple/main.tf b/modules/teamcity/examples/simple/main.tf
new file mode 100644
index 000000000..f2bf775cd
--- /dev/null
+++ b/modules/teamcity/examples/simple/main.tf
@@ -0,0 +1,7 @@
+module "teamcity" {
+  source              = "../../"
+  vpc_id              = aws_vpc.teamcity_vpc.id
+  service_subnets     = aws_subnet.private_subnets[*].id
+  alb_subnets         = aws_subnet.public_subnets[*].id
+  alb_certificate_arn = aws_acm_certificate.teamcity.arn
+}
diff --git a/modules/teamcity/examples/simple/versions.tf b/modules/teamcity/examples/simple/versions.tf
new file mode 100644
index 000000000..c5ef8f0f4
--- /dev/null
+++ b/modules/teamcity/examples/simple/versions.tf
@@ -0,0 +1,14 @@
+terraform {
+  required_version = ">= 1.0"
+
+  required_providers {
+    aws = {
+      source  = "hashicorp/aws"
+      version = "5.89.0"
+    }
+    random = {
+      source  = "hashicorp/random"
+      version = "3.5.1"
+    }
+  }
+}
diff --git a/modules/teamcity/examples/simple/vpc.tf b/modules/teamcity/examples/simple/vpc.tf
new file mode 100644
index 000000000..dac544a78
--- /dev/null
+++ b/modules/teamcity/examples/simple/vpc.tf
@@ -0,0 +1,123 @@
+resource "aws_vpc" "teamcity_vpc" {
+  cidr_block = local.vpc_cidr_block
+  tags = merge(local.tags,
+    {
+      Name = "teamcity-vpc"
+    }
+  )
+  enable_dns_hostnames = true
+  #checkov:skip=CKV2_AWS_11: VPC flow logging disabled by design
+}
+
+# Set default SG to restrict all traffic
+resource "aws_default_security_group" "default" {
+  vpc_id = aws_vpc.teamcity_vpc.id
+}
+
+resource "aws_subnet" "public_subnets" {
+  count             = length(local.public_subnet_cidrs)
+  vpc_id            = aws_vpc.teamcity_vpc.id
+  cidr_block        = element(local.public_subnet_cidrs, count.index)
+  availability_zone = element(local.azs, count.index)
+
+  tags = merge(local.tags,
+    {
+      Name = "pub-subnet-${count.index + 1}"
+    }
+  )
+}
+
+resource "aws_subnet" "private_subnets" {
+  count             = length(local.private_subnet_cidrs)
+  vpc_id            = aws_vpc.teamcity_vpc.id
+  cidr_block        = element(local.private_subnet_cidrs, count.index)
+  availability_zone = element(local.azs, count.index)
+
+  tags = merge(local.tags,
+    {
+      Name = "pvt-subnet-${count.index + 1}"
+    }
+  )
+}
+
+##########################################
+# Internet Gateway
+##########################################
+
+resource "aws_internet_gateway" "igw" {
+  vpc_id = aws_vpc.teamcity_vpc.id
+  tags = merge(local.tags,
+    {
+      Name = "teamcity-igw"
+    }
+  )
+}
+
+##########################################
+# Route Tables & NAT Gateway
+##########################################
+
+resource "aws_route_table" "public_rt" {
+  vpc_id = aws_vpc.teamcity_vpc.id
+
+  # public route to the internet
+  route {
+    cidr_block = "0.0.0.0/0"
+    gateway_id = aws_internet_gateway.igw.id
+  }
+
+  tags = merge(local.tags,
+    {
+      Name = "teamcity-public-rt"
+    }
+  )
+}
+
+resource "aws_route_table_association" "public_rt_asso" {
+  count          = length(aws_subnet.public_subnets)
+  route_table_id = aws_route_table.public_rt.id
+  subnet_id      = aws_subnet.public_subnets[count.index].id
+}
+
+resource "aws_eip" "nat_gateway_eip" {
+  depends_on = [aws_internet_gateway.igw]
+  #checkov:skip=CKV2_AWS_19:EIP associated with NAT Gateway through association ID
+  tags = merge(local.tags,
+    {
+      Name = "teamcity-nat-eip"
+    }
+  )
+}
+
+resource "aws_route_table" "private_rt" {
+  vpc_id = aws_vpc.teamcity_vpc.id
+
+  tags = merge(local.tags,
+    {
+      Name = "teamcity-private-rt"
+    }
+  )
+}
+
+# route to the internet through NAT gateway
+resource "aws_route" "private_rt_nat_gateway" {
+  route_table_id         = aws_route_table.private_rt.id
+  destination_cidr_block = "0.0.0.0/0"
+  nat_gateway_id         = aws_nat_gateway.nat_gateway.id
+}
+
+resource "aws_route_table_association" "private_rt_asso" {
+  count          = length(aws_subnet.private_subnets)
+  route_table_id = aws_route_table.private_rt.id
+  subnet_id      = aws_subnet.private_subnets[count.index].id
+}
+
+resource "aws_nat_gateway" "nat_gateway" {
+  allocation_id = aws_eip.nat_gateway_eip.id
+  subnet_id     = aws_subnet.public_subnets[0].id
+  tags = merge(local.tags,
+    {
+      Name = "teamcity-nat"
+    }
+  )
+}
diff --git a/modules/teamcity/local.tf b/modules/teamcity/local.tf
new file mode 100644
index 000000000..e21f6f25e
--- /dev/null
+++ b/modules/teamcity/local.tf
@@ -0,0 +1,50 @@
+locals {
+  name_prefix = "teamcity"
+  tags = merge(var.tags, {
+    "environment" = var.environment
+  })
+
+  # Database information
+  database_connection_string = var.database_connection_string != null ? var.database_connection_string : "jdbc:postgresql://${aws_rds_cluster.teamcity_db_cluster[0].endpoint}/teamcity"
+  database_master_username   = var.database_master_username != null ? var.database_master_username : aws_rds_cluster.teamcity_db_cluster[0].master_username
+  database_master_password   = var.database_master_password != null ? var.database_master_password : null
+
+  # Docker image to use for TeamCity Server
+  image = "jetbrains/teamcity-server"
+
+  # EFS information
+  efs_file_system_id  = var.efs_id != null ? var.efs_id : aws_efs_file_system.teamcity_efs_file_system[0].id
+  efs_file_system_arn = var.efs_id != null ? data.aws_efs_file_system.efs_file_system[0].arn : aws_efs_file_system.teamcity_efs_file_system[0].arn
+  efs_access_point_id = var.efs_access_point_id != null ? var.efs_access_point_id : aws_efs_access_point.teamcity_efs_data_access_point[0].id
+
+  # TeamCity Server Information
+  # Set environment variables
+  base_env = [
+    {
+      name  = "TEAMCITY_DB_URL"
+      value = local.database_connection_string
+    },
+    {
+      name  = "TEAMCITY_DB_USER"
+      value = local.database_master_username
+    },
+    {
+      name  = "TEAMCITY_DATA_PATH"
+      value = "/data/teamcity_server/datadir"
+    }
+  ]
+  # Define password environment variable if provided
+  password_env = local.database_master_password != null ? [
+    {
+      name  = "TEAMCITY_DB_PASSWORD"
+      value = local.database_master_password
+    }
+  ] : []
+}
+data "aws_region" "current" {}
+
+# Data source to look up existing EFS file system if ID is provided
+data "aws_efs_file_system" "efs_file_system" {
+  count          = var.efs_id != null ? 1 : 0
+  file_system_id = var.efs_id
+}
diff --git a/modules/teamcity/main.tf b/modules/teamcity/main.tf
new file mode 100644
index 000000000..80a7a30f4
--- /dev/null
+++ b/modules/teamcity/main.tf
@@ -0,0 +1,565 @@
+###################################
+# ECS Cluster for TeamCity Server #
+###################################
+# If cluster name is provided use a data source to access existing resource
+data "aws_ecs_cluster" "teamcity_cluster" {
+  count        = var.cluster_name != null ? 1 : 0
+  cluster_name = var.cluster_name
+}
+resource "aws_ecs_cluster" "teamcity_cluster" {
+  count = var.cluster_name != null ? 0 : 1
+  name  = "${local.name_prefix}-cluster"
+
+  setting {
+    name  = "containerInsights"
+    value = "enabled"
+  }
+
+  tags = local.tags
+}
+
+# TeamCity Task Definition
+resource "aws_ecs_task_definition" "teamcity_task_definition" {
+  family                   = var.name
+  requires_compatibilities = ["FARGATE"]
+  network_mode             = "awsvpc"
+  cpu                      = var.container_cpu
+  memory                   = var.container_memory
+
+  container_definitions = jsonencode([
+    {
+      name      = var.container_name
+      image     = local.image
+      cpu       = var.container_cpu
+      memory    = var.container_memory
+      essential = true
+
+      portMappings = [
+        {
+          containerPort = var.container_port
+          hostPort      = var.container_port
+        }
+      ]
+      logConfiguration = {
+        logDriver = "awslogs"
+        options = {
+          awslogs-group         = aws_cloudwatch_log_group.teamcity_log_group.name
+          awslogs-region        = data.aws_region.current.name
+          awslogs-stream-prefix = "[APP]"
+        }
+      }
+
+      mountPoints = [
+        {
+          sourceVolume  = "teamcity_data",
+          containerPath = "/data/teamcity_server/datadir"
+          readOnly      = false
+        }
+      ]
+
+      # Combine the lists
+      environment = concat(local.base_env, local.password_env)
+
+      secrets = var.database_connection_string == null ? [
+        {
+          name      = "TEAMCITY_DB_PASSWORD"
+          valueFrom = "${aws_rds_cluster.teamcity_db_cluster[0].master_user_secret[0].secret_arn}:password::"
+        }
+      ] : []
+    }
+  ])
+  tags = {
+    Name = var.name
+  }
+  task_role_arn      = aws_iam_role.teamcity_default_role.arn
+  execution_role_arn = aws_iam_role.teamcity_task_execution_role.arn
+
+  volume {
+    name = "teamcity_data"
+    efs_volume_configuration {
+      file_system_id          = local.efs_file_system_id
+      transit_encryption      = "ENABLED"
+      transit_encryption_port = 2999
+      authorization_config {
+        access_point_id = local.efs_access_point_id
+        iam             = "ENABLED"
+      }
+    }
+  }
+}
+
+# ECS Service
+resource "aws_ecs_service" "teamcity" {
+  name                   = local.name_prefix
+  cluster                = var.cluster_name != null ? data.aws_ecs_cluster.teamcity_cluster[0].arn : aws_ecs_cluster.teamcity_cluster[0].arn
+  task_definition        = aws_ecs_task_definition.teamcity_task_definition.arn
+  launch_type            = "FARGATE"
+  desired_count          = var.desired_container_count
+  force_new_deployment   = var.debug
+  enable_execute_command = var.debug
+
+  wait_for_steady_state = false #TODO: make this configurable
+
+  network_configuration {
+    subnets         = var.service_subnets
+    security_groups = [aws_security_group.teamcity_service_sg.id]
+  }
+  dynamic "load_balancer" {
+    for_each = var.create_external_alb ? [1] : []
+    content {
+      target_group_arn = aws_lb_target_group.teamcity_target_group[0].arn
+      container_name   = var.container_name
+      container_port   = var.container_port
+
+    }
+  }
+
+  depends_on = [
+    aws_rds_cluster.teamcity_db_cluster[0],
+    aws_rds_cluster_instance.teamcity_db_cluster_instance
+  ]
+
+  tags = local.tags
+}
+
+###################################
+# Security Groups                 #
+###################################
+
+# TeamCity service security group
+resource "aws_security_group" "teamcity_service_sg" {
+  name        = "${local.name_prefix}-service-sg"
+  vpc_id      = var.vpc_id
+  description = "TeamCity service security group"
+  tags        = local.tags
+}
+
+# TeamCity EFS security group
+resource "aws_security_group" "teamcity_efs_sg" {
+  count       = var.efs_id == null ? 1 : 0
+  name        = "${local.name_prefix}-efs-sg"
+  description = "TeamCity EFS mount target security group"
+  vpc_id      = var.vpc_id
+  tags        = local.tags
+}
+
+# Ingress rule for NFS traffic from service to EFS
+resource "aws_vpc_security_group_ingress_rule" "service_efs" {
+  count                        = var.efs_id == null ? 1 : 0
+  security_group_id            = aws_security_group.teamcity_efs_sg[0].id
+  referenced_security_group_id = aws_security_group.teamcity_service_sg.id
+  description                  = "Allow inbound access from TeamCity service containers to EFS"
+  ip_protocol                  = "TCP"
+  from_port                    = 2049
+  to_port                      = 2049
+}
+
+# TeamCity Aurora Serverless PostgreSQL security group
+resource "aws_security_group" "teamcity_db_sg" {
+  count       = var.database_connection_string == null ? 1 : 0
+  name        = "${local.name_prefix}-db-sg"
+  description = "TeamCity DB security group"
+  vpc_id      = var.vpc_id
+  tags        = local.tags
+}
+
+# Ingress rule for PostgreSQL from service to database cluster
+resource "aws_vpc_security_group_ingress_rule" "service_db" {
+  count                        = var.database_connection_string == null ? 1 : 0
+  security_group_id            = aws_security_group.teamcity_db_sg[0].id
+  referenced_security_group_id = aws_security_group.teamcity_service_sg.id
+  description                  = "Allow inbound access from TeamCity service containers to DB"
+  ip_protocol                  = "TCP"
+  from_port                    = 5432
+  to_port                      = 5432
+}
+
+# TeamCity ALB security group
+resource "aws_security_group" "teamcity_alb_sg" {
+  #checkov:skip=CKV2_AWS_5:SG is attached to TeamCity service ALB
+  count       = var.create_external_alb ? 1 : 0
+  name        = "${local.name_prefix}-alb-sg"
+  vpc_id      = var.vpc_id
+  description = "TeamCity ALB security group"
+  tags        = local.tags
+}
+
+# Ingress rule for HTTP traffic from ALB to service
+resource "aws_vpc_security_group_ingress_rule" "service_inbound_alb" {
+  count                        = var.create_external_alb ? 1 : 0
+  security_group_id            = aws_security_group.teamcity_service_sg.id
+  referenced_security_group_id = aws_security_group.teamcity_alb_sg[0].id
+  description                  = "Allow inbound HTTP traffic from ALB to service containers"
+  from_port                    = var.container_port
+  to_port                      = var.container_port
+  ip_protocol                  = "TCP"
+}
+
+# Egress rule for HTTP traffic from ALB to service
+resource "aws_vpc_security_group_egress_rule" "alb_outbound_service" {
+  count                        = var.create_external_alb ? 1 : 0
+  security_group_id            = aws_security_group.teamcity_alb_sg[0].id
+  referenced_security_group_id = aws_security_group.teamcity_service_sg.id
+  description                  = "Allow outbound HTTP traffic from ALB to service containers"
+  from_port                    = var.container_port
+  to_port                      = var.container_port
+  ip_protocol                  = "TCP"
+}
+
+# Grant TeamCity service access to internet
+resource "aws_vpc_security_group_egress_rule" "service_outbound_internet" {
+  security_group_id = aws_security_group.teamcity_service_sg.id
+  description       = "Allow outbound internet access from TeamCity service containers"
+  cidr_ipv4         = "0.0.0.0/0"
+  ip_protocol       = "-1"
+}
+
+#############################################
+# IAM Roles for TeamCity Module
+#############################################
+
+data "aws_iam_policy_document" "ecs_tasks_trust_relationship" {
+  statement {
+    effect  = "Allow"
+    actions = ["sts:AssumeRole"]
+    principals {
+      type        = "Service"
+      identifiers = ["ecs-tasks.amazonaws.com"]
+    }
+  }
+}
+
+data "aws_iam_policy_document" "teamcity_default_policy" {
+  # ECS
+  statement {
+    sid    = "ECSExec"
+    effect = "Allow"
+    actions = [
+      "ssmmessages:OpenDataChannel",
+      "ssmmessages:OpenControlChannel",
+      "ssmmessages:CreateDataChannel",
+      "ssmmessages:CreateControlChannel",
+    ]
+    resources = [
+      "*"
+    ]
+  }
+
+  #grant the task necessary EFS permissions to be able to modify directories
+  statement {
+    sid    = "EFS"
+    effect = "Allow"
+    actions = [
+      "elasticfilesystem:ClientWrite",
+      "elasticfilesystem:ClientRootAccess",
+      "elasticfilesystem:ClientMount"
+    ]
+    resources = [
+      local.efs_file_system_arn
+    ]
+  }
+}
+
+resource "aws_iam_policy" "teamcity_default_policy" {
+  name        = "teamcity-default-policy"
+  description = "Policy granting permissions for TeamCity."
+  policy      = data.aws_iam_policy_document.teamcity_default_policy.json
+}
+
+resource "aws_iam_role" "teamcity_default_role" {
+  name               = "teamcity-default-role"
+  description        = "Default role for TeamCity ECS task."
+  assume_role_policy = data.aws_iam_policy_document.ecs_tasks_trust_relationship.json
+  managed_policy_arns = [
+    aws_iam_policy.teamcity_default_policy.arn
+  ]
+
+  tags = local.tags
+}
+resource "aws_iam_role" "teamcity_task_execution_role" {
+  name = "teamcity-task-execution-role"
+
+  assume_role_policy  = data.aws_iam_policy_document.ecs_tasks_trust_relationship.json
+  managed_policy_arns = ["arn:aws:iam::aws:policy/service-role/AmazonECSTaskExecutionRolePolicy"]
+
+  dynamic "inline_policy" {
+    for_each = var.database_connection_string == null ? [1] : []
+    content {
+      name = "teamcity-secrets-access"
+      policy = jsonencode({
+        Version = "2012-10-17"
+        Statement = [
+          {
+            Sid    = "SecretsManager"
+            Effect = "Allow"
+            Action = [
+              "secretsmanager:GetSecretValue"
+            ]
+            Resource = [
+              aws_rds_cluster.teamcity_db_cluster[0].master_user_secret[0].secret_arn
+            ]
+          }
+        ]
+      })
+    }
+  }
+
+}
+
+# CloudWatch Logs
+resource "aws_cloudwatch_log_group" "teamcity_log_group" {
+  #checkov:skip=CKV_AWS_158: KMS Encryption disabled by default
+  name              = "${local.name_prefix}-log-group"
+  retention_in_days = var.teamcity_cloudwatch_log_retention_in_days
+  tags              = local.tags
+}
+
+# Load Balancer for TeamCity Service
+resource "aws_lb" "teamcity_external_lb" {
+  count              = var.create_external_alb ? 1 : 0
+  name               = "${local.name_prefix}-lb"
+  security_groups    = [aws_security_group.teamcity_alb_sg[0].id]
+  load_balancer_type = "application"
+  internal           = false
+  subnets            = var.alb_subnets
+
+
+  dynamic "access_logs" {
+    for_each = var.enable_teamcity_alb_access_logs ? [1] : []
+    content {
+      enabled = var.enable_teamcity_alb_access_logs
+      bucket  = var.teamcity_alb_access_logs_bucket != null ? var.teamcity_alb_access_logs_bucket : aws_s3_bucket.teamcity_alb_access_logs_bucket[0].id
+      prefix  = var.teamcity_alb_access_logs_prefix != null ? var.teamcity_alb_access_logs_prefix : "${local.name_prefix}-alb"
+    }
+  }
+  #checkov:skip=CKV_AWS_150:Deletion protection disabled by default
+  enable_deletion_protection = var.enable_teamcity_alb_deletion_protection
+
+  #checkov:skip=CKV2_AWS_28: ALB access is managed with SG allow listing
+
+  drop_invalid_header_fields = true
+  tags                       = local.tags
+}
+
+# TeamCity target group for ALB
+resource "aws_lb_target_group" "teamcity_target_group" {
+  #checkov:skip=CKV_AWS_378: Using ALB for TLS termination
+  count       = var.create_external_alb ? 1 : 0
+  name        = "${local.name_prefix}-tg"
+  port        = var.container_port
+  protocol    = "HTTP"
+  vpc_id      = var.vpc_id
+  target_type = "ip"
+
+  health_check {
+    path                = "/healthCheck/healthy"
+    interval            = 30
+    timeout             = 5
+    healthy_threshold   = 5
+    unhealthy_threshold = 2
+    port                = var.container_port
+    protocol            = "HTTP"
+    matcher             = "200"
+  }
+
+  tags = local.tags
+}
+
+# ALB HTTPS Listener
+resource "aws_lb_listener" "teamcity_listener" {
+  count             = var.create_external_alb ? 1 : 0
+  load_balancer_arn = aws_lb.teamcity_external_lb[0].arn
+  port              = "443"
+  protocol          = "HTTPS"
+  ssl_policy        = "ELBSecurityPolicy-TLS13-1-2-2021-06"
+  certificate_arn   = var.alb_certificate_arn
+  default_action {
+    type             = "forward"
+    target_group_arn = aws_lb_target_group.teamcity_target_group[0].arn
+  }
+  tags = local.tags
+}
+
+# #######################################
+# # TeamCity Aurora Serverless Database #
+# #######################################
+# Subnet group
+resource "aws_db_subnet_group" "teamcity_db_subnet_group" {
+  count      = var.database_connection_string == null ? 1 : 0
+  name       = "${local.name_prefix}-db-subnet-group"
+  subnet_ids = var.service_subnets
+  tags       = local.tags
+}
+
+# # RDS instance with Aurora serverless engine
+resource "aws_rds_cluster" "teamcity_db_cluster" {
+  #checkov:skip=CKV2_AWS_27:not enabling query logging by design
+  #checkov:skip=CKV2_AWS_8: TODO: add rds backup plan
+  count                       = var.database_connection_string == null ? 1 : 0
+  cluster_identifier          = "teamcity-cluster"
+  engine                      = "aurora-postgresql"
+  engine_mode                 = "provisioned"
+  engine_version              = "16.6" #check for latest as option
+  database_name               = "teamcity"
+  master_username             = "teamcity"
+  manage_master_user_password = true #using AWS Secrets Manager
+  storage_encrypted           = true
+  skip_final_snapshot         = var.aurora_skip_final_snapshot
+  db_subnet_group_name        = aws_db_subnet_group.teamcity_db_subnet_group[0].id
+  vpc_security_group_ids = [
+    aws_security_group.teamcity_db_sg[0].id
+  ]
+
+  serverlessv2_scaling_configuration {
+    max_capacity             = 1.0
+    min_capacity             = 0.0
+    seconds_until_auto_pause = 3600
+  }
+}
+
+resource "aws_rds_cluster_instance" "teamcity_db_cluster_instance" {
+  count              = var.database_connection_string == null ? var.aurora_instance_count : 0
+  cluster_identifier = aws_rds_cluster.teamcity_db_cluster[0].id
+  instance_class     = "db.serverless"
+  engine             = aws_rds_cluster.teamcity_db_cluster[0].engine
+  engine_version     = aws_rds_cluster.teamcity_db_cluster[0].engine_version
+}
+
+# ################
+# # TeamCity EFS #
+# ################
+
+# File system for teamcity
+resource "aws_efs_file_system" "teamcity_efs_file_system" {
+  count            = var.efs_id != null ? 0 : 1
+  creation_token   = "${local.name_prefix}-efs-file-system"
+  performance_mode = var.teamcity_efs_performance_mode
+  throughput_mode  = var.teamcity_efs_throughput_mode
+
+  encrypted = var.efs_encryption_enabled
+
+  lifecycle_policy {
+    transition_to_ia = "AFTER_30_DAYS"
+  }
+
+  lifecycle_policy {
+    transition_to_primary_storage_class = "AFTER_1_ACCESS"
+  }
+  #checkov:skip=CKV_AWS_184: CMK encryption not supported currently
+  tags = merge(local.tags, {
+    Name = "${local.name_prefix}-efs-file-system"
+  })
+}
+
+# Mount Point for teamcity file system
+resource "aws_efs_mount_target" "teamcity_efs_mount_target" {
+  count          = var.efs_id != null ? 0 : length(var.service_subnets)
+  file_system_id = var.efs_id != null ? var.efs_id : aws_efs_file_system.teamcity_efs_file_system[0].id
+  subnet_id      = var.service_subnets[count.index]
+  security_groups = [
+    aws_security_group.teamcity_efs_sg[0].id
+  ]
+}
+
+# TeamCity data directory
+resource "aws_efs_access_point" "teamcity_efs_data_access_point" {
+  count          = var.efs_access_point_id != null ? 0 : 1
+  file_system_id = aws_efs_file_system.teamcity_efs_file_system[0].id
+  posix_user {
+    gid = 1000
+    uid = 1000
+  }
+  root_directory {
+    path = "/data/teamcity_server/datadir"
+    creation_info {
+      owner_gid   = 1000
+      owner_uid   = 1000
+      permissions = 0755
+    }
+  }
+  tags = merge(local.tags, {
+    Name = "${local.name_prefix}-efs-access-point"
+  })
+}
+
+###########################
+# Access Logs
+###########################
+
+resource "random_string" "teamcity_alb_access_logs_bucket_suffix" {
+  count   = var.enable_teamcity_alb_access_logs && var.teamcity_alb_access_logs_bucket == null ? 1 : 0
+  length  = 8
+  special = false
+  upper   = false
+}
+
+resource "aws_s3_bucket" "teamcity_alb_access_logs_bucket" {
+  count  = var.enable_teamcity_alb_access_logs && var.teamcity_alb_access_logs_bucket == null ? 1 : 0
+  bucket = "${local.name_prefix}-alb-access-logs-${random_string.teamcity_alb_access_logs_bucket_suffix[0].result}"
+
+  #checkov:skip=CKV_AWS_21: Versioning not necessary for access logs
+  #checkov:skip=CKV_AWS_144: Cross-region replication not necessary for access logs
+  #checkov:skip=CKV_AWS_145: KMS encryption with CMK not currently supported
+  #checkov:skip=CKV_AWS_18: S3 access logs not necessary
+  #checkov:skip=CKV2_AWS_62: Event notifications not necessary
+
+  tags = merge(local.tags, {
+    Name = "${local.name_prefix}-alb-access-logs-${random_string.teamcity_alb_access_logs_bucket_suffix[0].result}"
+  })
+}
+
+data "aws_elb_service_account" "main" {}
+
+data "aws_iam_policy_document" "access_logs_bucket_alb_write" {
+  count = var.enable_teamcity_alb_access_logs && var.teamcity_alb_access_logs_bucket == null ? 1 : 0
+  statement {
+    effect  = "Allow"
+    actions = ["s3:PutObject"]
+    principals {
+      type        = "AWS"
+      identifiers = [data.aws_elb_service_account.main.arn]
+    }
+    resources = ["${var.teamcity_alb_access_logs_bucket != null ? var.teamcity_alb_access_logs_bucket : aws_s3_bucket.teamcity_alb_access_logs_bucket[0].arn}/${var.teamcity_alb_access_logs_prefix != null ? var.teamcity_alb_access_logs_prefix : "${local.name_prefix}-alb"}/*"
+    ]
+  }
+}
+
+resource "aws_s3_bucket_policy" "alb_access_logs_bucket_policy" {
+  count  = var.enable_teamcity_alb_access_logs && var.teamcity_alb_access_logs_bucket == null ? 1 : 0
+  bucket = var.teamcity_alb_access_logs_bucket == null ? aws_s3_bucket.teamcity_alb_access_logs_bucket[0].id : var.teamcity_alb_access_logs_bucket
+  policy = data.aws_iam_policy_document.access_logs_bucket_alb_write[0].json
+}
+
+resource "aws_s3_bucket_lifecycle_configuration" "access_logs_bucket_lifecycle_configuration" {
+  count = var.enable_teamcity_alb_access_logs && var.teamcity_alb_access_logs_bucket == null ? 1 : 0
+  depends_on = [
+    aws_s3_bucket.teamcity_alb_access_logs_bucket[0]
+  ]
+  bucket = aws_s3_bucket.teamcity_alb_access_logs_bucket[0].id
+  rule {
+    id     = "access-logs-lifecycle"
+    status = "Enabled"
+    transition {
+      days          = 30
+      storage_class = "STANDARD_IA"
+    }
+    expiration {
+      days = 90
+    }
+    abort_incomplete_multipart_upload {
+      days_after_initiation = 7
+    }
+  }
+}
+
+resource "aws_s3_bucket_public_access_block" "access_logs_bucket_public_block" {
+  count = var.enable_teamcity_alb_access_logs && var.teamcity_alb_access_logs_bucket == null ? 1 : 0
+  depends_on = [
+    aws_s3_bucket.teamcity_alb_access_logs_bucket[0]
+  ]
+  bucket                  = aws_s3_bucket.teamcity_alb_access_logs_bucket[0].id
+  block_public_acls       = true
+  block_public_policy     = true
+  ignore_public_acls      = true
+  restrict_public_buckets = true
+}
diff --git a/modules/teamcity/outputs.tf b/modules/teamcity/outputs.tf
new file mode 100644
index 000000000..b537df4a7
--- /dev/null
+++ b/modules/teamcity/outputs.tf
@@ -0,0 +1,9 @@
+output "external_alb_dns_name" {
+  value       = var.create_external_alb ? aws_lb.teamcity_external_lb[0].dns_name : null
+  description = "DNS endpoint of Application Load Balancer (ALB)"
+}
+
+output "external_alb_zone_id" {
+  value       = var.create_external_alb ? aws_lb.teamcity_external_lb[0].zone_id : null
+  description = "Zone ID for internet facing load balancer"
+}
diff --git a/modules/teamcity/variables.tf b/modules/teamcity/variables.tf
new file mode 100644
index 000000000..4a683f48a
--- /dev/null
+++ b/modules/teamcity/variables.tf
@@ -0,0 +1,227 @@
+########################################
+# GENERAL CONFIGURATION
+########################################
+
+variable "name" {
+  type        = string
+  default     = "teamcity"
+  description = "The name applied to resources in the TeamCity module"
+}
+variable "tags" {
+  type = map(any)
+  default = {
+    "iac-management" = "CGD-Toolkit"
+    "iac-module"     = "TeamCity"
+    "iac-provider"   = "Terraform"
+  }
+  description = "Tags to apply to resources."
+}
+variable "environment" {
+  type        = string
+  description = "The current environment (e.g. dev, prod, etc.)"
+  default     = "dev"
+}
+
+variable "debug" {
+  type        = bool
+  description = "Set this flag to enable ECS execute permissions on the TeamCity server container and force new service deployments on Terraform apply."
+  default     = false
+}
+
+########################################
+# TeamCity SERVICE CONFIGURATION (ECS)
+########################################
+
+variable "cluster_name" {
+  type        = string
+  description = "The name of the ECS cluster to deploy TeamCity to."
+  default     = null
+
+}
+
+variable "container_cpu" {
+  type        = number
+  default     = 1024
+  description = "The number of CPU units to allocate to the TeamCity server container"
+}
+variable "container_memory" {
+  type        = number
+  default     = 4096
+  description = "The number of MB of memory to allocate to the TeamCity server container"
+}
+
+variable "container_name" {
+  type        = string
+  default     = "teamcity"
+  description = "The name of the TeamCity server container"
+}
+
+variable "container_port" {
+  type        = number
+  default     = 8111
+  description = "The port on which the TeamCity server container listens"
+}
+
+variable "desired_container_count" {
+  type        = number
+  description = "The desired number of containers running TeamCity server."
+  default     = 1
+  nullable    = false
+}
+
+########################################
+# Networking
+########################################
+
+variable "service_subnets" {
+  type        = list(string)
+  description = "The subnets in which the TeamCity server service will be deployed"
+}
+
+variable "vpc_id" {
+  type        = string
+  description = "The ID of the VPC in which the service will be deployed"
+}
+# Logging
+variable "teamcity_cloudwatch_log_retention_in_days" {
+  type        = string
+  description = "The log retention in days of the cloudwatch log group for TeamCity."
+  default     = 365
+}
+
+########################################
+# EFS Configuration
+########################################
+
+variable "efs_id" {
+  type        = string
+  description = "The ID of the EFS file system to use for the TeamCity service."
+  default     = null
+}
+
+variable "teamcity_efs_performance_mode" {
+  type        = string
+  description = "The performance mode of the EFS file system used by the TeamCity service. Defaults to general purpose."
+  default     = "generalPurpose"
+}
+
+variable "teamcity_efs_throughput_mode" {
+  type        = string
+  description = "The throughput mode of the EFS file system used by the TeamCity service. Defaults to bursting."
+  default     = "bursting"
+}
+
+variable "efs_access_point_id" {
+  type        = string
+  description = "The ID of the EFS access point to use for the TeamCity data volume."
+
+  validation {
+    condition     = (var.efs_id == null && var.efs_access_point_id == null) || (var.efs_id != null && var.efs_access_point_id != null)
+    error_message = "The efs_access_point_id variable must be set if efs_id is set."
+  }
+  default = null
+
+}
+
+variable "efs_encryption_enabled" {
+  type        = bool
+  description = "Set this flag to true to enable EFS encryption."
+  default     = true
+}
+
+########################################
+# Load Balancing
+########################################
+variable "create_external_alb" {
+  type        = bool
+  description = "Set this flag to true to create an external load balancer for TeamCity."
+  default     = true
+}
+
+variable "alb_subnets" {
+  type        = list(string)
+  description = "The subnets in which the ALB will be deployed"
+
+  validation {
+    condition     = var.create_external_alb == true && length(var.alb_subnets) > 0
+    error_message = "The alb_subnets variable must be set if create_external_alb is true."
+  }
+  default = []
+}
+
+variable "alb_certificate_arn" {
+  type        = string
+  description = "The ARN of the SSL certificate to use for the ALB"
+
+  validation {
+    condition     = var.create_external_alb == true && var.alb_certificate_arn != null
+    error_message = "The alb_certificate_arn variable must be set if create_external_alb is true."
+  }
+  default = null
+}
+
+variable "enable_teamcity_alb_access_logs" {
+  type        = bool
+  description = "Enables access logging for the TeamCity ALB. Defaults to true."
+  default     = true
+}
+
+variable "teamcity_alb_access_logs_bucket" {
+  type        = string
+  description = "ID of the S3 bucket for TeamCity ALB access log storage. If access logging is enabled and this is null the module creates a bucket."
+  default     = null
+}
+
+variable "teamcity_alb_access_logs_prefix" {
+  type        = string
+  description = "Log prefix for TeamCity ALB access logs. If null the project prefix and module name are used."
+  default     = null
+}
+
+variable "enable_teamcity_alb_deletion_protection" {
+  type        = bool
+  description = "Enables deletion protection for the TeamCity ALB. Defaults to true."
+  default     = false
+}
+
+########################################
+# Aurora Cluster
+########################################
+
+variable "database_connection_string" {
+  type        = string
+  description = "The database connection string for TeamCity"
+  default     = null
+}
+
+variable "database_master_username" {
+  type        = string
+  description = "The master username for the database"
+
+  validation {
+    condition     = (var.database_connection_string == null && var.database_master_username == null) || (var.database_connection_string != null && var.database_master_username != null)
+    error_message = "The database_master_username variable must be set."
+  }
+  default = null
+}
+variable "database_master_password" {
+  type        = string
+  description = "The master password for the database"
+  validation {
+    condition     = (var.database_connection_string == null && var.database_master_password == null) || (var.database_connection_string != null && var.database_master_password != null)
+    error_message = "The database_master_password variable must be set."
+  }
+  default = null
+}
+
+variable "aurora_skip_final_snapshot" {
+  type        = bool
+  description = "Flag for whether a final snapshot should be created when the cluster is destroyed."
+  default     = true
+}
+
+variable "aurora_instance_count" {
+  type        = number
+  description = "Number of instances to provision for the Aurora cluster"
+  default     = 2
+}
diff --git a/modules/teamcity/versions.tf b/modules/teamcity/versions.tf
new file mode 100644
index 000000000..35dd8d17c
--- /dev/null
+++ b/modules/teamcity/versions.tf
@@ -0,0 +1,14 @@
+terraform {
+  required_version = ">= 1.9"
+
+  required_providers {
+    aws = {
+      source  = "hashicorp/aws"
+      version = "5.89.0"
+    }
+    random = {
+      source  = "hashicorp/random"
+      version = "3.5.1"
+    }
+  }
+}


Reviews:
user: login: github-advanced-security[bot]
state: COMMENTED
author_association: NONE
submitted_at: 2025-01-17T16:52:31Z
user: login: github-advanced-security[bot]
state: COMMENTED
author_association: NONE
submitted_at: 2025-01-25T00:21:54Z
user: login: github-advanced-security[bot]
state: COMMENTED
author_association: NONE
submitted_at: 2025-02-01T02:09:13Z
body: Great progress. Couple of minor things.
state: CHANGES_REQUESTED
author_association: NONE
submitted_at: 2025-02-06T18:50:25Z
state: COMMENTED
author_association: NONE
submitted_at: 2025-02-06T20:02:25Z
state: COMMENTED
author_association: NONE
submitted_at: 2025-02-06T20:05:23Z
state: COMMENTED
author_association: NONE
submitted_at: 2025-02-06T20:28:17Z
user: login: cwwalb
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-02-06T20:45:59Z
user: login: cwwalb
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-02-06T20:53:09Z
user: login: github-advanced-security[bot]
state: COMMENTED
author_association: NONE
submitted_at: 2025-02-06T21:14:49Z
state: COMMENTED
author_association: NONE
submitted_at: 2025-02-06T23:36:44Z
state: COMMENTED
author_association: NONE
submitted_at: 2025-02-06T23:37:56Z
body: Still a couple minor adjustments to make. We're getting closer though!
state: CHANGES_REQUESTED
author_association: NONE
submitted_at: 2025-02-06T23:45:11Z
state: COMMENTED
author_association: NONE
submitted_at: 2025-02-12T15:28:31Z
user: login: cwwalb
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-02-12T17:23:27Z
state: COMMENTED
author_association: NONE
submitted_at: 2025-02-12T17:47:16Z
state: COMMENTED
author_association: NONE
submitted_at: 2025-02-12T17:47:40Z
state: COMMENTED
author_association: NONE
submitted_at: 2025-02-12T17:47:49Z
user: login: cwwalb
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-02-12T18:04:53Z
user: login: cwwalb
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-02-12T19:20:06Z
user: login: cwwalb
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-02-12T20:14:42Z
user: login: cwwalb
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-02-12T20:16:28Z
user: login: github-advanced-security[bot]
state: COMMENTED
author_association: NONE
submitted_at: 2025-02-14T19:33:07Z
user: login: github-advanced-security[bot]
state: COMMENTED
author_association: NONE
submitted_at: 2025-03-06T22:53:05Z
user: login: github-advanced-security[bot]
state: COMMENTED
author_association: NONE
submitted_at: 2025-03-07T19:44:05Z
user: login: github-advanced-security[bot]
state: COMMENTED
author_association: NONE
submitted_at: 2025-03-11T16:22:26Z
body: Couple of small things. Looks good! Go ahead and mark this as "ready" so that it isn't a draft anymore.
state: CHANGES_REQUESTED
author_association: NONE
submitted_at: 2025-03-11T17:23:28Z
user: login: cwwalb
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-03-11T17:58:59Z
user: login: cwwalb
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-03-11T19:57:43Z
user: login: cwwalb
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-03-11T19:57:53Z
user: login: cwwalb
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-03-11T22:04:11Z
user: login: cwwalb
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-03-11T22:39:58Z
user: login: cwwalb
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-03-12T20:07:43Z
user: login: novekm
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-03-12T20:45:52Z
user: login: novekm
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-03-12T20:47:56Z
user: login: novekm
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-03-12T20:51:57Z
user: login: novekm
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-03-12T20:54:06Z
user: login: novekm
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-03-12T20:56:23Z
user: login: novekm
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-03-12T20:59:30Z
state: DISMISSED
author_association: NONE
submitted_at: 2025-03-13T05:46:35Z
user: login: novekm
body: LGTM besides some minor comments - have you validated successful run of `terraform apply` and `terraform destroy` ?
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-03-13T14:22:52Z
user: login: cwwalb
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-03-13T15:55:06Z
user: login: novekm
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-03-13T17:18:41Z
user: login: novekm
body: LGTM
state: DISMISSED
author_association: CONTRIBUTOR
submitted_at: 2025-03-13T17:19:10Z
user: login: cwwalb
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-03-13T19:39:37Z
body: LGTM 🚀 
state: APPROVED
author_association: NONE
submitted_at: 2025-03-13T19:48:51Z
user: login: novekm
body: LGTM 🚀
state: APPROVED
author_association: CONTRIBUTOR
submitted_at: 2025-03-13T20:18:56Z

Review comments:
diff_hunk: @@ -0,0 +1,123 @@
+resource "aws_vpc" "teamcity_vpc" {
+  cidr_block = local.vpc_cidr_block
+  tags = merge(local.tags,
+    {
+      Name = "teamcity-vpc"
+    }
+  )
+  enable_dns_hostnames = true
+  #checkov:skip=CKV2_AWS_11: VPC flow logging disabled by design
+}
path: modules/teamcity/examples/simple/vpc.tf
user: login: github-advanced-security[bot]
body: ## Ensure VPC flow logging is enabled in all VPCs

Ensure VPC flow logging is enabled in all VPCs

[Show more details](https://github.com/aws-games/cloud-game-development-toolkit/security/code-scanning/773)
created_at: 2025-01-17T16:52:31Z
author_association: NONE
diff_hunk: @@ -0,0 +1,123 @@
+resource "aws_vpc" "teamcity_vpc" {
+  cidr_block = local.vpc_cidr_block
+  tags = merge(local.tags,
+    {
+      Name = "teamcity-vpc"
+    }
+  )
+  enable_dns_hostnames = true
+  #checkov:skip=CKV2_AWS_11: VPC flow logging disabled by design
+}
+
+# Set default SG to restrict all traffic
+resource "aws_default_security_group" "default" {
+  vpc_id = aws_vpc.teamcity_vpc.id
+}
+
+resource "aws_subnet" "public_subnets" {
+  count             = length(local.public_subnet_cidrs)
+  vpc_id            = aws_vpc.teamcity_vpc.id
+  cidr_block        = element(local.public_subnet_cidrs, count.index)
+  availability_zone = element(local.azs, count.index)
+
+  tags = merge(local.tags,
+    {
+      Name = "pub-subnet-${count.index + 1}"
+    }
+  )
+}
+
+resource "aws_subnet" "private_subnets" {
+  count             = length(local.private_subnet_cidrs)
+  vpc_id            = aws_vpc.teamcity_vpc.id
+  cidr_block        = element(local.private_subnet_cidrs, count.index)
+  availability_zone = element(local.azs, count.index)
+
+  tags = merge(local.tags,
+    {
+      Name = "pvt-subnet-${count.index + 1}"
+    }
+  )
+}
+
+##########################################
+# Internet Gateway
+##########################################
+
+resource "aws_internet_gateway" "igw" {
+  vpc_id = aws_vpc.teamcity_vpc.id
+  tags = merge(local.tags,
+    {
+      Name = "teamcity-igw"
+    }
+  )
+}
+
+##########################################
+# Route Tables & NAT Gateway
+##########################################
+
+resource "aws_route_table" "public_rt" {
+  vpc_id = aws_vpc.teamcity_vpc.id
+
+  # public route to the internet
+  route {
+    cidr_block = "0.0.0.0/0"
+    gateway_id = aws_internet_gateway.igw.id
+  }
+
+  tags = merge(local.tags,
+    {
+      Name = "teamcity-public-rt"
+    }
+  )
+}
+
+resource "aws_route_table_association" "public_rt_asso" {
+  count          = length(aws_subnet.public_subnets)
+  route_table_id = aws_route_table.public_rt.id
+  subnet_id      = aws_subnet.public_subnets[count.index].id
+}
+
+resource "aws_eip" "nat_gateway_eip" {
+  depends_on = [aws_internet_gateway.igw]
+  #checkov:skip=CKV2_AWS_19:EIP associated with NAT Gateway through association ID
+  tags = merge(local.tags,
+    {
+      Name = "teamcity-nat-eip"
+    }
+  )
+}
path: modules/teamcity/examples/simple/vpc.tf
user: login: github-advanced-security[bot]
body: ## Ensure that all EIP addresses allocated to a VPC are attached to EC2 instances

Ensure that all EIP addresses allocated to a VPC are attached to EC2 instances

[Show more details](https://github.com/aws-games/cloud-game-development-toolkit/security/code-scanning/774)
created_at: 2025-01-17T16:52:31Z
author_association: NONE
diff_hunk: @@ -0,0 +1,144 @@
+resource "aws_ecs_cluster" "teamcity_cluster" {
+  name  = "${local.name_prefix}-cluster"
+
+  setting {
+    name  = "containerInsights"
+    value = "enabled"
+  }
+
+  tags = local.tags
+}
+resource "aws_ecs_task_definition" "teamcity_task_definition" {
+  family                   = var.name
+  requires_compatibilities = ["FARGATE"]
+  network_mode             = "awsvpc"
+  cpu                      = var.container_cpu
+  memory                   = var.container_memory
+
+
+  container_definitions = jsonencode([
+    {
+      name  = var.container_name
+      image = local.image
+      cpu       = var.container_cpu
+      memory    = var.container_memory
+      essential = true
+      portMappings = [
+        {
+          containerPort = var.container_port
+          hostPort      = var.container_port
+        }
+      ]
+      logConfiguration = {
+        logDriver = "awslogs"
+        options = {
+          awslogs-group         = aws_cloudwatch_log_group.teamcity_log_group.name
+          awslogs-region        = data.aws_region.current.name
+          awslogs-stream-prefix = "[APP]"
+        }
+      },
+    }
+  ])
+
+  tags = {
+    Name = var.name
+  }
+   task_role_arn      = aws_iam_role.teamcity_default_role.arn
+   execution_role_arn = aws_iam_role.teamcity_task_execution_role.arn
+}
+
+resource "aws_ecs_service" "teamcity" {
+  name = local.name_prefix
+
+  cluster                = aws_ecs_cluster.teamcity_cluster.name
+  task_definition        = aws_ecs_task_definition.teamcity_task_definition.arn
+  launch_type            = "FARGATE"
+  desired_count          = 1 #TODO: make this configurable
+  force_new_deployment   = true #TODO: make this configurable
+  enable_execute_command = true #TODO: make this configurable
+
+  wait_for_steady_state = true
+
+  network_configuration {
+    subnets         = var.service_subnets
+    security_groups = [aws_security_group.teamcity_service_sg.id]
+  }
+
+  tags = local.tags
+}
+
+# TeamCity service security group
+resource "aws_security_group" "teamcity_service_sg" {
+  name   = "${local.name_prefix}-sg"
+  vpc_id = var.vpc_id
+
+  tags = local.tags
+}
path: modules/teamcity/main.tf
user: login: github-advanced-security[bot]
body: ## Ensure every security group and rule has a description

Ensure every security group and rule has a description

[Show more details](https://github.com/aws-games/cloud-game-development-toolkit/security/code-scanning/770)
created_at: 2025-01-17T16:52:31Z
author_association: NONE
diff_hunk: @@ -0,0 +1,144 @@
+resource "aws_ecs_cluster" "teamcity_cluster" {
+  name  = "${local.name_prefix}-cluster"
+
+  setting {
+    name  = "containerInsights"
+    value = "enabled"
+  }
+
+  tags = local.tags
+}
+resource "aws_ecs_task_definition" "teamcity_task_definition" {
+  family                   = var.name
+  requires_compatibilities = ["FARGATE"]
+  network_mode             = "awsvpc"
+  cpu                      = var.container_cpu
+  memory                   = var.container_memory
+
+
+  container_definitions = jsonencode([
+    {
+      name  = var.container_name
+      image = local.image
+      cpu       = var.container_cpu
+      memory    = var.container_memory
+      essential = true
+      portMappings = [
+        {
+          containerPort = var.container_port
+          hostPort      = var.container_port
+        }
+      ]
+      logConfiguration = {
+        logDriver = "awslogs"
+        options = {
+          awslogs-group         = aws_cloudwatch_log_group.teamcity_log_group.name
+          awslogs-region        = data.aws_region.current.name
+          awslogs-stream-prefix = "[APP]"
+        }
+      },
+    }
+  ])
+
+  tags = {
+    Name = var.name
+  }
+   task_role_arn      = aws_iam_role.teamcity_default_role.arn
+   execution_role_arn = aws_iam_role.teamcity_task_execution_role.arn
+}
+
+resource "aws_ecs_service" "teamcity" {
+  name = local.name_prefix
+
+  cluster                = aws_ecs_cluster.teamcity_cluster.name
+  task_definition        = aws_ecs_task_definition.teamcity_task_definition.arn
+  launch_type            = "FARGATE"
+  desired_count          = 1 #TODO: make this configurable
+  force_new_deployment   = true #TODO: make this configurable
+  enable_execute_command = true #TODO: make this configurable
+
+  wait_for_steady_state = true
+
+  network_configuration {
+    subnets         = var.service_subnets
+    security_groups = [aws_security_group.teamcity_service_sg.id]
+  }
+
+  tags = local.tags
+}
+
+# TeamCity service security group
+resource "aws_security_group" "teamcity_service_sg" {
+  name   = "${local.name_prefix}-sg"
+  vpc_id = var.vpc_id
+
+  tags = local.tags
+}
+
+# Grant TeamCity service access to internet
+resource "aws_vpc_security_group_egress_rule" "internet_outbound" {
+
+  security_group_id = aws_security_group.teamcity_service_sg.id
+  cidr_ipv4         = "0.0.0.0/0"
+  ip_protocol       = "-1"
+}
path: modules/teamcity/main.tf
user: login: github-advanced-security[bot]
body: ## Ensure every security group and rule has a description

Ensure every security group and rule has a description

[Show more details](https://github.com/aws-games/cloud-game-development-toolkit/security/code-scanning/771)
created_at: 2025-01-17T16:52:31Z
author_association: NONE
diff_hunk: @@ -0,0 +1,144 @@
+resource "aws_ecs_cluster" "teamcity_cluster" {
+  name  = "${local.name_prefix}-cluster"
+
+  setting {
+    name  = "containerInsights"
+    value = "enabled"
+  }
+
+  tags = local.tags
+}
+resource "aws_ecs_task_definition" "teamcity_task_definition" {
+  family                   = var.name
+  requires_compatibilities = ["FARGATE"]
+  network_mode             = "awsvpc"
+  cpu                      = var.container_cpu
+  memory                   = var.container_memory
+
+
+  container_definitions = jsonencode([
+    {
+      name  = var.container_name
+      image = local.image
+      cpu       = var.container_cpu
+      memory    = var.container_memory
+      essential = true
+      portMappings = [
+        {
+          containerPort = var.container_port
+          hostPort      = var.container_port
+        }
+      ]
+      logConfiguration = {
+        logDriver = "awslogs"
+        options = {
+          awslogs-group         = aws_cloudwatch_log_group.teamcity_log_group.name
+          awslogs-region        = data.aws_region.current.name
+          awslogs-stream-prefix = "[APP]"
+        }
+      },
+    }
+  ])
+
+  tags = {
+    Name = var.name
+  }
+   task_role_arn      = aws_iam_role.teamcity_default_role.arn
+   execution_role_arn = aws_iam_role.teamcity_task_execution_role.arn
+}
+
+resource "aws_ecs_service" "teamcity" {
+  name = local.name_prefix
+
+  cluster                = aws_ecs_cluster.teamcity_cluster.name
+  task_definition        = aws_ecs_task_definition.teamcity_task_definition.arn
+  launch_type            = "FARGATE"
+  desired_count          = 1 #TODO: make this configurable
+  force_new_deployment   = true #TODO: make this configurable
+  enable_execute_command = true #TODO: make this configurable
+
+  wait_for_steady_state = true
+
+  network_configuration {
+    subnets         = var.service_subnets
+    security_groups = [aws_security_group.teamcity_service_sg.id]
+  }
+
+  tags = local.tags
+}
+
+# TeamCity service security group
+resource "aws_security_group" "teamcity_service_sg" {
+  name   = "${local.name_prefix}-sg"
+  vpc_id = var.vpc_id
+
+  tags = local.tags
+}
+
+# Grant TeamCity service access to internet
+resource "aws_vpc_security_group_egress_rule" "internet_outbound" {
+
+  security_group_id = aws_security_group.teamcity_service_sg.id
+  cidr_ipv4         = "0.0.0.0/0"
+  ip_protocol       = "-1"
+}
+
+#############################################
+# IAM Roles for Unreal Engine Horde Module
+#############################################
+data "aws_iam_policy_document" "ecs_tasks_trust_relationship" {
+  statement {
+    effect  = "Allow"
+    actions = ["sts:AssumeRole"]
+    principals {
+      type        = "Service"
+      identifiers = ["ecs-tasks.amazonaws.com"]
+    }
+  }
+}
+data "aws_iam_policy_document" "teamcity_default_policy" {
+  # ECS
+  statement {
+    sid    = "ECSExec"
+    effect = "Allow"
+    actions = [
+      "ssmmessages:OpenDataChannel",
+      "ssmmessages:OpenControlChannel",
+      "ssmmessages:CreateDataChannel",
+      "ssmmessages:CreateControlChannel",
+    ]
+    resources = [
+      "*"
+    ]
+  }
+}
+resource "aws_iam_policy" "teamcity_default_policy" {
+  name        = "teamcity-default-policy"
+  description = "Policy granting permissions for Unreal Horde."
+  policy      = data.aws_iam_policy_document.teamcity_default_policy.json
+}
+
+resource "aws_iam_role" "teamcity_default_role" {
+  name               = "teamcity-default-role"
+  assume_role_policy = data.aws_iam_policy_document.ecs_tasks_trust_relationship.json
+
+  managed_policy_arns = [
+    aws_iam_policy.teamcity_default_policy.arn
+  ]
+
+  tags = local.tags
+}
+resource "aws_iam_role" "teamcity_task_execution_role" {
+  name = "teamcity-task-execution-role"
+
+  assume_role_policy  = data.aws_iam_policy_document.ecs_tasks_trust_relationship.json
+  managed_policy_arns = ["arn:aws:iam::aws:policy/service-role/AmazonECSTaskExecutionRolePolicy"]
+}
+
+# CloudWatch Logs
+resource "aws_cloudwatch_log_group" "teamcity_log_group" {
+  #checkov:skip=CKV_AWS_158: KMS Encryption disabled by default
+  name              = "${local.name_prefix}-log-group"
+  retention_in_days = var.teamcity_cloudwatch_log_retention_in_days
+  tags              = local.tags
+}
path: modules/teamcity/main.tf
user: login: github-advanced-security[bot]
body: ## Ensure that CloudWatch Log Group is encrypted by KMS

Ensure that CloudWatch Log Group is encrypted by KMS

[Show more details](https://github.com/aws-games/cloud-game-development-toolkit/security/code-scanning/772)
created_at: 2025-01-17T16:52:31Z
author_association: NONE
diff_hunk: @@ -0,0 +1,293 @@
+###################################
+# ECS Cluster for TeamCity Server #
+###################################
+
+resource "aws_ecs_cluster" "teamcity_cluster" {
+  name = "${local.name_prefix}-cluster"
+
+  setting {
+    name  = "containerInsights"
+    value = "enabled"
+  }
+
+  tags = local.tags
+}
+
+
+# TeamCity Task Definition
+resource "aws_ecs_task_definition" "teamcity_task_definition" {
+  family                   = var.name
+  requires_compatibilities = ["FARGATE"]
+  network_mode             = "awsvpc"
+  cpu                      = var.container_cpu
+  memory                   = var.container_memory
+
+
+  container_definitions = jsonencode([
+    {
+      name      = var.container_name
+      image     = local.image
+      cpu       = var.container_cpu
+      memory    = var.container_memory
+      essential = true
+      portMappings = [
+        {
+          containerPort = var.container_port
+          hostPort      = var.container_port
+        }
+      ]
+      logConfiguration = {
+        logDriver = "awslogs"
+        options = {
+          awslogs-group         = aws_cloudwatch_log_group.teamcity_log_group.name
+          awslogs-region        = data.aws_region.current.name
+          awslogs-stream-prefix = "[APP]"
+        }
+      },
+    }
+  ])
+
+  tags = {
+    Name = var.name
+  }
+  task_role_arn      = aws_iam_role.teamcity_default_role.arn
+  execution_role_arn = aws_iam_role.teamcity_task_execution_role.arn
+}
+
+
+
+
+# ECS Service
+resource "aws_ecs_service" "teamcity" {
+  name = local.name_prefix
+
+  cluster                = aws_ecs_cluster.teamcity_cluster.name
+  task_definition        = aws_ecs_task_definition.teamcity_task_definition.arn
+  launch_type            = "FARGATE"
+  desired_count          = 1    #TODO: make this configurable
+  force_new_deployment   = true #TODO: make this configurable
+  enable_execute_command = true #TODO: make this configurable
+
+  wait_for_steady_state = true
+
+  network_configuration {
+    subnets         = var.service_subnets
+    security_groups = [aws_security_group.teamcity_service_sg.id]
+  }
+  load_balancer {
+    target_group_arn = aws_lb_target_group.teamcity_target_group.arn
+    container_name   = var.container_name
+    container_port   = var.container_port
+  }
+
+  tags = local.tags
+}
+
+# TeamCity service security group
+resource "aws_security_group" "teamcity_service_sg" {
+  name   = "${local.name_prefix}-service-sg"
+  vpc_id = var.vpc_id
+
+  tags = local.tags
+}
path: modules/teamcity/main.tf
user: login: github-advanced-security[bot]
body: ## Ensure every security group and rule has a description

Ensure every security group and rule has a description

[Show more details](https://github.com/aws-games/cloud-game-development-toolkit/security/code-scanning/790)
created_at: 2025-01-25T00:21:53Z
author_association: NONE
diff_hunk: @@ -0,0 +1,293 @@
+###################################
+# ECS Cluster for TeamCity Server #
+###################################
+
+resource "aws_ecs_cluster" "teamcity_cluster" {
+  name = "${local.name_prefix}-cluster"
+
+  setting {
+    name  = "containerInsights"
+    value = "enabled"
+  }
+
+  tags = local.tags
+}
+
+
+# TeamCity Task Definition
+resource "aws_ecs_task_definition" "teamcity_task_definition" {
+  family                   = var.name
+  requires_compatibilities = ["FARGATE"]
+  network_mode             = "awsvpc"
+  cpu                      = var.container_cpu
+  memory                   = var.container_memory
+
+
+  container_definitions = jsonencode([
+    {
+      name      = var.container_name
+      image     = local.image
+      cpu       = var.container_cpu
+      memory    = var.container_memory
+      essential = true
+      portMappings = [
+        {
+          containerPort = var.container_port
+          hostPort      = var.container_port
+        }
+      ]
+      logConfiguration = {
+        logDriver = "awslogs"
+        options = {
+          awslogs-group         = aws_cloudwatch_log_group.teamcity_log_group.name
+          awslogs-region        = data.aws_region.current.name
+          awslogs-stream-prefix = "[APP]"
+        }
+      },
+    }
+  ])
+
+  tags = {
+    Name = var.name
+  }
+  task_role_arn      = aws_iam_role.teamcity_default_role.arn
+  execution_role_arn = aws_iam_role.teamcity_task_execution_role.arn
+}
+
+
+
+
+# ECS Service
+resource "aws_ecs_service" "teamcity" {
+  name = local.name_prefix
+
+  cluster                = aws_ecs_cluster.teamcity_cluster.name
+  task_definition        = aws_ecs_task_definition.teamcity_task_definition.arn
+  launch_type            = "FARGATE"
+  desired_count          = 1    #TODO: make this configurable
+  force_new_deployment   = true #TODO: make this configurable
+  enable_execute_command = true #TODO: make this configurable
+
+  wait_for_steady_state = true
+
+  network_configuration {
+    subnets         = var.service_subnets
+    security_groups = [aws_security_group.teamcity_service_sg.id]
+  }
+  load_balancer {
+    target_group_arn = aws_lb_target_group.teamcity_target_group.arn
+    container_name   = var.container_name
+    container_port   = var.container_port
+  }
+
+  tags = local.tags
+}
+
+# TeamCity service security group
+resource "aws_security_group" "teamcity_service_sg" {
+  name   = "${local.name_prefix}-service-sg"
+  vpc_id = var.vpc_id
+
+  tags = local.tags
+}
+
+# TeamCity ALB security group
+resource "aws_security_group" "teamcity_alb_sg" {
+  name   = "${local.name_prefix}-alb-sg"
+  vpc_id = var.vpc_id
+
+  tags = local.tags
+}
path: modules/teamcity/main.tf
user: login: github-advanced-security[bot]
body: ## Ensure every security group and rule has a description

Ensure every security group and rule has a description

[Show more details](https://github.com/aws-games/cloud-game-development-toolkit/security/code-scanning/791)
created_at: 2025-01-25T00:21:53Z
author_association: NONE
diff_hunk: @@ -0,0 +1,293 @@
+###################################
+# ECS Cluster for TeamCity Server #
+###################################
+
+resource "aws_ecs_cluster" "teamcity_cluster" {
+  name = "${local.name_prefix}-cluster"
+
+  setting {
+    name  = "containerInsights"
+    value = "enabled"
+  }
+
+  tags = local.tags
+}
+
+
+# TeamCity Task Definition
+resource "aws_ecs_task_definition" "teamcity_task_definition" {
+  family                   = var.name
+  requires_compatibilities = ["FARGATE"]
+  network_mode             = "awsvpc"
+  cpu                      = var.container_cpu
+  memory                   = var.container_memory
+
+
+  container_definitions = jsonencode([
+    {
+      name      = var.container_name
+      image     = local.image
+      cpu       = var.container_cpu
+      memory    = var.container_memory
+      essential = true
+      portMappings = [
+        {
+          containerPort = var.container_port
+          hostPort      = var.container_port
+        }
+      ]
+      logConfiguration = {
+        logDriver = "awslogs"
+        options = {
+          awslogs-group         = aws_cloudwatch_log_group.teamcity_log_group.name
+          awslogs-region        = data.aws_region.current.name
+          awslogs-stream-prefix = "[APP]"
+        }
+      },
+    }
+  ])
+
+  tags = {
+    Name = var.name
+  }
+  task_role_arn      = aws_iam_role.teamcity_default_role.arn
+  execution_role_arn = aws_iam_role.teamcity_task_execution_role.arn
+}
+
+
+
+
+# ECS Service
+resource "aws_ecs_service" "teamcity" {
+  name = local.name_prefix
+
+  cluster                = aws_ecs_cluster.teamcity_cluster.name
+  task_definition        = aws_ecs_task_definition.teamcity_task_definition.arn
+  launch_type            = "FARGATE"
+  desired_count          = 1    #TODO: make this configurable
+  force_new_deployment   = true #TODO: make this configurable
+  enable_execute_command = true #TODO: make this configurable
+
+  wait_for_steady_state = true
+
+  network_configuration {
+    subnets         = var.service_subnets
+    security_groups = [aws_security_group.teamcity_service_sg.id]
+  }
+  load_balancer {
+    target_group_arn = aws_lb_target_group.teamcity_target_group.arn
+    container_name   = var.container_name
+    container_port   = var.container_port
+  }
+
+  tags = local.tags
+}
+
+# TeamCity service security group
+resource "aws_security_group" "teamcity_service_sg" {
+  name   = "${local.name_prefix}-service-sg"
+  vpc_id = var.vpc_id
+
+  tags = local.tags
+}
+
+# TeamCity ALB security group
+resource "aws_security_group" "teamcity_alb_sg" {
+  name   = "${local.name_prefix}-alb-sg"
+  vpc_id = var.vpc_id
+
+  tags = local.tags
+}
+
+# Ingress rule for HTTP traffic from ALB to service
+resource "aws_vpc_security_group_ingress_rule" "service_inbound_alb" {
+  security_group_id            = aws_security_group.teamcity_service_sg.id
+  referenced_security_group_id = aws_security_group.teamcity_alb_sg.id
+  from_port                    = var.container_port
+  to_port                      = var.container_port
+  ip_protocol                  = "TCP"
+
+}
path: modules/teamcity/main.tf
user: login: github-advanced-security[bot]
body: ## Ensure every security group and rule has a description

Ensure every security group and rule has a description

[Show more details](https://github.com/aws-games/cloud-game-development-toolkit/security/code-scanning/792)
created_at: 2025-01-25T00:21:53Z
author_association: NONE
diff_hunk: @@ -0,0 +1,293 @@
+###################################
+# ECS Cluster for TeamCity Server #
+###################################
+
+resource "aws_ecs_cluster" "teamcity_cluster" {
+  name = "${local.name_prefix}-cluster"
+
+  setting {
+    name  = "containerInsights"
+    value = "enabled"
+  }
+
+  tags = local.tags
+}
+
+
+# TeamCity Task Definition
+resource "aws_ecs_task_definition" "teamcity_task_definition" {
+  family                   = var.name
+  requires_compatibilities = ["FARGATE"]
+  network_mode             = "awsvpc"
+  cpu                      = var.container_cpu
+  memory                   = var.container_memory
+
+
+  container_definitions = jsonencode([
+    {
+      name      = var.container_name
+      image     = local.image
+      cpu       = var.container_cpu
+      memory    = var.container_memory
+      essential = true
+      portMappings = [
+        {
+          containerPort = var.container_port
+          hostPort      = var.container_port
+        }
+      ]
+      logConfiguration = {
+        logDriver = "awslogs"
+        options = {
+          awslogs-group         = aws_cloudwatch_log_group.teamcity_log_group.name
+          awslogs-region        = data.aws_region.current.name
+          awslogs-stream-prefix = "[APP]"
+        }
+      },
+    }
+  ])
+
+  tags = {
+    Name = var.name
+  }
+  task_role_arn      = aws_iam_role.teamcity_default_role.arn
+  execution_role_arn = aws_iam_role.teamcity_task_execution_role.arn
+}
+
+
+
+
+# ECS Service
+resource "aws_ecs_service" "teamcity" {
+  name = local.name_prefix
+
+  cluster                = aws_ecs_cluster.teamcity_cluster.name
+  task_definition        = aws_ecs_task_definition.teamcity_task_definition.arn
+  launch_type            = "FARGATE"
+  desired_count          = 1    #TODO: make this configurable
+  force_new_deployment   = true #TODO: make this configurable
+  enable_execute_command = true #TODO: make this configurable
+
+  wait_for_steady_state = true
+
+  network_configuration {
+    subnets         = var.service_subnets
+    security_groups = [aws_security_group.teamcity_service_sg.id]
+  }
+  load_balancer {
+    target_group_arn = aws_lb_target_group.teamcity_target_group.arn
+    container_name   = var.container_name
+    container_port   = var.container_port
+  }
+
+  tags = local.tags
+}
+
+# TeamCity service security group
+resource "aws_security_group" "teamcity_service_sg" {
+  name   = "${local.name_prefix}-service-sg"
+  vpc_id = var.vpc_id
+
+  tags = local.tags
+}
+
+# TeamCity ALB security group
+resource "aws_security_group" "teamcity_alb_sg" {
+  name   = "${local.name_prefix}-alb-sg"
+  vpc_id = var.vpc_id
+
+  tags = local.tags
+}
+
+# Ingress rule for HTTP traffic from ALB to service
+resource "aws_vpc_security_group_ingress_rule" "service_inbound_alb" {
+  security_group_id            = aws_security_group.teamcity_service_sg.id
+  referenced_security_group_id = aws_security_group.teamcity_alb_sg.id
+  from_port                    = var.container_port
+  to_port                      = var.container_port
+  ip_protocol                  = "TCP"
+
+}
+
+# Egress rule for HTTP traffic from ALB to service
+resource "aws_vpc_security_group_egress_rule" "service_outbound_alb" {
+  security_group_id            = aws_security_group.teamcity_alb_sg.id
+  referenced_security_group_id = aws_security_group.teamcity_service_sg.id
+  from_port                    = var.container_port
+  to_port                      = var.container_port
+  ip_protocol                  = "TCP"
+
+}
path: modules/teamcity/main.tf
user: login: github-advanced-security[bot]
body: ## Ensure every security group and rule has a description

Ensure every security group and rule has a description

[Show more details](https://github.com/aws-games/cloud-game-development-toolkit/security/code-scanning/793)
created_at: 2025-01-25T00:21:54Z
author_association: NONE
diff_hunk: @@ -0,0 +1,293 @@
+###################################
+# ECS Cluster for TeamCity Server #
+###################################
+
+resource "aws_ecs_cluster" "teamcity_cluster" {
+  name = "${local.name_prefix}-cluster"
+
+  setting {
+    name  = "containerInsights"
+    value = "enabled"
+  }
+
+  tags = local.tags
+}
+
+
+# TeamCity Task Definition
+resource "aws_ecs_task_definition" "teamcity_task_definition" {
+  family                   = var.name
+  requires_compatibilities = ["FARGATE"]
+  network_mode             = "awsvpc"
+  cpu                      = var.container_cpu
+  memory                   = var.container_memory
+
+
+  container_definitions = jsonencode([
+    {
+      name      = var.container_name
+      image     = local.image
+      cpu       = var.container_cpu
+      memory    = var.container_memory
+      essential = true
+      portMappings = [
+        {
+          containerPort = var.container_port
+          hostPort      = var.container_port
+        }
+      ]
+      logConfiguration = {
+        logDriver = "awslogs"
+        options = {
+          awslogs-group         = aws_cloudwatch_log_group.teamcity_log_group.name
+          awslogs-region        = data.aws_region.current.name
+          awslogs-stream-prefix = "[APP]"
+        }
+      },
+    }
+  ])
+
+  tags = {
+    Name = var.name
+  }
+  task_role_arn      = aws_iam_role.teamcity_default_role.arn
+  execution_role_arn = aws_iam_role.teamcity_task_execution_role.arn
+}
+
+
+
+
+# ECS Service
+resource "aws_ecs_service" "teamcity" {
+  name = local.name_prefix
+
+  cluster                = aws_ecs_cluster.teamcity_cluster.name
+  task_definition        = aws_ecs_task_definition.teamcity_task_definition.arn
+  launch_type            = "FARGATE"
+  desired_count          = 1    #TODO: make this configurable
+  force_new_deployment   = true #TODO: make this configurable
+  enable_execute_command = true #TODO: make this configurable
+
+  wait_for_steady_state = true
+
+  network_configuration {
+    subnets         = var.service_subnets
+    security_groups = [aws_security_group.teamcity_service_sg.id]
+  }
+  load_balancer {
+    target_group_arn = aws_lb_target_group.teamcity_target_group.arn
+    container_name   = var.container_name
+    container_port   = var.container_port
+  }
+
+  tags = local.tags
+}
+
+# TeamCity service security group
+resource "aws_security_group" "teamcity_service_sg" {
+  name   = "${local.name_prefix}-service-sg"
+  vpc_id = var.vpc_id
+
+  tags = local.tags
+}
+
+# TeamCity ALB security group
+resource "aws_security_group" "teamcity_alb_sg" {
+  name   = "${local.name_prefix}-alb-sg"
+  vpc_id = var.vpc_id
+
+  tags = local.tags
+}
+
+# Ingress rule for HTTP traffic from ALB to service
+resource "aws_vpc_security_group_ingress_rule" "service_inbound_alb" {
+  security_group_id            = aws_security_group.teamcity_service_sg.id
+  referenced_security_group_id = aws_security_group.teamcity_alb_sg.id
+  from_port                    = var.container_port
+  to_port                      = var.container_port
+  ip_protocol                  = "TCP"
+
+}
+
+# Egress rule for HTTP traffic from ALB to service
+resource "aws_vpc_security_group_egress_rule" "service_outbound_alb" {
+  security_group_id            = aws_security_group.teamcity_alb_sg.id
+  referenced_security_group_id = aws_security_group.teamcity_service_sg.id
+  from_port                    = var.container_port
+  to_port                      = var.container_port
+  ip_protocol                  = "TCP"
+
+}
+
+# Grant TeamCity service access to internet
+resource "aws_vpc_security_group_egress_rule" "internet_outbound" {
+
+  security_group_id = aws_security_group.teamcity_service_sg.id
+  cidr_ipv4         = "0.0.0.0/0"
+  ip_protocol       = "-1"
+}
+
+#############################################
+# IAM Roles for TeamCity Module
+#############################################
+data "aws_iam_policy_document" "ecs_tasks_trust_relationship" {
+  statement {
+    effect  = "Allow"
+    actions = ["sts:AssumeRole"]
+    principals {
+      type        = "Service"
+      identifiers = ["ecs-tasks.amazonaws.com"]
+    }
+  }
+}
+data "aws_iam_policy_document" "teamcity_default_policy" {
+  # ECS
+  statement {
+    sid    = "ECSExec"
+    effect = "Allow"
+    actions = [
+      "ssmmessages:OpenDataChannel",
+      "ssmmessages:OpenControlChannel",
+      "ssmmessages:CreateDataChannel",
+      "ssmmessages:CreateControlChannel",
+    ]
+    resources = [
+      "*"
+    ]
+  }
+}
+resource "aws_iam_policy" "teamcity_default_policy" {
+  name        = "teamcity-default-policy"
+  description = "Policy granting permissions for Unreal Horde."
+  policy      = data.aws_iam_policy_document.teamcity_default_policy.json
+}
+
+resource "aws_iam_role" "teamcity_default_role" {
+  name               = "teamcity-default-role"
+  assume_role_policy = data.aws_iam_policy_document.ecs_tasks_trust_relationship.json
+
+  managed_policy_arns = [
+    aws_iam_policy.teamcity_default_policy.arn
+  ]
+
+  tags = local.tags
+}
+resource "aws_iam_role" "teamcity_task_execution_role" {
+  name = "teamcity-task-execution-role"
+
+  assume_role_policy  = data.aws_iam_policy_document.ecs_tasks_trust_relationship.json
+  managed_policy_arns = ["arn:aws:iam::aws:policy/service-role/AmazonECSTaskExecutionRolePolicy"]
+}
+
+# CloudWatch Logs
+resource "aws_cloudwatch_log_group" "teamcity_log_group" {
+  #checkov:skip=CKV_AWS_158: KMS Encryption disabled by default
+  name              = "${local.name_prefix}-log-group"
+  retention_in_days = var.teamcity_cloudwatch_log_retention_in_days
+  tags              = local.tags
+}
+
+
+# Load Balancer for TeamCity Service
+resource "aws_lb" "teamcity_external_lb" {
+  name            = "${local.name_prefix}-lb"
+  security_groups = [aws_security_group.teamcity_alb_sg.id]
+
+  load_balancer_type         = "application"
+  internal                   = false
+  subnets                    = var.alb_subnets
+  drop_invalid_header_fields = true
+  tags                       = local.tags
+}
path: modules/teamcity/main.tf
user: login: github-advanced-security[bot]
body: ## Ensure the ELBv2 (Application/Network) has access logging enabled

Ensure the ELBv2 (Application/Network) has access logging enabled

[Show more details](https://github.com/aws-games/cloud-game-development-toolkit/security/code-scanning/794)
created_at: 2025-01-25T00:21:54Z
author_association: NONE
diff_hunk: @@ -0,0 +1,293 @@
+###################################
+# ECS Cluster for TeamCity Server #
+###################################
+
+resource "aws_ecs_cluster" "teamcity_cluster" {
+  name = "${local.name_prefix}-cluster"
+
+  setting {
+    name  = "containerInsights"
+    value = "enabled"
+  }
+
+  tags = local.tags
+}
+
+
+# TeamCity Task Definition
+resource "aws_ecs_task_definition" "teamcity_task_definition" {
+  family                   = var.name
+  requires_compatibilities = ["FARGATE"]
+  network_mode             = "awsvpc"
+  cpu                      = var.container_cpu
+  memory                   = var.container_memory
+
+
+  container_definitions = jsonencode([
+    {
+      name      = var.container_name
+      image     = local.image
+      cpu       = var.container_cpu
+      memory    = var.container_memory
+      essential = true
+      portMappings = [
+        {
+          containerPort = var.container_port
+          hostPort      = var.container_port
+        }
+      ]
+      logConfiguration = {
+        logDriver = "awslogs"
+        options = {
+          awslogs-group         = aws_cloudwatch_log_group.teamcity_log_group.name
+          awslogs-region        = data.aws_region.current.name
+          awslogs-stream-prefix = "[APP]"
+        }
+      },
+    }
+  ])
+
+  tags = {
+    Name = var.name
+  }
+  task_role_arn      = aws_iam_role.teamcity_default_role.arn
+  execution_role_arn = aws_iam_role.teamcity_task_execution_role.arn
+}
+
+
+
+
+# ECS Service
+resource "aws_ecs_service" "teamcity" {
+  name = local.name_prefix
+
+  cluster                = aws_ecs_cluster.teamcity_cluster.name
+  task_definition        = aws_ecs_task_definition.teamcity_task_definition.arn
+  launch_type            = "FARGATE"
+  desired_count          = 1    #TODO: make this configurable
+  force_new_deployment   = true #TODO: make this configurable
+  enable_execute_command = true #TODO: make this configurable
+
+  wait_for_steady_state = true
+
+  network_configuration {
+    subnets         = var.service_subnets
+    security_groups = [aws_security_group.teamcity_service_sg.id]
+  }
+  load_balancer {
+    target_group_arn = aws_lb_target_group.teamcity_target_group.arn
+    container_name   = var.container_name
+    container_port   = var.container_port
+  }
+
+  tags = local.tags
+}
+
+# TeamCity service security group
+resource "aws_security_group" "teamcity_service_sg" {
+  name   = "${local.name_prefix}-service-sg"
+  vpc_id = var.vpc_id
+
+  tags = local.tags
+}
+
+# TeamCity ALB security group
+resource "aws_security_group" "teamcity_alb_sg" {
+  name   = "${local.name_prefix}-alb-sg"
+  vpc_id = var.vpc_id
+
+  tags = local.tags
+}
+
+# Ingress rule for HTTP traffic from ALB to service
+resource "aws_vpc_security_group_ingress_rule" "service_inbound_alb" {
+  security_group_id            = aws_security_group.teamcity_service_sg.id
+  referenced_security_group_id = aws_security_group.teamcity_alb_sg.id
+  from_port                    = var.container_port
+  to_port                      = var.container_port
+  ip_protocol                  = "TCP"
+
+}
+
+# Egress rule for HTTP traffic from ALB to service
+resource "aws_vpc_security_group_egress_rule" "service_outbound_alb" {
+  security_group_id            = aws_security_group.teamcity_alb_sg.id
+  referenced_security_group_id = aws_security_group.teamcity_service_sg.id
+  from_port                    = var.container_port
+  to_port                      = var.container_port
+  ip_protocol                  = "TCP"
+
+}
+
+# Grant TeamCity service access to internet
+resource "aws_vpc_security_group_egress_rule" "internet_outbound" {
+
+  security_group_id = aws_security_group.teamcity_service_sg.id
+  cidr_ipv4         = "0.0.0.0/0"
+  ip_protocol       = "-1"
+}
+
+#############################################
+# IAM Roles for TeamCity Module
+#############################################
+data "aws_iam_policy_document" "ecs_tasks_trust_relationship" {
+  statement {
+    effect  = "Allow"
+    actions = ["sts:AssumeRole"]
+    principals {
+      type        = "Service"
+      identifiers = ["ecs-tasks.amazonaws.com"]
+    }
+  }
+}
+data "aws_iam_policy_document" "teamcity_default_policy" {
+  # ECS
+  statement {
+    sid    = "ECSExec"
+    effect = "Allow"
+    actions = [
+      "ssmmessages:OpenDataChannel",
+      "ssmmessages:OpenControlChannel",
+      "ssmmessages:CreateDataChannel",
+      "ssmmessages:CreateControlChannel",
+    ]
+    resources = [
+      "*"
+    ]
+  }
+}
+resource "aws_iam_policy" "teamcity_default_policy" {
+  name        = "teamcity-default-policy"
+  description = "Policy granting permissions for Unreal Horde."
+  policy      = data.aws_iam_policy_document.teamcity_default_policy.json
+}
+
+resource "aws_iam_role" "teamcity_default_role" {
+  name               = "teamcity-default-role"
+  assume_role_policy = data.aws_iam_policy_document.ecs_tasks_trust_relationship.json
+
+  managed_policy_arns = [
+    aws_iam_policy.teamcity_default_policy.arn
+  ]
+
+  tags = local.tags
+}
+resource "aws_iam_role" "teamcity_task_execution_role" {
+  name = "teamcity-task-execution-role"
+
+  assume_role_policy  = data.aws_iam_policy_document.ecs_tasks_trust_relationship.json
+  managed_policy_arns = ["arn:aws:iam::aws:policy/service-role/AmazonECSTaskExecutionRolePolicy"]
+}
+
+# CloudWatch Logs
+resource "aws_cloudwatch_log_group" "teamcity_log_group" {
+  #checkov:skip=CKV_AWS_158: KMS Encryption disabled by default
+  name              = "${local.name_prefix}-log-group"
+  retention_in_days = var.teamcity_cloudwatch_log_retention_in_days
+  tags              = local.tags
+}
+
+
+# Load Balancer for TeamCity Service
+resource "aws_lb" "teamcity_external_lb" {
+  name            = "${local.name_prefix}-lb"
+  security_groups = [aws_security_group.teamcity_alb_sg.id]
+
+  load_balancer_type         = "application"
+  internal                   = false
+  subnets                    = var.alb_subnets
+  drop_invalid_header_fields = true
+  tags                       = local.tags
+}
path: modules/teamcity/main.tf
user: login: github-advanced-security[bot]
body: ## Ensure that Load Balancer has deletion protection enabled

Ensure that Load Balancer has deletion protection enabled

[Show more details](https://github.com/aws-games/cloud-game-development-toolkit/security/code-scanning/795)
created_at: 2025-01-25T00:21:54Z
author_association: NONE
diff_hunk: @@ -0,0 +1,293 @@
+###################################
+# ECS Cluster for TeamCity Server #
+###################################
+
+resource "aws_ecs_cluster" "teamcity_cluster" {
+  name = "${local.name_prefix}-cluster"
+
+  setting {
+    name  = "containerInsights"
+    value = "enabled"
+  }
+
+  tags = local.tags
+}
+
+
+# TeamCity Task Definition
+resource "aws_ecs_task_definition" "teamcity_task_definition" {
+  family                   = var.name
+  requires_compatibilities = ["FARGATE"]
+  network_mode             = "awsvpc"
+  cpu                      = var.container_cpu
+  memory                   = var.container_memory
+
+
+  container_definitions = jsonencode([
+    {
+      name      = var.container_name
+      image     = local.image
+      cpu       = var.container_cpu
+      memory    = var.container_memory
+      essential = true
+      portMappings = [
+        {
+          containerPort = var.container_port
+          hostPort      = var.container_port
+        }
+      ]
+      logConfiguration = {
+        logDriver = "awslogs"
+        options = {
+          awslogs-group         = aws_cloudwatch_log_group.teamcity_log_group.name
+          awslogs-region        = data.aws_region.current.name
+          awslogs-stream-prefix = "[APP]"
+        }
+      },
+    }
+  ])
+
+  tags = {
+    Name = var.name
+  }
+  task_role_arn      = aws_iam_role.teamcity_default_role.arn
+  execution_role_arn = aws_iam_role.teamcity_task_execution_role.arn
+}
+
+
+
+
+# ECS Service
+resource "aws_ecs_service" "teamcity" {
+  name = local.name_prefix
+
+  cluster                = aws_ecs_cluster.teamcity_cluster.name
+  task_definition        = aws_ecs_task_definition.teamcity_task_definition.arn
+  launch_type            = "FARGATE"
+  desired_count          = 1    #TODO: make this configurable
+  force_new_deployment   = true #TODO: make this configurable
+  enable_execute_command = true #TODO: make this configurable
+
+  wait_for_steady_state = true
+
+  network_configuration {
+    subnets         = var.service_subnets
+    security_groups = [aws_security_group.teamcity_service_sg.id]
+  }
+  load_balancer {
+    target_group_arn = aws_lb_target_group.teamcity_target_group.arn
+    container_name   = var.container_name
+    container_port   = var.container_port
+  }
+
+  tags = local.tags
+}
+
+# TeamCity service security group
+resource "aws_security_group" "teamcity_service_sg" {
+  name   = "${local.name_prefix}-service-sg"
+  vpc_id = var.vpc_id
+
+  tags = local.tags
+}
+
+# TeamCity ALB security group
+resource "aws_security_group" "teamcity_alb_sg" {
+  name   = "${local.name_prefix}-alb-sg"
+  vpc_id = var.vpc_id
+
+  tags = local.tags
+}
+
+# Ingress rule for HTTP traffic from ALB to service
+resource "aws_vpc_security_group_ingress_rule" "service_inbound_alb" {
+  security_group_id            = aws_security_group.teamcity_service_sg.id
+  referenced_security_group_id = aws_security_group.teamcity_alb_sg.id
+  from_port                    = var.container_port
+  to_port                      = var.container_port
+  ip_protocol                  = "TCP"
+
+}
+
+# Egress rule for HTTP traffic from ALB to service
+resource "aws_vpc_security_group_egress_rule" "service_outbound_alb" {
+  security_group_id            = aws_security_group.teamcity_alb_sg.id
+  referenced_security_group_id = aws_security_group.teamcity_service_sg.id
+  from_port                    = var.container_port
+  to_port                      = var.container_port
+  ip_protocol                  = "TCP"
+
+}
+
+# Grant TeamCity service access to internet
+resource "aws_vpc_security_group_egress_rule" "internet_outbound" {
+
+  security_group_id = aws_security_group.teamcity_service_sg.id
+  cidr_ipv4         = "0.0.0.0/0"
+  ip_protocol       = "-1"
+}
+
+#############################################
+# IAM Roles for TeamCity Module
+#############################################
+data "aws_iam_policy_document" "ecs_tasks_trust_relationship" {
+  statement {
+    effect  = "Allow"
+    actions = ["sts:AssumeRole"]
+    principals {
+      type        = "Service"
+      identifiers = ["ecs-tasks.amazonaws.com"]
+    }
+  }
+}
+data "aws_iam_policy_document" "teamcity_default_policy" {
+  # ECS
+  statement {
+    sid    = "ECSExec"
+    effect = "Allow"
+    actions = [
+      "ssmmessages:OpenDataChannel",
+      "ssmmessages:OpenControlChannel",
+      "ssmmessages:CreateDataChannel",
+      "ssmmessages:CreateControlChannel",
+    ]
+    resources = [
+      "*"
+    ]
+  }
+}
+resource "aws_iam_policy" "teamcity_default_policy" {
+  name        = "teamcity-default-policy"
+  description = "Policy granting permissions for Unreal Horde."
+  policy      = data.aws_iam_policy_document.teamcity_default_policy.json
+}
+
+resource "aws_iam_role" "teamcity_default_role" {
+  name               = "teamcity-default-role"
+  assume_role_policy = data.aws_iam_policy_document.ecs_tasks_trust_relationship.json
+
+  managed_policy_arns = [
+    aws_iam_policy.teamcity_default_policy.arn
+  ]
+
+  tags = local.tags
+}
+resource "aws_iam_role" "teamcity_task_execution_role" {
+  name = "teamcity-task-execution-role"
+
+  assume_role_policy  = data.aws_iam_policy_document.ecs_tasks_trust_relationship.json
+  managed_policy_arns = ["arn:aws:iam::aws:policy/service-role/AmazonECSTaskExecutionRolePolicy"]
+}
+
+# CloudWatch Logs
+resource "aws_cloudwatch_log_group" "teamcity_log_group" {
+  #checkov:skip=CKV_AWS_158: KMS Encryption disabled by default
+  name              = "${local.name_prefix}-log-group"
+  retention_in_days = var.teamcity_cloudwatch_log_retention_in_days
+  tags              = local.tags
+}
+
+
+# Load Balancer for TeamCity Service
+resource "aws_lb" "teamcity_external_lb" {
+  name            = "${local.name_prefix}-lb"
+  security_groups = [aws_security_group.teamcity_alb_sg.id]
+
+  load_balancer_type         = "application"
+  internal                   = false
+  subnets                    = var.alb_subnets
+  drop_invalid_header_fields = true
+  tags                       = local.tags
+}
path: modules/teamcity/main.tf
user: login: github-advanced-security[bot]
body: ## Ensure public facing ALB are protected by WAF

Ensure public facing ALB are protected by WAF

[Show more details](https://github.com/aws-games/cloud-game-development-toolkit/security/code-scanning/797)
created_at: 2025-01-25T00:21:54Z
author_association: NONE
diff_hunk: @@ -0,0 +1,293 @@
+###################################
+# ECS Cluster for TeamCity Server #
+###################################
+
+resource "aws_ecs_cluster" "teamcity_cluster" {
+  name = "${local.name_prefix}-cluster"
+
+  setting {
+    name  = "containerInsights"
+    value = "enabled"
+  }
+
+  tags = local.tags
+}
+
+
+# TeamCity Task Definition
+resource "aws_ecs_task_definition" "teamcity_task_definition" {
+  family                   = var.name
+  requires_compatibilities = ["FARGATE"]
+  network_mode             = "awsvpc"
+  cpu                      = var.container_cpu
+  memory                   = var.container_memory
+
+
+  container_definitions = jsonencode([
+    {
+      name      = var.container_name
+      image     = local.image
+      cpu       = var.container_cpu
+      memory    = var.container_memory
+      essential = true
+      portMappings = [
+        {
+          containerPort = var.container_port
+          hostPort      = var.container_port
+        }
+      ]
+      logConfiguration = {
+        logDriver = "awslogs"
+        options = {
+          awslogs-group         = aws_cloudwatch_log_group.teamcity_log_group.name
+          awslogs-region        = data.aws_region.current.name
+          awslogs-stream-prefix = "[APP]"
+        }
+      },
+    }
+  ])
+
+  tags = {
+    Name = var.name
+  }
+  task_role_arn      = aws_iam_role.teamcity_default_role.arn
+  execution_role_arn = aws_iam_role.teamcity_task_execution_role.arn
+}
+
+
+
+
+# ECS Service
+resource "aws_ecs_service" "teamcity" {
+  name = local.name_prefix
+
+  cluster                = aws_ecs_cluster.teamcity_cluster.name
+  task_definition        = aws_ecs_task_definition.teamcity_task_definition.arn
+  launch_type            = "FARGATE"
+  desired_count          = 1    #TODO: make this configurable
+  force_new_deployment   = true #TODO: make this configurable
+  enable_execute_command = true #TODO: make this configurable
+
+  wait_for_steady_state = true
+
+  network_configuration {
+    subnets         = var.service_subnets
+    security_groups = [aws_security_group.teamcity_service_sg.id]
+  }
+  load_balancer {
+    target_group_arn = aws_lb_target_group.teamcity_target_group.arn
+    container_name   = var.container_name
+    container_port   = var.container_port
+  }
+
+  tags = local.tags
+}
+
+# TeamCity service security group
+resource "aws_security_group" "teamcity_service_sg" {
+  name   = "${local.name_prefix}-service-sg"
+  vpc_id = var.vpc_id
+
+  tags = local.tags
+}
+
+# TeamCity ALB security group
+resource "aws_security_group" "teamcity_alb_sg" {
+  name   = "${local.name_prefix}-alb-sg"
+  vpc_id = var.vpc_id
+
+  tags = local.tags
+}
+
+# Ingress rule for HTTP traffic from ALB to service
+resource "aws_vpc_security_group_ingress_rule" "service_inbound_alb" {
+  security_group_id            = aws_security_group.teamcity_service_sg.id
+  referenced_security_group_id = aws_security_group.teamcity_alb_sg.id
+  from_port                    = var.container_port
+  to_port                      = var.container_port
+  ip_protocol                  = "TCP"
+
+}
+
+# Egress rule for HTTP traffic from ALB to service
+resource "aws_vpc_security_group_egress_rule" "service_outbound_alb" {
+  security_group_id            = aws_security_group.teamcity_alb_sg.id
+  referenced_security_group_id = aws_security_group.teamcity_service_sg.id
+  from_port                    = var.container_port
+  to_port                      = var.container_port
+  ip_protocol                  = "TCP"
+
+}
+
+# Grant TeamCity service access to internet
+resource "aws_vpc_security_group_egress_rule" "internet_outbound" {
+
+  security_group_id = aws_security_group.teamcity_service_sg.id
+  cidr_ipv4         = "0.0.0.0/0"
+  ip_protocol       = "-1"
+}
+
+#############################################
+# IAM Roles for TeamCity Module
+#############################################
+data "aws_iam_policy_document" "ecs_tasks_trust_relationship" {
+  statement {
+    effect  = "Allow"
+    actions = ["sts:AssumeRole"]
+    principals {
+      type        = "Service"
+      identifiers = ["ecs-tasks.amazonaws.com"]
+    }
+  }
+}
+data "aws_iam_policy_document" "teamcity_default_policy" {
+  # ECS
+  statement {
+    sid    = "ECSExec"
+    effect = "Allow"
+    actions = [
+      "ssmmessages:OpenDataChannel",
+      "ssmmessages:OpenControlChannel",
+      "ssmmessages:CreateDataChannel",
+      "ssmmessages:CreateControlChannel",
+    ]
+    resources = [
+      "*"
+    ]
+  }
+}
+resource "aws_iam_policy" "teamcity_default_policy" {
+  name        = "teamcity-default-policy"
+  description = "Policy granting permissions for Unreal Horde."
+  policy      = data.aws_iam_policy_document.teamcity_default_policy.json
+}
+
+resource "aws_iam_role" "teamcity_default_role" {
+  name               = "teamcity-default-role"
+  assume_role_policy = data.aws_iam_policy_document.ecs_tasks_trust_relationship.json
+
+  managed_policy_arns = [
+    aws_iam_policy.teamcity_default_policy.arn
+  ]
+
+  tags = local.tags
+}
+resource "aws_iam_role" "teamcity_task_execution_role" {
+  name = "teamcity-task-execution-role"
+
+  assume_role_policy  = data.aws_iam_policy_document.ecs_tasks_trust_relationship.json
+  managed_policy_arns = ["arn:aws:iam::aws:policy/service-role/AmazonECSTaskExecutionRolePolicy"]
+}
+
+# CloudWatch Logs
+resource "aws_cloudwatch_log_group" "teamcity_log_group" {
+  #checkov:skip=CKV_AWS_158: KMS Encryption disabled by default
+  name              = "${local.name_prefix}-log-group"
+  retention_in_days = var.teamcity_cloudwatch_log_retention_in_days
+  tags              = local.tags
+}
+
+
+# Load Balancer for TeamCity Service
+resource "aws_lb" "teamcity_external_lb" {
+  name            = "${local.name_prefix}-lb"
+  security_groups = [aws_security_group.teamcity_alb_sg.id]
+
+  load_balancer_type         = "application"
+  internal                   = false
+  subnets                    = var.alb_subnets
+  drop_invalid_header_fields = true
+  tags                       = local.tags
+}
+
+# TeamCity target group for ALB
+resource "aws_lb_target_group" "teamcity_target_group" {
+  name        = "${local.name_prefix}-tg"
+  port        = var.container_port
+  protocol    = "HTTP"
+  vpc_id      = var.vpc_id
+  target_type = "ip"
+
+  # health_check {
+  #   path                = "/"
+  #   interval            = 30
+  #   timeout             = 5
+  #   healthy_threshold   = 2
+  #   unhealthy_threshold = 2
+  # }
+
+  tags = local.tags
+}
path: modules/teamcity/main.tf
user: login: github-advanced-security[bot]
body: ## Ensure HTTP HTTPS Target group defines Healthcheck

Ensure HTTP HTTPS Target group defines Healthcheck

[Show more details](https://github.com/aws-games/cloud-game-development-toolkit/security/code-scanning/796)
created_at: 2025-01-25T00:21:54Z
author_association: NONE
diff_hunk: @@ -0,0 +1,413 @@
+###################################
+# ECS Cluster for TeamCity Server #
+###################################
+
+resource "aws_ecs_cluster" "teamcity_cluster" {
+  name = "${local.name_prefix}-cluster"
+
+  setting {
+    name  = "containerInsights"
+    value = "enabled"
+  }
+
+  tags = local.tags
+}
+
+
+# TeamCity Task Definition
+resource "aws_ecs_task_definition" "teamcity_task_definition" {
+  family                   = var.name
+  requires_compatibilities = ["FARGATE"]
+  network_mode             = "awsvpc"
+  cpu                      = var.container_cpu
+  memory                   = var.container_memory
+
+
+  container_definitions = jsonencode([
+    {
+      name      = var.container_name
+      image     = local.image
+      cpu       = var.container_cpu
+      memory    = var.container_memory
+      essential = true
+      portMappings = [
+        {
+          containerPort = var.container_port
+          hostPort      = var.container_port
+        }
+      ]
+      logConfiguration = {
+        logDriver = "awslogs"
+        options = {
+          awslogs-group         = aws_cloudwatch_log_group.teamcity_log_group.name
+          awslogs-region        = data.aws_region.current.name
+          awslogs-stream-prefix = "[APP]"
+        }
+      },
+      mountPoints = [
+        {
+          sourceVolume  = "teamcity_data",
+          containerPath = "/data/teamcity_server/datadir"
+          readOnly      = false
+        },
+        {
+          sourceVolume  = "teamcity_logs",
+          containerPath = "/data/teamcity_server/logs"
+          readOnly      = false
+        }
+      ]
+    }
+  ])
+
+  tags = {
+    Name = var.name
+  }
+  task_role_arn      = aws_iam_role.teamcity_default_role.arn
+  execution_role_arn = aws_iam_role.teamcity_task_execution_role.arn
+
+  volume {
+    name = "teamcity_data"
+    efs_volume_configuration {
+      file_system_id          = aws_efs_file_system.teamcity_efs_file_system.id
+      transit_encryption      = "ENABLED"
+      transit_encryption_port = 2999
+      authorization_config {
+        access_point_id = aws_efs_access_point.teamcity_efs_data_access_point.id
+        iam             = "ENABLED"
+      }
+    }
+  }
+
+  volume {
+    name = "teamcity_logs"
+    efs_volume_configuration {
+      file_system_id     = aws_efs_file_system.teamcity_efs_file_system.id
+      transit_encryption = "ENABLED"
+      authorization_config {
+        access_point_id = aws_efs_access_point.teamcity_efs_logs_access_point.id
+        iam             = "ENABLED"
+      }
+    }
+  }
+}
+
+
+
+
+# ECS Service
+resource "aws_ecs_service" "teamcity" {
+  name = local.name_prefix
+
+  cluster                = aws_ecs_cluster.teamcity_cluster.name
+  task_definition        = aws_ecs_task_definition.teamcity_task_definition.arn
+  launch_type            = "FARGATE"
+  desired_count          = 1    #TODO: make this configurable
+  force_new_deployment   = true #TODO: make this configurable
+  enable_execute_command = true #TODO: make this configurable
+
+  wait_for_steady_state = true
+
+  network_configuration {
+    subnets         = var.service_subnets
+    security_groups = [aws_security_group.teamcity_service_sg.id]
+  }
+  load_balancer {
+    target_group_arn = aws_lb_target_group.teamcity_target_group.arn
+    container_name   = var.container_name
+    container_port   = var.container_port
+  }
+
+  tags = local.tags
+}
+
+# TeamCity service security group
+resource "aws_security_group" "teamcity_service_sg" {
+  name   = "${local.name_prefix}-service-sg"
+  vpc_id = var.vpc_id
+
+  tags = local.tags
+}
+
+# TeamCity EFS security group
+resource "aws_security_group" "teamcity_efs_sg" {
+  name        = "${local.name_prefix}-efs-sg"
+  description = "TeamCity EFS mount target security group"
+  vpc_id      = var.vpc_id
+
+  tags = local.tags
+}
+
+# Ingress rule for NFS traffic from service to EFS
+resource "aws_vpc_security_group_ingress_rule" "service_efs" {
+  security_group_id            = aws_security_group.teamcity_efs_sg.id
+  referenced_security_group_id = aws_security_group.teamcity_service_sg.id
+  description                  = "Allow inbound access from TeamCity service containers to EFS"
+  ip_protocol                  = "TCP"
+  from_port                    = 2049
+  to_port                      = 2049
+}
+
+# Ingress rule for NFS traffic from EFS to service
+resource "aws_vpc_security_group_ingress_rule" "efs_service" {
+  security_group_id            = aws_security_group.teamcity_service_sg.id
+  referenced_security_group_id = aws_security_group.teamcity_efs_sg.id
+  description                  = "Allow inbound access from EFS to TeamCity service containers"
+  ip_protocol                  = "TCP"
+  from_port                    = 2049
+  to_port                      = 2049
+}
+
+
+
+
+# TeamCity ALB security group
+resource "aws_security_group" "teamcity_alb_sg" {
+  name   = "${local.name_prefix}-alb-sg"
+  vpc_id = var.vpc_id
+
+  tags = local.tags
+}
+
+# Ingress rule for HTTP traffic from ALB to service
+resource "aws_vpc_security_group_ingress_rule" "service_inbound_alb" {
+  security_group_id            = aws_security_group.teamcity_service_sg.id
+  referenced_security_group_id = aws_security_group.teamcity_alb_sg.id
+  from_port                    = var.container_port
+  to_port                      = var.container_port
+  ip_protocol                  = "TCP"
+
+}
+
+# Egress rule for HTTP traffic from ALB to service
+resource "aws_vpc_security_group_egress_rule" "service_outbound_alb" {
+  security_group_id            = aws_security_group.teamcity_alb_sg.id
+  referenced_security_group_id = aws_security_group.teamcity_service_sg.id
+  from_port                    = var.container_port
+  to_port                      = var.container_port
+  ip_protocol                  = "TCP"
+
+}
+
+# Grant TeamCity service access to internet
+resource "aws_vpc_security_group_egress_rule" "internet_outbound" {
+
+  security_group_id = aws_security_group.teamcity_service_sg.id
+  cidr_ipv4         = "0.0.0.0/0"
+  ip_protocol       = "-1"
+}
+
+#############################################
+# IAM Roles for TeamCity Module
+#############################################
+data "aws_iam_policy_document" "ecs_tasks_trust_relationship" {
+  statement {
+    effect  = "Allow"
+    actions = ["sts:AssumeRole"]
+    principals {
+      type        = "Service"
+      identifiers = ["ecs-tasks.amazonaws.com"]
+    }
+  }
+}
+data "aws_iam_policy_document" "teamcity_default_policy" {
+  # ECS
+  statement {
+    sid    = "ECSExec"
+    effect = "Allow"
+    actions = [
+      "ssmmessages:OpenDataChannel",
+      "ssmmessages:OpenControlChannel",
+      "ssmmessages:CreateDataChannel",
+      "ssmmessages:CreateControlChannel",
+    ]
+    resources = [
+      "*"
+    ]
+  }
+}
+resource "aws_iam_policy" "teamcity_default_policy" {
+  name        = "teamcity-default-policy"
+  description = "Policy granting permissions for Unreal Horde."
+  policy      = data.aws_iam_policy_document.teamcity_default_policy.json
+}
+
+resource "aws_iam_role" "teamcity_default_role" {
+  name               = "teamcity-default-role"
+  assume_role_policy = data.aws_iam_policy_document.ecs_tasks_trust_relationship.json
+
+  managed_policy_arns = [
+    aws_iam_policy.teamcity_default_policy.arn
+  ]
+
+  tags = local.tags
+}
+resource "aws_iam_role" "teamcity_task_execution_role" {
+  name = "teamcity-task-execution-role"
+
+  assume_role_policy  = data.aws_iam_policy_document.ecs_tasks_trust_relationship.json
+  managed_policy_arns = ["arn:aws:iam::aws:policy/service-role/AmazonECSTaskExecutionRolePolicy"]
+}
+
+# CloudWatch Logs
+resource "aws_cloudwatch_log_group" "teamcity_log_group" {
+  #checkov:skip=CKV_AWS_158: KMS Encryption disabled by default
+  name              = "${local.name_prefix}-log-group"
+  retention_in_days = var.teamcity_cloudwatch_log_retention_in_days
+  tags              = local.tags
+}
+
+
+# Load Balancer for TeamCity Service
+resource "aws_lb" "teamcity_external_lb" {
+  name            = "${local.name_prefix}-lb"
+  security_groups = [aws_security_group.teamcity_alb_sg.id]
+
+  load_balancer_type         = "application"
+  internal                   = false
+  subnets                    = var.alb_subnets
+  drop_invalid_header_fields = true
+  tags                       = local.tags
+}
+
+# TeamCity target group for ALB
+resource "aws_lb_target_group" "teamcity_target_group" {
+  name        = "${local.name_prefix}-tg"
+  port        = var.container_port
+  protocol    = "HTTP"
+  vpc_id      = var.vpc_id
+  target_type = "ip"
+
+  # health_check {
+  #   path                = "/"
+  #   interval            = 30
+  #   timeout             = 5
+  #   healthy_threshold   = 2
+  #   unhealthy_threshold = 2
+  # }
+
+  tags = local.tags
+}
+
+# ALB HTTPS Listener
+resource "aws_lb_listener" "teamcity_listener" {
+  load_balancer_arn = aws_lb.teamcity_external_lb.arn
+  port              = "443"
+  protocol          = "HTTPS"
+  ssl_policy        = "ELBSecurityPolicy-TLS13-1-2-2021-06"
+  certificate_arn   = var.alb_certificate_arn
+  default_action {
+    type             = "forward"
+    target_group_arn = aws_lb_target_group.teamcity_target_group.arn
+  }
+  tags = local.tags
+}
+
+
+
+
+# #######################################
+# # TeamCity Aurora Serverless Database #
+# #######################################
+
+# # RDS instance with Aurora serverless engine
+# resource "aws_rds_cluster" "teamcity_db_cluster" {
+#   cluster_identifier = "teamcity-cluster"
+#   engine             = "aurora-postgresql"
+#   engine_mode        = "provisioned"
+#   engine_version     = "16.6"
+#   database_name      = "teamcity"
+#   master_username    = "teamcity"
+#   master_password    = "teamcity2025"
+#   storage_encrypted  = true
+
+#   serverlessv2_scaling_configuration {
+#     max_capacity             = 1.0
+#     min_capacity             = 0.0
+#     seconds_until_auto_pause = 3600
+#   }
+# }
+
+# resource "aws_rds_cluster_instance" "teamcity_db_cluster" {
+#   cluster_identifier = aws_rds_cluster.teamcity_db_cluster.id
+#   instance_class     = "db.serverless"
+#   engine             = aws_rds_cluster.teamcity_db_cluster.engine
+#   engine_version     = aws_rds_cluster.teamcity_db_cluster.engine_version
+#   skip_final_snapshot = true
+# }
+
+# ################
+# # TeamCity EFS #
+# ################
+
+# File system for teamcity
+resource "aws_efs_file_system" "teamcity_efs_file_system" {
+  creation_token   = "${local.name_prefix}-efs-file-system"
+  performance_mode = var.teamcity_efs_performance_mode
+  throughput_mode  = var.teamcity_efs_throughput_mode
+
+  #TODO: Parameterize encryption and customer managed key creation
+  encrypted = false
+
+  lifecycle_policy {
+    transition_to_ia = "AFTER_30_DAYS"
+  }
+
+  lifecycle_policy {
+    transition_to_primary_storage_class = "AFTER_1_ACCESS"
+  }
+  #checkov:skip=CKV_AWS_184: CMK encryption not supported currently
+  tags = merge(local.tags, {
+    Name = "${local.name_prefix}-efs-file-system"
+  })
+}
path: modules/teamcity/main.tf
user: login: github-advanced-security[bot]
body: ## Ensure EFS is securely encrypted

Ensure EFS is securely encrypted

[Show more details](https://github.com/aws-games/cloud-game-development-toolkit/security/code-scanning/802)
created_at: 2025-02-01T02:09:12Z
author_association: NONE
diff_hunk: @@ -0,0 +1,413 @@
+###################################
+# ECS Cluster for TeamCity Server #
+###################################
+
+resource "aws_ecs_cluster" "teamcity_cluster" {
+  name = "${local.name_prefix}-cluster"
+
+  setting {
+    name  = "containerInsights"
+    value = "enabled"
+  }
+
+  tags = local.tags
+}
+
+
+# TeamCity Task Definition
+resource "aws_ecs_task_definition" "teamcity_task_definition" {
+  family                   = var.name
+  requires_compatibilities = ["FARGATE"]
+  network_mode             = "awsvpc"
+  cpu                      = var.container_cpu
+  memory                   = var.container_memory
+
+
+  container_definitions = jsonencode([
+    {
+      name      = var.container_name
+      image     = local.image
+      cpu       = var.container_cpu
+      memory    = var.container_memory
+      essential = true
+      portMappings = [
+        {
+          containerPort = var.container_port
+          hostPort      = var.container_port
+        }
+      ]
+      logConfiguration = {
+        logDriver = "awslogs"
+        options = {
+          awslogs-group         = aws_cloudwatch_log_group.teamcity_log_group.name
+          awslogs-region        = data.aws_region.current.name
+          awslogs-stream-prefix = "[APP]"
+        }
+      },
+      mountPoints = [
+        {
+          sourceVolume  = "teamcity_data",
+          containerPath = "/data/teamcity_server/datadir"
+          readOnly      = false
+        },
+        {
+          sourceVolume  = "teamcity_logs",
+          containerPath = "/data/teamcity_server/logs"
+          readOnly      = false
+        }
+      ]
+    }
+  ])
+
+  tags = {
+    Name = var.name
+  }
+  task_role_arn      = aws_iam_role.teamcity_default_role.arn
+  execution_role_arn = aws_iam_role.teamcity_task_execution_role.arn
+
+  volume {
+    name = "teamcity_data"
+    efs_volume_configuration {
+      file_system_id          = aws_efs_file_system.teamcity_efs_file_system.id
+      transit_encryption      = "ENABLED"
+      transit_encryption_port = 2999
+      authorization_config {
+        access_point_id = aws_efs_access_point.teamcity_efs_data_access_point.id
+        iam             = "ENABLED"
+      }
+    }
+  }
+
+  volume {
+    name = "teamcity_logs"
+    efs_volume_configuration {
+      file_system_id     = aws_efs_file_system.teamcity_efs_file_system.id
+      transit_encryption = "ENABLED"
+      authorization_config {
+        access_point_id = aws_efs_access_point.teamcity_efs_logs_access_point.id
+        iam             = "ENABLED"
+      }
+    }
+  }
+}
+
+
+
+
+# ECS Service
+resource "aws_ecs_service" "teamcity" {
+  name = local.name_prefix
+
+  cluster                = aws_ecs_cluster.teamcity_cluster.name
+  task_definition        = aws_ecs_task_definition.teamcity_task_definition.arn
+  launch_type            = "FARGATE"
+  desired_count          = 1    #TODO: make this configurable
+  force_new_deployment   = true #TODO: make this configurable
+  enable_execute_command = true #TODO: make this configurable
+
+  wait_for_steady_state = true
+
+  network_configuration {
+    subnets         = var.service_subnets
+    security_groups = [aws_security_group.teamcity_service_sg.id]
+  }
+  load_balancer {
+    target_group_arn = aws_lb_target_group.teamcity_target_group.arn
+    container_name   = var.container_name
+    container_port   = var.container_port
+  }
+
+  tags = local.tags
+}
+
+# TeamCity service security group
+resource "aws_security_group" "teamcity_service_sg" {
+  name   = "${local.name_prefix}-service-sg"
+  vpc_id = var.vpc_id
+
+  tags = local.tags
+}
+
+# TeamCity EFS security group
+resource "aws_security_group" "teamcity_efs_sg" {
+  name        = "${local.name_prefix}-efs-sg"
+  description = "TeamCity EFS mount target security group"
+  vpc_id      = var.vpc_id
+
+  tags = local.tags
+}
+
+# Ingress rule for NFS traffic from service to EFS
+resource "aws_vpc_security_group_ingress_rule" "service_efs" {
+  security_group_id            = aws_security_group.teamcity_efs_sg.id
+  referenced_security_group_id = aws_security_group.teamcity_service_sg.id
+  description                  = "Allow inbound access from TeamCity service containers to EFS"
+  ip_protocol                  = "TCP"
+  from_port                    = 2049
+  to_port                      = 2049
+}
+
+# Ingress rule for NFS traffic from EFS to service
+resource "aws_vpc_security_group_ingress_rule" "efs_service" {
+  security_group_id            = aws_security_group.teamcity_service_sg.id
+  referenced_security_group_id = aws_security_group.teamcity_efs_sg.id
+  description                  = "Allow inbound access from EFS to TeamCity service containers"
+  ip_protocol                  = "TCP"
+  from_port                    = 2049
+  to_port                      = 2049
+}
+
+
+
+
+# TeamCity ALB security group
+resource "aws_security_group" "teamcity_alb_sg" {
+  name   = "${local.name_prefix}-alb-sg"
+  vpc_id = var.vpc_id
+
+  tags = local.tags
+}
+
+# Ingress rule for HTTP traffic from ALB to service
+resource "aws_vpc_security_group_ingress_rule" "service_inbound_alb" {
+  security_group_id            = aws_security_group.teamcity_service_sg.id
+  referenced_security_group_id = aws_security_group.teamcity_alb_sg.id
+  from_port                    = var.container_port
+  to_port                      = var.container_port
+  ip_protocol                  = "TCP"
+
+}
+
+# Egress rule for HTTP traffic from ALB to service
+resource "aws_vpc_security_group_egress_rule" "service_outbound_alb" {
+  security_group_id            = aws_security_group.teamcity_alb_sg.id
+  referenced_security_group_id = aws_security_group.teamcity_service_sg.id
+  from_port                    = var.container_port
+  to_port                      = var.container_port
+  ip_protocol                  = "TCP"
+
+}
+
+# Grant TeamCity service access to internet
+resource "aws_vpc_security_group_egress_rule" "internet_outbound" {
+
+  security_group_id = aws_security_group.teamcity_service_sg.id
+  cidr_ipv4         = "0.0.0.0/0"
+  ip_protocol       = "-1"
+}
+
+#############################################
+# IAM Roles for TeamCity Module
+#############################################
+data "aws_iam_policy_document" "ecs_tasks_trust_relationship" {
+  statement {
+    effect  = "Allow"
+    actions = ["sts:AssumeRole"]
+    principals {
+      type        = "Service"
+      identifiers = ["ecs-tasks.amazonaws.com"]
+    }
+  }
+}
+data "aws_iam_policy_document" "teamcity_default_policy" {
+  # ECS
+  statement {
+    sid    = "ECSExec"
+    effect = "Allow"
+    actions = [
+      "ssmmessages:OpenDataChannel",
+      "ssmmessages:OpenControlChannel",
+      "ssmmessages:CreateDataChannel",
+      "ssmmessages:CreateControlChannel",
+    ]
+    resources = [
+      "*"
+    ]
+  }
+}
+resource "aws_iam_policy" "teamcity_default_policy" {
+  name        = "teamcity-default-policy"
+  description = "Policy granting permissions for Unreal Horde."
+  policy      = data.aws_iam_policy_document.teamcity_default_policy.json
+}
+
+resource "aws_iam_role" "teamcity_default_role" {
+  name               = "teamcity-default-role"
+  assume_role_policy = data.aws_iam_policy_document.ecs_tasks_trust_relationship.json
+
+  managed_policy_arns = [
+    aws_iam_policy.teamcity_default_policy.arn
+  ]
+
+  tags = local.tags
+}
+resource "aws_iam_role" "teamcity_task_execution_role" {
+  name = "teamcity-task-execution-role"
+
+  assume_role_policy  = data.aws_iam_policy_document.ecs_tasks_trust_relationship.json
+  managed_policy_arns = ["arn:aws:iam::aws:policy/service-role/AmazonECSTaskExecutionRolePolicy"]
+}
+
+# CloudWatch Logs
+resource "aws_cloudwatch_log_group" "teamcity_log_group" {
+  #checkov:skip=CKV_AWS_158: KMS Encryption disabled by default
+  name              = "${local.name_prefix}-log-group"
+  retention_in_days = var.teamcity_cloudwatch_log_retention_in_days
+  tags              = local.tags
+}
+
+
+# Load Balancer for TeamCity Service
+resource "aws_lb" "teamcity_external_lb" {
+  name            = "${local.name_prefix}-lb"
+  security_groups = [aws_security_group.teamcity_alb_sg.id]
+
+  load_balancer_type         = "application"
+  internal                   = false
+  subnets                    = var.alb_subnets
+  drop_invalid_header_fields = true
+  tags                       = local.tags
+}
+
+# TeamCity target group for ALB
+resource "aws_lb_target_group" "teamcity_target_group" {
+  name        = "${local.name_prefix}-tg"
+  port        = var.container_port
+  protocol    = "HTTP"
+  vpc_id      = var.vpc_id
+  target_type = "ip"
+
+  # health_check {
+  #   path                = "/"
+  #   interval            = 30
+  #   timeout             = 5
+  #   healthy_threshold   = 2
+  #   unhealthy_threshold = 2
+  # }
+
+  tags = local.tags
+}
+
+# ALB HTTPS Listener
+resource "aws_lb_listener" "teamcity_listener" {
+  load_balancer_arn = aws_lb.teamcity_external_lb.arn
+  port              = "443"
+  protocol          = "HTTPS"
+  ssl_policy        = "ELBSecurityPolicy-TLS13-1-2-2021-06"
+  certificate_arn   = var.alb_certificate_arn
+  default_action {
+    type             = "forward"
+    target_group_arn = aws_lb_target_group.teamcity_target_group.arn
+  }
+  tags = local.tags
+}
+
+
+
+
+# #######################################
+# # TeamCity Aurora Serverless Database #
+# #######################################
+
+# # RDS instance with Aurora serverless engine
+# resource "aws_rds_cluster" "teamcity_db_cluster" {
+#   cluster_identifier = "teamcity-cluster"
+#   engine             = "aurora-postgresql"
+#   engine_mode        = "provisioned"
+#   engine_version     = "16.6"
+#   database_name      = "teamcity"
+#   master_username    = "teamcity"
+#   master_password    = "teamcity2025"
+#   storage_encrypted  = true
+
+#   serverlessv2_scaling_configuration {
+#     max_capacity             = 1.0
+#     min_capacity             = 0.0
+#     seconds_until_auto_pause = 3600
+#   }
+# }
+
+# resource "aws_rds_cluster_instance" "teamcity_db_cluster" {
+#   cluster_identifier = aws_rds_cluster.teamcity_db_cluster.id
+#   instance_class     = "db.serverless"
+#   engine             = aws_rds_cluster.teamcity_db_cluster.engine
+#   engine_version     = aws_rds_cluster.teamcity_db_cluster.engine_version
+#   skip_final_snapshot = true
+# }
+
+# ################
+# # TeamCity EFS #
+# ################
+
+# File system for teamcity
+resource "aws_efs_file_system" "teamcity_efs_file_system" {
+  creation_token   = "${local.name_prefix}-efs-file-system"
+  performance_mode = var.teamcity_efs_performance_mode
+  throughput_mode  = var.teamcity_efs_throughput_mode
+
+  #TODO: Parameterize encryption and customer managed key creation
+  encrypted = false
+
+  lifecycle_policy {
+    transition_to_ia = "AFTER_30_DAYS"
+  }
+
+  lifecycle_policy {
+    transition_to_primary_storage_class = "AFTER_1_ACCESS"
+  }
+  #checkov:skip=CKV_AWS_184: CMK encryption not supported currently
+  tags = merge(local.tags, {
+    Name = "${local.name_prefix}-efs-file-system"
+  })
+}
path: modules/teamcity/main.tf
user: login: github-advanced-security[bot]
body: ## Ensure resource is encrypted by KMS using a customer managed Key (CMK)

Ensure resource is encrypted by KMS using a customer managed Key (CMK)

[Show more details](https://github.com/aws-games/cloud-game-development-toolkit/security/code-scanning/803)
created_at: 2025-02-01T02:09:13Z
author_association: NONE
diff_hunk: @@ -0,0 +1,8 @@
+module "teamcity" {
+  source              = "../../"
+  vpc_id              = aws_vpc.teamcity_vpc.id
+  service_subnets     = aws_subnet.private_subnets[*].id
+  alb_subnets         = aws_subnet.public_subnets[*].id
+  alb_certificate_arn = aws_acm_certificate.teamcity.arn
+
path: modules/teamcity/examples/simple/main.tf
body: nit: remove blank line
created_at: 2025-02-06T18:45:54Z
author_association: NONE
diff_hunk: @@ -0,0 +1,10 @@
+terraform {
+  required_version = ">= 1.0"
path: modules/teamcity/examples/simple/versions.tf
body: I'd like to see us bump this to `required_version = ">= 1.9"` and see if it still deploys.
created_at: 2025-02-06T18:46:42Z
author_association: NONE
diff_hunk: @@ -0,0 +1,10 @@
+terraform {
+  required_version = ">= 1.0"
+
+  required_providers {
+    aws = {
+      source  = "hashicorp/aws"
+      version = "5.82.2"
path: modules/teamcity/examples/simple/versions.tf
body: This also needs to be bumped to latest. I believe its `5.85.0` now. Dependabot will keep this up to date going forward.
created_at: 2025-02-06T18:47:15Z
author_association: NONE
diff_hunk: @@ -0,0 +1,9 @@
+locals {
+  name_prefix = "teamcity"
+  tags = {
+
+  }
+  image = "jetbrains/teamcity-server"
+}
+
+data "aws_region" "current" {}
path: modules/teamcity/local.tf
body: pre-commit is not running correctly for you. Newlines like this should be caught, and Terraform should handle formatting and a README generation.
created_at: 2025-02-06T18:48:49Z
author_association: NONE
diff_hunk: @@ -0,0 +1,10 @@
+terraform {
+  required_version = ">= 1.0"
path: modules/teamcity/versions.tf
body: also bump to 1.9.
created_at: 2025-02-06T18:49:26Z
author_association: NONE
diff_hunk: @@ -0,0 +1,10 @@
+terraform {
+  required_version = ">= 1.0"
+
+  required_providers {
+    aws = {
+      source  = "hashicorp/aws"
+      version = "5.82.2"
path: modules/teamcity/versions.tf
body: Also bump to 5.85.0
created_at: 2025-02-06T18:49:32Z
author_association: NONE
diff_hunk: @@ -0,0 +1,7 @@
+output "external_alb_dns_name" {
+  value = aws_lb.teamcity_external_lb.dns_name
path: modules/teamcity/outputs.tf
body: add a description to this output
created_at: 2025-02-06T18:49:59Z
author_association: NONE
diff_hunk: @@ -0,0 +1,7 @@
+output "external_alb_dns_name" {
+  value = aws_lb.teamcity_external_lb.dns_name
+}
+
+output "external_alb_zone_id" {
+  value = aws_lb.teamcity_external_lb.zone_id
path: modules/teamcity/outputs.tf
body: add a description to this output
created_at: 2025-02-06T18:50:08Z
author_association: NONE
diff_hunk: @@ -0,0 +1,413 @@
+###################################
+# ECS Cluster for TeamCity Server #
+###################################
+
+resource "aws_ecs_cluster" "teamcity_cluster" {
+  name = "${local.name_prefix}-cluster"
+
+  setting {
+    name  = "containerInsights"
+    value = "enabled"
+  }
+
+  tags = local.tags
+}
+
+
+# TeamCity Task Definition
+resource "aws_ecs_task_definition" "teamcity_task_definition" {
+  family                   = var.name
+  requires_compatibilities = ["FARGATE"]
+  network_mode             = "awsvpc"
+  cpu                      = var.container_cpu
+  memory                   = var.container_memory
+
+
+  container_definitions = jsonencode([
+    {
+      name      = var.container_name
+      image     = local.image
+      cpu       = var.container_cpu
+      memory    = var.container_memory
+      essential = true
+      portMappings = [
+        {
+          containerPort = var.container_port
+          hostPort      = var.container_port
+        }
+      ]
+      logConfiguration = {
+        logDriver = "awslogs"
+        options = {
+          awslogs-group         = aws_cloudwatch_log_group.teamcity_log_group.name
+          awslogs-region        = data.aws_region.current.name
+          awslogs-stream-prefix = "[APP]"
+        }
+      },
+      mountPoints = [
+        {
+          sourceVolume  = "teamcity_data",
+          containerPath = "/data/teamcity_server/datadir"
+          readOnly      = false
+        },
+        {
+          sourceVolume  = "teamcity_logs",
+          containerPath = "/data/teamcity_server/logs"
+          readOnly      = false
+        }
+      ]
+    }
+  ])
+
+  tags = {
+    Name = var.name
+  }
+  task_role_arn      = aws_iam_role.teamcity_default_role.arn
+  execution_role_arn = aws_iam_role.teamcity_task_execution_role.arn
+
+  volume {
+    name = "teamcity_data"
+    efs_volume_configuration {
+      file_system_id          = aws_efs_file_system.teamcity_efs_file_system.id
+      transit_encryption      = "ENABLED"
+      transit_encryption_port = 2999
+      authorization_config {
+        access_point_id = aws_efs_access_point.teamcity_efs_data_access_point.id
+        iam             = "ENABLED"
+      }
+    }
+  }
+
+  volume {
+    name = "teamcity_logs"
+    efs_volume_configuration {
+      file_system_id     = aws_efs_file_system.teamcity_efs_file_system.id
+      transit_encryption = "ENABLED"
+      authorization_config {
+        access_point_id = aws_efs_access_point.teamcity_efs_logs_access_point.id
+        iam             = "ENABLED"
+      }
+    }
+  }
+}
+
+
+
+
+# ECS Service
+resource "aws_ecs_service" "teamcity" {
+  name = local.name_prefix
+
+  cluster                = aws_ecs_cluster.teamcity_cluster.name
+  task_definition        = aws_ecs_task_definition.teamcity_task_definition.arn
+  launch_type            = "FARGATE"
+  desired_count          = 1    #TODO: make this configurable
+  force_new_deployment   = true #TODO: make this configurable
+  enable_execute_command = true #TODO: make this configurable
+
+  wait_for_steady_state = true
+
+  network_configuration {
+    subnets         = var.service_subnets
+    security_groups = [aws_security_group.teamcity_service_sg.id]
+  }
+  load_balancer {
+    target_group_arn = aws_lb_target_group.teamcity_target_group.arn
+    container_name   = var.container_name
+    container_port   = var.container_port
+  }
+
+  tags = local.tags
+}
+
+# TeamCity service security group
+resource "aws_security_group" "teamcity_service_sg" {
+  name   = "${local.name_prefix}-service-sg"
+  vpc_id = var.vpc_id
+
+  tags = local.tags
+}
+
+# TeamCity EFS security group
+resource "aws_security_group" "teamcity_efs_sg" {
+  name        = "${local.name_prefix}-efs-sg"
+  description = "TeamCity EFS mount target security group"
+  vpc_id      = var.vpc_id
+
+  tags = local.tags
+}
+
+# Ingress rule for NFS traffic from service to EFS
+resource "aws_vpc_security_group_ingress_rule" "service_efs" {
+  security_group_id            = aws_security_group.teamcity_efs_sg.id
+  referenced_security_group_id = aws_security_group.teamcity_service_sg.id
+  description                  = "Allow inbound access from TeamCity service containers to EFS"
+  ip_protocol                  = "TCP"
+  from_port                    = 2049
+  to_port                      = 2049
+}
+
+# Ingress rule for NFS traffic from EFS to service
+resource "aws_vpc_security_group_ingress_rule" "efs_service" {
+  security_group_id            = aws_security_group.teamcity_service_sg.id
+  referenced_security_group_id = aws_security_group.teamcity_efs_sg.id
+  description                  = "Allow inbound access from EFS to TeamCity service containers"
+  ip_protocol                  = "TCP"
+  from_port                    = 2049
+  to_port                      = 2049
+}
+
+
+
+
+# TeamCity ALB security group
+resource "aws_security_group" "teamcity_alb_sg" {
+  name   = "${local.name_prefix}-alb-sg"
+  vpc_id = var.vpc_id
+
+  tags = local.tags
+}
+
+# Ingress rule for HTTP traffic from ALB to service
+resource "aws_vpc_security_group_ingress_rule" "service_inbound_alb" {
+  security_group_id            = aws_security_group.teamcity_service_sg.id
+  referenced_security_group_id = aws_security_group.teamcity_alb_sg.id
+  from_port                    = var.container_port
+  to_port                      = var.container_port
+  ip_protocol                  = "TCP"
+
+}
+
+# Egress rule for HTTP traffic from ALB to service
+resource "aws_vpc_security_group_egress_rule" "service_outbound_alb" {
+  security_group_id            = aws_security_group.teamcity_alb_sg.id
+  referenced_security_group_id = aws_security_group.teamcity_service_sg.id
+  from_port                    = var.container_port
+  to_port                      = var.container_port
+  ip_protocol                  = "TCP"
+
+}
+
+# Grant TeamCity service access to internet
+resource "aws_vpc_security_group_egress_rule" "internet_outbound" {
+
+  security_group_id = aws_security_group.teamcity_service_sg.id
+  cidr_ipv4         = "0.0.0.0/0"
+  ip_protocol       = "-1"
+}
+
+#############################################
+# IAM Roles for TeamCity Module
+#############################################
+data "aws_iam_policy_document" "ecs_tasks_trust_relationship" {
+  statement {
+    effect  = "Allow"
+    actions = ["sts:AssumeRole"]
+    principals {
+      type        = "Service"
+      identifiers = ["ecs-tasks.amazonaws.com"]
+    }
+  }
+}
+data "aws_iam_policy_document" "teamcity_default_policy" {
+  # ECS
+  statement {
+    sid    = "ECSExec"
+    effect = "Allow"
+    actions = [
+      "ssmmessages:OpenDataChannel",
+      "ssmmessages:OpenControlChannel",
+      "ssmmessages:CreateDataChannel",
+      "ssmmessages:CreateControlChannel",
+    ]
+    resources = [
+      "*"
+    ]
+  }
+}
+resource "aws_iam_policy" "teamcity_default_policy" {
+  name        = "teamcity-default-policy"
+  description = "Policy granting permissions for Unreal Horde."
+  policy      = data.aws_iam_policy_document.teamcity_default_policy.json
+}
+
+resource "aws_iam_role" "teamcity_default_role" {
+  name               = "teamcity-default-role"
+  assume_role_policy = data.aws_iam_policy_document.ecs_tasks_trust_relationship.json
+
+  managed_policy_arns = [
+    aws_iam_policy.teamcity_default_policy.arn
+  ]
+
+  tags = local.tags
+}
+resource "aws_iam_role" "teamcity_task_execution_role" {
+  name = "teamcity-task-execution-role"
+
+  assume_role_policy  = data.aws_iam_policy_document.ecs_tasks_trust_relationship.json
+  managed_policy_arns = ["arn:aws:iam::aws:policy/service-role/AmazonECSTaskExecutionRolePolicy"]
+}
+
+# CloudWatch Logs
+resource "aws_cloudwatch_log_group" "teamcity_log_group" {
+  #checkov:skip=CKV_AWS_158: KMS Encryption disabled by default
+  name              = "${local.name_prefix}-log-group"
+  retention_in_days = var.teamcity_cloudwatch_log_retention_in_days
+  tags              = local.tags
+}
+
+
+# Load Balancer for TeamCity Service
+resource "aws_lb" "teamcity_external_lb" {
+  name            = "${local.name_prefix}-lb"
+  security_groups = [aws_security_group.teamcity_alb_sg.id]
+
+  load_balancer_type         = "application"
+  internal                   = false
+  subnets                    = var.alb_subnets
+  drop_invalid_header_fields = true
+  tags                       = local.tags
+}
+
+# TeamCity target group for ALB
+resource "aws_lb_target_group" "teamcity_target_group" {
+  name        = "${local.name_prefix}-tg"
+  port        = var.container_port
+  protocol    = "HTTP"
+  vpc_id      = var.vpc_id
+  target_type = "ip"
+
+  # health_check {
+  #   path                = "/"
+  #   interval            = 30
+  #   timeout             = 5
+  #   healthy_threshold   = 2
+  #   unhealthy_threshold = 2
+  # }
path: modules/teamcity/main.tf
body: @cwwalb this is where we want to make adjustments to the healthcheck for TeamCity. Let's keep up our conversation here.
created_at: 2025-02-06T20:02:25Z
author_association: NONE
diff_hunk: @@ -0,0 +1,413 @@
+###################################
+# ECS Cluster for TeamCity Server #
+###################################
+
+resource "aws_ecs_cluster" "teamcity_cluster" {
+  name = "${local.name_prefix}-cluster"
+
+  setting {
+    name  = "containerInsights"
+    value = "enabled"
+  }
+
+  tags = local.tags
+}
+
+
+# TeamCity Task Definition
+resource "aws_ecs_task_definition" "teamcity_task_definition" {
+  family                   = var.name
+  requires_compatibilities = ["FARGATE"]
+  network_mode             = "awsvpc"
+  cpu                      = var.container_cpu
+  memory                   = var.container_memory
+
+
+  container_definitions = jsonencode([
+    {
+      name      = var.container_name
+      image     = local.image
+      cpu       = var.container_cpu
+      memory    = var.container_memory
+      essential = true
+      portMappings = [
+        {
+          containerPort = var.container_port
+          hostPort      = var.container_port
+        }
+      ]
+      logConfiguration = {
+        logDriver = "awslogs"
+        options = {
+          awslogs-group         = aws_cloudwatch_log_group.teamcity_log_group.name
+          awslogs-region        = data.aws_region.current.name
+          awslogs-stream-prefix = "[APP]"
+        }
+      },
+      mountPoints = [
+        {
+          sourceVolume  = "teamcity_data",
+          containerPath = "/data/teamcity_server/datadir"
+          readOnly      = false
+        },
+        {
+          sourceVolume  = "teamcity_logs",
+          containerPath = "/data/teamcity_server/logs"
+          readOnly      = false
+        }
+      ]
+    }
+  ])
+
+  tags = {
+    Name = var.name
+  }
+  task_role_arn      = aws_iam_role.teamcity_default_role.arn
+  execution_role_arn = aws_iam_role.teamcity_task_execution_role.arn
+
+  volume {
+    name = "teamcity_data"
+    efs_volume_configuration {
+      file_system_id          = aws_efs_file_system.teamcity_efs_file_system.id
+      transit_encryption      = "ENABLED"
+      transit_encryption_port = 2999
+      authorization_config {
+        access_point_id = aws_efs_access_point.teamcity_efs_data_access_point.id
+        iam             = "ENABLED"
+      }
+    }
+  }
+
+  volume {
+    name = "teamcity_logs"
+    efs_volume_configuration {
+      file_system_id     = aws_efs_file_system.teamcity_efs_file_system.id
+      transit_encryption = "ENABLED"
+      authorization_config {
+        access_point_id = aws_efs_access_point.teamcity_efs_logs_access_point.id
+        iam             = "ENABLED"
+      }
+    }
+  }
+}
+
+
+
+
+# ECS Service
+resource "aws_ecs_service" "teamcity" {
+  name = local.name_prefix
+
+  cluster                = aws_ecs_cluster.teamcity_cluster.name
+  task_definition        = aws_ecs_task_definition.teamcity_task_definition.arn
+  launch_type            = "FARGATE"
+  desired_count          = 1    #TODO: make this configurable
+  force_new_deployment   = true #TODO: make this configurable
+  enable_execute_command = true #TODO: make this configurable
+
+  wait_for_steady_state = true
+
+  network_configuration {
+    subnets         = var.service_subnets
+    security_groups = [aws_security_group.teamcity_service_sg.id]
+  }
+  load_balancer {
+    target_group_arn = aws_lb_target_group.teamcity_target_group.arn
+    container_name   = var.container_name
+    container_port   = var.container_port
+  }
+
+  tags = local.tags
+}
+
+# TeamCity service security group
+resource "aws_security_group" "teamcity_service_sg" {
+  name   = "${local.name_prefix}-service-sg"
+  vpc_id = var.vpc_id
+
+  tags = local.tags
+}
+
+# TeamCity EFS security group
+resource "aws_security_group" "teamcity_efs_sg" {
+  name        = "${local.name_prefix}-efs-sg"
+  description = "TeamCity EFS mount target security group"
+  vpc_id      = var.vpc_id
+
+  tags = local.tags
+}
+
+# Ingress rule for NFS traffic from service to EFS
+resource "aws_vpc_security_group_ingress_rule" "service_efs" {
+  security_group_id            = aws_security_group.teamcity_efs_sg.id
+  referenced_security_group_id = aws_security_group.teamcity_service_sg.id
+  description                  = "Allow inbound access from TeamCity service containers to EFS"
+  ip_protocol                  = "TCP"
+  from_port                    = 2049
+  to_port                      = 2049
+}
+
+# Ingress rule for NFS traffic from EFS to service
+resource "aws_vpc_security_group_ingress_rule" "efs_service" {
+  security_group_id            = aws_security_group.teamcity_service_sg.id
+  referenced_security_group_id = aws_security_group.teamcity_efs_sg.id
+  description                  = "Allow inbound access from EFS to TeamCity service containers"
+  ip_protocol                  = "TCP"
+  from_port                    = 2049
+  to_port                      = 2049
+}
+
+
+
+
+# TeamCity ALB security group
+resource "aws_security_group" "teamcity_alb_sg" {
+  name   = "${local.name_prefix}-alb-sg"
+  vpc_id = var.vpc_id
+
+  tags = local.tags
+}
+
+# Ingress rule for HTTP traffic from ALB to service
+resource "aws_vpc_security_group_ingress_rule" "service_inbound_alb" {
+  security_group_id            = aws_security_group.teamcity_service_sg.id
+  referenced_security_group_id = aws_security_group.teamcity_alb_sg.id
+  from_port                    = var.container_port
+  to_port                      = var.container_port
+  ip_protocol                  = "TCP"
+
+}
+
+# Egress rule for HTTP traffic from ALB to service
+resource "aws_vpc_security_group_egress_rule" "service_outbound_alb" {
+  security_group_id            = aws_security_group.teamcity_alb_sg.id
+  referenced_security_group_id = aws_security_group.teamcity_service_sg.id
+  from_port                    = var.container_port
+  to_port                      = var.container_port
+  ip_protocol                  = "TCP"
+
+}
+
+# Grant TeamCity service access to internet
+resource "aws_vpc_security_group_egress_rule" "internet_outbound" {
+
+  security_group_id = aws_security_group.teamcity_service_sg.id
+  cidr_ipv4         = "0.0.0.0/0"
+  ip_protocol       = "-1"
+}
+
+#############################################
+# IAM Roles for TeamCity Module
+#############################################
+data "aws_iam_policy_document" "ecs_tasks_trust_relationship" {
+  statement {
+    effect  = "Allow"
+    actions = ["sts:AssumeRole"]
+    principals {
+      type        = "Service"
+      identifiers = ["ecs-tasks.amazonaws.com"]
+    }
+  }
+}
+data "aws_iam_policy_document" "teamcity_default_policy" {
+  # ECS
+  statement {
+    sid    = "ECSExec"
+    effect = "Allow"
+    actions = [
+      "ssmmessages:OpenDataChannel",
+      "ssmmessages:OpenControlChannel",
+      "ssmmessages:CreateDataChannel",
+      "ssmmessages:CreateControlChannel",
+    ]
+    resources = [
+      "*"
+    ]
+  }
+}
+resource "aws_iam_policy" "teamcity_default_policy" {
+  name        = "teamcity-default-policy"
+  description = "Policy granting permissions for Unreal Horde."
+  policy      = data.aws_iam_policy_document.teamcity_default_policy.json
+}
+
+resource "aws_iam_role" "teamcity_default_role" {
+  name               = "teamcity-default-role"
+  assume_role_policy = data.aws_iam_policy_document.ecs_tasks_trust_relationship.json
+
+  managed_policy_arns = [
+    aws_iam_policy.teamcity_default_policy.arn
+  ]
+
+  tags = local.tags
+}
+resource "aws_iam_role" "teamcity_task_execution_role" {
+  name = "teamcity-task-execution-role"
+
+  assume_role_policy  = data.aws_iam_policy_document.ecs_tasks_trust_relationship.json
+  managed_policy_arns = ["arn:aws:iam::aws:policy/service-role/AmazonECSTaskExecutionRolePolicy"]
+}
+
+# CloudWatch Logs
+resource "aws_cloudwatch_log_group" "teamcity_log_group" {
+  #checkov:skip=CKV_AWS_158: KMS Encryption disabled by default
+  name              = "${local.name_prefix}-log-group"
+  retention_in_days = var.teamcity_cloudwatch_log_retention_in_days
+  tags              = local.tags
+}
+
+
+# Load Balancer for TeamCity Service
+resource "aws_lb" "teamcity_external_lb" {
+  name            = "${local.name_prefix}-lb"
+  security_groups = [aws_security_group.teamcity_alb_sg.id]
+
+  load_balancer_type         = "application"
+  internal                   = false
+  subnets                    = var.alb_subnets
+  drop_invalid_header_fields = true
+  tags                       = local.tags
+}
+
+# TeamCity target group for ALB
+resource "aws_lb_target_group" "teamcity_target_group" {
+  name        = "${local.name_prefix}-tg"
+  port        = var.container_port
+  protocol    = "HTTP"
+  vpc_id      = var.vpc_id
+  target_type = "ip"
+
+  # health_check {
+  #   path                = "/"
+  #   interval            = 30
+  #   timeout             = 5
+  #   healthy_threshold   = 2
+  #   unhealthy_threshold = 2
+  # }
path: modules/teamcity/main.tf
body: I think `/login` should be used instead of the root. The interval at 30 seconds and the unhealthy threshold should give TC enough time to boot to completion.
created_at: 2025-02-06T20:05:23Z
author_association: NONE
diff_hunk: @@ -0,0 +1,413 @@
+###################################
+# ECS Cluster for TeamCity Server #
+###################################
+
+resource "aws_ecs_cluster" "teamcity_cluster" {
+  name = "${local.name_prefix}-cluster"
+
+  setting {
+    name  = "containerInsights"
+    value = "enabled"
+  }
+
+  tags = local.tags
+}
+
+
+# TeamCity Task Definition
+resource "aws_ecs_task_definition" "teamcity_task_definition" {
+  family                   = var.name
+  requires_compatibilities = ["FARGATE"]
+  network_mode             = "awsvpc"
+  cpu                      = var.container_cpu
+  memory                   = var.container_memory
+
+
+  container_definitions = jsonencode([
+    {
+      name      = var.container_name
+      image     = local.image
+      cpu       = var.container_cpu
+      memory    = var.container_memory
+      essential = true
+      portMappings = [
+        {
+          containerPort = var.container_port
+          hostPort      = var.container_port
+        }
+      ]
+      logConfiguration = {
+        logDriver = "awslogs"
+        options = {
+          awslogs-group         = aws_cloudwatch_log_group.teamcity_log_group.name
+          awslogs-region        = data.aws_region.current.name
+          awslogs-stream-prefix = "[APP]"
+        }
+      },
+      mountPoints = [
+        {
+          sourceVolume  = "teamcity_data",
+          containerPath = "/data/teamcity_server/datadir"
+          readOnly      = false
+        },
+        {
+          sourceVolume  = "teamcity_logs",
+          containerPath = "/data/teamcity_server/logs"
+          readOnly      = false
+        }
+      ]
+    }
+  ])
+
+  tags = {
+    Name = var.name
+  }
+  task_role_arn      = aws_iam_role.teamcity_default_role.arn
+  execution_role_arn = aws_iam_role.teamcity_task_execution_role.arn
+
+  volume {
+    name = "teamcity_data"
+    efs_volume_configuration {
+      file_system_id          = aws_efs_file_system.teamcity_efs_file_system.id
+      transit_encryption      = "ENABLED"
+      transit_encryption_port = 2999
+      authorization_config {
+        access_point_id = aws_efs_access_point.teamcity_efs_data_access_point.id
+        iam             = "ENABLED"
+      }
+    }
+  }
+
+  volume {
+    name = "teamcity_logs"
+    efs_volume_configuration {
+      file_system_id     = aws_efs_file_system.teamcity_efs_file_system.id
+      transit_encryption = "ENABLED"
+      authorization_config {
+        access_point_id = aws_efs_access_point.teamcity_efs_logs_access_point.id
+        iam             = "ENABLED"
+      }
+    }
+  }
+}
+
+
+
+
+# ECS Service
+resource "aws_ecs_service" "teamcity" {
+  name = local.name_prefix
+
+  cluster                = aws_ecs_cluster.teamcity_cluster.name
+  task_definition        = aws_ecs_task_definition.teamcity_task_definition.arn
+  launch_type            = "FARGATE"
+  desired_count          = 1    #TODO: make this configurable
+  force_new_deployment   = true #TODO: make this configurable
+  enable_execute_command = true #TODO: make this configurable
+
+  wait_for_steady_state = true
+
+  network_configuration {
+    subnets         = var.service_subnets
+    security_groups = [aws_security_group.teamcity_service_sg.id]
+  }
+  load_balancer {
+    target_group_arn = aws_lb_target_group.teamcity_target_group.arn
+    container_name   = var.container_name
+    container_port   = var.container_port
+  }
+
+  tags = local.tags
+}
+
+# TeamCity service security group
+resource "aws_security_group" "teamcity_service_sg" {
+  name   = "${local.name_prefix}-service-sg"
+  vpc_id = var.vpc_id
+
+  tags = local.tags
+}
+
+# TeamCity EFS security group
+resource "aws_security_group" "teamcity_efs_sg" {
+  name        = "${local.name_prefix}-efs-sg"
+  description = "TeamCity EFS mount target security group"
+  vpc_id      = var.vpc_id
+
+  tags = local.tags
+}
+
+# Ingress rule for NFS traffic from service to EFS
+resource "aws_vpc_security_group_ingress_rule" "service_efs" {
+  security_group_id            = aws_security_group.teamcity_efs_sg.id
+  referenced_security_group_id = aws_security_group.teamcity_service_sg.id
+  description                  = "Allow inbound access from TeamCity service containers to EFS"
+  ip_protocol                  = "TCP"
+  from_port                    = 2049
+  to_port                      = 2049
+}
+
+# Ingress rule for NFS traffic from EFS to service
+resource "aws_vpc_security_group_ingress_rule" "efs_service" {
+  security_group_id            = aws_security_group.teamcity_service_sg.id
+  referenced_security_group_id = aws_security_group.teamcity_efs_sg.id
+  description                  = "Allow inbound access from EFS to TeamCity service containers"
+  ip_protocol                  = "TCP"
+  from_port                    = 2049
+  to_port                      = 2049
+}
+
+
+
+
+# TeamCity ALB security group
+resource "aws_security_group" "teamcity_alb_sg" {
+  name   = "${local.name_prefix}-alb-sg"
+  vpc_id = var.vpc_id
+
+  tags = local.tags
+}
+
+# Ingress rule for HTTP traffic from ALB to service
+resource "aws_vpc_security_group_ingress_rule" "service_inbound_alb" {
+  security_group_id            = aws_security_group.teamcity_service_sg.id
+  referenced_security_group_id = aws_security_group.teamcity_alb_sg.id
+  from_port                    = var.container_port
+  to_port                      = var.container_port
+  ip_protocol                  = "TCP"
+
+}
+
+# Egress rule for HTTP traffic from ALB to service
+resource "aws_vpc_security_group_egress_rule" "service_outbound_alb" {
+  security_group_id            = aws_security_group.teamcity_alb_sg.id
+  referenced_security_group_id = aws_security_group.teamcity_service_sg.id
+  from_port                    = var.container_port
+  to_port                      = var.container_port
+  ip_protocol                  = "TCP"
+
+}
+
+# Grant TeamCity service access to internet
+resource "aws_vpc_security_group_egress_rule" "internet_outbound" {
+
+  security_group_id = aws_security_group.teamcity_service_sg.id
+  cidr_ipv4         = "0.0.0.0/0"
+  ip_protocol       = "-1"
+}
+
+#############################################
+# IAM Roles for TeamCity Module
+#############################################
+data "aws_iam_policy_document" "ecs_tasks_trust_relationship" {
+  statement {
+    effect  = "Allow"
+    actions = ["sts:AssumeRole"]
+    principals {
+      type        = "Service"
+      identifiers = ["ecs-tasks.amazonaws.com"]
+    }
+  }
+}
+data "aws_iam_policy_document" "teamcity_default_policy" {
+  # ECS
+  statement {
+    sid    = "ECSExec"
+    effect = "Allow"
+    actions = [
+      "ssmmessages:OpenDataChannel",
+      "ssmmessages:OpenControlChannel",
+      "ssmmessages:CreateDataChannel",
+      "ssmmessages:CreateControlChannel",
+    ]
+    resources = [
+      "*"
+    ]
+  }
+}
+resource "aws_iam_policy" "teamcity_default_policy" {
+  name        = "teamcity-default-policy"
+  description = "Policy granting permissions for Unreal Horde."
+  policy      = data.aws_iam_policy_document.teamcity_default_policy.json
+}
+
+resource "aws_iam_role" "teamcity_default_role" {
+  name               = "teamcity-default-role"
+  assume_role_policy = data.aws_iam_policy_document.ecs_tasks_trust_relationship.json
+
+  managed_policy_arns = [
+    aws_iam_policy.teamcity_default_policy.arn
+  ]
+
+  tags = local.tags
+}
+resource "aws_iam_role" "teamcity_task_execution_role" {
+  name = "teamcity-task-execution-role"
+
+  assume_role_policy  = data.aws_iam_policy_document.ecs_tasks_trust_relationship.json
+  managed_policy_arns = ["arn:aws:iam::aws:policy/service-role/AmazonECSTaskExecutionRolePolicy"]
+}
+
+# CloudWatch Logs
+resource "aws_cloudwatch_log_group" "teamcity_log_group" {
+  #checkov:skip=CKV_AWS_158: KMS Encryption disabled by default
+  name              = "${local.name_prefix}-log-group"
+  retention_in_days = var.teamcity_cloudwatch_log_retention_in_days
+  tags              = local.tags
+}
+
+
+# Load Balancer for TeamCity Service
+resource "aws_lb" "teamcity_external_lb" {
+  name            = "${local.name_prefix}-lb"
+  security_groups = [aws_security_group.teamcity_alb_sg.id]
+
+  load_balancer_type         = "application"
+  internal                   = false
+  subnets                    = var.alb_subnets
+  drop_invalid_header_fields = true
+  tags                       = local.tags
+}
+
+# TeamCity target group for ALB
+resource "aws_lb_target_group" "teamcity_target_group" {
+  name        = "${local.name_prefix}-tg"
+  port        = var.container_port
+  protocol    = "HTTP"
+  vpc_id      = var.vpc_id
+  target_type = "ip"
+
+  # health_check {
+  #   path                = "/"
+  #   interval            = 30
+  #   timeout             = 5
+  #   healthy_threshold   = 2
+  #   unhealthy_threshold = 2
+  # }
path: modules/teamcity/main.tf
body: Wait! [TeamCity documentation points at `/healthCheck/healthy` and `/healthCheck/ready`](https://www.jetbrains.com/help/teamcity/teamcity-monitoring-and-diagnostics.html)

Let's use these :)
created_at: 2025-02-06T20:28:17Z
author_association: NONE
diff_hunk: @@ -0,0 +1,413 @@
+###################################
+# ECS Cluster for TeamCity Server #
+###################################
+
+resource "aws_ecs_cluster" "teamcity_cluster" {
+  name = "${local.name_prefix}-cluster"
+
+  setting {
+    name  = "containerInsights"
+    value = "enabled"
+  }
+
+  tags = local.tags
+}
+
+
+# TeamCity Task Definition
+resource "aws_ecs_task_definition" "teamcity_task_definition" {
+  family                   = var.name
+  requires_compatibilities = ["FARGATE"]
+  network_mode             = "awsvpc"
+  cpu                      = var.container_cpu
+  memory                   = var.container_memory
+
+
+  container_definitions = jsonencode([
+    {
+      name      = var.container_name
+      image     = local.image
+      cpu       = var.container_cpu
+      memory    = var.container_memory
+      essential = true
+      portMappings = [
+        {
+          containerPort = var.container_port
+          hostPort      = var.container_port
+        }
+      ]
+      logConfiguration = {
+        logDriver = "awslogs"
+        options = {
+          awslogs-group         = aws_cloudwatch_log_group.teamcity_log_group.name
+          awslogs-region        = data.aws_region.current.name
+          awslogs-stream-prefix = "[APP]"
+        }
+      },
+      mountPoints = [
+        {
+          sourceVolume  = "teamcity_data",
+          containerPath = "/data/teamcity_server/datadir"
+          readOnly      = false
+        },
+        {
+          sourceVolume  = "teamcity_logs",
+          containerPath = "/data/teamcity_server/logs"
+          readOnly      = false
+        }
+      ]
+    }
+  ])
+
+  tags = {
+    Name = var.name
+  }
+  task_role_arn      = aws_iam_role.teamcity_default_role.arn
+  execution_role_arn = aws_iam_role.teamcity_task_execution_role.arn
+
+  volume {
+    name = "teamcity_data"
+    efs_volume_configuration {
+      file_system_id          = aws_efs_file_system.teamcity_efs_file_system.id
+      transit_encryption      = "ENABLED"
+      transit_encryption_port = 2999
+      authorization_config {
+        access_point_id = aws_efs_access_point.teamcity_efs_data_access_point.id
+        iam             = "ENABLED"
+      }
+    }
+  }
+
+  volume {
+    name = "teamcity_logs"
+    efs_volume_configuration {
+      file_system_id     = aws_efs_file_system.teamcity_efs_file_system.id
+      transit_encryption = "ENABLED"
+      authorization_config {
+        access_point_id = aws_efs_access_point.teamcity_efs_logs_access_point.id
+        iam             = "ENABLED"
+      }
+    }
+  }
+}
+
+
+
+
+# ECS Service
+resource "aws_ecs_service" "teamcity" {
+  name = local.name_prefix
+
+  cluster                = aws_ecs_cluster.teamcity_cluster.name
+  task_definition        = aws_ecs_task_definition.teamcity_task_definition.arn
+  launch_type            = "FARGATE"
+  desired_count          = 1    #TODO: make this configurable
+  force_new_deployment   = true #TODO: make this configurable
+  enable_execute_command = true #TODO: make this configurable
+
+  wait_for_steady_state = true
+
+  network_configuration {
+    subnets         = var.service_subnets
+    security_groups = [aws_security_group.teamcity_service_sg.id]
+  }
+  load_balancer {
+    target_group_arn = aws_lb_target_group.teamcity_target_group.arn
+    container_name   = var.container_name
+    container_port   = var.container_port
+  }
+
+  tags = local.tags
+}
+
+# TeamCity service security group
+resource "aws_security_group" "teamcity_service_sg" {
+  name   = "${local.name_prefix}-service-sg"
+  vpc_id = var.vpc_id
+
+  tags = local.tags
+}
+
+# TeamCity EFS security group
+resource "aws_security_group" "teamcity_efs_sg" {
+  name        = "${local.name_prefix}-efs-sg"
+  description = "TeamCity EFS mount target security group"
+  vpc_id      = var.vpc_id
+
+  tags = local.tags
+}
+
+# Ingress rule for NFS traffic from service to EFS
+resource "aws_vpc_security_group_ingress_rule" "service_efs" {
+  security_group_id            = aws_security_group.teamcity_efs_sg.id
+  referenced_security_group_id = aws_security_group.teamcity_service_sg.id
+  description                  = "Allow inbound access from TeamCity service containers to EFS"
+  ip_protocol                  = "TCP"
+  from_port                    = 2049
+  to_port                      = 2049
+}
+
+# Ingress rule for NFS traffic from EFS to service
+resource "aws_vpc_security_group_ingress_rule" "efs_service" {
+  security_group_id            = aws_security_group.teamcity_service_sg.id
+  referenced_security_group_id = aws_security_group.teamcity_efs_sg.id
+  description                  = "Allow inbound access from EFS to TeamCity service containers"
+  ip_protocol                  = "TCP"
+  from_port                    = 2049
+  to_port                      = 2049
+}
+
+
+
+
+# TeamCity ALB security group
+resource "aws_security_group" "teamcity_alb_sg" {
+  name   = "${local.name_prefix}-alb-sg"
+  vpc_id = var.vpc_id
+
+  tags = local.tags
+}
+
+# Ingress rule for HTTP traffic from ALB to service
+resource "aws_vpc_security_group_ingress_rule" "service_inbound_alb" {
+  security_group_id            = aws_security_group.teamcity_service_sg.id
+  referenced_security_group_id = aws_security_group.teamcity_alb_sg.id
+  from_port                    = var.container_port
+  to_port                      = var.container_port
+  ip_protocol                  = "TCP"
+
+}
+
+# Egress rule for HTTP traffic from ALB to service
+resource "aws_vpc_security_group_egress_rule" "service_outbound_alb" {
+  security_group_id            = aws_security_group.teamcity_alb_sg.id
+  referenced_security_group_id = aws_security_group.teamcity_service_sg.id
+  from_port                    = var.container_port
+  to_port                      = var.container_port
+  ip_protocol                  = "TCP"
+
+}
+
+# Grant TeamCity service access to internet
+resource "aws_vpc_security_group_egress_rule" "internet_outbound" {
+
+  security_group_id = aws_security_group.teamcity_service_sg.id
+  cidr_ipv4         = "0.0.0.0/0"
+  ip_protocol       = "-1"
+}
+
+#############################################
+# IAM Roles for TeamCity Module
+#############################################
+data "aws_iam_policy_document" "ecs_tasks_trust_relationship" {
+  statement {
+    effect  = "Allow"
+    actions = ["sts:AssumeRole"]
+    principals {
+      type        = "Service"
+      identifiers = ["ecs-tasks.amazonaws.com"]
+    }
+  }
+}
+data "aws_iam_policy_document" "teamcity_default_policy" {
+  # ECS
+  statement {
+    sid    = "ECSExec"
+    effect = "Allow"
+    actions = [
+      "ssmmessages:OpenDataChannel",
+      "ssmmessages:OpenControlChannel",
+      "ssmmessages:CreateDataChannel",
+      "ssmmessages:CreateControlChannel",
+    ]
+    resources = [
+      "*"
+    ]
+  }
+}
+resource "aws_iam_policy" "teamcity_default_policy" {
+  name        = "teamcity-default-policy"
+  description = "Policy granting permissions for Unreal Horde."
+  policy      = data.aws_iam_policy_document.teamcity_default_policy.json
+}
+
+resource "aws_iam_role" "teamcity_default_role" {
+  name               = "teamcity-default-role"
+  assume_role_policy = data.aws_iam_policy_document.ecs_tasks_trust_relationship.json
+
+  managed_policy_arns = [
+    aws_iam_policy.teamcity_default_policy.arn
+  ]
+
+  tags = local.tags
+}
+resource "aws_iam_role" "teamcity_task_execution_role" {
+  name = "teamcity-task-execution-role"
+
+  assume_role_policy  = data.aws_iam_policy_document.ecs_tasks_trust_relationship.json
+  managed_policy_arns = ["arn:aws:iam::aws:policy/service-role/AmazonECSTaskExecutionRolePolicy"]
+}
+
+# CloudWatch Logs
+resource "aws_cloudwatch_log_group" "teamcity_log_group" {
+  #checkov:skip=CKV_AWS_158: KMS Encryption disabled by default
+  name              = "${local.name_prefix}-log-group"
+  retention_in_days = var.teamcity_cloudwatch_log_retention_in_days
+  tags              = local.tags
+}
+
+
+# Load Balancer for TeamCity Service
+resource "aws_lb" "teamcity_external_lb" {
+  name            = "${local.name_prefix}-lb"
+  security_groups = [aws_security_group.teamcity_alb_sg.id]
+
+  load_balancer_type         = "application"
+  internal                   = false
+  subnets                    = var.alb_subnets
+  drop_invalid_header_fields = true
+  tags                       = local.tags
+}
+
+# TeamCity target group for ALB
+resource "aws_lb_target_group" "teamcity_target_group" {
+  name        = "${local.name_prefix}-tg"
+  port        = var.container_port
+  protocol    = "HTTP"
+  vpc_id      = var.vpc_id
+  target_type = "ip"
+
+  # health_check {
+  #   path                = "/"
+  #   interval            = 30
+  #   timeout             = 5
+  #   healthy_threshold   = 2
+  #   unhealthy_threshold = 2
+  # }
path: modules/teamcity/main.tf
user: login: cwwalb
body: Do you want 2 different health checks? One for if the server is still in configuration mode and one when its ready for requests? referring to the /healthy vs /ready
created_at: 2025-02-06T20:45:59Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,9 @@
+locals {
+  name_prefix = "teamcity"
+  tags = {
+
+  }
+  image = "jetbrains/teamcity-server"
+}
+
+data "aws_region" "current" {}
path: modules/teamcity/local.tf
user: login: cwwalb
body: How do I fix that?
created_at: 2025-02-06T20:53:09Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,492 @@
+###################################
+# ECS Cluster for TeamCity Server #
+###################################
+
+resource "aws_ecs_cluster" "teamcity_cluster" {
+  name = "${local.name_prefix}-cluster"
+
+  setting {
+    name  = "containerInsights"
+    value = "enabled"
+  }
+
+  tags = local.tags
+}
+
+# TeamCity Task Definition
+resource "aws_ecs_task_definition" "teamcity_task_definition" {
+  family                   = var.name
+  requires_compatibilities = ["FARGATE"]
+  network_mode             = "awsvpc"
+  cpu                      = var.container_cpu
+  memory                   = var.container_memory
+
+  container_definitions = jsonencode([
+    {
+      name      = var.container_name
+      image     = local.image
+      cpu       = var.container_cpu
+      memory    = var.container_memory
+      essential = true
+      portMappings = [
+        {
+          containerPort = var.container_port
+          hostPort      = var.container_port
+        }
+      ]
+      logConfiguration = {
+        logDriver = "awslogs"
+        options = {
+          awslogs-group         = aws_cloudwatch_log_group.teamcity_log_group.name
+          awslogs-region        = data.aws_region.current.name
+          awslogs-stream-prefix = "[APP]"
+        }
+      }
+      mountPoints = [
+        {
+          sourceVolume  = "teamcity_data",
+          containerPath = "/data/teamcity_server/datadir"
+          readOnly      = false
+        },
+        {
+          sourceVolume  = "teamcity_logs",
+          containerPath = "/data/teamcity_server/logs"
+          readOnly      = false
+        }
+      ]
+      # Set environment variables for database connection string
+      environment = [
+        {
+          name  = "TEAMCITY_DB_URL"
+          value = local.database_connection_string
+        },
+        {
+          name  = "TEAMCITY_DB_USER"
+          value = local.database_user
+        },
+        {
+          name  = "TEAMCITY_DB_PASSWORD"
+          value = local.database_password
+        },
+        {
+          name  = "TEAMCITY_LOGS_PATH"
+          value = "/data/teamcity_server/logs"
+        }
+      ]
+    }
+  ])
+  tags = {
+    Name = var.name
+  }
+  task_role_arn      = aws_iam_role.teamcity_default_role.arn
+  execution_role_arn = aws_iam_role.teamcity_task_execution_role.arn
+
+  volume {
+    name = "teamcity_data"
+    efs_volume_configuration {
+      file_system_id          = aws_efs_file_system.teamcity_efs_file_system.id
+      transit_encryption      = "ENABLED"
+      transit_encryption_port = 2999
+      authorization_config {
+        access_point_id = aws_efs_access_point.teamcity_efs_data_access_point.id
+        iam             = "ENABLED"
+      }
+    }
+  }
+
+  volume {
+    name = "teamcity_logs"
+    efs_volume_configuration {
+      file_system_id          = aws_efs_file_system.teamcity_efs_file_system.id
+      transit_encryption      = "ENABLED"
+      transit_encryption_port = 2998
+      authorization_config {
+        access_point_id = aws_efs_access_point.teamcity_efs_logs_access_point.id
+        iam             = "ENABLED"
+      }
+    }
+  }
+}
+
+
+
+
+# ECS Service
+resource "aws_ecs_service" "teamcity" {
+  name = local.name_prefix
+
+  cluster                = aws_ecs_cluster.teamcity_cluster.name
+  task_definition        = aws_ecs_task_definition.teamcity_task_definition.arn
+  launch_type            = "FARGATE"
+  desired_count          = 1    #TODO: make this configurable
+  force_new_deployment   = true #TODO: make this configurable
+  enable_execute_command = true #TODO: make this configurable
+
+  wait_for_steady_state = true
+
+  network_configuration {
+    subnets         = var.service_subnets
+    security_groups = [aws_security_group.teamcity_service_sg.id]
+  }
+  load_balancer {
+    target_group_arn = aws_lb_target_group.teamcity_target_group.arn
+    container_name   = var.container_name
+    container_port   = var.container_port
+  }
+
+  tags = local.tags
+}
+
+# TeamCity service security group
+resource "aws_security_group" "teamcity_service_sg" {
+  name   = "${local.name_prefix}-service-sg"
+  vpc_id = var.vpc_id
+
+  tags = local.tags
+}
+
+# TeamCity EFS security group
+resource "aws_security_group" "teamcity_efs_sg" {
+  name        = "${local.name_prefix}-efs-sg"
+  description = "TeamCity EFS mount target security group"
+  vpc_id      = var.vpc_id
+
+  tags = local.tags
+}
+
+# Ingress rule for NFS traffic from service to EFS
+resource "aws_vpc_security_group_ingress_rule" "service_efs" {
+  security_group_id            = aws_security_group.teamcity_efs_sg.id
+  referenced_security_group_id = aws_security_group.teamcity_service_sg.id
+  description                  = "Allow inbound access from TeamCity service containers to EFS"
+  ip_protocol                  = "TCP"
+  from_port                    = 2049
+  to_port                      = 2049
+}
+
+# Ingress rule for NFS traffic from EFS to service
+resource "aws_vpc_security_group_ingress_rule" "efs_service" {
+  security_group_id            = aws_security_group.teamcity_service_sg.id
+  referenced_security_group_id = aws_security_group.teamcity_efs_sg.id
+  description                  = "Allow inbound access from EFS to TeamCity service containers"
+  ip_protocol                  = "TCP"
+  from_port                    = 2049
+  to_port                      = 2049
+}
+
+# TeamCity Aurora Serverless PostgreSQL security group
+resource "aws_security_group" "teamcity_db_sg" {
+  name        = "${local.name_prefix}-db-sg"
+  description = "TeamCity DB security group"
+  vpc_id      = var.vpc_id
+
+  tags = local.tags
+}
+
+# Ingress rule for PostgreSQL from service to database cluster
+resource "aws_vpc_security_group_ingress_rule" "service_db" {
+  security_group_id            = aws_security_group.teamcity_db_sg.id
+  referenced_security_group_id = aws_security_group.teamcity_service_sg.id
+  description                  = "Allow inbound access from TeamCity service containers to DB"
+  ip_protocol                  = "TCP"
+  from_port                    = 5432
+  to_port                      = 5432
+}
+
+# Ingress rule for PostgreSQL from database cluster to service
+resource "aws_vpc_security_group_ingress_rule" "db_service" {
+  security_group_id            = aws_security_group.teamcity_service_sg.id
+  referenced_security_group_id = aws_security_group.teamcity_db_sg.id
+  description                  = "Allow inbound access from DB to TeamCity service containers"
+  ip_protocol                  = "TCP"
+  from_port                    = 5432
+  to_port                      = 5432
+}
+
+# Egress rule for database to let all traffic out
+resource "aws_vpc_security_group_egress_rule" "db_outbound" {
+
+  security_group_id = aws_security_group.teamcity_db_sg.id
+  cidr_ipv4         = "0.0.0.0/0"
+  ip_protocol       = "-1"
+}
path: modules/teamcity/main.tf
user: login: github-advanced-security[bot]
body: ## Ensure every security group and rule has a description

Ensure every security group and rule has a description

[Show more details](https://github.com/aws-games/cloud-game-development-toolkit/security/code-scanning/805)
created_at: 2025-02-06T21:14:48Z
author_association: NONE
diff_hunk: @@ -0,0 +1,492 @@
+###################################
+# ECS Cluster for TeamCity Server #
+###################################
+
+resource "aws_ecs_cluster" "teamcity_cluster" {
+  name = "${local.name_prefix}-cluster"
+
+  setting {
+    name  = "containerInsights"
+    value = "enabled"
+  }
+
+  tags = local.tags
+}
+
+# TeamCity Task Definition
+resource "aws_ecs_task_definition" "teamcity_task_definition" {
+  family                   = var.name
+  requires_compatibilities = ["FARGATE"]
+  network_mode             = "awsvpc"
+  cpu                      = var.container_cpu
+  memory                   = var.container_memory
+
+  container_definitions = jsonencode([
+    {
+      name      = var.container_name
+      image     = local.image
+      cpu       = var.container_cpu
+      memory    = var.container_memory
+      essential = true
+      portMappings = [
+        {
+          containerPort = var.container_port
+          hostPort      = var.container_port
+        }
+      ]
+      logConfiguration = {
+        logDriver = "awslogs"
+        options = {
+          awslogs-group         = aws_cloudwatch_log_group.teamcity_log_group.name
+          awslogs-region        = data.aws_region.current.name
+          awslogs-stream-prefix = "[APP]"
+        }
+      }
+      mountPoints = [
+        {
+          sourceVolume  = "teamcity_data",
+          containerPath = "/data/teamcity_server/datadir"
+          readOnly      = false
+        },
+        {
+          sourceVolume  = "teamcity_logs",
+          containerPath = "/data/teamcity_server/logs"
+          readOnly      = false
+        }
+      ]
+      # Set environment variables for database connection string
+      environment = [
+        {
+          name  = "TEAMCITY_DB_URL"
+          value = local.database_connection_string
+        },
+        {
+          name  = "TEAMCITY_DB_USER"
+          value = local.database_user
+        },
+        {
+          name  = "TEAMCITY_DB_PASSWORD"
+          value = local.database_password
+        },
+        {
+          name  = "TEAMCITY_LOGS_PATH"
+          value = "/data/teamcity_server/logs"
+        }
+      ]
+    }
+  ])
+  tags = {
+    Name = var.name
+  }
+  task_role_arn      = aws_iam_role.teamcity_default_role.arn
+  execution_role_arn = aws_iam_role.teamcity_task_execution_role.arn
+
+  volume {
+    name = "teamcity_data"
+    efs_volume_configuration {
+      file_system_id          = aws_efs_file_system.teamcity_efs_file_system.id
+      transit_encryption      = "ENABLED"
+      transit_encryption_port = 2999
+      authorization_config {
+        access_point_id = aws_efs_access_point.teamcity_efs_data_access_point.id
+        iam             = "ENABLED"
+      }
+    }
+  }
+
+  volume {
+    name = "teamcity_logs"
+    efs_volume_configuration {
+      file_system_id          = aws_efs_file_system.teamcity_efs_file_system.id
+      transit_encryption      = "ENABLED"
+      transit_encryption_port = 2998
+      authorization_config {
+        access_point_id = aws_efs_access_point.teamcity_efs_logs_access_point.id
+        iam             = "ENABLED"
+      }
+    }
+  }
+}
+
+
+
+
+# ECS Service
+resource "aws_ecs_service" "teamcity" {
+  name = local.name_prefix
+
+  cluster                = aws_ecs_cluster.teamcity_cluster.name
+  task_definition        = aws_ecs_task_definition.teamcity_task_definition.arn
+  launch_type            = "FARGATE"
+  desired_count          = 1    #TODO: make this configurable
+  force_new_deployment   = true #TODO: make this configurable
+  enable_execute_command = true #TODO: make this configurable
+
+  wait_for_steady_state = true
+
+  network_configuration {
+    subnets         = var.service_subnets
+    security_groups = [aws_security_group.teamcity_service_sg.id]
+  }
+  load_balancer {
+    target_group_arn = aws_lb_target_group.teamcity_target_group.arn
+    container_name   = var.container_name
+    container_port   = var.container_port
+  }
+
+  tags = local.tags
+}
+
+# TeamCity service security group
+resource "aws_security_group" "teamcity_service_sg" {
+  name   = "${local.name_prefix}-service-sg"
+  vpc_id = var.vpc_id
+
+  tags = local.tags
+}
+
+# TeamCity EFS security group
+resource "aws_security_group" "teamcity_efs_sg" {
+  name        = "${local.name_prefix}-efs-sg"
+  description = "TeamCity EFS mount target security group"
+  vpc_id      = var.vpc_id
+
+  tags = local.tags
+}
+
+# Ingress rule for NFS traffic from service to EFS
+resource "aws_vpc_security_group_ingress_rule" "service_efs" {
+  security_group_id            = aws_security_group.teamcity_efs_sg.id
+  referenced_security_group_id = aws_security_group.teamcity_service_sg.id
+  description                  = "Allow inbound access from TeamCity service containers to EFS"
+  ip_protocol                  = "TCP"
+  from_port                    = 2049
+  to_port                      = 2049
+}
+
+# Ingress rule for NFS traffic from EFS to service
+resource "aws_vpc_security_group_ingress_rule" "efs_service" {
+  security_group_id            = aws_security_group.teamcity_service_sg.id
+  referenced_security_group_id = aws_security_group.teamcity_efs_sg.id
+  description                  = "Allow inbound access from EFS to TeamCity service containers"
+  ip_protocol                  = "TCP"
+  from_port                    = 2049
+  to_port                      = 2049
+}
+
+# TeamCity Aurora Serverless PostgreSQL security group
+resource "aws_security_group" "teamcity_db_sg" {
+  name        = "${local.name_prefix}-db-sg"
+  description = "TeamCity DB security group"
+  vpc_id      = var.vpc_id
+
+  tags = local.tags
+}
+
+# Ingress rule for PostgreSQL from service to database cluster
+resource "aws_vpc_security_group_ingress_rule" "service_db" {
+  security_group_id            = aws_security_group.teamcity_db_sg.id
+  referenced_security_group_id = aws_security_group.teamcity_service_sg.id
+  description                  = "Allow inbound access from TeamCity service containers to DB"
+  ip_protocol                  = "TCP"
+  from_port                    = 5432
+  to_port                      = 5432
+}
+
+# Ingress rule for PostgreSQL from database cluster to service
+resource "aws_vpc_security_group_ingress_rule" "db_service" {
+  security_group_id            = aws_security_group.teamcity_service_sg.id
+  referenced_security_group_id = aws_security_group.teamcity_db_sg.id
+  description                  = "Allow inbound access from DB to TeamCity service containers"
+  ip_protocol                  = "TCP"
+  from_port                    = 5432
+  to_port                      = 5432
+}
+
+# Egress rule for database to let all traffic out
+resource "aws_vpc_security_group_egress_rule" "db_outbound" {
+
+  security_group_id = aws_security_group.teamcity_db_sg.id
+  cidr_ipv4         = "0.0.0.0/0"
+  ip_protocol       = "-1"
+}
+
+# TeamCity ALB security group
+resource "aws_security_group" "teamcity_alb_sg" {
+  name   = "${local.name_prefix}-alb-sg"
+  vpc_id = var.vpc_id
+
+  tags = local.tags
+}
+
+# Ingress rule to allow ALB to send health checks to service
+resource "aws_vpc_security_group_ingress_rule" "alb_service_healthcheck" {
+  security_group_id            = aws_security_group.teamcity_service_sg.id
+  referenced_security_group_id = aws_security_group.teamcity_alb_sg.id
+  description                  = "Allow health checks from ALB to service containers"
+  ip_protocol                  = "TCP"
+  from_port                    = 80
+  to_port                      = 80
+}
path: modules/teamcity/main.tf
user: login: github-advanced-security[bot]
body: ## Ensure no security groups allow ingress from 0.0.0.0:0 to port 80

Ensure no security groups allow ingress from 0.0.0.0:0 to port 80

[Show more details](https://github.com/aws-games/cloud-game-development-toolkit/security/code-scanning/806)
created_at: 2025-02-06T21:14:48Z
author_association: NONE
diff_hunk: @@ -0,0 +1,492 @@
+###################################
+# ECS Cluster for TeamCity Server #
+###################################
+
+resource "aws_ecs_cluster" "teamcity_cluster" {
+  name = "${local.name_prefix}-cluster"
+
+  setting {
+    name  = "containerInsights"
+    value = "enabled"
+  }
+
+  tags = local.tags
+}
+
+# TeamCity Task Definition
+resource "aws_ecs_task_definition" "teamcity_task_definition" {
+  family                   = var.name
+  requires_compatibilities = ["FARGATE"]
+  network_mode             = "awsvpc"
+  cpu                      = var.container_cpu
+  memory                   = var.container_memory
+
+  container_definitions = jsonencode([
+    {
+      name      = var.container_name
+      image     = local.image
+      cpu       = var.container_cpu
+      memory    = var.container_memory
+      essential = true
+      portMappings = [
+        {
+          containerPort = var.container_port
+          hostPort      = var.container_port
+        }
+      ]
+      logConfiguration = {
+        logDriver = "awslogs"
+        options = {
+          awslogs-group         = aws_cloudwatch_log_group.teamcity_log_group.name
+          awslogs-region        = data.aws_region.current.name
+          awslogs-stream-prefix = "[APP]"
+        }
+      }
+      mountPoints = [
+        {
+          sourceVolume  = "teamcity_data",
+          containerPath = "/data/teamcity_server/datadir"
+          readOnly      = false
+        },
+        {
+          sourceVolume  = "teamcity_logs",
+          containerPath = "/data/teamcity_server/logs"
+          readOnly      = false
+        }
+      ]
+      # Set environment variables for database connection string
+      environment = [
+        {
+          name  = "TEAMCITY_DB_URL"
+          value = local.database_connection_string
+        },
+        {
+          name  = "TEAMCITY_DB_USER"
+          value = local.database_user
+        },
+        {
+          name  = "TEAMCITY_DB_PASSWORD"
+          value = local.database_password
+        },
+        {
+          name  = "TEAMCITY_LOGS_PATH"
+          value = "/data/teamcity_server/logs"
+        }
+      ]
+    }
+  ])
+  tags = {
+    Name = var.name
+  }
+  task_role_arn      = aws_iam_role.teamcity_default_role.arn
+  execution_role_arn = aws_iam_role.teamcity_task_execution_role.arn
+
+  volume {
+    name = "teamcity_data"
+    efs_volume_configuration {
+      file_system_id          = aws_efs_file_system.teamcity_efs_file_system.id
+      transit_encryption      = "ENABLED"
+      transit_encryption_port = 2999
+      authorization_config {
+        access_point_id = aws_efs_access_point.teamcity_efs_data_access_point.id
+        iam             = "ENABLED"
+      }
+    }
+  }
+
+  volume {
+    name = "teamcity_logs"
+    efs_volume_configuration {
+      file_system_id          = aws_efs_file_system.teamcity_efs_file_system.id
+      transit_encryption      = "ENABLED"
+      transit_encryption_port = 2998
+      authorization_config {
+        access_point_id = aws_efs_access_point.teamcity_efs_logs_access_point.id
+        iam             = "ENABLED"
+      }
+    }
+  }
+}
+
+
+
+
+# ECS Service
+resource "aws_ecs_service" "teamcity" {
+  name = local.name_prefix
+
+  cluster                = aws_ecs_cluster.teamcity_cluster.name
+  task_definition        = aws_ecs_task_definition.teamcity_task_definition.arn
+  launch_type            = "FARGATE"
+  desired_count          = 1    #TODO: make this configurable
+  force_new_deployment   = true #TODO: make this configurable
+  enable_execute_command = true #TODO: make this configurable
+
+  wait_for_steady_state = true
+
+  network_configuration {
+    subnets         = var.service_subnets
+    security_groups = [aws_security_group.teamcity_service_sg.id]
+  }
+  load_balancer {
+    target_group_arn = aws_lb_target_group.teamcity_target_group.arn
+    container_name   = var.container_name
+    container_port   = var.container_port
+  }
+
+  tags = local.tags
+}
+
+# TeamCity service security group
+resource "aws_security_group" "teamcity_service_sg" {
+  name   = "${local.name_prefix}-service-sg"
+  vpc_id = var.vpc_id
+
+  tags = local.tags
+}
+
+# TeamCity EFS security group
+resource "aws_security_group" "teamcity_efs_sg" {
+  name        = "${local.name_prefix}-efs-sg"
+  description = "TeamCity EFS mount target security group"
+  vpc_id      = var.vpc_id
+
+  tags = local.tags
+}
+
+# Ingress rule for NFS traffic from service to EFS
+resource "aws_vpc_security_group_ingress_rule" "service_efs" {
+  security_group_id            = aws_security_group.teamcity_efs_sg.id
+  referenced_security_group_id = aws_security_group.teamcity_service_sg.id
+  description                  = "Allow inbound access from TeamCity service containers to EFS"
+  ip_protocol                  = "TCP"
+  from_port                    = 2049
+  to_port                      = 2049
+}
+
+# Ingress rule for NFS traffic from EFS to service
+resource "aws_vpc_security_group_ingress_rule" "efs_service" {
+  security_group_id            = aws_security_group.teamcity_service_sg.id
+  referenced_security_group_id = aws_security_group.teamcity_efs_sg.id
+  description                  = "Allow inbound access from EFS to TeamCity service containers"
+  ip_protocol                  = "TCP"
+  from_port                    = 2049
+  to_port                      = 2049
+}
+
+# TeamCity Aurora Serverless PostgreSQL security group
+resource "aws_security_group" "teamcity_db_sg" {
+  name        = "${local.name_prefix}-db-sg"
+  description = "TeamCity DB security group"
+  vpc_id      = var.vpc_id
+
+  tags = local.tags
+}
+
+# Ingress rule for PostgreSQL from service to database cluster
+resource "aws_vpc_security_group_ingress_rule" "service_db" {
+  security_group_id            = aws_security_group.teamcity_db_sg.id
+  referenced_security_group_id = aws_security_group.teamcity_service_sg.id
+  description                  = "Allow inbound access from TeamCity service containers to DB"
+  ip_protocol                  = "TCP"
+  from_port                    = 5432
+  to_port                      = 5432
+}
+
+# Ingress rule for PostgreSQL from database cluster to service
+resource "aws_vpc_security_group_ingress_rule" "db_service" {
+  security_group_id            = aws_security_group.teamcity_service_sg.id
+  referenced_security_group_id = aws_security_group.teamcity_db_sg.id
+  description                  = "Allow inbound access from DB to TeamCity service containers"
+  ip_protocol                  = "TCP"
+  from_port                    = 5432
+  to_port                      = 5432
+}
+
+# Egress rule for database to let all traffic out
+resource "aws_vpc_security_group_egress_rule" "db_outbound" {
+
+  security_group_id = aws_security_group.teamcity_db_sg.id
+  cidr_ipv4         = "0.0.0.0/0"
+  ip_protocol       = "-1"
+}
+
+# TeamCity ALB security group
+resource "aws_security_group" "teamcity_alb_sg" {
+  name   = "${local.name_prefix}-alb-sg"
+  vpc_id = var.vpc_id
+
+  tags = local.tags
+}
+
+# Ingress rule to allow ALB to send health checks to service
+resource "aws_vpc_security_group_ingress_rule" "alb_service_healthcheck" {
+  security_group_id            = aws_security_group.teamcity_service_sg.id
+  referenced_security_group_id = aws_security_group.teamcity_alb_sg.id
+  description                  = "Allow health checks from ALB to service containers"
+  ip_protocol                  = "TCP"
+  from_port                    = 80
+  to_port                      = 80
+}
+
+# Ingress rule for HTTP traffic from ALB to service
+resource "aws_vpc_security_group_ingress_rule" "service_inbound_alb" {
+  security_group_id            = aws_security_group.teamcity_service_sg.id
+  referenced_security_group_id = aws_security_group.teamcity_alb_sg.id
+  description                  = "Allow inbound HTTP traffic from ALB to service containers"
+  from_port                    = var.container_port
+  to_port                      = var.container_port
+  ip_protocol                  = "TCP"
+
+}
+
+# Egress rule for HTTP traffic from ALB to service
+resource "aws_vpc_security_group_egress_rule" "service_outbound_alb" {
+  security_group_id            = aws_security_group.teamcity_alb_sg.id
+  referenced_security_group_id = aws_security_group.teamcity_service_sg.id
+  description                  = "Allow outbound HTTP traffic from ALB to service containers"
+  from_port                    = var.container_port
+  to_port                      = var.container_port
+  ip_protocol                  = "TCP"
+
+}
+
+# Grant TeamCity service access to internet
+resource "aws_vpc_security_group_egress_rule" "internet_outbound" {
+  security_group_id = aws_security_group.teamcity_service_sg.id
+  description       = "Allow outbound internet access from TeamCity service containers"
+  cidr_ipv4         = "0.0.0.0/0"
+  ip_protocol       = "-1"
+}
+
+
+#############################################
+# IAM Roles for TeamCity Module
+#############################################
+data "aws_iam_policy_document" "ecs_tasks_trust_relationship" {
+  statement {
+    effect  = "Allow"
+    actions = ["sts:AssumeRole"]
+    principals {
+      type        = "Service"
+      identifiers = ["ecs-tasks.amazonaws.com"]
+    }
+  }
+}
+data "aws_iam_policy_document" "teamcity_default_policy" {
+  # ECS
+  statement {
+    sid    = "ECSExec"
+    effect = "Allow"
+    actions = [
+      "ssmmessages:OpenDataChannel",
+      "ssmmessages:OpenControlChannel",
+      "ssmmessages:CreateDataChannel",
+      "ssmmessages:CreateControlChannel",
+    ]
+    resources = [
+      "*"
+    ]
+  }
+}
+resource "aws_iam_policy" "teamcity_default_policy" {
+  name        = "teamcity-default-policy"
+  description = "Policy granting permissions for Unreal Horde."
+  policy      = data.aws_iam_policy_document.teamcity_default_policy.json
+}
+
+resource "aws_iam_role" "teamcity_default_role" {
+  name               = "teamcity-default-role"
+  description        = "Default role for TeamCity ECS task."
+  assume_role_policy = data.aws_iam_policy_document.ecs_tasks_trust_relationship.json
+  managed_policy_arns = [
+    aws_iam_policy.teamcity_default_policy.arn
+  ]
+
+  tags = local.tags
+}
+resource "aws_iam_role" "teamcity_task_execution_role" {
+  name = "teamcity-task-execution-role"
+
+  assume_role_policy  = data.aws_iam_policy_document.ecs_tasks_trust_relationship.json
+  managed_policy_arns = ["arn:aws:iam::aws:policy/service-role/AmazonECSTaskExecutionRolePolicy"]
+}
+
+# CloudWatch Logs
+resource "aws_cloudwatch_log_group" "teamcity_log_group" {
+  #checkov:skip=CKV_AWS_158: KMS Encryption disabled by default
+  name              = "${local.name_prefix}-log-group"
+  retention_in_days = var.teamcity_cloudwatch_log_retention_in_days
+  tags              = local.tags
+}
+
+
+# Load Balancer for TeamCity Service
+resource "aws_lb" "teamcity_external_lb" {
+  name            = "${local.name_prefix}-lb"
+  security_groups = [aws_security_group.teamcity_alb_sg.id]
+
+  load_balancer_type         = "application"
+  internal                   = false
+  subnets                    = var.alb_subnets
+  drop_invalid_header_fields = true
+  tags                       = local.tags
+}
+
+# TeamCity target group for ALB
+resource "aws_lb_target_group" "teamcity_target_group" {
+  name        = "${local.name_prefix}-tg"
+  port        = var.container_port
+  protocol    = "HTTP"
+  vpc_id      = var.vpc_id
+  target_type = "ip"
+
+  health_check {
+    path                = "/healthCheck/healthy"
+    interval            = 30
+    timeout             = 5
+    healthy_threshold   = 5
+    unhealthy_threshold = 2
+    port                = var.container_port
+    protocol            = "HTTP"
+    matcher             = "200"
+  }
+
+  tags = local.tags
+}
+
+# ALB HTTPS Listener
+resource "aws_lb_listener" "teamcity_listener" {
+  load_balancer_arn = aws_lb.teamcity_external_lb.arn
+  port              = "443"
+  protocol          = "HTTPS"
+  ssl_policy        = "ELBSecurityPolicy-TLS13-1-2-2021-06"
+  certificate_arn   = var.alb_certificate_arn
+  default_action {
+    type             = "forward"
+    target_group_arn = aws_lb_target_group.teamcity_target_group.arn
+  }
+  tags = local.tags
+}
+
+
+
+
+# #######################################
+# # TeamCity Aurora Serverless Database #
+# #######################################
+# Subnet group
+resource "aws_db_subnet_group" "teamcity_db_subnet_group" {
+  name       = "${local.name_prefix}-db-subnet-group"
+  subnet_ids = var.service_subnets
+  tags       = local.tags
+}
+
+# # RDS instance with Aurora serverless engine
+resource "aws_rds_cluster" "teamcity_db_cluster" {
+  cluster_identifier   = "teamcity-cluster"
+  engine               = "aurora-postgresql"
+  engine_mode          = "provisioned"
+  engine_version       = "16.6"
+  database_name        = "teamcity"
+  master_username      = "teamcity"
+  master_password      = "teamcity2025"
+  storage_encrypted    = true
+  db_subnet_group_name = aws_db_subnet_group.teamcity_db_subnet_group.id
+  vpc_security_group_ids = [
+    aws_security_group.teamcity_db_sg.id
+  ]
+
+
+  serverlessv2_scaling_configuration {
+    max_capacity             = 1.0
+    min_capacity             = 0.0
+    seconds_until_auto_pause = 3600
+  }
+}
path: modules/teamcity/main.tf
user: login: github-advanced-security[bot]
body: ## Ensure RDS cluster has IAM authentication enabled

Ensure RDS cluster has IAM authentication enabled

[Show more details](https://github.com/aws-games/cloud-game-development-toolkit/security/code-scanning/807)
created_at: 2025-02-06T21:14:48Z
author_association: NONE
diff_hunk: @@ -0,0 +1,492 @@
+###################################
+# ECS Cluster for TeamCity Server #
+###################################
+
+resource "aws_ecs_cluster" "teamcity_cluster" {
+  name = "${local.name_prefix}-cluster"
+
+  setting {
+    name  = "containerInsights"
+    value = "enabled"
+  }
+
+  tags = local.tags
+}
+
+# TeamCity Task Definition
+resource "aws_ecs_task_definition" "teamcity_task_definition" {
+  family                   = var.name
+  requires_compatibilities = ["FARGATE"]
+  network_mode             = "awsvpc"
+  cpu                      = var.container_cpu
+  memory                   = var.container_memory
+
+  container_definitions = jsonencode([
+    {
+      name      = var.container_name
+      image     = local.image
+      cpu       = var.container_cpu
+      memory    = var.container_memory
+      essential = true
+      portMappings = [
+        {
+          containerPort = var.container_port
+          hostPort      = var.container_port
+        }
+      ]
+      logConfiguration = {
+        logDriver = "awslogs"
+        options = {
+          awslogs-group         = aws_cloudwatch_log_group.teamcity_log_group.name
+          awslogs-region        = data.aws_region.current.name
+          awslogs-stream-prefix = "[APP]"
+        }
+      }
+      mountPoints = [
+        {
+          sourceVolume  = "teamcity_data",
+          containerPath = "/data/teamcity_server/datadir"
+          readOnly      = false
+        },
+        {
+          sourceVolume  = "teamcity_logs",
+          containerPath = "/data/teamcity_server/logs"
+          readOnly      = false
+        }
+      ]
+      # Set environment variables for database connection string
+      environment = [
+        {
+          name  = "TEAMCITY_DB_URL"
+          value = local.database_connection_string
+        },
+        {
+          name  = "TEAMCITY_DB_USER"
+          value = local.database_user
+        },
+        {
+          name  = "TEAMCITY_DB_PASSWORD"
+          value = local.database_password
+        },
+        {
+          name  = "TEAMCITY_LOGS_PATH"
+          value = "/data/teamcity_server/logs"
+        }
+      ]
+    }
+  ])
+  tags = {
+    Name = var.name
+  }
+  task_role_arn      = aws_iam_role.teamcity_default_role.arn
+  execution_role_arn = aws_iam_role.teamcity_task_execution_role.arn
+
+  volume {
+    name = "teamcity_data"
+    efs_volume_configuration {
+      file_system_id          = aws_efs_file_system.teamcity_efs_file_system.id
+      transit_encryption      = "ENABLED"
+      transit_encryption_port = 2999
+      authorization_config {
+        access_point_id = aws_efs_access_point.teamcity_efs_data_access_point.id
+        iam             = "ENABLED"
+      }
+    }
+  }
+
+  volume {
+    name = "teamcity_logs"
+    efs_volume_configuration {
+      file_system_id          = aws_efs_file_system.teamcity_efs_file_system.id
+      transit_encryption      = "ENABLED"
+      transit_encryption_port = 2998
+      authorization_config {
+        access_point_id = aws_efs_access_point.teamcity_efs_logs_access_point.id
+        iam             = "ENABLED"
+      }
+    }
+  }
+}
+
+
+
+
+# ECS Service
+resource "aws_ecs_service" "teamcity" {
+  name = local.name_prefix
+
+  cluster                = aws_ecs_cluster.teamcity_cluster.name
+  task_definition        = aws_ecs_task_definition.teamcity_task_definition.arn
+  launch_type            = "FARGATE"
+  desired_count          = 1    #TODO: make this configurable
+  force_new_deployment   = true #TODO: make this configurable
+  enable_execute_command = true #TODO: make this configurable
+
+  wait_for_steady_state = true
+
+  network_configuration {
+    subnets         = var.service_subnets
+    security_groups = [aws_security_group.teamcity_service_sg.id]
+  }
+  load_balancer {
+    target_group_arn = aws_lb_target_group.teamcity_target_group.arn
+    container_name   = var.container_name
+    container_port   = var.container_port
+  }
+
+  tags = local.tags
+}
+
+# TeamCity service security group
+resource "aws_security_group" "teamcity_service_sg" {
+  name   = "${local.name_prefix}-service-sg"
+  vpc_id = var.vpc_id
+
+  tags = local.tags
+}
+
+# TeamCity EFS security group
+resource "aws_security_group" "teamcity_efs_sg" {
+  name        = "${local.name_prefix}-efs-sg"
+  description = "TeamCity EFS mount target security group"
+  vpc_id      = var.vpc_id
+
+  tags = local.tags
+}
+
+# Ingress rule for NFS traffic from service to EFS
+resource "aws_vpc_security_group_ingress_rule" "service_efs" {
+  security_group_id            = aws_security_group.teamcity_efs_sg.id
+  referenced_security_group_id = aws_security_group.teamcity_service_sg.id
+  description                  = "Allow inbound access from TeamCity service containers to EFS"
+  ip_protocol                  = "TCP"
+  from_port                    = 2049
+  to_port                      = 2049
+}
+
+# Ingress rule for NFS traffic from EFS to service
+resource "aws_vpc_security_group_ingress_rule" "efs_service" {
+  security_group_id            = aws_security_group.teamcity_service_sg.id
+  referenced_security_group_id = aws_security_group.teamcity_efs_sg.id
+  description                  = "Allow inbound access from EFS to TeamCity service containers"
+  ip_protocol                  = "TCP"
+  from_port                    = 2049
+  to_port                      = 2049
+}
+
+# TeamCity Aurora Serverless PostgreSQL security group
+resource "aws_security_group" "teamcity_db_sg" {
+  name        = "${local.name_prefix}-db-sg"
+  description = "TeamCity DB security group"
+  vpc_id      = var.vpc_id
+
+  tags = local.tags
+}
+
+# Ingress rule for PostgreSQL from service to database cluster
+resource "aws_vpc_security_group_ingress_rule" "service_db" {
+  security_group_id            = aws_security_group.teamcity_db_sg.id
+  referenced_security_group_id = aws_security_group.teamcity_service_sg.id
+  description                  = "Allow inbound access from TeamCity service containers to DB"
+  ip_protocol                  = "TCP"
+  from_port                    = 5432
+  to_port                      = 5432
+}
+
+# Ingress rule for PostgreSQL from database cluster to service
+resource "aws_vpc_security_group_ingress_rule" "db_service" {
+  security_group_id            = aws_security_group.teamcity_service_sg.id
+  referenced_security_group_id = aws_security_group.teamcity_db_sg.id
+  description                  = "Allow inbound access from DB to TeamCity service containers"
+  ip_protocol                  = "TCP"
+  from_port                    = 5432
+  to_port                      = 5432
+}
+
+# Egress rule for database to let all traffic out
+resource "aws_vpc_security_group_egress_rule" "db_outbound" {
+
+  security_group_id = aws_security_group.teamcity_db_sg.id
+  cidr_ipv4         = "0.0.0.0/0"
+  ip_protocol       = "-1"
+}
+
+# TeamCity ALB security group
+resource "aws_security_group" "teamcity_alb_sg" {
+  name   = "${local.name_prefix}-alb-sg"
+  vpc_id = var.vpc_id
+
+  tags = local.tags
+}
+
+# Ingress rule to allow ALB to send health checks to service
+resource "aws_vpc_security_group_ingress_rule" "alb_service_healthcheck" {
+  security_group_id            = aws_security_group.teamcity_service_sg.id
+  referenced_security_group_id = aws_security_group.teamcity_alb_sg.id
+  description                  = "Allow health checks from ALB to service containers"
+  ip_protocol                  = "TCP"
+  from_port                    = 80
+  to_port                      = 80
+}
+
+# Ingress rule for HTTP traffic from ALB to service
+resource "aws_vpc_security_group_ingress_rule" "service_inbound_alb" {
+  security_group_id            = aws_security_group.teamcity_service_sg.id
+  referenced_security_group_id = aws_security_group.teamcity_alb_sg.id
+  description                  = "Allow inbound HTTP traffic from ALB to service containers"
+  from_port                    = var.container_port
+  to_port                      = var.container_port
+  ip_protocol                  = "TCP"
+
+}
+
+# Egress rule for HTTP traffic from ALB to service
+resource "aws_vpc_security_group_egress_rule" "service_outbound_alb" {
+  security_group_id            = aws_security_group.teamcity_alb_sg.id
+  referenced_security_group_id = aws_security_group.teamcity_service_sg.id
+  description                  = "Allow outbound HTTP traffic from ALB to service containers"
+  from_port                    = var.container_port
+  to_port                      = var.container_port
+  ip_protocol                  = "TCP"
+
+}
+
+# Grant TeamCity service access to internet
+resource "aws_vpc_security_group_egress_rule" "internet_outbound" {
+  security_group_id = aws_security_group.teamcity_service_sg.id
+  description       = "Allow outbound internet access from TeamCity service containers"
+  cidr_ipv4         = "0.0.0.0/0"
+  ip_protocol       = "-1"
+}
+
+
+#############################################
+# IAM Roles for TeamCity Module
+#############################################
+data "aws_iam_policy_document" "ecs_tasks_trust_relationship" {
+  statement {
+    effect  = "Allow"
+    actions = ["sts:AssumeRole"]
+    principals {
+      type        = "Service"
+      identifiers = ["ecs-tasks.amazonaws.com"]
+    }
+  }
+}
+data "aws_iam_policy_document" "teamcity_default_policy" {
+  # ECS
+  statement {
+    sid    = "ECSExec"
+    effect = "Allow"
+    actions = [
+      "ssmmessages:OpenDataChannel",
+      "ssmmessages:OpenControlChannel",
+      "ssmmessages:CreateDataChannel",
+      "ssmmessages:CreateControlChannel",
+    ]
+    resources = [
+      "*"
+    ]
+  }
+}
+resource "aws_iam_policy" "teamcity_default_policy" {
+  name        = "teamcity-default-policy"
+  description = "Policy granting permissions for Unreal Horde."
+  policy      = data.aws_iam_policy_document.teamcity_default_policy.json
+}
+
+resource "aws_iam_role" "teamcity_default_role" {
+  name               = "teamcity-default-role"
+  description        = "Default role for TeamCity ECS task."
+  assume_role_policy = data.aws_iam_policy_document.ecs_tasks_trust_relationship.json
+  managed_policy_arns = [
+    aws_iam_policy.teamcity_default_policy.arn
+  ]
+
+  tags = local.tags
+}
+resource "aws_iam_role" "teamcity_task_execution_role" {
+  name = "teamcity-task-execution-role"
+
+  assume_role_policy  = data.aws_iam_policy_document.ecs_tasks_trust_relationship.json
+  managed_policy_arns = ["arn:aws:iam::aws:policy/service-role/AmazonECSTaskExecutionRolePolicy"]
+}
+
+# CloudWatch Logs
+resource "aws_cloudwatch_log_group" "teamcity_log_group" {
+  #checkov:skip=CKV_AWS_158: KMS Encryption disabled by default
+  name              = "${local.name_prefix}-log-group"
+  retention_in_days = var.teamcity_cloudwatch_log_retention_in_days
+  tags              = local.tags
+}
+
+
+# Load Balancer for TeamCity Service
+resource "aws_lb" "teamcity_external_lb" {
+  name            = "${local.name_prefix}-lb"
+  security_groups = [aws_security_group.teamcity_alb_sg.id]
+
+  load_balancer_type         = "application"
+  internal                   = false
+  subnets                    = var.alb_subnets
+  drop_invalid_header_fields = true
+  tags                       = local.tags
+}
+
+# TeamCity target group for ALB
+resource "aws_lb_target_group" "teamcity_target_group" {
+  name        = "${local.name_prefix}-tg"
+  port        = var.container_port
+  protocol    = "HTTP"
+  vpc_id      = var.vpc_id
+  target_type = "ip"
+
+  health_check {
+    path                = "/healthCheck/healthy"
+    interval            = 30
+    timeout             = 5
+    healthy_threshold   = 5
+    unhealthy_threshold = 2
+    port                = var.container_port
+    protocol            = "HTTP"
+    matcher             = "200"
+  }
+
+  tags = local.tags
+}
+
+# ALB HTTPS Listener
+resource "aws_lb_listener" "teamcity_listener" {
+  load_balancer_arn = aws_lb.teamcity_external_lb.arn
+  port              = "443"
+  protocol          = "HTTPS"
+  ssl_policy        = "ELBSecurityPolicy-TLS13-1-2-2021-06"
+  certificate_arn   = var.alb_certificate_arn
+  default_action {
+    type             = "forward"
+    target_group_arn = aws_lb_target_group.teamcity_target_group.arn
+  }
+  tags = local.tags
+}
+
+
+
+
+# #######################################
+# # TeamCity Aurora Serverless Database #
+# #######################################
+# Subnet group
+resource "aws_db_subnet_group" "teamcity_db_subnet_group" {
+  name       = "${local.name_prefix}-db-subnet-group"
+  subnet_ids = var.service_subnets
+  tags       = local.tags
+}
+
+# # RDS instance with Aurora serverless engine
+resource "aws_rds_cluster" "teamcity_db_cluster" {
+  cluster_identifier   = "teamcity-cluster"
+  engine               = "aurora-postgresql"
+  engine_mode          = "provisioned"
+  engine_version       = "16.6"
+  database_name        = "teamcity"
+  master_username      = "teamcity"
+  master_password      = "teamcity2025"
+  storage_encrypted    = true
+  db_subnet_group_name = aws_db_subnet_group.teamcity_db_subnet_group.id
+  vpc_security_group_ids = [
+    aws_security_group.teamcity_db_sg.id
+  ]
+
+
+  serverlessv2_scaling_configuration {
+    max_capacity             = 1.0
+    min_capacity             = 0.0
+    seconds_until_auto_pause = 3600
+  }
+}
path: modules/teamcity/main.tf
user: login: github-advanced-security[bot]
body: ## Ensure that RDS Cluster log capture is enabled

Ensure that RDS Cluster log capture is enabled

[Show more details](https://github.com/aws-games/cloud-game-development-toolkit/security/code-scanning/808)
created_at: 2025-02-06T21:14:48Z
author_association: NONE
diff_hunk: @@ -0,0 +1,492 @@
+###################################
+# ECS Cluster for TeamCity Server #
+###################################
+
+resource "aws_ecs_cluster" "teamcity_cluster" {
+  name = "${local.name_prefix}-cluster"
+
+  setting {
+    name  = "containerInsights"
+    value = "enabled"
+  }
+
+  tags = local.tags
+}
+
+# TeamCity Task Definition
+resource "aws_ecs_task_definition" "teamcity_task_definition" {
+  family                   = var.name
+  requires_compatibilities = ["FARGATE"]
+  network_mode             = "awsvpc"
+  cpu                      = var.container_cpu
+  memory                   = var.container_memory
+
+  container_definitions = jsonencode([
+    {
+      name      = var.container_name
+      image     = local.image
+      cpu       = var.container_cpu
+      memory    = var.container_memory
+      essential = true
+      portMappings = [
+        {
+          containerPort = var.container_port
+          hostPort      = var.container_port
+        }
+      ]
+      logConfiguration = {
+        logDriver = "awslogs"
+        options = {
+          awslogs-group         = aws_cloudwatch_log_group.teamcity_log_group.name
+          awslogs-region        = data.aws_region.current.name
+          awslogs-stream-prefix = "[APP]"
+        }
+      }
+      mountPoints = [
+        {
+          sourceVolume  = "teamcity_data",
+          containerPath = "/data/teamcity_server/datadir"
+          readOnly      = false
+        },
+        {
+          sourceVolume  = "teamcity_logs",
+          containerPath = "/data/teamcity_server/logs"
+          readOnly      = false
+        }
+      ]
+      # Set environment variables for database connection string
+      environment = [
+        {
+          name  = "TEAMCITY_DB_URL"
+          value = local.database_connection_string
+        },
+        {
+          name  = "TEAMCITY_DB_USER"
+          value = local.database_user
+        },
+        {
+          name  = "TEAMCITY_DB_PASSWORD"
+          value = local.database_password
+        },
+        {
+          name  = "TEAMCITY_LOGS_PATH"
+          value = "/data/teamcity_server/logs"
+        }
+      ]
+    }
+  ])
+  tags = {
+    Name = var.name
+  }
+  task_role_arn      = aws_iam_role.teamcity_default_role.arn
+  execution_role_arn = aws_iam_role.teamcity_task_execution_role.arn
+
+  volume {
+    name = "teamcity_data"
+    efs_volume_configuration {
+      file_system_id          = aws_efs_file_system.teamcity_efs_file_system.id
+      transit_encryption      = "ENABLED"
+      transit_encryption_port = 2999
+      authorization_config {
+        access_point_id = aws_efs_access_point.teamcity_efs_data_access_point.id
+        iam             = "ENABLED"
+      }
+    }
+  }
+
+  volume {
+    name = "teamcity_logs"
+    efs_volume_configuration {
+      file_system_id          = aws_efs_file_system.teamcity_efs_file_system.id
+      transit_encryption      = "ENABLED"
+      transit_encryption_port = 2998
+      authorization_config {
+        access_point_id = aws_efs_access_point.teamcity_efs_logs_access_point.id
+        iam             = "ENABLED"
+      }
+    }
+  }
+}
+
+
+
+
+# ECS Service
+resource "aws_ecs_service" "teamcity" {
+  name = local.name_prefix
+
+  cluster                = aws_ecs_cluster.teamcity_cluster.name
+  task_definition        = aws_ecs_task_definition.teamcity_task_definition.arn
+  launch_type            = "FARGATE"
+  desired_count          = 1    #TODO: make this configurable
+  force_new_deployment   = true #TODO: make this configurable
+  enable_execute_command = true #TODO: make this configurable
+
+  wait_for_steady_state = true
+
+  network_configuration {
+    subnets         = var.service_subnets
+    security_groups = [aws_security_group.teamcity_service_sg.id]
+  }
+  load_balancer {
+    target_group_arn = aws_lb_target_group.teamcity_target_group.arn
+    container_name   = var.container_name
+    container_port   = var.container_port
+  }
+
+  tags = local.tags
+}
+
+# TeamCity service security group
+resource "aws_security_group" "teamcity_service_sg" {
+  name   = "${local.name_prefix}-service-sg"
+  vpc_id = var.vpc_id
+
+  tags = local.tags
+}
+
+# TeamCity EFS security group
+resource "aws_security_group" "teamcity_efs_sg" {
+  name        = "${local.name_prefix}-efs-sg"
+  description = "TeamCity EFS mount target security group"
+  vpc_id      = var.vpc_id
+
+  tags = local.tags
+}
+
+# Ingress rule for NFS traffic from service to EFS
+resource "aws_vpc_security_group_ingress_rule" "service_efs" {
+  security_group_id            = aws_security_group.teamcity_efs_sg.id
+  referenced_security_group_id = aws_security_group.teamcity_service_sg.id
+  description                  = "Allow inbound access from TeamCity service containers to EFS"
+  ip_protocol                  = "TCP"
+  from_port                    = 2049
+  to_port                      = 2049
+}
+
+# Ingress rule for NFS traffic from EFS to service
+resource "aws_vpc_security_group_ingress_rule" "efs_service" {
+  security_group_id            = aws_security_group.teamcity_service_sg.id
+  referenced_security_group_id = aws_security_group.teamcity_efs_sg.id
+  description                  = "Allow inbound access from EFS to TeamCity service containers"
+  ip_protocol                  = "TCP"
+  from_port                    = 2049
+  to_port                      = 2049
+}
+
+# TeamCity Aurora Serverless PostgreSQL security group
+resource "aws_security_group" "teamcity_db_sg" {
+  name        = "${local.name_prefix}-db-sg"
+  description = "TeamCity DB security group"
+  vpc_id      = var.vpc_id
+
+  tags = local.tags
+}
+
+# Ingress rule for PostgreSQL from service to database cluster
+resource "aws_vpc_security_group_ingress_rule" "service_db" {
+  security_group_id            = aws_security_group.teamcity_db_sg.id
+  referenced_security_group_id = aws_security_group.teamcity_service_sg.id
+  description                  = "Allow inbound access from TeamCity service containers to DB"
+  ip_protocol                  = "TCP"
+  from_port                    = 5432
+  to_port                      = 5432
+}
+
+# Ingress rule for PostgreSQL from database cluster to service
+resource "aws_vpc_security_group_ingress_rule" "db_service" {
+  security_group_id            = aws_security_group.teamcity_service_sg.id
+  referenced_security_group_id = aws_security_group.teamcity_db_sg.id
+  description                  = "Allow inbound access from DB to TeamCity service containers"
+  ip_protocol                  = "TCP"
+  from_port                    = 5432
+  to_port                      = 5432
+}
+
+# Egress rule for database to let all traffic out
+resource "aws_vpc_security_group_egress_rule" "db_outbound" {
+
+  security_group_id = aws_security_group.teamcity_db_sg.id
+  cidr_ipv4         = "0.0.0.0/0"
+  ip_protocol       = "-1"
+}
+
+# TeamCity ALB security group
+resource "aws_security_group" "teamcity_alb_sg" {
+  name   = "${local.name_prefix}-alb-sg"
+  vpc_id = var.vpc_id
+
+  tags = local.tags
+}
+
+# Ingress rule to allow ALB to send health checks to service
+resource "aws_vpc_security_group_ingress_rule" "alb_service_healthcheck" {
+  security_group_id            = aws_security_group.teamcity_service_sg.id
+  referenced_security_group_id = aws_security_group.teamcity_alb_sg.id
+  description                  = "Allow health checks from ALB to service containers"
+  ip_protocol                  = "TCP"
+  from_port                    = 80
+  to_port                      = 80
+}
+
+# Ingress rule for HTTP traffic from ALB to service
+resource "aws_vpc_security_group_ingress_rule" "service_inbound_alb" {
+  security_group_id            = aws_security_group.teamcity_service_sg.id
+  referenced_security_group_id = aws_security_group.teamcity_alb_sg.id
+  description                  = "Allow inbound HTTP traffic from ALB to service containers"
+  from_port                    = var.container_port
+  to_port                      = var.container_port
+  ip_protocol                  = "TCP"
+
+}
+
+# Egress rule for HTTP traffic from ALB to service
+resource "aws_vpc_security_group_egress_rule" "service_outbound_alb" {
+  security_group_id            = aws_security_group.teamcity_alb_sg.id
+  referenced_security_group_id = aws_security_group.teamcity_service_sg.id
+  description                  = "Allow outbound HTTP traffic from ALB to service containers"
+  from_port                    = var.container_port
+  to_port                      = var.container_port
+  ip_protocol                  = "TCP"
+
+}
+
+# Grant TeamCity service access to internet
+resource "aws_vpc_security_group_egress_rule" "internet_outbound" {
+  security_group_id = aws_security_group.teamcity_service_sg.id
+  description       = "Allow outbound internet access from TeamCity service containers"
+  cidr_ipv4         = "0.0.0.0/0"
+  ip_protocol       = "-1"
+}
+
+
+#############################################
+# IAM Roles for TeamCity Module
+#############################################
+data "aws_iam_policy_document" "ecs_tasks_trust_relationship" {
+  statement {
+    effect  = "Allow"
+    actions = ["sts:AssumeRole"]
+    principals {
+      type        = "Service"
+      identifiers = ["ecs-tasks.amazonaws.com"]
+    }
+  }
+}
+data "aws_iam_policy_document" "teamcity_default_policy" {
+  # ECS
+  statement {
+    sid    = "ECSExec"
+    effect = "Allow"
+    actions = [
+      "ssmmessages:OpenDataChannel",
+      "ssmmessages:OpenControlChannel",
+      "ssmmessages:CreateDataChannel",
+      "ssmmessages:CreateControlChannel",
+    ]
+    resources = [
+      "*"
+    ]
+  }
+}
+resource "aws_iam_policy" "teamcity_default_policy" {
+  name        = "teamcity-default-policy"
+  description = "Policy granting permissions for Unreal Horde."
+  policy      = data.aws_iam_policy_document.teamcity_default_policy.json
+}
+
+resource "aws_iam_role" "teamcity_default_role" {
+  name               = "teamcity-default-role"
+  description        = "Default role for TeamCity ECS task."
+  assume_role_policy = data.aws_iam_policy_document.ecs_tasks_trust_relationship.json
+  managed_policy_arns = [
+    aws_iam_policy.teamcity_default_policy.arn
+  ]
+
+  tags = local.tags
+}
+resource "aws_iam_role" "teamcity_task_execution_role" {
+  name = "teamcity-task-execution-role"
+
+  assume_role_policy  = data.aws_iam_policy_document.ecs_tasks_trust_relationship.json
+  managed_policy_arns = ["arn:aws:iam::aws:policy/service-role/AmazonECSTaskExecutionRolePolicy"]
+}
+
+# CloudWatch Logs
+resource "aws_cloudwatch_log_group" "teamcity_log_group" {
+  #checkov:skip=CKV_AWS_158: KMS Encryption disabled by default
+  name              = "${local.name_prefix}-log-group"
+  retention_in_days = var.teamcity_cloudwatch_log_retention_in_days
+  tags              = local.tags
+}
+
+
+# Load Balancer for TeamCity Service
+resource "aws_lb" "teamcity_external_lb" {
+  name            = "${local.name_prefix}-lb"
+  security_groups = [aws_security_group.teamcity_alb_sg.id]
+
+  load_balancer_type         = "application"
+  internal                   = false
+  subnets                    = var.alb_subnets
+  drop_invalid_header_fields = true
+  tags                       = local.tags
+}
+
+# TeamCity target group for ALB
+resource "aws_lb_target_group" "teamcity_target_group" {
+  name        = "${local.name_prefix}-tg"
+  port        = var.container_port
+  protocol    = "HTTP"
+  vpc_id      = var.vpc_id
+  target_type = "ip"
+
+  health_check {
+    path                = "/healthCheck/healthy"
+    interval            = 30
+    timeout             = 5
+    healthy_threshold   = 5
+    unhealthy_threshold = 2
+    port                = var.container_port
+    protocol            = "HTTP"
+    matcher             = "200"
+  }
+
+  tags = local.tags
+}
+
+# ALB HTTPS Listener
+resource "aws_lb_listener" "teamcity_listener" {
+  load_balancer_arn = aws_lb.teamcity_external_lb.arn
+  port              = "443"
+  protocol          = "HTTPS"
+  ssl_policy        = "ELBSecurityPolicy-TLS13-1-2-2021-06"
+  certificate_arn   = var.alb_certificate_arn
+  default_action {
+    type             = "forward"
+    target_group_arn = aws_lb_target_group.teamcity_target_group.arn
+  }
+  tags = local.tags
+}
+
+
+
+
+# #######################################
+# # TeamCity Aurora Serverless Database #
+# #######################################
+# Subnet group
+resource "aws_db_subnet_group" "teamcity_db_subnet_group" {
+  name       = "${local.name_prefix}-db-subnet-group"
+  subnet_ids = var.service_subnets
+  tags       = local.tags
+}
+
+# # RDS instance with Aurora serverless engine
+resource "aws_rds_cluster" "teamcity_db_cluster" {
+  cluster_identifier   = "teamcity-cluster"
+  engine               = "aurora-postgresql"
+  engine_mode          = "provisioned"
+  engine_version       = "16.6"
+  database_name        = "teamcity"
+  master_username      = "teamcity"
+  master_password      = "teamcity2025"
+  storage_encrypted    = true
+  db_subnet_group_name = aws_db_subnet_group.teamcity_db_subnet_group.id
+  vpc_security_group_ids = [
+    aws_security_group.teamcity_db_sg.id
+  ]
+
+
+  serverlessv2_scaling_configuration {
+    max_capacity             = 1.0
+    min_capacity             = 0.0
+    seconds_until_auto_pause = 3600
+  }
+}
path: modules/teamcity/main.tf
user: login: github-advanced-security[bot]
body: ## Ensure RDS cluster configured to copy tags to snapshots

Ensure RDS cluster configured to copy tags to snapshots

[Show more details](https://github.com/aws-games/cloud-game-development-toolkit/security/code-scanning/809)
created_at: 2025-02-06T21:14:48Z
author_association: NONE
diff_hunk: @@ -0,0 +1,492 @@
+###################################
+# ECS Cluster for TeamCity Server #
+###################################
+
+resource "aws_ecs_cluster" "teamcity_cluster" {
+  name = "${local.name_prefix}-cluster"
+
+  setting {
+    name  = "containerInsights"
+    value = "enabled"
+  }
+
+  tags = local.tags
+}
+
+# TeamCity Task Definition
+resource "aws_ecs_task_definition" "teamcity_task_definition" {
+  family                   = var.name
+  requires_compatibilities = ["FARGATE"]
+  network_mode             = "awsvpc"
+  cpu                      = var.container_cpu
+  memory                   = var.container_memory
+
+  container_definitions = jsonencode([
+    {
+      name      = var.container_name
+      image     = local.image
+      cpu       = var.container_cpu
+      memory    = var.container_memory
+      essential = true
+      portMappings = [
+        {
+          containerPort = var.container_port
+          hostPort      = var.container_port
+        }
+      ]
+      logConfiguration = {
+        logDriver = "awslogs"
+        options = {
+          awslogs-group         = aws_cloudwatch_log_group.teamcity_log_group.name
+          awslogs-region        = data.aws_region.current.name
+          awslogs-stream-prefix = "[APP]"
+        }
+      }
+      mountPoints = [
+        {
+          sourceVolume  = "teamcity_data",
+          containerPath = "/data/teamcity_server/datadir"
+          readOnly      = false
+        },
+        {
+          sourceVolume  = "teamcity_logs",
+          containerPath = "/data/teamcity_server/logs"
+          readOnly      = false
+        }
+      ]
+      # Set environment variables for database connection string
+      environment = [
+        {
+          name  = "TEAMCITY_DB_URL"
+          value = local.database_connection_string
+        },
+        {
+          name  = "TEAMCITY_DB_USER"
+          value = local.database_user
+        },
+        {
+          name  = "TEAMCITY_DB_PASSWORD"
+          value = local.database_password
+        },
+        {
+          name  = "TEAMCITY_LOGS_PATH"
+          value = "/data/teamcity_server/logs"
+        }
+      ]
+    }
+  ])
+  tags = {
+    Name = var.name
+  }
+  task_role_arn      = aws_iam_role.teamcity_default_role.arn
+  execution_role_arn = aws_iam_role.teamcity_task_execution_role.arn
+
+  volume {
+    name = "teamcity_data"
+    efs_volume_configuration {
+      file_system_id          = aws_efs_file_system.teamcity_efs_file_system.id
+      transit_encryption      = "ENABLED"
+      transit_encryption_port = 2999
+      authorization_config {
+        access_point_id = aws_efs_access_point.teamcity_efs_data_access_point.id
+        iam             = "ENABLED"
+      }
+    }
+  }
+
+  volume {
+    name = "teamcity_logs"
+    efs_volume_configuration {
+      file_system_id          = aws_efs_file_system.teamcity_efs_file_system.id
+      transit_encryption      = "ENABLED"
+      transit_encryption_port = 2998
+      authorization_config {
+        access_point_id = aws_efs_access_point.teamcity_efs_logs_access_point.id
+        iam             = "ENABLED"
+      }
+    }
+  }
+}
+
+
+
+
+# ECS Service
+resource "aws_ecs_service" "teamcity" {
+  name = local.name_prefix
+
+  cluster                = aws_ecs_cluster.teamcity_cluster.name
+  task_definition        = aws_ecs_task_definition.teamcity_task_definition.arn
+  launch_type            = "FARGATE"
+  desired_count          = 1    #TODO: make this configurable
+  force_new_deployment   = true #TODO: make this configurable
+  enable_execute_command = true #TODO: make this configurable
+
+  wait_for_steady_state = true
+
+  network_configuration {
+    subnets         = var.service_subnets
+    security_groups = [aws_security_group.teamcity_service_sg.id]
+  }
+  load_balancer {
+    target_group_arn = aws_lb_target_group.teamcity_target_group.arn
+    container_name   = var.container_name
+    container_port   = var.container_port
+  }
+
+  tags = local.tags
+}
+
+# TeamCity service security group
+resource "aws_security_group" "teamcity_service_sg" {
+  name   = "${local.name_prefix}-service-sg"
+  vpc_id = var.vpc_id
+
+  tags = local.tags
+}
+
+# TeamCity EFS security group
+resource "aws_security_group" "teamcity_efs_sg" {
+  name        = "${local.name_prefix}-efs-sg"
+  description = "TeamCity EFS mount target security group"
+  vpc_id      = var.vpc_id
+
+  tags = local.tags
+}
+
+# Ingress rule for NFS traffic from service to EFS
+resource "aws_vpc_security_group_ingress_rule" "service_efs" {
+  security_group_id            = aws_security_group.teamcity_efs_sg.id
+  referenced_security_group_id = aws_security_group.teamcity_service_sg.id
+  description                  = "Allow inbound access from TeamCity service containers to EFS"
+  ip_protocol                  = "TCP"
+  from_port                    = 2049
+  to_port                      = 2049
+}
+
+# Ingress rule for NFS traffic from EFS to service
+resource "aws_vpc_security_group_ingress_rule" "efs_service" {
+  security_group_id            = aws_security_group.teamcity_service_sg.id
+  referenced_security_group_id = aws_security_group.teamcity_efs_sg.id
+  description                  = "Allow inbound access from EFS to TeamCity service containers"
+  ip_protocol                  = "TCP"
+  from_port                    = 2049
+  to_port                      = 2049
+}
+
+# TeamCity Aurora Serverless PostgreSQL security group
+resource "aws_security_group" "teamcity_db_sg" {
+  name        = "${local.name_prefix}-db-sg"
+  description = "TeamCity DB security group"
+  vpc_id      = var.vpc_id
+
+  tags = local.tags
+}
+
+# Ingress rule for PostgreSQL from service to database cluster
+resource "aws_vpc_security_group_ingress_rule" "service_db" {
+  security_group_id            = aws_security_group.teamcity_db_sg.id
+  referenced_security_group_id = aws_security_group.teamcity_service_sg.id
+  description                  = "Allow inbound access from TeamCity service containers to DB"
+  ip_protocol                  = "TCP"
+  from_port                    = 5432
+  to_port                      = 5432
+}
+
+# Ingress rule for PostgreSQL from database cluster to service
+resource "aws_vpc_security_group_ingress_rule" "db_service" {
+  security_group_id            = aws_security_group.teamcity_service_sg.id
+  referenced_security_group_id = aws_security_group.teamcity_db_sg.id
+  description                  = "Allow inbound access from DB to TeamCity service containers"
+  ip_protocol                  = "TCP"
+  from_port                    = 5432
+  to_port                      = 5432
+}
+
+# Egress rule for database to let all traffic out
+resource "aws_vpc_security_group_egress_rule" "db_outbound" {
+
+  security_group_id = aws_security_group.teamcity_db_sg.id
+  cidr_ipv4         = "0.0.0.0/0"
+  ip_protocol       = "-1"
+}
+
+# TeamCity ALB security group
+resource "aws_security_group" "teamcity_alb_sg" {
+  name   = "${local.name_prefix}-alb-sg"
+  vpc_id = var.vpc_id
+
+  tags = local.tags
+}
+
+# Ingress rule to allow ALB to send health checks to service
+resource "aws_vpc_security_group_ingress_rule" "alb_service_healthcheck" {
+  security_group_id            = aws_security_group.teamcity_service_sg.id
+  referenced_security_group_id = aws_security_group.teamcity_alb_sg.id
+  description                  = "Allow health checks from ALB to service containers"
+  ip_protocol                  = "TCP"
+  from_port                    = 80
+  to_port                      = 80
+}
+
+# Ingress rule for HTTP traffic from ALB to service
+resource "aws_vpc_security_group_ingress_rule" "service_inbound_alb" {
+  security_group_id            = aws_security_group.teamcity_service_sg.id
+  referenced_security_group_id = aws_security_group.teamcity_alb_sg.id
+  description                  = "Allow inbound HTTP traffic from ALB to service containers"
+  from_port                    = var.container_port
+  to_port                      = var.container_port
+  ip_protocol                  = "TCP"
+
+}
+
+# Egress rule for HTTP traffic from ALB to service
+resource "aws_vpc_security_group_egress_rule" "service_outbound_alb" {
+  security_group_id            = aws_security_group.teamcity_alb_sg.id
+  referenced_security_group_id = aws_security_group.teamcity_service_sg.id
+  description                  = "Allow outbound HTTP traffic from ALB to service containers"
+  from_port                    = var.container_port
+  to_port                      = var.container_port
+  ip_protocol                  = "TCP"
+
+}
+
+# Grant TeamCity service access to internet
+resource "aws_vpc_security_group_egress_rule" "internet_outbound" {
+  security_group_id = aws_security_group.teamcity_service_sg.id
+  description       = "Allow outbound internet access from TeamCity service containers"
+  cidr_ipv4         = "0.0.0.0/0"
+  ip_protocol       = "-1"
+}
+
+
+#############################################
+# IAM Roles for TeamCity Module
+#############################################
+data "aws_iam_policy_document" "ecs_tasks_trust_relationship" {
+  statement {
+    effect  = "Allow"
+    actions = ["sts:AssumeRole"]
+    principals {
+      type        = "Service"
+      identifiers = ["ecs-tasks.amazonaws.com"]
+    }
+  }
+}
+data "aws_iam_policy_document" "teamcity_default_policy" {
+  # ECS
+  statement {
+    sid    = "ECSExec"
+    effect = "Allow"
+    actions = [
+      "ssmmessages:OpenDataChannel",
+      "ssmmessages:OpenControlChannel",
+      "ssmmessages:CreateDataChannel",
+      "ssmmessages:CreateControlChannel",
+    ]
+    resources = [
+      "*"
+    ]
+  }
+}
+resource "aws_iam_policy" "teamcity_default_policy" {
+  name        = "teamcity-default-policy"
+  description = "Policy granting permissions for Unreal Horde."
+  policy      = data.aws_iam_policy_document.teamcity_default_policy.json
+}
+
+resource "aws_iam_role" "teamcity_default_role" {
+  name               = "teamcity-default-role"
+  description        = "Default role for TeamCity ECS task."
+  assume_role_policy = data.aws_iam_policy_document.ecs_tasks_trust_relationship.json
+  managed_policy_arns = [
+    aws_iam_policy.teamcity_default_policy.arn
+  ]
+
+  tags = local.tags
+}
+resource "aws_iam_role" "teamcity_task_execution_role" {
+  name = "teamcity-task-execution-role"
+
+  assume_role_policy  = data.aws_iam_policy_document.ecs_tasks_trust_relationship.json
+  managed_policy_arns = ["arn:aws:iam::aws:policy/service-role/AmazonECSTaskExecutionRolePolicy"]
+}
+
+# CloudWatch Logs
+resource "aws_cloudwatch_log_group" "teamcity_log_group" {
+  #checkov:skip=CKV_AWS_158: KMS Encryption disabled by default
+  name              = "${local.name_prefix}-log-group"
+  retention_in_days = var.teamcity_cloudwatch_log_retention_in_days
+  tags              = local.tags
+}
+
+
+# Load Balancer for TeamCity Service
+resource "aws_lb" "teamcity_external_lb" {
+  name            = "${local.name_prefix}-lb"
+  security_groups = [aws_security_group.teamcity_alb_sg.id]
+
+  load_balancer_type         = "application"
+  internal                   = false
+  subnets                    = var.alb_subnets
+  drop_invalid_header_fields = true
+  tags                       = local.tags
+}
+
+# TeamCity target group for ALB
+resource "aws_lb_target_group" "teamcity_target_group" {
+  name        = "${local.name_prefix}-tg"
+  port        = var.container_port
+  protocol    = "HTTP"
+  vpc_id      = var.vpc_id
+  target_type = "ip"
+
+  health_check {
+    path                = "/healthCheck/healthy"
+    interval            = 30
+    timeout             = 5
+    healthy_threshold   = 5
+    unhealthy_threshold = 2
+    port                = var.container_port
+    protocol            = "HTTP"
+    matcher             = "200"
+  }
+
+  tags = local.tags
+}
+
+# ALB HTTPS Listener
+resource "aws_lb_listener" "teamcity_listener" {
+  load_balancer_arn = aws_lb.teamcity_external_lb.arn
+  port              = "443"
+  protocol          = "HTTPS"
+  ssl_policy        = "ELBSecurityPolicy-TLS13-1-2-2021-06"
+  certificate_arn   = var.alb_certificate_arn
+  default_action {
+    type             = "forward"
+    target_group_arn = aws_lb_target_group.teamcity_target_group.arn
+  }
+  tags = local.tags
+}
+
+
+
+
+# #######################################
+# # TeamCity Aurora Serverless Database #
+# #######################################
+# Subnet group
+resource "aws_db_subnet_group" "teamcity_db_subnet_group" {
+  name       = "${local.name_prefix}-db-subnet-group"
+  subnet_ids = var.service_subnets
+  tags       = local.tags
+}
+
+# # RDS instance with Aurora serverless engine
+resource "aws_rds_cluster" "teamcity_db_cluster" {
+  cluster_identifier   = "teamcity-cluster"
+  engine               = "aurora-postgresql"
+  engine_mode          = "provisioned"
+  engine_version       = "16.6"
+  database_name        = "teamcity"
+  master_username      = "teamcity"
+  master_password      = "teamcity2025"
+  storage_encrypted    = true
+  db_subnet_group_name = aws_db_subnet_group.teamcity_db_subnet_group.id
+  vpc_security_group_ids = [
+    aws_security_group.teamcity_db_sg.id
+  ]
+
+
+  serverlessv2_scaling_configuration {
+    max_capacity             = 1.0
+    min_capacity             = 0.0
+    seconds_until_auto_pause = 3600
+  }
+}
path: modules/teamcity/main.tf
user: login: github-advanced-security[bot]
body: ## Ensure RDS Clusters are encrypted using KMS CMKs

Ensure RDS Clusters are encrypted using KMS CMKs

[Show more details](https://github.com/aws-games/cloud-game-development-toolkit/security/code-scanning/810)
created_at: 2025-02-06T21:14:48Z
author_association: NONE
diff_hunk: @@ -0,0 +1,492 @@
+###################################
+# ECS Cluster for TeamCity Server #
+###################################
+
+resource "aws_ecs_cluster" "teamcity_cluster" {
+  name = "${local.name_prefix}-cluster"
+
+  setting {
+    name  = "containerInsights"
+    value = "enabled"
+  }
+
+  tags = local.tags
+}
+
+# TeamCity Task Definition
+resource "aws_ecs_task_definition" "teamcity_task_definition" {
+  family                   = var.name
+  requires_compatibilities = ["FARGATE"]
+  network_mode             = "awsvpc"
+  cpu                      = var.container_cpu
+  memory                   = var.container_memory
+
+  container_definitions = jsonencode([
+    {
+      name      = var.container_name
+      image     = local.image
+      cpu       = var.container_cpu
+      memory    = var.container_memory
+      essential = true
+      portMappings = [
+        {
+          containerPort = var.container_port
+          hostPort      = var.container_port
+        }
+      ]
+      logConfiguration = {
+        logDriver = "awslogs"
+        options = {
+          awslogs-group         = aws_cloudwatch_log_group.teamcity_log_group.name
+          awslogs-region        = data.aws_region.current.name
+          awslogs-stream-prefix = "[APP]"
+        }
+      }
+      mountPoints = [
+        {
+          sourceVolume  = "teamcity_data",
+          containerPath = "/data/teamcity_server/datadir"
+          readOnly      = false
+        },
+        {
+          sourceVolume  = "teamcity_logs",
+          containerPath = "/data/teamcity_server/logs"
+          readOnly      = false
+        }
+      ]
+      # Set environment variables for database connection string
+      environment = [
+        {
+          name  = "TEAMCITY_DB_URL"
+          value = local.database_connection_string
+        },
+        {
+          name  = "TEAMCITY_DB_USER"
+          value = local.database_user
+        },
+        {
+          name  = "TEAMCITY_DB_PASSWORD"
+          value = local.database_password
+        },
+        {
+          name  = "TEAMCITY_LOGS_PATH"
+          value = "/data/teamcity_server/logs"
+        }
+      ]
+    }
+  ])
+  tags = {
+    Name = var.name
+  }
+  task_role_arn      = aws_iam_role.teamcity_default_role.arn
+  execution_role_arn = aws_iam_role.teamcity_task_execution_role.arn
+
+  volume {
+    name = "teamcity_data"
+    efs_volume_configuration {
+      file_system_id          = aws_efs_file_system.teamcity_efs_file_system.id
+      transit_encryption      = "ENABLED"
+      transit_encryption_port = 2999
+      authorization_config {
+        access_point_id = aws_efs_access_point.teamcity_efs_data_access_point.id
+        iam             = "ENABLED"
+      }
+    }
+  }
+
+  volume {
+    name = "teamcity_logs"
+    efs_volume_configuration {
+      file_system_id          = aws_efs_file_system.teamcity_efs_file_system.id
+      transit_encryption      = "ENABLED"
+      transit_encryption_port = 2998
+      authorization_config {
+        access_point_id = aws_efs_access_point.teamcity_efs_logs_access_point.id
+        iam             = "ENABLED"
+      }
+    }
+  }
+}
+
+
+
+
+# ECS Service
+resource "aws_ecs_service" "teamcity" {
+  name = local.name_prefix
+
+  cluster                = aws_ecs_cluster.teamcity_cluster.name
+  task_definition        = aws_ecs_task_definition.teamcity_task_definition.arn
+  launch_type            = "FARGATE"
+  desired_count          = 1    #TODO: make this configurable
+  force_new_deployment   = true #TODO: make this configurable
+  enable_execute_command = true #TODO: make this configurable
+
+  wait_for_steady_state = true
+
+  network_configuration {
+    subnets         = var.service_subnets
+    security_groups = [aws_security_group.teamcity_service_sg.id]
+  }
+  load_balancer {
+    target_group_arn = aws_lb_target_group.teamcity_target_group.arn
+    container_name   = var.container_name
+    container_port   = var.container_port
+  }
+
+  tags = local.tags
+}
+
+# TeamCity service security group
+resource "aws_security_group" "teamcity_service_sg" {
+  name   = "${local.name_prefix}-service-sg"
+  vpc_id = var.vpc_id
+
+  tags = local.tags
+}
+
+# TeamCity EFS security group
+resource "aws_security_group" "teamcity_efs_sg" {
+  name        = "${local.name_prefix}-efs-sg"
+  description = "TeamCity EFS mount target security group"
+  vpc_id      = var.vpc_id
+
+  tags = local.tags
+}
+
+# Ingress rule for NFS traffic from service to EFS
+resource "aws_vpc_security_group_ingress_rule" "service_efs" {
+  security_group_id            = aws_security_group.teamcity_efs_sg.id
+  referenced_security_group_id = aws_security_group.teamcity_service_sg.id
+  description                  = "Allow inbound access from TeamCity service containers to EFS"
+  ip_protocol                  = "TCP"
+  from_port                    = 2049
+  to_port                      = 2049
+}
+
+# Ingress rule for NFS traffic from EFS to service
+resource "aws_vpc_security_group_ingress_rule" "efs_service" {
+  security_group_id            = aws_security_group.teamcity_service_sg.id
+  referenced_security_group_id = aws_security_group.teamcity_efs_sg.id
+  description                  = "Allow inbound access from EFS to TeamCity service containers"
+  ip_protocol                  = "TCP"
+  from_port                    = 2049
+  to_port                      = 2049
+}
+
+# TeamCity Aurora Serverless PostgreSQL security group
+resource "aws_security_group" "teamcity_db_sg" {
+  name        = "${local.name_prefix}-db-sg"
+  description = "TeamCity DB security group"
+  vpc_id      = var.vpc_id
+
+  tags = local.tags
+}
+
+# Ingress rule for PostgreSQL from service to database cluster
+resource "aws_vpc_security_group_ingress_rule" "service_db" {
+  security_group_id            = aws_security_group.teamcity_db_sg.id
+  referenced_security_group_id = aws_security_group.teamcity_service_sg.id
+  description                  = "Allow inbound access from TeamCity service containers to DB"
+  ip_protocol                  = "TCP"
+  from_port                    = 5432
+  to_port                      = 5432
+}
+
+# Ingress rule for PostgreSQL from database cluster to service
+resource "aws_vpc_security_group_ingress_rule" "db_service" {
+  security_group_id            = aws_security_group.teamcity_service_sg.id
+  referenced_security_group_id = aws_security_group.teamcity_db_sg.id
+  description                  = "Allow inbound access from DB to TeamCity service containers"
+  ip_protocol                  = "TCP"
+  from_port                    = 5432
+  to_port                      = 5432
+}
+
+# Egress rule for database to let all traffic out
+resource "aws_vpc_security_group_egress_rule" "db_outbound" {
+
+  security_group_id = aws_security_group.teamcity_db_sg.id
+  cidr_ipv4         = "0.0.0.0/0"
+  ip_protocol       = "-1"
+}
+
+# TeamCity ALB security group
+resource "aws_security_group" "teamcity_alb_sg" {
+  name   = "${local.name_prefix}-alb-sg"
+  vpc_id = var.vpc_id
+
+  tags = local.tags
+}
+
+# Ingress rule to allow ALB to send health checks to service
+resource "aws_vpc_security_group_ingress_rule" "alb_service_healthcheck" {
+  security_group_id            = aws_security_group.teamcity_service_sg.id
+  referenced_security_group_id = aws_security_group.teamcity_alb_sg.id
+  description                  = "Allow health checks from ALB to service containers"
+  ip_protocol                  = "TCP"
+  from_port                    = 80
+  to_port                      = 80
+}
+
+# Ingress rule for HTTP traffic from ALB to service
+resource "aws_vpc_security_group_ingress_rule" "service_inbound_alb" {
+  security_group_id            = aws_security_group.teamcity_service_sg.id
+  referenced_security_group_id = aws_security_group.teamcity_alb_sg.id
+  description                  = "Allow inbound HTTP traffic from ALB to service containers"
+  from_port                    = var.container_port
+  to_port                      = var.container_port
+  ip_protocol                  = "TCP"
+
+}
+
+# Egress rule for HTTP traffic from ALB to service
+resource "aws_vpc_security_group_egress_rule" "service_outbound_alb" {
+  security_group_id            = aws_security_group.teamcity_alb_sg.id
+  referenced_security_group_id = aws_security_group.teamcity_service_sg.id
+  description                  = "Allow outbound HTTP traffic from ALB to service containers"
+  from_port                    = var.container_port
+  to_port                      = var.container_port
+  ip_protocol                  = "TCP"
+
+}
+
+# Grant TeamCity service access to internet
+resource "aws_vpc_security_group_egress_rule" "internet_outbound" {
+  security_group_id = aws_security_group.teamcity_service_sg.id
+  description       = "Allow outbound internet access from TeamCity service containers"
+  cidr_ipv4         = "0.0.0.0/0"
+  ip_protocol       = "-1"
+}
+
+
+#############################################
+# IAM Roles for TeamCity Module
+#############################################
+data "aws_iam_policy_document" "ecs_tasks_trust_relationship" {
+  statement {
+    effect  = "Allow"
+    actions = ["sts:AssumeRole"]
+    principals {
+      type        = "Service"
+      identifiers = ["ecs-tasks.amazonaws.com"]
+    }
+  }
+}
+data "aws_iam_policy_document" "teamcity_default_policy" {
+  # ECS
+  statement {
+    sid    = "ECSExec"
+    effect = "Allow"
+    actions = [
+      "ssmmessages:OpenDataChannel",
+      "ssmmessages:OpenControlChannel",
+      "ssmmessages:CreateDataChannel",
+      "ssmmessages:CreateControlChannel",
+    ]
+    resources = [
+      "*"
+    ]
+  }
+}
+resource "aws_iam_policy" "teamcity_default_policy" {
+  name        = "teamcity-default-policy"
+  description = "Policy granting permissions for Unreal Horde."
+  policy      = data.aws_iam_policy_document.teamcity_default_policy.json
+}
+
+resource "aws_iam_role" "teamcity_default_role" {
+  name               = "teamcity-default-role"
+  description        = "Default role for TeamCity ECS task."
+  assume_role_policy = data.aws_iam_policy_document.ecs_tasks_trust_relationship.json
+  managed_policy_arns = [
+    aws_iam_policy.teamcity_default_policy.arn
+  ]
+
+  tags = local.tags
+}
+resource "aws_iam_role" "teamcity_task_execution_role" {
+  name = "teamcity-task-execution-role"
+
+  assume_role_policy  = data.aws_iam_policy_document.ecs_tasks_trust_relationship.json
+  managed_policy_arns = ["arn:aws:iam::aws:policy/service-role/AmazonECSTaskExecutionRolePolicy"]
+}
+
+# CloudWatch Logs
+resource "aws_cloudwatch_log_group" "teamcity_log_group" {
+  #checkov:skip=CKV_AWS_158: KMS Encryption disabled by default
+  name              = "${local.name_prefix}-log-group"
+  retention_in_days = var.teamcity_cloudwatch_log_retention_in_days
+  tags              = local.tags
+}
+
+
+# Load Balancer for TeamCity Service
+resource "aws_lb" "teamcity_external_lb" {
+  name            = "${local.name_prefix}-lb"
+  security_groups = [aws_security_group.teamcity_alb_sg.id]
+
+  load_balancer_type         = "application"
+  internal                   = false
+  subnets                    = var.alb_subnets
+  drop_invalid_header_fields = true
+  tags                       = local.tags
+}
+
+# TeamCity target group for ALB
+resource "aws_lb_target_group" "teamcity_target_group" {
+  name        = "${local.name_prefix}-tg"
+  port        = var.container_port
+  protocol    = "HTTP"
+  vpc_id      = var.vpc_id
+  target_type = "ip"
+
+  health_check {
+    path                = "/healthCheck/healthy"
+    interval            = 30
+    timeout             = 5
+    healthy_threshold   = 5
+    unhealthy_threshold = 2
+    port                = var.container_port
+    protocol            = "HTTP"
+    matcher             = "200"
+  }
+
+  tags = local.tags
+}
+
+# ALB HTTPS Listener
+resource "aws_lb_listener" "teamcity_listener" {
+  load_balancer_arn = aws_lb.teamcity_external_lb.arn
+  port              = "443"
+  protocol          = "HTTPS"
+  ssl_policy        = "ELBSecurityPolicy-TLS13-1-2-2021-06"
+  certificate_arn   = var.alb_certificate_arn
+  default_action {
+    type             = "forward"
+    target_group_arn = aws_lb_target_group.teamcity_target_group.arn
+  }
+  tags = local.tags
+}
+
+
+
+
+# #######################################
+# # TeamCity Aurora Serverless Database #
+# #######################################
+# Subnet group
+resource "aws_db_subnet_group" "teamcity_db_subnet_group" {
+  name       = "${local.name_prefix}-db-subnet-group"
+  subnet_ids = var.service_subnets
+  tags       = local.tags
+}
+
+# # RDS instance with Aurora serverless engine
+resource "aws_rds_cluster" "teamcity_db_cluster" {
+  cluster_identifier   = "teamcity-cluster"
+  engine               = "aurora-postgresql"
+  engine_mode          = "provisioned"
+  engine_version       = "16.6"
+  database_name        = "teamcity"
+  master_username      = "teamcity"
+  master_password      = "teamcity2025"
+  storage_encrypted    = true
+  db_subnet_group_name = aws_db_subnet_group.teamcity_db_subnet_group.id
+  vpc_security_group_ids = [
+    aws_security_group.teamcity_db_sg.id
+  ]
+
+
+  serverlessv2_scaling_configuration {
+    max_capacity             = 1.0
+    min_capacity             = 0.0
+    seconds_until_auto_pause = 3600
+  }
+}
path: modules/teamcity/main.tf
user: login: github-advanced-security[bot]
body: ## Ensure that RDS clusters have deletion protection enabled

Ensure that RDS clusters have deletion protection enabled

[Show more details](https://github.com/aws-games/cloud-game-development-toolkit/security/code-scanning/811)
created_at: 2025-02-06T21:14:48Z
author_association: NONE
diff_hunk: @@ -0,0 +1,492 @@
+###################################
+# ECS Cluster for TeamCity Server #
+###################################
+
+resource "aws_ecs_cluster" "teamcity_cluster" {
+  name = "${local.name_prefix}-cluster"
+
+  setting {
+    name  = "containerInsights"
+    value = "enabled"
+  }
+
+  tags = local.tags
+}
+
+# TeamCity Task Definition
+resource "aws_ecs_task_definition" "teamcity_task_definition" {
+  family                   = var.name
+  requires_compatibilities = ["FARGATE"]
+  network_mode             = "awsvpc"
+  cpu                      = var.container_cpu
+  memory                   = var.container_memory
+
+  container_definitions = jsonencode([
+    {
+      name      = var.container_name
+      image     = local.image
+      cpu       = var.container_cpu
+      memory    = var.container_memory
+      essential = true
+      portMappings = [
+        {
+          containerPort = var.container_port
+          hostPort      = var.container_port
+        }
+      ]
+      logConfiguration = {
+        logDriver = "awslogs"
+        options = {
+          awslogs-group         = aws_cloudwatch_log_group.teamcity_log_group.name
+          awslogs-region        = data.aws_region.current.name
+          awslogs-stream-prefix = "[APP]"
+        }
+      }
+      mountPoints = [
+        {
+          sourceVolume  = "teamcity_data",
+          containerPath = "/data/teamcity_server/datadir"
+          readOnly      = false
+        },
+        {
+          sourceVolume  = "teamcity_logs",
+          containerPath = "/data/teamcity_server/logs"
+          readOnly      = false
+        }
+      ]
+      # Set environment variables for database connection string
+      environment = [
+        {
+          name  = "TEAMCITY_DB_URL"
+          value = local.database_connection_string
+        },
+        {
+          name  = "TEAMCITY_DB_USER"
+          value = local.database_user
+        },
+        {
+          name  = "TEAMCITY_DB_PASSWORD"
+          value = local.database_password
+        },
+        {
+          name  = "TEAMCITY_LOGS_PATH"
+          value = "/data/teamcity_server/logs"
+        }
+      ]
+    }
+  ])
+  tags = {
+    Name = var.name
+  }
+  task_role_arn      = aws_iam_role.teamcity_default_role.arn
+  execution_role_arn = aws_iam_role.teamcity_task_execution_role.arn
+
+  volume {
+    name = "teamcity_data"
+    efs_volume_configuration {
+      file_system_id          = aws_efs_file_system.teamcity_efs_file_system.id
+      transit_encryption      = "ENABLED"
+      transit_encryption_port = 2999
+      authorization_config {
+        access_point_id = aws_efs_access_point.teamcity_efs_data_access_point.id
+        iam             = "ENABLED"
+      }
+    }
+  }
+
+  volume {
+    name = "teamcity_logs"
+    efs_volume_configuration {
+      file_system_id          = aws_efs_file_system.teamcity_efs_file_system.id
+      transit_encryption      = "ENABLED"
+      transit_encryption_port = 2998
+      authorization_config {
+        access_point_id = aws_efs_access_point.teamcity_efs_logs_access_point.id
+        iam             = "ENABLED"
+      }
+    }
+  }
+}
+
+
+
+
+# ECS Service
+resource "aws_ecs_service" "teamcity" {
+  name = local.name_prefix
+
+  cluster                = aws_ecs_cluster.teamcity_cluster.name
+  task_definition        = aws_ecs_task_definition.teamcity_task_definition.arn
+  launch_type            = "FARGATE"
+  desired_count          = 1    #TODO: make this configurable
+  force_new_deployment   = true #TODO: make this configurable
+  enable_execute_command = true #TODO: make this configurable
+
+  wait_for_steady_state = true
+
+  network_configuration {
+    subnets         = var.service_subnets
+    security_groups = [aws_security_group.teamcity_service_sg.id]
+  }
+  load_balancer {
+    target_group_arn = aws_lb_target_group.teamcity_target_group.arn
+    container_name   = var.container_name
+    container_port   = var.container_port
+  }
+
+  tags = local.tags
+}
+
+# TeamCity service security group
+resource "aws_security_group" "teamcity_service_sg" {
+  name   = "${local.name_prefix}-service-sg"
+  vpc_id = var.vpc_id
+
+  tags = local.tags
+}
+
+# TeamCity EFS security group
+resource "aws_security_group" "teamcity_efs_sg" {
+  name        = "${local.name_prefix}-efs-sg"
+  description = "TeamCity EFS mount target security group"
+  vpc_id      = var.vpc_id
+
+  tags = local.tags
+}
+
+# Ingress rule for NFS traffic from service to EFS
+resource "aws_vpc_security_group_ingress_rule" "service_efs" {
+  security_group_id            = aws_security_group.teamcity_efs_sg.id
+  referenced_security_group_id = aws_security_group.teamcity_service_sg.id
+  description                  = "Allow inbound access from TeamCity service containers to EFS"
+  ip_protocol                  = "TCP"
+  from_port                    = 2049
+  to_port                      = 2049
+}
+
+# Ingress rule for NFS traffic from EFS to service
+resource "aws_vpc_security_group_ingress_rule" "efs_service" {
+  security_group_id            = aws_security_group.teamcity_service_sg.id
+  referenced_security_group_id = aws_security_group.teamcity_efs_sg.id
+  description                  = "Allow inbound access from EFS to TeamCity service containers"
+  ip_protocol                  = "TCP"
+  from_port                    = 2049
+  to_port                      = 2049
+}
+
+# TeamCity Aurora Serverless PostgreSQL security group
+resource "aws_security_group" "teamcity_db_sg" {
+  name        = "${local.name_prefix}-db-sg"
+  description = "TeamCity DB security group"
+  vpc_id      = var.vpc_id
+
+  tags = local.tags
+}
+
+# Ingress rule for PostgreSQL from service to database cluster
+resource "aws_vpc_security_group_ingress_rule" "service_db" {
+  security_group_id            = aws_security_group.teamcity_db_sg.id
+  referenced_security_group_id = aws_security_group.teamcity_service_sg.id
+  description                  = "Allow inbound access from TeamCity service containers to DB"
+  ip_protocol                  = "TCP"
+  from_port                    = 5432
+  to_port                      = 5432
+}
+
+# Ingress rule for PostgreSQL from database cluster to service
+resource "aws_vpc_security_group_ingress_rule" "db_service" {
+  security_group_id            = aws_security_group.teamcity_service_sg.id
+  referenced_security_group_id = aws_security_group.teamcity_db_sg.id
+  description                  = "Allow inbound access from DB to TeamCity service containers"
+  ip_protocol                  = "TCP"
+  from_port                    = 5432
+  to_port                      = 5432
+}
+
+# Egress rule for database to let all traffic out
+resource "aws_vpc_security_group_egress_rule" "db_outbound" {
+
+  security_group_id = aws_security_group.teamcity_db_sg.id
+  cidr_ipv4         = "0.0.0.0/0"
+  ip_protocol       = "-1"
+}
+
+# TeamCity ALB security group
+resource "aws_security_group" "teamcity_alb_sg" {
+  name   = "${local.name_prefix}-alb-sg"
+  vpc_id = var.vpc_id
+
+  tags = local.tags
+}
+
+# Ingress rule to allow ALB to send health checks to service
+resource "aws_vpc_security_group_ingress_rule" "alb_service_healthcheck" {
+  security_group_id            = aws_security_group.teamcity_service_sg.id
+  referenced_security_group_id = aws_security_group.teamcity_alb_sg.id
+  description                  = "Allow health checks from ALB to service containers"
+  ip_protocol                  = "TCP"
+  from_port                    = 80
+  to_port                      = 80
+}
+
+# Ingress rule for HTTP traffic from ALB to service
+resource "aws_vpc_security_group_ingress_rule" "service_inbound_alb" {
+  security_group_id            = aws_security_group.teamcity_service_sg.id
+  referenced_security_group_id = aws_security_group.teamcity_alb_sg.id
+  description                  = "Allow inbound HTTP traffic from ALB to service containers"
+  from_port                    = var.container_port
+  to_port                      = var.container_port
+  ip_protocol                  = "TCP"
+
+}
+
+# Egress rule for HTTP traffic from ALB to service
+resource "aws_vpc_security_group_egress_rule" "service_outbound_alb" {
+  security_group_id            = aws_security_group.teamcity_alb_sg.id
+  referenced_security_group_id = aws_security_group.teamcity_service_sg.id
+  description                  = "Allow outbound HTTP traffic from ALB to service containers"
+  from_port                    = var.container_port
+  to_port                      = var.container_port
+  ip_protocol                  = "TCP"
+
+}
+
+# Grant TeamCity service access to internet
+resource "aws_vpc_security_group_egress_rule" "internet_outbound" {
+  security_group_id = aws_security_group.teamcity_service_sg.id
+  description       = "Allow outbound internet access from TeamCity service containers"
+  cidr_ipv4         = "0.0.0.0/0"
+  ip_protocol       = "-1"
+}
+
+
+#############################################
+# IAM Roles for TeamCity Module
+#############################################
+data "aws_iam_policy_document" "ecs_tasks_trust_relationship" {
+  statement {
+    effect  = "Allow"
+    actions = ["sts:AssumeRole"]
+    principals {
+      type        = "Service"
+      identifiers = ["ecs-tasks.amazonaws.com"]
+    }
+  }
+}
+data "aws_iam_policy_document" "teamcity_default_policy" {
+  # ECS
+  statement {
+    sid    = "ECSExec"
+    effect = "Allow"
+    actions = [
+      "ssmmessages:OpenDataChannel",
+      "ssmmessages:OpenControlChannel",
+      "ssmmessages:CreateDataChannel",
+      "ssmmessages:CreateControlChannel",
+    ]
+    resources = [
+      "*"
+    ]
+  }
+}
+resource "aws_iam_policy" "teamcity_default_policy" {
+  name        = "teamcity-default-policy"
+  description = "Policy granting permissions for Unreal Horde."
+  policy      = data.aws_iam_policy_document.teamcity_default_policy.json
+}
+
+resource "aws_iam_role" "teamcity_default_role" {
+  name               = "teamcity-default-role"
+  description        = "Default role for TeamCity ECS task."
+  assume_role_policy = data.aws_iam_policy_document.ecs_tasks_trust_relationship.json
+  managed_policy_arns = [
+    aws_iam_policy.teamcity_default_policy.arn
+  ]
+
+  tags = local.tags
+}
+resource "aws_iam_role" "teamcity_task_execution_role" {
+  name = "teamcity-task-execution-role"
+
+  assume_role_policy  = data.aws_iam_policy_document.ecs_tasks_trust_relationship.json
+  managed_policy_arns = ["arn:aws:iam::aws:policy/service-role/AmazonECSTaskExecutionRolePolicy"]
+}
+
+# CloudWatch Logs
+resource "aws_cloudwatch_log_group" "teamcity_log_group" {
+  #checkov:skip=CKV_AWS_158: KMS Encryption disabled by default
+  name              = "${local.name_prefix}-log-group"
+  retention_in_days = var.teamcity_cloudwatch_log_retention_in_days
+  tags              = local.tags
+}
+
+
+# Load Balancer for TeamCity Service
+resource "aws_lb" "teamcity_external_lb" {
+  name            = "${local.name_prefix}-lb"
+  security_groups = [aws_security_group.teamcity_alb_sg.id]
+
+  load_balancer_type         = "application"
+  internal                   = false
+  subnets                    = var.alb_subnets
+  drop_invalid_header_fields = true
+  tags                       = local.tags
+}
+
+# TeamCity target group for ALB
+resource "aws_lb_target_group" "teamcity_target_group" {
+  name        = "${local.name_prefix}-tg"
+  port        = var.container_port
+  protocol    = "HTTP"
+  vpc_id      = var.vpc_id
+  target_type = "ip"
+
+  health_check {
+    path                = "/healthCheck/healthy"
+    interval            = 30
+    timeout             = 5
+    healthy_threshold   = 5
+    unhealthy_threshold = 2
+    port                = var.container_port
+    protocol            = "HTTP"
+    matcher             = "200"
+  }
+
+  tags = local.tags
+}
+
+# ALB HTTPS Listener
+resource "aws_lb_listener" "teamcity_listener" {
+  load_balancer_arn = aws_lb.teamcity_external_lb.arn
+  port              = "443"
+  protocol          = "HTTPS"
+  ssl_policy        = "ELBSecurityPolicy-TLS13-1-2-2021-06"
+  certificate_arn   = var.alb_certificate_arn
+  default_action {
+    type             = "forward"
+    target_group_arn = aws_lb_target_group.teamcity_target_group.arn
+  }
+  tags = local.tags
+}
+
+
+
+
+# #######################################
+# # TeamCity Aurora Serverless Database #
+# #######################################
+# Subnet group
+resource "aws_db_subnet_group" "teamcity_db_subnet_group" {
+  name       = "${local.name_prefix}-db-subnet-group"
+  subnet_ids = var.service_subnets
+  tags       = local.tags
+}
+
+# # RDS instance with Aurora serverless engine
+resource "aws_rds_cluster" "teamcity_db_cluster" {
+  cluster_identifier   = "teamcity-cluster"
+  engine               = "aurora-postgresql"
+  engine_mode          = "provisioned"
+  engine_version       = "16.6"
+  database_name        = "teamcity"
+  master_username      = "teamcity"
+  master_password      = "teamcity2025"
+  storage_encrypted    = true
+  db_subnet_group_name = aws_db_subnet_group.teamcity_db_subnet_group.id
+  vpc_security_group_ids = [
+    aws_security_group.teamcity_db_sg.id
+  ]
+
+
+  serverlessv2_scaling_configuration {
+    max_capacity             = 1.0
+    min_capacity             = 0.0
+    seconds_until_auto_pause = 3600
+  }
+}
path: modules/teamcity/main.tf
user: login: github-advanced-security[bot]
body: ## Ensure Postgres RDS as aws_rds_cluster has Query Logging enabled

Ensure Postgres RDS as aws_rds_cluster has Query Logging enabled

[Show more details](https://github.com/aws-games/cloud-game-development-toolkit/security/code-scanning/816)
created_at: 2025-02-06T21:14:48Z
author_association: NONE
diff_hunk: @@ -0,0 +1,492 @@
+###################################
+# ECS Cluster for TeamCity Server #
+###################################
+
+resource "aws_ecs_cluster" "teamcity_cluster" {
+  name = "${local.name_prefix}-cluster"
+
+  setting {
+    name  = "containerInsights"
+    value = "enabled"
+  }
+
+  tags = local.tags
+}
+
+# TeamCity Task Definition
+resource "aws_ecs_task_definition" "teamcity_task_definition" {
+  family                   = var.name
+  requires_compatibilities = ["FARGATE"]
+  network_mode             = "awsvpc"
+  cpu                      = var.container_cpu
+  memory                   = var.container_memory
+
+  container_definitions = jsonencode([
+    {
+      name      = var.container_name
+      image     = local.image
+      cpu       = var.container_cpu
+      memory    = var.container_memory
+      essential = true
+      portMappings = [
+        {
+          containerPort = var.container_port
+          hostPort      = var.container_port
+        }
+      ]
+      logConfiguration = {
+        logDriver = "awslogs"
+        options = {
+          awslogs-group         = aws_cloudwatch_log_group.teamcity_log_group.name
+          awslogs-region        = data.aws_region.current.name
+          awslogs-stream-prefix = "[APP]"
+        }
+      }
+      mountPoints = [
+        {
+          sourceVolume  = "teamcity_data",
+          containerPath = "/data/teamcity_server/datadir"
+          readOnly      = false
+        },
+        {
+          sourceVolume  = "teamcity_logs",
+          containerPath = "/data/teamcity_server/logs"
+          readOnly      = false
+        }
+      ]
+      # Set environment variables for database connection string
+      environment = [
+        {
+          name  = "TEAMCITY_DB_URL"
+          value = local.database_connection_string
+        },
+        {
+          name  = "TEAMCITY_DB_USER"
+          value = local.database_user
+        },
+        {
+          name  = "TEAMCITY_DB_PASSWORD"
+          value = local.database_password
+        },
+        {
+          name  = "TEAMCITY_LOGS_PATH"
+          value = "/data/teamcity_server/logs"
+        }
+      ]
+    }
+  ])
+  tags = {
+    Name = var.name
+  }
+  task_role_arn      = aws_iam_role.teamcity_default_role.arn
+  execution_role_arn = aws_iam_role.teamcity_task_execution_role.arn
+
+  volume {
+    name = "teamcity_data"
+    efs_volume_configuration {
+      file_system_id          = aws_efs_file_system.teamcity_efs_file_system.id
+      transit_encryption      = "ENABLED"
+      transit_encryption_port = 2999
+      authorization_config {
+        access_point_id = aws_efs_access_point.teamcity_efs_data_access_point.id
+        iam             = "ENABLED"
+      }
+    }
+  }
+
+  volume {
+    name = "teamcity_logs"
+    efs_volume_configuration {
+      file_system_id          = aws_efs_file_system.teamcity_efs_file_system.id
+      transit_encryption      = "ENABLED"
+      transit_encryption_port = 2998
+      authorization_config {
+        access_point_id = aws_efs_access_point.teamcity_efs_logs_access_point.id
+        iam             = "ENABLED"
+      }
+    }
+  }
+}
+
+
+
+
+# ECS Service
+resource "aws_ecs_service" "teamcity" {
+  name = local.name_prefix
+
+  cluster                = aws_ecs_cluster.teamcity_cluster.name
+  task_definition        = aws_ecs_task_definition.teamcity_task_definition.arn
+  launch_type            = "FARGATE"
+  desired_count          = 1    #TODO: make this configurable
+  force_new_deployment   = true #TODO: make this configurable
+  enable_execute_command = true #TODO: make this configurable
+
+  wait_for_steady_state = true
+
+  network_configuration {
+    subnets         = var.service_subnets
+    security_groups = [aws_security_group.teamcity_service_sg.id]
+  }
+  load_balancer {
+    target_group_arn = aws_lb_target_group.teamcity_target_group.arn
+    container_name   = var.container_name
+    container_port   = var.container_port
+  }
+
+  tags = local.tags
+}
+
+# TeamCity service security group
+resource "aws_security_group" "teamcity_service_sg" {
+  name   = "${local.name_prefix}-service-sg"
+  vpc_id = var.vpc_id
+
+  tags = local.tags
+}
+
+# TeamCity EFS security group
+resource "aws_security_group" "teamcity_efs_sg" {
+  name        = "${local.name_prefix}-efs-sg"
+  description = "TeamCity EFS mount target security group"
+  vpc_id      = var.vpc_id
+
+  tags = local.tags
+}
+
+# Ingress rule for NFS traffic from service to EFS
+resource "aws_vpc_security_group_ingress_rule" "service_efs" {
+  security_group_id            = aws_security_group.teamcity_efs_sg.id
+  referenced_security_group_id = aws_security_group.teamcity_service_sg.id
+  description                  = "Allow inbound access from TeamCity service containers to EFS"
+  ip_protocol                  = "TCP"
+  from_port                    = 2049
+  to_port                      = 2049
+}
+
+# Ingress rule for NFS traffic from EFS to service
+resource "aws_vpc_security_group_ingress_rule" "efs_service" {
+  security_group_id            = aws_security_group.teamcity_service_sg.id
+  referenced_security_group_id = aws_security_group.teamcity_efs_sg.id
+  description                  = "Allow inbound access from EFS to TeamCity service containers"
+  ip_protocol                  = "TCP"
+  from_port                    = 2049
+  to_port                      = 2049
+}
+
+# TeamCity Aurora Serverless PostgreSQL security group
+resource "aws_security_group" "teamcity_db_sg" {
+  name        = "${local.name_prefix}-db-sg"
+  description = "TeamCity DB security group"
+  vpc_id      = var.vpc_id
+
+  tags = local.tags
+}
+
+# Ingress rule for PostgreSQL from service to database cluster
+resource "aws_vpc_security_group_ingress_rule" "service_db" {
+  security_group_id            = aws_security_group.teamcity_db_sg.id
+  referenced_security_group_id = aws_security_group.teamcity_service_sg.id
+  description                  = "Allow inbound access from TeamCity service containers to DB"
+  ip_protocol                  = "TCP"
+  from_port                    = 5432
+  to_port                      = 5432
+}
+
+# Ingress rule for PostgreSQL from database cluster to service
+resource "aws_vpc_security_group_ingress_rule" "db_service" {
+  security_group_id            = aws_security_group.teamcity_service_sg.id
+  referenced_security_group_id = aws_security_group.teamcity_db_sg.id
+  description                  = "Allow inbound access from DB to TeamCity service containers"
+  ip_protocol                  = "TCP"
+  from_port                    = 5432
+  to_port                      = 5432
+}
+
+# Egress rule for database to let all traffic out
+resource "aws_vpc_security_group_egress_rule" "db_outbound" {
+
+  security_group_id = aws_security_group.teamcity_db_sg.id
+  cidr_ipv4         = "0.0.0.0/0"
+  ip_protocol       = "-1"
+}
+
+# TeamCity ALB security group
+resource "aws_security_group" "teamcity_alb_sg" {
+  name   = "${local.name_prefix}-alb-sg"
+  vpc_id = var.vpc_id
+
+  tags = local.tags
+}
+
+# Ingress rule to allow ALB to send health checks to service
+resource "aws_vpc_security_group_ingress_rule" "alb_service_healthcheck" {
+  security_group_id            = aws_security_group.teamcity_service_sg.id
+  referenced_security_group_id = aws_security_group.teamcity_alb_sg.id
+  description                  = "Allow health checks from ALB to service containers"
+  ip_protocol                  = "TCP"
+  from_port                    = 80
+  to_port                      = 80
+}
+
+# Ingress rule for HTTP traffic from ALB to service
+resource "aws_vpc_security_group_ingress_rule" "service_inbound_alb" {
+  security_group_id            = aws_security_group.teamcity_service_sg.id
+  referenced_security_group_id = aws_security_group.teamcity_alb_sg.id
+  description                  = "Allow inbound HTTP traffic from ALB to service containers"
+  from_port                    = var.container_port
+  to_port                      = var.container_port
+  ip_protocol                  = "TCP"
+
+}
+
+# Egress rule for HTTP traffic from ALB to service
+resource "aws_vpc_security_group_egress_rule" "service_outbound_alb" {
+  security_group_id            = aws_security_group.teamcity_alb_sg.id
+  referenced_security_group_id = aws_security_group.teamcity_service_sg.id
+  description                  = "Allow outbound HTTP traffic from ALB to service containers"
+  from_port                    = var.container_port
+  to_port                      = var.container_port
+  ip_protocol                  = "TCP"
+
+}
+
+# Grant TeamCity service access to internet
+resource "aws_vpc_security_group_egress_rule" "internet_outbound" {
+  security_group_id = aws_security_group.teamcity_service_sg.id
+  description       = "Allow outbound internet access from TeamCity service containers"
+  cidr_ipv4         = "0.0.0.0/0"
+  ip_protocol       = "-1"
+}
+
+
+#############################################
+# IAM Roles for TeamCity Module
+#############################################
+data "aws_iam_policy_document" "ecs_tasks_trust_relationship" {
+  statement {
+    effect  = "Allow"
+    actions = ["sts:AssumeRole"]
+    principals {
+      type        = "Service"
+      identifiers = ["ecs-tasks.amazonaws.com"]
+    }
+  }
+}
+data "aws_iam_policy_document" "teamcity_default_policy" {
+  # ECS
+  statement {
+    sid    = "ECSExec"
+    effect = "Allow"
+    actions = [
+      "ssmmessages:OpenDataChannel",
+      "ssmmessages:OpenControlChannel",
+      "ssmmessages:CreateDataChannel",
+      "ssmmessages:CreateControlChannel",
+    ]
+    resources = [
+      "*"
+    ]
+  }
+}
+resource "aws_iam_policy" "teamcity_default_policy" {
+  name        = "teamcity-default-policy"
+  description = "Policy granting permissions for Unreal Horde."
+  policy      = data.aws_iam_policy_document.teamcity_default_policy.json
+}
+
+resource "aws_iam_role" "teamcity_default_role" {
+  name               = "teamcity-default-role"
+  description        = "Default role for TeamCity ECS task."
+  assume_role_policy = data.aws_iam_policy_document.ecs_tasks_trust_relationship.json
+  managed_policy_arns = [
+    aws_iam_policy.teamcity_default_policy.arn
+  ]
+
+  tags = local.tags
+}
+resource "aws_iam_role" "teamcity_task_execution_role" {
+  name = "teamcity-task-execution-role"
+
+  assume_role_policy  = data.aws_iam_policy_document.ecs_tasks_trust_relationship.json
+  managed_policy_arns = ["arn:aws:iam::aws:policy/service-role/AmazonECSTaskExecutionRolePolicy"]
+}
+
+# CloudWatch Logs
+resource "aws_cloudwatch_log_group" "teamcity_log_group" {
+  #checkov:skip=CKV_AWS_158: KMS Encryption disabled by default
+  name              = "${local.name_prefix}-log-group"
+  retention_in_days = var.teamcity_cloudwatch_log_retention_in_days
+  tags              = local.tags
+}
+
+
+# Load Balancer for TeamCity Service
+resource "aws_lb" "teamcity_external_lb" {
+  name            = "${local.name_prefix}-lb"
+  security_groups = [aws_security_group.teamcity_alb_sg.id]
+
+  load_balancer_type         = "application"
+  internal                   = false
+  subnets                    = var.alb_subnets
+  drop_invalid_header_fields = true
+  tags                       = local.tags
+}
+
+# TeamCity target group for ALB
+resource "aws_lb_target_group" "teamcity_target_group" {
+  name        = "${local.name_prefix}-tg"
+  port        = var.container_port
+  protocol    = "HTTP"
+  vpc_id      = var.vpc_id
+  target_type = "ip"
+
+  health_check {
+    path                = "/healthCheck/healthy"
+    interval            = 30
+    timeout             = 5
+    healthy_threshold   = 5
+    unhealthy_threshold = 2
+    port                = var.container_port
+    protocol            = "HTTP"
+    matcher             = "200"
+  }
+
+  tags = local.tags
+}
+
+# ALB HTTPS Listener
+resource "aws_lb_listener" "teamcity_listener" {
+  load_balancer_arn = aws_lb.teamcity_external_lb.arn
+  port              = "443"
+  protocol          = "HTTPS"
+  ssl_policy        = "ELBSecurityPolicy-TLS13-1-2-2021-06"
+  certificate_arn   = var.alb_certificate_arn
+  default_action {
+    type             = "forward"
+    target_group_arn = aws_lb_target_group.teamcity_target_group.arn
+  }
+  tags = local.tags
+}
+
+
+
+
+# #######################################
+# # TeamCity Aurora Serverless Database #
+# #######################################
+# Subnet group
+resource "aws_db_subnet_group" "teamcity_db_subnet_group" {
+  name       = "${local.name_prefix}-db-subnet-group"
+  subnet_ids = var.service_subnets
+  tags       = local.tags
+}
+
+# # RDS instance with Aurora serverless engine
+resource "aws_rds_cluster" "teamcity_db_cluster" {
+  cluster_identifier   = "teamcity-cluster"
+  engine               = "aurora-postgresql"
+  engine_mode          = "provisioned"
+  engine_version       = "16.6"
+  database_name        = "teamcity"
+  master_username      = "teamcity"
+  master_password      = "teamcity2025"
+  storage_encrypted    = true
+  db_subnet_group_name = aws_db_subnet_group.teamcity_db_subnet_group.id
+  vpc_security_group_ids = [
+    aws_security_group.teamcity_db_sg.id
+  ]
+
+
+  serverlessv2_scaling_configuration {
+    max_capacity             = 1.0
+    min_capacity             = 0.0
+    seconds_until_auto_pause = 3600
+  }
+}
path: modules/teamcity/main.tf
user: login: github-advanced-security[bot]
body: ## Ensure that RDS clusters has backup plan of AWS Backup

Ensure that RDS clusters has backup plan of AWS Backup

[Show more details](https://github.com/aws-games/cloud-game-development-toolkit/security/code-scanning/817)
created_at: 2025-02-06T21:14:48Z
author_association: NONE
diff_hunk: @@ -0,0 +1,492 @@
+###################################
+# ECS Cluster for TeamCity Server #
+###################################
+
+resource "aws_ecs_cluster" "teamcity_cluster" {
+  name = "${local.name_prefix}-cluster"
+
+  setting {
+    name  = "containerInsights"
+    value = "enabled"
+  }
+
+  tags = local.tags
+}
+
+# TeamCity Task Definition
+resource "aws_ecs_task_definition" "teamcity_task_definition" {
+  family                   = var.name
+  requires_compatibilities = ["FARGATE"]
+  network_mode             = "awsvpc"
+  cpu                      = var.container_cpu
+  memory                   = var.container_memory
+
+  container_definitions = jsonencode([
+    {
+      name      = var.container_name
+      image     = local.image
+      cpu       = var.container_cpu
+      memory    = var.container_memory
+      essential = true
+      portMappings = [
+        {
+          containerPort = var.container_port
+          hostPort      = var.container_port
+        }
+      ]
+      logConfiguration = {
+        logDriver = "awslogs"
+        options = {
+          awslogs-group         = aws_cloudwatch_log_group.teamcity_log_group.name
+          awslogs-region        = data.aws_region.current.name
+          awslogs-stream-prefix = "[APP]"
+        }
+      }
+      mountPoints = [
+        {
+          sourceVolume  = "teamcity_data",
+          containerPath = "/data/teamcity_server/datadir"
+          readOnly      = false
+        },
+        {
+          sourceVolume  = "teamcity_logs",
+          containerPath = "/data/teamcity_server/logs"
+          readOnly      = false
+        }
+      ]
+      # Set environment variables for database connection string
+      environment = [
+        {
+          name  = "TEAMCITY_DB_URL"
+          value = local.database_connection_string
+        },
+        {
+          name  = "TEAMCITY_DB_USER"
+          value = local.database_user
+        },
+        {
+          name  = "TEAMCITY_DB_PASSWORD"
+          value = local.database_password
+        },
+        {
+          name  = "TEAMCITY_LOGS_PATH"
+          value = "/data/teamcity_server/logs"
+        }
+      ]
+    }
+  ])
+  tags = {
+    Name = var.name
+  }
+  task_role_arn      = aws_iam_role.teamcity_default_role.arn
+  execution_role_arn = aws_iam_role.teamcity_task_execution_role.arn
+
+  volume {
+    name = "teamcity_data"
+    efs_volume_configuration {
+      file_system_id          = aws_efs_file_system.teamcity_efs_file_system.id
+      transit_encryption      = "ENABLED"
+      transit_encryption_port = 2999
+      authorization_config {
+        access_point_id = aws_efs_access_point.teamcity_efs_data_access_point.id
+        iam             = "ENABLED"
+      }
+    }
+  }
+
+  volume {
+    name = "teamcity_logs"
+    efs_volume_configuration {
+      file_system_id          = aws_efs_file_system.teamcity_efs_file_system.id
+      transit_encryption      = "ENABLED"
+      transit_encryption_port = 2998
+      authorization_config {
+        access_point_id = aws_efs_access_point.teamcity_efs_logs_access_point.id
+        iam             = "ENABLED"
+      }
+    }
+  }
+}
+
+
+
+
+# ECS Service
+resource "aws_ecs_service" "teamcity" {
+  name = local.name_prefix
+
+  cluster                = aws_ecs_cluster.teamcity_cluster.name
+  task_definition        = aws_ecs_task_definition.teamcity_task_definition.arn
+  launch_type            = "FARGATE"
+  desired_count          = 1    #TODO: make this configurable
+  force_new_deployment   = true #TODO: make this configurable
+  enable_execute_command = true #TODO: make this configurable
+
+  wait_for_steady_state = true
+
+  network_configuration {
+    subnets         = var.service_subnets
+    security_groups = [aws_security_group.teamcity_service_sg.id]
+  }
+  load_balancer {
+    target_group_arn = aws_lb_target_group.teamcity_target_group.arn
+    container_name   = var.container_name
+    container_port   = var.container_port
+  }
+
+  tags = local.tags
+}
+
+# TeamCity service security group
+resource "aws_security_group" "teamcity_service_sg" {
+  name   = "${local.name_prefix}-service-sg"
+  vpc_id = var.vpc_id
+
+  tags = local.tags
+}
+
+# TeamCity EFS security group
+resource "aws_security_group" "teamcity_efs_sg" {
+  name        = "${local.name_prefix}-efs-sg"
+  description = "TeamCity EFS mount target security group"
+  vpc_id      = var.vpc_id
+
+  tags = local.tags
+}
+
+# Ingress rule for NFS traffic from service to EFS
+resource "aws_vpc_security_group_ingress_rule" "service_efs" {
+  security_group_id            = aws_security_group.teamcity_efs_sg.id
+  referenced_security_group_id = aws_security_group.teamcity_service_sg.id
+  description                  = "Allow inbound access from TeamCity service containers to EFS"
+  ip_protocol                  = "TCP"
+  from_port                    = 2049
+  to_port                      = 2049
+}
+
+# Ingress rule for NFS traffic from EFS to service
+resource "aws_vpc_security_group_ingress_rule" "efs_service" {
+  security_group_id            = aws_security_group.teamcity_service_sg.id
+  referenced_security_group_id = aws_security_group.teamcity_efs_sg.id
+  description                  = "Allow inbound access from EFS to TeamCity service containers"
+  ip_protocol                  = "TCP"
+  from_port                    = 2049
+  to_port                      = 2049
+}
+
+# TeamCity Aurora Serverless PostgreSQL security group
+resource "aws_security_group" "teamcity_db_sg" {
+  name        = "${local.name_prefix}-db-sg"
+  description = "TeamCity DB security group"
+  vpc_id      = var.vpc_id
+
+  tags = local.tags
+}
+
+# Ingress rule for PostgreSQL from service to database cluster
+resource "aws_vpc_security_group_ingress_rule" "service_db" {
+  security_group_id            = aws_security_group.teamcity_db_sg.id
+  referenced_security_group_id = aws_security_group.teamcity_service_sg.id
+  description                  = "Allow inbound access from TeamCity service containers to DB"
+  ip_protocol                  = "TCP"
+  from_port                    = 5432
+  to_port                      = 5432
+}
+
+# Ingress rule for PostgreSQL from database cluster to service
+resource "aws_vpc_security_group_ingress_rule" "db_service" {
+  security_group_id            = aws_security_group.teamcity_service_sg.id
+  referenced_security_group_id = aws_security_group.teamcity_db_sg.id
+  description                  = "Allow inbound access from DB to TeamCity service containers"
+  ip_protocol                  = "TCP"
+  from_port                    = 5432
+  to_port                      = 5432
+}
+
+# Egress rule for database to let all traffic out
+resource "aws_vpc_security_group_egress_rule" "db_outbound" {
+
+  security_group_id = aws_security_group.teamcity_db_sg.id
+  cidr_ipv4         = "0.0.0.0/0"
+  ip_protocol       = "-1"
+}
+
+# TeamCity ALB security group
+resource "aws_security_group" "teamcity_alb_sg" {
+  name   = "${local.name_prefix}-alb-sg"
+  vpc_id = var.vpc_id
+
+  tags = local.tags
+}
+
+# Ingress rule to allow ALB to send health checks to service
+resource "aws_vpc_security_group_ingress_rule" "alb_service_healthcheck" {
+  security_group_id            = aws_security_group.teamcity_service_sg.id
+  referenced_security_group_id = aws_security_group.teamcity_alb_sg.id
+  description                  = "Allow health checks from ALB to service containers"
+  ip_protocol                  = "TCP"
+  from_port                    = 80
+  to_port                      = 80
+}
+
+# Ingress rule for HTTP traffic from ALB to service
+resource "aws_vpc_security_group_ingress_rule" "service_inbound_alb" {
+  security_group_id            = aws_security_group.teamcity_service_sg.id
+  referenced_security_group_id = aws_security_group.teamcity_alb_sg.id
+  description                  = "Allow inbound HTTP traffic from ALB to service containers"
+  from_port                    = var.container_port
+  to_port                      = var.container_port
+  ip_protocol                  = "TCP"
+
+}
+
+# Egress rule for HTTP traffic from ALB to service
+resource "aws_vpc_security_group_egress_rule" "service_outbound_alb" {
+  security_group_id            = aws_security_group.teamcity_alb_sg.id
+  referenced_security_group_id = aws_security_group.teamcity_service_sg.id
+  description                  = "Allow outbound HTTP traffic from ALB to service containers"
+  from_port                    = var.container_port
+  to_port                      = var.container_port
+  ip_protocol                  = "TCP"
+
+}
+
+# Grant TeamCity service access to internet
+resource "aws_vpc_security_group_egress_rule" "internet_outbound" {
+  security_group_id = aws_security_group.teamcity_service_sg.id
+  description       = "Allow outbound internet access from TeamCity service containers"
+  cidr_ipv4         = "0.0.0.0/0"
+  ip_protocol       = "-1"
+}
+
+
+#############################################
+# IAM Roles for TeamCity Module
+#############################################
+data "aws_iam_policy_document" "ecs_tasks_trust_relationship" {
+  statement {
+    effect  = "Allow"
+    actions = ["sts:AssumeRole"]
+    principals {
+      type        = "Service"
+      identifiers = ["ecs-tasks.amazonaws.com"]
+    }
+  }
+}
+data "aws_iam_policy_document" "teamcity_default_policy" {
+  # ECS
+  statement {
+    sid    = "ECSExec"
+    effect = "Allow"
+    actions = [
+      "ssmmessages:OpenDataChannel",
+      "ssmmessages:OpenControlChannel",
+      "ssmmessages:CreateDataChannel",
+      "ssmmessages:CreateControlChannel",
+    ]
+    resources = [
+      "*"
+    ]
+  }
+}
+resource "aws_iam_policy" "teamcity_default_policy" {
+  name        = "teamcity-default-policy"
+  description = "Policy granting permissions for Unreal Horde."
+  policy      = data.aws_iam_policy_document.teamcity_default_policy.json
+}
+
+resource "aws_iam_role" "teamcity_default_role" {
+  name               = "teamcity-default-role"
+  description        = "Default role for TeamCity ECS task."
+  assume_role_policy = data.aws_iam_policy_document.ecs_tasks_trust_relationship.json
+  managed_policy_arns = [
+    aws_iam_policy.teamcity_default_policy.arn
+  ]
+
+  tags = local.tags
+}
+resource "aws_iam_role" "teamcity_task_execution_role" {
+  name = "teamcity-task-execution-role"
+
+  assume_role_policy  = data.aws_iam_policy_document.ecs_tasks_trust_relationship.json
+  managed_policy_arns = ["arn:aws:iam::aws:policy/service-role/AmazonECSTaskExecutionRolePolicy"]
+}
+
+# CloudWatch Logs
+resource "aws_cloudwatch_log_group" "teamcity_log_group" {
+  #checkov:skip=CKV_AWS_158: KMS Encryption disabled by default
+  name              = "${local.name_prefix}-log-group"
+  retention_in_days = var.teamcity_cloudwatch_log_retention_in_days
+  tags              = local.tags
+}
+
+
+# Load Balancer for TeamCity Service
+resource "aws_lb" "teamcity_external_lb" {
+  name            = "${local.name_prefix}-lb"
+  security_groups = [aws_security_group.teamcity_alb_sg.id]
+
+  load_balancer_type         = "application"
+  internal                   = false
+  subnets                    = var.alb_subnets
+  drop_invalid_header_fields = true
+  tags                       = local.tags
+}
+
+# TeamCity target group for ALB
+resource "aws_lb_target_group" "teamcity_target_group" {
+  name        = "${local.name_prefix}-tg"
+  port        = var.container_port
+  protocol    = "HTTP"
+  vpc_id      = var.vpc_id
+  target_type = "ip"
+
+  health_check {
+    path                = "/healthCheck/healthy"
+    interval            = 30
+    timeout             = 5
+    healthy_threshold   = 5
+    unhealthy_threshold = 2
+    port                = var.container_port
+    protocol            = "HTTP"
+    matcher             = "200"
+  }
+
+  tags = local.tags
+}
+
+# ALB HTTPS Listener
+resource "aws_lb_listener" "teamcity_listener" {
+  load_balancer_arn = aws_lb.teamcity_external_lb.arn
+  port              = "443"
+  protocol          = "HTTPS"
+  ssl_policy        = "ELBSecurityPolicy-TLS13-1-2-2021-06"
+  certificate_arn   = var.alb_certificate_arn
+  default_action {
+    type             = "forward"
+    target_group_arn = aws_lb_target_group.teamcity_target_group.arn
+  }
+  tags = local.tags
+}
+
+
+
+
+# #######################################
+# # TeamCity Aurora Serverless Database #
+# #######################################
+# Subnet group
+resource "aws_db_subnet_group" "teamcity_db_subnet_group" {
+  name       = "${local.name_prefix}-db-subnet-group"
+  subnet_ids = var.service_subnets
+  tags       = local.tags
+}
+
+# # RDS instance with Aurora serverless engine
+resource "aws_rds_cluster" "teamcity_db_cluster" {
+  cluster_identifier   = "teamcity-cluster"
+  engine               = "aurora-postgresql"
+  engine_mode          = "provisioned"
+  engine_version       = "16.6"
+  database_name        = "teamcity"
+  master_username      = "teamcity"
+  master_password      = "teamcity2025"
+  storage_encrypted    = true
+  db_subnet_group_name = aws_db_subnet_group.teamcity_db_subnet_group.id
+  vpc_security_group_ids = [
+    aws_security_group.teamcity_db_sg.id
+  ]
+
+
+  serverlessv2_scaling_configuration {
+    max_capacity             = 1.0
+    min_capacity             = 0.0
+    seconds_until_auto_pause = 3600
+  }
+}
+
+resource "aws_rds_cluster_instance" "teamcity_db_cluster" {
+  cluster_identifier = aws_rds_cluster.teamcity_db_cluster.id
+  instance_class     = "db.serverless"
+  engine             = aws_rds_cluster.teamcity_db_cluster.engine
+  engine_version     = aws_rds_cluster.teamcity_db_cluster.engine_version
+
+}
path: modules/teamcity/main.tf
user: login: github-advanced-security[bot]
body: ## Ensure RDS Performance Insights are encrypted using KMS CMKs

Ensure RDS Performance Insights are encrypted using KMS CMKs

[Show more details](https://github.com/aws-games/cloud-game-development-toolkit/security/code-scanning/812)
created_at: 2025-02-06T21:14:48Z
author_association: NONE
diff_hunk: @@ -0,0 +1,492 @@
+###################################
+# ECS Cluster for TeamCity Server #
+###################################
+
+resource "aws_ecs_cluster" "teamcity_cluster" {
+  name = "${local.name_prefix}-cluster"
+
+  setting {
+    name  = "containerInsights"
+    value = "enabled"
+  }
+
+  tags = local.tags
+}
+
+# TeamCity Task Definition
+resource "aws_ecs_task_definition" "teamcity_task_definition" {
+  family                   = var.name
+  requires_compatibilities = ["FARGATE"]
+  network_mode             = "awsvpc"
+  cpu                      = var.container_cpu
+  memory                   = var.container_memory
+
+  container_definitions = jsonencode([
+    {
+      name      = var.container_name
+      image     = local.image
+      cpu       = var.container_cpu
+      memory    = var.container_memory
+      essential = true
+      portMappings = [
+        {
+          containerPort = var.container_port
+          hostPort      = var.container_port
+        }
+      ]
+      logConfiguration = {
+        logDriver = "awslogs"
+        options = {
+          awslogs-group         = aws_cloudwatch_log_group.teamcity_log_group.name
+          awslogs-region        = data.aws_region.current.name
+          awslogs-stream-prefix = "[APP]"
+        }
+      }
+      mountPoints = [
+        {
+          sourceVolume  = "teamcity_data",
+          containerPath = "/data/teamcity_server/datadir"
+          readOnly      = false
+        },
+        {
+          sourceVolume  = "teamcity_logs",
+          containerPath = "/data/teamcity_server/logs"
+          readOnly      = false
+        }
+      ]
+      # Set environment variables for database connection string
+      environment = [
+        {
+          name  = "TEAMCITY_DB_URL"
+          value = local.database_connection_string
+        },
+        {
+          name  = "TEAMCITY_DB_USER"
+          value = local.database_user
+        },
+        {
+          name  = "TEAMCITY_DB_PASSWORD"
+          value = local.database_password
+        },
+        {
+          name  = "TEAMCITY_LOGS_PATH"
+          value = "/data/teamcity_server/logs"
+        }
+      ]
+    }
+  ])
+  tags = {
+    Name = var.name
+  }
+  task_role_arn      = aws_iam_role.teamcity_default_role.arn
+  execution_role_arn = aws_iam_role.teamcity_task_execution_role.arn
+
+  volume {
+    name = "teamcity_data"
+    efs_volume_configuration {
+      file_system_id          = aws_efs_file_system.teamcity_efs_file_system.id
+      transit_encryption      = "ENABLED"
+      transit_encryption_port = 2999
+      authorization_config {
+        access_point_id = aws_efs_access_point.teamcity_efs_data_access_point.id
+        iam             = "ENABLED"
+      }
+    }
+  }
+
+  volume {
+    name = "teamcity_logs"
+    efs_volume_configuration {
+      file_system_id          = aws_efs_file_system.teamcity_efs_file_system.id
+      transit_encryption      = "ENABLED"
+      transit_encryption_port = 2998
+      authorization_config {
+        access_point_id = aws_efs_access_point.teamcity_efs_logs_access_point.id
+        iam             = "ENABLED"
+      }
+    }
+  }
+}
+
+
+
+
+# ECS Service
+resource "aws_ecs_service" "teamcity" {
+  name = local.name_prefix
+
+  cluster                = aws_ecs_cluster.teamcity_cluster.name
+  task_definition        = aws_ecs_task_definition.teamcity_task_definition.arn
+  launch_type            = "FARGATE"
+  desired_count          = 1    #TODO: make this configurable
+  force_new_deployment   = true #TODO: make this configurable
+  enable_execute_command = true #TODO: make this configurable
+
+  wait_for_steady_state = true
+
+  network_configuration {
+    subnets         = var.service_subnets
+    security_groups = [aws_security_group.teamcity_service_sg.id]
+  }
+  load_balancer {
+    target_group_arn = aws_lb_target_group.teamcity_target_group.arn
+    container_name   = var.container_name
+    container_port   = var.container_port
+  }
+
+  tags = local.tags
+}
+
+# TeamCity service security group
+resource "aws_security_group" "teamcity_service_sg" {
+  name   = "${local.name_prefix}-service-sg"
+  vpc_id = var.vpc_id
+
+  tags = local.tags
+}
+
+# TeamCity EFS security group
+resource "aws_security_group" "teamcity_efs_sg" {
+  name        = "${local.name_prefix}-efs-sg"
+  description = "TeamCity EFS mount target security group"
+  vpc_id      = var.vpc_id
+
+  tags = local.tags
+}
+
+# Ingress rule for NFS traffic from service to EFS
+resource "aws_vpc_security_group_ingress_rule" "service_efs" {
+  security_group_id            = aws_security_group.teamcity_efs_sg.id
+  referenced_security_group_id = aws_security_group.teamcity_service_sg.id
+  description                  = "Allow inbound access from TeamCity service containers to EFS"
+  ip_protocol                  = "TCP"
+  from_port                    = 2049
+  to_port                      = 2049
+}
+
+# Ingress rule for NFS traffic from EFS to service
+resource "aws_vpc_security_group_ingress_rule" "efs_service" {
+  security_group_id            = aws_security_group.teamcity_service_sg.id
+  referenced_security_group_id = aws_security_group.teamcity_efs_sg.id
+  description                  = "Allow inbound access from EFS to TeamCity service containers"
+  ip_protocol                  = "TCP"
+  from_port                    = 2049
+  to_port                      = 2049
+}
+
+# TeamCity Aurora Serverless PostgreSQL security group
+resource "aws_security_group" "teamcity_db_sg" {
+  name        = "${local.name_prefix}-db-sg"
+  description = "TeamCity DB security group"
+  vpc_id      = var.vpc_id
+
+  tags = local.tags
+}
+
+# Ingress rule for PostgreSQL from service to database cluster
+resource "aws_vpc_security_group_ingress_rule" "service_db" {
+  security_group_id            = aws_security_group.teamcity_db_sg.id
+  referenced_security_group_id = aws_security_group.teamcity_service_sg.id
+  description                  = "Allow inbound access from TeamCity service containers to DB"
+  ip_protocol                  = "TCP"
+  from_port                    = 5432
+  to_port                      = 5432
+}
+
+# Ingress rule for PostgreSQL from database cluster to service
+resource "aws_vpc_security_group_ingress_rule" "db_service" {
+  security_group_id            = aws_security_group.teamcity_service_sg.id
+  referenced_security_group_id = aws_security_group.teamcity_db_sg.id
+  description                  = "Allow inbound access from DB to TeamCity service containers"
+  ip_protocol                  = "TCP"
+  from_port                    = 5432
+  to_port                      = 5432
+}
+
+# Egress rule for database to let all traffic out
+resource "aws_vpc_security_group_egress_rule" "db_outbound" {
+
+  security_group_id = aws_security_group.teamcity_db_sg.id
+  cidr_ipv4         = "0.0.0.0/0"
+  ip_protocol       = "-1"
+}
+
+# TeamCity ALB security group
+resource "aws_security_group" "teamcity_alb_sg" {
+  name   = "${local.name_prefix}-alb-sg"
+  vpc_id = var.vpc_id
+
+  tags = local.tags
+}
+
+# Ingress rule to allow ALB to send health checks to service
+resource "aws_vpc_security_group_ingress_rule" "alb_service_healthcheck" {
+  security_group_id            = aws_security_group.teamcity_service_sg.id
+  referenced_security_group_id = aws_security_group.teamcity_alb_sg.id
+  description                  = "Allow health checks from ALB to service containers"
+  ip_protocol                  = "TCP"
+  from_port                    = 80
+  to_port                      = 80
+}
+
+# Ingress rule for HTTP traffic from ALB to service
+resource "aws_vpc_security_group_ingress_rule" "service_inbound_alb" {
+  security_group_id            = aws_security_group.teamcity_service_sg.id
+  referenced_security_group_id = aws_security_group.teamcity_alb_sg.id
+  description                  = "Allow inbound HTTP traffic from ALB to service containers"
+  from_port                    = var.container_port
+  to_port                      = var.container_port
+  ip_protocol                  = "TCP"
+
+}
+
+# Egress rule for HTTP traffic from ALB to service
+resource "aws_vpc_security_group_egress_rule" "service_outbound_alb" {
+  security_group_id            = aws_security_group.teamcity_alb_sg.id
+  referenced_security_group_id = aws_security_group.teamcity_service_sg.id
+  description                  = "Allow outbound HTTP traffic from ALB to service containers"
+  from_port                    = var.container_port
+  to_port                      = var.container_port
+  ip_protocol                  = "TCP"
+
+}
+
+# Grant TeamCity service access to internet
+resource "aws_vpc_security_group_egress_rule" "internet_outbound" {
+  security_group_id = aws_security_group.teamcity_service_sg.id
+  description       = "Allow outbound internet access from TeamCity service containers"
+  cidr_ipv4         = "0.0.0.0/0"
+  ip_protocol       = "-1"
+}
+
+
+#############################################
+# IAM Roles for TeamCity Module
+#############################################
+data "aws_iam_policy_document" "ecs_tasks_trust_relationship" {
+  statement {
+    effect  = "Allow"
+    actions = ["sts:AssumeRole"]
+    principals {
+      type        = "Service"
+      identifiers = ["ecs-tasks.amazonaws.com"]
+    }
+  }
+}
+data "aws_iam_policy_document" "teamcity_default_policy" {
+  # ECS
+  statement {
+    sid    = "ECSExec"
+    effect = "Allow"
+    actions = [
+      "ssmmessages:OpenDataChannel",
+      "ssmmessages:OpenControlChannel",
+      "ssmmessages:CreateDataChannel",
+      "ssmmessages:CreateControlChannel",
+    ]
+    resources = [
+      "*"
+    ]
+  }
+}
+resource "aws_iam_policy" "teamcity_default_policy" {
+  name        = "teamcity-default-policy"
+  description = "Policy granting permissions for Unreal Horde."
+  policy      = data.aws_iam_policy_document.teamcity_default_policy.json
+}
+
+resource "aws_iam_role" "teamcity_default_role" {
+  name               = "teamcity-default-role"
+  description        = "Default role for TeamCity ECS task."
+  assume_role_policy = data.aws_iam_policy_document.ecs_tasks_trust_relationship.json
+  managed_policy_arns = [
+    aws_iam_policy.teamcity_default_policy.arn
+  ]
+
+  tags = local.tags
+}
+resource "aws_iam_role" "teamcity_task_execution_role" {
+  name = "teamcity-task-execution-role"
+
+  assume_role_policy  = data.aws_iam_policy_document.ecs_tasks_trust_relationship.json
+  managed_policy_arns = ["arn:aws:iam::aws:policy/service-role/AmazonECSTaskExecutionRolePolicy"]
+}
+
+# CloudWatch Logs
+resource "aws_cloudwatch_log_group" "teamcity_log_group" {
+  #checkov:skip=CKV_AWS_158: KMS Encryption disabled by default
+  name              = "${local.name_prefix}-log-group"
+  retention_in_days = var.teamcity_cloudwatch_log_retention_in_days
+  tags              = local.tags
+}
+
+
+# Load Balancer for TeamCity Service
+resource "aws_lb" "teamcity_external_lb" {
+  name            = "${local.name_prefix}-lb"
+  security_groups = [aws_security_group.teamcity_alb_sg.id]
+
+  load_balancer_type         = "application"
+  internal                   = false
+  subnets                    = var.alb_subnets
+  drop_invalid_header_fields = true
+  tags                       = local.tags
+}
+
+# TeamCity target group for ALB
+resource "aws_lb_target_group" "teamcity_target_group" {
+  name        = "${local.name_prefix}-tg"
+  port        = var.container_port
+  protocol    = "HTTP"
+  vpc_id      = var.vpc_id
+  target_type = "ip"
+
+  health_check {
+    path                = "/healthCheck/healthy"
+    interval            = 30
+    timeout             = 5
+    healthy_threshold   = 5
+    unhealthy_threshold = 2
+    port                = var.container_port
+    protocol            = "HTTP"
+    matcher             = "200"
+  }
+
+  tags = local.tags
+}
+
+# ALB HTTPS Listener
+resource "aws_lb_listener" "teamcity_listener" {
+  load_balancer_arn = aws_lb.teamcity_external_lb.arn
+  port              = "443"
+  protocol          = "HTTPS"
+  ssl_policy        = "ELBSecurityPolicy-TLS13-1-2-2021-06"
+  certificate_arn   = var.alb_certificate_arn
+  default_action {
+    type             = "forward"
+    target_group_arn = aws_lb_target_group.teamcity_target_group.arn
+  }
+  tags = local.tags
+}
+
+
+
+
+# #######################################
+# # TeamCity Aurora Serverless Database #
+# #######################################
+# Subnet group
+resource "aws_db_subnet_group" "teamcity_db_subnet_group" {
+  name       = "${local.name_prefix}-db-subnet-group"
+  subnet_ids = var.service_subnets
+  tags       = local.tags
+}
+
+# # RDS instance with Aurora serverless engine
+resource "aws_rds_cluster" "teamcity_db_cluster" {
+  cluster_identifier   = "teamcity-cluster"
+  engine               = "aurora-postgresql"
+  engine_mode          = "provisioned"
+  engine_version       = "16.6"
+  database_name        = "teamcity"
+  master_username      = "teamcity"
+  master_password      = "teamcity2025"
+  storage_encrypted    = true
+  db_subnet_group_name = aws_db_subnet_group.teamcity_db_subnet_group.id
+  vpc_security_group_ids = [
+    aws_security_group.teamcity_db_sg.id
+  ]
+
+
+  serverlessv2_scaling_configuration {
+    max_capacity             = 1.0
+    min_capacity             = 0.0
+    seconds_until_auto_pause = 3600
+  }
+}
+
+resource "aws_rds_cluster_instance" "teamcity_db_cluster" {
+  cluster_identifier = aws_rds_cluster.teamcity_db_cluster.id
+  instance_class     = "db.serverless"
+  engine             = aws_rds_cluster.teamcity_db_cluster.engine
+  engine_version     = aws_rds_cluster.teamcity_db_cluster.engine_version
+
+}
path: modules/teamcity/main.tf
user: login: github-advanced-security[bot]
body: ## Ensure that enhanced monitoring is enabled for Amazon RDS instances

Ensure that enhanced monitoring is enabled for Amazon RDS instances

[Show more details](https://github.com/aws-games/cloud-game-development-toolkit/security/code-scanning/813)
created_at: 2025-02-06T21:14:49Z
author_association: NONE
diff_hunk: @@ -0,0 +1,492 @@
+###################################
+# ECS Cluster for TeamCity Server #
+###################################
+
+resource "aws_ecs_cluster" "teamcity_cluster" {
+  name = "${local.name_prefix}-cluster"
+
+  setting {
+    name  = "containerInsights"
+    value = "enabled"
+  }
+
+  tags = local.tags
+}
+
+# TeamCity Task Definition
+resource "aws_ecs_task_definition" "teamcity_task_definition" {
+  family                   = var.name
+  requires_compatibilities = ["FARGATE"]
+  network_mode             = "awsvpc"
+  cpu                      = var.container_cpu
+  memory                   = var.container_memory
+
+  container_definitions = jsonencode([
+    {
+      name      = var.container_name
+      image     = local.image
+      cpu       = var.container_cpu
+      memory    = var.container_memory
+      essential = true
+      portMappings = [
+        {
+          containerPort = var.container_port
+          hostPort      = var.container_port
+        }
+      ]
+      logConfiguration = {
+        logDriver = "awslogs"
+        options = {
+          awslogs-group         = aws_cloudwatch_log_group.teamcity_log_group.name
+          awslogs-region        = data.aws_region.current.name
+          awslogs-stream-prefix = "[APP]"
+        }
+      }
+      mountPoints = [
+        {
+          sourceVolume  = "teamcity_data",
+          containerPath = "/data/teamcity_server/datadir"
+          readOnly      = false
+        },
+        {
+          sourceVolume  = "teamcity_logs",
+          containerPath = "/data/teamcity_server/logs"
+          readOnly      = false
+        }
+      ]
+      # Set environment variables for database connection string
+      environment = [
+        {
+          name  = "TEAMCITY_DB_URL"
+          value = local.database_connection_string
+        },
+        {
+          name  = "TEAMCITY_DB_USER"
+          value = local.database_user
+        },
+        {
+          name  = "TEAMCITY_DB_PASSWORD"
+          value = local.database_password
+        },
+        {
+          name  = "TEAMCITY_LOGS_PATH"
+          value = "/data/teamcity_server/logs"
+        }
+      ]
+    }
+  ])
+  tags = {
+    Name = var.name
+  }
+  task_role_arn      = aws_iam_role.teamcity_default_role.arn
+  execution_role_arn = aws_iam_role.teamcity_task_execution_role.arn
+
+  volume {
+    name = "teamcity_data"
+    efs_volume_configuration {
+      file_system_id          = aws_efs_file_system.teamcity_efs_file_system.id
+      transit_encryption      = "ENABLED"
+      transit_encryption_port = 2999
+      authorization_config {
+        access_point_id = aws_efs_access_point.teamcity_efs_data_access_point.id
+        iam             = "ENABLED"
+      }
+    }
+  }
+
+  volume {
+    name = "teamcity_logs"
+    efs_volume_configuration {
+      file_system_id          = aws_efs_file_system.teamcity_efs_file_system.id
+      transit_encryption      = "ENABLED"
+      transit_encryption_port = 2998
+      authorization_config {
+        access_point_id = aws_efs_access_point.teamcity_efs_logs_access_point.id
+        iam             = "ENABLED"
+      }
+    }
+  }
+}
+
+
+
+
+# ECS Service
+resource "aws_ecs_service" "teamcity" {
+  name = local.name_prefix
+
+  cluster                = aws_ecs_cluster.teamcity_cluster.name
+  task_definition        = aws_ecs_task_definition.teamcity_task_definition.arn
+  launch_type            = "FARGATE"
+  desired_count          = 1    #TODO: make this configurable
+  force_new_deployment   = true #TODO: make this configurable
+  enable_execute_command = true #TODO: make this configurable
+
+  wait_for_steady_state = true
+
+  network_configuration {
+    subnets         = var.service_subnets
+    security_groups = [aws_security_group.teamcity_service_sg.id]
+  }
+  load_balancer {
+    target_group_arn = aws_lb_target_group.teamcity_target_group.arn
+    container_name   = var.container_name
+    container_port   = var.container_port
+  }
+
+  tags = local.tags
+}
+
+# TeamCity service security group
+resource "aws_security_group" "teamcity_service_sg" {
+  name   = "${local.name_prefix}-service-sg"
+  vpc_id = var.vpc_id
+
+  tags = local.tags
+}
+
+# TeamCity EFS security group
+resource "aws_security_group" "teamcity_efs_sg" {
+  name        = "${local.name_prefix}-efs-sg"
+  description = "TeamCity EFS mount target security group"
+  vpc_id      = var.vpc_id
+
+  tags = local.tags
+}
+
+# Ingress rule for NFS traffic from service to EFS
+resource "aws_vpc_security_group_ingress_rule" "service_efs" {
+  security_group_id            = aws_security_group.teamcity_efs_sg.id
+  referenced_security_group_id = aws_security_group.teamcity_service_sg.id
+  description                  = "Allow inbound access from TeamCity service containers to EFS"
+  ip_protocol                  = "TCP"
+  from_port                    = 2049
+  to_port                      = 2049
+}
+
+# Ingress rule for NFS traffic from EFS to service
+resource "aws_vpc_security_group_ingress_rule" "efs_service" {
+  security_group_id            = aws_security_group.teamcity_service_sg.id
+  referenced_security_group_id = aws_security_group.teamcity_efs_sg.id
+  description                  = "Allow inbound access from EFS to TeamCity service containers"
+  ip_protocol                  = "TCP"
+  from_port                    = 2049
+  to_port                      = 2049
+}
+
+# TeamCity Aurora Serverless PostgreSQL security group
+resource "aws_security_group" "teamcity_db_sg" {
+  name        = "${local.name_prefix}-db-sg"
+  description = "TeamCity DB security group"
+  vpc_id      = var.vpc_id
+
+  tags = local.tags
+}
+
+# Ingress rule for PostgreSQL from service to database cluster
+resource "aws_vpc_security_group_ingress_rule" "service_db" {
+  security_group_id            = aws_security_group.teamcity_db_sg.id
+  referenced_security_group_id = aws_security_group.teamcity_service_sg.id
+  description                  = "Allow inbound access from TeamCity service containers to DB"
+  ip_protocol                  = "TCP"
+  from_port                    = 5432
+  to_port                      = 5432
+}
+
+# Ingress rule for PostgreSQL from database cluster to service
+resource "aws_vpc_security_group_ingress_rule" "db_service" {
+  security_group_id            = aws_security_group.teamcity_service_sg.id
+  referenced_security_group_id = aws_security_group.teamcity_db_sg.id
+  description                  = "Allow inbound access from DB to TeamCity service containers"
+  ip_protocol                  = "TCP"
+  from_port                    = 5432
+  to_port                      = 5432
+}
+
+# Egress rule for database to let all traffic out
+resource "aws_vpc_security_group_egress_rule" "db_outbound" {
+
+  security_group_id = aws_security_group.teamcity_db_sg.id
+  cidr_ipv4         = "0.0.0.0/0"
+  ip_protocol       = "-1"
+}
+
+# TeamCity ALB security group
+resource "aws_security_group" "teamcity_alb_sg" {
+  name   = "${local.name_prefix}-alb-sg"
+  vpc_id = var.vpc_id
+
+  tags = local.tags
+}
+
+# Ingress rule to allow ALB to send health checks to service
+resource "aws_vpc_security_group_ingress_rule" "alb_service_healthcheck" {
+  security_group_id            = aws_security_group.teamcity_service_sg.id
+  referenced_security_group_id = aws_security_group.teamcity_alb_sg.id
+  description                  = "Allow health checks from ALB to service containers"
+  ip_protocol                  = "TCP"
+  from_port                    = 80
+  to_port                      = 80
+}
+
+# Ingress rule for HTTP traffic from ALB to service
+resource "aws_vpc_security_group_ingress_rule" "service_inbound_alb" {
+  security_group_id            = aws_security_group.teamcity_service_sg.id
+  referenced_security_group_id = aws_security_group.teamcity_alb_sg.id
+  description                  = "Allow inbound HTTP traffic from ALB to service containers"
+  from_port                    = var.container_port
+  to_port                      = var.container_port
+  ip_protocol                  = "TCP"
+
+}
+
+# Egress rule for HTTP traffic from ALB to service
+resource "aws_vpc_security_group_egress_rule" "service_outbound_alb" {
+  security_group_id            = aws_security_group.teamcity_alb_sg.id
+  referenced_security_group_id = aws_security_group.teamcity_service_sg.id
+  description                  = "Allow outbound HTTP traffic from ALB to service containers"
+  from_port                    = var.container_port
+  to_port                      = var.container_port
+  ip_protocol                  = "TCP"
+
+}
+
+# Grant TeamCity service access to internet
+resource "aws_vpc_security_group_egress_rule" "internet_outbound" {
+  security_group_id = aws_security_group.teamcity_service_sg.id
+  description       = "Allow outbound internet access from TeamCity service containers"
+  cidr_ipv4         = "0.0.0.0/0"
+  ip_protocol       = "-1"
+}
+
+
+#############################################
+# IAM Roles for TeamCity Module
+#############################################
+data "aws_iam_policy_document" "ecs_tasks_trust_relationship" {
+  statement {
+    effect  = "Allow"
+    actions = ["sts:AssumeRole"]
+    principals {
+      type        = "Service"
+      identifiers = ["ecs-tasks.amazonaws.com"]
+    }
+  }
+}
+data "aws_iam_policy_document" "teamcity_default_policy" {
+  # ECS
+  statement {
+    sid    = "ECSExec"
+    effect = "Allow"
+    actions = [
+      "ssmmessages:OpenDataChannel",
+      "ssmmessages:OpenControlChannel",
+      "ssmmessages:CreateDataChannel",
+      "ssmmessages:CreateControlChannel",
+    ]
+    resources = [
+      "*"
+    ]
+  }
+}
+resource "aws_iam_policy" "teamcity_default_policy" {
+  name        = "teamcity-default-policy"
+  description = "Policy granting permissions for Unreal Horde."
+  policy      = data.aws_iam_policy_document.teamcity_default_policy.json
+}
+
+resource "aws_iam_role" "teamcity_default_role" {
+  name               = "teamcity-default-role"
+  description        = "Default role for TeamCity ECS task."
+  assume_role_policy = data.aws_iam_policy_document.ecs_tasks_trust_relationship.json
+  managed_policy_arns = [
+    aws_iam_policy.teamcity_default_policy.arn
+  ]
+
+  tags = local.tags
+}
+resource "aws_iam_role" "teamcity_task_execution_role" {
+  name = "teamcity-task-execution-role"
+
+  assume_role_policy  = data.aws_iam_policy_document.ecs_tasks_trust_relationship.json
+  managed_policy_arns = ["arn:aws:iam::aws:policy/service-role/AmazonECSTaskExecutionRolePolicy"]
+}
+
+# CloudWatch Logs
+resource "aws_cloudwatch_log_group" "teamcity_log_group" {
+  #checkov:skip=CKV_AWS_158: KMS Encryption disabled by default
+  name              = "${local.name_prefix}-log-group"
+  retention_in_days = var.teamcity_cloudwatch_log_retention_in_days
+  tags              = local.tags
+}
+
+
+# Load Balancer for TeamCity Service
+resource "aws_lb" "teamcity_external_lb" {
+  name            = "${local.name_prefix}-lb"
+  security_groups = [aws_security_group.teamcity_alb_sg.id]
+
+  load_balancer_type         = "application"
+  internal                   = false
+  subnets                    = var.alb_subnets
+  drop_invalid_header_fields = true
+  tags                       = local.tags
+}
+
+# TeamCity target group for ALB
+resource "aws_lb_target_group" "teamcity_target_group" {
+  name        = "${local.name_prefix}-tg"
+  port        = var.container_port
+  protocol    = "HTTP"
+  vpc_id      = var.vpc_id
+  target_type = "ip"
+
+  health_check {
+    path                = "/healthCheck/healthy"
+    interval            = 30
+    timeout             = 5
+    healthy_threshold   = 5
+    unhealthy_threshold = 2
+    port                = var.container_port
+    protocol            = "HTTP"
+    matcher             = "200"
+  }
+
+  tags = local.tags
+}
+
+# ALB HTTPS Listener
+resource "aws_lb_listener" "teamcity_listener" {
+  load_balancer_arn = aws_lb.teamcity_external_lb.arn
+  port              = "443"
+  protocol          = "HTTPS"
+  ssl_policy        = "ELBSecurityPolicy-TLS13-1-2-2021-06"
+  certificate_arn   = var.alb_certificate_arn
+  default_action {
+    type             = "forward"
+    target_group_arn = aws_lb_target_group.teamcity_target_group.arn
+  }
+  tags = local.tags
+}
+
+
+
+
+# #######################################
+# # TeamCity Aurora Serverless Database #
+# #######################################
+# Subnet group
+resource "aws_db_subnet_group" "teamcity_db_subnet_group" {
+  name       = "${local.name_prefix}-db-subnet-group"
+  subnet_ids = var.service_subnets
+  tags       = local.tags
+}
+
+# # RDS instance with Aurora serverless engine
+resource "aws_rds_cluster" "teamcity_db_cluster" {
+  cluster_identifier   = "teamcity-cluster"
+  engine               = "aurora-postgresql"
+  engine_mode          = "provisioned"
+  engine_version       = "16.6"
+  database_name        = "teamcity"
+  master_username      = "teamcity"
+  master_password      = "teamcity2025"
+  storage_encrypted    = true
+  db_subnet_group_name = aws_db_subnet_group.teamcity_db_subnet_group.id
+  vpc_security_group_ids = [
+    aws_security_group.teamcity_db_sg.id
+  ]
+
+
+  serverlessv2_scaling_configuration {
+    max_capacity             = 1.0
+    min_capacity             = 0.0
+    seconds_until_auto_pause = 3600
+  }
+}
+
+resource "aws_rds_cluster_instance" "teamcity_db_cluster" {
+  cluster_identifier = aws_rds_cluster.teamcity_db_cluster.id
+  instance_class     = "db.serverless"
+  engine             = aws_rds_cluster.teamcity_db_cluster.engine
+  engine_version     = aws_rds_cluster.teamcity_db_cluster.engine_version
+
+}
path: modules/teamcity/main.tf
user: login: github-advanced-security[bot]
body: ## Ensure that RDS instances have performance insights enabled

Ensure that RDS instances have performance insights enabled

[Show more details](https://github.com/aws-games/cloud-game-development-toolkit/security/code-scanning/814)
created_at: 2025-02-06T21:14:49Z
author_association: NONE
diff_hunk: @@ -0,0 +1,492 @@
+###################################
+# ECS Cluster for TeamCity Server #
+###################################
+
+resource "aws_ecs_cluster" "teamcity_cluster" {
+  name = "${local.name_prefix}-cluster"
+
+  setting {
+    name  = "containerInsights"
+    value = "enabled"
+  }
+
+  tags = local.tags
+}
+
+# TeamCity Task Definition
+resource "aws_ecs_task_definition" "teamcity_task_definition" {
+  family                   = var.name
+  requires_compatibilities = ["FARGATE"]
+  network_mode             = "awsvpc"
+  cpu                      = var.container_cpu
+  memory                   = var.container_memory
+
+  container_definitions = jsonencode([
+    {
+      name      = var.container_name
+      image     = local.image
+      cpu       = var.container_cpu
+      memory    = var.container_memory
+      essential = true
+      portMappings = [
+        {
+          containerPort = var.container_port
+          hostPort      = var.container_port
+        }
+      ]
+      logConfiguration = {
+        logDriver = "awslogs"
+        options = {
+          awslogs-group         = aws_cloudwatch_log_group.teamcity_log_group.name
+          awslogs-region        = data.aws_region.current.name
+          awslogs-stream-prefix = "[APP]"
+        }
+      }
+      mountPoints = [
+        {
+          sourceVolume  = "teamcity_data",
+          containerPath = "/data/teamcity_server/datadir"
+          readOnly      = false
+        },
+        {
+          sourceVolume  = "teamcity_logs",
+          containerPath = "/data/teamcity_server/logs"
+          readOnly      = false
+        }
+      ]
+      # Set environment variables for database connection string
+      environment = [
+        {
+          name  = "TEAMCITY_DB_URL"
+          value = local.database_connection_string
+        },
+        {
+          name  = "TEAMCITY_DB_USER"
+          value = local.database_user
+        },
+        {
+          name  = "TEAMCITY_DB_PASSWORD"
+          value = local.database_password
+        },
+        {
+          name  = "TEAMCITY_LOGS_PATH"
+          value = "/data/teamcity_server/logs"
+        }
+      ]
+    }
+  ])
+  tags = {
+    Name = var.name
+  }
+  task_role_arn      = aws_iam_role.teamcity_default_role.arn
+  execution_role_arn = aws_iam_role.teamcity_task_execution_role.arn
+
+  volume {
+    name = "teamcity_data"
+    efs_volume_configuration {
+      file_system_id          = aws_efs_file_system.teamcity_efs_file_system.id
+      transit_encryption      = "ENABLED"
+      transit_encryption_port = 2999
+      authorization_config {
+        access_point_id = aws_efs_access_point.teamcity_efs_data_access_point.id
+        iam             = "ENABLED"
+      }
+    }
+  }
+
+  volume {
+    name = "teamcity_logs"
+    efs_volume_configuration {
+      file_system_id          = aws_efs_file_system.teamcity_efs_file_system.id
+      transit_encryption      = "ENABLED"
+      transit_encryption_port = 2998
+      authorization_config {
+        access_point_id = aws_efs_access_point.teamcity_efs_logs_access_point.id
+        iam             = "ENABLED"
+      }
+    }
+  }
+}
+
+
+
+
+# ECS Service
+resource "aws_ecs_service" "teamcity" {
+  name = local.name_prefix
+
+  cluster                = aws_ecs_cluster.teamcity_cluster.name
+  task_definition        = aws_ecs_task_definition.teamcity_task_definition.arn
+  launch_type            = "FARGATE"
+  desired_count          = 1    #TODO: make this configurable
+  force_new_deployment   = true #TODO: make this configurable
+  enable_execute_command = true #TODO: make this configurable
+
+  wait_for_steady_state = true
+
+  network_configuration {
+    subnets         = var.service_subnets
+    security_groups = [aws_security_group.teamcity_service_sg.id]
+  }
+  load_balancer {
+    target_group_arn = aws_lb_target_group.teamcity_target_group.arn
+    container_name   = var.container_name
+    container_port   = var.container_port
+  }
+
+  tags = local.tags
+}
+
+# TeamCity service security group
+resource "aws_security_group" "teamcity_service_sg" {
+  name   = "${local.name_prefix}-service-sg"
+  vpc_id = var.vpc_id
+
+  tags = local.tags
+}
+
+# TeamCity EFS security group
+resource "aws_security_group" "teamcity_efs_sg" {
+  name        = "${local.name_prefix}-efs-sg"
+  description = "TeamCity EFS mount target security group"
+  vpc_id      = var.vpc_id
+
+  tags = local.tags
+}
+
+# Ingress rule for NFS traffic from service to EFS
+resource "aws_vpc_security_group_ingress_rule" "service_efs" {
+  security_group_id            = aws_security_group.teamcity_efs_sg.id
+  referenced_security_group_id = aws_security_group.teamcity_service_sg.id
+  description                  = "Allow inbound access from TeamCity service containers to EFS"
+  ip_protocol                  = "TCP"
+  from_port                    = 2049
+  to_port                      = 2049
+}
+
+# Ingress rule for NFS traffic from EFS to service
+resource "aws_vpc_security_group_ingress_rule" "efs_service" {
+  security_group_id            = aws_security_group.teamcity_service_sg.id
+  referenced_security_group_id = aws_security_group.teamcity_efs_sg.id
+  description                  = "Allow inbound access from EFS to TeamCity service containers"
+  ip_protocol                  = "TCP"
+  from_port                    = 2049
+  to_port                      = 2049
+}
+
+# TeamCity Aurora Serverless PostgreSQL security group
+resource "aws_security_group" "teamcity_db_sg" {
+  name        = "${local.name_prefix}-db-sg"
+  description = "TeamCity DB security group"
+  vpc_id      = var.vpc_id
+
+  tags = local.tags
+}
+
+# Ingress rule for PostgreSQL from service to database cluster
+resource "aws_vpc_security_group_ingress_rule" "service_db" {
+  security_group_id            = aws_security_group.teamcity_db_sg.id
+  referenced_security_group_id = aws_security_group.teamcity_service_sg.id
+  description                  = "Allow inbound access from TeamCity service containers to DB"
+  ip_protocol                  = "TCP"
+  from_port                    = 5432
+  to_port                      = 5432
+}
+
+# Ingress rule for PostgreSQL from database cluster to service
+resource "aws_vpc_security_group_ingress_rule" "db_service" {
+  security_group_id            = aws_security_group.teamcity_service_sg.id
+  referenced_security_group_id = aws_security_group.teamcity_db_sg.id
+  description                  = "Allow inbound access from DB to TeamCity service containers"
+  ip_protocol                  = "TCP"
+  from_port                    = 5432
+  to_port                      = 5432
+}
+
+# Egress rule for database to let all traffic out
+resource "aws_vpc_security_group_egress_rule" "db_outbound" {
+
+  security_group_id = aws_security_group.teamcity_db_sg.id
+  cidr_ipv4         = "0.0.0.0/0"
+  ip_protocol       = "-1"
+}
+
+# TeamCity ALB security group
+resource "aws_security_group" "teamcity_alb_sg" {
+  name   = "${local.name_prefix}-alb-sg"
+  vpc_id = var.vpc_id
+
+  tags = local.tags
+}
+
+# Ingress rule to allow ALB to send health checks to service
+resource "aws_vpc_security_group_ingress_rule" "alb_service_healthcheck" {
+  security_group_id            = aws_security_group.teamcity_service_sg.id
+  referenced_security_group_id = aws_security_group.teamcity_alb_sg.id
+  description                  = "Allow health checks from ALB to service containers"
+  ip_protocol                  = "TCP"
+  from_port                    = 80
+  to_port                      = 80
+}
+
+# Ingress rule for HTTP traffic from ALB to service
+resource "aws_vpc_security_group_ingress_rule" "service_inbound_alb" {
+  security_group_id            = aws_security_group.teamcity_service_sg.id
+  referenced_security_group_id = aws_security_group.teamcity_alb_sg.id
+  description                  = "Allow inbound HTTP traffic from ALB to service containers"
+  from_port                    = var.container_port
+  to_port                      = var.container_port
+  ip_protocol                  = "TCP"
+
+}
+
+# Egress rule for HTTP traffic from ALB to service
+resource "aws_vpc_security_group_egress_rule" "service_outbound_alb" {
+  security_group_id            = aws_security_group.teamcity_alb_sg.id
+  referenced_security_group_id = aws_security_group.teamcity_service_sg.id
+  description                  = "Allow outbound HTTP traffic from ALB to service containers"
+  from_port                    = var.container_port
+  to_port                      = var.container_port
+  ip_protocol                  = "TCP"
+
+}
+
+# Grant TeamCity service access to internet
+resource "aws_vpc_security_group_egress_rule" "internet_outbound" {
+  security_group_id = aws_security_group.teamcity_service_sg.id
+  description       = "Allow outbound internet access from TeamCity service containers"
+  cidr_ipv4         = "0.0.0.0/0"
+  ip_protocol       = "-1"
+}
+
+
+#############################################
+# IAM Roles for TeamCity Module
+#############################################
+data "aws_iam_policy_document" "ecs_tasks_trust_relationship" {
+  statement {
+    effect  = "Allow"
+    actions = ["sts:AssumeRole"]
+    principals {
+      type        = "Service"
+      identifiers = ["ecs-tasks.amazonaws.com"]
+    }
+  }
+}
+data "aws_iam_policy_document" "teamcity_default_policy" {
+  # ECS
+  statement {
+    sid    = "ECSExec"
+    effect = "Allow"
+    actions = [
+      "ssmmessages:OpenDataChannel",
+      "ssmmessages:OpenControlChannel",
+      "ssmmessages:CreateDataChannel",
+      "ssmmessages:CreateControlChannel",
+    ]
+    resources = [
+      "*"
+    ]
+  }
+}
+resource "aws_iam_policy" "teamcity_default_policy" {
+  name        = "teamcity-default-policy"
+  description = "Policy granting permissions for Unreal Horde."
+  policy      = data.aws_iam_policy_document.teamcity_default_policy.json
+}
+
+resource "aws_iam_role" "teamcity_default_role" {
+  name               = "teamcity-default-role"
+  description        = "Default role for TeamCity ECS task."
+  assume_role_policy = data.aws_iam_policy_document.ecs_tasks_trust_relationship.json
+  managed_policy_arns = [
+    aws_iam_policy.teamcity_default_policy.arn
+  ]
+
+  tags = local.tags
+}
+resource "aws_iam_role" "teamcity_task_execution_role" {
+  name = "teamcity-task-execution-role"
+
+  assume_role_policy  = data.aws_iam_policy_document.ecs_tasks_trust_relationship.json
+  managed_policy_arns = ["arn:aws:iam::aws:policy/service-role/AmazonECSTaskExecutionRolePolicy"]
+}
+
+# CloudWatch Logs
+resource "aws_cloudwatch_log_group" "teamcity_log_group" {
+  #checkov:skip=CKV_AWS_158: KMS Encryption disabled by default
+  name              = "${local.name_prefix}-log-group"
+  retention_in_days = var.teamcity_cloudwatch_log_retention_in_days
+  tags              = local.tags
+}
+
+
+# Load Balancer for TeamCity Service
+resource "aws_lb" "teamcity_external_lb" {
+  name            = "${local.name_prefix}-lb"
+  security_groups = [aws_security_group.teamcity_alb_sg.id]
+
+  load_balancer_type         = "application"
+  internal                   = false
+  subnets                    = var.alb_subnets
+  drop_invalid_header_fields = true
+  tags                       = local.tags
+}
+
+# TeamCity target group for ALB
+resource "aws_lb_target_group" "teamcity_target_group" {
+  name        = "${local.name_prefix}-tg"
+  port        = var.container_port
+  protocol    = "HTTP"
+  vpc_id      = var.vpc_id
+  target_type = "ip"
+
+  health_check {
+    path                = "/healthCheck/healthy"
+    interval            = 30
+    timeout             = 5
+    healthy_threshold   = 5
+    unhealthy_threshold = 2
+    port                = var.container_port
+    protocol            = "HTTP"
+    matcher             = "200"
+  }
+
+  tags = local.tags
+}
+
+# ALB HTTPS Listener
+resource "aws_lb_listener" "teamcity_listener" {
+  load_balancer_arn = aws_lb.teamcity_external_lb.arn
+  port              = "443"
+  protocol          = "HTTPS"
+  ssl_policy        = "ELBSecurityPolicy-TLS13-1-2-2021-06"
+  certificate_arn   = var.alb_certificate_arn
+  default_action {
+    type             = "forward"
+    target_group_arn = aws_lb_target_group.teamcity_target_group.arn
+  }
+  tags = local.tags
+}
+
+
+
+
+# #######################################
+# # TeamCity Aurora Serverless Database #
+# #######################################
+# Subnet group
+resource "aws_db_subnet_group" "teamcity_db_subnet_group" {
+  name       = "${local.name_prefix}-db-subnet-group"
+  subnet_ids = var.service_subnets
+  tags       = local.tags
+}
+
+# # RDS instance with Aurora serverless engine
+resource "aws_rds_cluster" "teamcity_db_cluster" {
+  cluster_identifier   = "teamcity-cluster"
+  engine               = "aurora-postgresql"
+  engine_mode          = "provisioned"
+  engine_version       = "16.6"
+  database_name        = "teamcity"
+  master_username      = "teamcity"
+  master_password      = "teamcity2025"
+  storage_encrypted    = true
+  db_subnet_group_name = aws_db_subnet_group.teamcity_db_subnet_group.id
+  vpc_security_group_ids = [
+    aws_security_group.teamcity_db_sg.id
+  ]
+
+
+  serverlessv2_scaling_configuration {
+    max_capacity             = 1.0
+    min_capacity             = 0.0
+    seconds_until_auto_pause = 3600
+  }
+}
+
+resource "aws_rds_cluster_instance" "teamcity_db_cluster" {
+  cluster_identifier = aws_rds_cluster.teamcity_db_cluster.id
+  instance_class     = "db.serverless"
+  engine             = aws_rds_cluster.teamcity_db_cluster.engine
+  engine_version     = aws_rds_cluster.teamcity_db_cluster.engine_version
+
+}
path: modules/teamcity/main.tf
user: login: github-advanced-security[bot]
body: ## Ensure DB instance gets all minor upgrades automatically

Ensure DB instance gets all minor upgrades automatically

[Show more details](https://github.com/aws-games/cloud-game-development-toolkit/security/code-scanning/815)
created_at: 2025-02-06T21:14:49Z
author_association: NONE
diff_hunk: @@ -0,0 +1,413 @@
+###################################
+# ECS Cluster for TeamCity Server #
+###################################
+
+resource "aws_ecs_cluster" "teamcity_cluster" {
+  name = "${local.name_prefix}-cluster"
+
+  setting {
+    name  = "containerInsights"
+    value = "enabled"
+  }
+
+  tags = local.tags
+}
+
+
+# TeamCity Task Definition
+resource "aws_ecs_task_definition" "teamcity_task_definition" {
+  family                   = var.name
+  requires_compatibilities = ["FARGATE"]
+  network_mode             = "awsvpc"
+  cpu                      = var.container_cpu
+  memory                   = var.container_memory
+
+
+  container_definitions = jsonencode([
+    {
+      name      = var.container_name
+      image     = local.image
+      cpu       = var.container_cpu
+      memory    = var.container_memory
+      essential = true
+      portMappings = [
+        {
+          containerPort = var.container_port
+          hostPort      = var.container_port
+        }
+      ]
+      logConfiguration = {
+        logDriver = "awslogs"
+        options = {
+          awslogs-group         = aws_cloudwatch_log_group.teamcity_log_group.name
+          awslogs-region        = data.aws_region.current.name
+          awslogs-stream-prefix = "[APP]"
+        }
+      },
+      mountPoints = [
+        {
+          sourceVolume  = "teamcity_data",
+          containerPath = "/data/teamcity_server/datadir"
+          readOnly      = false
+        },
+        {
+          sourceVolume  = "teamcity_logs",
+          containerPath = "/data/teamcity_server/logs"
+          readOnly      = false
+        }
+      ]
+    }
+  ])
+
+  tags = {
+    Name = var.name
+  }
+  task_role_arn      = aws_iam_role.teamcity_default_role.arn
+  execution_role_arn = aws_iam_role.teamcity_task_execution_role.arn
+
+  volume {
+    name = "teamcity_data"
+    efs_volume_configuration {
+      file_system_id          = aws_efs_file_system.teamcity_efs_file_system.id
+      transit_encryption      = "ENABLED"
+      transit_encryption_port = 2999
+      authorization_config {
+        access_point_id = aws_efs_access_point.teamcity_efs_data_access_point.id
+        iam             = "ENABLED"
+      }
+    }
+  }
+
+  volume {
+    name = "teamcity_logs"
+    efs_volume_configuration {
+      file_system_id     = aws_efs_file_system.teamcity_efs_file_system.id
+      transit_encryption = "ENABLED"
+      authorization_config {
+        access_point_id = aws_efs_access_point.teamcity_efs_logs_access_point.id
+        iam             = "ENABLED"
+      }
+    }
+  }
+}
+
+
+
+
+# ECS Service
+resource "aws_ecs_service" "teamcity" {
+  name = local.name_prefix
+
+  cluster                = aws_ecs_cluster.teamcity_cluster.name
+  task_definition        = aws_ecs_task_definition.teamcity_task_definition.arn
+  launch_type            = "FARGATE"
+  desired_count          = 1    #TODO: make this configurable
+  force_new_deployment   = true #TODO: make this configurable
+  enable_execute_command = true #TODO: make this configurable
+
+  wait_for_steady_state = true
+
+  network_configuration {
+    subnets         = var.service_subnets
+    security_groups = [aws_security_group.teamcity_service_sg.id]
+  }
+  load_balancer {
+    target_group_arn = aws_lb_target_group.teamcity_target_group.arn
+    container_name   = var.container_name
+    container_port   = var.container_port
+  }
+
+  tags = local.tags
+}
+
+# TeamCity service security group
+resource "aws_security_group" "teamcity_service_sg" {
+  name   = "${local.name_prefix}-service-sg"
+  vpc_id = var.vpc_id
+
+  tags = local.tags
+}
+
+# TeamCity EFS security group
+resource "aws_security_group" "teamcity_efs_sg" {
+  name        = "${local.name_prefix}-efs-sg"
+  description = "TeamCity EFS mount target security group"
+  vpc_id      = var.vpc_id
+
+  tags = local.tags
+}
+
+# Ingress rule for NFS traffic from service to EFS
+resource "aws_vpc_security_group_ingress_rule" "service_efs" {
+  security_group_id            = aws_security_group.teamcity_efs_sg.id
+  referenced_security_group_id = aws_security_group.teamcity_service_sg.id
+  description                  = "Allow inbound access from TeamCity service containers to EFS"
+  ip_protocol                  = "TCP"
+  from_port                    = 2049
+  to_port                      = 2049
+}
+
+# Ingress rule for NFS traffic from EFS to service
+resource "aws_vpc_security_group_ingress_rule" "efs_service" {
+  security_group_id            = aws_security_group.teamcity_service_sg.id
+  referenced_security_group_id = aws_security_group.teamcity_efs_sg.id
+  description                  = "Allow inbound access from EFS to TeamCity service containers"
+  ip_protocol                  = "TCP"
+  from_port                    = 2049
+  to_port                      = 2049
+}
+
+
+
+
+# TeamCity ALB security group
+resource "aws_security_group" "teamcity_alb_sg" {
+  name   = "${local.name_prefix}-alb-sg"
+  vpc_id = var.vpc_id
+
+  tags = local.tags
+}
+
+# Ingress rule for HTTP traffic from ALB to service
+resource "aws_vpc_security_group_ingress_rule" "service_inbound_alb" {
+  security_group_id            = aws_security_group.teamcity_service_sg.id
+  referenced_security_group_id = aws_security_group.teamcity_alb_sg.id
+  from_port                    = var.container_port
+  to_port                      = var.container_port
+  ip_protocol                  = "TCP"
+
+}
+
+# Egress rule for HTTP traffic from ALB to service
+resource "aws_vpc_security_group_egress_rule" "service_outbound_alb" {
+  security_group_id            = aws_security_group.teamcity_alb_sg.id
+  referenced_security_group_id = aws_security_group.teamcity_service_sg.id
+  from_port                    = var.container_port
+  to_port                      = var.container_port
+  ip_protocol                  = "TCP"
+
+}
+
+# Grant TeamCity service access to internet
+resource "aws_vpc_security_group_egress_rule" "internet_outbound" {
+
+  security_group_id = aws_security_group.teamcity_service_sg.id
+  cidr_ipv4         = "0.0.0.0/0"
+  ip_protocol       = "-1"
+}
+
+#############################################
+# IAM Roles for TeamCity Module
+#############################################
+data "aws_iam_policy_document" "ecs_tasks_trust_relationship" {
+  statement {
+    effect  = "Allow"
+    actions = ["sts:AssumeRole"]
+    principals {
+      type        = "Service"
+      identifiers = ["ecs-tasks.amazonaws.com"]
+    }
+  }
+}
+data "aws_iam_policy_document" "teamcity_default_policy" {
+  # ECS
+  statement {
+    sid    = "ECSExec"
+    effect = "Allow"
+    actions = [
+      "ssmmessages:OpenDataChannel",
+      "ssmmessages:OpenControlChannel",
+      "ssmmessages:CreateDataChannel",
+      "ssmmessages:CreateControlChannel",
+    ]
+    resources = [
+      "*"
+    ]
+  }
+}
+resource "aws_iam_policy" "teamcity_default_policy" {
+  name        = "teamcity-default-policy"
+  description = "Policy granting permissions for Unreal Horde."
+  policy      = data.aws_iam_policy_document.teamcity_default_policy.json
+}
+
+resource "aws_iam_role" "teamcity_default_role" {
+  name               = "teamcity-default-role"
+  assume_role_policy = data.aws_iam_policy_document.ecs_tasks_trust_relationship.json
+
+  managed_policy_arns = [
+    aws_iam_policy.teamcity_default_policy.arn
+  ]
+
+  tags = local.tags
+}
+resource "aws_iam_role" "teamcity_task_execution_role" {
+  name = "teamcity-task-execution-role"
+
+  assume_role_policy  = data.aws_iam_policy_document.ecs_tasks_trust_relationship.json
+  managed_policy_arns = ["arn:aws:iam::aws:policy/service-role/AmazonECSTaskExecutionRolePolicy"]
+}
+
+# CloudWatch Logs
+resource "aws_cloudwatch_log_group" "teamcity_log_group" {
+  #checkov:skip=CKV_AWS_158: KMS Encryption disabled by default
+  name              = "${local.name_prefix}-log-group"
+  retention_in_days = var.teamcity_cloudwatch_log_retention_in_days
+  tags              = local.tags
+}
+
+
+# Load Balancer for TeamCity Service
+resource "aws_lb" "teamcity_external_lb" {
+  name            = "${local.name_prefix}-lb"
+  security_groups = [aws_security_group.teamcity_alb_sg.id]
+
+  load_balancer_type         = "application"
+  internal                   = false
+  subnets                    = var.alb_subnets
+  drop_invalid_header_fields = true
+  tags                       = local.tags
+}
+
+# TeamCity target group for ALB
+resource "aws_lb_target_group" "teamcity_target_group" {
+  name        = "${local.name_prefix}-tg"
+  port        = var.container_port
+  protocol    = "HTTP"
+  vpc_id      = var.vpc_id
+  target_type = "ip"
+
+  # health_check {
+  #   path                = "/"
+  #   interval            = 30
+  #   timeout             = 5
+  #   healthy_threshold   = 2
+  #   unhealthy_threshold = 2
+  # }
path: modules/teamcity/main.tf
body: I think it makes sense to pick just one. At your discretion / testing to determine which one you want to use.
created_at: 2025-02-06T23:36:44Z
author_association: NONE
diff_hunk: @@ -0,0 +1,492 @@
+###################################
+# ECS Cluster for TeamCity Server #
+###################################
+
+resource "aws_ecs_cluster" "teamcity_cluster" {
+  name = "${local.name_prefix}-cluster"
+
+  setting {
+    name  = "containerInsights"
+    value = "enabled"
+  }
+
+  tags = local.tags
+}
+
+# TeamCity Task Definition
+resource "aws_ecs_task_definition" "teamcity_task_definition" {
+  family                   = var.name
+  requires_compatibilities = ["FARGATE"]
+  network_mode             = "awsvpc"
+  cpu                      = var.container_cpu
+  memory                   = var.container_memory
+
+  container_definitions = jsonencode([
+    {
+      name      = var.container_name
+      image     = local.image
+      cpu       = var.container_cpu
+      memory    = var.container_memory
+      essential = true
+      portMappings = [
+        {
+          containerPort = var.container_port
+          hostPort      = var.container_port
+        }
+      ]
+      logConfiguration = {
+        logDriver = "awslogs"
+        options = {
+          awslogs-group         = aws_cloudwatch_log_group.teamcity_log_group.name
+          awslogs-region        = data.aws_region.current.name
+          awslogs-stream-prefix = "[APP]"
+        }
+      }
+      mountPoints = [
+        {
+          sourceVolume  = "teamcity_data",
+          containerPath = "/data/teamcity_server/datadir"
+          readOnly      = false
+        },
+        {
+          sourceVolume  = "teamcity_logs",
+          containerPath = "/data/teamcity_server/logs"
+          readOnly      = false
+        }
+      ]
+      # Set environment variables for database connection string
+      environment = [
+        {
+          name  = "TEAMCITY_DB_URL"
+          value = local.database_connection_string
+        },
+        {
+          name  = "TEAMCITY_DB_USER"
+          value = local.database_user
+        },
+        {
+          name  = "TEAMCITY_DB_PASSWORD"
+          value = local.database_password
+        },
+        {
+          name  = "TEAMCITY_LOGS_PATH"
+          value = "/data/teamcity_server/logs"
+        }
+      ]
+    }
+  ])
+  tags = {
+    Name = var.name
+  }
+  task_role_arn      = aws_iam_role.teamcity_default_role.arn
+  execution_role_arn = aws_iam_role.teamcity_task_execution_role.arn
+
+  volume {
+    name = "teamcity_data"
+    efs_volume_configuration {
+      file_system_id          = aws_efs_file_system.teamcity_efs_file_system.id
+      transit_encryption      = "ENABLED"
+      transit_encryption_port = 2999
+      authorization_config {
+        access_point_id = aws_efs_access_point.teamcity_efs_data_access_point.id
+        iam             = "ENABLED"
+      }
+    }
+  }
+
+  volume {
+    name = "teamcity_logs"
+    efs_volume_configuration {
+      file_system_id          = aws_efs_file_system.teamcity_efs_file_system.id
+      transit_encryption      = "ENABLED"
+      transit_encryption_port = 2998
+      authorization_config {
+        access_point_id = aws_efs_access_point.teamcity_efs_logs_access_point.id
+        iam             = "ENABLED"
+      }
+    }
+  }
+}
+
+
+
+
+# ECS Service
+resource "aws_ecs_service" "teamcity" {
+  name = local.name_prefix
+
+  cluster                = aws_ecs_cluster.teamcity_cluster.name
+  task_definition        = aws_ecs_task_definition.teamcity_task_definition.arn
+  launch_type            = "FARGATE"
+  desired_count          = 1    #TODO: make this configurable
+  force_new_deployment   = true #TODO: make this configurable
+  enable_execute_command = true #TODO: make this configurable
+
+  wait_for_steady_state = true
+
+  network_configuration {
+    subnets         = var.service_subnets
+    security_groups = [aws_security_group.teamcity_service_sg.id]
+  }
+  load_balancer {
+    target_group_arn = aws_lb_target_group.teamcity_target_group.arn
+    container_name   = var.container_name
+    container_port   = var.container_port
+  }
+
+  tags = local.tags
+}
+
+# TeamCity service security group
+resource "aws_security_group" "teamcity_service_sg" {
+  name   = "${local.name_prefix}-service-sg"
+  vpc_id = var.vpc_id
+
+  tags = local.tags
+}
+
+# TeamCity EFS security group
+resource "aws_security_group" "teamcity_efs_sg" {
+  name        = "${local.name_prefix}-efs-sg"
+  description = "TeamCity EFS mount target security group"
+  vpc_id      = var.vpc_id
+
+  tags = local.tags
+}
+
+# Ingress rule for NFS traffic from service to EFS
+resource "aws_vpc_security_group_ingress_rule" "service_efs" {
+  security_group_id            = aws_security_group.teamcity_efs_sg.id
+  referenced_security_group_id = aws_security_group.teamcity_service_sg.id
+  description                  = "Allow inbound access from TeamCity service containers to EFS"
+  ip_protocol                  = "TCP"
+  from_port                    = 2049
+  to_port                      = 2049
+}
+
+# Ingress rule for NFS traffic from EFS to service
+resource "aws_vpc_security_group_ingress_rule" "efs_service" {
+  security_group_id            = aws_security_group.teamcity_service_sg.id
+  referenced_security_group_id = aws_security_group.teamcity_efs_sg.id
+  description                  = "Allow inbound access from EFS to TeamCity service containers"
+  ip_protocol                  = "TCP"
+  from_port                    = 2049
+  to_port                      = 2049
+}
+
+# TeamCity Aurora Serverless PostgreSQL security group
+resource "aws_security_group" "teamcity_db_sg" {
+  name        = "${local.name_prefix}-db-sg"
+  description = "TeamCity DB security group"
+  vpc_id      = var.vpc_id
+
+  tags = local.tags
+}
+
+# Ingress rule for PostgreSQL from service to database cluster
+resource "aws_vpc_security_group_ingress_rule" "service_db" {
+  security_group_id            = aws_security_group.teamcity_db_sg.id
+  referenced_security_group_id = aws_security_group.teamcity_service_sg.id
+  description                  = "Allow inbound access from TeamCity service containers to DB"
+  ip_protocol                  = "TCP"
+  from_port                    = 5432
+  to_port                      = 5432
+}
+
+# Ingress rule for PostgreSQL from database cluster to service
+resource "aws_vpc_security_group_ingress_rule" "db_service" {
+  security_group_id            = aws_security_group.teamcity_service_sg.id
+  referenced_security_group_id = aws_security_group.teamcity_db_sg.id
+  description                  = "Allow inbound access from DB to TeamCity service containers"
+  ip_protocol                  = "TCP"
+  from_port                    = 5432
+  to_port                      = 5432
+}
+
+# Egress rule for database to let all traffic out
+resource "aws_vpc_security_group_egress_rule" "db_outbound" {
+
+  security_group_id = aws_security_group.teamcity_db_sg.id
+  cidr_ipv4         = "0.0.0.0/0"
+  ip_protocol       = "-1"
+}
+
+# TeamCity ALB security group
+resource "aws_security_group" "teamcity_alb_sg" {
+  name   = "${local.name_prefix}-alb-sg"
+  vpc_id = var.vpc_id
+
+  tags = local.tags
+}
+
+# Ingress rule to allow ALB to send health checks to service
+resource "aws_vpc_security_group_ingress_rule" "alb_service_healthcheck" {
+  security_group_id            = aws_security_group.teamcity_service_sg.id
+  referenced_security_group_id = aws_security_group.teamcity_alb_sg.id
+  description                  = "Allow health checks from ALB to service containers"
+  ip_protocol                  = "TCP"
+  from_port                    = 80
+  to_port                      = 80
+}
+
+# Ingress rule for HTTP traffic from ALB to service
+resource "aws_vpc_security_group_ingress_rule" "service_inbound_alb" {
+  security_group_id            = aws_security_group.teamcity_service_sg.id
+  referenced_security_group_id = aws_security_group.teamcity_alb_sg.id
+  description                  = "Allow inbound HTTP traffic from ALB to service containers"
+  from_port                    = var.container_port
+  to_port                      = var.container_port
+  ip_protocol                  = "TCP"
+
+}
+
+# Egress rule for HTTP traffic from ALB to service
+resource "aws_vpc_security_group_egress_rule" "service_outbound_alb" {
+  security_group_id            = aws_security_group.teamcity_alb_sg.id
+  referenced_security_group_id = aws_security_group.teamcity_service_sg.id
+  description                  = "Allow outbound HTTP traffic from ALB to service containers"
+  from_port                    = var.container_port
+  to_port                      = var.container_port
+  ip_protocol                  = "TCP"
+
+}
+
+# Grant TeamCity service access to internet
+resource "aws_vpc_security_group_egress_rule" "internet_outbound" {
+  security_group_id = aws_security_group.teamcity_service_sg.id
+  description       = "Allow outbound internet access from TeamCity service containers"
+  cidr_ipv4         = "0.0.0.0/0"
+  ip_protocol       = "-1"
+}
+
+
+#############################################
+# IAM Roles for TeamCity Module
+#############################################
+data "aws_iam_policy_document" "ecs_tasks_trust_relationship" {
+  statement {
+    effect  = "Allow"
+    actions = ["sts:AssumeRole"]
+    principals {
+      type        = "Service"
+      identifiers = ["ecs-tasks.amazonaws.com"]
+    }
+  }
+}
+data "aws_iam_policy_document" "teamcity_default_policy" {
+  # ECS
+  statement {
+    sid    = "ECSExec"
+    effect = "Allow"
+    actions = [
+      "ssmmessages:OpenDataChannel",
+      "ssmmessages:OpenControlChannel",
+      "ssmmessages:CreateDataChannel",
+      "ssmmessages:CreateControlChannel",
+    ]
+    resources = [
+      "*"
+    ]
+  }
+}
+resource "aws_iam_policy" "teamcity_default_policy" {
+  name        = "teamcity-default-policy"
+  description = "Policy granting permissions for Unreal Horde."
+  policy      = data.aws_iam_policy_document.teamcity_default_policy.json
+}
+
+resource "aws_iam_role" "teamcity_default_role" {
+  name               = "teamcity-default-role"
+  description        = "Default role for TeamCity ECS task."
+  assume_role_policy = data.aws_iam_policy_document.ecs_tasks_trust_relationship.json
+  managed_policy_arns = [
+    aws_iam_policy.teamcity_default_policy.arn
+  ]
+
+  tags = local.tags
+}
+resource "aws_iam_role" "teamcity_task_execution_role" {
+  name = "teamcity-task-execution-role"
+
+  assume_role_policy  = data.aws_iam_policy_document.ecs_tasks_trust_relationship.json
+  managed_policy_arns = ["arn:aws:iam::aws:policy/service-role/AmazonECSTaskExecutionRolePolicy"]
+}
+
+# CloudWatch Logs
+resource "aws_cloudwatch_log_group" "teamcity_log_group" {
+  #checkov:skip=CKV_AWS_158: KMS Encryption disabled by default
+  name              = "${local.name_prefix}-log-group"
+  retention_in_days = var.teamcity_cloudwatch_log_retention_in_days
+  tags              = local.tags
+}
+
+
+# Load Balancer for TeamCity Service
+resource "aws_lb" "teamcity_external_lb" {
+  name            = "${local.name_prefix}-lb"
+  security_groups = [aws_security_group.teamcity_alb_sg.id]
+
+  load_balancer_type         = "application"
+  internal                   = false
+  subnets                    = var.alb_subnets
+  drop_invalid_header_fields = true
+  tags                       = local.tags
+}
+
+# TeamCity target group for ALB
+resource "aws_lb_target_group" "teamcity_target_group" {
+  name        = "${local.name_prefix}-tg"
+  port        = var.container_port
+  protocol    = "HTTP"
+  vpc_id      = var.vpc_id
+  target_type = "ip"
+
+  health_check {
+    path                = "/healthCheck/healthy"
+    interval            = 30
+    timeout             = 5
+    healthy_threshold   = 5
+    unhealthy_threshold = 2
+    port                = var.container_port
+    protocol            = "HTTP"
+    matcher             = "200"
+  }
+
+  tags = local.tags
+}
+
+# ALB HTTPS Listener
+resource "aws_lb_listener" "teamcity_listener" {
+  load_balancer_arn = aws_lb.teamcity_external_lb.arn
+  port              = "443"
+  protocol          = "HTTPS"
+  ssl_policy        = "ELBSecurityPolicy-TLS13-1-2-2021-06"
+  certificate_arn   = var.alb_certificate_arn
+  default_action {
+    type             = "forward"
+    target_group_arn = aws_lb_target_group.teamcity_target_group.arn
+  }
+  tags = local.tags
+}
+
+
+
+
+# #######################################
+# # TeamCity Aurora Serverless Database #
+# #######################################
+# Subnet group
+resource "aws_db_subnet_group" "teamcity_db_subnet_group" {
+  name       = "${local.name_prefix}-db-subnet-group"
+  subnet_ids = var.service_subnets
+  tags       = local.tags
+}
+
+# # RDS instance with Aurora serverless engine
+resource "aws_rds_cluster" "teamcity_db_cluster" {
+  cluster_identifier   = "teamcity-cluster"
+  engine               = "aurora-postgresql"
+  engine_mode          = "provisioned"
+  engine_version       = "16.6"
+  database_name        = "teamcity"
+  master_username      = "teamcity"
+  master_password      = "teamcity2025"
+  storage_encrypted    = true
+  db_subnet_group_name = aws_db_subnet_group.teamcity_db_subnet_group.id
+  vpc_security_group_ids = [
+    aws_security_group.teamcity_db_sg.id
+  ]
+
+
+  serverlessv2_scaling_configuration {
+    max_capacity             = 1.0
+    min_capacity             = 0.0
+    seconds_until_auto_pause = 3600
+  }
+}
path: modules/teamcity/main.tf
body: @cwwalb This is what I was referring to. We likely want to use IAM based authentication for connections from service -> cluster. I am not sure this works with the basic driver, so we probably need to do some more research here. Like, are the DB username and password environment variables we were passing optional? I am not sure.
created_at: 2025-02-06T23:37:55Z
author_association: NONE
diff_hunk: @@ -0,0 +1,11 @@
+locals {
+  name_prefix = "teamcity"
+  tags = merge(var.tags, {
+    "environment" = var.environment
+  })
+  image                      = "jetbrains/teamcity-server"
+  database_connection_string = "jdbc:postgresql://teamcity-cluster.cluster-cwmktevrnnqg.us-east-1.rds.amazonaws.com:5432/teamcity"
+  database_user              = "teamcity"
+  database_password          = "teamcity2025"
path: modules/teamcity/local.tf
body: I am not sure how [IAM database authentication](https://docs.aws.amazon.com/AmazonRDS/latest/AuroraUserGuide/UsingWithRDS.IAMDBAuth.html) works, but I'll do some digging here to determine if this is necessary. These should certainly be variables configured by the user (with defaults) if we cannot use IAM based authentication.
created_at: 2025-02-06T23:40:13Z
author_association: NONE
diff_hunk: @@ -0,0 +1,467 @@
+###################################
+# ECS Cluster for TeamCity Server #
+###################################
+
+resource "aws_ecs_cluster" "teamcity_cluster" {
+  name = "${local.name_prefix}-cluster"
+
+  setting {
+    name  = "containerInsights"
+    value = "enabled"
+  }
+
+  tags = local.tags
+}
+
+# TeamCity Task Definition
+resource "aws_ecs_task_definition" "teamcity_task_definition" {
+  family                   = var.name
+  requires_compatibilities = ["FARGATE"]
+  network_mode             = "awsvpc"
+  cpu                      = var.container_cpu
+  memory                   = var.container_memory
+
+  container_definitions = jsonencode([
+    {
+      name      = var.container_name
+      image     = local.image
+      cpu       = var.container_cpu
+      memory    = var.container_memory
+      essential = true
+      portMappings = [
+        {
+          containerPort = var.container_port
+          hostPort      = var.container_port
+        }
+      ]
+      logConfiguration = {
+        logDriver = "awslogs"
+        options = {
+          awslogs-group         = aws_cloudwatch_log_group.teamcity_log_group.name
+          awslogs-region        = data.aws_region.current.name
+          awslogs-stream-prefix = "[APP]"
+        }
+      }
+      mountPoints = [
+        {
+          sourceVolume  = "teamcity_data",
+          containerPath = "/data/teamcity_server/datadir"
+          readOnly      = false
+        },
+        {
+          sourceVolume  = "teamcity_logs",
+          containerPath = "/data/teamcity_server/logs"
+          readOnly      = false
+        }
+      ]
+      # Set environment variables for database connection string
+      environment = [
+        {
+          name  = "TEAMCITY_DB_URL"
+          value = local.database_connection_string
+        },
+        {
+          name  = "TEAMCITY_DB_USER"
+          value = local.database_user
+        },
+        {
+          name  = "TEAMCITY_DB_PASSWORD"
+          value = local.database_password
+        },
+        {
+          name  = "TEAMCITY_LOGS_PATH"
+          value = "/data/teamcity_server/logs"
+        }
+      ]
+    }
+  ])
+  tags = {
+    Name = var.name
+  }
+  task_role_arn      = aws_iam_role.teamcity_default_role.arn
+  execution_role_arn = aws_iam_role.teamcity_task_execution_role.arn
+
+  volume {
+    name = "teamcity_data"
+    efs_volume_configuration {
+      file_system_id          = aws_efs_file_system.teamcity_efs_file_system.id
+      transit_encryption      = "ENABLED"
+      transit_encryption_port = 2999
+      authorization_config {
+        access_point_id = aws_efs_access_point.teamcity_efs_data_access_point.id
+        iam             = "ENABLED"
+      }
+    }
+  }
+
+  volume {
+    name = "teamcity_logs"
+    efs_volume_configuration {
+      file_system_id          = aws_efs_file_system.teamcity_efs_file_system.id
+      transit_encryption      = "ENABLED"
+      transit_encryption_port = 2998
+      authorization_config {
+        access_point_id = aws_efs_access_point.teamcity_efs_logs_access_point.id
+        iam             = "ENABLED"
+      }
+    }
+  }
+}
+
+# ECS Service
+resource "aws_ecs_service" "teamcity" {
+  name                   = local.name_prefix
+  cluster                = aws_ecs_cluster.teamcity_cluster.name
+  task_definition        = aws_ecs_task_definition.teamcity_task_definition.arn
+  launch_type            = "FARGATE"
+  desired_count          = 1    #TODO: make this configurable
+  force_new_deployment   = true #TODO: make this configurable
+  enable_execute_command = true #TODO: make this configurable
path: modules/teamcity/main.tf
body: These should depend on a `debug` variable.
created_at: 2025-02-06T23:41:46Z
author_association: NONE
diff_hunk: @@ -0,0 +1,467 @@
+###################################
+# ECS Cluster for TeamCity Server #
+###################################
+
+resource "aws_ecs_cluster" "teamcity_cluster" {
+  name = "${local.name_prefix}-cluster"
+
+  setting {
+    name  = "containerInsights"
+    value = "enabled"
+  }
+
+  tags = local.tags
+}
+
+# TeamCity Task Definition
+resource "aws_ecs_task_definition" "teamcity_task_definition" {
+  family                   = var.name
+  requires_compatibilities = ["FARGATE"]
+  network_mode             = "awsvpc"
+  cpu                      = var.container_cpu
+  memory                   = var.container_memory
+
+  container_definitions = jsonencode([
+    {
+      name      = var.container_name
+      image     = local.image
+      cpu       = var.container_cpu
+      memory    = var.container_memory
+      essential = true
+      portMappings = [
+        {
+          containerPort = var.container_port
+          hostPort      = var.container_port
+        }
+      ]
+      logConfiguration = {
+        logDriver = "awslogs"
+        options = {
+          awslogs-group         = aws_cloudwatch_log_group.teamcity_log_group.name
+          awslogs-region        = data.aws_region.current.name
+          awslogs-stream-prefix = "[APP]"
+        }
+      }
+      mountPoints = [
+        {
+          sourceVolume  = "teamcity_data",
+          containerPath = "/data/teamcity_server/datadir"
+          readOnly      = false
+        },
+        {
+          sourceVolume  = "teamcity_logs",
+          containerPath = "/data/teamcity_server/logs"
+          readOnly      = false
+        }
+      ]
+      # Set environment variables for database connection string
+      environment = [
+        {
+          name  = "TEAMCITY_DB_URL"
+          value = local.database_connection_string
+        },
+        {
+          name  = "TEAMCITY_DB_USER"
+          value = local.database_user
+        },
+        {
+          name  = "TEAMCITY_DB_PASSWORD"
+          value = local.database_password
+        },
+        {
+          name  = "TEAMCITY_LOGS_PATH"
+          value = "/data/teamcity_server/logs"
+        }
+      ]
+    }
+  ])
+  tags = {
+    Name = var.name
+  }
+  task_role_arn      = aws_iam_role.teamcity_default_role.arn
+  execution_role_arn = aws_iam_role.teamcity_task_execution_role.arn
+
+  volume {
+    name = "teamcity_data"
+    efs_volume_configuration {
+      file_system_id          = aws_efs_file_system.teamcity_efs_file_system.id
+      transit_encryption      = "ENABLED"
+      transit_encryption_port = 2999
+      authorization_config {
+        access_point_id = aws_efs_access_point.teamcity_efs_data_access_point.id
+        iam             = "ENABLED"
+      }
+    }
+  }
+
+  volume {
+    name = "teamcity_logs"
+    efs_volume_configuration {
+      file_system_id          = aws_efs_file_system.teamcity_efs_file_system.id
+      transit_encryption      = "ENABLED"
+      transit_encryption_port = 2998
+      authorization_config {
+        access_point_id = aws_efs_access_point.teamcity_efs_logs_access_point.id
+        iam             = "ENABLED"
+      }
+    }
+  }
+}
+
+# ECS Service
+resource "aws_ecs_service" "teamcity" {
+  name                   = local.name_prefix
+  cluster                = aws_ecs_cluster.teamcity_cluster.name
+  task_definition        = aws_ecs_task_definition.teamcity_task_definition.arn
+  launch_type            = "FARGATE"
+  desired_count          = 1    #TODO: make this configurable
+  force_new_deployment   = true #TODO: make this configurable
+  enable_execute_command = true #TODO: make this configurable
+  wait_for_steady_state  = true
+
+  network_configuration {
+    subnets         = var.service_subnets
+    security_groups = [aws_security_group.teamcity_service_sg.id]
+  }
+  load_balancer {
+    target_group_arn = aws_lb_target_group.teamcity_target_group.arn
+    container_name   = var.container_name
+    container_port   = var.container_port
+  }
+
+  tags = local.tags
+}
+
+# TeamCity service security group
+resource "aws_security_group" "teamcity_service_sg" {
+  name        = "${local.name_prefix}-service-sg"
+  vpc_id      = var.vpc_id
+  description = "TeamCity service security group"
+  tags        = local.tags
+}
+
+# TeamCity EFS security group
+resource "aws_security_group" "teamcity_efs_sg" {
+  name        = "${local.name_prefix}-efs-sg"
+  description = "TeamCity EFS mount target security group"
+  vpc_id      = var.vpc_id
+  tags        = local.tags
+}
+
+# Ingress rule for NFS traffic from service to EFS
+resource "aws_vpc_security_group_ingress_rule" "service_efs" {
+  security_group_id            = aws_security_group.teamcity_efs_sg.id
+  referenced_security_group_id = aws_security_group.teamcity_service_sg.id
+  description                  = "Allow inbound access from TeamCity service containers to EFS"
+  ip_protocol                  = "TCP"
+  from_port                    = 2049
+  to_port                      = 2049
+}
+
+# Ingress rule for NFS traffic from EFS to service
+resource "aws_vpc_security_group_ingress_rule" "efs_service" {
+  security_group_id            = aws_security_group.teamcity_service_sg.id
+  referenced_security_group_id = aws_security_group.teamcity_efs_sg.id
+  description                  = "Allow inbound access from EFS to TeamCity service containers"
+  ip_protocol                  = "TCP"
+  from_port                    = 2049
+  to_port                      = 2049
+}
+
+# TeamCity Aurora Serverless PostgreSQL security group
+resource "aws_security_group" "teamcity_db_sg" {
+  name        = "${local.name_prefix}-db-sg"
+  description = "TeamCity DB security group"
+  vpc_id      = var.vpc_id
+  tags        = local.tags
+}
+
+# Ingress rule for PostgreSQL from service to database cluster
+resource "aws_vpc_security_group_ingress_rule" "service_db" {
+  security_group_id            = aws_security_group.teamcity_db_sg.id
+  referenced_security_group_id = aws_security_group.teamcity_service_sg.id
+  description                  = "Allow inbound access from TeamCity service containers to DB"
+  ip_protocol                  = "TCP"
+  from_port                    = 5432
+  to_port                      = 5432
+}
+
+# Ingress rule for PostgreSQL from database cluster to service
+resource "aws_vpc_security_group_ingress_rule" "db_service" {
+  security_group_id            = aws_security_group.teamcity_service_sg.id
+  referenced_security_group_id = aws_security_group.teamcity_db_sg.id
+  description                  = "Allow inbound access from DB to TeamCity service containers"
+  ip_protocol                  = "TCP"
+  from_port                    = 5432
+  to_port                      = 5432
+}
+
+# Egress rule for database to let all traffic out
+resource "aws_vpc_security_group_egress_rule" "db_outbound" {
+  security_group_id = aws_security_group.teamcity_db_sg.id
+  description       = "Allow outbound access from DB to all"
+  cidr_ipv4         = "0.0.0.0/0"
+  ip_protocol       = "-1"
+}
+
+# TeamCity ALB security group
+resource "aws_security_group" "teamcity_alb_sg" {
+  name        = "${local.name_prefix}-alb-sg"
+  vpc_id      = var.vpc_id
+  description = "TeamCity ALB security group"
+  tags        = local.tags
+}
+
+# Ingress rule for HTTP traffic from ALB to service
+resource "aws_vpc_security_group_ingress_rule" "service_inbound_alb" {
+  security_group_id            = aws_security_group.teamcity_service_sg.id
+  referenced_security_group_id = aws_security_group.teamcity_alb_sg.id
+  description                  = "Allow inbound HTTP traffic from ALB to service containers"
+  from_port                    = var.container_port
+  to_port                      = var.container_port
+  ip_protocol                  = "TCP"
+}
+
+# Egress rule for HTTP traffic from ALB to service
+resource "aws_vpc_security_group_egress_rule" "service_outbound_alb" {
+  security_group_id            = aws_security_group.teamcity_alb_sg.id
+  referenced_security_group_id = aws_security_group.teamcity_service_sg.id
+  description                  = "Allow outbound HTTP traffic from ALB to service containers"
+  from_port                    = var.container_port
+  to_port                      = var.container_port
+  ip_protocol                  = "TCP"
+}
+
+# Grant TeamCity service access to internet
+resource "aws_vpc_security_group_egress_rule" "internet_outbound" {
+  security_group_id = aws_security_group.teamcity_service_sg.id
+  description       = "Allow outbound internet access from TeamCity service containers"
+  cidr_ipv4         = "0.0.0.0/0"
+  ip_protocol       = "-1"
+}
+
+#############################################
+# IAM Roles for TeamCity Module
+#############################################
+
+data "aws_iam_policy_document" "ecs_tasks_trust_relationship" {
+  statement {
+    effect  = "Allow"
+    actions = ["sts:AssumeRole"]
+    principals {
+      type        = "Service"
+      identifiers = ["ecs-tasks.amazonaws.com"]
+    }
+  }
+}
+
+data "aws_iam_policy_document" "teamcity_default_policy" {
+  # ECS
+  statement {
+    sid    = "ECSExec"
+    effect = "Allow"
+    actions = [
+      "ssmmessages:OpenDataChannel",
+      "ssmmessages:OpenControlChannel",
+      "ssmmessages:CreateDataChannel",
+      "ssmmessages:CreateControlChannel",
+    ]
+    resources = [
+      "*"
+    ]
+  }
+}
path: modules/teamcity/main.tf
body: I think we are probably missing some [EFS permissions](https://docs.aws.amazon.com/efs/latest/ug/iam-access-control-nfs-efs.html) - that could be impacting our access point usage.
created_at: 2025-02-06T23:43:35Z
author_association: NONE
diff_hunk: @@ -0,0 +1,413 @@
+###################################
+# ECS Cluster for TeamCity Server #
+###################################
+
+resource "aws_ecs_cluster" "teamcity_cluster" {
+  name = "${local.name_prefix}-cluster"
+
+  setting {
+    name  = "containerInsights"
+    value = "enabled"
+  }
+
+  tags = local.tags
+}
+
+
+# TeamCity Task Definition
+resource "aws_ecs_task_definition" "teamcity_task_definition" {
+  family                   = var.name
+  requires_compatibilities = ["FARGATE"]
+  network_mode             = "awsvpc"
+  cpu                      = var.container_cpu
+  memory                   = var.container_memory
+
+
+  container_definitions = jsonencode([
+    {
+      name      = var.container_name
+      image     = local.image
+      cpu       = var.container_cpu
+      memory    = var.container_memory
+      essential = true
+      portMappings = [
+        {
+          containerPort = var.container_port
+          hostPort      = var.container_port
+        }
+      ]
+      logConfiguration = {
+        logDriver = "awslogs"
+        options = {
+          awslogs-group         = aws_cloudwatch_log_group.teamcity_log_group.name
+          awslogs-region        = data.aws_region.current.name
+          awslogs-stream-prefix = "[APP]"
+        }
+      },
+      mountPoints = [
+        {
+          sourceVolume  = "teamcity_data",
+          containerPath = "/data/teamcity_server/datadir"
+          readOnly      = false
+        },
+        {
+          sourceVolume  = "teamcity_logs",
+          containerPath = "/data/teamcity_server/logs"
+          readOnly      = false
+        }
+      ]
+    }
+  ])
+
+  tags = {
+    Name = var.name
+  }
+  task_role_arn      = aws_iam_role.teamcity_default_role.arn
+  execution_role_arn = aws_iam_role.teamcity_task_execution_role.arn
+
+  volume {
+    name = "teamcity_data"
+    efs_volume_configuration {
+      file_system_id          = aws_efs_file_system.teamcity_efs_file_system.id
+      transit_encryption      = "ENABLED"
+      transit_encryption_port = 2999
+      authorization_config {
+        access_point_id = aws_efs_access_point.teamcity_efs_data_access_point.id
+        iam             = "ENABLED"
+      }
+    }
+  }
+
+  volume {
+    name = "teamcity_logs"
+    efs_volume_configuration {
+      file_system_id     = aws_efs_file_system.teamcity_efs_file_system.id
+      transit_encryption = "ENABLED"
+      authorization_config {
+        access_point_id = aws_efs_access_point.teamcity_efs_logs_access_point.id
+        iam             = "ENABLED"
+      }
+    }
+  }
+}
+
+
+
+
+# ECS Service
+resource "aws_ecs_service" "teamcity" {
+  name = local.name_prefix
+
+  cluster                = aws_ecs_cluster.teamcity_cluster.name
+  task_definition        = aws_ecs_task_definition.teamcity_task_definition.arn
+  launch_type            = "FARGATE"
+  desired_count          = 1    #TODO: make this configurable
+  force_new_deployment   = true #TODO: make this configurable
+  enable_execute_command = true #TODO: make this configurable
+
+  wait_for_steady_state = true
+
+  network_configuration {
+    subnets         = var.service_subnets
+    security_groups = [aws_security_group.teamcity_service_sg.id]
+  }
+  load_balancer {
+    target_group_arn = aws_lb_target_group.teamcity_target_group.arn
+    container_name   = var.container_name
+    container_port   = var.container_port
+  }
+
+  tags = local.tags
+}
+
+# TeamCity service security group
+resource "aws_security_group" "teamcity_service_sg" {
+  name   = "${local.name_prefix}-service-sg"
+  vpc_id = var.vpc_id
+
+  tags = local.tags
+}
+
+# TeamCity EFS security group
+resource "aws_security_group" "teamcity_efs_sg" {
+  name        = "${local.name_prefix}-efs-sg"
+  description = "TeamCity EFS mount target security group"
+  vpc_id      = var.vpc_id
+
+  tags = local.tags
+}
+
+# Ingress rule for NFS traffic from service to EFS
+resource "aws_vpc_security_group_ingress_rule" "service_efs" {
+  security_group_id            = aws_security_group.teamcity_efs_sg.id
+  referenced_security_group_id = aws_security_group.teamcity_service_sg.id
+  description                  = "Allow inbound access from TeamCity service containers to EFS"
+  ip_protocol                  = "TCP"
+  from_port                    = 2049
+  to_port                      = 2049
+}
+
+# Ingress rule for NFS traffic from EFS to service
+resource "aws_vpc_security_group_ingress_rule" "efs_service" {
+  security_group_id            = aws_security_group.teamcity_service_sg.id
+  referenced_security_group_id = aws_security_group.teamcity_efs_sg.id
+  description                  = "Allow inbound access from EFS to TeamCity service containers"
+  ip_protocol                  = "TCP"
+  from_port                    = 2049
+  to_port                      = 2049
+}
+
+
+
+
+# TeamCity ALB security group
+resource "aws_security_group" "teamcity_alb_sg" {
+  name   = "${local.name_prefix}-alb-sg"
+  vpc_id = var.vpc_id
+
+  tags = local.tags
+}
+
+# Ingress rule for HTTP traffic from ALB to service
+resource "aws_vpc_security_group_ingress_rule" "service_inbound_alb" {
+  security_group_id            = aws_security_group.teamcity_service_sg.id
+  referenced_security_group_id = aws_security_group.teamcity_alb_sg.id
+  from_port                    = var.container_port
+  to_port                      = var.container_port
+  ip_protocol                  = "TCP"
+
+}
+
+# Egress rule for HTTP traffic from ALB to service
+resource "aws_vpc_security_group_egress_rule" "service_outbound_alb" {
+  security_group_id            = aws_security_group.teamcity_alb_sg.id
+  referenced_security_group_id = aws_security_group.teamcity_service_sg.id
+  from_port                    = var.container_port
+  to_port                      = var.container_port
+  ip_protocol                  = "TCP"
+
+}
+
+# Grant TeamCity service access to internet
+resource "aws_vpc_security_group_egress_rule" "internet_outbound" {
+
+  security_group_id = aws_security_group.teamcity_service_sg.id
+  cidr_ipv4         = "0.0.0.0/0"
+  ip_protocol       = "-1"
+}
+
+#############################################
+# IAM Roles for TeamCity Module
+#############################################
+data "aws_iam_policy_document" "ecs_tasks_trust_relationship" {
+  statement {
+    effect  = "Allow"
+    actions = ["sts:AssumeRole"]
+    principals {
+      type        = "Service"
+      identifiers = ["ecs-tasks.amazonaws.com"]
+    }
+  }
+}
+data "aws_iam_policy_document" "teamcity_default_policy" {
+  # ECS
+  statement {
+    sid    = "ECSExec"
+    effect = "Allow"
+    actions = [
+      "ssmmessages:OpenDataChannel",
+      "ssmmessages:OpenControlChannel",
+      "ssmmessages:CreateDataChannel",
+      "ssmmessages:CreateControlChannel",
+    ]
+    resources = [
+      "*"
+    ]
+  }
+}
+resource "aws_iam_policy" "teamcity_default_policy" {
+  name        = "teamcity-default-policy"
+  description = "Policy granting permissions for Unreal Horde."
+  policy      = data.aws_iam_policy_document.teamcity_default_policy.json
+}
+
+resource "aws_iam_role" "teamcity_default_role" {
+  name               = "teamcity-default-role"
+  assume_role_policy = data.aws_iam_policy_document.ecs_tasks_trust_relationship.json
+
+  managed_policy_arns = [
+    aws_iam_policy.teamcity_default_policy.arn
+  ]
+
+  tags = local.tags
+}
+resource "aws_iam_role" "teamcity_task_execution_role" {
+  name = "teamcity-task-execution-role"
+
+  assume_role_policy  = data.aws_iam_policy_document.ecs_tasks_trust_relationship.json
+  managed_policy_arns = ["arn:aws:iam::aws:policy/service-role/AmazonECSTaskExecutionRolePolicy"]
+}
+
+# CloudWatch Logs
+resource "aws_cloudwatch_log_group" "teamcity_log_group" {
+  #checkov:skip=CKV_AWS_158: KMS Encryption disabled by default
+  name              = "${local.name_prefix}-log-group"
+  retention_in_days = var.teamcity_cloudwatch_log_retention_in_days
+  tags              = local.tags
+}
+
+
+# Load Balancer for TeamCity Service
+resource "aws_lb" "teamcity_external_lb" {
+  name            = "${local.name_prefix}-lb"
+  security_groups = [aws_security_group.teamcity_alb_sg.id]
+
+  load_balancer_type         = "application"
+  internal                   = false
+  subnets                    = var.alb_subnets
+  drop_invalid_header_fields = true
+  tags                       = local.tags
+}
+
+# TeamCity target group for ALB
+resource "aws_lb_target_group" "teamcity_target_group" {
+  name        = "${local.name_prefix}-tg"
+  port        = var.container_port
+  protocol    = "HTTP"
+  vpc_id      = var.vpc_id
+  target_type = "ip"
+
+  # health_check {
+  #   path                = "/"
+  #   interval            = 30
+  #   timeout             = 5
+  #   healthy_threshold   = 2
+  #   unhealthy_threshold = 2
+  # }
path: modules/teamcity/main.tf
body: @cwwalb where did we land on this? Feel free to resolve this conversation.
created_at: 2025-02-12T15:28:31Z
author_association: NONE
diff_hunk: @@ -0,0 +1,413 @@
+###################################
+# ECS Cluster for TeamCity Server #
+###################################
+
+resource "aws_ecs_cluster" "teamcity_cluster" {
+  name = "${local.name_prefix}-cluster"
+
+  setting {
+    name  = "containerInsights"
+    value = "enabled"
+  }
+
+  tags = local.tags
+}
+
+
+# TeamCity Task Definition
+resource "aws_ecs_task_definition" "teamcity_task_definition" {
+  family                   = var.name
+  requires_compatibilities = ["FARGATE"]
+  network_mode             = "awsvpc"
+  cpu                      = var.container_cpu
+  memory                   = var.container_memory
+
+
+  container_definitions = jsonencode([
+    {
+      name      = var.container_name
+      image     = local.image
+      cpu       = var.container_cpu
+      memory    = var.container_memory
+      essential = true
+      portMappings = [
+        {
+          containerPort = var.container_port
+          hostPort      = var.container_port
+        }
+      ]
+      logConfiguration = {
+        logDriver = "awslogs"
+        options = {
+          awslogs-group         = aws_cloudwatch_log_group.teamcity_log_group.name
+          awslogs-region        = data.aws_region.current.name
+          awslogs-stream-prefix = "[APP]"
+        }
+      },
+      mountPoints = [
+        {
+          sourceVolume  = "teamcity_data",
+          containerPath = "/data/teamcity_server/datadir"
+          readOnly      = false
+        },
+        {
+          sourceVolume  = "teamcity_logs",
+          containerPath = "/data/teamcity_server/logs"
+          readOnly      = false
+        }
+      ]
+    }
+  ])
+
+  tags = {
+    Name = var.name
+  }
+  task_role_arn      = aws_iam_role.teamcity_default_role.arn
+  execution_role_arn = aws_iam_role.teamcity_task_execution_role.arn
+
+  volume {
+    name = "teamcity_data"
+    efs_volume_configuration {
+      file_system_id          = aws_efs_file_system.teamcity_efs_file_system.id
+      transit_encryption      = "ENABLED"
+      transit_encryption_port = 2999
+      authorization_config {
+        access_point_id = aws_efs_access_point.teamcity_efs_data_access_point.id
+        iam             = "ENABLED"
+      }
+    }
+  }
+
+  volume {
+    name = "teamcity_logs"
+    efs_volume_configuration {
+      file_system_id     = aws_efs_file_system.teamcity_efs_file_system.id
+      transit_encryption = "ENABLED"
+      authorization_config {
+        access_point_id = aws_efs_access_point.teamcity_efs_logs_access_point.id
+        iam             = "ENABLED"
+      }
+    }
+  }
+}
+
+
+
+
+# ECS Service
+resource "aws_ecs_service" "teamcity" {
+  name = local.name_prefix
+
+  cluster                = aws_ecs_cluster.teamcity_cluster.name
+  task_definition        = aws_ecs_task_definition.teamcity_task_definition.arn
+  launch_type            = "FARGATE"
+  desired_count          = 1    #TODO: make this configurable
+  force_new_deployment   = true #TODO: make this configurable
+  enable_execute_command = true #TODO: make this configurable
+
+  wait_for_steady_state = true
+
+  network_configuration {
+    subnets         = var.service_subnets
+    security_groups = [aws_security_group.teamcity_service_sg.id]
+  }
+  load_balancer {
+    target_group_arn = aws_lb_target_group.teamcity_target_group.arn
+    container_name   = var.container_name
+    container_port   = var.container_port
+  }
+
+  tags = local.tags
+}
+
+# TeamCity service security group
+resource "aws_security_group" "teamcity_service_sg" {
+  name   = "${local.name_prefix}-service-sg"
+  vpc_id = var.vpc_id
+
+  tags = local.tags
+}
+
+# TeamCity EFS security group
+resource "aws_security_group" "teamcity_efs_sg" {
+  name        = "${local.name_prefix}-efs-sg"
+  description = "TeamCity EFS mount target security group"
+  vpc_id      = var.vpc_id
+
+  tags = local.tags
+}
+
+# Ingress rule for NFS traffic from service to EFS
+resource "aws_vpc_security_group_ingress_rule" "service_efs" {
+  security_group_id            = aws_security_group.teamcity_efs_sg.id
+  referenced_security_group_id = aws_security_group.teamcity_service_sg.id
+  description                  = "Allow inbound access from TeamCity service containers to EFS"
+  ip_protocol                  = "TCP"
+  from_port                    = 2049
+  to_port                      = 2049
+}
+
+# Ingress rule for NFS traffic from EFS to service
+resource "aws_vpc_security_group_ingress_rule" "efs_service" {
+  security_group_id            = aws_security_group.teamcity_service_sg.id
+  referenced_security_group_id = aws_security_group.teamcity_efs_sg.id
+  description                  = "Allow inbound access from EFS to TeamCity service containers"
+  ip_protocol                  = "TCP"
+  from_port                    = 2049
+  to_port                      = 2049
+}
+
+
+
+
+# TeamCity ALB security group
+resource "aws_security_group" "teamcity_alb_sg" {
+  name   = "${local.name_prefix}-alb-sg"
+  vpc_id = var.vpc_id
+
+  tags = local.tags
+}
+
+# Ingress rule for HTTP traffic from ALB to service
+resource "aws_vpc_security_group_ingress_rule" "service_inbound_alb" {
+  security_group_id            = aws_security_group.teamcity_service_sg.id
+  referenced_security_group_id = aws_security_group.teamcity_alb_sg.id
+  from_port                    = var.container_port
+  to_port                      = var.container_port
+  ip_protocol                  = "TCP"
+
+}
+
+# Egress rule for HTTP traffic from ALB to service
+resource "aws_vpc_security_group_egress_rule" "service_outbound_alb" {
+  security_group_id            = aws_security_group.teamcity_alb_sg.id
+  referenced_security_group_id = aws_security_group.teamcity_service_sg.id
+  from_port                    = var.container_port
+  to_port                      = var.container_port
+  ip_protocol                  = "TCP"
+
+}
+
+# Grant TeamCity service access to internet
+resource "aws_vpc_security_group_egress_rule" "internet_outbound" {
+
+  security_group_id = aws_security_group.teamcity_service_sg.id
+  cidr_ipv4         = "0.0.0.0/0"
+  ip_protocol       = "-1"
+}
+
+#############################################
+# IAM Roles for TeamCity Module
+#############################################
+data "aws_iam_policy_document" "ecs_tasks_trust_relationship" {
+  statement {
+    effect  = "Allow"
+    actions = ["sts:AssumeRole"]
+    principals {
+      type        = "Service"
+      identifiers = ["ecs-tasks.amazonaws.com"]
+    }
+  }
+}
+data "aws_iam_policy_document" "teamcity_default_policy" {
+  # ECS
+  statement {
+    sid    = "ECSExec"
+    effect = "Allow"
+    actions = [
+      "ssmmessages:OpenDataChannel",
+      "ssmmessages:OpenControlChannel",
+      "ssmmessages:CreateDataChannel",
+      "ssmmessages:CreateControlChannel",
+    ]
+    resources = [
+      "*"
+    ]
+  }
+}
+resource "aws_iam_policy" "teamcity_default_policy" {
+  name        = "teamcity-default-policy"
+  description = "Policy granting permissions for Unreal Horde."
+  policy      = data.aws_iam_policy_document.teamcity_default_policy.json
+}
+
+resource "aws_iam_role" "teamcity_default_role" {
+  name               = "teamcity-default-role"
+  assume_role_policy = data.aws_iam_policy_document.ecs_tasks_trust_relationship.json
+
+  managed_policy_arns = [
+    aws_iam_policy.teamcity_default_policy.arn
+  ]
+
+  tags = local.tags
+}
+resource "aws_iam_role" "teamcity_task_execution_role" {
+  name = "teamcity-task-execution-role"
+
+  assume_role_policy  = data.aws_iam_policy_document.ecs_tasks_trust_relationship.json
+  managed_policy_arns = ["arn:aws:iam::aws:policy/service-role/AmazonECSTaskExecutionRolePolicy"]
+}
+
+# CloudWatch Logs
+resource "aws_cloudwatch_log_group" "teamcity_log_group" {
+  #checkov:skip=CKV_AWS_158: KMS Encryption disabled by default
+  name              = "${local.name_prefix}-log-group"
+  retention_in_days = var.teamcity_cloudwatch_log_retention_in_days
+  tags              = local.tags
+}
+
+
+# Load Balancer for TeamCity Service
+resource "aws_lb" "teamcity_external_lb" {
+  name            = "${local.name_prefix}-lb"
+  security_groups = [aws_security_group.teamcity_alb_sg.id]
+
+  load_balancer_type         = "application"
+  internal                   = false
+  subnets                    = var.alb_subnets
+  drop_invalid_header_fields = true
+  tags                       = local.tags
+}
+
+# TeamCity target group for ALB
+resource "aws_lb_target_group" "teamcity_target_group" {
+  name        = "${local.name_prefix}-tg"
+  port        = var.container_port
+  protocol    = "HTTP"
+  vpc_id      = var.vpc_id
+  target_type = "ip"
+
+  # health_check {
+  #   path                = "/"
+  #   interval            = 30
+  #   timeout             = 5
+  #   healthy_threshold   = 2
+  #   unhealthy_threshold = 2
+  # }
path: modules/teamcity/main.tf
user: login: cwwalb
body: that was fixed
created_at: 2025-02-12T17:23:27Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,467 @@
+###################################
+# ECS Cluster for TeamCity Server #
+###################################
+
+resource "aws_ecs_cluster" "teamcity_cluster" {
+  name = "${local.name_prefix}-cluster"
+
+  setting {
+    name  = "containerInsights"
+    value = "enabled"
+  }
+
+  tags = local.tags
+}
+
+# TeamCity Task Definition
+resource "aws_ecs_task_definition" "teamcity_task_definition" {
+  family                   = var.name
+  requires_compatibilities = ["FARGATE"]
+  network_mode             = "awsvpc"
+  cpu                      = var.container_cpu
+  memory                   = var.container_memory
+
+  container_definitions = jsonencode([
+    {
+      name      = var.container_name
+      image     = local.image
+      cpu       = var.container_cpu
+      memory    = var.container_memory
+      essential = true
+      portMappings = [
+        {
+          containerPort = var.container_port
+          hostPort      = var.container_port
+        }
+      ]
+      logConfiguration = {
+        logDriver = "awslogs"
+        options = {
+          awslogs-group         = aws_cloudwatch_log_group.teamcity_log_group.name
+          awslogs-region        = data.aws_region.current.name
+          awslogs-stream-prefix = "[APP]"
+        }
+      }
+      mountPoints = [
+        {
+          sourceVolume  = "teamcity_data",
+          containerPath = "/data/teamcity_server/datadir"
+          readOnly      = false
+        },
+        {
+          sourceVolume  = "teamcity_logs",
+          containerPath = "/data/teamcity_server/logs"
+          readOnly      = false
+        }
+      ]
+      # Set environment variables for database connection string
+      environment = [
+        {
+          name  = "TEAMCITY_DB_URL"
+          value = local.database_connection_string
+        },
+        {
+          name  = "TEAMCITY_DB_USER"
+          value = local.database_user
+        },
+        {
+          name  = "TEAMCITY_DB_PASSWORD"
+          value = local.database_password
+        },
+        {
+          name  = "TEAMCITY_LOGS_PATH"
+          value = "/data/teamcity_server/logs"
+        }
+      ]
+    }
+  ])
+  tags = {
+    Name = var.name
+  }
+  task_role_arn      = aws_iam_role.teamcity_default_role.arn
+  execution_role_arn = aws_iam_role.teamcity_task_execution_role.arn
+
+  volume {
+    name = "teamcity_data"
+    efs_volume_configuration {
+      file_system_id          = aws_efs_file_system.teamcity_efs_file_system.id
+      transit_encryption      = "ENABLED"
+      transit_encryption_port = 2999
+      authorization_config {
+        access_point_id = aws_efs_access_point.teamcity_efs_data_access_point.id
+        iam             = "ENABLED"
+      }
+    }
+  }
+
+  volume {
+    name = "teamcity_logs"
+    efs_volume_configuration {
+      file_system_id          = aws_efs_file_system.teamcity_efs_file_system.id
+      transit_encryption      = "ENABLED"
+      transit_encryption_port = 2998
+      authorization_config {
+        access_point_id = aws_efs_access_point.teamcity_efs_logs_access_point.id
+        iam             = "ENABLED"
+      }
+    }
+  }
+}
+
+# ECS Service
+resource "aws_ecs_service" "teamcity" {
+  name                   = local.name_prefix
+  cluster                = aws_ecs_cluster.teamcity_cluster.name
+  task_definition        = aws_ecs_task_definition.teamcity_task_definition.arn
+  launch_type            = "FARGATE"
+  desired_count          = 1    #TODO: make this configurable
+  force_new_deployment   = true #TODO: make this configurable
+  enable_execute_command = true #TODO: make this configurable
+  wait_for_steady_state  = true
+
+  network_configuration {
+    subnets         = var.service_subnets
+    security_groups = [aws_security_group.teamcity_service_sg.id]
+  }
+  load_balancer {
+    target_group_arn = aws_lb_target_group.teamcity_target_group.arn
+    container_name   = var.container_name
+    container_port   = var.container_port
+  }
+
+  tags = local.tags
+}
+
+# TeamCity service security group
+resource "aws_security_group" "teamcity_service_sg" {
+  name        = "${local.name_prefix}-service-sg"
+  vpc_id      = var.vpc_id
+  description = "TeamCity service security group"
+  tags        = local.tags
+}
+
+# TeamCity EFS security group
+resource "aws_security_group" "teamcity_efs_sg" {
+  name        = "${local.name_prefix}-efs-sg"
+  description = "TeamCity EFS mount target security group"
+  vpc_id      = var.vpc_id
+  tags        = local.tags
+}
+
+# Ingress rule for NFS traffic from service to EFS
+resource "aws_vpc_security_group_ingress_rule" "service_efs" {
+  security_group_id            = aws_security_group.teamcity_efs_sg.id
+  referenced_security_group_id = aws_security_group.teamcity_service_sg.id
+  description                  = "Allow inbound access from TeamCity service containers to EFS"
+  ip_protocol                  = "TCP"
+  from_port                    = 2049
+  to_port                      = 2049
+}
+
+# Ingress rule for NFS traffic from EFS to service
+resource "aws_vpc_security_group_ingress_rule" "efs_service" {
+  security_group_id            = aws_security_group.teamcity_service_sg.id
+  referenced_security_group_id = aws_security_group.teamcity_efs_sg.id
+  description                  = "Allow inbound access from EFS to TeamCity service containers"
+  ip_protocol                  = "TCP"
+  from_port                    = 2049
+  to_port                      = 2049
+}
+
+# TeamCity Aurora Serverless PostgreSQL security group
+resource "aws_security_group" "teamcity_db_sg" {
+  name        = "${local.name_prefix}-db-sg"
+  description = "TeamCity DB security group"
+  vpc_id      = var.vpc_id
+  tags        = local.tags
+}
+
+# Ingress rule for PostgreSQL from service to database cluster
+resource "aws_vpc_security_group_ingress_rule" "service_db" {
+  security_group_id            = aws_security_group.teamcity_db_sg.id
+  referenced_security_group_id = aws_security_group.teamcity_service_sg.id
+  description                  = "Allow inbound access from TeamCity service containers to DB"
+  ip_protocol                  = "TCP"
+  from_port                    = 5432
+  to_port                      = 5432
+}
+
+# Ingress rule for PostgreSQL from database cluster to service
+resource "aws_vpc_security_group_ingress_rule" "db_service" {
+  security_group_id            = aws_security_group.teamcity_service_sg.id
+  referenced_security_group_id = aws_security_group.teamcity_db_sg.id
+  description                  = "Allow inbound access from DB to TeamCity service containers"
+  ip_protocol                  = "TCP"
+  from_port                    = 5432
+  to_port                      = 5432
+}
+
+# Egress rule for database to let all traffic out
+resource "aws_vpc_security_group_egress_rule" "db_outbound" {
+  security_group_id = aws_security_group.teamcity_db_sg.id
+  description       = "Allow outbound access from DB to all"
+  cidr_ipv4         = "0.0.0.0/0"
+  ip_protocol       = "-1"
+}
+
+# TeamCity ALB security group
+resource "aws_security_group" "teamcity_alb_sg" {
+  name        = "${local.name_prefix}-alb-sg"
+  vpc_id      = var.vpc_id
+  description = "TeamCity ALB security group"
+  tags        = local.tags
+}
+
+# Ingress rule for HTTP traffic from ALB to service
+resource "aws_vpc_security_group_ingress_rule" "service_inbound_alb" {
+  security_group_id            = aws_security_group.teamcity_service_sg.id
+  referenced_security_group_id = aws_security_group.teamcity_alb_sg.id
+  description                  = "Allow inbound HTTP traffic from ALB to service containers"
+  from_port                    = var.container_port
+  to_port                      = var.container_port
+  ip_protocol                  = "TCP"
+}
+
+# Egress rule for HTTP traffic from ALB to service
+resource "aws_vpc_security_group_egress_rule" "service_outbound_alb" {
+  security_group_id            = aws_security_group.teamcity_alb_sg.id
+  referenced_security_group_id = aws_security_group.teamcity_service_sg.id
+  description                  = "Allow outbound HTTP traffic from ALB to service containers"
+  from_port                    = var.container_port
+  to_port                      = var.container_port
+  ip_protocol                  = "TCP"
+}
+
+# Grant TeamCity service access to internet
+resource "aws_vpc_security_group_egress_rule" "internet_outbound" {
+  security_group_id = aws_security_group.teamcity_service_sg.id
+  description       = "Allow outbound internet access from TeamCity service containers"
+  cidr_ipv4         = "0.0.0.0/0"
+  ip_protocol       = "-1"
+}
+
+#############################################
+# IAM Roles for TeamCity Module
+#############################################
+
+data "aws_iam_policy_document" "ecs_tasks_trust_relationship" {
+  statement {
+    effect  = "Allow"
+    actions = ["sts:AssumeRole"]
+    principals {
+      type        = "Service"
+      identifiers = ["ecs-tasks.amazonaws.com"]
+    }
+  }
+}
+
+data "aws_iam_policy_document" "teamcity_default_policy" {
+  # ECS
+  statement {
+    sid    = "ECSExec"
+    effect = "Allow"
+    actions = [
+      "ssmmessages:OpenDataChannel",
+      "ssmmessages:OpenControlChannel",
+      "ssmmessages:CreateDataChannel",
+      "ssmmessages:CreateControlChannel",
+    ]
+    resources = [
+      "*"
+    ]
+  }
+}
path: modules/teamcity/main.tf
body: @cwwalb Any further testing on this? Should we just save this for the access point issue you created?
created_at: 2025-02-12T17:47:16Z
author_association: NONE
diff_hunk: @@ -0,0 +1,11 @@
+locals {
+  name_prefix = "teamcity"
+  tags = merge(var.tags, {
+    "environment" = var.environment
+  })
+  image                      = "jetbrains/teamcity-server"
+  database_connection_string = "jdbc:postgresql://teamcity-cluster.cluster-cwmktevrnnqg.us-east-1.rds.amazonaws.com:5432/teamcity"
+  database_user              = "teamcity"
+  database_password          = "teamcity2025"
path: modules/teamcity/local.tf
body: @cwwalb any update on this?
created_at: 2025-02-12T17:47:40Z
author_association: NONE
diff_hunk: @@ -0,0 +1,467 @@
+###################################
+# ECS Cluster for TeamCity Server #
+###################################
+
+resource "aws_ecs_cluster" "teamcity_cluster" {
+  name = "${local.name_prefix}-cluster"
+
+  setting {
+    name  = "containerInsights"
+    value = "enabled"
+  }
+
+  tags = local.tags
+}
+
+# TeamCity Task Definition
+resource "aws_ecs_task_definition" "teamcity_task_definition" {
+  family                   = var.name
+  requires_compatibilities = ["FARGATE"]
+  network_mode             = "awsvpc"
+  cpu                      = var.container_cpu
+  memory                   = var.container_memory
+
+  container_definitions = jsonencode([
+    {
+      name      = var.container_name
+      image     = local.image
+      cpu       = var.container_cpu
+      memory    = var.container_memory
+      essential = true
+      portMappings = [
+        {
+          containerPort = var.container_port
+          hostPort      = var.container_port
+        }
+      ]
+      logConfiguration = {
+        logDriver = "awslogs"
+        options = {
+          awslogs-group         = aws_cloudwatch_log_group.teamcity_log_group.name
+          awslogs-region        = data.aws_region.current.name
+          awslogs-stream-prefix = "[APP]"
+        }
+      }
+      mountPoints = [
+        {
+          sourceVolume  = "teamcity_data",
+          containerPath = "/data/teamcity_server/datadir"
+          readOnly      = false
+        },
+        {
+          sourceVolume  = "teamcity_logs",
+          containerPath = "/data/teamcity_server/logs"
+          readOnly      = false
+        }
+      ]
+      # Set environment variables for database connection string
+      environment = [
+        {
+          name  = "TEAMCITY_DB_URL"
+          value = local.database_connection_string
+        },
+        {
+          name  = "TEAMCITY_DB_USER"
+          value = local.database_user
+        },
+        {
+          name  = "TEAMCITY_DB_PASSWORD"
+          value = local.database_password
+        },
+        {
+          name  = "TEAMCITY_LOGS_PATH"
+          value = "/data/teamcity_server/logs"
+        }
+      ]
+    }
+  ])
+  tags = {
+    Name = var.name
+  }
+  task_role_arn      = aws_iam_role.teamcity_default_role.arn
+  execution_role_arn = aws_iam_role.teamcity_task_execution_role.arn
+
+  volume {
+    name = "teamcity_data"
+    efs_volume_configuration {
+      file_system_id          = aws_efs_file_system.teamcity_efs_file_system.id
+      transit_encryption      = "ENABLED"
+      transit_encryption_port = 2999
+      authorization_config {
+        access_point_id = aws_efs_access_point.teamcity_efs_data_access_point.id
+        iam             = "ENABLED"
+      }
+    }
+  }
+
+  volume {
+    name = "teamcity_logs"
+    efs_volume_configuration {
+      file_system_id          = aws_efs_file_system.teamcity_efs_file_system.id
+      transit_encryption      = "ENABLED"
+      transit_encryption_port = 2998
+      authorization_config {
+        access_point_id = aws_efs_access_point.teamcity_efs_logs_access_point.id
+        iam             = "ENABLED"
+      }
+    }
+  }
+}
+
+# ECS Service
+resource "aws_ecs_service" "teamcity" {
+  name                   = local.name_prefix
+  cluster                = aws_ecs_cluster.teamcity_cluster.name
+  task_definition        = aws_ecs_task_definition.teamcity_task_definition.arn
+  launch_type            = "FARGATE"
+  desired_count          = 1    #TODO: make this configurable
+  force_new_deployment   = true #TODO: make this configurable
+  enable_execute_command = true #TODO: make this configurable
path: modules/teamcity/main.tf
body: @cwwalb any update on this?
created_at: 2025-02-12T17:47:49Z
author_association: NONE
diff_hunk: @@ -0,0 +1,11 @@
+locals {
+  name_prefix = "teamcity"
+  tags = merge(var.tags, {
+    "environment" = var.environment
+  })
+  image                      = "jetbrains/teamcity-server"
+  database_connection_string = "jdbc:postgresql://teamcity-cluster.cluster-cwmktevrnnqg.us-east-1.rds.amazonaws.com:5432/teamcity"
+  database_user              = "teamcity"
+  database_password          = "teamcity2025"
path: modules/teamcity/local.tf
user: login: cwwalb
body: Looking into this today

created_at: 2025-02-12T18:04:53Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,467 @@
+###################################
+# ECS Cluster for TeamCity Server #
+###################################
+
+resource "aws_ecs_cluster" "teamcity_cluster" {
+  name = "${local.name_prefix}-cluster"
+
+  setting {
+    name  = "containerInsights"
+    value = "enabled"
+  }
+
+  tags = local.tags
+}
+
+# TeamCity Task Definition
+resource "aws_ecs_task_definition" "teamcity_task_definition" {
+  family                   = var.name
+  requires_compatibilities = ["FARGATE"]
+  network_mode             = "awsvpc"
+  cpu                      = var.container_cpu
+  memory                   = var.container_memory
+
+  container_definitions = jsonencode([
+    {
+      name      = var.container_name
+      image     = local.image
+      cpu       = var.container_cpu
+      memory    = var.container_memory
+      essential = true
+      portMappings = [
+        {
+          containerPort = var.container_port
+          hostPort      = var.container_port
+        }
+      ]
+      logConfiguration = {
+        logDriver = "awslogs"
+        options = {
+          awslogs-group         = aws_cloudwatch_log_group.teamcity_log_group.name
+          awslogs-region        = data.aws_region.current.name
+          awslogs-stream-prefix = "[APP]"
+        }
+      }
+      mountPoints = [
+        {
+          sourceVolume  = "teamcity_data",
+          containerPath = "/data/teamcity_server/datadir"
+          readOnly      = false
+        },
+        {
+          sourceVolume  = "teamcity_logs",
+          containerPath = "/data/teamcity_server/logs"
+          readOnly      = false
+        }
+      ]
+      # Set environment variables for database connection string
+      environment = [
+        {
+          name  = "TEAMCITY_DB_URL"
+          value = local.database_connection_string
+        },
+        {
+          name  = "TEAMCITY_DB_USER"
+          value = local.database_user
+        },
+        {
+          name  = "TEAMCITY_DB_PASSWORD"
+          value = local.database_password
+        },
+        {
+          name  = "TEAMCITY_LOGS_PATH"
+          value = "/data/teamcity_server/logs"
+        }
+      ]
+    }
+  ])
+  tags = {
+    Name = var.name
+  }
+  task_role_arn      = aws_iam_role.teamcity_default_role.arn
+  execution_role_arn = aws_iam_role.teamcity_task_execution_role.arn
+
+  volume {
+    name = "teamcity_data"
+    efs_volume_configuration {
+      file_system_id          = aws_efs_file_system.teamcity_efs_file_system.id
+      transit_encryption      = "ENABLED"
+      transit_encryption_port = 2999
+      authorization_config {
+        access_point_id = aws_efs_access_point.teamcity_efs_data_access_point.id
+        iam             = "ENABLED"
+      }
+    }
+  }
+
+  volume {
+    name = "teamcity_logs"
+    efs_volume_configuration {
+      file_system_id          = aws_efs_file_system.teamcity_efs_file_system.id
+      transit_encryption      = "ENABLED"
+      transit_encryption_port = 2998
+      authorization_config {
+        access_point_id = aws_efs_access_point.teamcity_efs_logs_access_point.id
+        iam             = "ENABLED"
+      }
+    }
+  }
+}
+
+# ECS Service
+resource "aws_ecs_service" "teamcity" {
+  name                   = local.name_prefix
+  cluster                = aws_ecs_cluster.teamcity_cluster.name
+  task_definition        = aws_ecs_task_definition.teamcity_task_definition.arn
+  launch_type            = "FARGATE"
+  desired_count          = 1    #TODO: make this configurable
+  force_new_deployment   = true #TODO: make this configurable
+  enable_execute_command = true #TODO: make this configurable
+  wait_for_steady_state  = true
+
+  network_configuration {
+    subnets         = var.service_subnets
+    security_groups = [aws_security_group.teamcity_service_sg.id]
+  }
+  load_balancer {
+    target_group_arn = aws_lb_target_group.teamcity_target_group.arn
+    container_name   = var.container_name
+    container_port   = var.container_port
+  }
+
+  tags = local.tags
+}
+
+# TeamCity service security group
+resource "aws_security_group" "teamcity_service_sg" {
+  name        = "${local.name_prefix}-service-sg"
+  vpc_id      = var.vpc_id
+  description = "TeamCity service security group"
+  tags        = local.tags
+}
+
+# TeamCity EFS security group
+resource "aws_security_group" "teamcity_efs_sg" {
+  name        = "${local.name_prefix}-efs-sg"
+  description = "TeamCity EFS mount target security group"
+  vpc_id      = var.vpc_id
+  tags        = local.tags
+}
+
+# Ingress rule for NFS traffic from service to EFS
+resource "aws_vpc_security_group_ingress_rule" "service_efs" {
+  security_group_id            = aws_security_group.teamcity_efs_sg.id
+  referenced_security_group_id = aws_security_group.teamcity_service_sg.id
+  description                  = "Allow inbound access from TeamCity service containers to EFS"
+  ip_protocol                  = "TCP"
+  from_port                    = 2049
+  to_port                      = 2049
+}
+
+# Ingress rule for NFS traffic from EFS to service
+resource "aws_vpc_security_group_ingress_rule" "efs_service" {
+  security_group_id            = aws_security_group.teamcity_service_sg.id
+  referenced_security_group_id = aws_security_group.teamcity_efs_sg.id
+  description                  = "Allow inbound access from EFS to TeamCity service containers"
+  ip_protocol                  = "TCP"
+  from_port                    = 2049
+  to_port                      = 2049
+}
+
+# TeamCity Aurora Serverless PostgreSQL security group
+resource "aws_security_group" "teamcity_db_sg" {
+  name        = "${local.name_prefix}-db-sg"
+  description = "TeamCity DB security group"
+  vpc_id      = var.vpc_id
+  tags        = local.tags
+}
+
+# Ingress rule for PostgreSQL from service to database cluster
+resource "aws_vpc_security_group_ingress_rule" "service_db" {
+  security_group_id            = aws_security_group.teamcity_db_sg.id
+  referenced_security_group_id = aws_security_group.teamcity_service_sg.id
+  description                  = "Allow inbound access from TeamCity service containers to DB"
+  ip_protocol                  = "TCP"
+  from_port                    = 5432
+  to_port                      = 5432
+}
+
+# Ingress rule for PostgreSQL from database cluster to service
+resource "aws_vpc_security_group_ingress_rule" "db_service" {
+  security_group_id            = aws_security_group.teamcity_service_sg.id
+  referenced_security_group_id = aws_security_group.teamcity_db_sg.id
+  description                  = "Allow inbound access from DB to TeamCity service containers"
+  ip_protocol                  = "TCP"
+  from_port                    = 5432
+  to_port                      = 5432
+}
+
+# Egress rule for database to let all traffic out
+resource "aws_vpc_security_group_egress_rule" "db_outbound" {
+  security_group_id = aws_security_group.teamcity_db_sg.id
+  description       = "Allow outbound access from DB to all"
+  cidr_ipv4         = "0.0.0.0/0"
+  ip_protocol       = "-1"
+}
+
+# TeamCity ALB security group
+resource "aws_security_group" "teamcity_alb_sg" {
+  name        = "${local.name_prefix}-alb-sg"
+  vpc_id      = var.vpc_id
+  description = "TeamCity ALB security group"
+  tags        = local.tags
+}
+
+# Ingress rule for HTTP traffic from ALB to service
+resource "aws_vpc_security_group_ingress_rule" "service_inbound_alb" {
+  security_group_id            = aws_security_group.teamcity_service_sg.id
+  referenced_security_group_id = aws_security_group.teamcity_alb_sg.id
+  description                  = "Allow inbound HTTP traffic from ALB to service containers"
+  from_port                    = var.container_port
+  to_port                      = var.container_port
+  ip_protocol                  = "TCP"
+}
+
+# Egress rule for HTTP traffic from ALB to service
+resource "aws_vpc_security_group_egress_rule" "service_outbound_alb" {
+  security_group_id            = aws_security_group.teamcity_alb_sg.id
+  referenced_security_group_id = aws_security_group.teamcity_service_sg.id
+  description                  = "Allow outbound HTTP traffic from ALB to service containers"
+  from_port                    = var.container_port
+  to_port                      = var.container_port
+  ip_protocol                  = "TCP"
+}
+
+# Grant TeamCity service access to internet
+resource "aws_vpc_security_group_egress_rule" "internet_outbound" {
+  security_group_id = aws_security_group.teamcity_service_sg.id
+  description       = "Allow outbound internet access from TeamCity service containers"
+  cidr_ipv4         = "0.0.0.0/0"
+  ip_protocol       = "-1"
+}
+
+#############################################
+# IAM Roles for TeamCity Module
+#############################################
+
+data "aws_iam_policy_document" "ecs_tasks_trust_relationship" {
+  statement {
+    effect  = "Allow"
+    actions = ["sts:AssumeRole"]
+    principals {
+      type        = "Service"
+      identifiers = ["ecs-tasks.amazonaws.com"]
+    }
+  }
+}
+
+data "aws_iam_policy_document" "teamcity_default_policy" {
+  # ECS
+  statement {
+    sid    = "ECSExec"
+    effect = "Allow"
+    actions = [
+      "ssmmessages:OpenDataChannel",
+      "ssmmessages:OpenControlChannel",
+      "ssmmessages:CreateDataChannel",
+      "ssmmessages:CreateControlChannel",
+    ]
+    resources = [
+      "*"
+    ]
+  }
+}
path: modules/teamcity/main.tf
user: login: cwwalb
body: I don't think its iam because I tried elasticfilesystem:* on the file system and same error
created_at: 2025-02-12T19:20:05Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,467 @@
+###################################
+# ECS Cluster for TeamCity Server #
+###################################
+
+resource "aws_ecs_cluster" "teamcity_cluster" {
+  name = "${local.name_prefix}-cluster"
+
+  setting {
+    name  = "containerInsights"
+    value = "enabled"
+  }
+
+  tags = local.tags
+}
+
+# TeamCity Task Definition
+resource "aws_ecs_task_definition" "teamcity_task_definition" {
+  family                   = var.name
+  requires_compatibilities = ["FARGATE"]
+  network_mode             = "awsvpc"
+  cpu                      = var.container_cpu
+  memory                   = var.container_memory
+
+  container_definitions = jsonencode([
+    {
+      name      = var.container_name
+      image     = local.image
+      cpu       = var.container_cpu
+      memory    = var.container_memory
+      essential = true
+      portMappings = [
+        {
+          containerPort = var.container_port
+          hostPort      = var.container_port
+        }
+      ]
+      logConfiguration = {
+        logDriver = "awslogs"
+        options = {
+          awslogs-group         = aws_cloudwatch_log_group.teamcity_log_group.name
+          awslogs-region        = data.aws_region.current.name
+          awslogs-stream-prefix = "[APP]"
+        }
+      }
+      mountPoints = [
+        {
+          sourceVolume  = "teamcity_data",
+          containerPath = "/data/teamcity_server/datadir"
+          readOnly      = false
+        },
+        {
+          sourceVolume  = "teamcity_logs",
+          containerPath = "/data/teamcity_server/logs"
+          readOnly      = false
+        }
+      ]
+      # Set environment variables for database connection string
+      environment = [
+        {
+          name  = "TEAMCITY_DB_URL"
+          value = local.database_connection_string
+        },
+        {
+          name  = "TEAMCITY_DB_USER"
+          value = local.database_user
+        },
+        {
+          name  = "TEAMCITY_DB_PASSWORD"
+          value = local.database_password
+        },
+        {
+          name  = "TEAMCITY_LOGS_PATH"
+          value = "/data/teamcity_server/logs"
+        }
+      ]
+    }
+  ])
+  tags = {
+    Name = var.name
+  }
+  task_role_arn      = aws_iam_role.teamcity_default_role.arn
+  execution_role_arn = aws_iam_role.teamcity_task_execution_role.arn
+
+  volume {
+    name = "teamcity_data"
+    efs_volume_configuration {
+      file_system_id          = aws_efs_file_system.teamcity_efs_file_system.id
+      transit_encryption      = "ENABLED"
+      transit_encryption_port = 2999
+      authorization_config {
+        access_point_id = aws_efs_access_point.teamcity_efs_data_access_point.id
+        iam             = "ENABLED"
+      }
+    }
+  }
+
+  volume {
+    name = "teamcity_logs"
+    efs_volume_configuration {
+      file_system_id          = aws_efs_file_system.teamcity_efs_file_system.id
+      transit_encryption      = "ENABLED"
+      transit_encryption_port = 2998
+      authorization_config {
+        access_point_id = aws_efs_access_point.teamcity_efs_logs_access_point.id
+        iam             = "ENABLED"
+      }
+    }
+  }
+}
+
+# ECS Service
+resource "aws_ecs_service" "teamcity" {
+  name                   = local.name_prefix
+  cluster                = aws_ecs_cluster.teamcity_cluster.name
+  task_definition        = aws_ecs_task_definition.teamcity_task_definition.arn
+  launch_type            = "FARGATE"
+  desired_count          = 1    #TODO: make this configurable
+  force_new_deployment   = true #TODO: make this configurable
+  enable_execute_command = true #TODO: make this configurable
+  wait_for_steady_state  = true
+
+  network_configuration {
+    subnets         = var.service_subnets
+    security_groups = [aws_security_group.teamcity_service_sg.id]
+  }
+  load_balancer {
+    target_group_arn = aws_lb_target_group.teamcity_target_group.arn
+    container_name   = var.container_name
+    container_port   = var.container_port
+  }
+
+  tags = local.tags
+}
+
+# TeamCity service security group
+resource "aws_security_group" "teamcity_service_sg" {
+  name        = "${local.name_prefix}-service-sg"
+  vpc_id      = var.vpc_id
+  description = "TeamCity service security group"
+  tags        = local.tags
+}
+
+# TeamCity EFS security group
+resource "aws_security_group" "teamcity_efs_sg" {
+  name        = "${local.name_prefix}-efs-sg"
+  description = "TeamCity EFS mount target security group"
+  vpc_id      = var.vpc_id
+  tags        = local.tags
+}
+
+# Ingress rule for NFS traffic from service to EFS
+resource "aws_vpc_security_group_ingress_rule" "service_efs" {
+  security_group_id            = aws_security_group.teamcity_efs_sg.id
+  referenced_security_group_id = aws_security_group.teamcity_service_sg.id
+  description                  = "Allow inbound access from TeamCity service containers to EFS"
+  ip_protocol                  = "TCP"
+  from_port                    = 2049
+  to_port                      = 2049
+}
+
+# Ingress rule for NFS traffic from EFS to service
+resource "aws_vpc_security_group_ingress_rule" "efs_service" {
+  security_group_id            = aws_security_group.teamcity_service_sg.id
+  referenced_security_group_id = aws_security_group.teamcity_efs_sg.id
+  description                  = "Allow inbound access from EFS to TeamCity service containers"
+  ip_protocol                  = "TCP"
+  from_port                    = 2049
+  to_port                      = 2049
+}
+
+# TeamCity Aurora Serverless PostgreSQL security group
+resource "aws_security_group" "teamcity_db_sg" {
+  name        = "${local.name_prefix}-db-sg"
+  description = "TeamCity DB security group"
+  vpc_id      = var.vpc_id
+  tags        = local.tags
+}
+
+# Ingress rule for PostgreSQL from service to database cluster
+resource "aws_vpc_security_group_ingress_rule" "service_db" {
+  security_group_id            = aws_security_group.teamcity_db_sg.id
+  referenced_security_group_id = aws_security_group.teamcity_service_sg.id
+  description                  = "Allow inbound access from TeamCity service containers to DB"
+  ip_protocol                  = "TCP"
+  from_port                    = 5432
+  to_port                      = 5432
+}
+
+# Ingress rule for PostgreSQL from database cluster to service
+resource "aws_vpc_security_group_ingress_rule" "db_service" {
+  security_group_id            = aws_security_group.teamcity_service_sg.id
+  referenced_security_group_id = aws_security_group.teamcity_db_sg.id
+  description                  = "Allow inbound access from DB to TeamCity service containers"
+  ip_protocol                  = "TCP"
+  from_port                    = 5432
+  to_port                      = 5432
+}
+
+# Egress rule for database to let all traffic out
+resource "aws_vpc_security_group_egress_rule" "db_outbound" {
+  security_group_id = aws_security_group.teamcity_db_sg.id
+  description       = "Allow outbound access from DB to all"
+  cidr_ipv4         = "0.0.0.0/0"
+  ip_protocol       = "-1"
+}
+
+# TeamCity ALB security group
+resource "aws_security_group" "teamcity_alb_sg" {
+  name        = "${local.name_prefix}-alb-sg"
+  vpc_id      = var.vpc_id
+  description = "TeamCity ALB security group"
+  tags        = local.tags
+}
+
+# Ingress rule for HTTP traffic from ALB to service
+resource "aws_vpc_security_group_ingress_rule" "service_inbound_alb" {
+  security_group_id            = aws_security_group.teamcity_service_sg.id
+  referenced_security_group_id = aws_security_group.teamcity_alb_sg.id
+  description                  = "Allow inbound HTTP traffic from ALB to service containers"
+  from_port                    = var.container_port
+  to_port                      = var.container_port
+  ip_protocol                  = "TCP"
+}
+
+# Egress rule for HTTP traffic from ALB to service
+resource "aws_vpc_security_group_egress_rule" "service_outbound_alb" {
+  security_group_id            = aws_security_group.teamcity_alb_sg.id
+  referenced_security_group_id = aws_security_group.teamcity_service_sg.id
+  description                  = "Allow outbound HTTP traffic from ALB to service containers"
+  from_port                    = var.container_port
+  to_port                      = var.container_port
+  ip_protocol                  = "TCP"
+}
+
+# Grant TeamCity service access to internet
+resource "aws_vpc_security_group_egress_rule" "internet_outbound" {
+  security_group_id = aws_security_group.teamcity_service_sg.id
+  description       = "Allow outbound internet access from TeamCity service containers"
+  cidr_ipv4         = "0.0.0.0/0"
+  ip_protocol       = "-1"
+}
+
+#############################################
+# IAM Roles for TeamCity Module
+#############################################
+
+data "aws_iam_policy_document" "ecs_tasks_trust_relationship" {
+  statement {
+    effect  = "Allow"
+    actions = ["sts:AssumeRole"]
+    principals {
+      type        = "Service"
+      identifiers = ["ecs-tasks.amazonaws.com"]
+    }
+  }
+}
+
+data "aws_iam_policy_document" "teamcity_default_policy" {
+  # ECS
+  statement {
+    sid    = "ECSExec"
+    effect = "Allow"
+    actions = [
+      "ssmmessages:OpenDataChannel",
+      "ssmmessages:OpenControlChannel",
+      "ssmmessages:CreateDataChannel",
+      "ssmmessages:CreateControlChannel",
+    ]
+    resources = [
+      "*"
+    ]
+  }
+}
path: modules/teamcity/main.tf
user: login: cwwalb
body: oohhh this could be the issue 
<img width="650" alt="Screenshot 2025-02-12 at 2 13 47 PM" src="https://github.com/user-attachments/assets/ebdc8a4e-6f8e-45d6-b11a-0de45f4235a7" />

created_at: 2025-02-12T20:14:42Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,467 @@
+###################################
+# ECS Cluster for TeamCity Server #
+###################################
+
+resource "aws_ecs_cluster" "teamcity_cluster" {
+  name = "${local.name_prefix}-cluster"
+
+  setting {
+    name  = "containerInsights"
+    value = "enabled"
+  }
+
+  tags = local.tags
+}
+
+# TeamCity Task Definition
+resource "aws_ecs_task_definition" "teamcity_task_definition" {
+  family                   = var.name
+  requires_compatibilities = ["FARGATE"]
+  network_mode             = "awsvpc"
+  cpu                      = var.container_cpu
+  memory                   = var.container_memory
+
+  container_definitions = jsonencode([
+    {
+      name      = var.container_name
+      image     = local.image
+      cpu       = var.container_cpu
+      memory    = var.container_memory
+      essential = true
+      portMappings = [
+        {
+          containerPort = var.container_port
+          hostPort      = var.container_port
+        }
+      ]
+      logConfiguration = {
+        logDriver = "awslogs"
+        options = {
+          awslogs-group         = aws_cloudwatch_log_group.teamcity_log_group.name
+          awslogs-region        = data.aws_region.current.name
+          awslogs-stream-prefix = "[APP]"
+        }
+      }
+      mountPoints = [
+        {
+          sourceVolume  = "teamcity_data",
+          containerPath = "/data/teamcity_server/datadir"
+          readOnly      = false
+        },
+        {
+          sourceVolume  = "teamcity_logs",
+          containerPath = "/data/teamcity_server/logs"
+          readOnly      = false
+        }
+      ]
+      # Set environment variables for database connection string
+      environment = [
+        {
+          name  = "TEAMCITY_DB_URL"
+          value = local.database_connection_string
+        },
+        {
+          name  = "TEAMCITY_DB_USER"
+          value = local.database_user
+        },
+        {
+          name  = "TEAMCITY_DB_PASSWORD"
+          value = local.database_password
+        },
+        {
+          name  = "TEAMCITY_LOGS_PATH"
+          value = "/data/teamcity_server/logs"
+        }
+      ]
+    }
+  ])
+  tags = {
+    Name = var.name
+  }
+  task_role_arn      = aws_iam_role.teamcity_default_role.arn
+  execution_role_arn = aws_iam_role.teamcity_task_execution_role.arn
+
+  volume {
+    name = "teamcity_data"
+    efs_volume_configuration {
+      file_system_id          = aws_efs_file_system.teamcity_efs_file_system.id
+      transit_encryption      = "ENABLED"
+      transit_encryption_port = 2999
+      authorization_config {
+        access_point_id = aws_efs_access_point.teamcity_efs_data_access_point.id
+        iam             = "ENABLED"
+      }
+    }
+  }
+
+  volume {
+    name = "teamcity_logs"
+    efs_volume_configuration {
+      file_system_id          = aws_efs_file_system.teamcity_efs_file_system.id
+      transit_encryption      = "ENABLED"
+      transit_encryption_port = 2998
+      authorization_config {
+        access_point_id = aws_efs_access_point.teamcity_efs_logs_access_point.id
+        iam             = "ENABLED"
+      }
+    }
+  }
+}
+
+# ECS Service
+resource "aws_ecs_service" "teamcity" {
+  name                   = local.name_prefix
+  cluster                = aws_ecs_cluster.teamcity_cluster.name
+  task_definition        = aws_ecs_task_definition.teamcity_task_definition.arn
+  launch_type            = "FARGATE"
+  desired_count          = 1    #TODO: make this configurable
+  force_new_deployment   = true #TODO: make this configurable
+  enable_execute_command = true #TODO: make this configurable
+  wait_for_steady_state  = true
+
+  network_configuration {
+    subnets         = var.service_subnets
+    security_groups = [aws_security_group.teamcity_service_sg.id]
+  }
+  load_balancer {
+    target_group_arn = aws_lb_target_group.teamcity_target_group.arn
+    container_name   = var.container_name
+    container_port   = var.container_port
+  }
+
+  tags = local.tags
+}
+
+# TeamCity service security group
+resource "aws_security_group" "teamcity_service_sg" {
+  name        = "${local.name_prefix}-service-sg"
+  vpc_id      = var.vpc_id
+  description = "TeamCity service security group"
+  tags        = local.tags
+}
+
+# TeamCity EFS security group
+resource "aws_security_group" "teamcity_efs_sg" {
+  name        = "${local.name_prefix}-efs-sg"
+  description = "TeamCity EFS mount target security group"
+  vpc_id      = var.vpc_id
+  tags        = local.tags
+}
+
+# Ingress rule for NFS traffic from service to EFS
+resource "aws_vpc_security_group_ingress_rule" "service_efs" {
+  security_group_id            = aws_security_group.teamcity_efs_sg.id
+  referenced_security_group_id = aws_security_group.teamcity_service_sg.id
+  description                  = "Allow inbound access from TeamCity service containers to EFS"
+  ip_protocol                  = "TCP"
+  from_port                    = 2049
+  to_port                      = 2049
+}
+
+# Ingress rule for NFS traffic from EFS to service
+resource "aws_vpc_security_group_ingress_rule" "efs_service" {
+  security_group_id            = aws_security_group.teamcity_service_sg.id
+  referenced_security_group_id = aws_security_group.teamcity_efs_sg.id
+  description                  = "Allow inbound access from EFS to TeamCity service containers"
+  ip_protocol                  = "TCP"
+  from_port                    = 2049
+  to_port                      = 2049
+}
+
+# TeamCity Aurora Serverless PostgreSQL security group
+resource "aws_security_group" "teamcity_db_sg" {
+  name        = "${local.name_prefix}-db-sg"
+  description = "TeamCity DB security group"
+  vpc_id      = var.vpc_id
+  tags        = local.tags
+}
+
+# Ingress rule for PostgreSQL from service to database cluster
+resource "aws_vpc_security_group_ingress_rule" "service_db" {
+  security_group_id            = aws_security_group.teamcity_db_sg.id
+  referenced_security_group_id = aws_security_group.teamcity_service_sg.id
+  description                  = "Allow inbound access from TeamCity service containers to DB"
+  ip_protocol                  = "TCP"
+  from_port                    = 5432
+  to_port                      = 5432
+}
+
+# Ingress rule for PostgreSQL from database cluster to service
+resource "aws_vpc_security_group_ingress_rule" "db_service" {
+  security_group_id            = aws_security_group.teamcity_service_sg.id
+  referenced_security_group_id = aws_security_group.teamcity_db_sg.id
+  description                  = "Allow inbound access from DB to TeamCity service containers"
+  ip_protocol                  = "TCP"
+  from_port                    = 5432
+  to_port                      = 5432
+}
+
+# Egress rule for database to let all traffic out
+resource "aws_vpc_security_group_egress_rule" "db_outbound" {
+  security_group_id = aws_security_group.teamcity_db_sg.id
+  description       = "Allow outbound access from DB to all"
+  cidr_ipv4         = "0.0.0.0/0"
+  ip_protocol       = "-1"
+}
+
+# TeamCity ALB security group
+resource "aws_security_group" "teamcity_alb_sg" {
+  name        = "${local.name_prefix}-alb-sg"
+  vpc_id      = var.vpc_id
+  description = "TeamCity ALB security group"
+  tags        = local.tags
+}
+
+# Ingress rule for HTTP traffic from ALB to service
+resource "aws_vpc_security_group_ingress_rule" "service_inbound_alb" {
+  security_group_id            = aws_security_group.teamcity_service_sg.id
+  referenced_security_group_id = aws_security_group.teamcity_alb_sg.id
+  description                  = "Allow inbound HTTP traffic from ALB to service containers"
+  from_port                    = var.container_port
+  to_port                      = var.container_port
+  ip_protocol                  = "TCP"
+}
+
+# Egress rule for HTTP traffic from ALB to service
+resource "aws_vpc_security_group_egress_rule" "service_outbound_alb" {
+  security_group_id            = aws_security_group.teamcity_alb_sg.id
+  referenced_security_group_id = aws_security_group.teamcity_service_sg.id
+  description                  = "Allow outbound HTTP traffic from ALB to service containers"
+  from_port                    = var.container_port
+  to_port                      = var.container_port
+  ip_protocol                  = "TCP"
+}
+
+# Grant TeamCity service access to internet
+resource "aws_vpc_security_group_egress_rule" "internet_outbound" {
+  security_group_id = aws_security_group.teamcity_service_sg.id
+  description       = "Allow outbound internet access from TeamCity service containers"
+  cidr_ipv4         = "0.0.0.0/0"
+  ip_protocol       = "-1"
+}
+
+#############################################
+# IAM Roles for TeamCity Module
+#############################################
+
+data "aws_iam_policy_document" "ecs_tasks_trust_relationship" {
+  statement {
+    effect  = "Allow"
+    actions = ["sts:AssumeRole"]
+    principals {
+      type        = "Service"
+      identifiers = ["ecs-tasks.amazonaws.com"]
+    }
+  }
+}
+
+data "aws_iam_policy_document" "teamcity_default_policy" {
+  # ECS
+  statement {
+    sid    = "ECSExec"
+    effect = "Allow"
+    actions = [
+      "ssmmessages:OpenDataChannel",
+      "ssmmessages:OpenControlChannel",
+      "ssmmessages:CreateDataChannel",
+      "ssmmessages:CreateControlChannel",
+    ]
+    resources = [
+      "*"
+    ]
+  }
+}
path: modules/teamcity/main.tf
user: login: cwwalb
body: teamcity datadir is under /data/teamcity_server/datadir with the way we are deploying the path we are assigning to the tcuser is /services. Not sure why it is doing that
created_at: 2025-02-12T20:16:28Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,461 @@
+###################################
+# ECS Cluster for TeamCity Server #
+###################################
+
+resource "aws_ecs_cluster" "teamcity_cluster" {
+  name = "${local.name_prefix}-cluster"
+
+  setting {
+    name  = "containerInsights"
+    value = "enabled"
+  }
+
+  tags = local.tags
+}
+
+# TeamCity Task Definition
+resource "aws_ecs_task_definition" "teamcity_task_definition" {
+  family                   = var.name
+  requires_compatibilities = ["FARGATE"]
+  network_mode             = "awsvpc"
+  cpu                      = var.container_cpu
+  memory                   = var.container_memory
+
+  container_definitions = jsonencode([
+    {
+      name      = var.container_name
+      image     = local.image
+      cpu       = var.container_cpu
+      memory    = var.container_memory
+      essential = true
+
+      portMappings = [
+        {
+          containerPort = var.container_port
+          hostPort      = var.container_port
+        }
+      ]
+      logConfiguration = {
+        logDriver = "awslogs"
+        options = {
+          awslogs-group         = aws_cloudwatch_log_group.teamcity_log_group.name
+          awslogs-region        = data.aws_region.current.name
+          awslogs-stream-prefix = "[APP]"
+        }
+      }
+
+      mountPoints = [
+        {
+          sourceVolume  = "teamcity_data",
+          containerPath = "/data/teamcity_server/datadir"
+          readOnly      = false
+        }
+      ]
+
+      # Set environment variables for database connection string
+      environment = [
+        {
+          name  = "TEAMCITY_DB_URL"
+          value = var.database_connection_string
+        },
+        {
+          name  = "TEAMCITY_DB_USER"
+          value = var.database_master_username
+        },
+        {
+          name  = "TEAMCITY_DB_PASSWORD"
+          value = var.database_master_password
+        },
+        # {
+        #   name  = "TEAMCITY_LOGS_PATH"
+        #   value = "/data/teamcity_server/logs"
+        # },
+        {
+          name  = "TEAMCITY_DATA_PATH"
+          value = "/data/teamcity_server/datadir"
+        }
+      ]
+    }
+  ])
+  tags = {
+    Name = var.name
+  }
+  task_role_arn      = aws_iam_role.teamcity_default_role.arn
+  execution_role_arn = aws_iam_role.teamcity_task_execution_role.arn
+
+  volume {
+    name = "teamcity_data"
+    efs_volume_configuration {
+      file_system_id          = aws_efs_file_system.teamcity_efs_file_system.id
+      transit_encryption      = "ENABLED"
+      transit_encryption_port = 2999
+      authorization_config {
+        access_point_id = aws_efs_access_point.teamcity_efs_data_access_point.id
+        iam             = "ENABLED"
+      }
+    }
+  }
+}
+
+# ECS Service
+resource "aws_ecs_service" "teamcity" {
+  name                   = local.name_prefix
+  cluster                = aws_ecs_cluster.teamcity_cluster.name
+  task_definition        = aws_ecs_task_definition.teamcity_task_definition.arn
+  launch_type            = "FARGATE"
+  desired_count          = 1     #TODO: make this configurable
+  force_new_deployment   = true  #TODO: make this configurable
+  enable_execute_command = true  #TODO: make this configurable
+  wait_for_steady_state  = false #TODO: make this configurable
+
+  network_configuration {
+    subnets         = var.service_subnets
+    security_groups = [aws_security_group.teamcity_service_sg.id]
+  }
+  load_balancer {
+    target_group_arn = aws_lb_target_group.teamcity_target_group.arn
+    container_name   = var.container_name
+    container_port   = var.container_port
+  }
+
+  tags = local.tags
+}
+
+# TeamCity service security group
+resource "aws_security_group" "teamcity_service_sg" {
+  name        = "${local.name_prefix}-service-sg"
+  vpc_id      = var.vpc_id
+  description = "TeamCity service security group"
+  tags        = local.tags
+}
+
+# TeamCity EFS security group
+resource "aws_security_group" "teamcity_efs_sg" {
+  name        = "${local.name_prefix}-efs-sg"
+  description = "TeamCity EFS mount target security group"
+  vpc_id      = var.vpc_id
+  tags        = local.tags
+}
+
+# Ingress rule for NFS traffic from service to EFS
+resource "aws_vpc_security_group_ingress_rule" "service_efs" {
+  security_group_id            = aws_security_group.teamcity_efs_sg.id
+  referenced_security_group_id = aws_security_group.teamcity_service_sg.id
+  description                  = "Allow inbound access from TeamCity service containers to EFS"
+  ip_protocol                  = "TCP"
+  from_port                    = 2049
+  to_port                      = 2049
+}
+
+# Ingress rule for NFS traffic from EFS to service
+resource "aws_vpc_security_group_ingress_rule" "efs_service" {
+  security_group_id            = aws_security_group.teamcity_service_sg.id
+  referenced_security_group_id = aws_security_group.teamcity_efs_sg.id
+  description                  = "Allow inbound access from EFS to TeamCity service containers"
+  ip_protocol                  = "TCP"
+  from_port                    = 2049
+  to_port                      = 2049
+}
+
+# TeamCity Aurora Serverless PostgreSQL security group
+resource "aws_security_group" "teamcity_db_sg" {
+  name        = "${local.name_prefix}-db-sg"
+  description = "TeamCity DB security group"
+  vpc_id      = var.vpc_id
+  tags        = local.tags
+}
+
+# Ingress rule for PostgreSQL from service to database cluster
+resource "aws_vpc_security_group_ingress_rule" "service_db" {
+  security_group_id            = aws_security_group.teamcity_db_sg.id
+  referenced_security_group_id = aws_security_group.teamcity_service_sg.id
+  description                  = "Allow inbound access from TeamCity service containers to DB"
+  ip_protocol                  = "TCP"
+  from_port                    = 5432
+  to_port                      = 5432
+}
+
+# Ingress rule for PostgreSQL from database cluster to service
+resource "aws_vpc_security_group_ingress_rule" "db_service" {
+  security_group_id            = aws_security_group.teamcity_service_sg.id
+  referenced_security_group_id = aws_security_group.teamcity_db_sg.id
+  description                  = "Allow inbound access from DB to TeamCity service containers"
+  ip_protocol                  = "TCP"
+  from_port                    = 5432
+  to_port                      = 5432
+}
+
+# Egress rule for database to let all traffic out
+resource "aws_vpc_security_group_egress_rule" "db_outbound" {
+  security_group_id = aws_security_group.teamcity_db_sg.id
+  description       = "Allow outbound access from DB to all"
+  cidr_ipv4         = "0.0.0.0/0"
+  ip_protocol       = "-1"
+}
+
+# TeamCity ALB security group
+resource "aws_security_group" "teamcity_alb_sg" {
+  name        = "${local.name_prefix}-alb-sg"
+  vpc_id      = var.vpc_id
+  description = "TeamCity ALB security group"
+  tags        = local.tags
+}
+
+# Ingress rule for HTTP traffic from ALB to service
+resource "aws_vpc_security_group_ingress_rule" "service_inbound_alb" {
+  security_group_id            = aws_security_group.teamcity_service_sg.id
+  referenced_security_group_id = aws_security_group.teamcity_alb_sg.id
+  description                  = "Allow inbound HTTP traffic from ALB to service containers"
+  from_port                    = var.container_port
+  to_port                      = var.container_port
+  ip_protocol                  = "TCP"
+}
+
+# Egress rule for HTTP traffic from ALB to service
+resource "aws_vpc_security_group_egress_rule" "service_outbound_alb" {
+  security_group_id            = aws_security_group.teamcity_alb_sg.id
+  referenced_security_group_id = aws_security_group.teamcity_service_sg.id
+  description                  = "Allow outbound HTTP traffic from ALB to service containers"
+  from_port                    = var.container_port
+  to_port                      = var.container_port
+  ip_protocol                  = "TCP"
+}
+
+# Grant TeamCity service access to internet
+resource "aws_vpc_security_group_egress_rule" "internet_outbound" {
+  security_group_id = aws_security_group.teamcity_service_sg.id
+  description       = "Allow outbound internet access from TeamCity service containers"
+  cidr_ipv4         = "0.0.0.0/0"
+  ip_protocol       = "-1"
+}
+
+#############################################
+# IAM Roles for TeamCity Module
+#############################################
+
+data "aws_iam_policy_document" "ecs_tasks_trust_relationship" {
+  statement {
+    effect  = "Allow"
+    actions = ["sts:AssumeRole"]
+    principals {
+      type        = "Service"
+      identifiers = ["ecs-tasks.amazonaws.com"]
+    }
+  }
+}
+
+data "aws_iam_policy_document" "teamcity_default_policy" {
+  # ECS
+  statement {
+    sid    = "ECSExec"
+    effect = "Allow"
+    actions = [
+      "ssmmessages:OpenDataChannel",
+      "ssmmessages:OpenControlChannel",
+      "ssmmessages:CreateDataChannel",
+      "ssmmessages:CreateControlChannel",
+    ]
+    resources = [
+      "*"
+    ]
+  }
+
+  #grant the task necessary EFS permissions to be able to modify directories
+  statement {
+    sid    = "EFS"
+    effect = "Allow"
+    actions = [
+      "elasticfilesystem:ClientWrite",
+      "elasticfilesystem:ClientRootAccess",
+      "elasticfilesystem:ClientMount"
+    ]
+    resources = [
+      aws_efs_file_system.teamcity_efs_file_system.arn
+    ]
+    # condition {
+    #   test     = "StringEquals"
+    #   variable = "elasticfilesystem:AccessPointArn"
+    #   values = [
+    #     aws_efs_access_point.teamcity_efs_data_access_point.arn,
+    #     aws_efs_access_point.teamcity_efs_logs_access_point.arn
+    #   ]
+    # }
+  }
+}
+
+resource "aws_iam_policy" "teamcity_default_policy" {
+  name        = "teamcity-default-policy"
+  description = "Policy granting permissions for Unreal Horde."
+  policy      = data.aws_iam_policy_document.teamcity_default_policy.json
+}
+
+resource "aws_iam_role" "teamcity_default_role" {
+  name               = "teamcity-default-role"
+  description        = "Default role for TeamCity ECS task."
+  assume_role_policy = data.aws_iam_policy_document.ecs_tasks_trust_relationship.json
+  managed_policy_arns = [
+    aws_iam_policy.teamcity_default_policy.arn
+  ]
+
+  tags = local.tags
+}
+resource "aws_iam_role" "teamcity_task_execution_role" {
+  name = "teamcity-task-execution-role"
+
+  assume_role_policy  = data.aws_iam_policy_document.ecs_tasks_trust_relationship.json
+  managed_policy_arns = ["arn:aws:iam::aws:policy/service-role/AmazonECSTaskExecutionRolePolicy"]
+}
+
+# CloudWatch Logs
+resource "aws_cloudwatch_log_group" "teamcity_log_group" {
+  #checkov:skip=CKV_AWS_158: KMS Encryption disabled by default
+  name              = "${local.name_prefix}-log-group"
+  retention_in_days = var.teamcity_cloudwatch_log_retention_in_days
+  tags              = local.tags
+}
+
+# Load Balancer for TeamCity Service
+resource "aws_lb" "teamcity_external_lb" {
+  name                       = "${local.name_prefix}-lb"
+  security_groups            = [aws_security_group.teamcity_alb_sg.id]
+  load_balancer_type         = "application"
+  internal                   = false
+  subnets                    = var.alb_subnets
+  drop_invalid_header_fields = true
+  tags                       = local.tags
+}
+
+# TeamCity target group for ALB
+resource "aws_lb_target_group" "teamcity_target_group" {
+  name        = "${local.name_prefix}-tg"
+  port        = var.container_port
+  protocol    = "HTTP"
+  vpc_id      = var.vpc_id
+  target_type = "ip"
+
+  health_check {
+    path                = "/healthCheck/healthy"
+    interval            = 30
+    timeout             = 5
+    healthy_threshold   = 5
+    unhealthy_threshold = 2
+    port                = var.container_port
+    protocol            = "HTTP"
+    matcher             = "200"
+  }
+
+  tags = local.tags
+}
+
+# ALB HTTPS Listener
+resource "aws_lb_listener" "teamcity_listener" {
+  load_balancer_arn = aws_lb.teamcity_external_lb.arn
+  port              = "443"
+  protocol          = "HTTPS"
+  ssl_policy        = "ELBSecurityPolicy-TLS13-1-2-2021-06"
+  certificate_arn   = var.alb_certificate_arn
+  default_action {
+    type             = "forward"
+    target_group_arn = aws_lb_target_group.teamcity_target_group.arn
+  }
+  tags = local.tags
+}
+
+# #######################################
+# # TeamCity Aurora Serverless Database #
+# #######################################
+# Subnet group
+resource "aws_db_subnet_group" "teamcity_db_subnet_group" {
+  name       = "${local.name_prefix}-db-subnet-group"
+  subnet_ids = var.service_subnets
+  tags       = local.tags
+}
+
+# # RDS instance with Aurora serverless engine
+resource "aws_rds_cluster" "teamcity_db_cluster" {
+  #count = var.database_connection_string == null ? var.teamcity_instance_count : 0
+
+  cluster_identifier   = "teamcity-cluster"
+  engine               = "aurora-postgresql"
+  engine_mode          = "provisioned"
+  engine_version       = "16.6"
+  database_name        = "teamcity"
+  master_username      = "teamcity"
+  master_password      = "teamcity2025"
+  storage_encrypted    = true
+  db_subnet_group_name = aws_db_subnet_group.teamcity_db_subnet_group.id
+  vpc_security_group_ids = [
+    aws_security_group.teamcity_db_sg.id
+  ]
+
+  serverlessv2_scaling_configuration {
+    max_capacity             = 1.0
+    min_capacity             = 0.0
+    seconds_until_auto_pause = 3600
+  }
+}
path: modules/teamcity/main.tf
user: login: github-advanced-security[bot]
body: ## Ensure that RDS Cluster log capture is enabled

Ensure that RDS Cluster log capture is enabled

[Show more details](https://github.com/aws-games/cloud-game-development-toolkit/security/code-scanning/823)
created_at: 2025-02-14T19:33:06Z
author_association: NONE
diff_hunk: @@ -0,0 +1,461 @@
+###################################
+# ECS Cluster for TeamCity Server #
+###################################
+
+resource "aws_ecs_cluster" "teamcity_cluster" {
+  name = "${local.name_prefix}-cluster"
+
+  setting {
+    name  = "containerInsights"
+    value = "enabled"
+  }
+
+  tags = local.tags
+}
+
+# TeamCity Task Definition
+resource "aws_ecs_task_definition" "teamcity_task_definition" {
+  family                   = var.name
+  requires_compatibilities = ["FARGATE"]
+  network_mode             = "awsvpc"
+  cpu                      = var.container_cpu
+  memory                   = var.container_memory
+
+  container_definitions = jsonencode([
+    {
+      name      = var.container_name
+      image     = local.image
+      cpu       = var.container_cpu
+      memory    = var.container_memory
+      essential = true
+
+      portMappings = [
+        {
+          containerPort = var.container_port
+          hostPort      = var.container_port
+        }
+      ]
+      logConfiguration = {
+        logDriver = "awslogs"
+        options = {
+          awslogs-group         = aws_cloudwatch_log_group.teamcity_log_group.name
+          awslogs-region        = data.aws_region.current.name
+          awslogs-stream-prefix = "[APP]"
+        }
+      }
+
+      mountPoints = [
+        {
+          sourceVolume  = "teamcity_data",
+          containerPath = "/data/teamcity_server/datadir"
+          readOnly      = false
+        }
+      ]
+
+      # Set environment variables for database connection string
+      environment = [
+        {
+          name  = "TEAMCITY_DB_URL"
+          value = var.database_connection_string
+        },
+        {
+          name  = "TEAMCITY_DB_USER"
+          value = var.database_master_username
+        },
+        {
+          name  = "TEAMCITY_DB_PASSWORD"
+          value = var.database_master_password
+        },
+        # {
+        #   name  = "TEAMCITY_LOGS_PATH"
+        #   value = "/data/teamcity_server/logs"
+        # },
+        {
+          name  = "TEAMCITY_DATA_PATH"
+          value = "/data/teamcity_server/datadir"
+        }
+      ]
+    }
+  ])
+  tags = {
+    Name = var.name
+  }
+  task_role_arn      = aws_iam_role.teamcity_default_role.arn
+  execution_role_arn = aws_iam_role.teamcity_task_execution_role.arn
+
+  volume {
+    name = "teamcity_data"
+    efs_volume_configuration {
+      file_system_id          = aws_efs_file_system.teamcity_efs_file_system.id
+      transit_encryption      = "ENABLED"
+      transit_encryption_port = 2999
+      authorization_config {
+        access_point_id = aws_efs_access_point.teamcity_efs_data_access_point.id
+        iam             = "ENABLED"
+      }
+    }
+  }
+}
+
+# ECS Service
+resource "aws_ecs_service" "teamcity" {
+  name                   = local.name_prefix
+  cluster                = aws_ecs_cluster.teamcity_cluster.name
+  task_definition        = aws_ecs_task_definition.teamcity_task_definition.arn
+  launch_type            = "FARGATE"
+  desired_count          = 1     #TODO: make this configurable
+  force_new_deployment   = true  #TODO: make this configurable
+  enable_execute_command = true  #TODO: make this configurable
+  wait_for_steady_state  = false #TODO: make this configurable
+
+  network_configuration {
+    subnets         = var.service_subnets
+    security_groups = [aws_security_group.teamcity_service_sg.id]
+  }
+  load_balancer {
+    target_group_arn = aws_lb_target_group.teamcity_target_group.arn
+    container_name   = var.container_name
+    container_port   = var.container_port
+  }
+
+  tags = local.tags
+}
+
+# TeamCity service security group
+resource "aws_security_group" "teamcity_service_sg" {
+  name        = "${local.name_prefix}-service-sg"
+  vpc_id      = var.vpc_id
+  description = "TeamCity service security group"
+  tags        = local.tags
+}
+
+# TeamCity EFS security group
+resource "aws_security_group" "teamcity_efs_sg" {
+  name        = "${local.name_prefix}-efs-sg"
+  description = "TeamCity EFS mount target security group"
+  vpc_id      = var.vpc_id
+  tags        = local.tags
+}
+
+# Ingress rule for NFS traffic from service to EFS
+resource "aws_vpc_security_group_ingress_rule" "service_efs" {
+  security_group_id            = aws_security_group.teamcity_efs_sg.id
+  referenced_security_group_id = aws_security_group.teamcity_service_sg.id
+  description                  = "Allow inbound access from TeamCity service containers to EFS"
+  ip_protocol                  = "TCP"
+  from_port                    = 2049
+  to_port                      = 2049
+}
+
+# Ingress rule for NFS traffic from EFS to service
+resource "aws_vpc_security_group_ingress_rule" "efs_service" {
+  security_group_id            = aws_security_group.teamcity_service_sg.id
+  referenced_security_group_id = aws_security_group.teamcity_efs_sg.id
+  description                  = "Allow inbound access from EFS to TeamCity service containers"
+  ip_protocol                  = "TCP"
+  from_port                    = 2049
+  to_port                      = 2049
+}
+
+# TeamCity Aurora Serverless PostgreSQL security group
+resource "aws_security_group" "teamcity_db_sg" {
+  name        = "${local.name_prefix}-db-sg"
+  description = "TeamCity DB security group"
+  vpc_id      = var.vpc_id
+  tags        = local.tags
+}
+
+# Ingress rule for PostgreSQL from service to database cluster
+resource "aws_vpc_security_group_ingress_rule" "service_db" {
+  security_group_id            = aws_security_group.teamcity_db_sg.id
+  referenced_security_group_id = aws_security_group.teamcity_service_sg.id
+  description                  = "Allow inbound access from TeamCity service containers to DB"
+  ip_protocol                  = "TCP"
+  from_port                    = 5432
+  to_port                      = 5432
+}
+
+# Ingress rule for PostgreSQL from database cluster to service
+resource "aws_vpc_security_group_ingress_rule" "db_service" {
+  security_group_id            = aws_security_group.teamcity_service_sg.id
+  referenced_security_group_id = aws_security_group.teamcity_db_sg.id
+  description                  = "Allow inbound access from DB to TeamCity service containers"
+  ip_protocol                  = "TCP"
+  from_port                    = 5432
+  to_port                      = 5432
+}
+
+# Egress rule for database to let all traffic out
+resource "aws_vpc_security_group_egress_rule" "db_outbound" {
+  security_group_id = aws_security_group.teamcity_db_sg.id
+  description       = "Allow outbound access from DB to all"
+  cidr_ipv4         = "0.0.0.0/0"
+  ip_protocol       = "-1"
+}
+
+# TeamCity ALB security group
+resource "aws_security_group" "teamcity_alb_sg" {
+  name        = "${local.name_prefix}-alb-sg"
+  vpc_id      = var.vpc_id
+  description = "TeamCity ALB security group"
+  tags        = local.tags
+}
+
+# Ingress rule for HTTP traffic from ALB to service
+resource "aws_vpc_security_group_ingress_rule" "service_inbound_alb" {
+  security_group_id            = aws_security_group.teamcity_service_sg.id
+  referenced_security_group_id = aws_security_group.teamcity_alb_sg.id
+  description                  = "Allow inbound HTTP traffic from ALB to service containers"
+  from_port                    = var.container_port
+  to_port                      = var.container_port
+  ip_protocol                  = "TCP"
+}
+
+# Egress rule for HTTP traffic from ALB to service
+resource "aws_vpc_security_group_egress_rule" "service_outbound_alb" {
+  security_group_id            = aws_security_group.teamcity_alb_sg.id
+  referenced_security_group_id = aws_security_group.teamcity_service_sg.id
+  description                  = "Allow outbound HTTP traffic from ALB to service containers"
+  from_port                    = var.container_port
+  to_port                      = var.container_port
+  ip_protocol                  = "TCP"
+}
+
+# Grant TeamCity service access to internet
+resource "aws_vpc_security_group_egress_rule" "internet_outbound" {
+  security_group_id = aws_security_group.teamcity_service_sg.id
+  description       = "Allow outbound internet access from TeamCity service containers"
+  cidr_ipv4         = "0.0.0.0/0"
+  ip_protocol       = "-1"
+}
+
+#############################################
+# IAM Roles for TeamCity Module
+#############################################
+
+data "aws_iam_policy_document" "ecs_tasks_trust_relationship" {
+  statement {
+    effect  = "Allow"
+    actions = ["sts:AssumeRole"]
+    principals {
+      type        = "Service"
+      identifiers = ["ecs-tasks.amazonaws.com"]
+    }
+  }
+}
+
+data "aws_iam_policy_document" "teamcity_default_policy" {
+  # ECS
+  statement {
+    sid    = "ECSExec"
+    effect = "Allow"
+    actions = [
+      "ssmmessages:OpenDataChannel",
+      "ssmmessages:OpenControlChannel",
+      "ssmmessages:CreateDataChannel",
+      "ssmmessages:CreateControlChannel",
+    ]
+    resources = [
+      "*"
+    ]
+  }
+
+  #grant the task necessary EFS permissions to be able to modify directories
+  statement {
+    sid    = "EFS"
+    effect = "Allow"
+    actions = [
+      "elasticfilesystem:ClientWrite",
+      "elasticfilesystem:ClientRootAccess",
+      "elasticfilesystem:ClientMount"
+    ]
+    resources = [
+      aws_efs_file_system.teamcity_efs_file_system.arn
+    ]
+    # condition {
+    #   test     = "StringEquals"
+    #   variable = "elasticfilesystem:AccessPointArn"
+    #   values = [
+    #     aws_efs_access_point.teamcity_efs_data_access_point.arn,
+    #     aws_efs_access_point.teamcity_efs_logs_access_point.arn
+    #   ]
+    # }
+  }
+}
+
+resource "aws_iam_policy" "teamcity_default_policy" {
+  name        = "teamcity-default-policy"
+  description = "Policy granting permissions for Unreal Horde."
+  policy      = data.aws_iam_policy_document.teamcity_default_policy.json
+}
+
+resource "aws_iam_role" "teamcity_default_role" {
+  name               = "teamcity-default-role"
+  description        = "Default role for TeamCity ECS task."
+  assume_role_policy = data.aws_iam_policy_document.ecs_tasks_trust_relationship.json
+  managed_policy_arns = [
+    aws_iam_policy.teamcity_default_policy.arn
+  ]
+
+  tags = local.tags
+}
+resource "aws_iam_role" "teamcity_task_execution_role" {
+  name = "teamcity-task-execution-role"
+
+  assume_role_policy  = data.aws_iam_policy_document.ecs_tasks_trust_relationship.json
+  managed_policy_arns = ["arn:aws:iam::aws:policy/service-role/AmazonECSTaskExecutionRolePolicy"]
+}
+
+# CloudWatch Logs
+resource "aws_cloudwatch_log_group" "teamcity_log_group" {
+  #checkov:skip=CKV_AWS_158: KMS Encryption disabled by default
+  name              = "${local.name_prefix}-log-group"
+  retention_in_days = var.teamcity_cloudwatch_log_retention_in_days
+  tags              = local.tags
+}
+
+# Load Balancer for TeamCity Service
+resource "aws_lb" "teamcity_external_lb" {
+  name                       = "${local.name_prefix}-lb"
+  security_groups            = [aws_security_group.teamcity_alb_sg.id]
+  load_balancer_type         = "application"
+  internal                   = false
+  subnets                    = var.alb_subnets
+  drop_invalid_header_fields = true
+  tags                       = local.tags
+}
+
+# TeamCity target group for ALB
+resource "aws_lb_target_group" "teamcity_target_group" {
+  name        = "${local.name_prefix}-tg"
+  port        = var.container_port
+  protocol    = "HTTP"
+  vpc_id      = var.vpc_id
+  target_type = "ip"
+
+  health_check {
+    path                = "/healthCheck/healthy"
+    interval            = 30
+    timeout             = 5
+    healthy_threshold   = 5
+    unhealthy_threshold = 2
+    port                = var.container_port
+    protocol            = "HTTP"
+    matcher             = "200"
+  }
+
+  tags = local.tags
+}
+
+# ALB HTTPS Listener
+resource "aws_lb_listener" "teamcity_listener" {
+  load_balancer_arn = aws_lb.teamcity_external_lb.arn
+  port              = "443"
+  protocol          = "HTTPS"
+  ssl_policy        = "ELBSecurityPolicy-TLS13-1-2-2021-06"
+  certificate_arn   = var.alb_certificate_arn
+  default_action {
+    type             = "forward"
+    target_group_arn = aws_lb_target_group.teamcity_target_group.arn
+  }
+  tags = local.tags
+}
+
+# #######################################
+# # TeamCity Aurora Serverless Database #
+# #######################################
+# Subnet group
+resource "aws_db_subnet_group" "teamcity_db_subnet_group" {
+  name       = "${local.name_prefix}-db-subnet-group"
+  subnet_ids = var.service_subnets
+  tags       = local.tags
+}
+
+# # RDS instance with Aurora serverless engine
+resource "aws_rds_cluster" "teamcity_db_cluster" {
+  #count = var.database_connection_string == null ? var.teamcity_instance_count : 0
+
+  cluster_identifier   = "teamcity-cluster"
+  engine               = "aurora-postgresql"
+  engine_mode          = "provisioned"
+  engine_version       = "16.6"
+  database_name        = "teamcity"
+  master_username      = "teamcity"
+  master_password      = "teamcity2025"
+  storage_encrypted    = true
+  db_subnet_group_name = aws_db_subnet_group.teamcity_db_subnet_group.id
+  vpc_security_group_ids = [
+    aws_security_group.teamcity_db_sg.id
+  ]
+
+  serverlessv2_scaling_configuration {
+    max_capacity             = 1.0
+    min_capacity             = 0.0
+    seconds_until_auto_pause = 3600
+  }
+}
path: modules/teamcity/main.tf
user: login: github-advanced-security[bot]
body: ## Ensure RDS Clusters are encrypted using KMS CMKs

Ensure RDS Clusters are encrypted using KMS CMKs

[Show more details](https://github.com/aws-games/cloud-game-development-toolkit/security/code-scanning/824)
created_at: 2025-02-14T19:33:06Z
author_association: NONE
diff_hunk: @@ -0,0 +1,461 @@
+###################################
+# ECS Cluster for TeamCity Server #
+###################################
+
+resource "aws_ecs_cluster" "teamcity_cluster" {
+  name = "${local.name_prefix}-cluster"
+
+  setting {
+    name  = "containerInsights"
+    value = "enabled"
+  }
+
+  tags = local.tags
+}
+
+# TeamCity Task Definition
+resource "aws_ecs_task_definition" "teamcity_task_definition" {
+  family                   = var.name
+  requires_compatibilities = ["FARGATE"]
+  network_mode             = "awsvpc"
+  cpu                      = var.container_cpu
+  memory                   = var.container_memory
+
+  container_definitions = jsonencode([
+    {
+      name      = var.container_name
+      image     = local.image
+      cpu       = var.container_cpu
+      memory    = var.container_memory
+      essential = true
+
+      portMappings = [
+        {
+          containerPort = var.container_port
+          hostPort      = var.container_port
+        }
+      ]
+      logConfiguration = {
+        logDriver = "awslogs"
+        options = {
+          awslogs-group         = aws_cloudwatch_log_group.teamcity_log_group.name
+          awslogs-region        = data.aws_region.current.name
+          awslogs-stream-prefix = "[APP]"
+        }
+      }
+
+      mountPoints = [
+        {
+          sourceVolume  = "teamcity_data",
+          containerPath = "/data/teamcity_server/datadir"
+          readOnly      = false
+        }
+      ]
+
+      # Set environment variables for database connection string
+      environment = [
+        {
+          name  = "TEAMCITY_DB_URL"
+          value = var.database_connection_string
+        },
+        {
+          name  = "TEAMCITY_DB_USER"
+          value = var.database_master_username
+        },
+        {
+          name  = "TEAMCITY_DB_PASSWORD"
+          value = var.database_master_password
+        },
+        # {
+        #   name  = "TEAMCITY_LOGS_PATH"
+        #   value = "/data/teamcity_server/logs"
+        # },
+        {
+          name  = "TEAMCITY_DATA_PATH"
+          value = "/data/teamcity_server/datadir"
+        }
+      ]
+    }
+  ])
+  tags = {
+    Name = var.name
+  }
+  task_role_arn      = aws_iam_role.teamcity_default_role.arn
+  execution_role_arn = aws_iam_role.teamcity_task_execution_role.arn
+
+  volume {
+    name = "teamcity_data"
+    efs_volume_configuration {
+      file_system_id          = aws_efs_file_system.teamcity_efs_file_system.id
+      transit_encryption      = "ENABLED"
+      transit_encryption_port = 2999
+      authorization_config {
+        access_point_id = aws_efs_access_point.teamcity_efs_data_access_point.id
+        iam             = "ENABLED"
+      }
+    }
+  }
+}
+
+# ECS Service
+resource "aws_ecs_service" "teamcity" {
+  name                   = local.name_prefix
+  cluster                = aws_ecs_cluster.teamcity_cluster.name
+  task_definition        = aws_ecs_task_definition.teamcity_task_definition.arn
+  launch_type            = "FARGATE"
+  desired_count          = 1     #TODO: make this configurable
+  force_new_deployment   = true  #TODO: make this configurable
+  enable_execute_command = true  #TODO: make this configurable
+  wait_for_steady_state  = false #TODO: make this configurable
+
+  network_configuration {
+    subnets         = var.service_subnets
+    security_groups = [aws_security_group.teamcity_service_sg.id]
+  }
+  load_balancer {
+    target_group_arn = aws_lb_target_group.teamcity_target_group.arn
+    container_name   = var.container_name
+    container_port   = var.container_port
+  }
+
+  tags = local.tags
+}
+
+# TeamCity service security group
+resource "aws_security_group" "teamcity_service_sg" {
+  name        = "${local.name_prefix}-service-sg"
+  vpc_id      = var.vpc_id
+  description = "TeamCity service security group"
+  tags        = local.tags
+}
+
+# TeamCity EFS security group
+resource "aws_security_group" "teamcity_efs_sg" {
+  name        = "${local.name_prefix}-efs-sg"
+  description = "TeamCity EFS mount target security group"
+  vpc_id      = var.vpc_id
+  tags        = local.tags
+}
+
+# Ingress rule for NFS traffic from service to EFS
+resource "aws_vpc_security_group_ingress_rule" "service_efs" {
+  security_group_id            = aws_security_group.teamcity_efs_sg.id
+  referenced_security_group_id = aws_security_group.teamcity_service_sg.id
+  description                  = "Allow inbound access from TeamCity service containers to EFS"
+  ip_protocol                  = "TCP"
+  from_port                    = 2049
+  to_port                      = 2049
+}
+
+# Ingress rule for NFS traffic from EFS to service
+resource "aws_vpc_security_group_ingress_rule" "efs_service" {
+  security_group_id            = aws_security_group.teamcity_service_sg.id
+  referenced_security_group_id = aws_security_group.teamcity_efs_sg.id
+  description                  = "Allow inbound access from EFS to TeamCity service containers"
+  ip_protocol                  = "TCP"
+  from_port                    = 2049
+  to_port                      = 2049
+}
+
+# TeamCity Aurora Serverless PostgreSQL security group
+resource "aws_security_group" "teamcity_db_sg" {
+  name        = "${local.name_prefix}-db-sg"
+  description = "TeamCity DB security group"
+  vpc_id      = var.vpc_id
+  tags        = local.tags
+}
+
+# Ingress rule for PostgreSQL from service to database cluster
+resource "aws_vpc_security_group_ingress_rule" "service_db" {
+  security_group_id            = aws_security_group.teamcity_db_sg.id
+  referenced_security_group_id = aws_security_group.teamcity_service_sg.id
+  description                  = "Allow inbound access from TeamCity service containers to DB"
+  ip_protocol                  = "TCP"
+  from_port                    = 5432
+  to_port                      = 5432
+}
+
+# Ingress rule for PostgreSQL from database cluster to service
+resource "aws_vpc_security_group_ingress_rule" "db_service" {
+  security_group_id            = aws_security_group.teamcity_service_sg.id
+  referenced_security_group_id = aws_security_group.teamcity_db_sg.id
+  description                  = "Allow inbound access from DB to TeamCity service containers"
+  ip_protocol                  = "TCP"
+  from_port                    = 5432
+  to_port                      = 5432
+}
+
+# Egress rule for database to let all traffic out
+resource "aws_vpc_security_group_egress_rule" "db_outbound" {
+  security_group_id = aws_security_group.teamcity_db_sg.id
+  description       = "Allow outbound access from DB to all"
+  cidr_ipv4         = "0.0.0.0/0"
+  ip_protocol       = "-1"
+}
+
+# TeamCity ALB security group
+resource "aws_security_group" "teamcity_alb_sg" {
+  name        = "${local.name_prefix}-alb-sg"
+  vpc_id      = var.vpc_id
+  description = "TeamCity ALB security group"
+  tags        = local.tags
+}
+
+# Ingress rule for HTTP traffic from ALB to service
+resource "aws_vpc_security_group_ingress_rule" "service_inbound_alb" {
+  security_group_id            = aws_security_group.teamcity_service_sg.id
+  referenced_security_group_id = aws_security_group.teamcity_alb_sg.id
+  description                  = "Allow inbound HTTP traffic from ALB to service containers"
+  from_port                    = var.container_port
+  to_port                      = var.container_port
+  ip_protocol                  = "TCP"
+}
+
+# Egress rule for HTTP traffic from ALB to service
+resource "aws_vpc_security_group_egress_rule" "service_outbound_alb" {
+  security_group_id            = aws_security_group.teamcity_alb_sg.id
+  referenced_security_group_id = aws_security_group.teamcity_service_sg.id
+  description                  = "Allow outbound HTTP traffic from ALB to service containers"
+  from_port                    = var.container_port
+  to_port                      = var.container_port
+  ip_protocol                  = "TCP"
+}
+
+# Grant TeamCity service access to internet
+resource "aws_vpc_security_group_egress_rule" "internet_outbound" {
+  security_group_id = aws_security_group.teamcity_service_sg.id
+  description       = "Allow outbound internet access from TeamCity service containers"
+  cidr_ipv4         = "0.0.0.0/0"
+  ip_protocol       = "-1"
+}
+
+#############################################
+# IAM Roles for TeamCity Module
+#############################################
+
+data "aws_iam_policy_document" "ecs_tasks_trust_relationship" {
+  statement {
+    effect  = "Allow"
+    actions = ["sts:AssumeRole"]
+    principals {
+      type        = "Service"
+      identifiers = ["ecs-tasks.amazonaws.com"]
+    }
+  }
+}
+
+data "aws_iam_policy_document" "teamcity_default_policy" {
+  # ECS
+  statement {
+    sid    = "ECSExec"
+    effect = "Allow"
+    actions = [
+      "ssmmessages:OpenDataChannel",
+      "ssmmessages:OpenControlChannel",
+      "ssmmessages:CreateDataChannel",
+      "ssmmessages:CreateControlChannel",
+    ]
+    resources = [
+      "*"
+    ]
+  }
+
+  #grant the task necessary EFS permissions to be able to modify directories
+  statement {
+    sid    = "EFS"
+    effect = "Allow"
+    actions = [
+      "elasticfilesystem:ClientWrite",
+      "elasticfilesystem:ClientRootAccess",
+      "elasticfilesystem:ClientMount"
+    ]
+    resources = [
+      aws_efs_file_system.teamcity_efs_file_system.arn
+    ]
+    # condition {
+    #   test     = "StringEquals"
+    #   variable = "elasticfilesystem:AccessPointArn"
+    #   values = [
+    #     aws_efs_access_point.teamcity_efs_data_access_point.arn,
+    #     aws_efs_access_point.teamcity_efs_logs_access_point.arn
+    #   ]
+    # }
+  }
+}
+
+resource "aws_iam_policy" "teamcity_default_policy" {
+  name        = "teamcity-default-policy"
+  description = "Policy granting permissions for Unreal Horde."
+  policy      = data.aws_iam_policy_document.teamcity_default_policy.json
+}
+
+resource "aws_iam_role" "teamcity_default_role" {
+  name               = "teamcity-default-role"
+  description        = "Default role for TeamCity ECS task."
+  assume_role_policy = data.aws_iam_policy_document.ecs_tasks_trust_relationship.json
+  managed_policy_arns = [
+    aws_iam_policy.teamcity_default_policy.arn
+  ]
+
+  tags = local.tags
+}
+resource "aws_iam_role" "teamcity_task_execution_role" {
+  name = "teamcity-task-execution-role"
+
+  assume_role_policy  = data.aws_iam_policy_document.ecs_tasks_trust_relationship.json
+  managed_policy_arns = ["arn:aws:iam::aws:policy/service-role/AmazonECSTaskExecutionRolePolicy"]
+}
+
+# CloudWatch Logs
+resource "aws_cloudwatch_log_group" "teamcity_log_group" {
+  #checkov:skip=CKV_AWS_158: KMS Encryption disabled by default
+  name              = "${local.name_prefix}-log-group"
+  retention_in_days = var.teamcity_cloudwatch_log_retention_in_days
+  tags              = local.tags
+}
+
+# Load Balancer for TeamCity Service
+resource "aws_lb" "teamcity_external_lb" {
+  name                       = "${local.name_prefix}-lb"
+  security_groups            = [aws_security_group.teamcity_alb_sg.id]
+  load_balancer_type         = "application"
+  internal                   = false
+  subnets                    = var.alb_subnets
+  drop_invalid_header_fields = true
+  tags                       = local.tags
+}
+
+# TeamCity target group for ALB
+resource "aws_lb_target_group" "teamcity_target_group" {
+  name        = "${local.name_prefix}-tg"
+  port        = var.container_port
+  protocol    = "HTTP"
+  vpc_id      = var.vpc_id
+  target_type = "ip"
+
+  health_check {
+    path                = "/healthCheck/healthy"
+    interval            = 30
+    timeout             = 5
+    healthy_threshold   = 5
+    unhealthy_threshold = 2
+    port                = var.container_port
+    protocol            = "HTTP"
+    matcher             = "200"
+  }
+
+  tags = local.tags
+}
+
+# ALB HTTPS Listener
+resource "aws_lb_listener" "teamcity_listener" {
+  load_balancer_arn = aws_lb.teamcity_external_lb.arn
+  port              = "443"
+  protocol          = "HTTPS"
+  ssl_policy        = "ELBSecurityPolicy-TLS13-1-2-2021-06"
+  certificate_arn   = var.alb_certificate_arn
+  default_action {
+    type             = "forward"
+    target_group_arn = aws_lb_target_group.teamcity_target_group.arn
+  }
+  tags = local.tags
+}
+
+# #######################################
+# # TeamCity Aurora Serverless Database #
+# #######################################
+# Subnet group
+resource "aws_db_subnet_group" "teamcity_db_subnet_group" {
+  name       = "${local.name_prefix}-db-subnet-group"
+  subnet_ids = var.service_subnets
+  tags       = local.tags
+}
+
+# # RDS instance with Aurora serverless engine
+resource "aws_rds_cluster" "teamcity_db_cluster" {
+  #count = var.database_connection_string == null ? var.teamcity_instance_count : 0
+
+  cluster_identifier   = "teamcity-cluster"
+  engine               = "aurora-postgresql"
+  engine_mode          = "provisioned"
+  engine_version       = "16.6"
+  database_name        = "teamcity"
+  master_username      = "teamcity"
+  master_password      = "teamcity2025"
+  storage_encrypted    = true
+  db_subnet_group_name = aws_db_subnet_group.teamcity_db_subnet_group.id
+  vpc_security_group_ids = [
+    aws_security_group.teamcity_db_sg.id
+  ]
+
+  serverlessv2_scaling_configuration {
+    max_capacity             = 1.0
+    min_capacity             = 0.0
+    seconds_until_auto_pause = 3600
+  }
+}
path: modules/teamcity/main.tf
user: login: github-advanced-security[bot]
body: ## Ensure RDS cluster has IAM authentication enabled

Ensure RDS cluster has IAM authentication enabled

[Show more details](https://github.com/aws-games/cloud-game-development-toolkit/security/code-scanning/825)
created_at: 2025-02-14T19:33:06Z
author_association: NONE
diff_hunk: @@ -0,0 +1,461 @@
+###################################
+# ECS Cluster for TeamCity Server #
+###################################
+
+resource "aws_ecs_cluster" "teamcity_cluster" {
+  name = "${local.name_prefix}-cluster"
+
+  setting {
+    name  = "containerInsights"
+    value = "enabled"
+  }
+
+  tags = local.tags
+}
+
+# TeamCity Task Definition
+resource "aws_ecs_task_definition" "teamcity_task_definition" {
+  family                   = var.name
+  requires_compatibilities = ["FARGATE"]
+  network_mode             = "awsvpc"
+  cpu                      = var.container_cpu
+  memory                   = var.container_memory
+
+  container_definitions = jsonencode([
+    {
+      name      = var.container_name
+      image     = local.image
+      cpu       = var.container_cpu
+      memory    = var.container_memory
+      essential = true
+
+      portMappings = [
+        {
+          containerPort = var.container_port
+          hostPort      = var.container_port
+        }
+      ]
+      logConfiguration = {
+        logDriver = "awslogs"
+        options = {
+          awslogs-group         = aws_cloudwatch_log_group.teamcity_log_group.name
+          awslogs-region        = data.aws_region.current.name
+          awslogs-stream-prefix = "[APP]"
+        }
+      }
+
+      mountPoints = [
+        {
+          sourceVolume  = "teamcity_data",
+          containerPath = "/data/teamcity_server/datadir"
+          readOnly      = false
+        }
+      ]
+
+      # Set environment variables for database connection string
+      environment = [
+        {
+          name  = "TEAMCITY_DB_URL"
+          value = var.database_connection_string
+        },
+        {
+          name  = "TEAMCITY_DB_USER"
+          value = var.database_master_username
+        },
+        {
+          name  = "TEAMCITY_DB_PASSWORD"
+          value = var.database_master_password
+        },
+        # {
+        #   name  = "TEAMCITY_LOGS_PATH"
+        #   value = "/data/teamcity_server/logs"
+        # },
+        {
+          name  = "TEAMCITY_DATA_PATH"
+          value = "/data/teamcity_server/datadir"
+        }
+      ]
+    }
+  ])
+  tags = {
+    Name = var.name
+  }
+  task_role_arn      = aws_iam_role.teamcity_default_role.arn
+  execution_role_arn = aws_iam_role.teamcity_task_execution_role.arn
+
+  volume {
+    name = "teamcity_data"
+    efs_volume_configuration {
+      file_system_id          = aws_efs_file_system.teamcity_efs_file_system.id
+      transit_encryption      = "ENABLED"
+      transit_encryption_port = 2999
+      authorization_config {
+        access_point_id = aws_efs_access_point.teamcity_efs_data_access_point.id
+        iam             = "ENABLED"
+      }
+    }
+  }
+}
+
+# ECS Service
+resource "aws_ecs_service" "teamcity" {
+  name                   = local.name_prefix
+  cluster                = aws_ecs_cluster.teamcity_cluster.name
+  task_definition        = aws_ecs_task_definition.teamcity_task_definition.arn
+  launch_type            = "FARGATE"
+  desired_count          = 1     #TODO: make this configurable
+  force_new_deployment   = true  #TODO: make this configurable
+  enable_execute_command = true  #TODO: make this configurable
+  wait_for_steady_state  = false #TODO: make this configurable
+
+  network_configuration {
+    subnets         = var.service_subnets
+    security_groups = [aws_security_group.teamcity_service_sg.id]
+  }
+  load_balancer {
+    target_group_arn = aws_lb_target_group.teamcity_target_group.arn
+    container_name   = var.container_name
+    container_port   = var.container_port
+  }
+
+  tags = local.tags
+}
+
+# TeamCity service security group
+resource "aws_security_group" "teamcity_service_sg" {
+  name        = "${local.name_prefix}-service-sg"
+  vpc_id      = var.vpc_id
+  description = "TeamCity service security group"
+  tags        = local.tags
+}
+
+# TeamCity EFS security group
+resource "aws_security_group" "teamcity_efs_sg" {
+  name        = "${local.name_prefix}-efs-sg"
+  description = "TeamCity EFS mount target security group"
+  vpc_id      = var.vpc_id
+  tags        = local.tags
+}
+
+# Ingress rule for NFS traffic from service to EFS
+resource "aws_vpc_security_group_ingress_rule" "service_efs" {
+  security_group_id            = aws_security_group.teamcity_efs_sg.id
+  referenced_security_group_id = aws_security_group.teamcity_service_sg.id
+  description                  = "Allow inbound access from TeamCity service containers to EFS"
+  ip_protocol                  = "TCP"
+  from_port                    = 2049
+  to_port                      = 2049
+}
+
+# Ingress rule for NFS traffic from EFS to service
+resource "aws_vpc_security_group_ingress_rule" "efs_service" {
+  security_group_id            = aws_security_group.teamcity_service_sg.id
+  referenced_security_group_id = aws_security_group.teamcity_efs_sg.id
+  description                  = "Allow inbound access from EFS to TeamCity service containers"
+  ip_protocol                  = "TCP"
+  from_port                    = 2049
+  to_port                      = 2049
+}
+
+# TeamCity Aurora Serverless PostgreSQL security group
+resource "aws_security_group" "teamcity_db_sg" {
+  name        = "${local.name_prefix}-db-sg"
+  description = "TeamCity DB security group"
+  vpc_id      = var.vpc_id
+  tags        = local.tags
+}
+
+# Ingress rule for PostgreSQL from service to database cluster
+resource "aws_vpc_security_group_ingress_rule" "service_db" {
+  security_group_id            = aws_security_group.teamcity_db_sg.id
+  referenced_security_group_id = aws_security_group.teamcity_service_sg.id
+  description                  = "Allow inbound access from TeamCity service containers to DB"
+  ip_protocol                  = "TCP"
+  from_port                    = 5432
+  to_port                      = 5432
+}
+
+# Ingress rule for PostgreSQL from database cluster to service
+resource "aws_vpc_security_group_ingress_rule" "db_service" {
+  security_group_id            = aws_security_group.teamcity_service_sg.id
+  referenced_security_group_id = aws_security_group.teamcity_db_sg.id
+  description                  = "Allow inbound access from DB to TeamCity service containers"
+  ip_protocol                  = "TCP"
+  from_port                    = 5432
+  to_port                      = 5432
+}
+
+# Egress rule for database to let all traffic out
+resource "aws_vpc_security_group_egress_rule" "db_outbound" {
+  security_group_id = aws_security_group.teamcity_db_sg.id
+  description       = "Allow outbound access from DB to all"
+  cidr_ipv4         = "0.0.0.0/0"
+  ip_protocol       = "-1"
+}
+
+# TeamCity ALB security group
+resource "aws_security_group" "teamcity_alb_sg" {
+  name        = "${local.name_prefix}-alb-sg"
+  vpc_id      = var.vpc_id
+  description = "TeamCity ALB security group"
+  tags        = local.tags
+}
+
+# Ingress rule for HTTP traffic from ALB to service
+resource "aws_vpc_security_group_ingress_rule" "service_inbound_alb" {
+  security_group_id            = aws_security_group.teamcity_service_sg.id
+  referenced_security_group_id = aws_security_group.teamcity_alb_sg.id
+  description                  = "Allow inbound HTTP traffic from ALB to service containers"
+  from_port                    = var.container_port
+  to_port                      = var.container_port
+  ip_protocol                  = "TCP"
+}
+
+# Egress rule for HTTP traffic from ALB to service
+resource "aws_vpc_security_group_egress_rule" "service_outbound_alb" {
+  security_group_id            = aws_security_group.teamcity_alb_sg.id
+  referenced_security_group_id = aws_security_group.teamcity_service_sg.id
+  description                  = "Allow outbound HTTP traffic from ALB to service containers"
+  from_port                    = var.container_port
+  to_port                      = var.container_port
+  ip_protocol                  = "TCP"
+}
+
+# Grant TeamCity service access to internet
+resource "aws_vpc_security_group_egress_rule" "internet_outbound" {
+  security_group_id = aws_security_group.teamcity_service_sg.id
+  description       = "Allow outbound internet access from TeamCity service containers"
+  cidr_ipv4         = "0.0.0.0/0"
+  ip_protocol       = "-1"
+}
+
+#############################################
+# IAM Roles for TeamCity Module
+#############################################
+
+data "aws_iam_policy_document" "ecs_tasks_trust_relationship" {
+  statement {
+    effect  = "Allow"
+    actions = ["sts:AssumeRole"]
+    principals {
+      type        = "Service"
+      identifiers = ["ecs-tasks.amazonaws.com"]
+    }
+  }
+}
+
+data "aws_iam_policy_document" "teamcity_default_policy" {
+  # ECS
+  statement {
+    sid    = "ECSExec"
+    effect = "Allow"
+    actions = [
+      "ssmmessages:OpenDataChannel",
+      "ssmmessages:OpenControlChannel",
+      "ssmmessages:CreateDataChannel",
+      "ssmmessages:CreateControlChannel",
+    ]
+    resources = [
+      "*"
+    ]
+  }
+
+  #grant the task necessary EFS permissions to be able to modify directories
+  statement {
+    sid    = "EFS"
+    effect = "Allow"
+    actions = [
+      "elasticfilesystem:ClientWrite",
+      "elasticfilesystem:ClientRootAccess",
+      "elasticfilesystem:ClientMount"
+    ]
+    resources = [
+      aws_efs_file_system.teamcity_efs_file_system.arn
+    ]
+    # condition {
+    #   test     = "StringEquals"
+    #   variable = "elasticfilesystem:AccessPointArn"
+    #   values = [
+    #     aws_efs_access_point.teamcity_efs_data_access_point.arn,
+    #     aws_efs_access_point.teamcity_efs_logs_access_point.arn
+    #   ]
+    # }
+  }
+}
+
+resource "aws_iam_policy" "teamcity_default_policy" {
+  name        = "teamcity-default-policy"
+  description = "Policy granting permissions for Unreal Horde."
+  policy      = data.aws_iam_policy_document.teamcity_default_policy.json
+}
+
+resource "aws_iam_role" "teamcity_default_role" {
+  name               = "teamcity-default-role"
+  description        = "Default role for TeamCity ECS task."
+  assume_role_policy = data.aws_iam_policy_document.ecs_tasks_trust_relationship.json
+  managed_policy_arns = [
+    aws_iam_policy.teamcity_default_policy.arn
+  ]
+
+  tags = local.tags
+}
+resource "aws_iam_role" "teamcity_task_execution_role" {
+  name = "teamcity-task-execution-role"
+
+  assume_role_policy  = data.aws_iam_policy_document.ecs_tasks_trust_relationship.json
+  managed_policy_arns = ["arn:aws:iam::aws:policy/service-role/AmazonECSTaskExecutionRolePolicy"]
+}
+
+# CloudWatch Logs
+resource "aws_cloudwatch_log_group" "teamcity_log_group" {
+  #checkov:skip=CKV_AWS_158: KMS Encryption disabled by default
+  name              = "${local.name_prefix}-log-group"
+  retention_in_days = var.teamcity_cloudwatch_log_retention_in_days
+  tags              = local.tags
+}
+
+# Load Balancer for TeamCity Service
+resource "aws_lb" "teamcity_external_lb" {
+  name                       = "${local.name_prefix}-lb"
+  security_groups            = [aws_security_group.teamcity_alb_sg.id]
+  load_balancer_type         = "application"
+  internal                   = false
+  subnets                    = var.alb_subnets
+  drop_invalid_header_fields = true
+  tags                       = local.tags
+}
+
+# TeamCity target group for ALB
+resource "aws_lb_target_group" "teamcity_target_group" {
+  name        = "${local.name_prefix}-tg"
+  port        = var.container_port
+  protocol    = "HTTP"
+  vpc_id      = var.vpc_id
+  target_type = "ip"
+
+  health_check {
+    path                = "/healthCheck/healthy"
+    interval            = 30
+    timeout             = 5
+    healthy_threshold   = 5
+    unhealthy_threshold = 2
+    port                = var.container_port
+    protocol            = "HTTP"
+    matcher             = "200"
+  }
+
+  tags = local.tags
+}
+
+# ALB HTTPS Listener
+resource "aws_lb_listener" "teamcity_listener" {
+  load_balancer_arn = aws_lb.teamcity_external_lb.arn
+  port              = "443"
+  protocol          = "HTTPS"
+  ssl_policy        = "ELBSecurityPolicy-TLS13-1-2-2021-06"
+  certificate_arn   = var.alb_certificate_arn
+  default_action {
+    type             = "forward"
+    target_group_arn = aws_lb_target_group.teamcity_target_group.arn
+  }
+  tags = local.tags
+}
+
+# #######################################
+# # TeamCity Aurora Serverless Database #
+# #######################################
+# Subnet group
+resource "aws_db_subnet_group" "teamcity_db_subnet_group" {
+  name       = "${local.name_prefix}-db-subnet-group"
+  subnet_ids = var.service_subnets
+  tags       = local.tags
+}
+
+# # RDS instance with Aurora serverless engine
+resource "aws_rds_cluster" "teamcity_db_cluster" {
+  #count = var.database_connection_string == null ? var.teamcity_instance_count : 0
+
+  cluster_identifier   = "teamcity-cluster"
+  engine               = "aurora-postgresql"
+  engine_mode          = "provisioned"
+  engine_version       = "16.6"
+  database_name        = "teamcity"
+  master_username      = "teamcity"
+  master_password      = "teamcity2025"
+  storage_encrypted    = true
+  db_subnet_group_name = aws_db_subnet_group.teamcity_db_subnet_group.id
+  vpc_security_group_ids = [
+    aws_security_group.teamcity_db_sg.id
+  ]
+
+  serverlessv2_scaling_configuration {
+    max_capacity             = 1.0
+    min_capacity             = 0.0
+    seconds_until_auto_pause = 3600
+  }
+}
path: modules/teamcity/main.tf
user: login: github-advanced-security[bot]
body: ## Ensure RDS cluster configured to copy tags to snapshots

Ensure RDS cluster configured to copy tags to snapshots

[Show more details](https://github.com/aws-games/cloud-game-development-toolkit/security/code-scanning/826)
created_at: 2025-02-14T19:33:06Z
author_association: NONE
diff_hunk: @@ -0,0 +1,461 @@
+###################################
+# ECS Cluster for TeamCity Server #
+###################################
+
+resource "aws_ecs_cluster" "teamcity_cluster" {
+  name = "${local.name_prefix}-cluster"
+
+  setting {
+    name  = "containerInsights"
+    value = "enabled"
+  }
+
+  tags = local.tags
+}
+
+# TeamCity Task Definition
+resource "aws_ecs_task_definition" "teamcity_task_definition" {
+  family                   = var.name
+  requires_compatibilities = ["FARGATE"]
+  network_mode             = "awsvpc"
+  cpu                      = var.container_cpu
+  memory                   = var.container_memory
+
+  container_definitions = jsonencode([
+    {
+      name      = var.container_name
+      image     = local.image
+      cpu       = var.container_cpu
+      memory    = var.container_memory
+      essential = true
+
+      portMappings = [
+        {
+          containerPort = var.container_port
+          hostPort      = var.container_port
+        }
+      ]
+      logConfiguration = {
+        logDriver = "awslogs"
+        options = {
+          awslogs-group         = aws_cloudwatch_log_group.teamcity_log_group.name
+          awslogs-region        = data.aws_region.current.name
+          awslogs-stream-prefix = "[APP]"
+        }
+      }
+
+      mountPoints = [
+        {
+          sourceVolume  = "teamcity_data",
+          containerPath = "/data/teamcity_server/datadir"
+          readOnly      = false
+        }
+      ]
+
+      # Set environment variables for database connection string
+      environment = [
+        {
+          name  = "TEAMCITY_DB_URL"
+          value = var.database_connection_string
+        },
+        {
+          name  = "TEAMCITY_DB_USER"
+          value = var.database_master_username
+        },
+        {
+          name  = "TEAMCITY_DB_PASSWORD"
+          value = var.database_master_password
+        },
+        # {
+        #   name  = "TEAMCITY_LOGS_PATH"
+        #   value = "/data/teamcity_server/logs"
+        # },
+        {
+          name  = "TEAMCITY_DATA_PATH"
+          value = "/data/teamcity_server/datadir"
+        }
+      ]
+    }
+  ])
+  tags = {
+    Name = var.name
+  }
+  task_role_arn      = aws_iam_role.teamcity_default_role.arn
+  execution_role_arn = aws_iam_role.teamcity_task_execution_role.arn
+
+  volume {
+    name = "teamcity_data"
+    efs_volume_configuration {
+      file_system_id          = aws_efs_file_system.teamcity_efs_file_system.id
+      transit_encryption      = "ENABLED"
+      transit_encryption_port = 2999
+      authorization_config {
+        access_point_id = aws_efs_access_point.teamcity_efs_data_access_point.id
+        iam             = "ENABLED"
+      }
+    }
+  }
+}
+
+# ECS Service
+resource "aws_ecs_service" "teamcity" {
+  name                   = local.name_prefix
+  cluster                = aws_ecs_cluster.teamcity_cluster.name
+  task_definition        = aws_ecs_task_definition.teamcity_task_definition.arn
+  launch_type            = "FARGATE"
+  desired_count          = 1     #TODO: make this configurable
+  force_new_deployment   = true  #TODO: make this configurable
+  enable_execute_command = true  #TODO: make this configurable
+  wait_for_steady_state  = false #TODO: make this configurable
+
+  network_configuration {
+    subnets         = var.service_subnets
+    security_groups = [aws_security_group.teamcity_service_sg.id]
+  }
+  load_balancer {
+    target_group_arn = aws_lb_target_group.teamcity_target_group.arn
+    container_name   = var.container_name
+    container_port   = var.container_port
+  }
+
+  tags = local.tags
+}
+
+# TeamCity service security group
+resource "aws_security_group" "teamcity_service_sg" {
+  name        = "${local.name_prefix}-service-sg"
+  vpc_id      = var.vpc_id
+  description = "TeamCity service security group"
+  tags        = local.tags
+}
+
+# TeamCity EFS security group
+resource "aws_security_group" "teamcity_efs_sg" {
+  name        = "${local.name_prefix}-efs-sg"
+  description = "TeamCity EFS mount target security group"
+  vpc_id      = var.vpc_id
+  tags        = local.tags
+}
+
+# Ingress rule for NFS traffic from service to EFS
+resource "aws_vpc_security_group_ingress_rule" "service_efs" {
+  security_group_id            = aws_security_group.teamcity_efs_sg.id
+  referenced_security_group_id = aws_security_group.teamcity_service_sg.id
+  description                  = "Allow inbound access from TeamCity service containers to EFS"
+  ip_protocol                  = "TCP"
+  from_port                    = 2049
+  to_port                      = 2049
+}
+
+# Ingress rule for NFS traffic from EFS to service
+resource "aws_vpc_security_group_ingress_rule" "efs_service" {
+  security_group_id            = aws_security_group.teamcity_service_sg.id
+  referenced_security_group_id = aws_security_group.teamcity_efs_sg.id
+  description                  = "Allow inbound access from EFS to TeamCity service containers"
+  ip_protocol                  = "TCP"
+  from_port                    = 2049
+  to_port                      = 2049
+}
+
+# TeamCity Aurora Serverless PostgreSQL security group
+resource "aws_security_group" "teamcity_db_sg" {
+  name        = "${local.name_prefix}-db-sg"
+  description = "TeamCity DB security group"
+  vpc_id      = var.vpc_id
+  tags        = local.tags
+}
+
+# Ingress rule for PostgreSQL from service to database cluster
+resource "aws_vpc_security_group_ingress_rule" "service_db" {
+  security_group_id            = aws_security_group.teamcity_db_sg.id
+  referenced_security_group_id = aws_security_group.teamcity_service_sg.id
+  description                  = "Allow inbound access from TeamCity service containers to DB"
+  ip_protocol                  = "TCP"
+  from_port                    = 5432
+  to_port                      = 5432
+}
+
+# Ingress rule for PostgreSQL from database cluster to service
+resource "aws_vpc_security_group_ingress_rule" "db_service" {
+  security_group_id            = aws_security_group.teamcity_service_sg.id
+  referenced_security_group_id = aws_security_group.teamcity_db_sg.id
+  description                  = "Allow inbound access from DB to TeamCity service containers"
+  ip_protocol                  = "TCP"
+  from_port                    = 5432
+  to_port                      = 5432
+}
+
+# Egress rule for database to let all traffic out
+resource "aws_vpc_security_group_egress_rule" "db_outbound" {
+  security_group_id = aws_security_group.teamcity_db_sg.id
+  description       = "Allow outbound access from DB to all"
+  cidr_ipv4         = "0.0.0.0/0"
+  ip_protocol       = "-1"
+}
+
+# TeamCity ALB security group
+resource "aws_security_group" "teamcity_alb_sg" {
+  name        = "${local.name_prefix}-alb-sg"
+  vpc_id      = var.vpc_id
+  description = "TeamCity ALB security group"
+  tags        = local.tags
+}
+
+# Ingress rule for HTTP traffic from ALB to service
+resource "aws_vpc_security_group_ingress_rule" "service_inbound_alb" {
+  security_group_id            = aws_security_group.teamcity_service_sg.id
+  referenced_security_group_id = aws_security_group.teamcity_alb_sg.id
+  description                  = "Allow inbound HTTP traffic from ALB to service containers"
+  from_port                    = var.container_port
+  to_port                      = var.container_port
+  ip_protocol                  = "TCP"
+}
+
+# Egress rule for HTTP traffic from ALB to service
+resource "aws_vpc_security_group_egress_rule" "service_outbound_alb" {
+  security_group_id            = aws_security_group.teamcity_alb_sg.id
+  referenced_security_group_id = aws_security_group.teamcity_service_sg.id
+  description                  = "Allow outbound HTTP traffic from ALB to service containers"
+  from_port                    = var.container_port
+  to_port                      = var.container_port
+  ip_protocol                  = "TCP"
+}
+
+# Grant TeamCity service access to internet
+resource "aws_vpc_security_group_egress_rule" "internet_outbound" {
+  security_group_id = aws_security_group.teamcity_service_sg.id
+  description       = "Allow outbound internet access from TeamCity service containers"
+  cidr_ipv4         = "0.0.0.0/0"
+  ip_protocol       = "-1"
+}
+
+#############################################
+# IAM Roles for TeamCity Module
+#############################################
+
+data "aws_iam_policy_document" "ecs_tasks_trust_relationship" {
+  statement {
+    effect  = "Allow"
+    actions = ["sts:AssumeRole"]
+    principals {
+      type        = "Service"
+      identifiers = ["ecs-tasks.amazonaws.com"]
+    }
+  }
+}
+
+data "aws_iam_policy_document" "teamcity_default_policy" {
+  # ECS
+  statement {
+    sid    = "ECSExec"
+    effect = "Allow"
+    actions = [
+      "ssmmessages:OpenDataChannel",
+      "ssmmessages:OpenControlChannel",
+      "ssmmessages:CreateDataChannel",
+      "ssmmessages:CreateControlChannel",
+    ]
+    resources = [
+      "*"
+    ]
+  }
+
+  #grant the task necessary EFS permissions to be able to modify directories
+  statement {
+    sid    = "EFS"
+    effect = "Allow"
+    actions = [
+      "elasticfilesystem:ClientWrite",
+      "elasticfilesystem:ClientRootAccess",
+      "elasticfilesystem:ClientMount"
+    ]
+    resources = [
+      aws_efs_file_system.teamcity_efs_file_system.arn
+    ]
+    # condition {
+    #   test     = "StringEquals"
+    #   variable = "elasticfilesystem:AccessPointArn"
+    #   values = [
+    #     aws_efs_access_point.teamcity_efs_data_access_point.arn,
+    #     aws_efs_access_point.teamcity_efs_logs_access_point.arn
+    #   ]
+    # }
+  }
+}
+
+resource "aws_iam_policy" "teamcity_default_policy" {
+  name        = "teamcity-default-policy"
+  description = "Policy granting permissions for Unreal Horde."
+  policy      = data.aws_iam_policy_document.teamcity_default_policy.json
+}
+
+resource "aws_iam_role" "teamcity_default_role" {
+  name               = "teamcity-default-role"
+  description        = "Default role for TeamCity ECS task."
+  assume_role_policy = data.aws_iam_policy_document.ecs_tasks_trust_relationship.json
+  managed_policy_arns = [
+    aws_iam_policy.teamcity_default_policy.arn
+  ]
+
+  tags = local.tags
+}
+resource "aws_iam_role" "teamcity_task_execution_role" {
+  name = "teamcity-task-execution-role"
+
+  assume_role_policy  = data.aws_iam_policy_document.ecs_tasks_trust_relationship.json
+  managed_policy_arns = ["arn:aws:iam::aws:policy/service-role/AmazonECSTaskExecutionRolePolicy"]
+}
+
+# CloudWatch Logs
+resource "aws_cloudwatch_log_group" "teamcity_log_group" {
+  #checkov:skip=CKV_AWS_158: KMS Encryption disabled by default
+  name              = "${local.name_prefix}-log-group"
+  retention_in_days = var.teamcity_cloudwatch_log_retention_in_days
+  tags              = local.tags
+}
+
+# Load Balancer for TeamCity Service
+resource "aws_lb" "teamcity_external_lb" {
+  name                       = "${local.name_prefix}-lb"
+  security_groups            = [aws_security_group.teamcity_alb_sg.id]
+  load_balancer_type         = "application"
+  internal                   = false
+  subnets                    = var.alb_subnets
+  drop_invalid_header_fields = true
+  tags                       = local.tags
+}
+
+# TeamCity target group for ALB
+resource "aws_lb_target_group" "teamcity_target_group" {
+  name        = "${local.name_prefix}-tg"
+  port        = var.container_port
+  protocol    = "HTTP"
+  vpc_id      = var.vpc_id
+  target_type = "ip"
+
+  health_check {
+    path                = "/healthCheck/healthy"
+    interval            = 30
+    timeout             = 5
+    healthy_threshold   = 5
+    unhealthy_threshold = 2
+    port                = var.container_port
+    protocol            = "HTTP"
+    matcher             = "200"
+  }
+
+  tags = local.tags
+}
+
+# ALB HTTPS Listener
+resource "aws_lb_listener" "teamcity_listener" {
+  load_balancer_arn = aws_lb.teamcity_external_lb.arn
+  port              = "443"
+  protocol          = "HTTPS"
+  ssl_policy        = "ELBSecurityPolicy-TLS13-1-2-2021-06"
+  certificate_arn   = var.alb_certificate_arn
+  default_action {
+    type             = "forward"
+    target_group_arn = aws_lb_target_group.teamcity_target_group.arn
+  }
+  tags = local.tags
+}
+
+# #######################################
+# # TeamCity Aurora Serverless Database #
+# #######################################
+# Subnet group
+resource "aws_db_subnet_group" "teamcity_db_subnet_group" {
+  name       = "${local.name_prefix}-db-subnet-group"
+  subnet_ids = var.service_subnets
+  tags       = local.tags
+}
+
+# # RDS instance with Aurora serverless engine
+resource "aws_rds_cluster" "teamcity_db_cluster" {
+  #count = var.database_connection_string == null ? var.teamcity_instance_count : 0
+
+  cluster_identifier   = "teamcity-cluster"
+  engine               = "aurora-postgresql"
+  engine_mode          = "provisioned"
+  engine_version       = "16.6"
+  database_name        = "teamcity"
+  master_username      = "teamcity"
+  master_password      = "teamcity2025"
+  storage_encrypted    = true
+  db_subnet_group_name = aws_db_subnet_group.teamcity_db_subnet_group.id
+  vpc_security_group_ids = [
+    aws_security_group.teamcity_db_sg.id
+  ]
+
+  serverlessv2_scaling_configuration {
+    max_capacity             = 1.0
+    min_capacity             = 0.0
+    seconds_until_auto_pause = 3600
+  }
+}
path: modules/teamcity/main.tf
user: login: github-advanced-security[bot]
body: ## Ensure that RDS clusters have deletion protection enabled

Ensure that RDS clusters have deletion protection enabled

[Show more details](https://github.com/aws-games/cloud-game-development-toolkit/security/code-scanning/827)
created_at: 2025-02-14T19:33:06Z
author_association: NONE
diff_hunk: @@ -0,0 +1,461 @@
+###################################
+# ECS Cluster for TeamCity Server #
+###################################
+
+resource "aws_ecs_cluster" "teamcity_cluster" {
+  name = "${local.name_prefix}-cluster"
+
+  setting {
+    name  = "containerInsights"
+    value = "enabled"
+  }
+
+  tags = local.tags
+}
+
+# TeamCity Task Definition
+resource "aws_ecs_task_definition" "teamcity_task_definition" {
+  family                   = var.name
+  requires_compatibilities = ["FARGATE"]
+  network_mode             = "awsvpc"
+  cpu                      = var.container_cpu
+  memory                   = var.container_memory
+
+  container_definitions = jsonencode([
+    {
+      name      = var.container_name
+      image     = local.image
+      cpu       = var.container_cpu
+      memory    = var.container_memory
+      essential = true
+
+      portMappings = [
+        {
+          containerPort = var.container_port
+          hostPort      = var.container_port
+        }
+      ]
+      logConfiguration = {
+        logDriver = "awslogs"
+        options = {
+          awslogs-group         = aws_cloudwatch_log_group.teamcity_log_group.name
+          awslogs-region        = data.aws_region.current.name
+          awslogs-stream-prefix = "[APP]"
+        }
+      }
+
+      mountPoints = [
+        {
+          sourceVolume  = "teamcity_data",
+          containerPath = "/data/teamcity_server/datadir"
+          readOnly      = false
+        }
+      ]
+
+      # Set environment variables for database connection string
+      environment = [
+        {
+          name  = "TEAMCITY_DB_URL"
+          value = var.database_connection_string
+        },
+        {
+          name  = "TEAMCITY_DB_USER"
+          value = var.database_master_username
+        },
+        {
+          name  = "TEAMCITY_DB_PASSWORD"
+          value = var.database_master_password
+        },
+        # {
+        #   name  = "TEAMCITY_LOGS_PATH"
+        #   value = "/data/teamcity_server/logs"
+        # },
+        {
+          name  = "TEAMCITY_DATA_PATH"
+          value = "/data/teamcity_server/datadir"
+        }
+      ]
+    }
+  ])
+  tags = {
+    Name = var.name
+  }
+  task_role_arn      = aws_iam_role.teamcity_default_role.arn
+  execution_role_arn = aws_iam_role.teamcity_task_execution_role.arn
+
+  volume {
+    name = "teamcity_data"
+    efs_volume_configuration {
+      file_system_id          = aws_efs_file_system.teamcity_efs_file_system.id
+      transit_encryption      = "ENABLED"
+      transit_encryption_port = 2999
+      authorization_config {
+        access_point_id = aws_efs_access_point.teamcity_efs_data_access_point.id
+        iam             = "ENABLED"
+      }
+    }
+  }
+}
+
+# ECS Service
+resource "aws_ecs_service" "teamcity" {
+  name                   = local.name_prefix
+  cluster                = aws_ecs_cluster.teamcity_cluster.name
+  task_definition        = aws_ecs_task_definition.teamcity_task_definition.arn
+  launch_type            = "FARGATE"
+  desired_count          = 1     #TODO: make this configurable
+  force_new_deployment   = true  #TODO: make this configurable
+  enable_execute_command = true  #TODO: make this configurable
+  wait_for_steady_state  = false #TODO: make this configurable
+
+  network_configuration {
+    subnets         = var.service_subnets
+    security_groups = [aws_security_group.teamcity_service_sg.id]
+  }
+  load_balancer {
+    target_group_arn = aws_lb_target_group.teamcity_target_group.arn
+    container_name   = var.container_name
+    container_port   = var.container_port
+  }
+
+  tags = local.tags
+}
+
+# TeamCity service security group
+resource "aws_security_group" "teamcity_service_sg" {
+  name        = "${local.name_prefix}-service-sg"
+  vpc_id      = var.vpc_id
+  description = "TeamCity service security group"
+  tags        = local.tags
+}
+
+# TeamCity EFS security group
+resource "aws_security_group" "teamcity_efs_sg" {
+  name        = "${local.name_prefix}-efs-sg"
+  description = "TeamCity EFS mount target security group"
+  vpc_id      = var.vpc_id
+  tags        = local.tags
+}
+
+# Ingress rule for NFS traffic from service to EFS
+resource "aws_vpc_security_group_ingress_rule" "service_efs" {
+  security_group_id            = aws_security_group.teamcity_efs_sg.id
+  referenced_security_group_id = aws_security_group.teamcity_service_sg.id
+  description                  = "Allow inbound access from TeamCity service containers to EFS"
+  ip_protocol                  = "TCP"
+  from_port                    = 2049
+  to_port                      = 2049
+}
+
+# Ingress rule for NFS traffic from EFS to service
+resource "aws_vpc_security_group_ingress_rule" "efs_service" {
+  security_group_id            = aws_security_group.teamcity_service_sg.id
+  referenced_security_group_id = aws_security_group.teamcity_efs_sg.id
+  description                  = "Allow inbound access from EFS to TeamCity service containers"
+  ip_protocol                  = "TCP"
+  from_port                    = 2049
+  to_port                      = 2049
+}
+
+# TeamCity Aurora Serverless PostgreSQL security group
+resource "aws_security_group" "teamcity_db_sg" {
+  name        = "${local.name_prefix}-db-sg"
+  description = "TeamCity DB security group"
+  vpc_id      = var.vpc_id
+  tags        = local.tags
+}
+
+# Ingress rule for PostgreSQL from service to database cluster
+resource "aws_vpc_security_group_ingress_rule" "service_db" {
+  security_group_id            = aws_security_group.teamcity_db_sg.id
+  referenced_security_group_id = aws_security_group.teamcity_service_sg.id
+  description                  = "Allow inbound access from TeamCity service containers to DB"
+  ip_protocol                  = "TCP"
+  from_port                    = 5432
+  to_port                      = 5432
+}
+
+# Ingress rule for PostgreSQL from database cluster to service
+resource "aws_vpc_security_group_ingress_rule" "db_service" {
+  security_group_id            = aws_security_group.teamcity_service_sg.id
+  referenced_security_group_id = aws_security_group.teamcity_db_sg.id
+  description                  = "Allow inbound access from DB to TeamCity service containers"
+  ip_protocol                  = "TCP"
+  from_port                    = 5432
+  to_port                      = 5432
+}
+
+# Egress rule for database to let all traffic out
+resource "aws_vpc_security_group_egress_rule" "db_outbound" {
+  security_group_id = aws_security_group.teamcity_db_sg.id
+  description       = "Allow outbound access from DB to all"
+  cidr_ipv4         = "0.0.0.0/0"
+  ip_protocol       = "-1"
+}
+
+# TeamCity ALB security group
+resource "aws_security_group" "teamcity_alb_sg" {
+  name        = "${local.name_prefix}-alb-sg"
+  vpc_id      = var.vpc_id
+  description = "TeamCity ALB security group"
+  tags        = local.tags
+}
+
+# Ingress rule for HTTP traffic from ALB to service
+resource "aws_vpc_security_group_ingress_rule" "service_inbound_alb" {
+  security_group_id            = aws_security_group.teamcity_service_sg.id
+  referenced_security_group_id = aws_security_group.teamcity_alb_sg.id
+  description                  = "Allow inbound HTTP traffic from ALB to service containers"
+  from_port                    = var.container_port
+  to_port                      = var.container_port
+  ip_protocol                  = "TCP"
+}
+
+# Egress rule for HTTP traffic from ALB to service
+resource "aws_vpc_security_group_egress_rule" "service_outbound_alb" {
+  security_group_id            = aws_security_group.teamcity_alb_sg.id
+  referenced_security_group_id = aws_security_group.teamcity_service_sg.id
+  description                  = "Allow outbound HTTP traffic from ALB to service containers"
+  from_port                    = var.container_port
+  to_port                      = var.container_port
+  ip_protocol                  = "TCP"
+}
+
+# Grant TeamCity service access to internet
+resource "aws_vpc_security_group_egress_rule" "internet_outbound" {
+  security_group_id = aws_security_group.teamcity_service_sg.id
+  description       = "Allow outbound internet access from TeamCity service containers"
+  cidr_ipv4         = "0.0.0.0/0"
+  ip_protocol       = "-1"
+}
+
+#############################################
+# IAM Roles for TeamCity Module
+#############################################
+
+data "aws_iam_policy_document" "ecs_tasks_trust_relationship" {
+  statement {
+    effect  = "Allow"
+    actions = ["sts:AssumeRole"]
+    principals {
+      type        = "Service"
+      identifiers = ["ecs-tasks.amazonaws.com"]
+    }
+  }
+}
+
+data "aws_iam_policy_document" "teamcity_default_policy" {
+  # ECS
+  statement {
+    sid    = "ECSExec"
+    effect = "Allow"
+    actions = [
+      "ssmmessages:OpenDataChannel",
+      "ssmmessages:OpenControlChannel",
+      "ssmmessages:CreateDataChannel",
+      "ssmmessages:CreateControlChannel",
+    ]
+    resources = [
+      "*"
+    ]
+  }
+
+  #grant the task necessary EFS permissions to be able to modify directories
+  statement {
+    sid    = "EFS"
+    effect = "Allow"
+    actions = [
+      "elasticfilesystem:ClientWrite",
+      "elasticfilesystem:ClientRootAccess",
+      "elasticfilesystem:ClientMount"
+    ]
+    resources = [
+      aws_efs_file_system.teamcity_efs_file_system.arn
+    ]
+    # condition {
+    #   test     = "StringEquals"
+    #   variable = "elasticfilesystem:AccessPointArn"
+    #   values = [
+    #     aws_efs_access_point.teamcity_efs_data_access_point.arn,
+    #     aws_efs_access_point.teamcity_efs_logs_access_point.arn
+    #   ]
+    # }
+  }
+}
+
+resource "aws_iam_policy" "teamcity_default_policy" {
+  name        = "teamcity-default-policy"
+  description = "Policy granting permissions for Unreal Horde."
+  policy      = data.aws_iam_policy_document.teamcity_default_policy.json
+}
+
+resource "aws_iam_role" "teamcity_default_role" {
+  name               = "teamcity-default-role"
+  description        = "Default role for TeamCity ECS task."
+  assume_role_policy = data.aws_iam_policy_document.ecs_tasks_trust_relationship.json
+  managed_policy_arns = [
+    aws_iam_policy.teamcity_default_policy.arn
+  ]
+
+  tags = local.tags
+}
+resource "aws_iam_role" "teamcity_task_execution_role" {
+  name = "teamcity-task-execution-role"
+
+  assume_role_policy  = data.aws_iam_policy_document.ecs_tasks_trust_relationship.json
+  managed_policy_arns = ["arn:aws:iam::aws:policy/service-role/AmazonECSTaskExecutionRolePolicy"]
+}
+
+# CloudWatch Logs
+resource "aws_cloudwatch_log_group" "teamcity_log_group" {
+  #checkov:skip=CKV_AWS_158: KMS Encryption disabled by default
+  name              = "${local.name_prefix}-log-group"
+  retention_in_days = var.teamcity_cloudwatch_log_retention_in_days
+  tags              = local.tags
+}
+
+# Load Balancer for TeamCity Service
+resource "aws_lb" "teamcity_external_lb" {
+  name                       = "${local.name_prefix}-lb"
+  security_groups            = [aws_security_group.teamcity_alb_sg.id]
+  load_balancer_type         = "application"
+  internal                   = false
+  subnets                    = var.alb_subnets
+  drop_invalid_header_fields = true
+  tags                       = local.tags
+}
+
+# TeamCity target group for ALB
+resource "aws_lb_target_group" "teamcity_target_group" {
+  name        = "${local.name_prefix}-tg"
+  port        = var.container_port
+  protocol    = "HTTP"
+  vpc_id      = var.vpc_id
+  target_type = "ip"
+
+  health_check {
+    path                = "/healthCheck/healthy"
+    interval            = 30
+    timeout             = 5
+    healthy_threshold   = 5
+    unhealthy_threshold = 2
+    port                = var.container_port
+    protocol            = "HTTP"
+    matcher             = "200"
+  }
+
+  tags = local.tags
+}
+
+# ALB HTTPS Listener
+resource "aws_lb_listener" "teamcity_listener" {
+  load_balancer_arn = aws_lb.teamcity_external_lb.arn
+  port              = "443"
+  protocol          = "HTTPS"
+  ssl_policy        = "ELBSecurityPolicy-TLS13-1-2-2021-06"
+  certificate_arn   = var.alb_certificate_arn
+  default_action {
+    type             = "forward"
+    target_group_arn = aws_lb_target_group.teamcity_target_group.arn
+  }
+  tags = local.tags
+}
+
+# #######################################
+# # TeamCity Aurora Serverless Database #
+# #######################################
+# Subnet group
+resource "aws_db_subnet_group" "teamcity_db_subnet_group" {
+  name       = "${local.name_prefix}-db-subnet-group"
+  subnet_ids = var.service_subnets
+  tags       = local.tags
+}
+
+# # RDS instance with Aurora serverless engine
+resource "aws_rds_cluster" "teamcity_db_cluster" {
+  #count = var.database_connection_string == null ? var.teamcity_instance_count : 0
+
+  cluster_identifier   = "teamcity-cluster"
+  engine               = "aurora-postgresql"
+  engine_mode          = "provisioned"
+  engine_version       = "16.6"
+  database_name        = "teamcity"
+  master_username      = "teamcity"
+  master_password      = "teamcity2025"
+  storage_encrypted    = true
+  db_subnet_group_name = aws_db_subnet_group.teamcity_db_subnet_group.id
+  vpc_security_group_ids = [
+    aws_security_group.teamcity_db_sg.id
+  ]
+
+  serverlessv2_scaling_configuration {
+    max_capacity             = 1.0
+    min_capacity             = 0.0
+    seconds_until_auto_pause = 3600
+  }
+}
path: modules/teamcity/main.tf
user: login: github-advanced-security[bot]
body: ## Ensure that RDS clusters has backup plan of AWS Backup

Ensure that RDS clusters has backup plan of AWS Backup

[Show more details](https://github.com/aws-games/cloud-game-development-toolkit/security/code-scanning/831)
created_at: 2025-02-14T19:33:06Z
author_association: NONE
diff_hunk: @@ -0,0 +1,461 @@
+###################################
+# ECS Cluster for TeamCity Server #
+###################################
+
+resource "aws_ecs_cluster" "teamcity_cluster" {
+  name = "${local.name_prefix}-cluster"
+
+  setting {
+    name  = "containerInsights"
+    value = "enabled"
+  }
+
+  tags = local.tags
+}
+
+# TeamCity Task Definition
+resource "aws_ecs_task_definition" "teamcity_task_definition" {
+  family                   = var.name
+  requires_compatibilities = ["FARGATE"]
+  network_mode             = "awsvpc"
+  cpu                      = var.container_cpu
+  memory                   = var.container_memory
+
+  container_definitions = jsonencode([
+    {
+      name      = var.container_name
+      image     = local.image
+      cpu       = var.container_cpu
+      memory    = var.container_memory
+      essential = true
+
+      portMappings = [
+        {
+          containerPort = var.container_port
+          hostPort      = var.container_port
+        }
+      ]
+      logConfiguration = {
+        logDriver = "awslogs"
+        options = {
+          awslogs-group         = aws_cloudwatch_log_group.teamcity_log_group.name
+          awslogs-region        = data.aws_region.current.name
+          awslogs-stream-prefix = "[APP]"
+        }
+      }
+
+      mountPoints = [
+        {
+          sourceVolume  = "teamcity_data",
+          containerPath = "/data/teamcity_server/datadir"
+          readOnly      = false
+        }
+      ]
+
+      # Set environment variables for database connection string
+      environment = [
+        {
+          name  = "TEAMCITY_DB_URL"
+          value = var.database_connection_string
+        },
+        {
+          name  = "TEAMCITY_DB_USER"
+          value = var.database_master_username
+        },
+        {
+          name  = "TEAMCITY_DB_PASSWORD"
+          value = var.database_master_password
+        },
+        # {
+        #   name  = "TEAMCITY_LOGS_PATH"
+        #   value = "/data/teamcity_server/logs"
+        # },
+        {
+          name  = "TEAMCITY_DATA_PATH"
+          value = "/data/teamcity_server/datadir"
+        }
+      ]
+    }
+  ])
+  tags = {
+    Name = var.name
+  }
+  task_role_arn      = aws_iam_role.teamcity_default_role.arn
+  execution_role_arn = aws_iam_role.teamcity_task_execution_role.arn
+
+  volume {
+    name = "teamcity_data"
+    efs_volume_configuration {
+      file_system_id          = aws_efs_file_system.teamcity_efs_file_system.id
+      transit_encryption      = "ENABLED"
+      transit_encryption_port = 2999
+      authorization_config {
+        access_point_id = aws_efs_access_point.teamcity_efs_data_access_point.id
+        iam             = "ENABLED"
+      }
+    }
+  }
+}
+
+# ECS Service
+resource "aws_ecs_service" "teamcity" {
+  name                   = local.name_prefix
+  cluster                = aws_ecs_cluster.teamcity_cluster.name
+  task_definition        = aws_ecs_task_definition.teamcity_task_definition.arn
+  launch_type            = "FARGATE"
+  desired_count          = 1     #TODO: make this configurable
+  force_new_deployment   = true  #TODO: make this configurable
+  enable_execute_command = true  #TODO: make this configurable
+  wait_for_steady_state  = false #TODO: make this configurable
+
+  network_configuration {
+    subnets         = var.service_subnets
+    security_groups = [aws_security_group.teamcity_service_sg.id]
+  }
+  load_balancer {
+    target_group_arn = aws_lb_target_group.teamcity_target_group.arn
+    container_name   = var.container_name
+    container_port   = var.container_port
+  }
+
+  tags = local.tags
+}
+
+# TeamCity service security group
+resource "aws_security_group" "teamcity_service_sg" {
+  name        = "${local.name_prefix}-service-sg"
+  vpc_id      = var.vpc_id
+  description = "TeamCity service security group"
+  tags        = local.tags
+}
+
+# TeamCity EFS security group
+resource "aws_security_group" "teamcity_efs_sg" {
+  name        = "${local.name_prefix}-efs-sg"
+  description = "TeamCity EFS mount target security group"
+  vpc_id      = var.vpc_id
+  tags        = local.tags
+}
+
+# Ingress rule for NFS traffic from service to EFS
+resource "aws_vpc_security_group_ingress_rule" "service_efs" {
+  security_group_id            = aws_security_group.teamcity_efs_sg.id
+  referenced_security_group_id = aws_security_group.teamcity_service_sg.id
+  description                  = "Allow inbound access from TeamCity service containers to EFS"
+  ip_protocol                  = "TCP"
+  from_port                    = 2049
+  to_port                      = 2049
+}
+
+# Ingress rule for NFS traffic from EFS to service
+resource "aws_vpc_security_group_ingress_rule" "efs_service" {
+  security_group_id            = aws_security_group.teamcity_service_sg.id
+  referenced_security_group_id = aws_security_group.teamcity_efs_sg.id
+  description                  = "Allow inbound access from EFS to TeamCity service containers"
+  ip_protocol                  = "TCP"
+  from_port                    = 2049
+  to_port                      = 2049
+}
+
+# TeamCity Aurora Serverless PostgreSQL security group
+resource "aws_security_group" "teamcity_db_sg" {
+  name        = "${local.name_prefix}-db-sg"
+  description = "TeamCity DB security group"
+  vpc_id      = var.vpc_id
+  tags        = local.tags
+}
+
+# Ingress rule for PostgreSQL from service to database cluster
+resource "aws_vpc_security_group_ingress_rule" "service_db" {
+  security_group_id            = aws_security_group.teamcity_db_sg.id
+  referenced_security_group_id = aws_security_group.teamcity_service_sg.id
+  description                  = "Allow inbound access from TeamCity service containers to DB"
+  ip_protocol                  = "TCP"
+  from_port                    = 5432
+  to_port                      = 5432
+}
+
+# Ingress rule for PostgreSQL from database cluster to service
+resource "aws_vpc_security_group_ingress_rule" "db_service" {
+  security_group_id            = aws_security_group.teamcity_service_sg.id
+  referenced_security_group_id = aws_security_group.teamcity_db_sg.id
+  description                  = "Allow inbound access from DB to TeamCity service containers"
+  ip_protocol                  = "TCP"
+  from_port                    = 5432
+  to_port                      = 5432
+}
+
+# Egress rule for database to let all traffic out
+resource "aws_vpc_security_group_egress_rule" "db_outbound" {
+  security_group_id = aws_security_group.teamcity_db_sg.id
+  description       = "Allow outbound access from DB to all"
+  cidr_ipv4         = "0.0.0.0/0"
+  ip_protocol       = "-1"
+}
+
+# TeamCity ALB security group
+resource "aws_security_group" "teamcity_alb_sg" {
+  name        = "${local.name_prefix}-alb-sg"
+  vpc_id      = var.vpc_id
+  description = "TeamCity ALB security group"
+  tags        = local.tags
+}
+
+# Ingress rule for HTTP traffic from ALB to service
+resource "aws_vpc_security_group_ingress_rule" "service_inbound_alb" {
+  security_group_id            = aws_security_group.teamcity_service_sg.id
+  referenced_security_group_id = aws_security_group.teamcity_alb_sg.id
+  description                  = "Allow inbound HTTP traffic from ALB to service containers"
+  from_port                    = var.container_port
+  to_port                      = var.container_port
+  ip_protocol                  = "TCP"
+}
+
+# Egress rule for HTTP traffic from ALB to service
+resource "aws_vpc_security_group_egress_rule" "service_outbound_alb" {
+  security_group_id            = aws_security_group.teamcity_alb_sg.id
+  referenced_security_group_id = aws_security_group.teamcity_service_sg.id
+  description                  = "Allow outbound HTTP traffic from ALB to service containers"
+  from_port                    = var.container_port
+  to_port                      = var.container_port
+  ip_protocol                  = "TCP"
+}
+
+# Grant TeamCity service access to internet
+resource "aws_vpc_security_group_egress_rule" "internet_outbound" {
+  security_group_id = aws_security_group.teamcity_service_sg.id
+  description       = "Allow outbound internet access from TeamCity service containers"
+  cidr_ipv4         = "0.0.0.0/0"
+  ip_protocol       = "-1"
+}
+
+#############################################
+# IAM Roles for TeamCity Module
+#############################################
+
+data "aws_iam_policy_document" "ecs_tasks_trust_relationship" {
+  statement {
+    effect  = "Allow"
+    actions = ["sts:AssumeRole"]
+    principals {
+      type        = "Service"
+      identifiers = ["ecs-tasks.amazonaws.com"]
+    }
+  }
+}
+
+data "aws_iam_policy_document" "teamcity_default_policy" {
+  # ECS
+  statement {
+    sid    = "ECSExec"
+    effect = "Allow"
+    actions = [
+      "ssmmessages:OpenDataChannel",
+      "ssmmessages:OpenControlChannel",
+      "ssmmessages:CreateDataChannel",
+      "ssmmessages:CreateControlChannel",
+    ]
+    resources = [
+      "*"
+    ]
+  }
+
+  #grant the task necessary EFS permissions to be able to modify directories
+  statement {
+    sid    = "EFS"
+    effect = "Allow"
+    actions = [
+      "elasticfilesystem:ClientWrite",
+      "elasticfilesystem:ClientRootAccess",
+      "elasticfilesystem:ClientMount"
+    ]
+    resources = [
+      aws_efs_file_system.teamcity_efs_file_system.arn
+    ]
+    # condition {
+    #   test     = "StringEquals"
+    #   variable = "elasticfilesystem:AccessPointArn"
+    #   values = [
+    #     aws_efs_access_point.teamcity_efs_data_access_point.arn,
+    #     aws_efs_access_point.teamcity_efs_logs_access_point.arn
+    #   ]
+    # }
+  }
+}
+
+resource "aws_iam_policy" "teamcity_default_policy" {
+  name        = "teamcity-default-policy"
+  description = "Policy granting permissions for Unreal Horde."
+  policy      = data.aws_iam_policy_document.teamcity_default_policy.json
+}
+
+resource "aws_iam_role" "teamcity_default_role" {
+  name               = "teamcity-default-role"
+  description        = "Default role for TeamCity ECS task."
+  assume_role_policy = data.aws_iam_policy_document.ecs_tasks_trust_relationship.json
+  managed_policy_arns = [
+    aws_iam_policy.teamcity_default_policy.arn
+  ]
+
+  tags = local.tags
+}
+resource "aws_iam_role" "teamcity_task_execution_role" {
+  name = "teamcity-task-execution-role"
+
+  assume_role_policy  = data.aws_iam_policy_document.ecs_tasks_trust_relationship.json
+  managed_policy_arns = ["arn:aws:iam::aws:policy/service-role/AmazonECSTaskExecutionRolePolicy"]
+}
+
+# CloudWatch Logs
+resource "aws_cloudwatch_log_group" "teamcity_log_group" {
+  #checkov:skip=CKV_AWS_158: KMS Encryption disabled by default
+  name              = "${local.name_prefix}-log-group"
+  retention_in_days = var.teamcity_cloudwatch_log_retention_in_days
+  tags              = local.tags
+}
+
+# Load Balancer for TeamCity Service
+resource "aws_lb" "teamcity_external_lb" {
+  name                       = "${local.name_prefix}-lb"
+  security_groups            = [aws_security_group.teamcity_alb_sg.id]
+  load_balancer_type         = "application"
+  internal                   = false
+  subnets                    = var.alb_subnets
+  drop_invalid_header_fields = true
+  tags                       = local.tags
+}
+
+# TeamCity target group for ALB
+resource "aws_lb_target_group" "teamcity_target_group" {
+  name        = "${local.name_prefix}-tg"
+  port        = var.container_port
+  protocol    = "HTTP"
+  vpc_id      = var.vpc_id
+  target_type = "ip"
+
+  health_check {
+    path                = "/healthCheck/healthy"
+    interval            = 30
+    timeout             = 5
+    healthy_threshold   = 5
+    unhealthy_threshold = 2
+    port                = var.container_port
+    protocol            = "HTTP"
+    matcher             = "200"
+  }
+
+  tags = local.tags
+}
+
+# ALB HTTPS Listener
+resource "aws_lb_listener" "teamcity_listener" {
+  load_balancer_arn = aws_lb.teamcity_external_lb.arn
+  port              = "443"
+  protocol          = "HTTPS"
+  ssl_policy        = "ELBSecurityPolicy-TLS13-1-2-2021-06"
+  certificate_arn   = var.alb_certificate_arn
+  default_action {
+    type             = "forward"
+    target_group_arn = aws_lb_target_group.teamcity_target_group.arn
+  }
+  tags = local.tags
+}
+
+# #######################################
+# # TeamCity Aurora Serverless Database #
+# #######################################
+# Subnet group
+resource "aws_db_subnet_group" "teamcity_db_subnet_group" {
+  name       = "${local.name_prefix}-db-subnet-group"
+  subnet_ids = var.service_subnets
+  tags       = local.tags
+}
+
+# # RDS instance with Aurora serverless engine
+resource "aws_rds_cluster" "teamcity_db_cluster" {
+  #count = var.database_connection_string == null ? var.teamcity_instance_count : 0
+
+  cluster_identifier   = "teamcity-cluster"
+  engine               = "aurora-postgresql"
+  engine_mode          = "provisioned"
+  engine_version       = "16.6"
+  database_name        = "teamcity"
+  master_username      = "teamcity"
+  master_password      = "teamcity2025"
+  storage_encrypted    = true
+  db_subnet_group_name = aws_db_subnet_group.teamcity_db_subnet_group.id
+  vpc_security_group_ids = [
+    aws_security_group.teamcity_db_sg.id
+  ]
+
+  serverlessv2_scaling_configuration {
+    max_capacity             = 1.0
+    min_capacity             = 0.0
+    seconds_until_auto_pause = 3600
+  }
+}
path: modules/teamcity/main.tf
user: login: github-advanced-security[bot]
body: ## Ensure Postgres RDS as aws_rds_cluster has Query Logging enabled

Ensure Postgres RDS as aws_rds_cluster has Query Logging enabled

[Show more details](https://github.com/aws-games/cloud-game-development-toolkit/security/code-scanning/832)
created_at: 2025-02-14T19:33:07Z
author_association: NONE
diff_hunk: @@ -0,0 +1,461 @@
+###################################
+# ECS Cluster for TeamCity Server #
+###################################
+
+resource "aws_ecs_cluster" "teamcity_cluster" {
+  name = "${local.name_prefix}-cluster"
+
+  setting {
+    name  = "containerInsights"
+    value = "enabled"
+  }
+
+  tags = local.tags
+}
+
+# TeamCity Task Definition
+resource "aws_ecs_task_definition" "teamcity_task_definition" {
+  family                   = var.name
+  requires_compatibilities = ["FARGATE"]
+  network_mode             = "awsvpc"
+  cpu                      = var.container_cpu
+  memory                   = var.container_memory
+
+  container_definitions = jsonencode([
+    {
+      name      = var.container_name
+      image     = local.image
+      cpu       = var.container_cpu
+      memory    = var.container_memory
+      essential = true
+
+      portMappings = [
+        {
+          containerPort = var.container_port
+          hostPort      = var.container_port
+        }
+      ]
+      logConfiguration = {
+        logDriver = "awslogs"
+        options = {
+          awslogs-group         = aws_cloudwatch_log_group.teamcity_log_group.name
+          awslogs-region        = data.aws_region.current.name
+          awslogs-stream-prefix = "[APP]"
+        }
+      }
+
+      mountPoints = [
+        {
+          sourceVolume  = "teamcity_data",
+          containerPath = "/data/teamcity_server/datadir"
+          readOnly      = false
+        }
+      ]
+
+      # Set environment variables for database connection string
+      environment = [
+        {
+          name  = "TEAMCITY_DB_URL"
+          value = var.database_connection_string
+        },
+        {
+          name  = "TEAMCITY_DB_USER"
+          value = var.database_master_username
+        },
+        {
+          name  = "TEAMCITY_DB_PASSWORD"
+          value = var.database_master_password
+        },
+        # {
+        #   name  = "TEAMCITY_LOGS_PATH"
+        #   value = "/data/teamcity_server/logs"
+        # },
+        {
+          name  = "TEAMCITY_DATA_PATH"
+          value = "/data/teamcity_server/datadir"
+        }
+      ]
+    }
+  ])
+  tags = {
+    Name = var.name
+  }
+  task_role_arn      = aws_iam_role.teamcity_default_role.arn
+  execution_role_arn = aws_iam_role.teamcity_task_execution_role.arn
+
+  volume {
+    name = "teamcity_data"
+    efs_volume_configuration {
+      file_system_id          = aws_efs_file_system.teamcity_efs_file_system.id
+      transit_encryption      = "ENABLED"
+      transit_encryption_port = 2999
+      authorization_config {
+        access_point_id = aws_efs_access_point.teamcity_efs_data_access_point.id
+        iam             = "ENABLED"
+      }
+    }
+  }
+}
+
+# ECS Service
+resource "aws_ecs_service" "teamcity" {
+  name                   = local.name_prefix
+  cluster                = aws_ecs_cluster.teamcity_cluster.name
+  task_definition        = aws_ecs_task_definition.teamcity_task_definition.arn
+  launch_type            = "FARGATE"
+  desired_count          = 1     #TODO: make this configurable
+  force_new_deployment   = true  #TODO: make this configurable
+  enable_execute_command = true  #TODO: make this configurable
+  wait_for_steady_state  = false #TODO: make this configurable
+
+  network_configuration {
+    subnets         = var.service_subnets
+    security_groups = [aws_security_group.teamcity_service_sg.id]
+  }
+  load_balancer {
+    target_group_arn = aws_lb_target_group.teamcity_target_group.arn
+    container_name   = var.container_name
+    container_port   = var.container_port
+  }
+
+  tags = local.tags
+}
+
+# TeamCity service security group
+resource "aws_security_group" "teamcity_service_sg" {
+  name        = "${local.name_prefix}-service-sg"
+  vpc_id      = var.vpc_id
+  description = "TeamCity service security group"
+  tags        = local.tags
+}
+
+# TeamCity EFS security group
+resource "aws_security_group" "teamcity_efs_sg" {
+  name        = "${local.name_prefix}-efs-sg"
+  description = "TeamCity EFS mount target security group"
+  vpc_id      = var.vpc_id
+  tags        = local.tags
+}
+
+# Ingress rule for NFS traffic from service to EFS
+resource "aws_vpc_security_group_ingress_rule" "service_efs" {
+  security_group_id            = aws_security_group.teamcity_efs_sg.id
+  referenced_security_group_id = aws_security_group.teamcity_service_sg.id
+  description                  = "Allow inbound access from TeamCity service containers to EFS"
+  ip_protocol                  = "TCP"
+  from_port                    = 2049
+  to_port                      = 2049
+}
+
+# Ingress rule for NFS traffic from EFS to service
+resource "aws_vpc_security_group_ingress_rule" "efs_service" {
+  security_group_id            = aws_security_group.teamcity_service_sg.id
+  referenced_security_group_id = aws_security_group.teamcity_efs_sg.id
+  description                  = "Allow inbound access from EFS to TeamCity service containers"
+  ip_protocol                  = "TCP"
+  from_port                    = 2049
+  to_port                      = 2049
+}
+
+# TeamCity Aurora Serverless PostgreSQL security group
+resource "aws_security_group" "teamcity_db_sg" {
+  name        = "${local.name_prefix}-db-sg"
+  description = "TeamCity DB security group"
+  vpc_id      = var.vpc_id
+  tags        = local.tags
+}
+
+# Ingress rule for PostgreSQL from service to database cluster
+resource "aws_vpc_security_group_ingress_rule" "service_db" {
+  security_group_id            = aws_security_group.teamcity_db_sg.id
+  referenced_security_group_id = aws_security_group.teamcity_service_sg.id
+  description                  = "Allow inbound access from TeamCity service containers to DB"
+  ip_protocol                  = "TCP"
+  from_port                    = 5432
+  to_port                      = 5432
+}
+
+# Ingress rule for PostgreSQL from database cluster to service
+resource "aws_vpc_security_group_ingress_rule" "db_service" {
+  security_group_id            = aws_security_group.teamcity_service_sg.id
+  referenced_security_group_id = aws_security_group.teamcity_db_sg.id
+  description                  = "Allow inbound access from DB to TeamCity service containers"
+  ip_protocol                  = "TCP"
+  from_port                    = 5432
+  to_port                      = 5432
+}
+
+# Egress rule for database to let all traffic out
+resource "aws_vpc_security_group_egress_rule" "db_outbound" {
+  security_group_id = aws_security_group.teamcity_db_sg.id
+  description       = "Allow outbound access from DB to all"
+  cidr_ipv4         = "0.0.0.0/0"
+  ip_protocol       = "-1"
+}
+
+# TeamCity ALB security group
+resource "aws_security_group" "teamcity_alb_sg" {
+  name        = "${local.name_prefix}-alb-sg"
+  vpc_id      = var.vpc_id
+  description = "TeamCity ALB security group"
+  tags        = local.tags
+}
+
+# Ingress rule for HTTP traffic from ALB to service
+resource "aws_vpc_security_group_ingress_rule" "service_inbound_alb" {
+  security_group_id            = aws_security_group.teamcity_service_sg.id
+  referenced_security_group_id = aws_security_group.teamcity_alb_sg.id
+  description                  = "Allow inbound HTTP traffic from ALB to service containers"
+  from_port                    = var.container_port
+  to_port                      = var.container_port
+  ip_protocol                  = "TCP"
+}
+
+# Egress rule for HTTP traffic from ALB to service
+resource "aws_vpc_security_group_egress_rule" "service_outbound_alb" {
+  security_group_id            = aws_security_group.teamcity_alb_sg.id
+  referenced_security_group_id = aws_security_group.teamcity_service_sg.id
+  description                  = "Allow outbound HTTP traffic from ALB to service containers"
+  from_port                    = var.container_port
+  to_port                      = var.container_port
+  ip_protocol                  = "TCP"
+}
+
+# Grant TeamCity service access to internet
+resource "aws_vpc_security_group_egress_rule" "internet_outbound" {
+  security_group_id = aws_security_group.teamcity_service_sg.id
+  description       = "Allow outbound internet access from TeamCity service containers"
+  cidr_ipv4         = "0.0.0.0/0"
+  ip_protocol       = "-1"
+}
+
+#############################################
+# IAM Roles for TeamCity Module
+#############################################
+
+data "aws_iam_policy_document" "ecs_tasks_trust_relationship" {
+  statement {
+    effect  = "Allow"
+    actions = ["sts:AssumeRole"]
+    principals {
+      type        = "Service"
+      identifiers = ["ecs-tasks.amazonaws.com"]
+    }
+  }
+}
+
+data "aws_iam_policy_document" "teamcity_default_policy" {
+  # ECS
+  statement {
+    sid    = "ECSExec"
+    effect = "Allow"
+    actions = [
+      "ssmmessages:OpenDataChannel",
+      "ssmmessages:OpenControlChannel",
+      "ssmmessages:CreateDataChannel",
+      "ssmmessages:CreateControlChannel",
+    ]
+    resources = [
+      "*"
+    ]
+  }
+
+  #grant the task necessary EFS permissions to be able to modify directories
+  statement {
+    sid    = "EFS"
+    effect = "Allow"
+    actions = [
+      "elasticfilesystem:ClientWrite",
+      "elasticfilesystem:ClientRootAccess",
+      "elasticfilesystem:ClientMount"
+    ]
+    resources = [
+      aws_efs_file_system.teamcity_efs_file_system.arn
+    ]
+    # condition {
+    #   test     = "StringEquals"
+    #   variable = "elasticfilesystem:AccessPointArn"
+    #   values = [
+    #     aws_efs_access_point.teamcity_efs_data_access_point.arn,
+    #     aws_efs_access_point.teamcity_efs_logs_access_point.arn
+    #   ]
+    # }
+  }
+}
+
+resource "aws_iam_policy" "teamcity_default_policy" {
+  name        = "teamcity-default-policy"
+  description = "Policy granting permissions for Unreal Horde."
+  policy      = data.aws_iam_policy_document.teamcity_default_policy.json
+}
+
+resource "aws_iam_role" "teamcity_default_role" {
+  name               = "teamcity-default-role"
+  description        = "Default role for TeamCity ECS task."
+  assume_role_policy = data.aws_iam_policy_document.ecs_tasks_trust_relationship.json
+  managed_policy_arns = [
+    aws_iam_policy.teamcity_default_policy.arn
+  ]
+
+  tags = local.tags
+}
+resource "aws_iam_role" "teamcity_task_execution_role" {
+  name = "teamcity-task-execution-role"
+
+  assume_role_policy  = data.aws_iam_policy_document.ecs_tasks_trust_relationship.json
+  managed_policy_arns = ["arn:aws:iam::aws:policy/service-role/AmazonECSTaskExecutionRolePolicy"]
+}
+
+# CloudWatch Logs
+resource "aws_cloudwatch_log_group" "teamcity_log_group" {
+  #checkov:skip=CKV_AWS_158: KMS Encryption disabled by default
+  name              = "${local.name_prefix}-log-group"
+  retention_in_days = var.teamcity_cloudwatch_log_retention_in_days
+  tags              = local.tags
+}
+
+# Load Balancer for TeamCity Service
+resource "aws_lb" "teamcity_external_lb" {
+  name                       = "${local.name_prefix}-lb"
+  security_groups            = [aws_security_group.teamcity_alb_sg.id]
+  load_balancer_type         = "application"
+  internal                   = false
+  subnets                    = var.alb_subnets
+  drop_invalid_header_fields = true
+  tags                       = local.tags
+}
+
+# TeamCity target group for ALB
+resource "aws_lb_target_group" "teamcity_target_group" {
+  name        = "${local.name_prefix}-tg"
+  port        = var.container_port
+  protocol    = "HTTP"
+  vpc_id      = var.vpc_id
+  target_type = "ip"
+
+  health_check {
+    path                = "/healthCheck/healthy"
+    interval            = 30
+    timeout             = 5
+    healthy_threshold   = 5
+    unhealthy_threshold = 2
+    port                = var.container_port
+    protocol            = "HTTP"
+    matcher             = "200"
+  }
+
+  tags = local.tags
+}
+
+# ALB HTTPS Listener
+resource "aws_lb_listener" "teamcity_listener" {
+  load_balancer_arn = aws_lb.teamcity_external_lb.arn
+  port              = "443"
+  protocol          = "HTTPS"
+  ssl_policy        = "ELBSecurityPolicy-TLS13-1-2-2021-06"
+  certificate_arn   = var.alb_certificate_arn
+  default_action {
+    type             = "forward"
+    target_group_arn = aws_lb_target_group.teamcity_target_group.arn
+  }
+  tags = local.tags
+}
+
+# #######################################
+# # TeamCity Aurora Serverless Database #
+# #######################################
+# Subnet group
+resource "aws_db_subnet_group" "teamcity_db_subnet_group" {
+  name       = "${local.name_prefix}-db-subnet-group"
+  subnet_ids = var.service_subnets
+  tags       = local.tags
+}
+
+# # RDS instance with Aurora serverless engine
+resource "aws_rds_cluster" "teamcity_db_cluster" {
+  #count = var.database_connection_string == null ? var.teamcity_instance_count : 0
+
+  cluster_identifier   = "teamcity-cluster"
+  engine               = "aurora-postgresql"
+  engine_mode          = "provisioned"
+  engine_version       = "16.6"
+  database_name        = "teamcity"
+  master_username      = "teamcity"
+  master_password      = "teamcity2025"
+  storage_encrypted    = true
+  db_subnet_group_name = aws_db_subnet_group.teamcity_db_subnet_group.id
+  vpc_security_group_ids = [
+    aws_security_group.teamcity_db_sg.id
+  ]
+
+  serverlessv2_scaling_configuration {
+    max_capacity             = 1.0
+    min_capacity             = 0.0
+    seconds_until_auto_pause = 3600
+  }
+}
+
+resource "aws_rds_cluster_instance" "teamcity_db_cluster" {
+  #count = var.database_connection_string == null ? 1 : 0
+
+  cluster_identifier = aws_rds_cluster.teamcity_db_cluster.id
+  instance_class     = "db.serverless"
+  engine             = aws_rds_cluster.teamcity_db_cluster.engine
+  engine_version     = aws_rds_cluster.teamcity_db_cluster.engine_version
+}
path: modules/teamcity/main.tf
user: login: github-advanced-security[bot]
body: ## Ensure DB instance gets all minor upgrades automatically

Ensure DB instance gets all minor upgrades automatically

[Show more details](https://github.com/aws-games/cloud-game-development-toolkit/security/code-scanning/828)
created_at: 2025-02-14T19:33:07Z
author_association: NONE
diff_hunk: @@ -0,0 +1,461 @@
+###################################
+# ECS Cluster for TeamCity Server #
+###################################
+
+resource "aws_ecs_cluster" "teamcity_cluster" {
+  name = "${local.name_prefix}-cluster"
+
+  setting {
+    name  = "containerInsights"
+    value = "enabled"
+  }
+
+  tags = local.tags
+}
+
+# TeamCity Task Definition
+resource "aws_ecs_task_definition" "teamcity_task_definition" {
+  family                   = var.name
+  requires_compatibilities = ["FARGATE"]
+  network_mode             = "awsvpc"
+  cpu                      = var.container_cpu
+  memory                   = var.container_memory
+
+  container_definitions = jsonencode([
+    {
+      name      = var.container_name
+      image     = local.image
+      cpu       = var.container_cpu
+      memory    = var.container_memory
+      essential = true
+
+      portMappings = [
+        {
+          containerPort = var.container_port
+          hostPort      = var.container_port
+        }
+      ]
+      logConfiguration = {
+        logDriver = "awslogs"
+        options = {
+          awslogs-group         = aws_cloudwatch_log_group.teamcity_log_group.name
+          awslogs-region        = data.aws_region.current.name
+          awslogs-stream-prefix = "[APP]"
+        }
+      }
+
+      mountPoints = [
+        {
+          sourceVolume  = "teamcity_data",
+          containerPath = "/data/teamcity_server/datadir"
+          readOnly      = false
+        }
+      ]
+
+      # Set environment variables for database connection string
+      environment = [
+        {
+          name  = "TEAMCITY_DB_URL"
+          value = var.database_connection_string
+        },
+        {
+          name  = "TEAMCITY_DB_USER"
+          value = var.database_master_username
+        },
+        {
+          name  = "TEAMCITY_DB_PASSWORD"
+          value = var.database_master_password
+        },
+        # {
+        #   name  = "TEAMCITY_LOGS_PATH"
+        #   value = "/data/teamcity_server/logs"
+        # },
+        {
+          name  = "TEAMCITY_DATA_PATH"
+          value = "/data/teamcity_server/datadir"
+        }
+      ]
+    }
+  ])
+  tags = {
+    Name = var.name
+  }
+  task_role_arn      = aws_iam_role.teamcity_default_role.arn
+  execution_role_arn = aws_iam_role.teamcity_task_execution_role.arn
+
+  volume {
+    name = "teamcity_data"
+    efs_volume_configuration {
+      file_system_id          = aws_efs_file_system.teamcity_efs_file_system.id
+      transit_encryption      = "ENABLED"
+      transit_encryption_port = 2999
+      authorization_config {
+        access_point_id = aws_efs_access_point.teamcity_efs_data_access_point.id
+        iam             = "ENABLED"
+      }
+    }
+  }
+}
+
+# ECS Service
+resource "aws_ecs_service" "teamcity" {
+  name                   = local.name_prefix
+  cluster                = aws_ecs_cluster.teamcity_cluster.name
+  task_definition        = aws_ecs_task_definition.teamcity_task_definition.arn
+  launch_type            = "FARGATE"
+  desired_count          = 1     #TODO: make this configurable
+  force_new_deployment   = true  #TODO: make this configurable
+  enable_execute_command = true  #TODO: make this configurable
+  wait_for_steady_state  = false #TODO: make this configurable
+
+  network_configuration {
+    subnets         = var.service_subnets
+    security_groups = [aws_security_group.teamcity_service_sg.id]
+  }
+  load_balancer {
+    target_group_arn = aws_lb_target_group.teamcity_target_group.arn
+    container_name   = var.container_name
+    container_port   = var.container_port
+  }
+
+  tags = local.tags
+}
+
+# TeamCity service security group
+resource "aws_security_group" "teamcity_service_sg" {
+  name        = "${local.name_prefix}-service-sg"
+  vpc_id      = var.vpc_id
+  description = "TeamCity service security group"
+  tags        = local.tags
+}
+
+# TeamCity EFS security group
+resource "aws_security_group" "teamcity_efs_sg" {
+  name        = "${local.name_prefix}-efs-sg"
+  description = "TeamCity EFS mount target security group"
+  vpc_id      = var.vpc_id
+  tags        = local.tags
+}
+
+# Ingress rule for NFS traffic from service to EFS
+resource "aws_vpc_security_group_ingress_rule" "service_efs" {
+  security_group_id            = aws_security_group.teamcity_efs_sg.id
+  referenced_security_group_id = aws_security_group.teamcity_service_sg.id
+  description                  = "Allow inbound access from TeamCity service containers to EFS"
+  ip_protocol                  = "TCP"
+  from_port                    = 2049
+  to_port                      = 2049
+}
+
+# Ingress rule for NFS traffic from EFS to service
+resource "aws_vpc_security_group_ingress_rule" "efs_service" {
+  security_group_id            = aws_security_group.teamcity_service_sg.id
+  referenced_security_group_id = aws_security_group.teamcity_efs_sg.id
+  description                  = "Allow inbound access from EFS to TeamCity service containers"
+  ip_protocol                  = "TCP"
+  from_port                    = 2049
+  to_port                      = 2049
+}
+
+# TeamCity Aurora Serverless PostgreSQL security group
+resource "aws_security_group" "teamcity_db_sg" {
+  name        = "${local.name_prefix}-db-sg"
+  description = "TeamCity DB security group"
+  vpc_id      = var.vpc_id
+  tags        = local.tags
+}
+
+# Ingress rule for PostgreSQL from service to database cluster
+resource "aws_vpc_security_group_ingress_rule" "service_db" {
+  security_group_id            = aws_security_group.teamcity_db_sg.id
+  referenced_security_group_id = aws_security_group.teamcity_service_sg.id
+  description                  = "Allow inbound access from TeamCity service containers to DB"
+  ip_protocol                  = "TCP"
+  from_port                    = 5432
+  to_port                      = 5432
+}
+
+# Ingress rule for PostgreSQL from database cluster to service
+resource "aws_vpc_security_group_ingress_rule" "db_service" {
+  security_group_id            = aws_security_group.teamcity_service_sg.id
+  referenced_security_group_id = aws_security_group.teamcity_db_sg.id
+  description                  = "Allow inbound access from DB to TeamCity service containers"
+  ip_protocol                  = "TCP"
+  from_port                    = 5432
+  to_port                      = 5432
+}
+
+# Egress rule for database to let all traffic out
+resource "aws_vpc_security_group_egress_rule" "db_outbound" {
+  security_group_id = aws_security_group.teamcity_db_sg.id
+  description       = "Allow outbound access from DB to all"
+  cidr_ipv4         = "0.0.0.0/0"
+  ip_protocol       = "-1"
+}
+
+# TeamCity ALB security group
+resource "aws_security_group" "teamcity_alb_sg" {
+  name        = "${local.name_prefix}-alb-sg"
+  vpc_id      = var.vpc_id
+  description = "TeamCity ALB security group"
+  tags        = local.tags
+}
+
+# Ingress rule for HTTP traffic from ALB to service
+resource "aws_vpc_security_group_ingress_rule" "service_inbound_alb" {
+  security_group_id            = aws_security_group.teamcity_service_sg.id
+  referenced_security_group_id = aws_security_group.teamcity_alb_sg.id
+  description                  = "Allow inbound HTTP traffic from ALB to service containers"
+  from_port                    = var.container_port
+  to_port                      = var.container_port
+  ip_protocol                  = "TCP"
+}
+
+# Egress rule for HTTP traffic from ALB to service
+resource "aws_vpc_security_group_egress_rule" "service_outbound_alb" {
+  security_group_id            = aws_security_group.teamcity_alb_sg.id
+  referenced_security_group_id = aws_security_group.teamcity_service_sg.id
+  description                  = "Allow outbound HTTP traffic from ALB to service containers"
+  from_port                    = var.container_port
+  to_port                      = var.container_port
+  ip_protocol                  = "TCP"
+}
+
+# Grant TeamCity service access to internet
+resource "aws_vpc_security_group_egress_rule" "internet_outbound" {
+  security_group_id = aws_security_group.teamcity_service_sg.id
+  description       = "Allow outbound internet access from TeamCity service containers"
+  cidr_ipv4         = "0.0.0.0/0"
+  ip_protocol       = "-1"
+}
+
+#############################################
+# IAM Roles for TeamCity Module
+#############################################
+
+data "aws_iam_policy_document" "ecs_tasks_trust_relationship" {
+  statement {
+    effect  = "Allow"
+    actions = ["sts:AssumeRole"]
+    principals {
+      type        = "Service"
+      identifiers = ["ecs-tasks.amazonaws.com"]
+    }
+  }
+}
+
+data "aws_iam_policy_document" "teamcity_default_policy" {
+  # ECS
+  statement {
+    sid    = "ECSExec"
+    effect = "Allow"
+    actions = [
+      "ssmmessages:OpenDataChannel",
+      "ssmmessages:OpenControlChannel",
+      "ssmmessages:CreateDataChannel",
+      "ssmmessages:CreateControlChannel",
+    ]
+    resources = [
+      "*"
+    ]
+  }
+
+  #grant the task necessary EFS permissions to be able to modify directories
+  statement {
+    sid    = "EFS"
+    effect = "Allow"
+    actions = [
+      "elasticfilesystem:ClientWrite",
+      "elasticfilesystem:ClientRootAccess",
+      "elasticfilesystem:ClientMount"
+    ]
+    resources = [
+      aws_efs_file_system.teamcity_efs_file_system.arn
+    ]
+    # condition {
+    #   test     = "StringEquals"
+    #   variable = "elasticfilesystem:AccessPointArn"
+    #   values = [
+    #     aws_efs_access_point.teamcity_efs_data_access_point.arn,
+    #     aws_efs_access_point.teamcity_efs_logs_access_point.arn
+    #   ]
+    # }
+  }
+}
+
+resource "aws_iam_policy" "teamcity_default_policy" {
+  name        = "teamcity-default-policy"
+  description = "Policy granting permissions for Unreal Horde."
+  policy      = data.aws_iam_policy_document.teamcity_default_policy.json
+}
+
+resource "aws_iam_role" "teamcity_default_role" {
+  name               = "teamcity-default-role"
+  description        = "Default role for TeamCity ECS task."
+  assume_role_policy = data.aws_iam_policy_document.ecs_tasks_trust_relationship.json
+  managed_policy_arns = [
+    aws_iam_policy.teamcity_default_policy.arn
+  ]
+
+  tags = local.tags
+}
+resource "aws_iam_role" "teamcity_task_execution_role" {
+  name = "teamcity-task-execution-role"
+
+  assume_role_policy  = data.aws_iam_policy_document.ecs_tasks_trust_relationship.json
+  managed_policy_arns = ["arn:aws:iam::aws:policy/service-role/AmazonECSTaskExecutionRolePolicy"]
+}
+
+# CloudWatch Logs
+resource "aws_cloudwatch_log_group" "teamcity_log_group" {
+  #checkov:skip=CKV_AWS_158: KMS Encryption disabled by default
+  name              = "${local.name_prefix}-log-group"
+  retention_in_days = var.teamcity_cloudwatch_log_retention_in_days
+  tags              = local.tags
+}
+
+# Load Balancer for TeamCity Service
+resource "aws_lb" "teamcity_external_lb" {
+  name                       = "${local.name_prefix}-lb"
+  security_groups            = [aws_security_group.teamcity_alb_sg.id]
+  load_balancer_type         = "application"
+  internal                   = false
+  subnets                    = var.alb_subnets
+  drop_invalid_header_fields = true
+  tags                       = local.tags
+}
+
+# TeamCity target group for ALB
+resource "aws_lb_target_group" "teamcity_target_group" {
+  name        = "${local.name_prefix}-tg"
+  port        = var.container_port
+  protocol    = "HTTP"
+  vpc_id      = var.vpc_id
+  target_type = "ip"
+
+  health_check {
+    path                = "/healthCheck/healthy"
+    interval            = 30
+    timeout             = 5
+    healthy_threshold   = 5
+    unhealthy_threshold = 2
+    port                = var.container_port
+    protocol            = "HTTP"
+    matcher             = "200"
+  }
+
+  tags = local.tags
+}
+
+# ALB HTTPS Listener
+resource "aws_lb_listener" "teamcity_listener" {
+  load_balancer_arn = aws_lb.teamcity_external_lb.arn
+  port              = "443"
+  protocol          = "HTTPS"
+  ssl_policy        = "ELBSecurityPolicy-TLS13-1-2-2021-06"
+  certificate_arn   = var.alb_certificate_arn
+  default_action {
+    type             = "forward"
+    target_group_arn = aws_lb_target_group.teamcity_target_group.arn
+  }
+  tags = local.tags
+}
+
+# #######################################
+# # TeamCity Aurora Serverless Database #
+# #######################################
+# Subnet group
+resource "aws_db_subnet_group" "teamcity_db_subnet_group" {
+  name       = "${local.name_prefix}-db-subnet-group"
+  subnet_ids = var.service_subnets
+  tags       = local.tags
+}
+
+# # RDS instance with Aurora serverless engine
+resource "aws_rds_cluster" "teamcity_db_cluster" {
+  #count = var.database_connection_string == null ? var.teamcity_instance_count : 0
+
+  cluster_identifier   = "teamcity-cluster"
+  engine               = "aurora-postgresql"
+  engine_mode          = "provisioned"
+  engine_version       = "16.6"
+  database_name        = "teamcity"
+  master_username      = "teamcity"
+  master_password      = "teamcity2025"
+  storage_encrypted    = true
+  db_subnet_group_name = aws_db_subnet_group.teamcity_db_subnet_group.id
+  vpc_security_group_ids = [
+    aws_security_group.teamcity_db_sg.id
+  ]
+
+  serverlessv2_scaling_configuration {
+    max_capacity             = 1.0
+    min_capacity             = 0.0
+    seconds_until_auto_pause = 3600
+  }
+}
+
+resource "aws_rds_cluster_instance" "teamcity_db_cluster" {
+  #count = var.database_connection_string == null ? 1 : 0
+
+  cluster_identifier = aws_rds_cluster.teamcity_db_cluster.id
+  instance_class     = "db.serverless"
+  engine             = aws_rds_cluster.teamcity_db_cluster.engine
+  engine_version     = aws_rds_cluster.teamcity_db_cluster.engine_version
+}
path: modules/teamcity/main.tf
user: login: github-advanced-security[bot]
body: ## Ensure that enhanced monitoring is enabled for Amazon RDS instances

Ensure that enhanced monitoring is enabled for Amazon RDS instances

[Show more details](https://github.com/aws-games/cloud-game-development-toolkit/security/code-scanning/829)
created_at: 2025-02-14T19:33:07Z
author_association: NONE
diff_hunk: @@ -0,0 +1,461 @@
+###################################
+# ECS Cluster for TeamCity Server #
+###################################
+
+resource "aws_ecs_cluster" "teamcity_cluster" {
+  name = "${local.name_prefix}-cluster"
+
+  setting {
+    name  = "containerInsights"
+    value = "enabled"
+  }
+
+  tags = local.tags
+}
+
+# TeamCity Task Definition
+resource "aws_ecs_task_definition" "teamcity_task_definition" {
+  family                   = var.name
+  requires_compatibilities = ["FARGATE"]
+  network_mode             = "awsvpc"
+  cpu                      = var.container_cpu
+  memory                   = var.container_memory
+
+  container_definitions = jsonencode([
+    {
+      name      = var.container_name
+      image     = local.image
+      cpu       = var.container_cpu
+      memory    = var.container_memory
+      essential = true
+
+      portMappings = [
+        {
+          containerPort = var.container_port
+          hostPort      = var.container_port
+        }
+      ]
+      logConfiguration = {
+        logDriver = "awslogs"
+        options = {
+          awslogs-group         = aws_cloudwatch_log_group.teamcity_log_group.name
+          awslogs-region        = data.aws_region.current.name
+          awslogs-stream-prefix = "[APP]"
+        }
+      }
+
+      mountPoints = [
+        {
+          sourceVolume  = "teamcity_data",
+          containerPath = "/data/teamcity_server/datadir"
+          readOnly      = false
+        }
+      ]
+
+      # Set environment variables for database connection string
+      environment = [
+        {
+          name  = "TEAMCITY_DB_URL"
+          value = var.database_connection_string
+        },
+        {
+          name  = "TEAMCITY_DB_USER"
+          value = var.database_master_username
+        },
+        {
+          name  = "TEAMCITY_DB_PASSWORD"
+          value = var.database_master_password
+        },
+        # {
+        #   name  = "TEAMCITY_LOGS_PATH"
+        #   value = "/data/teamcity_server/logs"
+        # },
+        {
+          name  = "TEAMCITY_DATA_PATH"
+          value = "/data/teamcity_server/datadir"
+        }
+      ]
+    }
+  ])
+  tags = {
+    Name = var.name
+  }
+  task_role_arn      = aws_iam_role.teamcity_default_role.arn
+  execution_role_arn = aws_iam_role.teamcity_task_execution_role.arn
+
+  volume {
+    name = "teamcity_data"
+    efs_volume_configuration {
+      file_system_id          = aws_efs_file_system.teamcity_efs_file_system.id
+      transit_encryption      = "ENABLED"
+      transit_encryption_port = 2999
+      authorization_config {
+        access_point_id = aws_efs_access_point.teamcity_efs_data_access_point.id
+        iam             = "ENABLED"
+      }
+    }
+  }
+}
+
+# ECS Service
+resource "aws_ecs_service" "teamcity" {
+  name                   = local.name_prefix
+  cluster                = aws_ecs_cluster.teamcity_cluster.name
+  task_definition        = aws_ecs_task_definition.teamcity_task_definition.arn
+  launch_type            = "FARGATE"
+  desired_count          = 1     #TODO: make this configurable
+  force_new_deployment   = true  #TODO: make this configurable
+  enable_execute_command = true  #TODO: make this configurable
+  wait_for_steady_state  = false #TODO: make this configurable
+
+  network_configuration {
+    subnets         = var.service_subnets
+    security_groups = [aws_security_group.teamcity_service_sg.id]
+  }
+  load_balancer {
+    target_group_arn = aws_lb_target_group.teamcity_target_group.arn
+    container_name   = var.container_name
+    container_port   = var.container_port
+  }
+
+  tags = local.tags
+}
+
+# TeamCity service security group
+resource "aws_security_group" "teamcity_service_sg" {
+  name        = "${local.name_prefix}-service-sg"
+  vpc_id      = var.vpc_id
+  description = "TeamCity service security group"
+  tags        = local.tags
+}
+
+# TeamCity EFS security group
+resource "aws_security_group" "teamcity_efs_sg" {
+  name        = "${local.name_prefix}-efs-sg"
+  description = "TeamCity EFS mount target security group"
+  vpc_id      = var.vpc_id
+  tags        = local.tags
+}
+
+# Ingress rule for NFS traffic from service to EFS
+resource "aws_vpc_security_group_ingress_rule" "service_efs" {
+  security_group_id            = aws_security_group.teamcity_efs_sg.id
+  referenced_security_group_id = aws_security_group.teamcity_service_sg.id
+  description                  = "Allow inbound access from TeamCity service containers to EFS"
+  ip_protocol                  = "TCP"
+  from_port                    = 2049
+  to_port                      = 2049
+}
+
+# Ingress rule for NFS traffic from EFS to service
+resource "aws_vpc_security_group_ingress_rule" "efs_service" {
+  security_group_id            = aws_security_group.teamcity_service_sg.id
+  referenced_security_group_id = aws_security_group.teamcity_efs_sg.id
+  description                  = "Allow inbound access from EFS to TeamCity service containers"
+  ip_protocol                  = "TCP"
+  from_port                    = 2049
+  to_port                      = 2049
+}
+
+# TeamCity Aurora Serverless PostgreSQL security group
+resource "aws_security_group" "teamcity_db_sg" {
+  name        = "${local.name_prefix}-db-sg"
+  description = "TeamCity DB security group"
+  vpc_id      = var.vpc_id
+  tags        = local.tags
+}
+
+# Ingress rule for PostgreSQL from service to database cluster
+resource "aws_vpc_security_group_ingress_rule" "service_db" {
+  security_group_id            = aws_security_group.teamcity_db_sg.id
+  referenced_security_group_id = aws_security_group.teamcity_service_sg.id
+  description                  = "Allow inbound access from TeamCity service containers to DB"
+  ip_protocol                  = "TCP"
+  from_port                    = 5432
+  to_port                      = 5432
+}
+
+# Ingress rule for PostgreSQL from database cluster to service
+resource "aws_vpc_security_group_ingress_rule" "db_service" {
+  security_group_id            = aws_security_group.teamcity_service_sg.id
+  referenced_security_group_id = aws_security_group.teamcity_db_sg.id
+  description                  = "Allow inbound access from DB to TeamCity service containers"
+  ip_protocol                  = "TCP"
+  from_port                    = 5432
+  to_port                      = 5432
+}
+
+# Egress rule for database to let all traffic out
+resource "aws_vpc_security_group_egress_rule" "db_outbound" {
+  security_group_id = aws_security_group.teamcity_db_sg.id
+  description       = "Allow outbound access from DB to all"
+  cidr_ipv4         = "0.0.0.0/0"
+  ip_protocol       = "-1"
+}
+
+# TeamCity ALB security group
+resource "aws_security_group" "teamcity_alb_sg" {
+  name        = "${local.name_prefix}-alb-sg"
+  vpc_id      = var.vpc_id
+  description = "TeamCity ALB security group"
+  tags        = local.tags
+}
+
+# Ingress rule for HTTP traffic from ALB to service
+resource "aws_vpc_security_group_ingress_rule" "service_inbound_alb" {
+  security_group_id            = aws_security_group.teamcity_service_sg.id
+  referenced_security_group_id = aws_security_group.teamcity_alb_sg.id
+  description                  = "Allow inbound HTTP traffic from ALB to service containers"
+  from_port                    = var.container_port
+  to_port                      = var.container_port
+  ip_protocol                  = "TCP"
+}
+
+# Egress rule for HTTP traffic from ALB to service
+resource "aws_vpc_security_group_egress_rule" "service_outbound_alb" {
+  security_group_id            = aws_security_group.teamcity_alb_sg.id
+  referenced_security_group_id = aws_security_group.teamcity_service_sg.id
+  description                  = "Allow outbound HTTP traffic from ALB to service containers"
+  from_port                    = var.container_port
+  to_port                      = var.container_port
+  ip_protocol                  = "TCP"
+}
+
+# Grant TeamCity service access to internet
+resource "aws_vpc_security_group_egress_rule" "internet_outbound" {
+  security_group_id = aws_security_group.teamcity_service_sg.id
+  description       = "Allow outbound internet access from TeamCity service containers"
+  cidr_ipv4         = "0.0.0.0/0"
+  ip_protocol       = "-1"
+}
+
+#############################################
+# IAM Roles for TeamCity Module
+#############################################
+
+data "aws_iam_policy_document" "ecs_tasks_trust_relationship" {
+  statement {
+    effect  = "Allow"
+    actions = ["sts:AssumeRole"]
+    principals {
+      type        = "Service"
+      identifiers = ["ecs-tasks.amazonaws.com"]
+    }
+  }
+}
+
+data "aws_iam_policy_document" "teamcity_default_policy" {
+  # ECS
+  statement {
+    sid    = "ECSExec"
+    effect = "Allow"
+    actions = [
+      "ssmmessages:OpenDataChannel",
+      "ssmmessages:OpenControlChannel",
+      "ssmmessages:CreateDataChannel",
+      "ssmmessages:CreateControlChannel",
+    ]
+    resources = [
+      "*"
+    ]
+  }
+
+  #grant the task necessary EFS permissions to be able to modify directories
+  statement {
+    sid    = "EFS"
+    effect = "Allow"
+    actions = [
+      "elasticfilesystem:ClientWrite",
+      "elasticfilesystem:ClientRootAccess",
+      "elasticfilesystem:ClientMount"
+    ]
+    resources = [
+      aws_efs_file_system.teamcity_efs_file_system.arn
+    ]
+    # condition {
+    #   test     = "StringEquals"
+    #   variable = "elasticfilesystem:AccessPointArn"
+    #   values = [
+    #     aws_efs_access_point.teamcity_efs_data_access_point.arn,
+    #     aws_efs_access_point.teamcity_efs_logs_access_point.arn
+    #   ]
+    # }
+  }
+}
+
+resource "aws_iam_policy" "teamcity_default_policy" {
+  name        = "teamcity-default-policy"
+  description = "Policy granting permissions for Unreal Horde."
+  policy      = data.aws_iam_policy_document.teamcity_default_policy.json
+}
+
+resource "aws_iam_role" "teamcity_default_role" {
+  name               = "teamcity-default-role"
+  description        = "Default role for TeamCity ECS task."
+  assume_role_policy = data.aws_iam_policy_document.ecs_tasks_trust_relationship.json
+  managed_policy_arns = [
+    aws_iam_policy.teamcity_default_policy.arn
+  ]
+
+  tags = local.tags
+}
+resource "aws_iam_role" "teamcity_task_execution_role" {
+  name = "teamcity-task-execution-role"
+
+  assume_role_policy  = data.aws_iam_policy_document.ecs_tasks_trust_relationship.json
+  managed_policy_arns = ["arn:aws:iam::aws:policy/service-role/AmazonECSTaskExecutionRolePolicy"]
+}
+
+# CloudWatch Logs
+resource "aws_cloudwatch_log_group" "teamcity_log_group" {
+  #checkov:skip=CKV_AWS_158: KMS Encryption disabled by default
+  name              = "${local.name_prefix}-log-group"
+  retention_in_days = var.teamcity_cloudwatch_log_retention_in_days
+  tags              = local.tags
+}
+
+# Load Balancer for TeamCity Service
+resource "aws_lb" "teamcity_external_lb" {
+  name                       = "${local.name_prefix}-lb"
+  security_groups            = [aws_security_group.teamcity_alb_sg.id]
+  load_balancer_type         = "application"
+  internal                   = false
+  subnets                    = var.alb_subnets
+  drop_invalid_header_fields = true
+  tags                       = local.tags
+}
+
+# TeamCity target group for ALB
+resource "aws_lb_target_group" "teamcity_target_group" {
+  name        = "${local.name_prefix}-tg"
+  port        = var.container_port
+  protocol    = "HTTP"
+  vpc_id      = var.vpc_id
+  target_type = "ip"
+
+  health_check {
+    path                = "/healthCheck/healthy"
+    interval            = 30
+    timeout             = 5
+    healthy_threshold   = 5
+    unhealthy_threshold = 2
+    port                = var.container_port
+    protocol            = "HTTP"
+    matcher             = "200"
+  }
+
+  tags = local.tags
+}
+
+# ALB HTTPS Listener
+resource "aws_lb_listener" "teamcity_listener" {
+  load_balancer_arn = aws_lb.teamcity_external_lb.arn
+  port              = "443"
+  protocol          = "HTTPS"
+  ssl_policy        = "ELBSecurityPolicy-TLS13-1-2-2021-06"
+  certificate_arn   = var.alb_certificate_arn
+  default_action {
+    type             = "forward"
+    target_group_arn = aws_lb_target_group.teamcity_target_group.arn
+  }
+  tags = local.tags
+}
+
+# #######################################
+# # TeamCity Aurora Serverless Database #
+# #######################################
+# Subnet group
+resource "aws_db_subnet_group" "teamcity_db_subnet_group" {
+  name       = "${local.name_prefix}-db-subnet-group"
+  subnet_ids = var.service_subnets
+  tags       = local.tags
+}
+
+# # RDS instance with Aurora serverless engine
+resource "aws_rds_cluster" "teamcity_db_cluster" {
+  #count = var.database_connection_string == null ? var.teamcity_instance_count : 0
+
+  cluster_identifier   = "teamcity-cluster"
+  engine               = "aurora-postgresql"
+  engine_mode          = "provisioned"
+  engine_version       = "16.6"
+  database_name        = "teamcity"
+  master_username      = "teamcity"
+  master_password      = "teamcity2025"
+  storage_encrypted    = true
+  db_subnet_group_name = aws_db_subnet_group.teamcity_db_subnet_group.id
+  vpc_security_group_ids = [
+    aws_security_group.teamcity_db_sg.id
+  ]
+
+  serverlessv2_scaling_configuration {
+    max_capacity             = 1.0
+    min_capacity             = 0.0
+    seconds_until_auto_pause = 3600
+  }
+}
+
+resource "aws_rds_cluster_instance" "teamcity_db_cluster" {
+  #count = var.database_connection_string == null ? 1 : 0
+
+  cluster_identifier = aws_rds_cluster.teamcity_db_cluster.id
+  instance_class     = "db.serverless"
+  engine             = aws_rds_cluster.teamcity_db_cluster.engine
+  engine_version     = aws_rds_cluster.teamcity_db_cluster.engine_version
+}
path: modules/teamcity/main.tf
user: login: github-advanced-security[bot]
body: ## Ensure that RDS instances have performance insights enabled

Ensure that RDS instances have performance insights enabled

[Show more details](https://github.com/aws-games/cloud-game-development-toolkit/security/code-scanning/830)
created_at: 2025-02-14T19:33:07Z
author_association: NONE
diff_hunk: @@ -0,0 +1,617 @@
+###################################
+# ECS Cluster for TeamCity Server #
+###################################
+# If cluster name is provided use a data source to access existing resource
+data "aws_ecs_cluster" "teamcity_cluster" {
+  count        = var.cluster_name != null ? 1 : 0
+  cluster_name = var.cluster_name
+}
+resource "aws_ecs_cluster" "teamcity_cluster" {
+  count = var.cluster_name != null ? 0 : 1
+  name  = "${local.name_prefix}-cluster"
+
+  setting {
+    name  = "containerInsights"
+    value = "enabled"
+  }
+
+  tags = local.tags
+}
+
+# TeamCity Task Definition
+resource "aws_ecs_task_definition" "teamcity_task_definition" {
+  family                   = var.name
+  requires_compatibilities = ["FARGATE"]
+  network_mode             = "awsvpc"
+  cpu                      = var.container_cpu
+  memory                   = var.container_memory
+
+  container_definitions = jsonencode([
+    {
+      name      = var.container_name
+      image     = local.image
+      cpu       = var.container_cpu
+      memory    = var.container_memory
+      essential = true
+
+      portMappings = [
+        {
+          containerPort = var.container_port
+          hostPort      = var.container_port
+        }
+      ]
+      logConfiguration = {
+        logDriver = "awslogs"
+        options = {
+          awslogs-group         = aws_cloudwatch_log_group.teamcity_log_group.name
+          awslogs-region        = data.aws_region.current.name
+          awslogs-stream-prefix = "[APP]"
+        }
+      }
+
+      mountPoints = [
+        {
+          sourceVolume  = "teamcity_data",
+          containerPath = "/data/teamcity_server/datadir"
+          readOnly      = false
+        }
+      ]
+
+      # # Set environment variables for database connection string
+      # environment = [
+      #   {
+      #     name  = "TEAMCITY_DB_URL"
+      #     value = local.database_connection_string
+      #   },
+      #   {
+      #     name  = "TEAMCITY_DB_USER"
+      #     value = local.database_master_username
+      #   },
+      #   # {
+      #   #   name  = "TEAMCITY_LOGS_PATH"
+      #   #   value = "/data/teamcity_server/logs"
+      #   # },
+      #   {
+      #     name  = "TEAMCITY_DATA_PATH"
+      #     value = "/data/teamcity_server/datadir"
+      #   }
+      # ]
+      # # Define password environment variable if provided
+      # password_env = local.database_master_password != null ? [
+      #   {
+      #     name  = "TEAMCITY_DB_PASSWORD"
+      #     value = local.database_master_password
+      #   }
+      # ] : []
+
+      # Combine the lists
+      environment = concat(local.base_env, local.password_env)
+
+      secrets = var.database_connection_string == null ? [
+        {
+          name      = "TEAMCITY_DB_PASSWORD"
+          valueFrom = "${aws_rds_cluster.teamcity_db_cluster[0].master_user_secret[0].secret_arn}:password::"
+        }
+      ] : []
+    }
+  ])
+  tags = {
+    Name = var.name
+  }
+  task_role_arn      = aws_iam_role.teamcity_default_role.arn
+  execution_role_arn = aws_iam_role.teamcity_task_execution_role.arn
+
+  volume {
+    name = "teamcity_data"
+    efs_volume_configuration {
+      file_system_id          = aws_efs_file_system.teamcity_efs_file_system[0].id
+      transit_encryption      = "ENABLED"
+      transit_encryption_port = 2999
+      authorization_config {
+        access_point_id = aws_efs_access_point.teamcity_efs_data_access_point.id
+        iam             = "ENABLED"
+      }
+    }
+  }
+}
+
+# ECS Service
+resource "aws_ecs_service" "teamcity" {
+  name                   = local.name_prefix
+  cluster                = var.cluster_name != null ? data.aws_ecs_cluster.teamcity_cluster[0].arn : aws_ecs_cluster.teamcity_cluster[0].arn
+  task_definition        = aws_ecs_task_definition.teamcity_task_definition.arn
+  launch_type            = "FARGATE"
+  desired_count          = var.desired_container_count
+  force_new_deployment   = var.debug
+  enable_execute_command = var.debug
+
+  wait_for_steady_state = false #TODO: make this configurable
+
+  network_configuration {
+    subnets         = var.service_subnets
+    security_groups = [aws_security_group.teamcity_service_sg.id]
+  }
+  dynamic "load_balancer" {
+    for_each = var.create_external_alb ? [1] : []
+    content {
+      target_group_arn = aws_lb_target_group.teamcity_target_group[0].arn
+      container_name   = var.container_name
+      container_port   = var.container_port
+
+    }
+  }
+
+  tags = local.tags
+}
+
+###################################
+# Security Groups                 #
+###################################
+
+# TeamCity service security group
+resource "aws_security_group" "teamcity_service_sg" {
+  name        = "${local.name_prefix}-service-sg"
+  vpc_id      = var.vpc_id
+  description = "TeamCity service security group"
+  tags        = local.tags
+}
+
+# TeamCity EFS security group
+resource "aws_security_group" "teamcity_efs_sg" {
+  # count       = var.efs_id == null ? 1 : 0
+  name        = "${local.name_prefix}-efs-sg"
+  description = "TeamCity EFS mount target security group"
+  vpc_id      = var.vpc_id
+  tags        = local.tags
+}
+
+# Ingress rule for NFS traffic from service to EFS
+resource "aws_vpc_security_group_ingress_rule" "service_efs" {
+  # count                        = var.efs_id == null ? 1 : 0
+  security_group_id            = aws_security_group.teamcity_efs_sg.id
+  referenced_security_group_id = aws_security_group.teamcity_service_sg.id
+  description                  = "Allow inbound access from TeamCity service containers to EFS"
+  ip_protocol                  = "TCP"
+  from_port                    = 2049
+  to_port                      = 2049
+}
+
+# Ingress rule for NFS traffic from EFS to service
+resource "aws_vpc_security_group_ingress_rule" "efs_service" {
+  # count                        = var.efs_id == null ? 1 : 0
+  security_group_id            = aws_security_group.teamcity_service_sg.id
+  referenced_security_group_id = aws_security_group.teamcity_efs_sg.id
+  description                  = "Allow inbound access from EFS to TeamCity service containers"
+  ip_protocol                  = "TCP"
+  from_port                    = 2049
+  to_port                      = 2049
+}
+
+# TeamCity Aurora Serverless PostgreSQL security group
+resource "aws_security_group" "teamcity_db_sg" {
+  count       = var.database_connection_string == null ? 1 : 0
+  name        = "${local.name_prefix}-db-sg"
+  description = "TeamCity DB security group"
+  vpc_id      = var.vpc_id
+  tags        = local.tags
+}
+
+# Ingress rule for PostgreSQL from service to database cluster
+resource "aws_vpc_security_group_ingress_rule" "service_db" {
+  count                        = var.database_connection_string == null ? 1 : 0
+  security_group_id            = aws_security_group.teamcity_db_sg[0].id
+  referenced_security_group_id = aws_security_group.teamcity_service_sg.id
+  description                  = "Allow inbound access from TeamCity service containers to DB"
+  ip_protocol                  = "TCP"
+  from_port                    = 5432
+  to_port                      = 5432
+}
+
+# Ingress rule for PostgreSQL from database cluster to service
+resource "aws_vpc_security_group_ingress_rule" "db_service" {
+  count                        = var.database_connection_string == null ? 1 : 0
+  security_group_id            = aws_security_group.teamcity_service_sg.id
+  referenced_security_group_id = aws_security_group.teamcity_db_sg[0].id
+  description                  = "Allow inbound access from DB to TeamCity service containers"
+  ip_protocol                  = "TCP"
+  from_port                    = 5432
+  to_port                      = 5432
+}
+
+# Egress rule for database to let all traffic out
+resource "aws_vpc_security_group_egress_rule" "db_outbound" {
+  count             = var.database_connection_string == null ? 1 : 0
+  security_group_id = aws_security_group.teamcity_db_sg[0].id
+  description       = "Allow outbound access from DB to all"
+  cidr_ipv4         = "0.0.0.0/0"
+  ip_protocol       = "-1"
+}
+
+# TeamCity ALB security group
+resource "aws_security_group" "teamcity_alb_sg" {
+  #checkov:skip=CKV2_AWS_5:SG is attached to TeamCity service ALB
+  count       = var.create_external_alb ? 1 : 0
+  name        = "${local.name_prefix}-alb-sg"
+  vpc_id      = var.vpc_id
+  description = "TeamCity ALB security group"
+  tags        = local.tags
+}
path: modules/teamcity/main.tf
user: login: github-advanced-security[bot]
body: ## Ensure that Security Groups are attached to another resource

Ensure that Security Groups are attached to another resource

[Show more details](https://github.com/aws-games/cloud-game-development-toolkit/security/code-scanning/843)
created_at: 2025-03-06T22:53:04Z
author_association: NONE
diff_hunk: @@ -0,0 +1,617 @@
+###################################
+# ECS Cluster for TeamCity Server #
+###################################
+# If cluster name is provided use a data source to access existing resource
+data "aws_ecs_cluster" "teamcity_cluster" {
+  count        = var.cluster_name != null ? 1 : 0
+  cluster_name = var.cluster_name
+}
+resource "aws_ecs_cluster" "teamcity_cluster" {
+  count = var.cluster_name != null ? 0 : 1
+  name  = "${local.name_prefix}-cluster"
+
+  setting {
+    name  = "containerInsights"
+    value = "enabled"
+  }
+
+  tags = local.tags
+}
+
+# TeamCity Task Definition
+resource "aws_ecs_task_definition" "teamcity_task_definition" {
+  family                   = var.name
+  requires_compatibilities = ["FARGATE"]
+  network_mode             = "awsvpc"
+  cpu                      = var.container_cpu
+  memory                   = var.container_memory
+
+  container_definitions = jsonencode([
+    {
+      name      = var.container_name
+      image     = local.image
+      cpu       = var.container_cpu
+      memory    = var.container_memory
+      essential = true
+
+      portMappings = [
+        {
+          containerPort = var.container_port
+          hostPort      = var.container_port
+        }
+      ]
+      logConfiguration = {
+        logDriver = "awslogs"
+        options = {
+          awslogs-group         = aws_cloudwatch_log_group.teamcity_log_group.name
+          awslogs-region        = data.aws_region.current.name
+          awslogs-stream-prefix = "[APP]"
+        }
+      }
+
+      mountPoints = [
+        {
+          sourceVolume  = "teamcity_data",
+          containerPath = "/data/teamcity_server/datadir"
+          readOnly      = false
+        }
+      ]
+
+      # # Set environment variables for database connection string
+      # environment = [
+      #   {
+      #     name  = "TEAMCITY_DB_URL"
+      #     value = local.database_connection_string
+      #   },
+      #   {
+      #     name  = "TEAMCITY_DB_USER"
+      #     value = local.database_master_username
+      #   },
+      #   # {
+      #   #   name  = "TEAMCITY_LOGS_PATH"
+      #   #   value = "/data/teamcity_server/logs"
+      #   # },
+      #   {
+      #     name  = "TEAMCITY_DATA_PATH"
+      #     value = "/data/teamcity_server/datadir"
+      #   }
+      # ]
+      # # Define password environment variable if provided
+      # password_env = local.database_master_password != null ? [
+      #   {
+      #     name  = "TEAMCITY_DB_PASSWORD"
+      #     value = local.database_master_password
+      #   }
+      # ] : []
+
+      # Combine the lists
+      environment = concat(local.base_env, local.password_env)
+
+      secrets = var.database_connection_string == null ? [
+        {
+          name      = "TEAMCITY_DB_PASSWORD"
+          valueFrom = "${aws_rds_cluster.teamcity_db_cluster[0].master_user_secret[0].secret_arn}:password::"
+        }
+      ] : []
+    }
+  ])
+  tags = {
+    Name = var.name
+  }
+  task_role_arn      = aws_iam_role.teamcity_default_role.arn
+  execution_role_arn = aws_iam_role.teamcity_task_execution_role.arn
+
+  volume {
+    name = "teamcity_data"
+    efs_volume_configuration {
+      file_system_id          = aws_efs_file_system.teamcity_efs_file_system[0].id
+      transit_encryption      = "ENABLED"
+      transit_encryption_port = 2999
+      authorization_config {
+        access_point_id = aws_efs_access_point.teamcity_efs_data_access_point.id
+        iam             = "ENABLED"
+      }
+    }
+  }
+}
+
+# ECS Service
+resource "aws_ecs_service" "teamcity" {
+  name                   = local.name_prefix
+  cluster                = var.cluster_name != null ? data.aws_ecs_cluster.teamcity_cluster[0].arn : aws_ecs_cluster.teamcity_cluster[0].arn
+  task_definition        = aws_ecs_task_definition.teamcity_task_definition.arn
+  launch_type            = "FARGATE"
+  desired_count          = var.desired_container_count
+  force_new_deployment   = var.debug
+  enable_execute_command = var.debug
+
+  wait_for_steady_state = false #TODO: make this configurable
+
+  network_configuration {
+    subnets         = var.service_subnets
+    security_groups = [aws_security_group.teamcity_service_sg.id]
+  }
+  dynamic "load_balancer" {
+    for_each = var.create_external_alb ? [1] : []
+    content {
+      target_group_arn = aws_lb_target_group.teamcity_target_group[0].arn
+      container_name   = var.container_name
+      container_port   = var.container_port
+
+    }
+  }
+
+  tags = local.tags
+}
+
+###################################
+# Security Groups                 #
+###################################
+
+# TeamCity service security group
+resource "aws_security_group" "teamcity_service_sg" {
+  name        = "${local.name_prefix}-service-sg"
+  vpc_id      = var.vpc_id
+  description = "TeamCity service security group"
+  tags        = local.tags
+}
+
+# TeamCity EFS security group
+resource "aws_security_group" "teamcity_efs_sg" {
+  # count       = var.efs_id == null ? 1 : 0
+  name        = "${local.name_prefix}-efs-sg"
+  description = "TeamCity EFS mount target security group"
+  vpc_id      = var.vpc_id
+  tags        = local.tags
+}
+
+# Ingress rule for NFS traffic from service to EFS
+resource "aws_vpc_security_group_ingress_rule" "service_efs" {
+  # count                        = var.efs_id == null ? 1 : 0
+  security_group_id            = aws_security_group.teamcity_efs_sg.id
+  referenced_security_group_id = aws_security_group.teamcity_service_sg.id
+  description                  = "Allow inbound access from TeamCity service containers to EFS"
+  ip_protocol                  = "TCP"
+  from_port                    = 2049
+  to_port                      = 2049
+}
+
+# Ingress rule for NFS traffic from EFS to service
+resource "aws_vpc_security_group_ingress_rule" "efs_service" {
+  # count                        = var.efs_id == null ? 1 : 0
+  security_group_id            = aws_security_group.teamcity_service_sg.id
+  referenced_security_group_id = aws_security_group.teamcity_efs_sg.id
+  description                  = "Allow inbound access from EFS to TeamCity service containers"
+  ip_protocol                  = "TCP"
+  from_port                    = 2049
+  to_port                      = 2049
+}
+
+# TeamCity Aurora Serverless PostgreSQL security group
+resource "aws_security_group" "teamcity_db_sg" {
+  count       = var.database_connection_string == null ? 1 : 0
+  name        = "${local.name_prefix}-db-sg"
+  description = "TeamCity DB security group"
+  vpc_id      = var.vpc_id
+  tags        = local.tags
+}
+
+# Ingress rule for PostgreSQL from service to database cluster
+resource "aws_vpc_security_group_ingress_rule" "service_db" {
+  count                        = var.database_connection_string == null ? 1 : 0
+  security_group_id            = aws_security_group.teamcity_db_sg[0].id
+  referenced_security_group_id = aws_security_group.teamcity_service_sg.id
+  description                  = "Allow inbound access from TeamCity service containers to DB"
+  ip_protocol                  = "TCP"
+  from_port                    = 5432
+  to_port                      = 5432
+}
+
+# Ingress rule for PostgreSQL from database cluster to service
+resource "aws_vpc_security_group_ingress_rule" "db_service" {
+  count                        = var.database_connection_string == null ? 1 : 0
+  security_group_id            = aws_security_group.teamcity_service_sg.id
+  referenced_security_group_id = aws_security_group.teamcity_db_sg[0].id
+  description                  = "Allow inbound access from DB to TeamCity service containers"
+  ip_protocol                  = "TCP"
+  from_port                    = 5432
+  to_port                      = 5432
+}
+
+# Egress rule for database to let all traffic out
+resource "aws_vpc_security_group_egress_rule" "db_outbound" {
+  count             = var.database_connection_string == null ? 1 : 0
+  security_group_id = aws_security_group.teamcity_db_sg[0].id
+  description       = "Allow outbound access from DB to all"
+  cidr_ipv4         = "0.0.0.0/0"
+  ip_protocol       = "-1"
+}
+
+# TeamCity ALB security group
+resource "aws_security_group" "teamcity_alb_sg" {
+  #checkov:skip=CKV2_AWS_5:SG is attached to TeamCity service ALB
+  count       = var.create_external_alb ? 1 : 0
+  name        = "${local.name_prefix}-alb-sg"
+  vpc_id      = var.vpc_id
+  description = "TeamCity ALB security group"
+  tags        = local.tags
+}
+
+# Ingress rule for HTTP traffic from ALB to service
+resource "aws_vpc_security_group_ingress_rule" "service_inbound_alb" {
+  count                        = var.create_external_alb ? 1 : 0
+  security_group_id            = aws_security_group.teamcity_service_sg.id
+  referenced_security_group_id = aws_security_group.teamcity_alb_sg[0].id
+  description                  = "Allow inbound HTTP traffic from ALB to service containers"
+  from_port                    = var.container_port
+  to_port                      = var.container_port
+  ip_protocol                  = "TCP"
+}
+
+# Egress rule for HTTP traffic from ALB to service
+resource "aws_vpc_security_group_egress_rule" "service_outbound_alb" {
+  count                        = var.create_external_alb ? 1 : 0
+  security_group_id            = aws_security_group.teamcity_alb_sg[0].id
+  referenced_security_group_id = aws_security_group.teamcity_service_sg.id
+  description                  = "Allow outbound HTTP traffic from ALB to service containers"
+  from_port                    = var.container_port
+  to_port                      = var.container_port
+  ip_protocol                  = "TCP"
+}
+
+# Grant TeamCity service access to internet
+resource "aws_vpc_security_group_egress_rule" "internet_outbound" {
+  security_group_id = aws_security_group.teamcity_service_sg.id
+  description       = "Allow outbound internet access from TeamCity service containers"
+  cidr_ipv4         = "0.0.0.0/0"
+  ip_protocol       = "-1"
+}
+
+#############################################
+# IAM Roles for TeamCity Module
+#############################################
+
+data "aws_iam_policy_document" "ecs_tasks_trust_relationship" {
+  statement {
+    effect  = "Allow"
+    actions = ["sts:AssumeRole"]
+    principals {
+      type        = "Service"
+      identifiers = ["ecs-tasks.amazonaws.com"]
+    }
+  }
+}
+
+data "aws_iam_policy_document" "teamcity_default_policy" {
+  # ECS
+  statement {
+    sid    = "ECSExec"
+    effect = "Allow"
+    actions = [
+      "ssmmessages:OpenDataChannel",
+      "ssmmessages:OpenControlChannel",
+      "ssmmessages:CreateDataChannel",
+      "ssmmessages:CreateControlChannel",
+    ]
+    resources = [
+      "*"
+    ]
+  }
+
+  #grant the task necessary EFS permissions to be able to modify directories
+  statement {
+    sid    = "EFS"
+    effect = "Allow"
+    actions = [
+      "elasticfilesystem:ClientWrite",
+      "elasticfilesystem:ClientRootAccess",
+      "elasticfilesystem:ClientMount"
+    ]
+    resources = [
+      aws_efs_file_system.teamcity_efs_file_system[0].arn
+    ]
+  }
+}
+
+resource "aws_iam_policy" "teamcity_default_policy" {
+  name        = "teamcity-default-policy"
+  description = "Policy granting permissions for Unreal Horde."
+  policy      = data.aws_iam_policy_document.teamcity_default_policy.json
+}
+
+resource "aws_iam_role" "teamcity_default_role" {
+  name               = "teamcity-default-role"
+  description        = "Default role for TeamCity ECS task."
+  assume_role_policy = data.aws_iam_policy_document.ecs_tasks_trust_relationship.json
+  managed_policy_arns = [
+    aws_iam_policy.teamcity_default_policy.arn
+  ]
+
+  tags = local.tags
+}
+resource "aws_iam_role" "teamcity_task_execution_role" {
+  name = "teamcity-task-execution-role"
+
+  assume_role_policy  = data.aws_iam_policy_document.ecs_tasks_trust_relationship.json
+  managed_policy_arns = ["arn:aws:iam::aws:policy/service-role/AmazonECSTaskExecutionRolePolicy"]
+
+  inline_policy {
+    name = "teamcity-secrets-access"
+    policy = jsonencode({
+      Version = "2012-10-17"
+      Statement = [
+        {
+          Sid    = "SecretsManager"
+          Effect = "Allow"
+          Action = [
+            "secretsmanager:GetSecretValue"
+          ]
+          Resource = [
+            aws_rds_cluster.teamcity_db_cluster[0].master_user_secret[0].secret_arn
+          ]
+        }
+      ]
+    })
+  }
+}
+
+
+# CloudWatch Logs
+resource "aws_cloudwatch_log_group" "teamcity_log_group" {
+  #checkov:skip=CKV_AWS_158: KMS Encryption disabled by default
+  name              = "${local.name_prefix}-log-group"
+  retention_in_days = var.teamcity_cloudwatch_log_retention_in_days
+  tags              = local.tags
+}
+
+# Load Balancer for TeamCity Service
+resource "aws_lb" "teamcity_external_lb" {
+  count              = var.create_external_alb ? 1 : 0
+  name               = "${local.name_prefix}-lb"
+  security_groups    = [aws_security_group.teamcity_alb_sg[0].id]
+  load_balancer_type = "application"
+  internal           = false
+  subnets            = var.alb_subnets
+
+
+  dynamic "access_logs" {
+    for_each = var.enable_teamcity_alb_access_logs ? [1] : []
+    content {
+      enabled = var.enable_teamcity_alb_access_logs
+      bucket  = var.teamcity_alb_access_logs_bucket != null ? var.teamcity_alb_access_logs_bucket : aws_s3_bucket.teamcity_alb_access_logs_bucket[0].id
+      prefix  = var.teamcity_alb_access_logs_prefix != null ? var.teamcity_alb_access_logs_prefix : "${local.name_prefix}-alb"
+    }
+  }
+  #checkov:skip=CKV_AWS_150:Deletion protection disabled by default
+  enable_deletion_protection = var.enable_teamcity_alb_deletion_protection
+
+  #checkov:skip=CKV2_AWS_28: ALB access is managed with SG allow listing
+
+  drop_invalid_header_fields = true
+  tags                       = local.tags
+}
path: modules/teamcity/main.tf
user: login: github-advanced-security[bot]
body: ## Ensure that Load Balancer has deletion protection enabled

Ensure that Load Balancer has deletion protection enabled

[Show more details](https://github.com/aws-games/cloud-game-development-toolkit/security/code-scanning/835)
created_at: 2025-03-06T22:53:04Z
author_association: NONE
diff_hunk: @@ -0,0 +1,617 @@
+###################################
+# ECS Cluster for TeamCity Server #
+###################################
+# If cluster name is provided use a data source to access existing resource
+data "aws_ecs_cluster" "teamcity_cluster" {
+  count        = var.cluster_name != null ? 1 : 0
+  cluster_name = var.cluster_name
+}
+resource "aws_ecs_cluster" "teamcity_cluster" {
+  count = var.cluster_name != null ? 0 : 1
+  name  = "${local.name_prefix}-cluster"
+
+  setting {
+    name  = "containerInsights"
+    value = "enabled"
+  }
+
+  tags = local.tags
+}
+
+# TeamCity Task Definition
+resource "aws_ecs_task_definition" "teamcity_task_definition" {
+  family                   = var.name
+  requires_compatibilities = ["FARGATE"]
+  network_mode             = "awsvpc"
+  cpu                      = var.container_cpu
+  memory                   = var.container_memory
+
+  container_definitions = jsonencode([
+    {
+      name      = var.container_name
+      image     = local.image
+      cpu       = var.container_cpu
+      memory    = var.container_memory
+      essential = true
+
+      portMappings = [
+        {
+          containerPort = var.container_port
+          hostPort      = var.container_port
+        }
+      ]
+      logConfiguration = {
+        logDriver = "awslogs"
+        options = {
+          awslogs-group         = aws_cloudwatch_log_group.teamcity_log_group.name
+          awslogs-region        = data.aws_region.current.name
+          awslogs-stream-prefix = "[APP]"
+        }
+      }
+
+      mountPoints = [
+        {
+          sourceVolume  = "teamcity_data",
+          containerPath = "/data/teamcity_server/datadir"
+          readOnly      = false
+        }
+      ]
+
+      # # Set environment variables for database connection string
+      # environment = [
+      #   {
+      #     name  = "TEAMCITY_DB_URL"
+      #     value = local.database_connection_string
+      #   },
+      #   {
+      #     name  = "TEAMCITY_DB_USER"
+      #     value = local.database_master_username
+      #   },
+      #   # {
+      #   #   name  = "TEAMCITY_LOGS_PATH"
+      #   #   value = "/data/teamcity_server/logs"
+      #   # },
+      #   {
+      #     name  = "TEAMCITY_DATA_PATH"
+      #     value = "/data/teamcity_server/datadir"
+      #   }
+      # ]
+      # # Define password environment variable if provided
+      # password_env = local.database_master_password != null ? [
+      #   {
+      #     name  = "TEAMCITY_DB_PASSWORD"
+      #     value = local.database_master_password
+      #   }
+      # ] : []
+
+      # Combine the lists
+      environment = concat(local.base_env, local.password_env)
+
+      secrets = var.database_connection_string == null ? [
+        {
+          name      = "TEAMCITY_DB_PASSWORD"
+          valueFrom = "${aws_rds_cluster.teamcity_db_cluster[0].master_user_secret[0].secret_arn}:password::"
+        }
+      ] : []
+    }
+  ])
+  tags = {
+    Name = var.name
+  }
+  task_role_arn      = aws_iam_role.teamcity_default_role.arn
+  execution_role_arn = aws_iam_role.teamcity_task_execution_role.arn
+
+  volume {
+    name = "teamcity_data"
+    efs_volume_configuration {
+      file_system_id          = aws_efs_file_system.teamcity_efs_file_system[0].id
+      transit_encryption      = "ENABLED"
+      transit_encryption_port = 2999
+      authorization_config {
+        access_point_id = aws_efs_access_point.teamcity_efs_data_access_point.id
+        iam             = "ENABLED"
+      }
+    }
+  }
+}
+
+# ECS Service
+resource "aws_ecs_service" "teamcity" {
+  name                   = local.name_prefix
+  cluster                = var.cluster_name != null ? data.aws_ecs_cluster.teamcity_cluster[0].arn : aws_ecs_cluster.teamcity_cluster[0].arn
+  task_definition        = aws_ecs_task_definition.teamcity_task_definition.arn
+  launch_type            = "FARGATE"
+  desired_count          = var.desired_container_count
+  force_new_deployment   = var.debug
+  enable_execute_command = var.debug
+
+  wait_for_steady_state = false #TODO: make this configurable
+
+  network_configuration {
+    subnets         = var.service_subnets
+    security_groups = [aws_security_group.teamcity_service_sg.id]
+  }
+  dynamic "load_balancer" {
+    for_each = var.create_external_alb ? [1] : []
+    content {
+      target_group_arn = aws_lb_target_group.teamcity_target_group[0].arn
+      container_name   = var.container_name
+      container_port   = var.container_port
+
+    }
+  }
+
+  tags = local.tags
+}
+
+###################################
+# Security Groups                 #
+###################################
+
+# TeamCity service security group
+resource "aws_security_group" "teamcity_service_sg" {
+  name        = "${local.name_prefix}-service-sg"
+  vpc_id      = var.vpc_id
+  description = "TeamCity service security group"
+  tags        = local.tags
+}
+
+# TeamCity EFS security group
+resource "aws_security_group" "teamcity_efs_sg" {
+  # count       = var.efs_id == null ? 1 : 0
+  name        = "${local.name_prefix}-efs-sg"
+  description = "TeamCity EFS mount target security group"
+  vpc_id      = var.vpc_id
+  tags        = local.tags
+}
+
+# Ingress rule for NFS traffic from service to EFS
+resource "aws_vpc_security_group_ingress_rule" "service_efs" {
+  # count                        = var.efs_id == null ? 1 : 0
+  security_group_id            = aws_security_group.teamcity_efs_sg.id
+  referenced_security_group_id = aws_security_group.teamcity_service_sg.id
+  description                  = "Allow inbound access from TeamCity service containers to EFS"
+  ip_protocol                  = "TCP"
+  from_port                    = 2049
+  to_port                      = 2049
+}
+
+# Ingress rule for NFS traffic from EFS to service
+resource "aws_vpc_security_group_ingress_rule" "efs_service" {
+  # count                        = var.efs_id == null ? 1 : 0
+  security_group_id            = aws_security_group.teamcity_service_sg.id
+  referenced_security_group_id = aws_security_group.teamcity_efs_sg.id
+  description                  = "Allow inbound access from EFS to TeamCity service containers"
+  ip_protocol                  = "TCP"
+  from_port                    = 2049
+  to_port                      = 2049
+}
+
+# TeamCity Aurora Serverless PostgreSQL security group
+resource "aws_security_group" "teamcity_db_sg" {
+  count       = var.database_connection_string == null ? 1 : 0
+  name        = "${local.name_prefix}-db-sg"
+  description = "TeamCity DB security group"
+  vpc_id      = var.vpc_id
+  tags        = local.tags
+}
+
+# Ingress rule for PostgreSQL from service to database cluster
+resource "aws_vpc_security_group_ingress_rule" "service_db" {
+  count                        = var.database_connection_string == null ? 1 : 0
+  security_group_id            = aws_security_group.teamcity_db_sg[0].id
+  referenced_security_group_id = aws_security_group.teamcity_service_sg.id
+  description                  = "Allow inbound access from TeamCity service containers to DB"
+  ip_protocol                  = "TCP"
+  from_port                    = 5432
+  to_port                      = 5432
+}
+
+# Ingress rule for PostgreSQL from database cluster to service
+resource "aws_vpc_security_group_ingress_rule" "db_service" {
+  count                        = var.database_connection_string == null ? 1 : 0
+  security_group_id            = aws_security_group.teamcity_service_sg.id
+  referenced_security_group_id = aws_security_group.teamcity_db_sg[0].id
+  description                  = "Allow inbound access from DB to TeamCity service containers"
+  ip_protocol                  = "TCP"
+  from_port                    = 5432
+  to_port                      = 5432
+}
+
+# Egress rule for database to let all traffic out
+resource "aws_vpc_security_group_egress_rule" "db_outbound" {
+  count             = var.database_connection_string == null ? 1 : 0
+  security_group_id = aws_security_group.teamcity_db_sg[0].id
+  description       = "Allow outbound access from DB to all"
+  cidr_ipv4         = "0.0.0.0/0"
+  ip_protocol       = "-1"
+}
+
+# TeamCity ALB security group
+resource "aws_security_group" "teamcity_alb_sg" {
+  #checkov:skip=CKV2_AWS_5:SG is attached to TeamCity service ALB
+  count       = var.create_external_alb ? 1 : 0
+  name        = "${local.name_prefix}-alb-sg"
+  vpc_id      = var.vpc_id
+  description = "TeamCity ALB security group"
+  tags        = local.tags
+}
+
+# Ingress rule for HTTP traffic from ALB to service
+resource "aws_vpc_security_group_ingress_rule" "service_inbound_alb" {
+  count                        = var.create_external_alb ? 1 : 0
+  security_group_id            = aws_security_group.teamcity_service_sg.id
+  referenced_security_group_id = aws_security_group.teamcity_alb_sg[0].id
+  description                  = "Allow inbound HTTP traffic from ALB to service containers"
+  from_port                    = var.container_port
+  to_port                      = var.container_port
+  ip_protocol                  = "TCP"
+}
+
+# Egress rule for HTTP traffic from ALB to service
+resource "aws_vpc_security_group_egress_rule" "service_outbound_alb" {
+  count                        = var.create_external_alb ? 1 : 0
+  security_group_id            = aws_security_group.teamcity_alb_sg[0].id
+  referenced_security_group_id = aws_security_group.teamcity_service_sg.id
+  description                  = "Allow outbound HTTP traffic from ALB to service containers"
+  from_port                    = var.container_port
+  to_port                      = var.container_port
+  ip_protocol                  = "TCP"
+}
+
+# Grant TeamCity service access to internet
+resource "aws_vpc_security_group_egress_rule" "internet_outbound" {
+  security_group_id = aws_security_group.teamcity_service_sg.id
+  description       = "Allow outbound internet access from TeamCity service containers"
+  cidr_ipv4         = "0.0.0.0/0"
+  ip_protocol       = "-1"
+}
+
+#############################################
+# IAM Roles for TeamCity Module
+#############################################
+
+data "aws_iam_policy_document" "ecs_tasks_trust_relationship" {
+  statement {
+    effect  = "Allow"
+    actions = ["sts:AssumeRole"]
+    principals {
+      type        = "Service"
+      identifiers = ["ecs-tasks.amazonaws.com"]
+    }
+  }
+}
+
+data "aws_iam_policy_document" "teamcity_default_policy" {
+  # ECS
+  statement {
+    sid    = "ECSExec"
+    effect = "Allow"
+    actions = [
+      "ssmmessages:OpenDataChannel",
+      "ssmmessages:OpenControlChannel",
+      "ssmmessages:CreateDataChannel",
+      "ssmmessages:CreateControlChannel",
+    ]
+    resources = [
+      "*"
+    ]
+  }
+
+  #grant the task necessary EFS permissions to be able to modify directories
+  statement {
+    sid    = "EFS"
+    effect = "Allow"
+    actions = [
+      "elasticfilesystem:ClientWrite",
+      "elasticfilesystem:ClientRootAccess",
+      "elasticfilesystem:ClientMount"
+    ]
+    resources = [
+      aws_efs_file_system.teamcity_efs_file_system[0].arn
+    ]
+  }
+}
+
+resource "aws_iam_policy" "teamcity_default_policy" {
+  name        = "teamcity-default-policy"
+  description = "Policy granting permissions for Unreal Horde."
+  policy      = data.aws_iam_policy_document.teamcity_default_policy.json
+}
+
+resource "aws_iam_role" "teamcity_default_role" {
+  name               = "teamcity-default-role"
+  description        = "Default role for TeamCity ECS task."
+  assume_role_policy = data.aws_iam_policy_document.ecs_tasks_trust_relationship.json
+  managed_policy_arns = [
+    aws_iam_policy.teamcity_default_policy.arn
+  ]
+
+  tags = local.tags
+}
+resource "aws_iam_role" "teamcity_task_execution_role" {
+  name = "teamcity-task-execution-role"
+
+  assume_role_policy  = data.aws_iam_policy_document.ecs_tasks_trust_relationship.json
+  managed_policy_arns = ["arn:aws:iam::aws:policy/service-role/AmazonECSTaskExecutionRolePolicy"]
+
+  inline_policy {
+    name = "teamcity-secrets-access"
+    policy = jsonencode({
+      Version = "2012-10-17"
+      Statement = [
+        {
+          Sid    = "SecretsManager"
+          Effect = "Allow"
+          Action = [
+            "secretsmanager:GetSecretValue"
+          ]
+          Resource = [
+            aws_rds_cluster.teamcity_db_cluster[0].master_user_secret[0].secret_arn
+          ]
+        }
+      ]
+    })
+  }
+}
+
+
+# CloudWatch Logs
+resource "aws_cloudwatch_log_group" "teamcity_log_group" {
+  #checkov:skip=CKV_AWS_158: KMS Encryption disabled by default
+  name              = "${local.name_prefix}-log-group"
+  retention_in_days = var.teamcity_cloudwatch_log_retention_in_days
+  tags              = local.tags
+}
+
+# Load Balancer for TeamCity Service
+resource "aws_lb" "teamcity_external_lb" {
+  count              = var.create_external_alb ? 1 : 0
+  name               = "${local.name_prefix}-lb"
+  security_groups    = [aws_security_group.teamcity_alb_sg[0].id]
+  load_balancer_type = "application"
+  internal           = false
+  subnets            = var.alb_subnets
+
+
+  dynamic "access_logs" {
+    for_each = var.enable_teamcity_alb_access_logs ? [1] : []
+    content {
+      enabled = var.enable_teamcity_alb_access_logs
+      bucket  = var.teamcity_alb_access_logs_bucket != null ? var.teamcity_alb_access_logs_bucket : aws_s3_bucket.teamcity_alb_access_logs_bucket[0].id
+      prefix  = var.teamcity_alb_access_logs_prefix != null ? var.teamcity_alb_access_logs_prefix : "${local.name_prefix}-alb"
+    }
+  }
+  #checkov:skip=CKV_AWS_150:Deletion protection disabled by default
+  enable_deletion_protection = var.enable_teamcity_alb_deletion_protection
+
+  #checkov:skip=CKV2_AWS_28: ALB access is managed with SG allow listing
+
+  drop_invalid_header_fields = true
+  tags                       = local.tags
+}
path: modules/teamcity/main.tf
user: login: github-advanced-security[bot]
body: ## Ensure public facing ALB are protected by WAF

Ensure public facing ALB are protected by WAF

[Show more details](https://github.com/aws-games/cloud-game-development-toolkit/security/code-scanning/840)
created_at: 2025-03-06T22:53:04Z
author_association: NONE
diff_hunk: @@ -0,0 +1,617 @@
+###################################
+# ECS Cluster for TeamCity Server #
+###################################
+# If cluster name is provided use a data source to access existing resource
+data "aws_ecs_cluster" "teamcity_cluster" {
+  count        = var.cluster_name != null ? 1 : 0
+  cluster_name = var.cluster_name
+}
+resource "aws_ecs_cluster" "teamcity_cluster" {
+  count = var.cluster_name != null ? 0 : 1
+  name  = "${local.name_prefix}-cluster"
+
+  setting {
+    name  = "containerInsights"
+    value = "enabled"
+  }
+
+  tags = local.tags
+}
+
+# TeamCity Task Definition
+resource "aws_ecs_task_definition" "teamcity_task_definition" {
+  family                   = var.name
+  requires_compatibilities = ["FARGATE"]
+  network_mode             = "awsvpc"
+  cpu                      = var.container_cpu
+  memory                   = var.container_memory
+
+  container_definitions = jsonencode([
+    {
+      name      = var.container_name
+      image     = local.image
+      cpu       = var.container_cpu
+      memory    = var.container_memory
+      essential = true
+
+      portMappings = [
+        {
+          containerPort = var.container_port
+          hostPort      = var.container_port
+        }
+      ]
+      logConfiguration = {
+        logDriver = "awslogs"
+        options = {
+          awslogs-group         = aws_cloudwatch_log_group.teamcity_log_group.name
+          awslogs-region        = data.aws_region.current.name
+          awslogs-stream-prefix = "[APP]"
+        }
+      }
+
+      mountPoints = [
+        {
+          sourceVolume  = "teamcity_data",
+          containerPath = "/data/teamcity_server/datadir"
+          readOnly      = false
+        }
+      ]
+
+      # # Set environment variables for database connection string
+      # environment = [
+      #   {
+      #     name  = "TEAMCITY_DB_URL"
+      #     value = local.database_connection_string
+      #   },
+      #   {
+      #     name  = "TEAMCITY_DB_USER"
+      #     value = local.database_master_username
+      #   },
+      #   # {
+      #   #   name  = "TEAMCITY_LOGS_PATH"
+      #   #   value = "/data/teamcity_server/logs"
+      #   # },
+      #   {
+      #     name  = "TEAMCITY_DATA_PATH"
+      #     value = "/data/teamcity_server/datadir"
+      #   }
+      # ]
+      # # Define password environment variable if provided
+      # password_env = local.database_master_password != null ? [
+      #   {
+      #     name  = "TEAMCITY_DB_PASSWORD"
+      #     value = local.database_master_password
+      #   }
+      # ] : []
+
+      # Combine the lists
+      environment = concat(local.base_env, local.password_env)
+
+      secrets = var.database_connection_string == null ? [
+        {
+          name      = "TEAMCITY_DB_PASSWORD"
+          valueFrom = "${aws_rds_cluster.teamcity_db_cluster[0].master_user_secret[0].secret_arn}:password::"
+        }
+      ] : []
+    }
+  ])
+  tags = {
+    Name = var.name
+  }
+  task_role_arn      = aws_iam_role.teamcity_default_role.arn
+  execution_role_arn = aws_iam_role.teamcity_task_execution_role.arn
+
+  volume {
+    name = "teamcity_data"
+    efs_volume_configuration {
+      file_system_id          = aws_efs_file_system.teamcity_efs_file_system[0].id
+      transit_encryption      = "ENABLED"
+      transit_encryption_port = 2999
+      authorization_config {
+        access_point_id = aws_efs_access_point.teamcity_efs_data_access_point.id
+        iam             = "ENABLED"
+      }
+    }
+  }
+}
+
+# ECS Service
+resource "aws_ecs_service" "teamcity" {
+  name                   = local.name_prefix
+  cluster                = var.cluster_name != null ? data.aws_ecs_cluster.teamcity_cluster[0].arn : aws_ecs_cluster.teamcity_cluster[0].arn
+  task_definition        = aws_ecs_task_definition.teamcity_task_definition.arn
+  launch_type            = "FARGATE"
+  desired_count          = var.desired_container_count
+  force_new_deployment   = var.debug
+  enable_execute_command = var.debug
+
+  wait_for_steady_state = false #TODO: make this configurable
+
+  network_configuration {
+    subnets         = var.service_subnets
+    security_groups = [aws_security_group.teamcity_service_sg.id]
+  }
+  dynamic "load_balancer" {
+    for_each = var.create_external_alb ? [1] : []
+    content {
+      target_group_arn = aws_lb_target_group.teamcity_target_group[0].arn
+      container_name   = var.container_name
+      container_port   = var.container_port
+
+    }
+  }
+
+  tags = local.tags
+}
+
+###################################
+# Security Groups                 #
+###################################
+
+# TeamCity service security group
+resource "aws_security_group" "teamcity_service_sg" {
+  name        = "${local.name_prefix}-service-sg"
+  vpc_id      = var.vpc_id
+  description = "TeamCity service security group"
+  tags        = local.tags
+}
+
+# TeamCity EFS security group
+resource "aws_security_group" "teamcity_efs_sg" {
+  # count       = var.efs_id == null ? 1 : 0
+  name        = "${local.name_prefix}-efs-sg"
+  description = "TeamCity EFS mount target security group"
+  vpc_id      = var.vpc_id
+  tags        = local.tags
+}
+
+# Ingress rule for NFS traffic from service to EFS
+resource "aws_vpc_security_group_ingress_rule" "service_efs" {
+  # count                        = var.efs_id == null ? 1 : 0
+  security_group_id            = aws_security_group.teamcity_efs_sg.id
+  referenced_security_group_id = aws_security_group.teamcity_service_sg.id
+  description                  = "Allow inbound access from TeamCity service containers to EFS"
+  ip_protocol                  = "TCP"
+  from_port                    = 2049
+  to_port                      = 2049
+}
+
+# Ingress rule for NFS traffic from EFS to service
+resource "aws_vpc_security_group_ingress_rule" "efs_service" {
+  # count                        = var.efs_id == null ? 1 : 0
+  security_group_id            = aws_security_group.teamcity_service_sg.id
+  referenced_security_group_id = aws_security_group.teamcity_efs_sg.id
+  description                  = "Allow inbound access from EFS to TeamCity service containers"
+  ip_protocol                  = "TCP"
+  from_port                    = 2049
+  to_port                      = 2049
+}
+
+# TeamCity Aurora Serverless PostgreSQL security group
+resource "aws_security_group" "teamcity_db_sg" {
+  count       = var.database_connection_string == null ? 1 : 0
+  name        = "${local.name_prefix}-db-sg"
+  description = "TeamCity DB security group"
+  vpc_id      = var.vpc_id
+  tags        = local.tags
+}
+
+# Ingress rule for PostgreSQL from service to database cluster
+resource "aws_vpc_security_group_ingress_rule" "service_db" {
+  count                        = var.database_connection_string == null ? 1 : 0
+  security_group_id            = aws_security_group.teamcity_db_sg[0].id
+  referenced_security_group_id = aws_security_group.teamcity_service_sg.id
+  description                  = "Allow inbound access from TeamCity service containers to DB"
+  ip_protocol                  = "TCP"
+  from_port                    = 5432
+  to_port                      = 5432
+}
+
+# Ingress rule for PostgreSQL from database cluster to service
+resource "aws_vpc_security_group_ingress_rule" "db_service" {
+  count                        = var.database_connection_string == null ? 1 : 0
+  security_group_id            = aws_security_group.teamcity_service_sg.id
+  referenced_security_group_id = aws_security_group.teamcity_db_sg[0].id
+  description                  = "Allow inbound access from DB to TeamCity service containers"
+  ip_protocol                  = "TCP"
+  from_port                    = 5432
+  to_port                      = 5432
+}
+
+# Egress rule for database to let all traffic out
+resource "aws_vpc_security_group_egress_rule" "db_outbound" {
+  count             = var.database_connection_string == null ? 1 : 0
+  security_group_id = aws_security_group.teamcity_db_sg[0].id
+  description       = "Allow outbound access from DB to all"
+  cidr_ipv4         = "0.0.0.0/0"
+  ip_protocol       = "-1"
+}
+
+# TeamCity ALB security group
+resource "aws_security_group" "teamcity_alb_sg" {
+  #checkov:skip=CKV2_AWS_5:SG is attached to TeamCity service ALB
+  count       = var.create_external_alb ? 1 : 0
+  name        = "${local.name_prefix}-alb-sg"
+  vpc_id      = var.vpc_id
+  description = "TeamCity ALB security group"
+  tags        = local.tags
+}
+
+# Ingress rule for HTTP traffic from ALB to service
+resource "aws_vpc_security_group_ingress_rule" "service_inbound_alb" {
+  count                        = var.create_external_alb ? 1 : 0
+  security_group_id            = aws_security_group.teamcity_service_sg.id
+  referenced_security_group_id = aws_security_group.teamcity_alb_sg[0].id
+  description                  = "Allow inbound HTTP traffic from ALB to service containers"
+  from_port                    = var.container_port
+  to_port                      = var.container_port
+  ip_protocol                  = "TCP"
+}
+
+# Egress rule for HTTP traffic from ALB to service
+resource "aws_vpc_security_group_egress_rule" "service_outbound_alb" {
+  count                        = var.create_external_alb ? 1 : 0
+  security_group_id            = aws_security_group.teamcity_alb_sg[0].id
+  referenced_security_group_id = aws_security_group.teamcity_service_sg.id
+  description                  = "Allow outbound HTTP traffic from ALB to service containers"
+  from_port                    = var.container_port
+  to_port                      = var.container_port
+  ip_protocol                  = "TCP"
+}
+
+# Grant TeamCity service access to internet
+resource "aws_vpc_security_group_egress_rule" "internet_outbound" {
+  security_group_id = aws_security_group.teamcity_service_sg.id
+  description       = "Allow outbound internet access from TeamCity service containers"
+  cidr_ipv4         = "0.0.0.0/0"
+  ip_protocol       = "-1"
+}
+
+#############################################
+# IAM Roles for TeamCity Module
+#############################################
+
+data "aws_iam_policy_document" "ecs_tasks_trust_relationship" {
+  statement {
+    effect  = "Allow"
+    actions = ["sts:AssumeRole"]
+    principals {
+      type        = "Service"
+      identifiers = ["ecs-tasks.amazonaws.com"]
+    }
+  }
+}
+
+data "aws_iam_policy_document" "teamcity_default_policy" {
+  # ECS
+  statement {
+    sid    = "ECSExec"
+    effect = "Allow"
+    actions = [
+      "ssmmessages:OpenDataChannel",
+      "ssmmessages:OpenControlChannel",
+      "ssmmessages:CreateDataChannel",
+      "ssmmessages:CreateControlChannel",
+    ]
+    resources = [
+      "*"
+    ]
+  }
+
+  #grant the task necessary EFS permissions to be able to modify directories
+  statement {
+    sid    = "EFS"
+    effect = "Allow"
+    actions = [
+      "elasticfilesystem:ClientWrite",
+      "elasticfilesystem:ClientRootAccess",
+      "elasticfilesystem:ClientMount"
+    ]
+    resources = [
+      aws_efs_file_system.teamcity_efs_file_system[0].arn
+    ]
+  }
+}
+
+resource "aws_iam_policy" "teamcity_default_policy" {
+  name        = "teamcity-default-policy"
+  description = "Policy granting permissions for Unreal Horde."
+  policy      = data.aws_iam_policy_document.teamcity_default_policy.json
+}
+
+resource "aws_iam_role" "teamcity_default_role" {
+  name               = "teamcity-default-role"
+  description        = "Default role for TeamCity ECS task."
+  assume_role_policy = data.aws_iam_policy_document.ecs_tasks_trust_relationship.json
+  managed_policy_arns = [
+    aws_iam_policy.teamcity_default_policy.arn
+  ]
+
+  tags = local.tags
+}
+resource "aws_iam_role" "teamcity_task_execution_role" {
+  name = "teamcity-task-execution-role"
+
+  assume_role_policy  = data.aws_iam_policy_document.ecs_tasks_trust_relationship.json
+  managed_policy_arns = ["arn:aws:iam::aws:policy/service-role/AmazonECSTaskExecutionRolePolicy"]
+
+  inline_policy {
+    name = "teamcity-secrets-access"
+    policy = jsonencode({
+      Version = "2012-10-17"
+      Statement = [
+        {
+          Sid    = "SecretsManager"
+          Effect = "Allow"
+          Action = [
+            "secretsmanager:GetSecretValue"
+          ]
+          Resource = [
+            aws_rds_cluster.teamcity_db_cluster[0].master_user_secret[0].secret_arn
+          ]
+        }
+      ]
+    })
+  }
+}
+
+
+# CloudWatch Logs
+resource "aws_cloudwatch_log_group" "teamcity_log_group" {
+  #checkov:skip=CKV_AWS_158: KMS Encryption disabled by default
+  name              = "${local.name_prefix}-log-group"
+  retention_in_days = var.teamcity_cloudwatch_log_retention_in_days
+  tags              = local.tags
+}
+
+# Load Balancer for TeamCity Service
+resource "aws_lb" "teamcity_external_lb" {
+  count              = var.create_external_alb ? 1 : 0
+  name               = "${local.name_prefix}-lb"
+  security_groups    = [aws_security_group.teamcity_alb_sg[0].id]
+  load_balancer_type = "application"
+  internal           = false
+  subnets            = var.alb_subnets
+
+
+  dynamic "access_logs" {
+    for_each = var.enable_teamcity_alb_access_logs ? [1] : []
+    content {
+      enabled = var.enable_teamcity_alb_access_logs
+      bucket  = var.teamcity_alb_access_logs_bucket != null ? var.teamcity_alb_access_logs_bucket : aws_s3_bucket.teamcity_alb_access_logs_bucket[0].id
+      prefix  = var.teamcity_alb_access_logs_prefix != null ? var.teamcity_alb_access_logs_prefix : "${local.name_prefix}-alb"
+    }
+  }
+  #checkov:skip=CKV_AWS_150:Deletion protection disabled by default
+  enable_deletion_protection = var.enable_teamcity_alb_deletion_protection
+
+  #checkov:skip=CKV2_AWS_28: ALB access is managed with SG allow listing
+
+  drop_invalid_header_fields = true
+  tags                       = local.tags
+}
+
+# TeamCity target group for ALB
+resource "aws_lb_target_group" "teamcity_target_group" {
+  count       = var.create_external_alb ? 1 : 0
+  name        = "${local.name_prefix}-tg"
+  port        = var.container_port
+  protocol    = "HTTP"
+  vpc_id      = var.vpc_id
+  target_type = "ip"
+
+  health_check {
+    path                = "/healthCheck/healthy"
+    interval            = 30
+    timeout             = 5
+    healthy_threshold   = 5
+    unhealthy_threshold = 2
+    port                = var.container_port
+    protocol            = "HTTP"
+    matcher             = "200"
+  }
+
+  tags = local.tags
+}
path: modules/teamcity/main.tf
user: login: github-advanced-security[bot]
body: ## Ensure AWS Load Balancer doesn't use HTTP protocol

Ensure AWS Load Balancer doesn't use HTTP protocol

[Show more details](https://github.com/aws-games/cloud-game-development-toolkit/security/code-scanning/834)
created_at: 2025-03-06T22:53:04Z
author_association: NONE
diff_hunk: @@ -0,0 +1,617 @@
+###################################
+# ECS Cluster for TeamCity Server #
+###################################
+# If cluster name is provided use a data source to access existing resource
+data "aws_ecs_cluster" "teamcity_cluster" {
+  count        = var.cluster_name != null ? 1 : 0
+  cluster_name = var.cluster_name
+}
+resource "aws_ecs_cluster" "teamcity_cluster" {
+  count = var.cluster_name != null ? 0 : 1
+  name  = "${local.name_prefix}-cluster"
+
+  setting {
+    name  = "containerInsights"
+    value = "enabled"
+  }
+
+  tags = local.tags
+}
+
+# TeamCity Task Definition
+resource "aws_ecs_task_definition" "teamcity_task_definition" {
+  family                   = var.name
+  requires_compatibilities = ["FARGATE"]
+  network_mode             = "awsvpc"
+  cpu                      = var.container_cpu
+  memory                   = var.container_memory
+
+  container_definitions = jsonencode([
+    {
+      name      = var.container_name
+      image     = local.image
+      cpu       = var.container_cpu
+      memory    = var.container_memory
+      essential = true
+
+      portMappings = [
+        {
+          containerPort = var.container_port
+          hostPort      = var.container_port
+        }
+      ]
+      logConfiguration = {
+        logDriver = "awslogs"
+        options = {
+          awslogs-group         = aws_cloudwatch_log_group.teamcity_log_group.name
+          awslogs-region        = data.aws_region.current.name
+          awslogs-stream-prefix = "[APP]"
+        }
+      }
+
+      mountPoints = [
+        {
+          sourceVolume  = "teamcity_data",
+          containerPath = "/data/teamcity_server/datadir"
+          readOnly      = false
+        }
+      ]
+
+      # # Set environment variables for database connection string
+      # environment = [
+      #   {
+      #     name  = "TEAMCITY_DB_URL"
+      #     value = local.database_connection_string
+      #   },
+      #   {
+      #     name  = "TEAMCITY_DB_USER"
+      #     value = local.database_master_username
+      #   },
+      #   # {
+      #   #   name  = "TEAMCITY_LOGS_PATH"
+      #   #   value = "/data/teamcity_server/logs"
+      #   # },
+      #   {
+      #     name  = "TEAMCITY_DATA_PATH"
+      #     value = "/data/teamcity_server/datadir"
+      #   }
+      # ]
+      # # Define password environment variable if provided
+      # password_env = local.database_master_password != null ? [
+      #   {
+      #     name  = "TEAMCITY_DB_PASSWORD"
+      #     value = local.database_master_password
+      #   }
+      # ] : []
+
+      # Combine the lists
+      environment = concat(local.base_env, local.password_env)
+
+      secrets = var.database_connection_string == null ? [
+        {
+          name      = "TEAMCITY_DB_PASSWORD"
+          valueFrom = "${aws_rds_cluster.teamcity_db_cluster[0].master_user_secret[0].secret_arn}:password::"
+        }
+      ] : []
+    }
+  ])
+  tags = {
+    Name = var.name
+  }
+  task_role_arn      = aws_iam_role.teamcity_default_role.arn
+  execution_role_arn = aws_iam_role.teamcity_task_execution_role.arn
+
+  volume {
+    name = "teamcity_data"
+    efs_volume_configuration {
+      file_system_id          = aws_efs_file_system.teamcity_efs_file_system[0].id
+      transit_encryption      = "ENABLED"
+      transit_encryption_port = 2999
+      authorization_config {
+        access_point_id = aws_efs_access_point.teamcity_efs_data_access_point.id
+        iam             = "ENABLED"
+      }
+    }
+  }
+}
+
+# ECS Service
+resource "aws_ecs_service" "teamcity" {
+  name                   = local.name_prefix
+  cluster                = var.cluster_name != null ? data.aws_ecs_cluster.teamcity_cluster[0].arn : aws_ecs_cluster.teamcity_cluster[0].arn
+  task_definition        = aws_ecs_task_definition.teamcity_task_definition.arn
+  launch_type            = "FARGATE"
+  desired_count          = var.desired_container_count
+  force_new_deployment   = var.debug
+  enable_execute_command = var.debug
+
+  wait_for_steady_state = false #TODO: make this configurable
+
+  network_configuration {
+    subnets         = var.service_subnets
+    security_groups = [aws_security_group.teamcity_service_sg.id]
+  }
+  dynamic "load_balancer" {
+    for_each = var.create_external_alb ? [1] : []
+    content {
+      target_group_arn = aws_lb_target_group.teamcity_target_group[0].arn
+      container_name   = var.container_name
+      container_port   = var.container_port
+
+    }
+  }
+
+  tags = local.tags
+}
+
+###################################
+# Security Groups                 #
+###################################
+
+# TeamCity service security group
+resource "aws_security_group" "teamcity_service_sg" {
+  name        = "${local.name_prefix}-service-sg"
+  vpc_id      = var.vpc_id
+  description = "TeamCity service security group"
+  tags        = local.tags
+}
+
+# TeamCity EFS security group
+resource "aws_security_group" "teamcity_efs_sg" {
+  # count       = var.efs_id == null ? 1 : 0
+  name        = "${local.name_prefix}-efs-sg"
+  description = "TeamCity EFS mount target security group"
+  vpc_id      = var.vpc_id
+  tags        = local.tags
+}
+
+# Ingress rule for NFS traffic from service to EFS
+resource "aws_vpc_security_group_ingress_rule" "service_efs" {
+  # count                        = var.efs_id == null ? 1 : 0
+  security_group_id            = aws_security_group.teamcity_efs_sg.id
+  referenced_security_group_id = aws_security_group.teamcity_service_sg.id
+  description                  = "Allow inbound access from TeamCity service containers to EFS"
+  ip_protocol                  = "TCP"
+  from_port                    = 2049
+  to_port                      = 2049
+}
+
+# Ingress rule for NFS traffic from EFS to service
+resource "aws_vpc_security_group_ingress_rule" "efs_service" {
+  # count                        = var.efs_id == null ? 1 : 0
+  security_group_id            = aws_security_group.teamcity_service_sg.id
+  referenced_security_group_id = aws_security_group.teamcity_efs_sg.id
+  description                  = "Allow inbound access from EFS to TeamCity service containers"
+  ip_protocol                  = "TCP"
+  from_port                    = 2049
+  to_port                      = 2049
+}
+
+# TeamCity Aurora Serverless PostgreSQL security group
+resource "aws_security_group" "teamcity_db_sg" {
+  count       = var.database_connection_string == null ? 1 : 0
+  name        = "${local.name_prefix}-db-sg"
+  description = "TeamCity DB security group"
+  vpc_id      = var.vpc_id
+  tags        = local.tags
+}
+
+# Ingress rule for PostgreSQL from service to database cluster
+resource "aws_vpc_security_group_ingress_rule" "service_db" {
+  count                        = var.database_connection_string == null ? 1 : 0
+  security_group_id            = aws_security_group.teamcity_db_sg[0].id
+  referenced_security_group_id = aws_security_group.teamcity_service_sg.id
+  description                  = "Allow inbound access from TeamCity service containers to DB"
+  ip_protocol                  = "TCP"
+  from_port                    = 5432
+  to_port                      = 5432
+}
+
+# Ingress rule for PostgreSQL from database cluster to service
+resource "aws_vpc_security_group_ingress_rule" "db_service" {
+  count                        = var.database_connection_string == null ? 1 : 0
+  security_group_id            = aws_security_group.teamcity_service_sg.id
+  referenced_security_group_id = aws_security_group.teamcity_db_sg[0].id
+  description                  = "Allow inbound access from DB to TeamCity service containers"
+  ip_protocol                  = "TCP"
+  from_port                    = 5432
+  to_port                      = 5432
+}
+
+# Egress rule for database to let all traffic out
+resource "aws_vpc_security_group_egress_rule" "db_outbound" {
+  count             = var.database_connection_string == null ? 1 : 0
+  security_group_id = aws_security_group.teamcity_db_sg[0].id
+  description       = "Allow outbound access from DB to all"
+  cidr_ipv4         = "0.0.0.0/0"
+  ip_protocol       = "-1"
+}
+
+# TeamCity ALB security group
+resource "aws_security_group" "teamcity_alb_sg" {
+  #checkov:skip=CKV2_AWS_5:SG is attached to TeamCity service ALB
+  count       = var.create_external_alb ? 1 : 0
+  name        = "${local.name_prefix}-alb-sg"
+  vpc_id      = var.vpc_id
+  description = "TeamCity ALB security group"
+  tags        = local.tags
+}
+
+# Ingress rule for HTTP traffic from ALB to service
+resource "aws_vpc_security_group_ingress_rule" "service_inbound_alb" {
+  count                        = var.create_external_alb ? 1 : 0
+  security_group_id            = aws_security_group.teamcity_service_sg.id
+  referenced_security_group_id = aws_security_group.teamcity_alb_sg[0].id
+  description                  = "Allow inbound HTTP traffic from ALB to service containers"
+  from_port                    = var.container_port
+  to_port                      = var.container_port
+  ip_protocol                  = "TCP"
+}
+
+# Egress rule for HTTP traffic from ALB to service
+resource "aws_vpc_security_group_egress_rule" "service_outbound_alb" {
+  count                        = var.create_external_alb ? 1 : 0
+  security_group_id            = aws_security_group.teamcity_alb_sg[0].id
+  referenced_security_group_id = aws_security_group.teamcity_service_sg.id
+  description                  = "Allow outbound HTTP traffic from ALB to service containers"
+  from_port                    = var.container_port
+  to_port                      = var.container_port
+  ip_protocol                  = "TCP"
+}
+
+# Grant TeamCity service access to internet
+resource "aws_vpc_security_group_egress_rule" "internet_outbound" {
+  security_group_id = aws_security_group.teamcity_service_sg.id
+  description       = "Allow outbound internet access from TeamCity service containers"
+  cidr_ipv4         = "0.0.0.0/0"
+  ip_protocol       = "-1"
+}
+
+#############################################
+# IAM Roles for TeamCity Module
+#############################################
+
+data "aws_iam_policy_document" "ecs_tasks_trust_relationship" {
+  statement {
+    effect  = "Allow"
+    actions = ["sts:AssumeRole"]
+    principals {
+      type        = "Service"
+      identifiers = ["ecs-tasks.amazonaws.com"]
+    }
+  }
+}
+
+data "aws_iam_policy_document" "teamcity_default_policy" {
+  # ECS
+  statement {
+    sid    = "ECSExec"
+    effect = "Allow"
+    actions = [
+      "ssmmessages:OpenDataChannel",
+      "ssmmessages:OpenControlChannel",
+      "ssmmessages:CreateDataChannel",
+      "ssmmessages:CreateControlChannel",
+    ]
+    resources = [
+      "*"
+    ]
+  }
+
+  #grant the task necessary EFS permissions to be able to modify directories
+  statement {
+    sid    = "EFS"
+    effect = "Allow"
+    actions = [
+      "elasticfilesystem:ClientWrite",
+      "elasticfilesystem:ClientRootAccess",
+      "elasticfilesystem:ClientMount"
+    ]
+    resources = [
+      aws_efs_file_system.teamcity_efs_file_system[0].arn
+    ]
+  }
+}
+
+resource "aws_iam_policy" "teamcity_default_policy" {
+  name        = "teamcity-default-policy"
+  description = "Policy granting permissions for Unreal Horde."
+  policy      = data.aws_iam_policy_document.teamcity_default_policy.json
+}
+
+resource "aws_iam_role" "teamcity_default_role" {
+  name               = "teamcity-default-role"
+  description        = "Default role for TeamCity ECS task."
+  assume_role_policy = data.aws_iam_policy_document.ecs_tasks_trust_relationship.json
+  managed_policy_arns = [
+    aws_iam_policy.teamcity_default_policy.arn
+  ]
+
+  tags = local.tags
+}
+resource "aws_iam_role" "teamcity_task_execution_role" {
+  name = "teamcity-task-execution-role"
+
+  assume_role_policy  = data.aws_iam_policy_document.ecs_tasks_trust_relationship.json
+  managed_policy_arns = ["arn:aws:iam::aws:policy/service-role/AmazonECSTaskExecutionRolePolicy"]
+
+  inline_policy {
+    name = "teamcity-secrets-access"
+    policy = jsonencode({
+      Version = "2012-10-17"
+      Statement = [
+        {
+          Sid    = "SecretsManager"
+          Effect = "Allow"
+          Action = [
+            "secretsmanager:GetSecretValue"
+          ]
+          Resource = [
+            aws_rds_cluster.teamcity_db_cluster[0].master_user_secret[0].secret_arn
+          ]
+        }
+      ]
+    })
+  }
+}
+
+
+# CloudWatch Logs
+resource "aws_cloudwatch_log_group" "teamcity_log_group" {
+  #checkov:skip=CKV_AWS_158: KMS Encryption disabled by default
+  name              = "${local.name_prefix}-log-group"
+  retention_in_days = var.teamcity_cloudwatch_log_retention_in_days
+  tags              = local.tags
+}
+
+# Load Balancer for TeamCity Service
+resource "aws_lb" "teamcity_external_lb" {
+  count              = var.create_external_alb ? 1 : 0
+  name               = "${local.name_prefix}-lb"
+  security_groups    = [aws_security_group.teamcity_alb_sg[0].id]
+  load_balancer_type = "application"
+  internal           = false
+  subnets            = var.alb_subnets
+
+
+  dynamic "access_logs" {
+    for_each = var.enable_teamcity_alb_access_logs ? [1] : []
+    content {
+      enabled = var.enable_teamcity_alb_access_logs
+      bucket  = var.teamcity_alb_access_logs_bucket != null ? var.teamcity_alb_access_logs_bucket : aws_s3_bucket.teamcity_alb_access_logs_bucket[0].id
+      prefix  = var.teamcity_alb_access_logs_prefix != null ? var.teamcity_alb_access_logs_prefix : "${local.name_prefix}-alb"
+    }
+  }
+  #checkov:skip=CKV_AWS_150:Deletion protection disabled by default
+  enable_deletion_protection = var.enable_teamcity_alb_deletion_protection
+
+  #checkov:skip=CKV2_AWS_28: ALB access is managed with SG allow listing
+
+  drop_invalid_header_fields = true
+  tags                       = local.tags
+}
+
+# TeamCity target group for ALB
+resource "aws_lb_target_group" "teamcity_target_group" {
+  count       = var.create_external_alb ? 1 : 0
+  name        = "${local.name_prefix}-tg"
+  port        = var.container_port
+  protocol    = "HTTP"
+  vpc_id      = var.vpc_id
+  target_type = "ip"
+
+  health_check {
+    path                = "/healthCheck/healthy"
+    interval            = 30
+    timeout             = 5
+    healthy_threshold   = 5
+    unhealthy_threshold = 2
+    port                = var.container_port
+    protocol            = "HTTP"
+    matcher             = "200"
+  }
+
+  tags = local.tags
+}
+
+# ALB HTTPS Listener
+resource "aws_lb_listener" "teamcity_listener" {
+  count             = var.create_external_alb ? 1 : 0
+  load_balancer_arn = aws_lb.teamcity_external_lb[0].arn
+  port              = "443"
+  protocol          = "HTTPS"
+  ssl_policy        = "ELBSecurityPolicy-TLS13-1-2-2021-06"
+  certificate_arn   = var.alb_certificate_arn
+  default_action {
+    type             = "forward"
+    target_group_arn = aws_lb_target_group.teamcity_target_group[0].arn
+  }
+  tags = local.tags
+}
+
+# #######################################
+# # TeamCity Aurora Serverless Database #
+# #######################################
+# Subnet group
+resource "aws_db_subnet_group" "teamcity_db_subnet_group" {
+  count      = var.database_connection_string == null ? 1 : 0
+  name       = "${local.name_prefix}-db-subnet-group"
+  subnet_ids = var.service_subnets
+  tags       = local.tags
+}
+
+# # RDS instance with Aurora serverless engine
+resource "aws_rds_cluster" "teamcity_db_cluster" {
+  #checkov:skip=CKV2_AWS_27:not enabling query logging by design
+  #checkov:skip=CKV2_AWS_8: TODO: add rds backup plan
+  count                       = var.database_connection_string == null ? 1 : 0
+  cluster_identifier          = "teamcity-cluster"
+  engine                      = "aurora-postgresql"
+  engine_mode                 = "provisioned"
+  engine_version              = "16.6" #check for latest as option
+  database_name               = "teamcity"
+  master_username             = "teamcity"
+  manage_master_user_password = true #using AWS Secrets Manager
+  storage_encrypted           = true
+  skip_final_snapshot         = var.aurora_skip_final_snapshot
+  db_subnet_group_name        = aws_db_subnet_group.teamcity_db_subnet_group[0].id
+  vpc_security_group_ids = [
+    aws_security_group.teamcity_db_sg[0].id
+  ]
+
+  serverlessv2_scaling_configuration {
+    max_capacity             = 1.0
+    min_capacity             = 0.0
+    seconds_until_auto_pause = 3600
+  }
+}
path: modules/teamcity/main.tf
user: login: github-advanced-security[bot]
body: ## Ensure Postgres RDS as aws_rds_cluster has Query Logging enabled

Ensure Postgres RDS as aws_rds_cluster has Query Logging enabled

[Show more details](https://github.com/aws-games/cloud-game-development-toolkit/security/code-scanning/837)
created_at: 2025-03-06T22:53:04Z
author_association: NONE
diff_hunk: @@ -0,0 +1,617 @@
+###################################
+# ECS Cluster for TeamCity Server #
+###################################
+# If cluster name is provided use a data source to access existing resource
+data "aws_ecs_cluster" "teamcity_cluster" {
+  count        = var.cluster_name != null ? 1 : 0
+  cluster_name = var.cluster_name
+}
+resource "aws_ecs_cluster" "teamcity_cluster" {
+  count = var.cluster_name != null ? 0 : 1
+  name  = "${local.name_prefix}-cluster"
+
+  setting {
+    name  = "containerInsights"
+    value = "enabled"
+  }
+
+  tags = local.tags
+}
+
+# TeamCity Task Definition
+resource "aws_ecs_task_definition" "teamcity_task_definition" {
+  family                   = var.name
+  requires_compatibilities = ["FARGATE"]
+  network_mode             = "awsvpc"
+  cpu                      = var.container_cpu
+  memory                   = var.container_memory
+
+  container_definitions = jsonencode([
+    {
+      name      = var.container_name
+      image     = local.image
+      cpu       = var.container_cpu
+      memory    = var.container_memory
+      essential = true
+
+      portMappings = [
+        {
+          containerPort = var.container_port
+          hostPort      = var.container_port
+        }
+      ]
+      logConfiguration = {
+        logDriver = "awslogs"
+        options = {
+          awslogs-group         = aws_cloudwatch_log_group.teamcity_log_group.name
+          awslogs-region        = data.aws_region.current.name
+          awslogs-stream-prefix = "[APP]"
+        }
+      }
+
+      mountPoints = [
+        {
+          sourceVolume  = "teamcity_data",
+          containerPath = "/data/teamcity_server/datadir"
+          readOnly      = false
+        }
+      ]
+
+      # # Set environment variables for database connection string
+      # environment = [
+      #   {
+      #     name  = "TEAMCITY_DB_URL"
+      #     value = local.database_connection_string
+      #   },
+      #   {
+      #     name  = "TEAMCITY_DB_USER"
+      #     value = local.database_master_username
+      #   },
+      #   # {
+      #   #   name  = "TEAMCITY_LOGS_PATH"
+      #   #   value = "/data/teamcity_server/logs"
+      #   # },
+      #   {
+      #     name  = "TEAMCITY_DATA_PATH"
+      #     value = "/data/teamcity_server/datadir"
+      #   }
+      # ]
+      # # Define password environment variable if provided
+      # password_env = local.database_master_password != null ? [
+      #   {
+      #     name  = "TEAMCITY_DB_PASSWORD"
+      #     value = local.database_master_password
+      #   }
+      # ] : []
+
+      # Combine the lists
+      environment = concat(local.base_env, local.password_env)
+
+      secrets = var.database_connection_string == null ? [
+        {
+          name      = "TEAMCITY_DB_PASSWORD"
+          valueFrom = "${aws_rds_cluster.teamcity_db_cluster[0].master_user_secret[0].secret_arn}:password::"
+        }
+      ] : []
+    }
+  ])
+  tags = {
+    Name = var.name
+  }
+  task_role_arn      = aws_iam_role.teamcity_default_role.arn
+  execution_role_arn = aws_iam_role.teamcity_task_execution_role.arn
+
+  volume {
+    name = "teamcity_data"
+    efs_volume_configuration {
+      file_system_id          = aws_efs_file_system.teamcity_efs_file_system[0].id
+      transit_encryption      = "ENABLED"
+      transit_encryption_port = 2999
+      authorization_config {
+        access_point_id = aws_efs_access_point.teamcity_efs_data_access_point.id
+        iam             = "ENABLED"
+      }
+    }
+  }
+}
+
+# ECS Service
+resource "aws_ecs_service" "teamcity" {
+  name                   = local.name_prefix
+  cluster                = var.cluster_name != null ? data.aws_ecs_cluster.teamcity_cluster[0].arn : aws_ecs_cluster.teamcity_cluster[0].arn
+  task_definition        = aws_ecs_task_definition.teamcity_task_definition.arn
+  launch_type            = "FARGATE"
+  desired_count          = var.desired_container_count
+  force_new_deployment   = var.debug
+  enable_execute_command = var.debug
+
+  wait_for_steady_state = false #TODO: make this configurable
+
+  network_configuration {
+    subnets         = var.service_subnets
+    security_groups = [aws_security_group.teamcity_service_sg.id]
+  }
+  dynamic "load_balancer" {
+    for_each = var.create_external_alb ? [1] : []
+    content {
+      target_group_arn = aws_lb_target_group.teamcity_target_group[0].arn
+      container_name   = var.container_name
+      container_port   = var.container_port
+
+    }
+  }
+
+  tags = local.tags
+}
+
+###################################
+# Security Groups                 #
+###################################
+
+# TeamCity service security group
+resource "aws_security_group" "teamcity_service_sg" {
+  name        = "${local.name_prefix}-service-sg"
+  vpc_id      = var.vpc_id
+  description = "TeamCity service security group"
+  tags        = local.tags
+}
+
+# TeamCity EFS security group
+resource "aws_security_group" "teamcity_efs_sg" {
+  # count       = var.efs_id == null ? 1 : 0
+  name        = "${local.name_prefix}-efs-sg"
+  description = "TeamCity EFS mount target security group"
+  vpc_id      = var.vpc_id
+  tags        = local.tags
+}
+
+# Ingress rule for NFS traffic from service to EFS
+resource "aws_vpc_security_group_ingress_rule" "service_efs" {
+  # count                        = var.efs_id == null ? 1 : 0
+  security_group_id            = aws_security_group.teamcity_efs_sg.id
+  referenced_security_group_id = aws_security_group.teamcity_service_sg.id
+  description                  = "Allow inbound access from TeamCity service containers to EFS"
+  ip_protocol                  = "TCP"
+  from_port                    = 2049
+  to_port                      = 2049
+}
+
+# Ingress rule for NFS traffic from EFS to service
+resource "aws_vpc_security_group_ingress_rule" "efs_service" {
+  # count                        = var.efs_id == null ? 1 : 0
+  security_group_id            = aws_security_group.teamcity_service_sg.id
+  referenced_security_group_id = aws_security_group.teamcity_efs_sg.id
+  description                  = "Allow inbound access from EFS to TeamCity service containers"
+  ip_protocol                  = "TCP"
+  from_port                    = 2049
+  to_port                      = 2049
+}
+
+# TeamCity Aurora Serverless PostgreSQL security group
+resource "aws_security_group" "teamcity_db_sg" {
+  count       = var.database_connection_string == null ? 1 : 0
+  name        = "${local.name_prefix}-db-sg"
+  description = "TeamCity DB security group"
+  vpc_id      = var.vpc_id
+  tags        = local.tags
+}
+
+# Ingress rule for PostgreSQL from service to database cluster
+resource "aws_vpc_security_group_ingress_rule" "service_db" {
+  count                        = var.database_connection_string == null ? 1 : 0
+  security_group_id            = aws_security_group.teamcity_db_sg[0].id
+  referenced_security_group_id = aws_security_group.teamcity_service_sg.id
+  description                  = "Allow inbound access from TeamCity service containers to DB"
+  ip_protocol                  = "TCP"
+  from_port                    = 5432
+  to_port                      = 5432
+}
+
+# Ingress rule for PostgreSQL from database cluster to service
+resource "aws_vpc_security_group_ingress_rule" "db_service" {
+  count                        = var.database_connection_string == null ? 1 : 0
+  security_group_id            = aws_security_group.teamcity_service_sg.id
+  referenced_security_group_id = aws_security_group.teamcity_db_sg[0].id
+  description                  = "Allow inbound access from DB to TeamCity service containers"
+  ip_protocol                  = "TCP"
+  from_port                    = 5432
+  to_port                      = 5432
+}
+
+# Egress rule for database to let all traffic out
+resource "aws_vpc_security_group_egress_rule" "db_outbound" {
+  count             = var.database_connection_string == null ? 1 : 0
+  security_group_id = aws_security_group.teamcity_db_sg[0].id
+  description       = "Allow outbound access from DB to all"
+  cidr_ipv4         = "0.0.0.0/0"
+  ip_protocol       = "-1"
+}
+
+# TeamCity ALB security group
+resource "aws_security_group" "teamcity_alb_sg" {
+  #checkov:skip=CKV2_AWS_5:SG is attached to TeamCity service ALB
+  count       = var.create_external_alb ? 1 : 0
+  name        = "${local.name_prefix}-alb-sg"
+  vpc_id      = var.vpc_id
+  description = "TeamCity ALB security group"
+  tags        = local.tags
+}
+
+# Ingress rule for HTTP traffic from ALB to service
+resource "aws_vpc_security_group_ingress_rule" "service_inbound_alb" {
+  count                        = var.create_external_alb ? 1 : 0
+  security_group_id            = aws_security_group.teamcity_service_sg.id
+  referenced_security_group_id = aws_security_group.teamcity_alb_sg[0].id
+  description                  = "Allow inbound HTTP traffic from ALB to service containers"
+  from_port                    = var.container_port
+  to_port                      = var.container_port
+  ip_protocol                  = "TCP"
+}
+
+# Egress rule for HTTP traffic from ALB to service
+resource "aws_vpc_security_group_egress_rule" "service_outbound_alb" {
+  count                        = var.create_external_alb ? 1 : 0
+  security_group_id            = aws_security_group.teamcity_alb_sg[0].id
+  referenced_security_group_id = aws_security_group.teamcity_service_sg.id
+  description                  = "Allow outbound HTTP traffic from ALB to service containers"
+  from_port                    = var.container_port
+  to_port                      = var.container_port
+  ip_protocol                  = "TCP"
+}
+
+# Grant TeamCity service access to internet
+resource "aws_vpc_security_group_egress_rule" "internet_outbound" {
+  security_group_id = aws_security_group.teamcity_service_sg.id
+  description       = "Allow outbound internet access from TeamCity service containers"
+  cidr_ipv4         = "0.0.0.0/0"
+  ip_protocol       = "-1"
+}
+
+#############################################
+# IAM Roles for TeamCity Module
+#############################################
+
+data "aws_iam_policy_document" "ecs_tasks_trust_relationship" {
+  statement {
+    effect  = "Allow"
+    actions = ["sts:AssumeRole"]
+    principals {
+      type        = "Service"
+      identifiers = ["ecs-tasks.amazonaws.com"]
+    }
+  }
+}
+
+data "aws_iam_policy_document" "teamcity_default_policy" {
+  # ECS
+  statement {
+    sid    = "ECSExec"
+    effect = "Allow"
+    actions = [
+      "ssmmessages:OpenDataChannel",
+      "ssmmessages:OpenControlChannel",
+      "ssmmessages:CreateDataChannel",
+      "ssmmessages:CreateControlChannel",
+    ]
+    resources = [
+      "*"
+    ]
+  }
+
+  #grant the task necessary EFS permissions to be able to modify directories
+  statement {
+    sid    = "EFS"
+    effect = "Allow"
+    actions = [
+      "elasticfilesystem:ClientWrite",
+      "elasticfilesystem:ClientRootAccess",
+      "elasticfilesystem:ClientMount"
+    ]
+    resources = [
+      aws_efs_file_system.teamcity_efs_file_system[0].arn
+    ]
+  }
+}
+
+resource "aws_iam_policy" "teamcity_default_policy" {
+  name        = "teamcity-default-policy"
+  description = "Policy granting permissions for Unreal Horde."
+  policy      = data.aws_iam_policy_document.teamcity_default_policy.json
+}
+
+resource "aws_iam_role" "teamcity_default_role" {
+  name               = "teamcity-default-role"
+  description        = "Default role for TeamCity ECS task."
+  assume_role_policy = data.aws_iam_policy_document.ecs_tasks_trust_relationship.json
+  managed_policy_arns = [
+    aws_iam_policy.teamcity_default_policy.arn
+  ]
+
+  tags = local.tags
+}
+resource "aws_iam_role" "teamcity_task_execution_role" {
+  name = "teamcity-task-execution-role"
+
+  assume_role_policy  = data.aws_iam_policy_document.ecs_tasks_trust_relationship.json
+  managed_policy_arns = ["arn:aws:iam::aws:policy/service-role/AmazonECSTaskExecutionRolePolicy"]
+
+  inline_policy {
+    name = "teamcity-secrets-access"
+    policy = jsonencode({
+      Version = "2012-10-17"
+      Statement = [
+        {
+          Sid    = "SecretsManager"
+          Effect = "Allow"
+          Action = [
+            "secretsmanager:GetSecretValue"
+          ]
+          Resource = [
+            aws_rds_cluster.teamcity_db_cluster[0].master_user_secret[0].secret_arn
+          ]
+        }
+      ]
+    })
+  }
+}
+
+
+# CloudWatch Logs
+resource "aws_cloudwatch_log_group" "teamcity_log_group" {
+  #checkov:skip=CKV_AWS_158: KMS Encryption disabled by default
+  name              = "${local.name_prefix}-log-group"
+  retention_in_days = var.teamcity_cloudwatch_log_retention_in_days
+  tags              = local.tags
+}
+
+# Load Balancer for TeamCity Service
+resource "aws_lb" "teamcity_external_lb" {
+  count              = var.create_external_alb ? 1 : 0
+  name               = "${local.name_prefix}-lb"
+  security_groups    = [aws_security_group.teamcity_alb_sg[0].id]
+  load_balancer_type = "application"
+  internal           = false
+  subnets            = var.alb_subnets
+
+
+  dynamic "access_logs" {
+    for_each = var.enable_teamcity_alb_access_logs ? [1] : []
+    content {
+      enabled = var.enable_teamcity_alb_access_logs
+      bucket  = var.teamcity_alb_access_logs_bucket != null ? var.teamcity_alb_access_logs_bucket : aws_s3_bucket.teamcity_alb_access_logs_bucket[0].id
+      prefix  = var.teamcity_alb_access_logs_prefix != null ? var.teamcity_alb_access_logs_prefix : "${local.name_prefix}-alb"
+    }
+  }
+  #checkov:skip=CKV_AWS_150:Deletion protection disabled by default
+  enable_deletion_protection = var.enable_teamcity_alb_deletion_protection
+
+  #checkov:skip=CKV2_AWS_28: ALB access is managed with SG allow listing
+
+  drop_invalid_header_fields = true
+  tags                       = local.tags
+}
+
+# TeamCity target group for ALB
+resource "aws_lb_target_group" "teamcity_target_group" {
+  count       = var.create_external_alb ? 1 : 0
+  name        = "${local.name_prefix}-tg"
+  port        = var.container_port
+  protocol    = "HTTP"
+  vpc_id      = var.vpc_id
+  target_type = "ip"
+
+  health_check {
+    path                = "/healthCheck/healthy"
+    interval            = 30
+    timeout             = 5
+    healthy_threshold   = 5
+    unhealthy_threshold = 2
+    port                = var.container_port
+    protocol            = "HTTP"
+    matcher             = "200"
+  }
+
+  tags = local.tags
+}
+
+# ALB HTTPS Listener
+resource "aws_lb_listener" "teamcity_listener" {
+  count             = var.create_external_alb ? 1 : 0
+  load_balancer_arn = aws_lb.teamcity_external_lb[0].arn
+  port              = "443"
+  protocol          = "HTTPS"
+  ssl_policy        = "ELBSecurityPolicy-TLS13-1-2-2021-06"
+  certificate_arn   = var.alb_certificate_arn
+  default_action {
+    type             = "forward"
+    target_group_arn = aws_lb_target_group.teamcity_target_group[0].arn
+  }
+  tags = local.tags
+}
+
+# #######################################
+# # TeamCity Aurora Serverless Database #
+# #######################################
+# Subnet group
+resource "aws_db_subnet_group" "teamcity_db_subnet_group" {
+  count      = var.database_connection_string == null ? 1 : 0
+  name       = "${local.name_prefix}-db-subnet-group"
+  subnet_ids = var.service_subnets
+  tags       = local.tags
+}
+
+# # RDS instance with Aurora serverless engine
+resource "aws_rds_cluster" "teamcity_db_cluster" {
+  #checkov:skip=CKV2_AWS_27:not enabling query logging by design
+  #checkov:skip=CKV2_AWS_8: TODO: add rds backup plan
+  count                       = var.database_connection_string == null ? 1 : 0
+  cluster_identifier          = "teamcity-cluster"
+  engine                      = "aurora-postgresql"
+  engine_mode                 = "provisioned"
+  engine_version              = "16.6" #check for latest as option
+  database_name               = "teamcity"
+  master_username             = "teamcity"
+  manage_master_user_password = true #using AWS Secrets Manager
+  storage_encrypted           = true
+  skip_final_snapshot         = var.aurora_skip_final_snapshot
+  db_subnet_group_name        = aws_db_subnet_group.teamcity_db_subnet_group[0].id
+  vpc_security_group_ids = [
+    aws_security_group.teamcity_db_sg[0].id
+  ]
+
+  serverlessv2_scaling_configuration {
+    max_capacity             = 1.0
+    min_capacity             = 0.0
+    seconds_until_auto_pause = 3600
+  }
+}
path: modules/teamcity/main.tf
user: login: github-advanced-security[bot]
body: ## Ensure that RDS clusters has backup plan of AWS Backup

Ensure that RDS clusters has backup plan of AWS Backup

[Show more details](https://github.com/aws-games/cloud-game-development-toolkit/security/code-scanning/841)
created_at: 2025-03-06T22:53:05Z
author_association: NONE
diff_hunk: @@ -0,0 +1,617 @@
+###################################
+# ECS Cluster for TeamCity Server #
+###################################
+# If cluster name is provided use a data source to access existing resource
+data "aws_ecs_cluster" "teamcity_cluster" {
+  count        = var.cluster_name != null ? 1 : 0
+  cluster_name = var.cluster_name
+}
+resource "aws_ecs_cluster" "teamcity_cluster" {
+  count = var.cluster_name != null ? 0 : 1
+  name  = "${local.name_prefix}-cluster"
+
+  setting {
+    name  = "containerInsights"
+    value = "enabled"
+  }
+
+  tags = local.tags
+}
+
+# TeamCity Task Definition
+resource "aws_ecs_task_definition" "teamcity_task_definition" {
+  family                   = var.name
+  requires_compatibilities = ["FARGATE"]
+  network_mode             = "awsvpc"
+  cpu                      = var.container_cpu
+  memory                   = var.container_memory
+
+  container_definitions = jsonencode([
+    {
+      name      = var.container_name
+      image     = local.image
+      cpu       = var.container_cpu
+      memory    = var.container_memory
+      essential = true
+
+      portMappings = [
+        {
+          containerPort = var.container_port
+          hostPort      = var.container_port
+        }
+      ]
+      logConfiguration = {
+        logDriver = "awslogs"
+        options = {
+          awslogs-group         = aws_cloudwatch_log_group.teamcity_log_group.name
+          awslogs-region        = data.aws_region.current.name
+          awslogs-stream-prefix = "[APP]"
+        }
+      }
+
+      mountPoints = [
+        {
+          sourceVolume  = "teamcity_data",
+          containerPath = "/data/teamcity_server/datadir"
+          readOnly      = false
+        }
+      ]
+
+      # # Set environment variables for database connection string
+      # environment = [
+      #   {
+      #     name  = "TEAMCITY_DB_URL"
+      #     value = local.database_connection_string
+      #   },
+      #   {
+      #     name  = "TEAMCITY_DB_USER"
+      #     value = local.database_master_username
+      #   },
+      #   # {
+      #   #   name  = "TEAMCITY_LOGS_PATH"
+      #   #   value = "/data/teamcity_server/logs"
+      #   # },
+      #   {
+      #     name  = "TEAMCITY_DATA_PATH"
+      #     value = "/data/teamcity_server/datadir"
+      #   }
+      # ]
+      # # Define password environment variable if provided
+      # password_env = local.database_master_password != null ? [
+      #   {
+      #     name  = "TEAMCITY_DB_PASSWORD"
+      #     value = local.database_master_password
+      #   }
+      # ] : []
+
+      # Combine the lists
+      environment = concat(local.base_env, local.password_env)
+
+      secrets = var.database_connection_string == null ? [
+        {
+          name      = "TEAMCITY_DB_PASSWORD"
+          valueFrom = "${aws_rds_cluster.teamcity_db_cluster[0].master_user_secret[0].secret_arn}:password::"
+        }
+      ] : []
+    }
+  ])
+  tags = {
+    Name = var.name
+  }
+  task_role_arn      = aws_iam_role.teamcity_default_role.arn
+  execution_role_arn = aws_iam_role.teamcity_task_execution_role.arn
+
+  volume {
+    name = "teamcity_data"
+    efs_volume_configuration {
+      file_system_id          = aws_efs_file_system.teamcity_efs_file_system[0].id
+      transit_encryption      = "ENABLED"
+      transit_encryption_port = 2999
+      authorization_config {
+        access_point_id = aws_efs_access_point.teamcity_efs_data_access_point.id
+        iam             = "ENABLED"
+      }
+    }
+  }
+}
+
+# ECS Service
+resource "aws_ecs_service" "teamcity" {
+  name                   = local.name_prefix
+  cluster                = var.cluster_name != null ? data.aws_ecs_cluster.teamcity_cluster[0].arn : aws_ecs_cluster.teamcity_cluster[0].arn
+  task_definition        = aws_ecs_task_definition.teamcity_task_definition.arn
+  launch_type            = "FARGATE"
+  desired_count          = var.desired_container_count
+  force_new_deployment   = var.debug
+  enable_execute_command = var.debug
+
+  wait_for_steady_state = false #TODO: make this configurable
+
+  network_configuration {
+    subnets         = var.service_subnets
+    security_groups = [aws_security_group.teamcity_service_sg.id]
+  }
+  dynamic "load_balancer" {
+    for_each = var.create_external_alb ? [1] : []
+    content {
+      target_group_arn = aws_lb_target_group.teamcity_target_group[0].arn
+      container_name   = var.container_name
+      container_port   = var.container_port
+
+    }
+  }
+
+  tags = local.tags
+}
+
+###################################
+# Security Groups                 #
+###################################
+
+# TeamCity service security group
+resource "aws_security_group" "teamcity_service_sg" {
+  name        = "${local.name_prefix}-service-sg"
+  vpc_id      = var.vpc_id
+  description = "TeamCity service security group"
+  tags        = local.tags
+}
+
+# TeamCity EFS security group
+resource "aws_security_group" "teamcity_efs_sg" {
+  # count       = var.efs_id == null ? 1 : 0
+  name        = "${local.name_prefix}-efs-sg"
+  description = "TeamCity EFS mount target security group"
+  vpc_id      = var.vpc_id
+  tags        = local.tags
+}
+
+# Ingress rule for NFS traffic from service to EFS
+resource "aws_vpc_security_group_ingress_rule" "service_efs" {
+  # count                        = var.efs_id == null ? 1 : 0
+  security_group_id            = aws_security_group.teamcity_efs_sg.id
+  referenced_security_group_id = aws_security_group.teamcity_service_sg.id
+  description                  = "Allow inbound access from TeamCity service containers to EFS"
+  ip_protocol                  = "TCP"
+  from_port                    = 2049
+  to_port                      = 2049
+}
+
+# Ingress rule for NFS traffic from EFS to service
+resource "aws_vpc_security_group_ingress_rule" "efs_service" {
+  # count                        = var.efs_id == null ? 1 : 0
+  security_group_id            = aws_security_group.teamcity_service_sg.id
+  referenced_security_group_id = aws_security_group.teamcity_efs_sg.id
+  description                  = "Allow inbound access from EFS to TeamCity service containers"
+  ip_protocol                  = "TCP"
+  from_port                    = 2049
+  to_port                      = 2049
+}
+
+# TeamCity Aurora Serverless PostgreSQL security group
+resource "aws_security_group" "teamcity_db_sg" {
+  count       = var.database_connection_string == null ? 1 : 0
+  name        = "${local.name_prefix}-db-sg"
+  description = "TeamCity DB security group"
+  vpc_id      = var.vpc_id
+  tags        = local.tags
+}
+
+# Ingress rule for PostgreSQL from service to database cluster
+resource "aws_vpc_security_group_ingress_rule" "service_db" {
+  count                        = var.database_connection_string == null ? 1 : 0
+  security_group_id            = aws_security_group.teamcity_db_sg[0].id
+  referenced_security_group_id = aws_security_group.teamcity_service_sg.id
+  description                  = "Allow inbound access from TeamCity service containers to DB"
+  ip_protocol                  = "TCP"
+  from_port                    = 5432
+  to_port                      = 5432
+}
+
+# Ingress rule for PostgreSQL from database cluster to service
+resource "aws_vpc_security_group_ingress_rule" "db_service" {
+  count                        = var.database_connection_string == null ? 1 : 0
+  security_group_id            = aws_security_group.teamcity_service_sg.id
+  referenced_security_group_id = aws_security_group.teamcity_db_sg[0].id
+  description                  = "Allow inbound access from DB to TeamCity service containers"
+  ip_protocol                  = "TCP"
+  from_port                    = 5432
+  to_port                      = 5432
+}
+
+# Egress rule for database to let all traffic out
+resource "aws_vpc_security_group_egress_rule" "db_outbound" {
+  count             = var.database_connection_string == null ? 1 : 0
+  security_group_id = aws_security_group.teamcity_db_sg[0].id
+  description       = "Allow outbound access from DB to all"
+  cidr_ipv4         = "0.0.0.0/0"
+  ip_protocol       = "-1"
+}
+
+# TeamCity ALB security group
+resource "aws_security_group" "teamcity_alb_sg" {
+  #checkov:skip=CKV2_AWS_5:SG is attached to TeamCity service ALB
+  count       = var.create_external_alb ? 1 : 0
+  name        = "${local.name_prefix}-alb-sg"
+  vpc_id      = var.vpc_id
+  description = "TeamCity ALB security group"
+  tags        = local.tags
+}
+
+# Ingress rule for HTTP traffic from ALB to service
+resource "aws_vpc_security_group_ingress_rule" "service_inbound_alb" {
+  count                        = var.create_external_alb ? 1 : 0
+  security_group_id            = aws_security_group.teamcity_service_sg.id
+  referenced_security_group_id = aws_security_group.teamcity_alb_sg[0].id
+  description                  = "Allow inbound HTTP traffic from ALB to service containers"
+  from_port                    = var.container_port
+  to_port                      = var.container_port
+  ip_protocol                  = "TCP"
+}
+
+# Egress rule for HTTP traffic from ALB to service
+resource "aws_vpc_security_group_egress_rule" "service_outbound_alb" {
+  count                        = var.create_external_alb ? 1 : 0
+  security_group_id            = aws_security_group.teamcity_alb_sg[0].id
+  referenced_security_group_id = aws_security_group.teamcity_service_sg.id
+  description                  = "Allow outbound HTTP traffic from ALB to service containers"
+  from_port                    = var.container_port
+  to_port                      = var.container_port
+  ip_protocol                  = "TCP"
+}
+
+# Grant TeamCity service access to internet
+resource "aws_vpc_security_group_egress_rule" "internet_outbound" {
+  security_group_id = aws_security_group.teamcity_service_sg.id
+  description       = "Allow outbound internet access from TeamCity service containers"
+  cidr_ipv4         = "0.0.0.0/0"
+  ip_protocol       = "-1"
+}
+
+#############################################
+# IAM Roles for TeamCity Module
+#############################################
+
+data "aws_iam_policy_document" "ecs_tasks_trust_relationship" {
+  statement {
+    effect  = "Allow"
+    actions = ["sts:AssumeRole"]
+    principals {
+      type        = "Service"
+      identifiers = ["ecs-tasks.amazonaws.com"]
+    }
+  }
+}
+
+data "aws_iam_policy_document" "teamcity_default_policy" {
+  # ECS
+  statement {
+    sid    = "ECSExec"
+    effect = "Allow"
+    actions = [
+      "ssmmessages:OpenDataChannel",
+      "ssmmessages:OpenControlChannel",
+      "ssmmessages:CreateDataChannel",
+      "ssmmessages:CreateControlChannel",
+    ]
+    resources = [
+      "*"
+    ]
+  }
+
+  #grant the task necessary EFS permissions to be able to modify directories
+  statement {
+    sid    = "EFS"
+    effect = "Allow"
+    actions = [
+      "elasticfilesystem:ClientWrite",
+      "elasticfilesystem:ClientRootAccess",
+      "elasticfilesystem:ClientMount"
+    ]
+    resources = [
+      aws_efs_file_system.teamcity_efs_file_system[0].arn
+    ]
+  }
+}
+
+resource "aws_iam_policy" "teamcity_default_policy" {
+  name        = "teamcity-default-policy"
+  description = "Policy granting permissions for Unreal Horde."
+  policy      = data.aws_iam_policy_document.teamcity_default_policy.json
+}
+
+resource "aws_iam_role" "teamcity_default_role" {
+  name               = "teamcity-default-role"
+  description        = "Default role for TeamCity ECS task."
+  assume_role_policy = data.aws_iam_policy_document.ecs_tasks_trust_relationship.json
+  managed_policy_arns = [
+    aws_iam_policy.teamcity_default_policy.arn
+  ]
+
+  tags = local.tags
+}
+resource "aws_iam_role" "teamcity_task_execution_role" {
+  name = "teamcity-task-execution-role"
+
+  assume_role_policy  = data.aws_iam_policy_document.ecs_tasks_trust_relationship.json
+  managed_policy_arns = ["arn:aws:iam::aws:policy/service-role/AmazonECSTaskExecutionRolePolicy"]
+
+  inline_policy {
+    name = "teamcity-secrets-access"
+    policy = jsonencode({
+      Version = "2012-10-17"
+      Statement = [
+        {
+          Sid    = "SecretsManager"
+          Effect = "Allow"
+          Action = [
+            "secretsmanager:GetSecretValue"
+          ]
+          Resource = [
+            aws_rds_cluster.teamcity_db_cluster[0].master_user_secret[0].secret_arn
+          ]
+        }
+      ]
+    })
+  }
+}
+
+
+# CloudWatch Logs
+resource "aws_cloudwatch_log_group" "teamcity_log_group" {
+  #checkov:skip=CKV_AWS_158: KMS Encryption disabled by default
+  name              = "${local.name_prefix}-log-group"
+  retention_in_days = var.teamcity_cloudwatch_log_retention_in_days
+  tags              = local.tags
+}
+
+# Load Balancer for TeamCity Service
+resource "aws_lb" "teamcity_external_lb" {
+  count              = var.create_external_alb ? 1 : 0
+  name               = "${local.name_prefix}-lb"
+  security_groups    = [aws_security_group.teamcity_alb_sg[0].id]
+  load_balancer_type = "application"
+  internal           = false
+  subnets            = var.alb_subnets
+
+
+  dynamic "access_logs" {
+    for_each = var.enable_teamcity_alb_access_logs ? [1] : []
+    content {
+      enabled = var.enable_teamcity_alb_access_logs
+      bucket  = var.teamcity_alb_access_logs_bucket != null ? var.teamcity_alb_access_logs_bucket : aws_s3_bucket.teamcity_alb_access_logs_bucket[0].id
+      prefix  = var.teamcity_alb_access_logs_prefix != null ? var.teamcity_alb_access_logs_prefix : "${local.name_prefix}-alb"
+    }
+  }
+  #checkov:skip=CKV_AWS_150:Deletion protection disabled by default
+  enable_deletion_protection = var.enable_teamcity_alb_deletion_protection
+
+  #checkov:skip=CKV2_AWS_28: ALB access is managed with SG allow listing
+
+  drop_invalid_header_fields = true
+  tags                       = local.tags
+}
+
+# TeamCity target group for ALB
+resource "aws_lb_target_group" "teamcity_target_group" {
+  count       = var.create_external_alb ? 1 : 0
+  name        = "${local.name_prefix}-tg"
+  port        = var.container_port
+  protocol    = "HTTP"
+  vpc_id      = var.vpc_id
+  target_type = "ip"
+
+  health_check {
+    path                = "/healthCheck/healthy"
+    interval            = 30
+    timeout             = 5
+    healthy_threshold   = 5
+    unhealthy_threshold = 2
+    port                = var.container_port
+    protocol            = "HTTP"
+    matcher             = "200"
+  }
+
+  tags = local.tags
+}
+
+# ALB HTTPS Listener
+resource "aws_lb_listener" "teamcity_listener" {
+  count             = var.create_external_alb ? 1 : 0
+  load_balancer_arn = aws_lb.teamcity_external_lb[0].arn
+  port              = "443"
+  protocol          = "HTTPS"
+  ssl_policy        = "ELBSecurityPolicy-TLS13-1-2-2021-06"
+  certificate_arn   = var.alb_certificate_arn
+  default_action {
+    type             = "forward"
+    target_group_arn = aws_lb_target_group.teamcity_target_group[0].arn
+  }
+  tags = local.tags
+}
+
+# #######################################
+# # TeamCity Aurora Serverless Database #
+# #######################################
+# Subnet group
+resource "aws_db_subnet_group" "teamcity_db_subnet_group" {
+  count      = var.database_connection_string == null ? 1 : 0
+  name       = "${local.name_prefix}-db-subnet-group"
+  subnet_ids = var.service_subnets
+  tags       = local.tags
+}
+
+# # RDS instance with Aurora serverless engine
+resource "aws_rds_cluster" "teamcity_db_cluster" {
+  #checkov:skip=CKV2_AWS_27:not enabling query logging by design
+  #checkov:skip=CKV2_AWS_8: TODO: add rds backup plan
+  count                       = var.database_connection_string == null ? 1 : 0
+  cluster_identifier          = "teamcity-cluster"
+  engine                      = "aurora-postgresql"
+  engine_mode                 = "provisioned"
+  engine_version              = "16.6" #check for latest as option
+  database_name               = "teamcity"
+  master_username             = "teamcity"
+  manage_master_user_password = true #using AWS Secrets Manager
+  storage_encrypted           = true
+  skip_final_snapshot         = var.aurora_skip_final_snapshot
+  db_subnet_group_name        = aws_db_subnet_group.teamcity_db_subnet_group[0].id
+  vpc_security_group_ids = [
+    aws_security_group.teamcity_db_sg[0].id
+  ]
+
+  serverlessv2_scaling_configuration {
+    max_capacity             = 1.0
+    min_capacity             = 0.0
+    seconds_until_auto_pause = 3600
+  }
+}
+
+resource "aws_rds_cluster_instance" "teamcity_db_cluster_instance" {
+  count              = var.database_connection_string == null ? var.aurora_instance_count : 0
+  cluster_identifier = aws_rds_cluster.teamcity_db_cluster[0].id
+  instance_class     = "db.serverless"
+  engine             = aws_rds_cluster.teamcity_db_cluster[0].engine
+  engine_version     = aws_rds_cluster.teamcity_db_cluster[0].engine_version
+}
+
+# ################
+# # TeamCity EFS #
+# ################
+
+# File system for teamcity
+resource "aws_efs_file_system" "teamcity_efs_file_system" {
+  count            = var.efs_id == null ? 1 : 0
+  creation_token   = "${local.name_prefix}-efs-file-system"
+  performance_mode = var.teamcity_efs_performance_mode
+  throughput_mode  = var.teamcity_efs_throughput_mode
+
+  #TODO: Parameterize encryption and customer managed key creation
+  encrypted = false
+
+  lifecycle_policy {
+    transition_to_ia = "AFTER_30_DAYS"
+  }
+
+  lifecycle_policy {
+    transition_to_primary_storage_class = "AFTER_1_ACCESS"
+  }
+  #checkov:skip=CKV_AWS_184: CMK encryption not supported currently
+  tags = merge(local.tags, {
+    Name = "${local.name_prefix}-efs-file-system"
+  })
+}
path: modules/teamcity/main.tf
user: login: github-advanced-security[bot]
body: ## Ensure resource is encrypted by KMS using a customer managed Key (CMK)

Ensure resource is encrypted by KMS using a customer managed Key (CMK)

[Show more details](https://github.com/aws-games/cloud-game-development-toolkit/security/code-scanning/836)
created_at: 2025-03-06T22:53:05Z
author_association: NONE
diff_hunk: @@ -0,0 +1,617 @@
+###################################
+# ECS Cluster for TeamCity Server #
+###################################
+# If cluster name is provided use a data source to access existing resource
+data "aws_ecs_cluster" "teamcity_cluster" {
+  count        = var.cluster_name != null ? 1 : 0
+  cluster_name = var.cluster_name
+}
+resource "aws_ecs_cluster" "teamcity_cluster" {
+  count = var.cluster_name != null ? 0 : 1
+  name  = "${local.name_prefix}-cluster"
+
+  setting {
+    name  = "containerInsights"
+    value = "enabled"
+  }
+
+  tags = local.tags
+}
+
+# TeamCity Task Definition
+resource "aws_ecs_task_definition" "teamcity_task_definition" {
+  family                   = var.name
+  requires_compatibilities = ["FARGATE"]
+  network_mode             = "awsvpc"
+  cpu                      = var.container_cpu
+  memory                   = var.container_memory
+
+  container_definitions = jsonencode([
+    {
+      name      = var.container_name
+      image     = local.image
+      cpu       = var.container_cpu
+      memory    = var.container_memory
+      essential = true
+
+      portMappings = [
+        {
+          containerPort = var.container_port
+          hostPort      = var.container_port
+        }
+      ]
+      logConfiguration = {
+        logDriver = "awslogs"
+        options = {
+          awslogs-group         = aws_cloudwatch_log_group.teamcity_log_group.name
+          awslogs-region        = data.aws_region.current.name
+          awslogs-stream-prefix = "[APP]"
+        }
+      }
+
+      mountPoints = [
+        {
+          sourceVolume  = "teamcity_data",
+          containerPath = "/data/teamcity_server/datadir"
+          readOnly      = false
+        }
+      ]
+
+      # # Set environment variables for database connection string
+      # environment = [
+      #   {
+      #     name  = "TEAMCITY_DB_URL"
+      #     value = local.database_connection_string
+      #   },
+      #   {
+      #     name  = "TEAMCITY_DB_USER"
+      #     value = local.database_master_username
+      #   },
+      #   # {
+      #   #   name  = "TEAMCITY_LOGS_PATH"
+      #   #   value = "/data/teamcity_server/logs"
+      #   # },
+      #   {
+      #     name  = "TEAMCITY_DATA_PATH"
+      #     value = "/data/teamcity_server/datadir"
+      #   }
+      # ]
+      # # Define password environment variable if provided
+      # password_env = local.database_master_password != null ? [
+      #   {
+      #     name  = "TEAMCITY_DB_PASSWORD"
+      #     value = local.database_master_password
+      #   }
+      # ] : []
+
+      # Combine the lists
+      environment = concat(local.base_env, local.password_env)
+
+      secrets = var.database_connection_string == null ? [
+        {
+          name      = "TEAMCITY_DB_PASSWORD"
+          valueFrom = "${aws_rds_cluster.teamcity_db_cluster[0].master_user_secret[0].secret_arn}:password::"
+        }
+      ] : []
+    }
+  ])
+  tags = {
+    Name = var.name
+  }
+  task_role_arn      = aws_iam_role.teamcity_default_role.arn
+  execution_role_arn = aws_iam_role.teamcity_task_execution_role.arn
+
+  volume {
+    name = "teamcity_data"
+    efs_volume_configuration {
+      file_system_id          = aws_efs_file_system.teamcity_efs_file_system[0].id
+      transit_encryption      = "ENABLED"
+      transit_encryption_port = 2999
+      authorization_config {
+        access_point_id = aws_efs_access_point.teamcity_efs_data_access_point.id
+        iam             = "ENABLED"
+      }
+    }
+  }
+}
+
+# ECS Service
+resource "aws_ecs_service" "teamcity" {
+  name                   = local.name_prefix
+  cluster                = var.cluster_name != null ? data.aws_ecs_cluster.teamcity_cluster[0].arn : aws_ecs_cluster.teamcity_cluster[0].arn
+  task_definition        = aws_ecs_task_definition.teamcity_task_definition.arn
+  launch_type            = "FARGATE"
+  desired_count          = var.desired_container_count
+  force_new_deployment   = var.debug
+  enable_execute_command = var.debug
+
+  wait_for_steady_state = false #TODO: make this configurable
+
+  network_configuration {
+    subnets         = var.service_subnets
+    security_groups = [aws_security_group.teamcity_service_sg.id]
+  }
+  dynamic "load_balancer" {
+    for_each = var.create_external_alb ? [1] : []
+    content {
+      target_group_arn = aws_lb_target_group.teamcity_target_group[0].arn
+      container_name   = var.container_name
+      container_port   = var.container_port
+
+    }
+  }
+
+  tags = local.tags
+}
+
+###################################
+# Security Groups                 #
+###################################
+
+# TeamCity service security group
+resource "aws_security_group" "teamcity_service_sg" {
+  name        = "${local.name_prefix}-service-sg"
+  vpc_id      = var.vpc_id
+  description = "TeamCity service security group"
+  tags        = local.tags
+}
+
+# TeamCity EFS security group
+resource "aws_security_group" "teamcity_efs_sg" {
+  # count       = var.efs_id == null ? 1 : 0
+  name        = "${local.name_prefix}-efs-sg"
+  description = "TeamCity EFS mount target security group"
+  vpc_id      = var.vpc_id
+  tags        = local.tags
+}
+
+# Ingress rule for NFS traffic from service to EFS
+resource "aws_vpc_security_group_ingress_rule" "service_efs" {
+  # count                        = var.efs_id == null ? 1 : 0
+  security_group_id            = aws_security_group.teamcity_efs_sg.id
+  referenced_security_group_id = aws_security_group.teamcity_service_sg.id
+  description                  = "Allow inbound access from TeamCity service containers to EFS"
+  ip_protocol                  = "TCP"
+  from_port                    = 2049
+  to_port                      = 2049
+}
+
+# Ingress rule for NFS traffic from EFS to service
+resource "aws_vpc_security_group_ingress_rule" "efs_service" {
+  # count                        = var.efs_id == null ? 1 : 0
+  security_group_id            = aws_security_group.teamcity_service_sg.id
+  referenced_security_group_id = aws_security_group.teamcity_efs_sg.id
+  description                  = "Allow inbound access from EFS to TeamCity service containers"
+  ip_protocol                  = "TCP"
+  from_port                    = 2049
+  to_port                      = 2049
+}
+
+# TeamCity Aurora Serverless PostgreSQL security group
+resource "aws_security_group" "teamcity_db_sg" {
+  count       = var.database_connection_string == null ? 1 : 0
+  name        = "${local.name_prefix}-db-sg"
+  description = "TeamCity DB security group"
+  vpc_id      = var.vpc_id
+  tags        = local.tags
+}
+
+# Ingress rule for PostgreSQL from service to database cluster
+resource "aws_vpc_security_group_ingress_rule" "service_db" {
+  count                        = var.database_connection_string == null ? 1 : 0
+  security_group_id            = aws_security_group.teamcity_db_sg[0].id
+  referenced_security_group_id = aws_security_group.teamcity_service_sg.id
+  description                  = "Allow inbound access from TeamCity service containers to DB"
+  ip_protocol                  = "TCP"
+  from_port                    = 5432
+  to_port                      = 5432
+}
+
+# Ingress rule for PostgreSQL from database cluster to service
+resource "aws_vpc_security_group_ingress_rule" "db_service" {
+  count                        = var.database_connection_string == null ? 1 : 0
+  security_group_id            = aws_security_group.teamcity_service_sg.id
+  referenced_security_group_id = aws_security_group.teamcity_db_sg[0].id
+  description                  = "Allow inbound access from DB to TeamCity service containers"
+  ip_protocol                  = "TCP"
+  from_port                    = 5432
+  to_port                      = 5432
+}
+
+# Egress rule for database to let all traffic out
+resource "aws_vpc_security_group_egress_rule" "db_outbound" {
+  count             = var.database_connection_string == null ? 1 : 0
+  security_group_id = aws_security_group.teamcity_db_sg[0].id
+  description       = "Allow outbound access from DB to all"
+  cidr_ipv4         = "0.0.0.0/0"
+  ip_protocol       = "-1"
+}
+
+# TeamCity ALB security group
+resource "aws_security_group" "teamcity_alb_sg" {
+  #checkov:skip=CKV2_AWS_5:SG is attached to TeamCity service ALB
+  count       = var.create_external_alb ? 1 : 0
+  name        = "${local.name_prefix}-alb-sg"
+  vpc_id      = var.vpc_id
+  description = "TeamCity ALB security group"
+  tags        = local.tags
+}
+
+# Ingress rule for HTTP traffic from ALB to service
+resource "aws_vpc_security_group_ingress_rule" "service_inbound_alb" {
+  count                        = var.create_external_alb ? 1 : 0
+  security_group_id            = aws_security_group.teamcity_service_sg.id
+  referenced_security_group_id = aws_security_group.teamcity_alb_sg[0].id
+  description                  = "Allow inbound HTTP traffic from ALB to service containers"
+  from_port                    = var.container_port
+  to_port                      = var.container_port
+  ip_protocol                  = "TCP"
+}
+
+# Egress rule for HTTP traffic from ALB to service
+resource "aws_vpc_security_group_egress_rule" "service_outbound_alb" {
+  count                        = var.create_external_alb ? 1 : 0
+  security_group_id            = aws_security_group.teamcity_alb_sg[0].id
+  referenced_security_group_id = aws_security_group.teamcity_service_sg.id
+  description                  = "Allow outbound HTTP traffic from ALB to service containers"
+  from_port                    = var.container_port
+  to_port                      = var.container_port
+  ip_protocol                  = "TCP"
+}
+
+# Grant TeamCity service access to internet
+resource "aws_vpc_security_group_egress_rule" "internet_outbound" {
+  security_group_id = aws_security_group.teamcity_service_sg.id
+  description       = "Allow outbound internet access from TeamCity service containers"
+  cidr_ipv4         = "0.0.0.0/0"
+  ip_protocol       = "-1"
+}
+
+#############################################
+# IAM Roles for TeamCity Module
+#############################################
+
+data "aws_iam_policy_document" "ecs_tasks_trust_relationship" {
+  statement {
+    effect  = "Allow"
+    actions = ["sts:AssumeRole"]
+    principals {
+      type        = "Service"
+      identifiers = ["ecs-tasks.amazonaws.com"]
+    }
+  }
+}
+
+data "aws_iam_policy_document" "teamcity_default_policy" {
+  # ECS
+  statement {
+    sid    = "ECSExec"
+    effect = "Allow"
+    actions = [
+      "ssmmessages:OpenDataChannel",
+      "ssmmessages:OpenControlChannel",
+      "ssmmessages:CreateDataChannel",
+      "ssmmessages:CreateControlChannel",
+    ]
+    resources = [
+      "*"
+    ]
+  }
+
+  #grant the task necessary EFS permissions to be able to modify directories
+  statement {
+    sid    = "EFS"
+    effect = "Allow"
+    actions = [
+      "elasticfilesystem:ClientWrite",
+      "elasticfilesystem:ClientRootAccess",
+      "elasticfilesystem:ClientMount"
+    ]
+    resources = [
+      aws_efs_file_system.teamcity_efs_file_system[0].arn
+    ]
+  }
+}
+
+resource "aws_iam_policy" "teamcity_default_policy" {
+  name        = "teamcity-default-policy"
+  description = "Policy granting permissions for Unreal Horde."
+  policy      = data.aws_iam_policy_document.teamcity_default_policy.json
+}
+
+resource "aws_iam_role" "teamcity_default_role" {
+  name               = "teamcity-default-role"
+  description        = "Default role for TeamCity ECS task."
+  assume_role_policy = data.aws_iam_policy_document.ecs_tasks_trust_relationship.json
+  managed_policy_arns = [
+    aws_iam_policy.teamcity_default_policy.arn
+  ]
+
+  tags = local.tags
+}
+resource "aws_iam_role" "teamcity_task_execution_role" {
+  name = "teamcity-task-execution-role"
+
+  assume_role_policy  = data.aws_iam_policy_document.ecs_tasks_trust_relationship.json
+  managed_policy_arns = ["arn:aws:iam::aws:policy/service-role/AmazonECSTaskExecutionRolePolicy"]
+
+  inline_policy {
+    name = "teamcity-secrets-access"
+    policy = jsonencode({
+      Version = "2012-10-17"
+      Statement = [
+        {
+          Sid    = "SecretsManager"
+          Effect = "Allow"
+          Action = [
+            "secretsmanager:GetSecretValue"
+          ]
+          Resource = [
+            aws_rds_cluster.teamcity_db_cluster[0].master_user_secret[0].secret_arn
+          ]
+        }
+      ]
+    })
+  }
+}
+
+
+# CloudWatch Logs
+resource "aws_cloudwatch_log_group" "teamcity_log_group" {
+  #checkov:skip=CKV_AWS_158: KMS Encryption disabled by default
+  name              = "${local.name_prefix}-log-group"
+  retention_in_days = var.teamcity_cloudwatch_log_retention_in_days
+  tags              = local.tags
+}
+
+# Load Balancer for TeamCity Service
+resource "aws_lb" "teamcity_external_lb" {
+  count              = var.create_external_alb ? 1 : 0
+  name               = "${local.name_prefix}-lb"
+  security_groups    = [aws_security_group.teamcity_alb_sg[0].id]
+  load_balancer_type = "application"
+  internal           = false
+  subnets            = var.alb_subnets
+
+
+  dynamic "access_logs" {
+    for_each = var.enable_teamcity_alb_access_logs ? [1] : []
+    content {
+      enabled = var.enable_teamcity_alb_access_logs
+      bucket  = var.teamcity_alb_access_logs_bucket != null ? var.teamcity_alb_access_logs_bucket : aws_s3_bucket.teamcity_alb_access_logs_bucket[0].id
+      prefix  = var.teamcity_alb_access_logs_prefix != null ? var.teamcity_alb_access_logs_prefix : "${local.name_prefix}-alb"
+    }
+  }
+  #checkov:skip=CKV_AWS_150:Deletion protection disabled by default
+  enable_deletion_protection = var.enable_teamcity_alb_deletion_protection
+
+  #checkov:skip=CKV2_AWS_28: ALB access is managed with SG allow listing
+
+  drop_invalid_header_fields = true
+  tags                       = local.tags
+}
+
+# TeamCity target group for ALB
+resource "aws_lb_target_group" "teamcity_target_group" {
+  count       = var.create_external_alb ? 1 : 0
+  name        = "${local.name_prefix}-tg"
+  port        = var.container_port
+  protocol    = "HTTP"
+  vpc_id      = var.vpc_id
+  target_type = "ip"
+
+  health_check {
+    path                = "/healthCheck/healthy"
+    interval            = 30
+    timeout             = 5
+    healthy_threshold   = 5
+    unhealthy_threshold = 2
+    port                = var.container_port
+    protocol            = "HTTP"
+    matcher             = "200"
+  }
+
+  tags = local.tags
+}
+
+# ALB HTTPS Listener
+resource "aws_lb_listener" "teamcity_listener" {
+  count             = var.create_external_alb ? 1 : 0
+  load_balancer_arn = aws_lb.teamcity_external_lb[0].arn
+  port              = "443"
+  protocol          = "HTTPS"
+  ssl_policy        = "ELBSecurityPolicy-TLS13-1-2-2021-06"
+  certificate_arn   = var.alb_certificate_arn
+  default_action {
+    type             = "forward"
+    target_group_arn = aws_lb_target_group.teamcity_target_group[0].arn
+  }
+  tags = local.tags
+}
+
+# #######################################
+# # TeamCity Aurora Serverless Database #
+# #######################################
+# Subnet group
+resource "aws_db_subnet_group" "teamcity_db_subnet_group" {
+  count      = var.database_connection_string == null ? 1 : 0
+  name       = "${local.name_prefix}-db-subnet-group"
+  subnet_ids = var.service_subnets
+  tags       = local.tags
+}
+
+# # RDS instance with Aurora serverless engine
+resource "aws_rds_cluster" "teamcity_db_cluster" {
+  #checkov:skip=CKV2_AWS_27:not enabling query logging by design
+  #checkov:skip=CKV2_AWS_8: TODO: add rds backup plan
+  count                       = var.database_connection_string == null ? 1 : 0
+  cluster_identifier          = "teamcity-cluster"
+  engine                      = "aurora-postgresql"
+  engine_mode                 = "provisioned"
+  engine_version              = "16.6" #check for latest as option
+  database_name               = "teamcity"
+  master_username             = "teamcity"
+  manage_master_user_password = true #using AWS Secrets Manager
+  storage_encrypted           = true
+  skip_final_snapshot         = var.aurora_skip_final_snapshot
+  db_subnet_group_name        = aws_db_subnet_group.teamcity_db_subnet_group[0].id
+  vpc_security_group_ids = [
+    aws_security_group.teamcity_db_sg[0].id
+  ]
+
+  serverlessv2_scaling_configuration {
+    max_capacity             = 1.0
+    min_capacity             = 0.0
+    seconds_until_auto_pause = 3600
+  }
+}
+
+resource "aws_rds_cluster_instance" "teamcity_db_cluster_instance" {
+  count              = var.database_connection_string == null ? var.aurora_instance_count : 0
+  cluster_identifier = aws_rds_cluster.teamcity_db_cluster[0].id
+  instance_class     = "db.serverless"
+  engine             = aws_rds_cluster.teamcity_db_cluster[0].engine
+  engine_version     = aws_rds_cluster.teamcity_db_cluster[0].engine_version
+}
+
+# ################
+# # TeamCity EFS #
+# ################
+
+# File system for teamcity
+resource "aws_efs_file_system" "teamcity_efs_file_system" {
+  count            = var.efs_id == null ? 1 : 0
+  creation_token   = "${local.name_prefix}-efs-file-system"
+  performance_mode = var.teamcity_efs_performance_mode
+  throughput_mode  = var.teamcity_efs_throughput_mode
+
+  #TODO: Parameterize encryption and customer managed key creation
+  encrypted = false
+
+  lifecycle_policy {
+    transition_to_ia = "AFTER_30_DAYS"
+  }
+
+  lifecycle_policy {
+    transition_to_primary_storage_class = "AFTER_1_ACCESS"
+  }
+  #checkov:skip=CKV_AWS_184: CMK encryption not supported currently
+  tags = merge(local.tags, {
+    Name = "${local.name_prefix}-efs-file-system"
+  })
+}
+
+#check for existing mount targets
+
+
+# # Mount Point for teamcity file system
+resource "aws_efs_mount_target" "teamcity_efs_mount_target" {
+  count          = length(var.service_subnets)
+  file_system_id = var.efs_id != null ? var.efs_id : aws_efs_file_system.teamcity_efs_file_system[0].id
+  subnet_id      = var.service_subnets[count.index]
+  security_groups = [
+    aws_security_group.teamcity_efs_sg.id
+  ]
+}
+
+# TeamCity data directory
+resource "aws_efs_access_point" "teamcity_efs_data_access_point" {
+  file_system_id = aws_efs_file_system.teamcity_efs_file_system[0].id
+  posix_user {
+    gid = 1000
+    uid = 1000
+  }
+  root_directory {
+    path = "/data/teamcity_user/datadir"
+    creation_info {
+      owner_gid   = 1000
+      owner_uid   = 1000
+      permissions = 0755
+    }
+  }
+  tags = merge(local.tags, {
+    Name = "${local.name_prefix}-efs-access-point"
+  })
+}
+
+###########################
+# Access Logs
+###########################
+
+resource "random_string" "teamcity_alb_access_logs_bucket_suffix" {
+  count   = var.enable_teamcity_alb_access_logs && var.teamcity_alb_access_logs_bucket == null ? 1 : 0
+  length  = 8
+  special = false
+  upper   = false
+}
+
+resource "aws_s3_bucket" "teamcity_alb_access_logs_bucket" {
+  count  = var.enable_teamcity_alb_access_logs && var.teamcity_alb_access_logs_bucket == null ? 1 : 0
+  bucket = "${local.name_prefix}-alb-access-logs-${random_string.teamcity_alb_access_logs_bucket_suffix[0].result}"
+
+  #checkov:skip=CKV_AWS_21: Versioning not necessary for access logs
+  #checkov:skip=CKV_AWS_144: Cross-region replication not necessary for access logs
+  #checkov:skip=CKV_AWS_145: KMS encryption with CMK not currently supported
+  #checkov:skip=CKV_AWS_18: S3 access logs not necessary
+  #checkov:skip=CKV2_AWS_62: Event notifications not necessary
+
+  tags = merge(local.tags, {
+    Name = "${local.name_prefix}-alb-access-logs-${random_string.teamcity_alb_access_logs_bucket_suffix[0].result}"
+  })
+}
path: modules/teamcity/main.tf
user: login: github-advanced-security[bot]
body: ## Ensure that S3 buckets are encrypted with KMS by default

Ensure that S3 buckets are encrypted with KMS by default

[Show more details](https://github.com/aws-games/cloud-game-development-toolkit/security/code-scanning/838)
created_at: 2025-03-06T22:53:05Z
author_association: NONE
diff_hunk: @@ -0,0 +1,617 @@
+###################################
+# ECS Cluster for TeamCity Server #
+###################################
+# If cluster name is provided use a data source to access existing resource
+data "aws_ecs_cluster" "teamcity_cluster" {
+  count        = var.cluster_name != null ? 1 : 0
+  cluster_name = var.cluster_name
+}
+resource "aws_ecs_cluster" "teamcity_cluster" {
+  count = var.cluster_name != null ? 0 : 1
+  name  = "${local.name_prefix}-cluster"
+
+  setting {
+    name  = "containerInsights"
+    value = "enabled"
+  }
+
+  tags = local.tags
+}
+
+# TeamCity Task Definition
+resource "aws_ecs_task_definition" "teamcity_task_definition" {
+  family                   = var.name
+  requires_compatibilities = ["FARGATE"]
+  network_mode             = "awsvpc"
+  cpu                      = var.container_cpu
+  memory                   = var.container_memory
+
+  container_definitions = jsonencode([
+    {
+      name      = var.container_name
+      image     = local.image
+      cpu       = var.container_cpu
+      memory    = var.container_memory
+      essential = true
+
+      portMappings = [
+        {
+          containerPort = var.container_port
+          hostPort      = var.container_port
+        }
+      ]
+      logConfiguration = {
+        logDriver = "awslogs"
+        options = {
+          awslogs-group         = aws_cloudwatch_log_group.teamcity_log_group.name
+          awslogs-region        = data.aws_region.current.name
+          awslogs-stream-prefix = "[APP]"
+        }
+      }
+
+      mountPoints = [
+        {
+          sourceVolume  = "teamcity_data",
+          containerPath = "/data/teamcity_server/datadir"
+          readOnly      = false
+        }
+      ]
+
+      # # Set environment variables for database connection string
+      # environment = [
+      #   {
+      #     name  = "TEAMCITY_DB_URL"
+      #     value = local.database_connection_string
+      #   },
+      #   {
+      #     name  = "TEAMCITY_DB_USER"
+      #     value = local.database_master_username
+      #   },
+      #   # {
+      #   #   name  = "TEAMCITY_LOGS_PATH"
+      #   #   value = "/data/teamcity_server/logs"
+      #   # },
+      #   {
+      #     name  = "TEAMCITY_DATA_PATH"
+      #     value = "/data/teamcity_server/datadir"
+      #   }
+      # ]
+      # # Define password environment variable if provided
+      # password_env = local.database_master_password != null ? [
+      #   {
+      #     name  = "TEAMCITY_DB_PASSWORD"
+      #     value = local.database_master_password
+      #   }
+      # ] : []
+
+      # Combine the lists
+      environment = concat(local.base_env, local.password_env)
+
+      secrets = var.database_connection_string == null ? [
+        {
+          name      = "TEAMCITY_DB_PASSWORD"
+          valueFrom = "${aws_rds_cluster.teamcity_db_cluster[0].master_user_secret[0].secret_arn}:password::"
+        }
+      ] : []
+    }
+  ])
+  tags = {
+    Name = var.name
+  }
+  task_role_arn      = aws_iam_role.teamcity_default_role.arn
+  execution_role_arn = aws_iam_role.teamcity_task_execution_role.arn
+
+  volume {
+    name = "teamcity_data"
+    efs_volume_configuration {
+      file_system_id          = aws_efs_file_system.teamcity_efs_file_system[0].id
+      transit_encryption      = "ENABLED"
+      transit_encryption_port = 2999
+      authorization_config {
+        access_point_id = aws_efs_access_point.teamcity_efs_data_access_point.id
+        iam             = "ENABLED"
+      }
+    }
+  }
+}
+
+# ECS Service
+resource "aws_ecs_service" "teamcity" {
+  name                   = local.name_prefix
+  cluster                = var.cluster_name != null ? data.aws_ecs_cluster.teamcity_cluster[0].arn : aws_ecs_cluster.teamcity_cluster[0].arn
+  task_definition        = aws_ecs_task_definition.teamcity_task_definition.arn
+  launch_type            = "FARGATE"
+  desired_count          = var.desired_container_count
+  force_new_deployment   = var.debug
+  enable_execute_command = var.debug
+
+  wait_for_steady_state = false #TODO: make this configurable
+
+  network_configuration {
+    subnets         = var.service_subnets
+    security_groups = [aws_security_group.teamcity_service_sg.id]
+  }
+  dynamic "load_balancer" {
+    for_each = var.create_external_alb ? [1] : []
+    content {
+      target_group_arn = aws_lb_target_group.teamcity_target_group[0].arn
+      container_name   = var.container_name
+      container_port   = var.container_port
+
+    }
+  }
+
+  tags = local.tags
+}
+
+###################################
+# Security Groups                 #
+###################################
+
+# TeamCity service security group
+resource "aws_security_group" "teamcity_service_sg" {
+  name        = "${local.name_prefix}-service-sg"
+  vpc_id      = var.vpc_id
+  description = "TeamCity service security group"
+  tags        = local.tags
+}
+
+# TeamCity EFS security group
+resource "aws_security_group" "teamcity_efs_sg" {
+  # count       = var.efs_id == null ? 1 : 0
+  name        = "${local.name_prefix}-efs-sg"
+  description = "TeamCity EFS mount target security group"
+  vpc_id      = var.vpc_id
+  tags        = local.tags
+}
+
+# Ingress rule for NFS traffic from service to EFS
+resource "aws_vpc_security_group_ingress_rule" "service_efs" {
+  # count                        = var.efs_id == null ? 1 : 0
+  security_group_id            = aws_security_group.teamcity_efs_sg.id
+  referenced_security_group_id = aws_security_group.teamcity_service_sg.id
+  description                  = "Allow inbound access from TeamCity service containers to EFS"
+  ip_protocol                  = "TCP"
+  from_port                    = 2049
+  to_port                      = 2049
+}
+
+# Ingress rule for NFS traffic from EFS to service
+resource "aws_vpc_security_group_ingress_rule" "efs_service" {
+  # count                        = var.efs_id == null ? 1 : 0
+  security_group_id            = aws_security_group.teamcity_service_sg.id
+  referenced_security_group_id = aws_security_group.teamcity_efs_sg.id
+  description                  = "Allow inbound access from EFS to TeamCity service containers"
+  ip_protocol                  = "TCP"
+  from_port                    = 2049
+  to_port                      = 2049
+}
+
+# TeamCity Aurora Serverless PostgreSQL security group
+resource "aws_security_group" "teamcity_db_sg" {
+  count       = var.database_connection_string == null ? 1 : 0
+  name        = "${local.name_prefix}-db-sg"
+  description = "TeamCity DB security group"
+  vpc_id      = var.vpc_id
+  tags        = local.tags
+}
+
+# Ingress rule for PostgreSQL from service to database cluster
+resource "aws_vpc_security_group_ingress_rule" "service_db" {
+  count                        = var.database_connection_string == null ? 1 : 0
+  security_group_id            = aws_security_group.teamcity_db_sg[0].id
+  referenced_security_group_id = aws_security_group.teamcity_service_sg.id
+  description                  = "Allow inbound access from TeamCity service containers to DB"
+  ip_protocol                  = "TCP"
+  from_port                    = 5432
+  to_port                      = 5432
+}
+
+# Ingress rule for PostgreSQL from database cluster to service
+resource "aws_vpc_security_group_ingress_rule" "db_service" {
+  count                        = var.database_connection_string == null ? 1 : 0
+  security_group_id            = aws_security_group.teamcity_service_sg.id
+  referenced_security_group_id = aws_security_group.teamcity_db_sg[0].id
+  description                  = "Allow inbound access from DB to TeamCity service containers"
+  ip_protocol                  = "TCP"
+  from_port                    = 5432
+  to_port                      = 5432
+}
+
+# Egress rule for database to let all traffic out
+resource "aws_vpc_security_group_egress_rule" "db_outbound" {
+  count             = var.database_connection_string == null ? 1 : 0
+  security_group_id = aws_security_group.teamcity_db_sg[0].id
+  description       = "Allow outbound access from DB to all"
+  cidr_ipv4         = "0.0.0.0/0"
+  ip_protocol       = "-1"
+}
+
+# TeamCity ALB security group
+resource "aws_security_group" "teamcity_alb_sg" {
+  #checkov:skip=CKV2_AWS_5:SG is attached to TeamCity service ALB
+  count       = var.create_external_alb ? 1 : 0
+  name        = "${local.name_prefix}-alb-sg"
+  vpc_id      = var.vpc_id
+  description = "TeamCity ALB security group"
+  tags        = local.tags
+}
+
+# Ingress rule for HTTP traffic from ALB to service
+resource "aws_vpc_security_group_ingress_rule" "service_inbound_alb" {
+  count                        = var.create_external_alb ? 1 : 0
+  security_group_id            = aws_security_group.teamcity_service_sg.id
+  referenced_security_group_id = aws_security_group.teamcity_alb_sg[0].id
+  description                  = "Allow inbound HTTP traffic from ALB to service containers"
+  from_port                    = var.container_port
+  to_port                      = var.container_port
+  ip_protocol                  = "TCP"
+}
+
+# Egress rule for HTTP traffic from ALB to service
+resource "aws_vpc_security_group_egress_rule" "service_outbound_alb" {
+  count                        = var.create_external_alb ? 1 : 0
+  security_group_id            = aws_security_group.teamcity_alb_sg[0].id
+  referenced_security_group_id = aws_security_group.teamcity_service_sg.id
+  description                  = "Allow outbound HTTP traffic from ALB to service containers"
+  from_port                    = var.container_port
+  to_port                      = var.container_port
+  ip_protocol                  = "TCP"
+}
+
+# Grant TeamCity service access to internet
+resource "aws_vpc_security_group_egress_rule" "internet_outbound" {
+  security_group_id = aws_security_group.teamcity_service_sg.id
+  description       = "Allow outbound internet access from TeamCity service containers"
+  cidr_ipv4         = "0.0.0.0/0"
+  ip_protocol       = "-1"
+}
+
+#############################################
+# IAM Roles for TeamCity Module
+#############################################
+
+data "aws_iam_policy_document" "ecs_tasks_trust_relationship" {
+  statement {
+    effect  = "Allow"
+    actions = ["sts:AssumeRole"]
+    principals {
+      type        = "Service"
+      identifiers = ["ecs-tasks.amazonaws.com"]
+    }
+  }
+}
+
+data "aws_iam_policy_document" "teamcity_default_policy" {
+  # ECS
+  statement {
+    sid    = "ECSExec"
+    effect = "Allow"
+    actions = [
+      "ssmmessages:OpenDataChannel",
+      "ssmmessages:OpenControlChannel",
+      "ssmmessages:CreateDataChannel",
+      "ssmmessages:CreateControlChannel",
+    ]
+    resources = [
+      "*"
+    ]
+  }
+
+  #grant the task necessary EFS permissions to be able to modify directories
+  statement {
+    sid    = "EFS"
+    effect = "Allow"
+    actions = [
+      "elasticfilesystem:ClientWrite",
+      "elasticfilesystem:ClientRootAccess",
+      "elasticfilesystem:ClientMount"
+    ]
+    resources = [
+      aws_efs_file_system.teamcity_efs_file_system[0].arn
+    ]
+  }
+}
+
+resource "aws_iam_policy" "teamcity_default_policy" {
+  name        = "teamcity-default-policy"
+  description = "Policy granting permissions for Unreal Horde."
+  policy      = data.aws_iam_policy_document.teamcity_default_policy.json
+}
+
+resource "aws_iam_role" "teamcity_default_role" {
+  name               = "teamcity-default-role"
+  description        = "Default role for TeamCity ECS task."
+  assume_role_policy = data.aws_iam_policy_document.ecs_tasks_trust_relationship.json
+  managed_policy_arns = [
+    aws_iam_policy.teamcity_default_policy.arn
+  ]
+
+  tags = local.tags
+}
+resource "aws_iam_role" "teamcity_task_execution_role" {
+  name = "teamcity-task-execution-role"
+
+  assume_role_policy  = data.aws_iam_policy_document.ecs_tasks_trust_relationship.json
+  managed_policy_arns = ["arn:aws:iam::aws:policy/service-role/AmazonECSTaskExecutionRolePolicy"]
+
+  inline_policy {
+    name = "teamcity-secrets-access"
+    policy = jsonencode({
+      Version = "2012-10-17"
+      Statement = [
+        {
+          Sid    = "SecretsManager"
+          Effect = "Allow"
+          Action = [
+            "secretsmanager:GetSecretValue"
+          ]
+          Resource = [
+            aws_rds_cluster.teamcity_db_cluster[0].master_user_secret[0].secret_arn
+          ]
+        }
+      ]
+    })
+  }
+}
+
+
+# CloudWatch Logs
+resource "aws_cloudwatch_log_group" "teamcity_log_group" {
+  #checkov:skip=CKV_AWS_158: KMS Encryption disabled by default
+  name              = "${local.name_prefix}-log-group"
+  retention_in_days = var.teamcity_cloudwatch_log_retention_in_days
+  tags              = local.tags
+}
+
+# Load Balancer for TeamCity Service
+resource "aws_lb" "teamcity_external_lb" {
+  count              = var.create_external_alb ? 1 : 0
+  name               = "${local.name_prefix}-lb"
+  security_groups    = [aws_security_group.teamcity_alb_sg[0].id]
+  load_balancer_type = "application"
+  internal           = false
+  subnets            = var.alb_subnets
+
+
+  dynamic "access_logs" {
+    for_each = var.enable_teamcity_alb_access_logs ? [1] : []
+    content {
+      enabled = var.enable_teamcity_alb_access_logs
+      bucket  = var.teamcity_alb_access_logs_bucket != null ? var.teamcity_alb_access_logs_bucket : aws_s3_bucket.teamcity_alb_access_logs_bucket[0].id
+      prefix  = var.teamcity_alb_access_logs_prefix != null ? var.teamcity_alb_access_logs_prefix : "${local.name_prefix}-alb"
+    }
+  }
+  #checkov:skip=CKV_AWS_150:Deletion protection disabled by default
+  enable_deletion_protection = var.enable_teamcity_alb_deletion_protection
+
+  #checkov:skip=CKV2_AWS_28: ALB access is managed with SG allow listing
+
+  drop_invalid_header_fields = true
+  tags                       = local.tags
+}
+
+# TeamCity target group for ALB
+resource "aws_lb_target_group" "teamcity_target_group" {
+  count       = var.create_external_alb ? 1 : 0
+  name        = "${local.name_prefix}-tg"
+  port        = var.container_port
+  protocol    = "HTTP"
+  vpc_id      = var.vpc_id
+  target_type = "ip"
+
+  health_check {
+    path                = "/healthCheck/healthy"
+    interval            = 30
+    timeout             = 5
+    healthy_threshold   = 5
+    unhealthy_threshold = 2
+    port                = var.container_port
+    protocol            = "HTTP"
+    matcher             = "200"
+  }
+
+  tags = local.tags
+}
+
+# ALB HTTPS Listener
+resource "aws_lb_listener" "teamcity_listener" {
+  count             = var.create_external_alb ? 1 : 0
+  load_balancer_arn = aws_lb.teamcity_external_lb[0].arn
+  port              = "443"
+  protocol          = "HTTPS"
+  ssl_policy        = "ELBSecurityPolicy-TLS13-1-2-2021-06"
+  certificate_arn   = var.alb_certificate_arn
+  default_action {
+    type             = "forward"
+    target_group_arn = aws_lb_target_group.teamcity_target_group[0].arn
+  }
+  tags = local.tags
+}
+
+# #######################################
+# # TeamCity Aurora Serverless Database #
+# #######################################
+# Subnet group
+resource "aws_db_subnet_group" "teamcity_db_subnet_group" {
+  count      = var.database_connection_string == null ? 1 : 0
+  name       = "${local.name_prefix}-db-subnet-group"
+  subnet_ids = var.service_subnets
+  tags       = local.tags
+}
+
+# # RDS instance with Aurora serverless engine
+resource "aws_rds_cluster" "teamcity_db_cluster" {
+  #checkov:skip=CKV2_AWS_27:not enabling query logging by design
+  #checkov:skip=CKV2_AWS_8: TODO: add rds backup plan
+  count                       = var.database_connection_string == null ? 1 : 0
+  cluster_identifier          = "teamcity-cluster"
+  engine                      = "aurora-postgresql"
+  engine_mode                 = "provisioned"
+  engine_version              = "16.6" #check for latest as option
+  database_name               = "teamcity"
+  master_username             = "teamcity"
+  manage_master_user_password = true #using AWS Secrets Manager
+  storage_encrypted           = true
+  skip_final_snapshot         = var.aurora_skip_final_snapshot
+  db_subnet_group_name        = aws_db_subnet_group.teamcity_db_subnet_group[0].id
+  vpc_security_group_ids = [
+    aws_security_group.teamcity_db_sg[0].id
+  ]
+
+  serverlessv2_scaling_configuration {
+    max_capacity             = 1.0
+    min_capacity             = 0.0
+    seconds_until_auto_pause = 3600
+  }
+}
+
+resource "aws_rds_cluster_instance" "teamcity_db_cluster_instance" {
+  count              = var.database_connection_string == null ? var.aurora_instance_count : 0
+  cluster_identifier = aws_rds_cluster.teamcity_db_cluster[0].id
+  instance_class     = "db.serverless"
+  engine             = aws_rds_cluster.teamcity_db_cluster[0].engine
+  engine_version     = aws_rds_cluster.teamcity_db_cluster[0].engine_version
+}
+
+# ################
+# # TeamCity EFS #
+# ################
+
+# File system for teamcity
+resource "aws_efs_file_system" "teamcity_efs_file_system" {
+  count            = var.efs_id == null ? 1 : 0
+  creation_token   = "${local.name_prefix}-efs-file-system"
+  performance_mode = var.teamcity_efs_performance_mode
+  throughput_mode  = var.teamcity_efs_throughput_mode
+
+  #TODO: Parameterize encryption and customer managed key creation
+  encrypted = false
+
+  lifecycle_policy {
+    transition_to_ia = "AFTER_30_DAYS"
+  }
+
+  lifecycle_policy {
+    transition_to_primary_storage_class = "AFTER_1_ACCESS"
+  }
+  #checkov:skip=CKV_AWS_184: CMK encryption not supported currently
+  tags = merge(local.tags, {
+    Name = "${local.name_prefix}-efs-file-system"
+  })
+}
+
+#check for existing mount targets
+
+
+# # Mount Point for teamcity file system
+resource "aws_efs_mount_target" "teamcity_efs_mount_target" {
+  count          = length(var.service_subnets)
+  file_system_id = var.efs_id != null ? var.efs_id : aws_efs_file_system.teamcity_efs_file_system[0].id
+  subnet_id      = var.service_subnets[count.index]
+  security_groups = [
+    aws_security_group.teamcity_efs_sg.id
+  ]
+}
+
+# TeamCity data directory
+resource "aws_efs_access_point" "teamcity_efs_data_access_point" {
+  file_system_id = aws_efs_file_system.teamcity_efs_file_system[0].id
+  posix_user {
+    gid = 1000
+    uid = 1000
+  }
+  root_directory {
+    path = "/data/teamcity_user/datadir"
+    creation_info {
+      owner_gid   = 1000
+      owner_uid   = 1000
+      permissions = 0755
+    }
+  }
+  tags = merge(local.tags, {
+    Name = "${local.name_prefix}-efs-access-point"
+  })
+}
+
+###########################
+# Access Logs
+###########################
+
+resource "random_string" "teamcity_alb_access_logs_bucket_suffix" {
+  count   = var.enable_teamcity_alb_access_logs && var.teamcity_alb_access_logs_bucket == null ? 1 : 0
+  length  = 8
+  special = false
+  upper   = false
+}
+
+resource "aws_s3_bucket" "teamcity_alb_access_logs_bucket" {
+  count  = var.enable_teamcity_alb_access_logs && var.teamcity_alb_access_logs_bucket == null ? 1 : 0
+  bucket = "${local.name_prefix}-alb-access-logs-${random_string.teamcity_alb_access_logs_bucket_suffix[0].result}"
+
+  #checkov:skip=CKV_AWS_21: Versioning not necessary for access logs
+  #checkov:skip=CKV_AWS_144: Cross-region replication not necessary for access logs
+  #checkov:skip=CKV_AWS_145: KMS encryption with CMK not currently supported
+  #checkov:skip=CKV_AWS_18: S3 access logs not necessary
+  #checkov:skip=CKV2_AWS_62: Event notifications not necessary
+
+  tags = merge(local.tags, {
+    Name = "${local.name_prefix}-alb-access-logs-${random_string.teamcity_alb_access_logs_bucket_suffix[0].result}"
+  })
+}
path: modules/teamcity/main.tf
user: login: github-advanced-security[bot]
body: ## Ensure S3 buckets should have event notifications enabled

Ensure S3 buckets should have event notifications enabled

[Show more details](https://github.com/aws-games/cloud-game-development-toolkit/security/code-scanning/839)
created_at: 2025-03-06T22:53:05Z
author_association: NONE
diff_hunk: @@ -0,0 +1,617 @@
+###################################
+# ECS Cluster for TeamCity Server #
+###################################
+# If cluster name is provided use a data source to access existing resource
+data "aws_ecs_cluster" "teamcity_cluster" {
+  count        = var.cluster_name != null ? 1 : 0
+  cluster_name = var.cluster_name
+}
+resource "aws_ecs_cluster" "teamcity_cluster" {
+  count = var.cluster_name != null ? 0 : 1
+  name  = "${local.name_prefix}-cluster"
+
+  setting {
+    name  = "containerInsights"
+    value = "enabled"
+  }
+
+  tags = local.tags
+}
+
+# TeamCity Task Definition
+resource "aws_ecs_task_definition" "teamcity_task_definition" {
+  family                   = var.name
+  requires_compatibilities = ["FARGATE"]
+  network_mode             = "awsvpc"
+  cpu                      = var.container_cpu
+  memory                   = var.container_memory
+
+  container_definitions = jsonencode([
+    {
+      name      = var.container_name
+      image     = local.image
+      cpu       = var.container_cpu
+      memory    = var.container_memory
+      essential = true
+
+      portMappings = [
+        {
+          containerPort = var.container_port
+          hostPort      = var.container_port
+        }
+      ]
+      logConfiguration = {
+        logDriver = "awslogs"
+        options = {
+          awslogs-group         = aws_cloudwatch_log_group.teamcity_log_group.name
+          awslogs-region        = data.aws_region.current.name
+          awslogs-stream-prefix = "[APP]"
+        }
+      }
+
+      mountPoints = [
+        {
+          sourceVolume  = "teamcity_data",
+          containerPath = "/data/teamcity_server/datadir"
+          readOnly      = false
+        }
+      ]
+
+      # # Set environment variables for database connection string
+      # environment = [
+      #   {
+      #     name  = "TEAMCITY_DB_URL"
+      #     value = local.database_connection_string
+      #   },
+      #   {
+      #     name  = "TEAMCITY_DB_USER"
+      #     value = local.database_master_username
+      #   },
+      #   # {
+      #   #   name  = "TEAMCITY_LOGS_PATH"
+      #   #   value = "/data/teamcity_server/logs"
+      #   # },
+      #   {
+      #     name  = "TEAMCITY_DATA_PATH"
+      #     value = "/data/teamcity_server/datadir"
+      #   }
+      # ]
+      # # Define password environment variable if provided
+      # password_env = local.database_master_password != null ? [
+      #   {
+      #     name  = "TEAMCITY_DB_PASSWORD"
+      #     value = local.database_master_password
+      #   }
+      # ] : []
+
+      # Combine the lists
+      environment = concat(local.base_env, local.password_env)
+
+      secrets = var.database_connection_string == null ? [
+        {
+          name      = "TEAMCITY_DB_PASSWORD"
+          valueFrom = "${aws_rds_cluster.teamcity_db_cluster[0].master_user_secret[0].secret_arn}:password::"
+        }
+      ] : []
+    }
+  ])
+  tags = {
+    Name = var.name
+  }
+  task_role_arn      = aws_iam_role.teamcity_default_role.arn
+  execution_role_arn = aws_iam_role.teamcity_task_execution_role.arn
+
+  volume {
+    name = "teamcity_data"
+    efs_volume_configuration {
+      file_system_id          = aws_efs_file_system.teamcity_efs_file_system[0].id
+      transit_encryption      = "ENABLED"
+      transit_encryption_port = 2999
+      authorization_config {
+        access_point_id = aws_efs_access_point.teamcity_efs_data_access_point.id
+        iam             = "ENABLED"
+      }
+    }
+  }
+}
+
+# ECS Service
+resource "aws_ecs_service" "teamcity" {
+  name                   = local.name_prefix
+  cluster                = var.cluster_name != null ? data.aws_ecs_cluster.teamcity_cluster[0].arn : aws_ecs_cluster.teamcity_cluster[0].arn
+  task_definition        = aws_ecs_task_definition.teamcity_task_definition.arn
+  launch_type            = "FARGATE"
+  desired_count          = var.desired_container_count
+  force_new_deployment   = var.debug
+  enable_execute_command = var.debug
+
+  wait_for_steady_state = false #TODO: make this configurable
+
+  network_configuration {
+    subnets         = var.service_subnets
+    security_groups = [aws_security_group.teamcity_service_sg.id]
+  }
+  dynamic "load_balancer" {
+    for_each = var.create_external_alb ? [1] : []
+    content {
+      target_group_arn = aws_lb_target_group.teamcity_target_group[0].arn
+      container_name   = var.container_name
+      container_port   = var.container_port
+
+    }
+  }
+
+  tags = local.tags
+}
+
+###################################
+# Security Groups                 #
+###################################
+
+# TeamCity service security group
+resource "aws_security_group" "teamcity_service_sg" {
+  name        = "${local.name_prefix}-service-sg"
+  vpc_id      = var.vpc_id
+  description = "TeamCity service security group"
+  tags        = local.tags
+}
+
+# TeamCity EFS security group
+resource "aws_security_group" "teamcity_efs_sg" {
+  # count       = var.efs_id == null ? 1 : 0
+  name        = "${local.name_prefix}-efs-sg"
+  description = "TeamCity EFS mount target security group"
+  vpc_id      = var.vpc_id
+  tags        = local.tags
+}
+
+# Ingress rule for NFS traffic from service to EFS
+resource "aws_vpc_security_group_ingress_rule" "service_efs" {
+  # count                        = var.efs_id == null ? 1 : 0
+  security_group_id            = aws_security_group.teamcity_efs_sg.id
+  referenced_security_group_id = aws_security_group.teamcity_service_sg.id
+  description                  = "Allow inbound access from TeamCity service containers to EFS"
+  ip_protocol                  = "TCP"
+  from_port                    = 2049
+  to_port                      = 2049
+}
+
+# Ingress rule for NFS traffic from EFS to service
+resource "aws_vpc_security_group_ingress_rule" "efs_service" {
+  # count                        = var.efs_id == null ? 1 : 0
+  security_group_id            = aws_security_group.teamcity_service_sg.id
+  referenced_security_group_id = aws_security_group.teamcity_efs_sg.id
+  description                  = "Allow inbound access from EFS to TeamCity service containers"
+  ip_protocol                  = "TCP"
+  from_port                    = 2049
+  to_port                      = 2049
+}
+
+# TeamCity Aurora Serverless PostgreSQL security group
+resource "aws_security_group" "teamcity_db_sg" {
+  count       = var.database_connection_string == null ? 1 : 0
+  name        = "${local.name_prefix}-db-sg"
+  description = "TeamCity DB security group"
+  vpc_id      = var.vpc_id
+  tags        = local.tags
+}
+
+# Ingress rule for PostgreSQL from service to database cluster
+resource "aws_vpc_security_group_ingress_rule" "service_db" {
+  count                        = var.database_connection_string == null ? 1 : 0
+  security_group_id            = aws_security_group.teamcity_db_sg[0].id
+  referenced_security_group_id = aws_security_group.teamcity_service_sg.id
+  description                  = "Allow inbound access from TeamCity service containers to DB"
+  ip_protocol                  = "TCP"
+  from_port                    = 5432
+  to_port                      = 5432
+}
+
+# Ingress rule for PostgreSQL from database cluster to service
+resource "aws_vpc_security_group_ingress_rule" "db_service" {
+  count                        = var.database_connection_string == null ? 1 : 0
+  security_group_id            = aws_security_group.teamcity_service_sg.id
+  referenced_security_group_id = aws_security_group.teamcity_db_sg[0].id
+  description                  = "Allow inbound access from DB to TeamCity service containers"
+  ip_protocol                  = "TCP"
+  from_port                    = 5432
+  to_port                      = 5432
+}
+
+# Egress rule for database to let all traffic out
+resource "aws_vpc_security_group_egress_rule" "db_outbound" {
+  count             = var.database_connection_string == null ? 1 : 0
+  security_group_id = aws_security_group.teamcity_db_sg[0].id
+  description       = "Allow outbound access from DB to all"
+  cidr_ipv4         = "0.0.0.0/0"
+  ip_protocol       = "-1"
+}
+
+# TeamCity ALB security group
+resource "aws_security_group" "teamcity_alb_sg" {
+  #checkov:skip=CKV2_AWS_5:SG is attached to TeamCity service ALB
+  count       = var.create_external_alb ? 1 : 0
+  name        = "${local.name_prefix}-alb-sg"
+  vpc_id      = var.vpc_id
+  description = "TeamCity ALB security group"
+  tags        = local.tags
+}
+
+# Ingress rule for HTTP traffic from ALB to service
+resource "aws_vpc_security_group_ingress_rule" "service_inbound_alb" {
+  count                        = var.create_external_alb ? 1 : 0
+  security_group_id            = aws_security_group.teamcity_service_sg.id
+  referenced_security_group_id = aws_security_group.teamcity_alb_sg[0].id
+  description                  = "Allow inbound HTTP traffic from ALB to service containers"
+  from_port                    = var.container_port
+  to_port                      = var.container_port
+  ip_protocol                  = "TCP"
+}
+
+# Egress rule for HTTP traffic from ALB to service
+resource "aws_vpc_security_group_egress_rule" "service_outbound_alb" {
+  count                        = var.create_external_alb ? 1 : 0
+  security_group_id            = aws_security_group.teamcity_alb_sg[0].id
+  referenced_security_group_id = aws_security_group.teamcity_service_sg.id
+  description                  = "Allow outbound HTTP traffic from ALB to service containers"
+  from_port                    = var.container_port
+  to_port                      = var.container_port
+  ip_protocol                  = "TCP"
+}
+
+# Grant TeamCity service access to internet
+resource "aws_vpc_security_group_egress_rule" "internet_outbound" {
+  security_group_id = aws_security_group.teamcity_service_sg.id
+  description       = "Allow outbound internet access from TeamCity service containers"
+  cidr_ipv4         = "0.0.0.0/0"
+  ip_protocol       = "-1"
+}
+
+#############################################
+# IAM Roles for TeamCity Module
+#############################################
+
+data "aws_iam_policy_document" "ecs_tasks_trust_relationship" {
+  statement {
+    effect  = "Allow"
+    actions = ["sts:AssumeRole"]
+    principals {
+      type        = "Service"
+      identifiers = ["ecs-tasks.amazonaws.com"]
+    }
+  }
+}
+
+data "aws_iam_policy_document" "teamcity_default_policy" {
+  # ECS
+  statement {
+    sid    = "ECSExec"
+    effect = "Allow"
+    actions = [
+      "ssmmessages:OpenDataChannel",
+      "ssmmessages:OpenControlChannel",
+      "ssmmessages:CreateDataChannel",
+      "ssmmessages:CreateControlChannel",
+    ]
+    resources = [
+      "*"
+    ]
+  }
+
+  #grant the task necessary EFS permissions to be able to modify directories
+  statement {
+    sid    = "EFS"
+    effect = "Allow"
+    actions = [
+      "elasticfilesystem:ClientWrite",
+      "elasticfilesystem:ClientRootAccess",
+      "elasticfilesystem:ClientMount"
+    ]
+    resources = [
+      aws_efs_file_system.teamcity_efs_file_system[0].arn
+    ]
+  }
+}
+
+resource "aws_iam_policy" "teamcity_default_policy" {
+  name        = "teamcity-default-policy"
+  description = "Policy granting permissions for Unreal Horde."
+  policy      = data.aws_iam_policy_document.teamcity_default_policy.json
+}
+
+resource "aws_iam_role" "teamcity_default_role" {
+  name               = "teamcity-default-role"
+  description        = "Default role for TeamCity ECS task."
+  assume_role_policy = data.aws_iam_policy_document.ecs_tasks_trust_relationship.json
+  managed_policy_arns = [
+    aws_iam_policy.teamcity_default_policy.arn
+  ]
+
+  tags = local.tags
+}
+resource "aws_iam_role" "teamcity_task_execution_role" {
+  name = "teamcity-task-execution-role"
+
+  assume_role_policy  = data.aws_iam_policy_document.ecs_tasks_trust_relationship.json
+  managed_policy_arns = ["arn:aws:iam::aws:policy/service-role/AmazonECSTaskExecutionRolePolicy"]
+
+  inline_policy {
+    name = "teamcity-secrets-access"
+    policy = jsonencode({
+      Version = "2012-10-17"
+      Statement = [
+        {
+          Sid    = "SecretsManager"
+          Effect = "Allow"
+          Action = [
+            "secretsmanager:GetSecretValue"
+          ]
+          Resource = [
+            aws_rds_cluster.teamcity_db_cluster[0].master_user_secret[0].secret_arn
+          ]
+        }
+      ]
+    })
+  }
+}
+
+
+# CloudWatch Logs
+resource "aws_cloudwatch_log_group" "teamcity_log_group" {
+  #checkov:skip=CKV_AWS_158: KMS Encryption disabled by default
+  name              = "${local.name_prefix}-log-group"
+  retention_in_days = var.teamcity_cloudwatch_log_retention_in_days
+  tags              = local.tags
+}
+
+# Load Balancer for TeamCity Service
+resource "aws_lb" "teamcity_external_lb" {
+  count              = var.create_external_alb ? 1 : 0
+  name               = "${local.name_prefix}-lb"
+  security_groups    = [aws_security_group.teamcity_alb_sg[0].id]
+  load_balancer_type = "application"
+  internal           = false
+  subnets            = var.alb_subnets
+
+
+  dynamic "access_logs" {
+    for_each = var.enable_teamcity_alb_access_logs ? [1] : []
+    content {
+      enabled = var.enable_teamcity_alb_access_logs
+      bucket  = var.teamcity_alb_access_logs_bucket != null ? var.teamcity_alb_access_logs_bucket : aws_s3_bucket.teamcity_alb_access_logs_bucket[0].id
+      prefix  = var.teamcity_alb_access_logs_prefix != null ? var.teamcity_alb_access_logs_prefix : "${local.name_prefix}-alb"
+    }
+  }
+  #checkov:skip=CKV_AWS_150:Deletion protection disabled by default
+  enable_deletion_protection = var.enable_teamcity_alb_deletion_protection
+
+  #checkov:skip=CKV2_AWS_28: ALB access is managed with SG allow listing
+
+  drop_invalid_header_fields = true
+  tags                       = local.tags
+}
+
+# TeamCity target group for ALB
+resource "aws_lb_target_group" "teamcity_target_group" {
+  count       = var.create_external_alb ? 1 : 0
+  name        = "${local.name_prefix}-tg"
+  port        = var.container_port
+  protocol    = "HTTP"
+  vpc_id      = var.vpc_id
+  target_type = "ip"
+
+  health_check {
+    path                = "/healthCheck/healthy"
+    interval            = 30
+    timeout             = 5
+    healthy_threshold   = 5
+    unhealthy_threshold = 2
+    port                = var.container_port
+    protocol            = "HTTP"
+    matcher             = "200"
+  }
+
+  tags = local.tags
+}
+
+# ALB HTTPS Listener
+resource "aws_lb_listener" "teamcity_listener" {
+  count             = var.create_external_alb ? 1 : 0
+  load_balancer_arn = aws_lb.teamcity_external_lb[0].arn
+  port              = "443"
+  protocol          = "HTTPS"
+  ssl_policy        = "ELBSecurityPolicy-TLS13-1-2-2021-06"
+  certificate_arn   = var.alb_certificate_arn
+  default_action {
+    type             = "forward"
+    target_group_arn = aws_lb_target_group.teamcity_target_group[0].arn
+  }
+  tags = local.tags
+}
+
+# #######################################
+# # TeamCity Aurora Serverless Database #
+# #######################################
+# Subnet group
+resource "aws_db_subnet_group" "teamcity_db_subnet_group" {
+  count      = var.database_connection_string == null ? 1 : 0
+  name       = "${local.name_prefix}-db-subnet-group"
+  subnet_ids = var.service_subnets
+  tags       = local.tags
+}
+
+# # RDS instance with Aurora serverless engine
+resource "aws_rds_cluster" "teamcity_db_cluster" {
+  #checkov:skip=CKV2_AWS_27:not enabling query logging by design
+  #checkov:skip=CKV2_AWS_8: TODO: add rds backup plan
+  count                       = var.database_connection_string == null ? 1 : 0
+  cluster_identifier          = "teamcity-cluster"
+  engine                      = "aurora-postgresql"
+  engine_mode                 = "provisioned"
+  engine_version              = "16.6" #check for latest as option
+  database_name               = "teamcity"
+  master_username             = "teamcity"
+  manage_master_user_password = true #using AWS Secrets Manager
+  storage_encrypted           = true
+  skip_final_snapshot         = var.aurora_skip_final_snapshot
+  db_subnet_group_name        = aws_db_subnet_group.teamcity_db_subnet_group[0].id
+  vpc_security_group_ids = [
+    aws_security_group.teamcity_db_sg[0].id
+  ]
+
+  serverlessv2_scaling_configuration {
+    max_capacity             = 1.0
+    min_capacity             = 0.0
+    seconds_until_auto_pause = 3600
+  }
+}
+
+resource "aws_rds_cluster_instance" "teamcity_db_cluster_instance" {
+  count              = var.database_connection_string == null ? var.aurora_instance_count : 0
+  cluster_identifier = aws_rds_cluster.teamcity_db_cluster[0].id
+  instance_class     = "db.serverless"
+  engine             = aws_rds_cluster.teamcity_db_cluster[0].engine
+  engine_version     = aws_rds_cluster.teamcity_db_cluster[0].engine_version
+}
+
+# ################
+# # TeamCity EFS #
+# ################
+
+# File system for teamcity
+resource "aws_efs_file_system" "teamcity_efs_file_system" {
+  count            = var.efs_id == null ? 1 : 0
+  creation_token   = "${local.name_prefix}-efs-file-system"
+  performance_mode = var.teamcity_efs_performance_mode
+  throughput_mode  = var.teamcity_efs_throughput_mode
+
+  #TODO: Parameterize encryption and customer managed key creation
+  encrypted = false
+
+  lifecycle_policy {
+    transition_to_ia = "AFTER_30_DAYS"
+  }
+
+  lifecycle_policy {
+    transition_to_primary_storage_class = "AFTER_1_ACCESS"
+  }
+  #checkov:skip=CKV_AWS_184: CMK encryption not supported currently
+  tags = merge(local.tags, {
+    Name = "${local.name_prefix}-efs-file-system"
+  })
+}
+
+#check for existing mount targets
+
+
+# # Mount Point for teamcity file system
+resource "aws_efs_mount_target" "teamcity_efs_mount_target" {
+  count          = length(var.service_subnets)
+  file_system_id = var.efs_id != null ? var.efs_id : aws_efs_file_system.teamcity_efs_file_system[0].id
+  subnet_id      = var.service_subnets[count.index]
+  security_groups = [
+    aws_security_group.teamcity_efs_sg.id
+  ]
+}
+
+# TeamCity data directory
+resource "aws_efs_access_point" "teamcity_efs_data_access_point" {
+  file_system_id = aws_efs_file_system.teamcity_efs_file_system[0].id
+  posix_user {
+    gid = 1000
+    uid = 1000
+  }
+  root_directory {
+    path = "/data/teamcity_user/datadir"
+    creation_info {
+      owner_gid   = 1000
+      owner_uid   = 1000
+      permissions = 0755
+    }
+  }
+  tags = merge(local.tags, {
+    Name = "${local.name_prefix}-efs-access-point"
+  })
+}
+
+###########################
+# Access Logs
+###########################
+
+resource "random_string" "teamcity_alb_access_logs_bucket_suffix" {
+  count   = var.enable_teamcity_alb_access_logs && var.teamcity_alb_access_logs_bucket == null ? 1 : 0
+  length  = 8
+  special = false
+  upper   = false
+}
+
+resource "aws_s3_bucket" "teamcity_alb_access_logs_bucket" {
+  count  = var.enable_teamcity_alb_access_logs && var.teamcity_alb_access_logs_bucket == null ? 1 : 0
+  bucket = "${local.name_prefix}-alb-access-logs-${random_string.teamcity_alb_access_logs_bucket_suffix[0].result}"
+
+  #checkov:skip=CKV_AWS_21: Versioning not necessary for access logs
+  #checkov:skip=CKV_AWS_144: Cross-region replication not necessary for access logs
+  #checkov:skip=CKV_AWS_145: KMS encryption with CMK not currently supported
+  #checkov:skip=CKV_AWS_18: S3 access logs not necessary
+  #checkov:skip=CKV2_AWS_62: Event notifications not necessary
+
+  tags = merge(local.tags, {
+    Name = "${local.name_prefix}-alb-access-logs-${random_string.teamcity_alb_access_logs_bucket_suffix[0].result}"
+  })
+}
path: modules/teamcity/main.tf
user: login: github-advanced-security[bot]
body: ## Ensure all data stored in the S3 bucket have versioning enabled

Ensure all data stored in the S3 bucket have versioning enabled

[Show more details](https://github.com/aws-games/cloud-game-development-toolkit/security/code-scanning/842)
created_at: 2025-03-06T22:53:05Z
author_association: NONE
diff_hunk: @@ -0,0 +1,617 @@
+###################################
+# ECS Cluster for TeamCity Server #
+###################################
+# If cluster name is provided use a data source to access existing resource
+data "aws_ecs_cluster" "teamcity_cluster" {
+  count        = var.cluster_name != null ? 1 : 0
+  cluster_name = var.cluster_name
+}
+resource "aws_ecs_cluster" "teamcity_cluster" {
+  count = var.cluster_name != null ? 0 : 1
+  name  = "${local.name_prefix}-cluster"
+
+  setting {
+    name  = "containerInsights"
+    value = "enabled"
+  }
+
+  tags = local.tags
+}
+
+# TeamCity Task Definition
+resource "aws_ecs_task_definition" "teamcity_task_definition" {
+  family                   = var.name
+  requires_compatibilities = ["FARGATE"]
+  network_mode             = "awsvpc"
+  cpu                      = var.container_cpu
+  memory                   = var.container_memory
+
+  container_definitions = jsonencode([
+    {
+      name      = var.container_name
+      image     = local.image
+      cpu       = var.container_cpu
+      memory    = var.container_memory
+      essential = true
+
+      portMappings = [
+        {
+          containerPort = var.container_port
+          hostPort      = var.container_port
+        }
+      ]
+      logConfiguration = {
+        logDriver = "awslogs"
+        options = {
+          awslogs-group         = aws_cloudwatch_log_group.teamcity_log_group.name
+          awslogs-region        = data.aws_region.current.name
+          awslogs-stream-prefix = "[APP]"
+        }
+      }
+
+      mountPoints = [
+        {
+          sourceVolume  = "teamcity_data",
+          containerPath = "/data/teamcity_server/datadir"
+          readOnly      = false
+        }
+      ]
+
+      # # Set environment variables for database connection string
+      # environment = [
+      #   {
+      #     name  = "TEAMCITY_DB_URL"
+      #     value = local.database_connection_string
+      #   },
+      #   {
+      #     name  = "TEAMCITY_DB_USER"
+      #     value = local.database_master_username
+      #   },
+      #   # {
+      #   #   name  = "TEAMCITY_LOGS_PATH"
+      #   #   value = "/data/teamcity_server/logs"
+      #   # },
+      #   {
+      #     name  = "TEAMCITY_DATA_PATH"
+      #     value = "/data/teamcity_server/datadir"
+      #   }
+      # ]
+      # # Define password environment variable if provided
+      # password_env = local.database_master_password != null ? [
+      #   {
+      #     name  = "TEAMCITY_DB_PASSWORD"
+      #     value = local.database_master_password
+      #   }
+      # ] : []
+
+      # Combine the lists
+      environment = concat(local.base_env, local.password_env)
+
+      secrets = var.database_connection_string == null ? [
+        {
+          name      = "TEAMCITY_DB_PASSWORD"
+          valueFrom = "${aws_rds_cluster.teamcity_db_cluster[0].master_user_secret[0].secret_arn}:password::"
+        }
+      ] : []
+    }
+  ])
+  tags = {
+    Name = var.name
+  }
+  task_role_arn      = aws_iam_role.teamcity_default_role.arn
+  execution_role_arn = aws_iam_role.teamcity_task_execution_role.arn
+
+  volume {
+    name = "teamcity_data"
+    efs_volume_configuration {
+      file_system_id          = aws_efs_file_system.teamcity_efs_file_system[0].id
+      transit_encryption      = "ENABLED"
+      transit_encryption_port = 2999
+      authorization_config {
+        access_point_id = aws_efs_access_point.teamcity_efs_data_access_point.id
+        iam             = "ENABLED"
+      }
+    }
+  }
+}
+
+# ECS Service
+resource "aws_ecs_service" "teamcity" {
+  name                   = local.name_prefix
+  cluster                = var.cluster_name != null ? data.aws_ecs_cluster.teamcity_cluster[0].arn : aws_ecs_cluster.teamcity_cluster[0].arn
+  task_definition        = aws_ecs_task_definition.teamcity_task_definition.arn
+  launch_type            = "FARGATE"
+  desired_count          = var.desired_container_count
+  force_new_deployment   = var.debug
+  enable_execute_command = var.debug
+
+  wait_for_steady_state = false #TODO: make this configurable
+
+  network_configuration {
+    subnets         = var.service_subnets
+    security_groups = [aws_security_group.teamcity_service_sg.id]
+  }
+  dynamic "load_balancer" {
+    for_each = var.create_external_alb ? [1] : []
+    content {
+      target_group_arn = aws_lb_target_group.teamcity_target_group[0].arn
+      container_name   = var.container_name
+      container_port   = var.container_port
+
+    }
+  }
+
+  tags = local.tags
+}
+
+###################################
+# Security Groups                 #
+###################################
+
+# TeamCity service security group
+resource "aws_security_group" "teamcity_service_sg" {
+  name        = "${local.name_prefix}-service-sg"
+  vpc_id      = var.vpc_id
+  description = "TeamCity service security group"
+  tags        = local.tags
+}
+
+# TeamCity EFS security group
+resource "aws_security_group" "teamcity_efs_sg" {
+  # count       = var.efs_id == null ? 1 : 0
+  name        = "${local.name_prefix}-efs-sg"
+  description = "TeamCity EFS mount target security group"
+  vpc_id      = var.vpc_id
+  tags        = local.tags
+}
+
+# Ingress rule for NFS traffic from service to EFS
+resource "aws_vpc_security_group_ingress_rule" "service_efs" {
+  # count                        = var.efs_id == null ? 1 : 0
+  security_group_id            = aws_security_group.teamcity_efs_sg.id
+  referenced_security_group_id = aws_security_group.teamcity_service_sg.id
+  description                  = "Allow inbound access from TeamCity service containers to EFS"
+  ip_protocol                  = "TCP"
+  from_port                    = 2049
+  to_port                      = 2049
+}
+
+# Ingress rule for NFS traffic from EFS to service
+resource "aws_vpc_security_group_ingress_rule" "efs_service" {
+  # count                        = var.efs_id == null ? 1 : 0
+  security_group_id            = aws_security_group.teamcity_service_sg.id
+  referenced_security_group_id = aws_security_group.teamcity_efs_sg.id
+  description                  = "Allow inbound access from EFS to TeamCity service containers"
+  ip_protocol                  = "TCP"
+  from_port                    = 2049
+  to_port                      = 2049
+}
+
+# TeamCity Aurora Serverless PostgreSQL security group
+resource "aws_security_group" "teamcity_db_sg" {
+  count       = var.database_connection_string == null ? 1 : 0
+  name        = "${local.name_prefix}-db-sg"
+  description = "TeamCity DB security group"
+  vpc_id      = var.vpc_id
+  tags        = local.tags
+}
+
+# Ingress rule for PostgreSQL from service to database cluster
+resource "aws_vpc_security_group_ingress_rule" "service_db" {
+  count                        = var.database_connection_string == null ? 1 : 0
+  security_group_id            = aws_security_group.teamcity_db_sg[0].id
+  referenced_security_group_id = aws_security_group.teamcity_service_sg.id
+  description                  = "Allow inbound access from TeamCity service containers to DB"
+  ip_protocol                  = "TCP"
+  from_port                    = 5432
+  to_port                      = 5432
+}
+
+# Ingress rule for PostgreSQL from database cluster to service
+resource "aws_vpc_security_group_ingress_rule" "db_service" {
+  count                        = var.database_connection_string == null ? 1 : 0
+  security_group_id            = aws_security_group.teamcity_service_sg.id
+  referenced_security_group_id = aws_security_group.teamcity_db_sg[0].id
+  description                  = "Allow inbound access from DB to TeamCity service containers"
+  ip_protocol                  = "TCP"
+  from_port                    = 5432
+  to_port                      = 5432
+}
+
+# Egress rule for database to let all traffic out
+resource "aws_vpc_security_group_egress_rule" "db_outbound" {
+  count             = var.database_connection_string == null ? 1 : 0
+  security_group_id = aws_security_group.teamcity_db_sg[0].id
+  description       = "Allow outbound access from DB to all"
+  cidr_ipv4         = "0.0.0.0/0"
+  ip_protocol       = "-1"
+}
+
+# TeamCity ALB security group
+resource "aws_security_group" "teamcity_alb_sg" {
+  #checkov:skip=CKV2_AWS_5:SG is attached to TeamCity service ALB
+  count       = var.create_external_alb ? 1 : 0
+  name        = "${local.name_prefix}-alb-sg"
+  vpc_id      = var.vpc_id
+  description = "TeamCity ALB security group"
+  tags        = local.tags
+}
+
+# Ingress rule for HTTP traffic from ALB to service
+resource "aws_vpc_security_group_ingress_rule" "service_inbound_alb" {
+  count                        = var.create_external_alb ? 1 : 0
+  security_group_id            = aws_security_group.teamcity_service_sg.id
+  referenced_security_group_id = aws_security_group.teamcity_alb_sg[0].id
+  description                  = "Allow inbound HTTP traffic from ALB to service containers"
+  from_port                    = var.container_port
+  to_port                      = var.container_port
+  ip_protocol                  = "TCP"
+}
+
+# Egress rule for HTTP traffic from ALB to service
+resource "aws_vpc_security_group_egress_rule" "service_outbound_alb" {
+  count                        = var.create_external_alb ? 1 : 0
+  security_group_id            = aws_security_group.teamcity_alb_sg[0].id
+  referenced_security_group_id = aws_security_group.teamcity_service_sg.id
+  description                  = "Allow outbound HTTP traffic from ALB to service containers"
+  from_port                    = var.container_port
+  to_port                      = var.container_port
+  ip_protocol                  = "TCP"
+}
+
+# Grant TeamCity service access to internet
+resource "aws_vpc_security_group_egress_rule" "internet_outbound" {
+  security_group_id = aws_security_group.teamcity_service_sg.id
+  description       = "Allow outbound internet access from TeamCity service containers"
+  cidr_ipv4         = "0.0.0.0/0"
+  ip_protocol       = "-1"
+}
+
+#############################################
+# IAM Roles for TeamCity Module
+#############################################
+
+data "aws_iam_policy_document" "ecs_tasks_trust_relationship" {
+  statement {
+    effect  = "Allow"
+    actions = ["sts:AssumeRole"]
+    principals {
+      type        = "Service"
+      identifiers = ["ecs-tasks.amazonaws.com"]
+    }
+  }
+}
+
+data "aws_iam_policy_document" "teamcity_default_policy" {
+  # ECS
+  statement {
+    sid    = "ECSExec"
+    effect = "Allow"
+    actions = [
+      "ssmmessages:OpenDataChannel",
+      "ssmmessages:OpenControlChannel",
+      "ssmmessages:CreateDataChannel",
+      "ssmmessages:CreateControlChannel",
+    ]
+    resources = [
+      "*"
+    ]
+  }
+
+  #grant the task necessary EFS permissions to be able to modify directories
+  statement {
+    sid    = "EFS"
+    effect = "Allow"
+    actions = [
+      "elasticfilesystem:ClientWrite",
+      "elasticfilesystem:ClientRootAccess",
+      "elasticfilesystem:ClientMount"
+    ]
+    resources = [
+      aws_efs_file_system.teamcity_efs_file_system[0].arn
+    ]
+  }
+}
+
+resource "aws_iam_policy" "teamcity_default_policy" {
+  name        = "teamcity-default-policy"
+  description = "Policy granting permissions for Unreal Horde."
+  policy      = data.aws_iam_policy_document.teamcity_default_policy.json
+}
+
+resource "aws_iam_role" "teamcity_default_role" {
+  name               = "teamcity-default-role"
+  description        = "Default role for TeamCity ECS task."
+  assume_role_policy = data.aws_iam_policy_document.ecs_tasks_trust_relationship.json
+  managed_policy_arns = [
+    aws_iam_policy.teamcity_default_policy.arn
+  ]
+
+  tags = local.tags
+}
+resource "aws_iam_role" "teamcity_task_execution_role" {
+  name = "teamcity-task-execution-role"
+
+  assume_role_policy  = data.aws_iam_policy_document.ecs_tasks_trust_relationship.json
+  managed_policy_arns = ["arn:aws:iam::aws:policy/service-role/AmazonECSTaskExecutionRolePolicy"]
+
+  inline_policy {
+    name = "teamcity-secrets-access"
+    policy = jsonencode({
+      Version = "2012-10-17"
+      Statement = [
+        {
+          Sid    = "SecretsManager"
+          Effect = "Allow"
+          Action = [
+            "secretsmanager:GetSecretValue"
+          ]
+          Resource = [
+            aws_rds_cluster.teamcity_db_cluster[0].master_user_secret[0].secret_arn
+          ]
+        }
+      ]
+    })
+  }
+}
+
+
+# CloudWatch Logs
+resource "aws_cloudwatch_log_group" "teamcity_log_group" {
+  #checkov:skip=CKV_AWS_158: KMS Encryption disabled by default
+  name              = "${local.name_prefix}-log-group"
+  retention_in_days = var.teamcity_cloudwatch_log_retention_in_days
+  tags              = local.tags
+}
+
+# Load Balancer for TeamCity Service
+resource "aws_lb" "teamcity_external_lb" {
+  count              = var.create_external_alb ? 1 : 0
+  name               = "${local.name_prefix}-lb"
+  security_groups    = [aws_security_group.teamcity_alb_sg[0].id]
+  load_balancer_type = "application"
+  internal           = false
+  subnets            = var.alb_subnets
+
+
+  dynamic "access_logs" {
+    for_each = var.enable_teamcity_alb_access_logs ? [1] : []
+    content {
+      enabled = var.enable_teamcity_alb_access_logs
+      bucket  = var.teamcity_alb_access_logs_bucket != null ? var.teamcity_alb_access_logs_bucket : aws_s3_bucket.teamcity_alb_access_logs_bucket[0].id
+      prefix  = var.teamcity_alb_access_logs_prefix != null ? var.teamcity_alb_access_logs_prefix : "${local.name_prefix}-alb"
+    }
+  }
+  #checkov:skip=CKV_AWS_150:Deletion protection disabled by default
+  enable_deletion_protection = var.enable_teamcity_alb_deletion_protection
+
+  #checkov:skip=CKV2_AWS_28: ALB access is managed with SG allow listing
+
+  drop_invalid_header_fields = true
+  tags                       = local.tags
+}
+
+# TeamCity target group for ALB
+resource "aws_lb_target_group" "teamcity_target_group" {
+  count       = var.create_external_alb ? 1 : 0
+  name        = "${local.name_prefix}-tg"
+  port        = var.container_port
+  protocol    = "HTTP"
+  vpc_id      = var.vpc_id
+  target_type = "ip"
+
+  health_check {
+    path                = "/healthCheck/healthy"
+    interval            = 30
+    timeout             = 5
+    healthy_threshold   = 5
+    unhealthy_threshold = 2
+    port                = var.container_port
+    protocol            = "HTTP"
+    matcher             = "200"
+  }
+
+  tags = local.tags
+}
+
+# ALB HTTPS Listener
+resource "aws_lb_listener" "teamcity_listener" {
+  count             = var.create_external_alb ? 1 : 0
+  load_balancer_arn = aws_lb.teamcity_external_lb[0].arn
+  port              = "443"
+  protocol          = "HTTPS"
+  ssl_policy        = "ELBSecurityPolicy-TLS13-1-2-2021-06"
+  certificate_arn   = var.alb_certificate_arn
+  default_action {
+    type             = "forward"
+    target_group_arn = aws_lb_target_group.teamcity_target_group[0].arn
+  }
+  tags = local.tags
+}
+
+# #######################################
+# # TeamCity Aurora Serverless Database #
+# #######################################
+# Subnet group
+resource "aws_db_subnet_group" "teamcity_db_subnet_group" {
+  count      = var.database_connection_string == null ? 1 : 0
+  name       = "${local.name_prefix}-db-subnet-group"
+  subnet_ids = var.service_subnets
+  tags       = local.tags
+}
+
+# # RDS instance with Aurora serverless engine
+resource "aws_rds_cluster" "teamcity_db_cluster" {
+  #checkov:skip=CKV2_AWS_27:not enabling query logging by design
+  #checkov:skip=CKV2_AWS_8: TODO: add rds backup plan
+  count                       = var.database_connection_string == null ? 1 : 0
+  cluster_identifier          = "teamcity-cluster"
+  engine                      = "aurora-postgresql"
+  engine_mode                 = "provisioned"
+  engine_version              = "16.6" #check for latest as option
+  database_name               = "teamcity"
+  master_username             = "teamcity"
+  manage_master_user_password = true #using AWS Secrets Manager
+  storage_encrypted           = true
+  skip_final_snapshot         = var.aurora_skip_final_snapshot
+  db_subnet_group_name        = aws_db_subnet_group.teamcity_db_subnet_group[0].id
+  vpc_security_group_ids = [
+    aws_security_group.teamcity_db_sg[0].id
+  ]
+
+  serverlessv2_scaling_configuration {
+    max_capacity             = 1.0
+    min_capacity             = 0.0
+    seconds_until_auto_pause = 3600
+  }
+}
+
+resource "aws_rds_cluster_instance" "teamcity_db_cluster_instance" {
+  count              = var.database_connection_string == null ? var.aurora_instance_count : 0
+  cluster_identifier = aws_rds_cluster.teamcity_db_cluster[0].id
+  instance_class     = "db.serverless"
+  engine             = aws_rds_cluster.teamcity_db_cluster[0].engine
+  engine_version     = aws_rds_cluster.teamcity_db_cluster[0].engine_version
+}
+
+# ################
+# # TeamCity EFS #
+# ################
+
+# File system for teamcity
+resource "aws_efs_file_system" "teamcity_efs_file_system" {
+  count            = var.efs_id == null ? 1 : 0
+  creation_token   = "${local.name_prefix}-efs-file-system"
+  performance_mode = var.teamcity_efs_performance_mode
+  throughput_mode  = var.teamcity_efs_throughput_mode
+
+  #TODO: Parameterize encryption and customer managed key creation
+  encrypted = false
+
+  lifecycle_policy {
+    transition_to_ia = "AFTER_30_DAYS"
+  }
+
+  lifecycle_policy {
+    transition_to_primary_storage_class = "AFTER_1_ACCESS"
+  }
+  #checkov:skip=CKV_AWS_184: CMK encryption not supported currently
+  tags = merge(local.tags, {
+    Name = "${local.name_prefix}-efs-file-system"
+  })
+}
+
+#check for existing mount targets
+
+
+# # Mount Point for teamcity file system
+resource "aws_efs_mount_target" "teamcity_efs_mount_target" {
+  count          = length(var.service_subnets)
+  file_system_id = var.efs_id != null ? var.efs_id : aws_efs_file_system.teamcity_efs_file_system[0].id
+  subnet_id      = var.service_subnets[count.index]
+  security_groups = [
+    aws_security_group.teamcity_efs_sg.id
+  ]
+}
+
+# TeamCity data directory
+resource "aws_efs_access_point" "teamcity_efs_data_access_point" {
+  file_system_id = aws_efs_file_system.teamcity_efs_file_system[0].id
+  posix_user {
+    gid = 1000
+    uid = 1000
+  }
+  root_directory {
+    path = "/data/teamcity_user/datadir"
+    creation_info {
+      owner_gid   = 1000
+      owner_uid   = 1000
+      permissions = 0755
+    }
+  }
+  tags = merge(local.tags, {
+    Name = "${local.name_prefix}-efs-access-point"
+  })
+}
+
+###########################
+# Access Logs
+###########################
+
+resource "random_string" "teamcity_alb_access_logs_bucket_suffix" {
+  count   = var.enable_teamcity_alb_access_logs && var.teamcity_alb_access_logs_bucket == null ? 1 : 0
+  length  = 8
+  special = false
+  upper   = false
+}
+
+resource "aws_s3_bucket" "teamcity_alb_access_logs_bucket" {
+  count  = var.enable_teamcity_alb_access_logs && var.teamcity_alb_access_logs_bucket == null ? 1 : 0
+  bucket = "${local.name_prefix}-alb-access-logs-${random_string.teamcity_alb_access_logs_bucket_suffix[0].result}"
+
+  #checkov:skip=CKV_AWS_21: Versioning not necessary for access logs
+  #checkov:skip=CKV_AWS_144: Cross-region replication not necessary for access logs
+  #checkov:skip=CKV_AWS_145: KMS encryption with CMK not currently supported
+  #checkov:skip=CKV_AWS_18: S3 access logs not necessary
+  #checkov:skip=CKV2_AWS_62: Event notifications not necessary
+
+  tags = merge(local.tags, {
+    Name = "${local.name_prefix}-alb-access-logs-${random_string.teamcity_alb_access_logs_bucket_suffix[0].result}"
+  })
+}
path: modules/teamcity/main.tf
user: login: github-advanced-security[bot]
body: ## Ensure that S3 bucket has cross-region replication enabled

Ensure that S3 bucket has cross-region replication enabled

[Show more details](https://github.com/aws-games/cloud-game-development-toolkit/security/code-scanning/844)
created_at: 2025-03-06T22:53:05Z
author_association: NONE
diff_hunk: @@ -0,0 +1,617 @@
+###################################
+# ECS Cluster for TeamCity Server #
+###################################
+# If cluster name is provided use a data source to access existing resource
+data "aws_ecs_cluster" "teamcity_cluster" {
+  count        = var.cluster_name != null ? 1 : 0
+  cluster_name = var.cluster_name
+}
+resource "aws_ecs_cluster" "teamcity_cluster" {
+  count = var.cluster_name != null ? 0 : 1
+  name  = "${local.name_prefix}-cluster"
+
+  setting {
+    name  = "containerInsights"
+    value = "enabled"
+  }
+
+  tags = local.tags
+}
+
+# TeamCity Task Definition
+resource "aws_ecs_task_definition" "teamcity_task_definition" {
+  family                   = var.name
+  requires_compatibilities = ["FARGATE"]
+  network_mode             = "awsvpc"
+  cpu                      = var.container_cpu
+  memory                   = var.container_memory
+
+  container_definitions = jsonencode([
+    {
+      name      = var.container_name
+      image     = local.image
+      cpu       = var.container_cpu
+      memory    = var.container_memory
+      essential = true
+
+      portMappings = [
+        {
+          containerPort = var.container_port
+          hostPort      = var.container_port
+        }
+      ]
+      logConfiguration = {
+        logDriver = "awslogs"
+        options = {
+          awslogs-group         = aws_cloudwatch_log_group.teamcity_log_group.name
+          awslogs-region        = data.aws_region.current.name
+          awslogs-stream-prefix = "[APP]"
+        }
+      }
+
+      mountPoints = [
+        {
+          sourceVolume  = "teamcity_data",
+          containerPath = "/data/teamcity_server/datadir"
+          readOnly      = false
+        }
+      ]
+
+      # # Set environment variables for database connection string
+      # environment = [
+      #   {
+      #     name  = "TEAMCITY_DB_URL"
+      #     value = local.database_connection_string
+      #   },
+      #   {
+      #     name  = "TEAMCITY_DB_USER"
+      #     value = local.database_master_username
+      #   },
+      #   # {
+      #   #   name  = "TEAMCITY_LOGS_PATH"
+      #   #   value = "/data/teamcity_server/logs"
+      #   # },
+      #   {
+      #     name  = "TEAMCITY_DATA_PATH"
+      #     value = "/data/teamcity_server/datadir"
+      #   }
+      # ]
+      # # Define password environment variable if provided
+      # password_env = local.database_master_password != null ? [
+      #   {
+      #     name  = "TEAMCITY_DB_PASSWORD"
+      #     value = local.database_master_password
+      #   }
+      # ] : []
+
+      # Combine the lists
+      environment = concat(local.base_env, local.password_env)
+
+      secrets = var.database_connection_string == null ? [
+        {
+          name      = "TEAMCITY_DB_PASSWORD"
+          valueFrom = "${aws_rds_cluster.teamcity_db_cluster[0].master_user_secret[0].secret_arn}:password::"
+        }
+      ] : []
+    }
+  ])
+  tags = {
+    Name = var.name
+  }
+  task_role_arn      = aws_iam_role.teamcity_default_role.arn
+  execution_role_arn = aws_iam_role.teamcity_task_execution_role.arn
+
+  volume {
+    name = "teamcity_data"
+    efs_volume_configuration {
+      file_system_id          = aws_efs_file_system.teamcity_efs_file_system[0].id
+      transit_encryption      = "ENABLED"
+      transit_encryption_port = 2999
+      authorization_config {
+        access_point_id = aws_efs_access_point.teamcity_efs_data_access_point.id
+        iam             = "ENABLED"
+      }
+    }
+  }
+}
+
+# ECS Service
+resource "aws_ecs_service" "teamcity" {
+  name                   = local.name_prefix
+  cluster                = var.cluster_name != null ? data.aws_ecs_cluster.teamcity_cluster[0].arn : aws_ecs_cluster.teamcity_cluster[0].arn
+  task_definition        = aws_ecs_task_definition.teamcity_task_definition.arn
+  launch_type            = "FARGATE"
+  desired_count          = var.desired_container_count
+  force_new_deployment   = var.debug
+  enable_execute_command = var.debug
+
+  wait_for_steady_state = false #TODO: make this configurable
+
+  network_configuration {
+    subnets         = var.service_subnets
+    security_groups = [aws_security_group.teamcity_service_sg.id]
+  }
+  dynamic "load_balancer" {
+    for_each = var.create_external_alb ? [1] : []
+    content {
+      target_group_arn = aws_lb_target_group.teamcity_target_group[0].arn
+      container_name   = var.container_name
+      container_port   = var.container_port
+
+    }
+  }
+
+  tags = local.tags
+}
+
+###################################
+# Security Groups                 #
+###################################
+
+# TeamCity service security group
+resource "aws_security_group" "teamcity_service_sg" {
+  name        = "${local.name_prefix}-service-sg"
+  vpc_id      = var.vpc_id
+  description = "TeamCity service security group"
+  tags        = local.tags
+}
+
+# TeamCity EFS security group
+resource "aws_security_group" "teamcity_efs_sg" {
+  # count       = var.efs_id == null ? 1 : 0
+  name        = "${local.name_prefix}-efs-sg"
+  description = "TeamCity EFS mount target security group"
+  vpc_id      = var.vpc_id
+  tags        = local.tags
+}
+
+# Ingress rule for NFS traffic from service to EFS
+resource "aws_vpc_security_group_ingress_rule" "service_efs" {
+  # count                        = var.efs_id == null ? 1 : 0
+  security_group_id            = aws_security_group.teamcity_efs_sg.id
+  referenced_security_group_id = aws_security_group.teamcity_service_sg.id
+  description                  = "Allow inbound access from TeamCity service containers to EFS"
+  ip_protocol                  = "TCP"
+  from_port                    = 2049
+  to_port                      = 2049
+}
+
+# Ingress rule for NFS traffic from EFS to service
+resource "aws_vpc_security_group_ingress_rule" "efs_service" {
+  # count                        = var.efs_id == null ? 1 : 0
+  security_group_id            = aws_security_group.teamcity_service_sg.id
+  referenced_security_group_id = aws_security_group.teamcity_efs_sg.id
+  description                  = "Allow inbound access from EFS to TeamCity service containers"
+  ip_protocol                  = "TCP"
+  from_port                    = 2049
+  to_port                      = 2049
+}
+
+# TeamCity Aurora Serverless PostgreSQL security group
+resource "aws_security_group" "teamcity_db_sg" {
+  count       = var.database_connection_string == null ? 1 : 0
+  name        = "${local.name_prefix}-db-sg"
+  description = "TeamCity DB security group"
+  vpc_id      = var.vpc_id
+  tags        = local.tags
+}
+
+# Ingress rule for PostgreSQL from service to database cluster
+resource "aws_vpc_security_group_ingress_rule" "service_db" {
+  count                        = var.database_connection_string == null ? 1 : 0
+  security_group_id            = aws_security_group.teamcity_db_sg[0].id
+  referenced_security_group_id = aws_security_group.teamcity_service_sg.id
+  description                  = "Allow inbound access from TeamCity service containers to DB"
+  ip_protocol                  = "TCP"
+  from_port                    = 5432
+  to_port                      = 5432
+}
+
+# Ingress rule for PostgreSQL from database cluster to service
+resource "aws_vpc_security_group_ingress_rule" "db_service" {
+  count                        = var.database_connection_string == null ? 1 : 0
+  security_group_id            = aws_security_group.teamcity_service_sg.id
+  referenced_security_group_id = aws_security_group.teamcity_db_sg[0].id
+  description                  = "Allow inbound access from DB to TeamCity service containers"
+  ip_protocol                  = "TCP"
+  from_port                    = 5432
+  to_port                      = 5432
+}
+
+# Egress rule for database to let all traffic out
+resource "aws_vpc_security_group_egress_rule" "db_outbound" {
+  count             = var.database_connection_string == null ? 1 : 0
+  security_group_id = aws_security_group.teamcity_db_sg[0].id
+  description       = "Allow outbound access from DB to all"
+  cidr_ipv4         = "0.0.0.0/0"
+  ip_protocol       = "-1"
+}
+
+# TeamCity ALB security group
+resource "aws_security_group" "teamcity_alb_sg" {
+  #checkov:skip=CKV2_AWS_5:SG is attached to TeamCity service ALB
+  count       = var.create_external_alb ? 1 : 0
+  name        = "${local.name_prefix}-alb-sg"
+  vpc_id      = var.vpc_id
+  description = "TeamCity ALB security group"
+  tags        = local.tags
+}
+
+# Ingress rule for HTTP traffic from ALB to service
+resource "aws_vpc_security_group_ingress_rule" "service_inbound_alb" {
+  count                        = var.create_external_alb ? 1 : 0
+  security_group_id            = aws_security_group.teamcity_service_sg.id
+  referenced_security_group_id = aws_security_group.teamcity_alb_sg[0].id
+  description                  = "Allow inbound HTTP traffic from ALB to service containers"
+  from_port                    = var.container_port
+  to_port                      = var.container_port
+  ip_protocol                  = "TCP"
+}
+
+# Egress rule for HTTP traffic from ALB to service
+resource "aws_vpc_security_group_egress_rule" "service_outbound_alb" {
+  count                        = var.create_external_alb ? 1 : 0
+  security_group_id            = aws_security_group.teamcity_alb_sg[0].id
+  referenced_security_group_id = aws_security_group.teamcity_service_sg.id
+  description                  = "Allow outbound HTTP traffic from ALB to service containers"
+  from_port                    = var.container_port
+  to_port                      = var.container_port
+  ip_protocol                  = "TCP"
+}
+
+# Grant TeamCity service access to internet
+resource "aws_vpc_security_group_egress_rule" "internet_outbound" {
+  security_group_id = aws_security_group.teamcity_service_sg.id
+  description       = "Allow outbound internet access from TeamCity service containers"
+  cidr_ipv4         = "0.0.0.0/0"
+  ip_protocol       = "-1"
+}
+
+#############################################
+# IAM Roles for TeamCity Module
+#############################################
+
+data "aws_iam_policy_document" "ecs_tasks_trust_relationship" {
+  statement {
+    effect  = "Allow"
+    actions = ["sts:AssumeRole"]
+    principals {
+      type        = "Service"
+      identifiers = ["ecs-tasks.amazonaws.com"]
+    }
+  }
+}
+
+data "aws_iam_policy_document" "teamcity_default_policy" {
+  # ECS
+  statement {
+    sid    = "ECSExec"
+    effect = "Allow"
+    actions = [
+      "ssmmessages:OpenDataChannel",
+      "ssmmessages:OpenControlChannel",
+      "ssmmessages:CreateDataChannel",
+      "ssmmessages:CreateControlChannel",
+    ]
+    resources = [
+      "*"
+    ]
+  }
+
+  #grant the task necessary EFS permissions to be able to modify directories
+  statement {
+    sid    = "EFS"
+    effect = "Allow"
+    actions = [
+      "elasticfilesystem:ClientWrite",
+      "elasticfilesystem:ClientRootAccess",
+      "elasticfilesystem:ClientMount"
+    ]
+    resources = [
+      aws_efs_file_system.teamcity_efs_file_system[0].arn
+    ]
+  }
+}
+
+resource "aws_iam_policy" "teamcity_default_policy" {
+  name        = "teamcity-default-policy"
+  description = "Policy granting permissions for Unreal Horde."
+  policy      = data.aws_iam_policy_document.teamcity_default_policy.json
+}
+
+resource "aws_iam_role" "teamcity_default_role" {
+  name               = "teamcity-default-role"
+  description        = "Default role for TeamCity ECS task."
+  assume_role_policy = data.aws_iam_policy_document.ecs_tasks_trust_relationship.json
+  managed_policy_arns = [
+    aws_iam_policy.teamcity_default_policy.arn
+  ]
+
+  tags = local.tags
+}
+resource "aws_iam_role" "teamcity_task_execution_role" {
+  name = "teamcity-task-execution-role"
+
+  assume_role_policy  = data.aws_iam_policy_document.ecs_tasks_trust_relationship.json
+  managed_policy_arns = ["arn:aws:iam::aws:policy/service-role/AmazonECSTaskExecutionRolePolicy"]
+
+  inline_policy {
+    name = "teamcity-secrets-access"
+    policy = jsonencode({
+      Version = "2012-10-17"
+      Statement = [
+        {
+          Sid    = "SecretsManager"
+          Effect = "Allow"
+          Action = [
+            "secretsmanager:GetSecretValue"
+          ]
+          Resource = [
+            aws_rds_cluster.teamcity_db_cluster[0].master_user_secret[0].secret_arn
+          ]
+        }
+      ]
+    })
+  }
+}
+
+
+# CloudWatch Logs
+resource "aws_cloudwatch_log_group" "teamcity_log_group" {
+  #checkov:skip=CKV_AWS_158: KMS Encryption disabled by default
+  name              = "${local.name_prefix}-log-group"
+  retention_in_days = var.teamcity_cloudwatch_log_retention_in_days
+  tags              = local.tags
+}
+
+# Load Balancer for TeamCity Service
+resource "aws_lb" "teamcity_external_lb" {
+  count              = var.create_external_alb ? 1 : 0
+  name               = "${local.name_prefix}-lb"
+  security_groups    = [aws_security_group.teamcity_alb_sg[0].id]
+  load_balancer_type = "application"
+  internal           = false
+  subnets            = var.alb_subnets
+
+
+  dynamic "access_logs" {
+    for_each = var.enable_teamcity_alb_access_logs ? [1] : []
+    content {
+      enabled = var.enable_teamcity_alb_access_logs
+      bucket  = var.teamcity_alb_access_logs_bucket != null ? var.teamcity_alb_access_logs_bucket : aws_s3_bucket.teamcity_alb_access_logs_bucket[0].id
+      prefix  = var.teamcity_alb_access_logs_prefix != null ? var.teamcity_alb_access_logs_prefix : "${local.name_prefix}-alb"
+    }
+  }
+  #checkov:skip=CKV_AWS_150:Deletion protection disabled by default
+  enable_deletion_protection = var.enable_teamcity_alb_deletion_protection
+
+  #checkov:skip=CKV2_AWS_28: ALB access is managed with SG allow listing
+
+  drop_invalid_header_fields = true
+  tags                       = local.tags
+}
+
+# TeamCity target group for ALB
+resource "aws_lb_target_group" "teamcity_target_group" {
+  count       = var.create_external_alb ? 1 : 0
+  name        = "${local.name_prefix}-tg"
+  port        = var.container_port
+  protocol    = "HTTP"
+  vpc_id      = var.vpc_id
+  target_type = "ip"
+
+  health_check {
+    path                = "/healthCheck/healthy"
+    interval            = 30
+    timeout             = 5
+    healthy_threshold   = 5
+    unhealthy_threshold = 2
+    port                = var.container_port
+    protocol            = "HTTP"
+    matcher             = "200"
+  }
+
+  tags = local.tags
+}
+
+# ALB HTTPS Listener
+resource "aws_lb_listener" "teamcity_listener" {
+  count             = var.create_external_alb ? 1 : 0
+  load_balancer_arn = aws_lb.teamcity_external_lb[0].arn
+  port              = "443"
+  protocol          = "HTTPS"
+  ssl_policy        = "ELBSecurityPolicy-TLS13-1-2-2021-06"
+  certificate_arn   = var.alb_certificate_arn
+  default_action {
+    type             = "forward"
+    target_group_arn = aws_lb_target_group.teamcity_target_group[0].arn
+  }
+  tags = local.tags
+}
+
+# #######################################
+# # TeamCity Aurora Serverless Database #
+# #######################################
+# Subnet group
+resource "aws_db_subnet_group" "teamcity_db_subnet_group" {
+  count      = var.database_connection_string == null ? 1 : 0
+  name       = "${local.name_prefix}-db-subnet-group"
+  subnet_ids = var.service_subnets
+  tags       = local.tags
+}
+
+# # RDS instance with Aurora serverless engine
+resource "aws_rds_cluster" "teamcity_db_cluster" {
+  #checkov:skip=CKV2_AWS_27:not enabling query logging by design
+  #checkov:skip=CKV2_AWS_8: TODO: add rds backup plan
+  count                       = var.database_connection_string == null ? 1 : 0
+  cluster_identifier          = "teamcity-cluster"
+  engine                      = "aurora-postgresql"
+  engine_mode                 = "provisioned"
+  engine_version              = "16.6" #check for latest as option
+  database_name               = "teamcity"
+  master_username             = "teamcity"
+  manage_master_user_password = true #using AWS Secrets Manager
+  storage_encrypted           = true
+  skip_final_snapshot         = var.aurora_skip_final_snapshot
+  db_subnet_group_name        = aws_db_subnet_group.teamcity_db_subnet_group[0].id
+  vpc_security_group_ids = [
+    aws_security_group.teamcity_db_sg[0].id
+  ]
+
+  serverlessv2_scaling_configuration {
+    max_capacity             = 1.0
+    min_capacity             = 0.0
+    seconds_until_auto_pause = 3600
+  }
+}
+
+resource "aws_rds_cluster_instance" "teamcity_db_cluster_instance" {
+  count              = var.database_connection_string == null ? var.aurora_instance_count : 0
+  cluster_identifier = aws_rds_cluster.teamcity_db_cluster[0].id
+  instance_class     = "db.serverless"
+  engine             = aws_rds_cluster.teamcity_db_cluster[0].engine
+  engine_version     = aws_rds_cluster.teamcity_db_cluster[0].engine_version
+}
+
+# ################
+# # TeamCity EFS #
+# ################
+
+# File system for teamcity
+resource "aws_efs_file_system" "teamcity_efs_file_system" {
+  count            = var.efs_id == null ? 1 : 0
+  creation_token   = "${local.name_prefix}-efs-file-system"
+  performance_mode = var.teamcity_efs_performance_mode
+  throughput_mode  = var.teamcity_efs_throughput_mode
+
+  #TODO: Parameterize encryption and customer managed key creation
+  encrypted = false
+
+  lifecycle_policy {
+    transition_to_ia = "AFTER_30_DAYS"
+  }
+
+  lifecycle_policy {
+    transition_to_primary_storage_class = "AFTER_1_ACCESS"
+  }
+  #checkov:skip=CKV_AWS_184: CMK encryption not supported currently
+  tags = merge(local.tags, {
+    Name = "${local.name_prefix}-efs-file-system"
+  })
+}
+
+#check for existing mount targets
+
+
+# # Mount Point for teamcity file system
+resource "aws_efs_mount_target" "teamcity_efs_mount_target" {
+  count          = length(var.service_subnets)
+  file_system_id = var.efs_id != null ? var.efs_id : aws_efs_file_system.teamcity_efs_file_system[0].id
+  subnet_id      = var.service_subnets[count.index]
+  security_groups = [
+    aws_security_group.teamcity_efs_sg.id
+  ]
+}
+
+# TeamCity data directory
+resource "aws_efs_access_point" "teamcity_efs_data_access_point" {
+  file_system_id = aws_efs_file_system.teamcity_efs_file_system[0].id
+  posix_user {
+    gid = 1000
+    uid = 1000
+  }
+  root_directory {
+    path = "/data/teamcity_user/datadir"
+    creation_info {
+      owner_gid   = 1000
+      owner_uid   = 1000
+      permissions = 0755
+    }
+  }
+  tags = merge(local.tags, {
+    Name = "${local.name_prefix}-efs-access-point"
+  })
+}
+
+###########################
+# Access Logs
+###########################
+
+resource "random_string" "teamcity_alb_access_logs_bucket_suffix" {
+  count   = var.enable_teamcity_alb_access_logs && var.teamcity_alb_access_logs_bucket == null ? 1 : 0
+  length  = 8
+  special = false
+  upper   = false
+}
+
+resource "aws_s3_bucket" "teamcity_alb_access_logs_bucket" {
+  count  = var.enable_teamcity_alb_access_logs && var.teamcity_alb_access_logs_bucket == null ? 1 : 0
+  bucket = "${local.name_prefix}-alb-access-logs-${random_string.teamcity_alb_access_logs_bucket_suffix[0].result}"
+
+  #checkov:skip=CKV_AWS_21: Versioning not necessary for access logs
+  #checkov:skip=CKV_AWS_144: Cross-region replication not necessary for access logs
+  #checkov:skip=CKV_AWS_145: KMS encryption with CMK not currently supported
+  #checkov:skip=CKV_AWS_18: S3 access logs not necessary
+  #checkov:skip=CKV2_AWS_62: Event notifications not necessary
+
+  tags = merge(local.tags, {
+    Name = "${local.name_prefix}-alb-access-logs-${random_string.teamcity_alb_access_logs_bucket_suffix[0].result}"
+  })
+}
path: modules/teamcity/main.tf
user: login: github-advanced-security[bot]
body: ## Ensure the S3 bucket has access logging enabled

Ensure the S3 bucket has access logging enabled

[Show more details](https://github.com/aws-games/cloud-game-development-toolkit/security/code-scanning/845)
created_at: 2025-03-06T22:53:05Z
author_association: NONE
diff_hunk: @@ -0,0 +1,590 @@
+###################################
+# ECS Cluster for TeamCity Server #
+###################################
+# If cluster name is provided use a data source to access existing resource
+data "aws_ecs_cluster" "teamcity_cluster" {
+  count        = var.cluster_name != null ? 1 : 0
+  cluster_name = var.cluster_name
+}
+resource "aws_ecs_cluster" "teamcity_cluster" {
+  count = var.cluster_name != null ? 0 : 1
+  name  = "${local.name_prefix}-cluster"
+
+  setting {
+    name  = "containerInsights"
+    value = "enabled"
+  }
+
+  tags = local.tags
+}
+
+# TeamCity Task Definition
+resource "aws_ecs_task_definition" "teamcity_task_definition" {
+  family                   = var.name
+  requires_compatibilities = ["FARGATE"]
+  network_mode             = "awsvpc"
+  cpu                      = var.container_cpu
+  memory                   = var.container_memory
+
+  container_definitions = jsonencode([
+    {
+      name      = var.container_name
+      image     = local.image
+      cpu       = var.container_cpu
+      memory    = var.container_memory
+      essential = true
+
+      portMappings = [
+        {
+          containerPort = var.container_port
+          hostPort      = var.container_port
+        }
+      ]
+      logConfiguration = {
+        logDriver = "awslogs"
+        options = {
+          awslogs-group         = aws_cloudwatch_log_group.teamcity_log_group.name
+          awslogs-region        = data.aws_region.current.name
+          awslogs-stream-prefix = "[APP]"
+        }
+      }
+
+      mountPoints = [
+        {
+          sourceVolume  = "teamcity_data",
+          containerPath = "/data/teamcity_server/datadir"
+          readOnly      = false
+        }
+      ]
+
+      # Combine the lists
+      environment = concat(local.base_env, local.password_env)
+
+      secrets = var.database_connection_string == null ? [
+        {
+          name      = "TEAMCITY_DB_PASSWORD"
+          valueFrom = "${aws_rds_cluster.teamcity_db_cluster[0].master_user_secret[0].secret_arn}:password::"
+        }
+      ] : []
+    }
+  ])
+  tags = {
+    Name = var.name
+  }
+  task_role_arn      = aws_iam_role.teamcity_default_role.arn
+  execution_role_arn = aws_iam_role.teamcity_task_execution_role.arn
+
+  volume {
+    name = "teamcity_data"
+    efs_volume_configuration {
+      file_system_id          = local.efs_file_system_id
+      transit_encryption      = "ENABLED"
+      transit_encryption_port = 2999
+      authorization_config {
+        access_point_id = local.efs_access_point_id
+        iam             = "ENABLED"
+      }
+    }
+  }
+}
+
+# ECS Service
+resource "aws_ecs_service" "teamcity" {
+  name                   = local.name_prefix
+  cluster                = var.cluster_name != null ? data.aws_ecs_cluster.teamcity_cluster[0].arn : aws_ecs_cluster.teamcity_cluster[0].arn
+  task_definition        = aws_ecs_task_definition.teamcity_task_definition.arn
+  launch_type            = "FARGATE"
+  desired_count          = var.desired_container_count
+  force_new_deployment   = var.debug
+  enable_execute_command = var.debug
+
+  wait_for_steady_state = false #TODO: make this configurable
+
+  network_configuration {
+    subnets         = var.service_subnets
+    security_groups = [aws_security_group.teamcity_service_sg.id]
+  }
+  dynamic "load_balancer" {
+    for_each = var.create_external_alb ? [1] : []
+    content {
+      target_group_arn = aws_lb_target_group.teamcity_target_group[0].arn
+      container_name   = var.container_name
+      container_port   = var.container_port
+
+    }
+  }
+
+  tags = local.tags
+}
+
+###################################
+# Security Groups                 #
+###################################
+
+# TeamCity service security group
+resource "aws_security_group" "teamcity_service_sg" {
+  name        = "${local.name_prefix}-service-sg"
+  vpc_id      = var.vpc_id
+  description = "TeamCity service security group"
+  tags        = local.tags
+}
+
+# TeamCity EFS security group
+resource "aws_security_group" "teamcity_efs_sg" {
+  count       = var.efs_id == null ? 1 : 0
+  name        = "${local.name_prefix}-efs-sg"
+  description = "TeamCity EFS mount target security group"
+  vpc_id      = var.vpc_id
+  tags        = local.tags
+}
+
+# Ingress rule for NFS traffic from service to EFS
+resource "aws_vpc_security_group_ingress_rule" "service_efs" {
+  count                        = var.efs_id == null ? 1 : 0
+  security_group_id            = aws_security_group.teamcity_efs_sg[0].id
+  referenced_security_group_id = aws_security_group.teamcity_service_sg.id
+  description                  = "Allow inbound access from TeamCity service containers to EFS"
+  ip_protocol                  = "TCP"
+  from_port                    = 2049
+  to_port                      = 2049
+}
+
+# Ingress rule for NFS traffic from EFS to service
+resource "aws_vpc_security_group_ingress_rule" "efs_service" {
+  count                        = var.efs_id == null ? 1 : 0
+  security_group_id            = aws_security_group.teamcity_service_sg.id
+  referenced_security_group_id = aws_security_group.teamcity_efs_sg[0].id
+  description                  = "Allow inbound access from EFS to TeamCity service containers"
+  ip_protocol                  = "TCP"
+  from_port                    = 2049
+  to_port                      = 2049
+}
+
+# TeamCity Aurora Serverless PostgreSQL security group
+resource "aws_security_group" "teamcity_db_sg" {
+  count       = var.database_connection_string == null ? 1 : 0
+  name        = "${local.name_prefix}-db-sg"
+  description = "TeamCity DB security group"
+  vpc_id      = var.vpc_id
+  tags        = local.tags
+}
+
+# Ingress rule for PostgreSQL from service to database cluster
+resource "aws_vpc_security_group_ingress_rule" "service_db" {
+  count                        = var.database_connection_string == null ? 1 : 0
+  security_group_id            = aws_security_group.teamcity_db_sg[0].id
+  referenced_security_group_id = aws_security_group.teamcity_service_sg.id
+  description                  = "Allow inbound access from TeamCity service containers to DB"
+  ip_protocol                  = "TCP"
+  from_port                    = 5432
+  to_port                      = 5432
+}
+
+# Ingress rule for PostgreSQL from database cluster to service
+resource "aws_vpc_security_group_ingress_rule" "db_service" {
+  count                        = var.database_connection_string == null ? 1 : 0
+  security_group_id            = aws_security_group.teamcity_service_sg.id
+  referenced_security_group_id = aws_security_group.teamcity_db_sg[0].id
+  description                  = "Allow inbound access from DB to TeamCity service containers"
+  ip_protocol                  = "TCP"
+  from_port                    = 5432
+  to_port                      = 5432
+}
+
+# Egress rule for database to let all traffic out
+resource "aws_vpc_security_group_egress_rule" "db_outbound" {
+  count             = var.database_connection_string == null ? 1 : 0
+  security_group_id = aws_security_group.teamcity_db_sg[0].id
+  description       = "Allow outbound access from DB to all"
+  cidr_ipv4         = "0.0.0.0/0"
+  ip_protocol       = "-1"
+}
+
+# TeamCity ALB security group
+resource "aws_security_group" "teamcity_alb_sg" {
+  #checkov:skip=CKV2_AWS_5:SG is attached to TeamCity service ALB
+  count       = var.create_external_alb ? 1 : 0
+  name        = "${local.name_prefix}-alb-sg"
+  vpc_id      = var.vpc_id
+  description = "TeamCity ALB security group"
+  tags        = local.tags
+}
+
+# Ingress rule for HTTP traffic from ALB to service
+resource "aws_vpc_security_group_ingress_rule" "service_inbound_alb" {
+  count                        = var.create_external_alb ? 1 : 0
+  security_group_id            = aws_security_group.teamcity_service_sg.id
+  referenced_security_group_id = aws_security_group.teamcity_alb_sg[0].id
+  description                  = "Allow inbound HTTP traffic from ALB to service containers"
+  from_port                    = var.container_port
+  to_port                      = var.container_port
+  ip_protocol                  = "TCP"
+}
+
+# Egress rule for HTTP traffic from ALB to service
+resource "aws_vpc_security_group_egress_rule" "service_outbound_alb" {
+  count                        = var.create_external_alb ? 1 : 0
+  security_group_id            = aws_security_group.teamcity_alb_sg[0].id
+  referenced_security_group_id = aws_security_group.teamcity_service_sg.id
+  description                  = "Allow outbound HTTP traffic from ALB to service containers"
+  from_port                    = var.container_port
+  to_port                      = var.container_port
+  ip_protocol                  = "TCP"
+}
+
+# Grant TeamCity service access to internet
+resource "aws_vpc_security_group_egress_rule" "internet_outbound" {
+  security_group_id = aws_security_group.teamcity_service_sg.id
+  description       = "Allow outbound internet access from TeamCity service containers"
+  cidr_ipv4         = "0.0.0.0/0"
+  ip_protocol       = "-1"
+}
+
+#############################################
+# IAM Roles for TeamCity Module
+#############################################
+
+data "aws_iam_policy_document" "ecs_tasks_trust_relationship" {
+  statement {
+    effect  = "Allow"
+    actions = ["sts:AssumeRole"]
+    principals {
+      type        = "Service"
+      identifiers = ["ecs-tasks.amazonaws.com"]
+    }
+  }
+}
+
+data "aws_iam_policy_document" "teamcity_default_policy" {
+  # ECS
+  statement {
+    sid    = "ECSExec"
+    effect = "Allow"
+    actions = [
+      "ssmmessages:OpenDataChannel",
+      "ssmmessages:OpenControlChannel",
+      "ssmmessages:CreateDataChannel",
+      "ssmmessages:CreateControlChannel",
+    ]
+    resources = [
+      "*"
+    ]
+  }
+
+  #grant the task necessary EFS permissions to be able to modify directories
+  statement {
+    sid    = "EFS"
+    effect = "Allow"
+    actions = [
+      "elasticfilesystem:ClientWrite",
+      "elasticfilesystem:ClientRootAccess",
+      "elasticfilesystem:ClientMount"
+    ]
+    resources = [
+      local.efs_file_system_arn
+    ]
+  }
+}
+
+resource "aws_iam_policy" "teamcity_default_policy" {
+  name        = "teamcity-default-policy"
+  description = "Policy granting permissions for TeamCity."
+  policy      = data.aws_iam_policy_document.teamcity_default_policy.json
+}
+
+resource "aws_iam_role" "teamcity_default_role" {
+  name               = "teamcity-default-role"
+  description        = "Default role for TeamCity ECS task."
+  assume_role_policy = data.aws_iam_policy_document.ecs_tasks_trust_relationship.json
+  managed_policy_arns = [
+    aws_iam_policy.teamcity_default_policy.arn
+  ]
+
+  tags = local.tags
+}
+resource "aws_iam_role" "teamcity_task_execution_role" {
+  name = "teamcity-task-execution-role"
+
+  assume_role_policy  = data.aws_iam_policy_document.ecs_tasks_trust_relationship.json
+  managed_policy_arns = ["arn:aws:iam::aws:policy/service-role/AmazonECSTaskExecutionRolePolicy"]
+
+  inline_policy {
+    name = "teamcity-secrets-access"
+    policy = jsonencode({
+      Version = "2012-10-17"
+      Statement = [
+        {
+          Sid    = "SecretsManager"
+          Effect = "Allow"
+          Action = [
+            "secretsmanager:GetSecretValue"
+          ]
+          Resource = [
+            aws_rds_cluster.teamcity_db_cluster[0].master_user_secret[0].secret_arn
+          ]
+        }
+      ]
+    })
+  }
+}
+
+# CloudWatch Logs
+resource "aws_cloudwatch_log_group" "teamcity_log_group" {
+  #checkov:skip=CKV_AWS_158: KMS Encryption disabled by default
+  name              = "${local.name_prefix}-log-group"
+  retention_in_days = var.teamcity_cloudwatch_log_retention_in_days
+  tags              = local.tags
+}
+
+# Load Balancer for TeamCity Service
+resource "aws_lb" "teamcity_external_lb" {
+  count              = var.create_external_alb ? 1 : 0
+  name               = "${local.name_prefix}-lb"
+  security_groups    = [aws_security_group.teamcity_alb_sg[0].id]
+  load_balancer_type = "application"
+  internal           = false
+  subnets            = var.alb_subnets
+
+
+  dynamic "access_logs" {
+    for_each = var.enable_teamcity_alb_access_logs ? [1] : []
+    content {
+      enabled = var.enable_teamcity_alb_access_logs
+      bucket  = var.teamcity_alb_access_logs_bucket != null ? var.teamcity_alb_access_logs_bucket : aws_s3_bucket.teamcity_alb_access_logs_bucket[0].id
+      prefix  = var.teamcity_alb_access_logs_prefix != null ? var.teamcity_alb_access_logs_prefix : "${local.name_prefix}-alb"
+    }
+  }
+  #checkov:skip=CKV_AWS_150:Deletion protection disabled by default
+  enable_deletion_protection = var.enable_teamcity_alb_deletion_protection
+
+  #checkov:skip=CKV2_AWS_28: ALB access is managed with SG allow listing
+
+  drop_invalid_header_fields = true
+  tags                       = local.tags
+}
+
+# TeamCity target group for ALB
+resource "aws_lb_target_group" "teamcity_target_group" {
+  count       = var.create_external_alb ? 1 : 0
+  name        = "${local.name_prefix}-tg"
+  port        = var.container_port
+  protocol    = "HTTP"
+  vpc_id      = var.vpc_id
+  target_type = "ip"
+
+  health_check {
+    path                = "/healthCheck/healthy"
+    interval            = 30
+    timeout             = 5
+    healthy_threshold   = 5
+    unhealthy_threshold = 2
+    port                = var.container_port
+    protocol            = "HTTP"
+    matcher             = "200"
+  }
+
+  tags = local.tags
+}
+
+# ALB HTTPS Listener
+resource "aws_lb_listener" "teamcity_listener" {
+  count             = var.create_external_alb ? 1 : 0
+  load_balancer_arn = aws_lb.teamcity_external_lb[0].arn
+  port              = "443"
+  protocol          = "HTTPS"
+  ssl_policy        = "ELBSecurityPolicy-TLS13-1-2-2021-06"
+  certificate_arn   = var.alb_certificate_arn
+  default_action {
+    type             = "forward"
+    target_group_arn = aws_lb_target_group.teamcity_target_group[0].arn
+  }
+  tags = local.tags
+}
+
+# #######################################
+# # TeamCity Aurora Serverless Database #
+# #######################################
+# Subnet group
+resource "aws_db_subnet_group" "teamcity_db_subnet_group" {
+  count      = var.database_connection_string == null ? 1 : 0
+  name       = "${local.name_prefix}-db-subnet-group"
+  subnet_ids = var.service_subnets
+  tags       = local.tags
+}
+
+# # RDS instance with Aurora serverless engine
+resource "aws_rds_cluster" "teamcity_db_cluster" {
+  #checkov:skip=CKV2_AWS_27:not enabling query logging by design
+  #checkov:skip=CKV2_AWS_8: TODO: add rds backup plan
+  count                       = var.database_connection_string == null ? 1 : 0
+  cluster_identifier          = "teamcity-cluster"
+  engine                      = "aurora-postgresql"
+  engine_mode                 = "provisioned"
+  engine_version              = "16.6" #check for latest as option
+  database_name               = "teamcity"
+  master_username             = "teamcity"
+  manage_master_user_password = true #using AWS Secrets Manager
+  storage_encrypted           = true
+  skip_final_snapshot         = var.aurora_skip_final_snapshot
+  db_subnet_group_name        = aws_db_subnet_group.teamcity_db_subnet_group[0].id
+  vpc_security_group_ids = [
+    aws_security_group.teamcity_db_sg[0].id
+  ]
+
+  serverlessv2_scaling_configuration {
+    max_capacity             = 1.0
+    min_capacity             = 0.0
+    seconds_until_auto_pause = 3600
+  }
+}
+
+resource "aws_rds_cluster_instance" "teamcity_db_cluster_instance" {
+  count              = var.database_connection_string == null ? var.aurora_instance_count : 0
+  cluster_identifier = aws_rds_cluster.teamcity_db_cluster[0].id
+  instance_class     = "db.serverless"
+  engine             = aws_rds_cluster.teamcity_db_cluster[0].engine
+  engine_version     = aws_rds_cluster.teamcity_db_cluster[0].engine_version
+}
+
+# ################
+# # TeamCity EFS #
+# ################
+
+# File system for teamcity
+resource "aws_efs_file_system" "teamcity_efs_file_system" {
+  count            = var.efs_id != null ? 0 : 1
+  creation_token   = "${local.name_prefix}-efs-file-system"
+  performance_mode = var.teamcity_efs_performance_mode
+  throughput_mode  = var.teamcity_efs_throughput_mode
+
+  #TODO: Parameterize encryption and customer managed key creation
+  encrypted = false
+
+  lifecycle_policy {
+    transition_to_ia = "AFTER_30_DAYS"
+  }
+
+  lifecycle_policy {
+    transition_to_primary_storage_class = "AFTER_1_ACCESS"
+  }
+  #checkov:skip=CKV_AWS_184: CMK encryption not supported currently
+  tags = merge(local.tags, {
+    Name = "${local.name_prefix}-efs-file-system"
+  })
+}
path: modules/teamcity/main.tf
user: login: github-advanced-security[bot]
body: ## Ensure resource is encrypted by KMS using a customer managed Key (CMK)

Ensure resource is encrypted by KMS using a customer managed Key (CMK)

[Show more details](https://github.com/aws-games/cloud-game-development-toolkit/security/code-scanning/846)
created_at: 2025-03-07T19:44:05Z
author_association: NONE
diff_hunk: @@ -0,0 +1,595 @@
+###################################
+# ECS Cluster for TeamCity Server #
+###################################
+# If cluster name is provided use a data source to access existing resource
+data "aws_ecs_cluster" "teamcity_cluster" {
+  count        = var.cluster_name != null ? 1 : 0
+  cluster_name = var.cluster_name
+}
+resource "aws_ecs_cluster" "teamcity_cluster" {
+  count = var.cluster_name != null ? 0 : 1
+  name  = "${local.name_prefix}-cluster"
+
+  setting {
+    name  = "containerInsights"
+    value = "enabled"
+  }
+
+  tags = local.tags
+}
+
+# TeamCity Task Definition
+resource "aws_ecs_task_definition" "teamcity_task_definition" {
+  family                   = var.name
+  requires_compatibilities = ["FARGATE"]
+  network_mode             = "awsvpc"
+  cpu                      = var.container_cpu
+  memory                   = var.container_memory
+
+  container_definitions = jsonencode([
+    {
+      name      = var.container_name
+      image     = local.image
+      cpu       = var.container_cpu
+      memory    = var.container_memory
+      essential = true
+
+      portMappings = [
+        {
+          containerPort = var.container_port
+          hostPort      = var.container_port
+        }
+      ]
+      logConfiguration = {
+        logDriver = "awslogs"
+        options = {
+          awslogs-group         = aws_cloudwatch_log_group.teamcity_log_group.name
+          awslogs-region        = data.aws_region.current.name
+          awslogs-stream-prefix = "[APP]"
+        }
+      }
+
+      mountPoints = [
+        {
+          sourceVolume  = "teamcity_data",
+          containerPath = "/data/teamcity_server/datadir"
+          readOnly      = false
+        }
+      ]
+
+      # Combine the lists
+      environment = concat(local.base_env, local.password_env)
+
+      secrets = var.database_connection_string == null ? [
+        {
+          name      = "TEAMCITY_DB_PASSWORD"
+          valueFrom = "${aws_rds_cluster.teamcity_db_cluster[0].master_user_secret[0].secret_arn}:password::"
+        }
+      ] : []
+    }
+  ])
+  tags = {
+    Name = var.name
+  }
+  task_role_arn      = aws_iam_role.teamcity_default_role.arn
+  execution_role_arn = aws_iam_role.teamcity_task_execution_role.arn
+
+  volume {
+    name = "teamcity_data"
+    efs_volume_configuration {
+      file_system_id          = local.efs_file_system_id
+      transit_encryption      = "ENABLED"
+      transit_encryption_port = 2999
+      authorization_config {
+        access_point_id = local.efs_access_point_id
+        iam             = "ENABLED"
+      }
+    }
+  }
+}
+
+# ECS Service
+resource "aws_ecs_service" "teamcity" {
+  name                   = local.name_prefix
+  cluster                = var.cluster_name != null ? data.aws_ecs_cluster.teamcity_cluster[0].arn : aws_ecs_cluster.teamcity_cluster[0].arn
+  task_definition        = aws_ecs_task_definition.teamcity_task_definition.arn
+  launch_type            = "FARGATE"
+  desired_count          = var.desired_container_count
+  force_new_deployment   = var.debug
+  enable_execute_command = var.debug
+
+  wait_for_steady_state = false #TODO: make this configurable
+
+  network_configuration {
+    subnets         = var.service_subnets
+    security_groups = [aws_security_group.teamcity_service_sg.id]
+  }
+  dynamic "load_balancer" {
+    for_each = var.create_external_alb ? [1] : []
+    content {
+      target_group_arn = aws_lb_target_group.teamcity_target_group[0].arn
+      container_name   = var.container_name
+      container_port   = var.container_port
+
+    }
+  }
+
+  tags = local.tags
+}
+
+###################################
+# Security Groups                 #
+###################################
+
+# TeamCity service security group
+resource "aws_security_group" "teamcity_service_sg" {
+  name        = "${local.name_prefix}-service-sg"
+  vpc_id      = var.vpc_id
+  description = "TeamCity service security group"
+  tags        = local.tags
+}
+
+# TeamCity EFS security group
+resource "aws_security_group" "teamcity_efs_sg" {
+  count       = var.efs_id == null ? 1 : 0
+  name        = "${local.name_prefix}-efs-sg"
+  description = "TeamCity EFS mount target security group"
+  vpc_id      = var.vpc_id
+  tags        = local.tags
+}
+
+# Ingress rule for NFS traffic from service to EFS
+resource "aws_vpc_security_group_ingress_rule" "service_efs" {
+  count                        = var.efs_id == null ? 1 : 0
+  security_group_id            = aws_security_group.teamcity_efs_sg[0].id
+  referenced_security_group_id = aws_security_group.teamcity_service_sg.id
+  description                  = "Allow inbound access from TeamCity service containers to EFS"
+  ip_protocol                  = "TCP"
+  from_port                    = 2049
+  to_port                      = 2049
+}
+
+# Ingress rule for NFS traffic from EFS to service
+resource "aws_vpc_security_group_ingress_rule" "efs_service" {
+  count                        = var.efs_id == null ? 1 : 0
+  security_group_id            = aws_security_group.teamcity_service_sg.id
+  referenced_security_group_id = aws_security_group.teamcity_efs_sg[0].id
+  description                  = "Allow inbound access from EFS to TeamCity service containers"
+  ip_protocol                  = "TCP"
+  from_port                    = 2049
+  to_port                      = 2049
+}
+
+# TeamCity Aurora Serverless PostgreSQL security group
+resource "aws_security_group" "teamcity_db_sg" {
+  count       = var.database_connection_string == null ? 1 : 0
+  name        = "${local.name_prefix}-db-sg"
+  description = "TeamCity DB security group"
+  vpc_id      = var.vpc_id
+  tags        = local.tags
+}
+
+# Ingress rule for PostgreSQL from service to database cluster
+resource "aws_vpc_security_group_ingress_rule" "service_db" {
+  count                        = var.database_connection_string == null ? 1 : 0
+  security_group_id            = aws_security_group.teamcity_db_sg[0].id
+  referenced_security_group_id = aws_security_group.teamcity_service_sg.id
+  description                  = "Allow inbound access from TeamCity service containers to DB"
+  ip_protocol                  = "TCP"
+  from_port                    = 5432
+  to_port                      = 5432
+}
+
+# Ingress rule for PostgreSQL from database cluster to service
+resource "aws_vpc_security_group_ingress_rule" "db_service" {
+  count                        = var.database_connection_string == null ? 1 : 0
+  security_group_id            = aws_security_group.teamcity_service_sg.id
+  referenced_security_group_id = aws_security_group.teamcity_db_sg[0].id
+  description                  = "Allow inbound access from DB to TeamCity service containers"
+  ip_protocol                  = "TCP"
+  from_port                    = 5432
+  to_port                      = 5432
+}
+
+# Egress rule for database to let all traffic out
+resource "aws_vpc_security_group_egress_rule" "db_outbound" {
+  count             = var.database_connection_string == null ? 1 : 0
+  security_group_id = aws_security_group.teamcity_db_sg[0].id
+  description       = "Allow outbound access from DB to all"
+  cidr_ipv4         = "0.0.0.0/0"
+  ip_protocol       = "-1"
+}
+
+# TeamCity ALB security group
+resource "aws_security_group" "teamcity_alb_sg" {
+  #checkov:skip=CKV2_AWS_5:SG is attached to TeamCity service ALB
+  count       = var.create_external_alb ? 1 : 0
+  name        = "${local.name_prefix}-alb-sg"
+  vpc_id      = var.vpc_id
+  description = "TeamCity ALB security group"
+  tags        = local.tags
+}
+
+# Ingress rule for HTTP traffic from ALB to service
+resource "aws_vpc_security_group_ingress_rule" "service_inbound_alb" {
+  count                        = var.create_external_alb ? 1 : 0
+  security_group_id            = aws_security_group.teamcity_service_sg.id
+  referenced_security_group_id = aws_security_group.teamcity_alb_sg[0].id
+  description                  = "Allow inbound HTTP traffic from ALB to service containers"
+  from_port                    = var.container_port
+  to_port                      = var.container_port
+  ip_protocol                  = "TCP"
+}
+
+# Egress rule for HTTP traffic from ALB to service
+resource "aws_vpc_security_group_egress_rule" "service_outbound_alb" {
+  count                        = var.create_external_alb ? 1 : 0
+  security_group_id            = aws_security_group.teamcity_alb_sg[0].id
+  referenced_security_group_id = aws_security_group.teamcity_service_sg.id
+  description                  = "Allow outbound HTTP traffic from ALB to service containers"
+  from_port                    = var.container_port
+  to_port                      = var.container_port
+  ip_protocol                  = "TCP"
+}
+
+# Grant TeamCity service access to internet
+resource "aws_vpc_security_group_egress_rule" "internet_outbound" {
+  security_group_id = aws_security_group.teamcity_service_sg.id
+  description       = "Allow outbound internet access from TeamCity service containers"
+  cidr_ipv4         = "0.0.0.0/0"
+  ip_protocol       = "-1"
+}
+
+#############################################
+# IAM Roles for TeamCity Module
+#############################################
+
+data "aws_iam_policy_document" "ecs_tasks_trust_relationship" {
+  statement {
+    effect  = "Allow"
+    actions = ["sts:AssumeRole"]
+    principals {
+      type        = "Service"
+      identifiers = ["ecs-tasks.amazonaws.com"]
+    }
+  }
+}
+
+data "aws_iam_policy_document" "teamcity_default_policy" {
+  # ECS
+  statement {
+    sid    = "ECSExec"
+    effect = "Allow"
+    actions = [
+      "ssmmessages:OpenDataChannel",
+      "ssmmessages:OpenControlChannel",
+      "ssmmessages:CreateDataChannel",
+      "ssmmessages:CreateControlChannel",
+    ]
+    resources = [
+      "*"
+    ]
+  }
+
+  #grant the task necessary EFS permissions to be able to modify directories
+  statement {
+    sid    = "EFS"
+    effect = "Allow"
+    actions = [
+      "elasticfilesystem:ClientWrite",
+      "elasticfilesystem:ClientRootAccess",
+      "elasticfilesystem:ClientMount"
+    ]
+    resources = [
+      local.efs_file_system_arn
+    ]
+  }
+}
+
+resource "aws_iam_policy" "teamcity_default_policy" {
+  name        = "teamcity-default-policy"
+  description = "Policy granting permissions for TeamCity."
+  policy      = data.aws_iam_policy_document.teamcity_default_policy.json
+}
+
+resource "aws_iam_role" "teamcity_default_role" {
+  name               = "teamcity-default-role"
+  description        = "Default role for TeamCity ECS task."
+  assume_role_policy = data.aws_iam_policy_document.ecs_tasks_trust_relationship.json
+  managed_policy_arns = [
+    aws_iam_policy.teamcity_default_policy.arn
+  ]
+
+  tags = local.tags
+}
+resource "aws_iam_role" "teamcity_task_execution_role" {
+  name = "teamcity-task-execution-role"
+
+  assume_role_policy  = data.aws_iam_policy_document.ecs_tasks_trust_relationship.json
+  managed_policy_arns = ["arn:aws:iam::aws:policy/service-role/AmazonECSTaskExecutionRolePolicy"]
+
+  dynamic "inline_policy" {
+    for_each = var.database_connection_string == null ? [1] : []
+    content {
+      name = "teamcity-secrets-access"
+      policy = jsonencode({
+        Version = "2012-10-17"
+        Statement = [
+          {
+            Sid    = "SecretsManager"
+            Effect = "Allow"
+            Action = [
+              "secretsmanager:GetSecretValue"
+            ]
+            Resource = [
+              aws_rds_cluster.teamcity_db_cluster[0].master_user_secret[0].secret_arn
+            ]
+          }
+        ]
+      })
+    }
+  }
+
+}
+
+# CloudWatch Logs
+resource "aws_cloudwatch_log_group" "teamcity_log_group" {
+  #checkov:skip=CKV_AWS_158: KMS Encryption disabled by default
+  name              = "${local.name_prefix}-log-group"
+  retention_in_days = var.teamcity_cloudwatch_log_retention_in_days
+  tags              = local.tags
+}
+
+# Load Balancer for TeamCity Service
+resource "aws_lb" "teamcity_external_lb" {
+  count              = var.create_external_alb ? 1 : 0
+  name               = "${local.name_prefix}-lb"
+  security_groups    = [aws_security_group.teamcity_alb_sg[0].id]
+  load_balancer_type = "application"
+  internal           = false
+  subnets            = var.alb_subnets
+
+
+  dynamic "access_logs" {
+    for_each = var.enable_teamcity_alb_access_logs ? [1] : []
+    content {
+      enabled = var.enable_teamcity_alb_access_logs
+      bucket  = var.teamcity_alb_access_logs_bucket != null ? var.teamcity_alb_access_logs_bucket : aws_s3_bucket.teamcity_alb_access_logs_bucket[0].id
+      prefix  = var.teamcity_alb_access_logs_prefix != null ? var.teamcity_alb_access_logs_prefix : "${local.name_prefix}-alb"
+    }
+  }
+  #checkov:skip=CKV_AWS_150:Deletion protection disabled by default
+  enable_deletion_protection = var.enable_teamcity_alb_deletion_protection
+
+  #checkov:skip=CKV2_AWS_28: ALB access is managed with SG allow listing
+
+  drop_invalid_header_fields = true
+  tags                       = local.tags
+}
+
+# TeamCity target group for ALB
+resource "aws_lb_target_group" "teamcity_target_group" {
+  #checkov:skip=CKV_AWS_378: Using ALB for TLS termination
+  count       = var.create_external_alb ? 1 : 0
+  name        = "${local.name_prefix}-tg"
+  port        = var.container_port
+  protocol    = "HTTP"
+  vpc_id      = var.vpc_id
+  target_type = "ip"
+
+  health_check {
+    path                = "/healthCheck/healthy"
+    interval            = 30
+    timeout             = 5
+    healthy_threshold   = 5
+    unhealthy_threshold = 2
+    port                = var.container_port
+    protocol            = "HTTP"
+    matcher             = "200"
+  }
+
+  tags = local.tags
+}
path: modules/teamcity/main.tf
user: login: github-advanced-security[bot]
body: ## Ensure AWS Load Balancer doesn't use HTTP protocol

Ensure AWS Load Balancer doesn't use HTTP protocol

[Show more details](https://github.com/aws-games/cloud-game-development-toolkit/security/code-scanning/847)
created_at: 2025-03-11T16:22:26Z
author_association: NONE
diff_hunk: @@ -0,0 +1,8 @@
+module "teamcity" {
+  source              = "../../"
+  vpc_id              = aws_vpc.teamcity_vpc.id
+  service_subnets     = aws_subnet.private_subnets[*].id
+  alb_subnets         = aws_subnet.public_subnets[*].id
+  alb_certificate_arn = aws_acm_certificate.teamcity.arn
+  debug               = true
path: modules/teamcity/examples/simple/main.tf
body: Do you want debug enabled here?
created_at: 2025-03-11T16:29:55Z
author_association: NONE
diff_hunk: @@ -0,0 +1,8 @@
+module "teamcity" {
+  source              = "../../"
+  vpc_id              = aws_vpc.teamcity_vpc.id
+  service_subnets     = aws_subnet.private_subnets[*].id
+  alb_subnets         = aws_subnet.public_subnets[*].id
+  alb_certificate_arn = aws_acm_certificate.teamcity.arn
+  debug               = true
path: modules/teamcity/examples/simple/main.tf
body: for the "example" you may want to leave it in but comment it out. or disable it and leave a comment explaining that it can be enabled for access to the container.
created_at: 2025-03-11T16:30:32Z
author_association: NONE
diff_hunk: @@ -0,0 +1,595 @@
+###################################
+# ECS Cluster for TeamCity Server #
+###################################
+# If cluster name is provided use a data source to access existing resource
+data "aws_ecs_cluster" "teamcity_cluster" {
+  count        = var.cluster_name != null ? 1 : 0
+  cluster_name = var.cluster_name
+}
+resource "aws_ecs_cluster" "teamcity_cluster" {
+  count = var.cluster_name != null ? 0 : 1
+  name  = "${local.name_prefix}-cluster"
+
+  setting {
+    name  = "containerInsights"
+    value = "enabled"
+  }
+
+  tags = local.tags
+}
+
+# TeamCity Task Definition
+resource "aws_ecs_task_definition" "teamcity_task_definition" {
+  family                   = var.name
+  requires_compatibilities = ["FARGATE"]
+  network_mode             = "awsvpc"
+  cpu                      = var.container_cpu
+  memory                   = var.container_memory
+
+  container_definitions = jsonencode([
+    {
+      name      = var.container_name
+      image     = local.image
+      cpu       = var.container_cpu
+      memory    = var.container_memory
+      essential = true
+
+      portMappings = [
+        {
+          containerPort = var.container_port
+          hostPort      = var.container_port
+        }
+      ]
+      logConfiguration = {
+        logDriver = "awslogs"
+        options = {
+          awslogs-group         = aws_cloudwatch_log_group.teamcity_log_group.name
+          awslogs-region        = data.aws_region.current.name
+          awslogs-stream-prefix = "[APP]"
+        }
+      }
+
+      mountPoints = [
+        {
+          sourceVolume  = "teamcity_data",
+          containerPath = "/data/teamcity_server/datadir"
+          readOnly      = false
+        }
+      ]
+
+      # Combine the lists
+      environment = concat(local.base_env, local.password_env)
+
+      secrets = var.database_connection_string == null ? [
+        {
+          name      = "TEAMCITY_DB_PASSWORD"
+          valueFrom = "${aws_rds_cluster.teamcity_db_cluster[0].master_user_secret[0].secret_arn}:password::"
+        }
+      ] : []
+    }
+  ])
+  tags = {
+    Name = var.name
+  }
+  task_role_arn      = aws_iam_role.teamcity_default_role.arn
+  execution_role_arn = aws_iam_role.teamcity_task_execution_role.arn
+
+  volume {
+    name = "teamcity_data"
+    efs_volume_configuration {
+      file_system_id          = local.efs_file_system_id
+      transit_encryption      = "ENABLED"
+      transit_encryption_port = 2999
+      authorization_config {
+        access_point_id = local.efs_access_point_id
+        iam             = "ENABLED"
+      }
+    }
+  }
+}
+
+# ECS Service
+resource "aws_ecs_service" "teamcity" {
+  name                   = local.name_prefix
+  cluster                = var.cluster_name != null ? data.aws_ecs_cluster.teamcity_cluster[0].arn : aws_ecs_cluster.teamcity_cluster[0].arn
+  task_definition        = aws_ecs_task_definition.teamcity_task_definition.arn
+  launch_type            = "FARGATE"
+  desired_count          = var.desired_container_count
+  force_new_deployment   = var.debug
+  enable_execute_command = var.debug
+
+  wait_for_steady_state = false #TODO: make this configurable
+
+  network_configuration {
+    subnets         = var.service_subnets
+    security_groups = [aws_security_group.teamcity_service_sg.id]
+  }
+  dynamic "load_balancer" {
+    for_each = var.create_external_alb ? [1] : []
+    content {
+      target_group_arn = aws_lb_target_group.teamcity_target_group[0].arn
+      container_name   = var.container_name
+      container_port   = var.container_port
+
+    }
+  }
+
+  tags = local.tags
+}
+
+###################################
+# Security Groups                 #
+###################################
+
+# TeamCity service security group
+resource "aws_security_group" "teamcity_service_sg" {
+  name        = "${local.name_prefix}-service-sg"
+  vpc_id      = var.vpc_id
+  description = "TeamCity service security group"
+  tags        = local.tags
+}
+
+# TeamCity EFS security group
+resource "aws_security_group" "teamcity_efs_sg" {
+  count       = var.efs_id == null ? 1 : 0
+  name        = "${local.name_prefix}-efs-sg"
+  description = "TeamCity EFS mount target security group"
+  vpc_id      = var.vpc_id
+  tags        = local.tags
+}
+
+# Ingress rule for NFS traffic from service to EFS
+resource "aws_vpc_security_group_ingress_rule" "service_efs" {
+  count                        = var.efs_id == null ? 1 : 0
+  security_group_id            = aws_security_group.teamcity_efs_sg[0].id
+  referenced_security_group_id = aws_security_group.teamcity_service_sg.id
+  description                  = "Allow inbound access from TeamCity service containers to EFS"
+  ip_protocol                  = "TCP"
+  from_port                    = 2049
+  to_port                      = 2049
+}
+
+# Ingress rule for NFS traffic from EFS to service
+resource "aws_vpc_security_group_ingress_rule" "efs_service" {
+  count                        = var.efs_id == null ? 1 : 0
+  security_group_id            = aws_security_group.teamcity_service_sg.id
+  referenced_security_group_id = aws_security_group.teamcity_efs_sg[0].id
+  description                  = "Allow inbound access from EFS to TeamCity service containers"
+  ip_protocol                  = "TCP"
+  from_port                    = 2049
+  to_port                      = 2049
+}
path: modules/teamcity/main.tf
body: An ingress rule... Why? Shouldn't this be an egress rule? `aws_vpc_security_group_egress_rule` since this is supposed to allow service -> efs?
created_at: 2025-03-11T16:34:41Z
author_association: NONE
diff_hunk: @@ -0,0 +1,595 @@
+###################################
+# ECS Cluster for TeamCity Server #
+###################################
+# If cluster name is provided use a data source to access existing resource
+data "aws_ecs_cluster" "teamcity_cluster" {
+  count        = var.cluster_name != null ? 1 : 0
+  cluster_name = var.cluster_name
+}
+resource "aws_ecs_cluster" "teamcity_cluster" {
+  count = var.cluster_name != null ? 0 : 1
+  name  = "${local.name_prefix}-cluster"
+
+  setting {
+    name  = "containerInsights"
+    value = "enabled"
+  }
+
+  tags = local.tags
+}
+
+# TeamCity Task Definition
+resource "aws_ecs_task_definition" "teamcity_task_definition" {
+  family                   = var.name
+  requires_compatibilities = ["FARGATE"]
+  network_mode             = "awsvpc"
+  cpu                      = var.container_cpu
+  memory                   = var.container_memory
+
+  container_definitions = jsonencode([
+    {
+      name      = var.container_name
+      image     = local.image
+      cpu       = var.container_cpu
+      memory    = var.container_memory
+      essential = true
+
+      portMappings = [
+        {
+          containerPort = var.container_port
+          hostPort      = var.container_port
+        }
+      ]
+      logConfiguration = {
+        logDriver = "awslogs"
+        options = {
+          awslogs-group         = aws_cloudwatch_log_group.teamcity_log_group.name
+          awslogs-region        = data.aws_region.current.name
+          awslogs-stream-prefix = "[APP]"
+        }
+      }
+
+      mountPoints = [
+        {
+          sourceVolume  = "teamcity_data",
+          containerPath = "/data/teamcity_server/datadir"
+          readOnly      = false
+        }
+      ]
+
+      # Combine the lists
+      environment = concat(local.base_env, local.password_env)
+
+      secrets = var.database_connection_string == null ? [
+        {
+          name      = "TEAMCITY_DB_PASSWORD"
+          valueFrom = "${aws_rds_cluster.teamcity_db_cluster[0].master_user_secret[0].secret_arn}:password::"
+        }
+      ] : []
+    }
+  ])
+  tags = {
+    Name = var.name
+  }
+  task_role_arn      = aws_iam_role.teamcity_default_role.arn
+  execution_role_arn = aws_iam_role.teamcity_task_execution_role.arn
+
+  volume {
+    name = "teamcity_data"
+    efs_volume_configuration {
+      file_system_id          = local.efs_file_system_id
+      transit_encryption      = "ENABLED"
+      transit_encryption_port = 2999
+      authorization_config {
+        access_point_id = local.efs_access_point_id
+        iam             = "ENABLED"
+      }
+    }
+  }
+}
+
+# ECS Service
+resource "aws_ecs_service" "teamcity" {
+  name                   = local.name_prefix
+  cluster                = var.cluster_name != null ? data.aws_ecs_cluster.teamcity_cluster[0].arn : aws_ecs_cluster.teamcity_cluster[0].arn
+  task_definition        = aws_ecs_task_definition.teamcity_task_definition.arn
+  launch_type            = "FARGATE"
+  desired_count          = var.desired_container_count
+  force_new_deployment   = var.debug
+  enable_execute_command = var.debug
+
+  wait_for_steady_state = false #TODO: make this configurable
+
+  network_configuration {
+    subnets         = var.service_subnets
+    security_groups = [aws_security_group.teamcity_service_sg.id]
+  }
+  dynamic "load_balancer" {
+    for_each = var.create_external_alb ? [1] : []
+    content {
+      target_group_arn = aws_lb_target_group.teamcity_target_group[0].arn
+      container_name   = var.container_name
+      container_port   = var.container_port
+
+    }
+  }
+
+  tags = local.tags
+}
+
+###################################
+# Security Groups                 #
+###################################
+
+# TeamCity service security group
+resource "aws_security_group" "teamcity_service_sg" {
+  name        = "${local.name_prefix}-service-sg"
+  vpc_id      = var.vpc_id
+  description = "TeamCity service security group"
+  tags        = local.tags
+}
+
+# TeamCity EFS security group
+resource "aws_security_group" "teamcity_efs_sg" {
+  count       = var.efs_id == null ? 1 : 0
+  name        = "${local.name_prefix}-efs-sg"
+  description = "TeamCity EFS mount target security group"
+  vpc_id      = var.vpc_id
+  tags        = local.tags
+}
+
+# Ingress rule for NFS traffic from service to EFS
+resource "aws_vpc_security_group_ingress_rule" "service_efs" {
+  count                        = var.efs_id == null ? 1 : 0
+  security_group_id            = aws_security_group.teamcity_efs_sg[0].id
+  referenced_security_group_id = aws_security_group.teamcity_service_sg.id
+  description                  = "Allow inbound access from TeamCity service containers to EFS"
+  ip_protocol                  = "TCP"
+  from_port                    = 2049
+  to_port                      = 2049
+}
+
+# Ingress rule for NFS traffic from EFS to service
+resource "aws_vpc_security_group_ingress_rule" "efs_service" {
+  count                        = var.efs_id == null ? 1 : 0
+  security_group_id            = aws_security_group.teamcity_service_sg.id
+  referenced_security_group_id = aws_security_group.teamcity_efs_sg[0].id
+  description                  = "Allow inbound access from EFS to TeamCity service containers"
+  ip_protocol                  = "TCP"
+  from_port                    = 2049
+  to_port                      = 2049
+}
+
+# TeamCity Aurora Serverless PostgreSQL security group
+resource "aws_security_group" "teamcity_db_sg" {
+  count       = var.database_connection_string == null ? 1 : 0
+  name        = "${local.name_prefix}-db-sg"
+  description = "TeamCity DB security group"
+  vpc_id      = var.vpc_id
+  tags        = local.tags
+}
+
+# Ingress rule for PostgreSQL from service to database cluster
+resource "aws_vpc_security_group_ingress_rule" "service_db" {
+  count                        = var.database_connection_string == null ? 1 : 0
+  security_group_id            = aws_security_group.teamcity_db_sg[0].id
+  referenced_security_group_id = aws_security_group.teamcity_service_sg.id
+  description                  = "Allow inbound access from TeamCity service containers to DB"
+  ip_protocol                  = "TCP"
+  from_port                    = 5432
+  to_port                      = 5432
+}
+
+# Ingress rule for PostgreSQL from database cluster to service
+resource "aws_vpc_security_group_ingress_rule" "db_service" {
+  count                        = var.database_connection_string == null ? 1 : 0
+  security_group_id            = aws_security_group.teamcity_service_sg.id
+  referenced_security_group_id = aws_security_group.teamcity_db_sg[0].id
+  description                  = "Allow inbound access from DB to TeamCity service containers"
+  ip_protocol                  = "TCP"
+  from_port                    = 5432
+  to_port                      = 5432
+}
+
+# Egress rule for database to let all traffic out
+resource "aws_vpc_security_group_egress_rule" "db_outbound" {
+  count             = var.database_connection_string == null ? 1 : 0
+  security_group_id = aws_security_group.teamcity_db_sg[0].id
+  description       = "Allow outbound access from DB to all"
+  cidr_ipv4         = "0.0.0.0/0"
+  ip_protocol       = "-1"
+}
path: modules/teamcity/main.tf
body: See above - this is what I am referring to. The EFS one should mirror this.
created_at: 2025-03-11T16:36:08Z
author_association: NONE
diff_hunk: @@ -0,0 +1,595 @@
+###################################
+# ECS Cluster for TeamCity Server #
+###################################
+# If cluster name is provided use a data source to access existing resource
+data "aws_ecs_cluster" "teamcity_cluster" {
+  count        = var.cluster_name != null ? 1 : 0
+  cluster_name = var.cluster_name
+}
+resource "aws_ecs_cluster" "teamcity_cluster" {
+  count = var.cluster_name != null ? 0 : 1
+  name  = "${local.name_prefix}-cluster"
+
+  setting {
+    name  = "containerInsights"
+    value = "enabled"
+  }
+
+  tags = local.tags
+}
+
+# TeamCity Task Definition
+resource "aws_ecs_task_definition" "teamcity_task_definition" {
+  family                   = var.name
+  requires_compatibilities = ["FARGATE"]
+  network_mode             = "awsvpc"
+  cpu                      = var.container_cpu
+  memory                   = var.container_memory
+
+  container_definitions = jsonencode([
+    {
+      name      = var.container_name
+      image     = local.image
+      cpu       = var.container_cpu
+      memory    = var.container_memory
+      essential = true
+
+      portMappings = [
+        {
+          containerPort = var.container_port
+          hostPort      = var.container_port
+        }
+      ]
+      logConfiguration = {
+        logDriver = "awslogs"
+        options = {
+          awslogs-group         = aws_cloudwatch_log_group.teamcity_log_group.name
+          awslogs-region        = data.aws_region.current.name
+          awslogs-stream-prefix = "[APP]"
+        }
+      }
+
+      mountPoints = [
+        {
+          sourceVolume  = "teamcity_data",
+          containerPath = "/data/teamcity_server/datadir"
+          readOnly      = false
+        }
+      ]
+
+      # Combine the lists
+      environment = concat(local.base_env, local.password_env)
+
+      secrets = var.database_connection_string == null ? [
+        {
+          name      = "TEAMCITY_DB_PASSWORD"
+          valueFrom = "${aws_rds_cluster.teamcity_db_cluster[0].master_user_secret[0].secret_arn}:password::"
+        }
+      ] : []
+    }
+  ])
+  tags = {
+    Name = var.name
+  }
+  task_role_arn      = aws_iam_role.teamcity_default_role.arn
+  execution_role_arn = aws_iam_role.teamcity_task_execution_role.arn
+
+  volume {
+    name = "teamcity_data"
+    efs_volume_configuration {
+      file_system_id          = local.efs_file_system_id
+      transit_encryption      = "ENABLED"
+      transit_encryption_port = 2999
+      authorization_config {
+        access_point_id = local.efs_access_point_id
+        iam             = "ENABLED"
+      }
+    }
+  }
+}
+
+# ECS Service
+resource "aws_ecs_service" "teamcity" {
+  name                   = local.name_prefix
+  cluster                = var.cluster_name != null ? data.aws_ecs_cluster.teamcity_cluster[0].arn : aws_ecs_cluster.teamcity_cluster[0].arn
+  task_definition        = aws_ecs_task_definition.teamcity_task_definition.arn
+  launch_type            = "FARGATE"
+  desired_count          = var.desired_container_count
+  force_new_deployment   = var.debug
+  enable_execute_command = var.debug
+
+  wait_for_steady_state = false #TODO: make this configurable
+
+  network_configuration {
+    subnets         = var.service_subnets
+    security_groups = [aws_security_group.teamcity_service_sg.id]
+  }
+  dynamic "load_balancer" {
+    for_each = var.create_external_alb ? [1] : []
+    content {
+      target_group_arn = aws_lb_target_group.teamcity_target_group[0].arn
+      container_name   = var.container_name
+      container_port   = var.container_port
+
+    }
+  }
+
+  tags = local.tags
+}
+
+###################################
+# Security Groups                 #
+###################################
+
+# TeamCity service security group
+resource "aws_security_group" "teamcity_service_sg" {
+  name        = "${local.name_prefix}-service-sg"
+  vpc_id      = var.vpc_id
+  description = "TeamCity service security group"
+  tags        = local.tags
+}
+
+# TeamCity EFS security group
+resource "aws_security_group" "teamcity_efs_sg" {
+  count       = var.efs_id == null ? 1 : 0
+  name        = "${local.name_prefix}-efs-sg"
+  description = "TeamCity EFS mount target security group"
+  vpc_id      = var.vpc_id
+  tags        = local.tags
+}
+
+# Ingress rule for NFS traffic from service to EFS
+resource "aws_vpc_security_group_ingress_rule" "service_efs" {
+  count                        = var.efs_id == null ? 1 : 0
+  security_group_id            = aws_security_group.teamcity_efs_sg[0].id
+  referenced_security_group_id = aws_security_group.teamcity_service_sg.id
+  description                  = "Allow inbound access from TeamCity service containers to EFS"
+  ip_protocol                  = "TCP"
+  from_port                    = 2049
+  to_port                      = 2049
+}
+
+# Ingress rule for NFS traffic from EFS to service
+resource "aws_vpc_security_group_ingress_rule" "efs_service" {
+  count                        = var.efs_id == null ? 1 : 0
+  security_group_id            = aws_security_group.teamcity_service_sg.id
+  referenced_security_group_id = aws_security_group.teamcity_efs_sg[0].id
+  description                  = "Allow inbound access from EFS to TeamCity service containers"
+  ip_protocol                  = "TCP"
+  from_port                    = 2049
+  to_port                      = 2049
+}
+
+# TeamCity Aurora Serverless PostgreSQL security group
+resource "aws_security_group" "teamcity_db_sg" {
+  count       = var.database_connection_string == null ? 1 : 0
+  name        = "${local.name_prefix}-db-sg"
+  description = "TeamCity DB security group"
+  vpc_id      = var.vpc_id
+  tags        = local.tags
+}
+
+# Ingress rule for PostgreSQL from service to database cluster
+resource "aws_vpc_security_group_ingress_rule" "service_db" {
+  count                        = var.database_connection_string == null ? 1 : 0
+  security_group_id            = aws_security_group.teamcity_db_sg[0].id
+  referenced_security_group_id = aws_security_group.teamcity_service_sg.id
+  description                  = "Allow inbound access from TeamCity service containers to DB"
+  ip_protocol                  = "TCP"
+  from_port                    = 5432
+  to_port                      = 5432
+}
+
+# Ingress rule for PostgreSQL from database cluster to service
+resource "aws_vpc_security_group_ingress_rule" "db_service" {
+  count                        = var.database_connection_string == null ? 1 : 0
+  security_group_id            = aws_security_group.teamcity_service_sg.id
+  referenced_security_group_id = aws_security_group.teamcity_db_sg[0].id
+  description                  = "Allow inbound access from DB to TeamCity service containers"
+  ip_protocol                  = "TCP"
+  from_port                    = 5432
+  to_port                      = 5432
+}
path: modules/teamcity/main.tf
body: Not sure this is necessary. DB doesn't need ingress access to service.
created_at: 2025-03-11T16:41:24Z
author_association: NONE
diff_hunk: @@ -0,0 +1,595 @@
+###################################
+# ECS Cluster for TeamCity Server #
+###################################
+# If cluster name is provided use a data source to access existing resource
+data "aws_ecs_cluster" "teamcity_cluster" {
+  count        = var.cluster_name != null ? 1 : 0
+  cluster_name = var.cluster_name
+}
+resource "aws_ecs_cluster" "teamcity_cluster" {
+  count = var.cluster_name != null ? 0 : 1
+  name  = "${local.name_prefix}-cluster"
+
+  setting {
+    name  = "containerInsights"
+    value = "enabled"
+  }
+
+  tags = local.tags
+}
+
+# TeamCity Task Definition
+resource "aws_ecs_task_definition" "teamcity_task_definition" {
+  family                   = var.name
+  requires_compatibilities = ["FARGATE"]
+  network_mode             = "awsvpc"
+  cpu                      = var.container_cpu
+  memory                   = var.container_memory
+
+  container_definitions = jsonencode([
+    {
+      name      = var.container_name
+      image     = local.image
+      cpu       = var.container_cpu
+      memory    = var.container_memory
+      essential = true
+
+      portMappings = [
+        {
+          containerPort = var.container_port
+          hostPort      = var.container_port
+        }
+      ]
+      logConfiguration = {
+        logDriver = "awslogs"
+        options = {
+          awslogs-group         = aws_cloudwatch_log_group.teamcity_log_group.name
+          awslogs-region        = data.aws_region.current.name
+          awslogs-stream-prefix = "[APP]"
+        }
+      }
+
+      mountPoints = [
+        {
+          sourceVolume  = "teamcity_data",
+          containerPath = "/data/teamcity_server/datadir"
+          readOnly      = false
+        }
+      ]
+
+      # Combine the lists
+      environment = concat(local.base_env, local.password_env)
+
+      secrets = var.database_connection_string == null ? [
+        {
+          name      = "TEAMCITY_DB_PASSWORD"
+          valueFrom = "${aws_rds_cluster.teamcity_db_cluster[0].master_user_secret[0].secret_arn}:password::"
+        }
+      ] : []
+    }
+  ])
+  tags = {
+    Name = var.name
+  }
+  task_role_arn      = aws_iam_role.teamcity_default_role.arn
+  execution_role_arn = aws_iam_role.teamcity_task_execution_role.arn
+
+  volume {
+    name = "teamcity_data"
+    efs_volume_configuration {
+      file_system_id          = local.efs_file_system_id
+      transit_encryption      = "ENABLED"
+      transit_encryption_port = 2999
+      authorization_config {
+        access_point_id = local.efs_access_point_id
+        iam             = "ENABLED"
+      }
+    }
+  }
+}
+
+# ECS Service
+resource "aws_ecs_service" "teamcity" {
+  name                   = local.name_prefix
+  cluster                = var.cluster_name != null ? data.aws_ecs_cluster.teamcity_cluster[0].arn : aws_ecs_cluster.teamcity_cluster[0].arn
+  task_definition        = aws_ecs_task_definition.teamcity_task_definition.arn
+  launch_type            = "FARGATE"
+  desired_count          = var.desired_container_count
+  force_new_deployment   = var.debug
+  enable_execute_command = var.debug
+
+  wait_for_steady_state = false #TODO: make this configurable
+
+  network_configuration {
+    subnets         = var.service_subnets
+    security_groups = [aws_security_group.teamcity_service_sg.id]
+  }
+  dynamic "load_balancer" {
+    for_each = var.create_external_alb ? [1] : []
+    content {
+      target_group_arn = aws_lb_target_group.teamcity_target_group[0].arn
+      container_name   = var.container_name
+      container_port   = var.container_port
+
+    }
+  }
+
+  tags = local.tags
+}
+
+###################################
+# Security Groups                 #
+###################################
+
+# TeamCity service security group
+resource "aws_security_group" "teamcity_service_sg" {
+  name        = "${local.name_prefix}-service-sg"
+  vpc_id      = var.vpc_id
+  description = "TeamCity service security group"
+  tags        = local.tags
+}
+
+# TeamCity EFS security group
+resource "aws_security_group" "teamcity_efs_sg" {
+  count       = var.efs_id == null ? 1 : 0
+  name        = "${local.name_prefix}-efs-sg"
+  description = "TeamCity EFS mount target security group"
+  vpc_id      = var.vpc_id
+  tags        = local.tags
+}
+
+# Ingress rule for NFS traffic from service to EFS
+resource "aws_vpc_security_group_ingress_rule" "service_efs" {
+  count                        = var.efs_id == null ? 1 : 0
+  security_group_id            = aws_security_group.teamcity_efs_sg[0].id
+  referenced_security_group_id = aws_security_group.teamcity_service_sg.id
+  description                  = "Allow inbound access from TeamCity service containers to EFS"
+  ip_protocol                  = "TCP"
+  from_port                    = 2049
+  to_port                      = 2049
+}
+
+# Ingress rule for NFS traffic from EFS to service
+resource "aws_vpc_security_group_ingress_rule" "efs_service" {
+  count                        = var.efs_id == null ? 1 : 0
+  security_group_id            = aws_security_group.teamcity_service_sg.id
+  referenced_security_group_id = aws_security_group.teamcity_efs_sg[0].id
+  description                  = "Allow inbound access from EFS to TeamCity service containers"
+  ip_protocol                  = "TCP"
+  from_port                    = 2049
+  to_port                      = 2049
+}
+
+# TeamCity Aurora Serverless PostgreSQL security group
+resource "aws_security_group" "teamcity_db_sg" {
+  count       = var.database_connection_string == null ? 1 : 0
+  name        = "${local.name_prefix}-db-sg"
+  description = "TeamCity DB security group"
+  vpc_id      = var.vpc_id
+  tags        = local.tags
+}
+
+# Ingress rule for PostgreSQL from service to database cluster
+resource "aws_vpc_security_group_ingress_rule" "service_db" {
+  count                        = var.database_connection_string == null ? 1 : 0
+  security_group_id            = aws_security_group.teamcity_db_sg[0].id
+  referenced_security_group_id = aws_security_group.teamcity_service_sg.id
+  description                  = "Allow inbound access from TeamCity service containers to DB"
+  ip_protocol                  = "TCP"
+  from_port                    = 5432
+  to_port                      = 5432
+}
+
+# Ingress rule for PostgreSQL from database cluster to service
+resource "aws_vpc_security_group_ingress_rule" "db_service" {
+  count                        = var.database_connection_string == null ? 1 : 0
+  security_group_id            = aws_security_group.teamcity_service_sg.id
+  referenced_security_group_id = aws_security_group.teamcity_db_sg[0].id
+  description                  = "Allow inbound access from DB to TeamCity service containers"
+  ip_protocol                  = "TCP"
+  from_port                    = 5432
+  to_port                      = 5432
+}
+
+# Egress rule for database to let all traffic out
+resource "aws_vpc_security_group_egress_rule" "db_outbound" {
+  count             = var.database_connection_string == null ? 1 : 0
+  security_group_id = aws_security_group.teamcity_db_sg[0].id
+  description       = "Allow outbound access from DB to all"
+  cidr_ipv4         = "0.0.0.0/0"
+  ip_protocol       = "-1"
+}
+
+# TeamCity ALB security group
+resource "aws_security_group" "teamcity_alb_sg" {
+  #checkov:skip=CKV2_AWS_5:SG is attached to TeamCity service ALB
+  count       = var.create_external_alb ? 1 : 0
+  name        = "${local.name_prefix}-alb-sg"
+  vpc_id      = var.vpc_id
+  description = "TeamCity ALB security group"
+  tags        = local.tags
+}
+
+# Ingress rule for HTTP traffic from ALB to service
+resource "aws_vpc_security_group_ingress_rule" "service_inbound_alb" {
+  count                        = var.create_external_alb ? 1 : 0
+  security_group_id            = aws_security_group.teamcity_service_sg.id
+  referenced_security_group_id = aws_security_group.teamcity_alb_sg[0].id
+  description                  = "Allow inbound HTTP traffic from ALB to service containers"
+  from_port                    = var.container_port
+  to_port                      = var.container_port
+  ip_protocol                  = "TCP"
+}
+
+# Egress rule for HTTP traffic from ALB to service
+resource "aws_vpc_security_group_egress_rule" "service_outbound_alb" {
+  count                        = var.create_external_alb ? 1 : 0
+  security_group_id            = aws_security_group.teamcity_alb_sg[0].id
+  referenced_security_group_id = aws_security_group.teamcity_service_sg.id
+  description                  = "Allow outbound HTTP traffic from ALB to service containers"
+  from_port                    = var.container_port
+  to_port                      = var.container_port
+  ip_protocol                  = "TCP"
+}
+
+# Grant TeamCity service access to internet
+resource "aws_vpc_security_group_egress_rule" "internet_outbound" {
+  security_group_id = aws_security_group.teamcity_service_sg.id
+  description       = "Allow outbound internet access from TeamCity service containers"
+  cidr_ipv4         = "0.0.0.0/0"
+  ip_protocol       = "-1"
+}
+
+#############################################
+# IAM Roles for TeamCity Module
+#############################################
+
+data "aws_iam_policy_document" "ecs_tasks_trust_relationship" {
+  statement {
+    effect  = "Allow"
+    actions = ["sts:AssumeRole"]
+    principals {
+      type        = "Service"
+      identifiers = ["ecs-tasks.amazonaws.com"]
+    }
+  }
+}
+
+data "aws_iam_policy_document" "teamcity_default_policy" {
+  # ECS
+  statement {
+    sid    = "ECSExec"
+    effect = "Allow"
+    actions = [
+      "ssmmessages:OpenDataChannel",
+      "ssmmessages:OpenControlChannel",
+      "ssmmessages:CreateDataChannel",
+      "ssmmessages:CreateControlChannel",
+    ]
+    resources = [
+      "*"
+    ]
+  }
+
+  #grant the task necessary EFS permissions to be able to modify directories
+  statement {
+    sid    = "EFS"
+    effect = "Allow"
+    actions = [
+      "elasticfilesystem:ClientWrite",
+      "elasticfilesystem:ClientRootAccess",
+      "elasticfilesystem:ClientMount"
+    ]
+    resources = [
+      local.efs_file_system_arn
+    ]
+  }
+}
path: modules/teamcity/main.tf
body: Does this work if they provide a filesystem?
created_at: 2025-03-11T16:42:30Z
author_association: NONE
diff_hunk: @@ -0,0 +1,595 @@
+###################################
+# ECS Cluster for TeamCity Server #
+###################################
+# If cluster name is provided use a data source to access existing resource
+data "aws_ecs_cluster" "teamcity_cluster" {
+  count        = var.cluster_name != null ? 1 : 0
+  cluster_name = var.cluster_name
+}
+resource "aws_ecs_cluster" "teamcity_cluster" {
+  count = var.cluster_name != null ? 0 : 1
+  name  = "${local.name_prefix}-cluster"
+
+  setting {
+    name  = "containerInsights"
+    value = "enabled"
+  }
+
+  tags = local.tags
+}
+
+# TeamCity Task Definition
+resource "aws_ecs_task_definition" "teamcity_task_definition" {
+  family                   = var.name
+  requires_compatibilities = ["FARGATE"]
+  network_mode             = "awsvpc"
+  cpu                      = var.container_cpu
+  memory                   = var.container_memory
+
+  container_definitions = jsonencode([
+    {
+      name      = var.container_name
+      image     = local.image
+      cpu       = var.container_cpu
+      memory    = var.container_memory
+      essential = true
+
+      portMappings = [
+        {
+          containerPort = var.container_port
+          hostPort      = var.container_port
+        }
+      ]
+      logConfiguration = {
+        logDriver = "awslogs"
+        options = {
+          awslogs-group         = aws_cloudwatch_log_group.teamcity_log_group.name
+          awslogs-region        = data.aws_region.current.name
+          awslogs-stream-prefix = "[APP]"
+        }
+      }
+
+      mountPoints = [
+        {
+          sourceVolume  = "teamcity_data",
+          containerPath = "/data/teamcity_server/datadir"
+          readOnly      = false
+        }
+      ]
+
+      # Combine the lists
+      environment = concat(local.base_env, local.password_env)
+
+      secrets = var.database_connection_string == null ? [
+        {
+          name      = "TEAMCITY_DB_PASSWORD"
+          valueFrom = "${aws_rds_cluster.teamcity_db_cluster[0].master_user_secret[0].secret_arn}:password::"
+        }
+      ] : []
+    }
+  ])
+  tags = {
+    Name = var.name
+  }
+  task_role_arn      = aws_iam_role.teamcity_default_role.arn
+  execution_role_arn = aws_iam_role.teamcity_task_execution_role.arn
+
+  volume {
+    name = "teamcity_data"
+    efs_volume_configuration {
+      file_system_id          = local.efs_file_system_id
+      transit_encryption      = "ENABLED"
+      transit_encryption_port = 2999
+      authorization_config {
+        access_point_id = local.efs_access_point_id
+        iam             = "ENABLED"
+      }
+    }
+  }
+}
+
+# ECS Service
+resource "aws_ecs_service" "teamcity" {
+  name                   = local.name_prefix
+  cluster                = var.cluster_name != null ? data.aws_ecs_cluster.teamcity_cluster[0].arn : aws_ecs_cluster.teamcity_cluster[0].arn
+  task_definition        = aws_ecs_task_definition.teamcity_task_definition.arn
+  launch_type            = "FARGATE"
+  desired_count          = var.desired_container_count
+  force_new_deployment   = var.debug
+  enable_execute_command = var.debug
+
+  wait_for_steady_state = false #TODO: make this configurable
+
+  network_configuration {
+    subnets         = var.service_subnets
+    security_groups = [aws_security_group.teamcity_service_sg.id]
+  }
+  dynamic "load_balancer" {
+    for_each = var.create_external_alb ? [1] : []
+    content {
+      target_group_arn = aws_lb_target_group.teamcity_target_group[0].arn
+      container_name   = var.container_name
+      container_port   = var.container_port
+
+    }
+  }
+
+  tags = local.tags
+}
+
+###################################
+# Security Groups                 #
+###################################
+
+# TeamCity service security group
+resource "aws_security_group" "teamcity_service_sg" {
+  name        = "${local.name_prefix}-service-sg"
+  vpc_id      = var.vpc_id
+  description = "TeamCity service security group"
+  tags        = local.tags
+}
+
+# TeamCity EFS security group
+resource "aws_security_group" "teamcity_efs_sg" {
+  count       = var.efs_id == null ? 1 : 0
+  name        = "${local.name_prefix}-efs-sg"
+  description = "TeamCity EFS mount target security group"
+  vpc_id      = var.vpc_id
+  tags        = local.tags
+}
+
+# Ingress rule for NFS traffic from service to EFS
+resource "aws_vpc_security_group_ingress_rule" "service_efs" {
+  count                        = var.efs_id == null ? 1 : 0
+  security_group_id            = aws_security_group.teamcity_efs_sg[0].id
+  referenced_security_group_id = aws_security_group.teamcity_service_sg.id
+  description                  = "Allow inbound access from TeamCity service containers to EFS"
+  ip_protocol                  = "TCP"
+  from_port                    = 2049
+  to_port                      = 2049
+}
+
+# Ingress rule for NFS traffic from EFS to service
+resource "aws_vpc_security_group_ingress_rule" "efs_service" {
+  count                        = var.efs_id == null ? 1 : 0
+  security_group_id            = aws_security_group.teamcity_service_sg.id
+  referenced_security_group_id = aws_security_group.teamcity_efs_sg[0].id
+  description                  = "Allow inbound access from EFS to TeamCity service containers"
+  ip_protocol                  = "TCP"
+  from_port                    = 2049
+  to_port                      = 2049
+}
+
+# TeamCity Aurora Serverless PostgreSQL security group
+resource "aws_security_group" "teamcity_db_sg" {
+  count       = var.database_connection_string == null ? 1 : 0
+  name        = "${local.name_prefix}-db-sg"
+  description = "TeamCity DB security group"
+  vpc_id      = var.vpc_id
+  tags        = local.tags
+}
+
+# Ingress rule for PostgreSQL from service to database cluster
+resource "aws_vpc_security_group_ingress_rule" "service_db" {
+  count                        = var.database_connection_string == null ? 1 : 0
+  security_group_id            = aws_security_group.teamcity_db_sg[0].id
+  referenced_security_group_id = aws_security_group.teamcity_service_sg.id
+  description                  = "Allow inbound access from TeamCity service containers to DB"
+  ip_protocol                  = "TCP"
+  from_port                    = 5432
+  to_port                      = 5432
+}
+
+# Ingress rule for PostgreSQL from database cluster to service
+resource "aws_vpc_security_group_ingress_rule" "db_service" {
+  count                        = var.database_connection_string == null ? 1 : 0
+  security_group_id            = aws_security_group.teamcity_service_sg.id
+  referenced_security_group_id = aws_security_group.teamcity_db_sg[0].id
+  description                  = "Allow inbound access from DB to TeamCity service containers"
+  ip_protocol                  = "TCP"
+  from_port                    = 5432
+  to_port                      = 5432
+}
+
+# Egress rule for database to let all traffic out
+resource "aws_vpc_security_group_egress_rule" "db_outbound" {
+  count             = var.database_connection_string == null ? 1 : 0
+  security_group_id = aws_security_group.teamcity_db_sg[0].id
+  description       = "Allow outbound access from DB to all"
+  cidr_ipv4         = "0.0.0.0/0"
+  ip_protocol       = "-1"
+}
+
+# TeamCity ALB security group
+resource "aws_security_group" "teamcity_alb_sg" {
+  #checkov:skip=CKV2_AWS_5:SG is attached to TeamCity service ALB
+  count       = var.create_external_alb ? 1 : 0
+  name        = "${local.name_prefix}-alb-sg"
+  vpc_id      = var.vpc_id
+  description = "TeamCity ALB security group"
+  tags        = local.tags
+}
+
+# Ingress rule for HTTP traffic from ALB to service
+resource "aws_vpc_security_group_ingress_rule" "service_inbound_alb" {
+  count                        = var.create_external_alb ? 1 : 0
+  security_group_id            = aws_security_group.teamcity_service_sg.id
+  referenced_security_group_id = aws_security_group.teamcity_alb_sg[0].id
+  description                  = "Allow inbound HTTP traffic from ALB to service containers"
+  from_port                    = var.container_port
+  to_port                      = var.container_port
+  ip_protocol                  = "TCP"
+}
+
+# Egress rule for HTTP traffic from ALB to service
+resource "aws_vpc_security_group_egress_rule" "service_outbound_alb" {
+  count                        = var.create_external_alb ? 1 : 0
+  security_group_id            = aws_security_group.teamcity_alb_sg[0].id
+  referenced_security_group_id = aws_security_group.teamcity_service_sg.id
+  description                  = "Allow outbound HTTP traffic from ALB to service containers"
+  from_port                    = var.container_port
+  to_port                      = var.container_port
+  ip_protocol                  = "TCP"
+}
+
+# Grant TeamCity service access to internet
+resource "aws_vpc_security_group_egress_rule" "internet_outbound" {
+  security_group_id = aws_security_group.teamcity_service_sg.id
+  description       = "Allow outbound internet access from TeamCity service containers"
+  cidr_ipv4         = "0.0.0.0/0"
+  ip_protocol       = "-1"
+}
+
+#############################################
+# IAM Roles for TeamCity Module
+#############################################
+
+data "aws_iam_policy_document" "ecs_tasks_trust_relationship" {
+  statement {
+    effect  = "Allow"
+    actions = ["sts:AssumeRole"]
+    principals {
+      type        = "Service"
+      identifiers = ["ecs-tasks.amazonaws.com"]
+    }
+  }
+}
+
+data "aws_iam_policy_document" "teamcity_default_policy" {
+  # ECS
+  statement {
+    sid    = "ECSExec"
+    effect = "Allow"
+    actions = [
+      "ssmmessages:OpenDataChannel",
+      "ssmmessages:OpenControlChannel",
+      "ssmmessages:CreateDataChannel",
+      "ssmmessages:CreateControlChannel",
+    ]
+    resources = [
+      "*"
+    ]
+  }
+
+  #grant the task necessary EFS permissions to be able to modify directories
+  statement {
+    sid    = "EFS"
+    effect = "Allow"
+    actions = [
+      "elasticfilesystem:ClientWrite",
+      "elasticfilesystem:ClientRootAccess",
+      "elasticfilesystem:ClientMount"
+    ]
+    resources = [
+      local.efs_file_system_arn
+    ]
+  }
+}
+
+resource "aws_iam_policy" "teamcity_default_policy" {
+  name        = "teamcity-default-policy"
+  description = "Policy granting permissions for TeamCity."
+  policy      = data.aws_iam_policy_document.teamcity_default_policy.json
+}
+
+resource "aws_iam_role" "teamcity_default_role" {
+  name               = "teamcity-default-role"
+  description        = "Default role for TeamCity ECS task."
+  assume_role_policy = data.aws_iam_policy_document.ecs_tasks_trust_relationship.json
+  managed_policy_arns = [
+    aws_iam_policy.teamcity_default_policy.arn
+  ]
+
+  tags = local.tags
+}
+resource "aws_iam_role" "teamcity_task_execution_role" {
+  name = "teamcity-task-execution-role"
+
+  assume_role_policy  = data.aws_iam_policy_document.ecs_tasks_trust_relationship.json
+  managed_policy_arns = ["arn:aws:iam::aws:policy/service-role/AmazonECSTaskExecutionRolePolicy"]
+
+  dynamic "inline_policy" {
+    for_each = var.database_connection_string == null ? [1] : []
+    content {
+      name = "teamcity-secrets-access"
+      policy = jsonencode({
+        Version = "2012-10-17"
+        Statement = [
+          {
+            Sid    = "SecretsManager"
+            Effect = "Allow"
+            Action = [
+              "secretsmanager:GetSecretValue"
+            ]
+            Resource = [
+              aws_rds_cluster.teamcity_db_cluster[0].master_user_secret[0].secret_arn
+            ]
+          }
+        ]
+      })
+    }
+  }
+
+}
+
+# CloudWatch Logs
+resource "aws_cloudwatch_log_group" "teamcity_log_group" {
+  #checkov:skip=CKV_AWS_158: KMS Encryption disabled by default
+  name              = "${local.name_prefix}-log-group"
+  retention_in_days = var.teamcity_cloudwatch_log_retention_in_days
+  tags              = local.tags
+}
+
+# Load Balancer for TeamCity Service
+resource "aws_lb" "teamcity_external_lb" {
+  count              = var.create_external_alb ? 1 : 0
+  name               = "${local.name_prefix}-lb"
+  security_groups    = [aws_security_group.teamcity_alb_sg[0].id]
+  load_balancer_type = "application"
+  internal           = false
+  subnets            = var.alb_subnets
+
+
+  dynamic "access_logs" {
+    for_each = var.enable_teamcity_alb_access_logs ? [1] : []
+    content {
+      enabled = var.enable_teamcity_alb_access_logs
+      bucket  = var.teamcity_alb_access_logs_bucket != null ? var.teamcity_alb_access_logs_bucket : aws_s3_bucket.teamcity_alb_access_logs_bucket[0].id
+      prefix  = var.teamcity_alb_access_logs_prefix != null ? var.teamcity_alb_access_logs_prefix : "${local.name_prefix}-alb"
+    }
+  }
+  #checkov:skip=CKV_AWS_150:Deletion protection disabled by default
+  enable_deletion_protection = var.enable_teamcity_alb_deletion_protection
+
+  #checkov:skip=CKV2_AWS_28: ALB access is managed with SG allow listing
+
+  drop_invalid_header_fields = true
+  tags                       = local.tags
+}
+
+# TeamCity target group for ALB
+resource "aws_lb_target_group" "teamcity_target_group" {
+  #checkov:skip=CKV_AWS_378: Using ALB for TLS termination
+  count       = var.create_external_alb ? 1 : 0
+  name        = "${local.name_prefix}-tg"
+  port        = var.container_port
+  protocol    = "HTTP"
+  vpc_id      = var.vpc_id
+  target_type = "ip"
+
+  health_check {
+    path                = "/healthCheck/healthy"
+    interval            = 30
+    timeout             = 5
+    healthy_threshold   = 5
+    unhealthy_threshold = 2
+    port                = var.container_port
+    protocol            = "HTTP"
+    matcher             = "200"
+  }
+
+  tags = local.tags
+}
+
+# ALB HTTPS Listener
+resource "aws_lb_listener" "teamcity_listener" {
+  count             = var.create_external_alb ? 1 : 0
+  load_balancer_arn = aws_lb.teamcity_external_lb[0].arn
+  port              = "443"
+  protocol          = "HTTPS"
+  ssl_policy        = "ELBSecurityPolicy-TLS13-1-2-2021-06"
+  certificate_arn   = var.alb_certificate_arn
+  default_action {
+    type             = "forward"
+    target_group_arn = aws_lb_target_group.teamcity_target_group[0].arn
+  }
+  tags = local.tags
+}
+
+# #######################################
+# # TeamCity Aurora Serverless Database #
+# #######################################
+# Subnet group
+resource "aws_db_subnet_group" "teamcity_db_subnet_group" {
+  count      = var.database_connection_string == null ? 1 : 0
+  name       = "${local.name_prefix}-db-subnet-group"
+  subnet_ids = var.service_subnets
+  tags       = local.tags
+}
+
+# # RDS instance with Aurora serverless engine
+resource "aws_rds_cluster" "teamcity_db_cluster" {
+  #checkov:skip=CKV2_AWS_27:not enabling query logging by design
+  #checkov:skip=CKV2_AWS_8: TODO: add rds backup plan
+  count                       = var.database_connection_string == null ? 1 : 0
+  cluster_identifier          = "teamcity-cluster"
+  engine                      = "aurora-postgresql"
+  engine_mode                 = "provisioned"
+  engine_version              = "16.6" #check for latest as option
+  database_name               = "teamcity"
+  master_username             = "teamcity"
+  manage_master_user_password = true #using AWS Secrets Manager
+  storage_encrypted           = true
+  skip_final_snapshot         = var.aurora_skip_final_snapshot
+  db_subnet_group_name        = aws_db_subnet_group.teamcity_db_subnet_group[0].id
+  vpc_security_group_ids = [
+    aws_security_group.teamcity_db_sg[0].id
+  ]
+
+  serverlessv2_scaling_configuration {
+    max_capacity             = 1.0
+    min_capacity             = 0.0
+    seconds_until_auto_pause = 3600
+  }
+}
+
+resource "aws_rds_cluster_instance" "teamcity_db_cluster_instance" {
+  count              = var.database_connection_string == null ? var.aurora_instance_count : 0
+  cluster_identifier = aws_rds_cluster.teamcity_db_cluster[0].id
+  instance_class     = "db.serverless"
+  engine             = aws_rds_cluster.teamcity_db_cluster[0].engine
+  engine_version     = aws_rds_cluster.teamcity_db_cluster[0].engine_version
+}
+
+# ################
+# # TeamCity EFS #
+# ################
+
+# File system for teamcity
+resource "aws_efs_file_system" "teamcity_efs_file_system" {
+  count            = var.efs_id != null ? 0 : 1
+  creation_token   = "${local.name_prefix}-efs-file-system"
+  performance_mode = var.teamcity_efs_performance_mode
+  throughput_mode  = var.teamcity_efs_throughput_mode
+
+  #TODO: Parameterize encryption and customer managed key creation
+  encrypted = false
path: modules/teamcity/main.tf
body: Hmm. This is the one that was throwing errors when enabled?
created_at: 2025-03-11T16:44:49Z
author_association: NONE
diff_hunk: @@ -0,0 +1,194 @@
+########################################
+# GENERAL CONFIGURATION
+########################################
+
+variable "name" {
+  type        = string
+  default     = "teamcity"
+  description = "The name applied to resources in the TeamCity module"
+}
+variable "tags" {
+  type = map(any)
+  default = {
+    "iac-management" = "CGD-Toolkit"
+    "iac-module"     = "TeamCity"
+    "iac-provider"   = "Terraform"
+  }
+  description = "Tags to apply to resources."
+}
+variable "environment" {
+  type        = string
+  description = "The current environment (e.g. dev, prod, etc.)"
+  default     = "dev"
+}
+
+variable "debug" {
+  type        = bool
+  description = "Set this flag to enable ECS execute permissions on the TeamCity server container and force new service deployments on Terraform apply."
+  default     = false
+}
+
+########################################
+# TeamCity SERVICE CONFIGURATION (ECS)
+########################################
+
+variable "cluster_name" {
+  type        = string
+  description = "The name of the ECS cluster to deploy TeamCity to."
+  default     = null
+
+}
+
+variable "container_cpu" {
+  type        = number
+  default     = 1024
+  description = "The number of CPU units to allocate to the TeamCity server container"
+}
+variable "container_memory" {
+  type        = number
+  default     = 4096
+  description = "The number of MB of memory to allocate to the TeamCity server container"
+}
+
+variable "container_name" {
+  type        = string
+  default     = "teamcity"
+  description = "The name of the TeamCity server container"
+}
+
+variable "container_port" {
+  type        = number
+  default     = 8111
+  description = "The port on which the TeamCity server container listens"
+}
+
+variable "desired_container_count" {
+  type        = number
+  description = "The desired number of containers running TeamCity server."
+  default     = 1
+  nullable    = false
+}
+
+########################################
+# Networking
+########################################
+
+variable "service_subnets" {
+  type        = list(string)
+  description = "The subnets in which the TeamCity server service will be deployed"
+}
+
+variable "vpc_id" {
+  type        = string
+  description = "The ID of the VPC in which the service will be deployed"
+}
+# Logging
+variable "teamcity_cloudwatch_log_retention_in_days" {
+  type        = string
+  description = "The log retention in days of the cloudwatch log group for TeamCity."
+  default     = 365
+}
+
+########################################
+# EFS Configuration
+########################################
+
+variable "efs_id" {
+  type        = string
+  description = "The ID of the EFS file system to use for the TeamCity service."
+  default     = null
+}
+
+variable "teamcity_efs_performance_mode" {
+  type        = string
+  description = "The performance mode of the EFS file system used by the TeamCity service. Defaults to general purpose."
+  default     = "generalPurpose"
+}
+
+variable "teamcity_efs_throughput_mode" {
+  type        = string
+  description = "The throughput mode of the EFS file system used by the TeamCity service. Defaults to bursting."
+  default     = "bursting"
+}
+
+variable "efs_access_point_id" {
+  type        = string
+  description = "The ID of the EFS access point to use for the TeamCity data volume."
+  default     = null
+}
+
+########################################
+# Load Balancing
+########################################
+variable "create_external_alb" {
+  type        = bool
+  description = "Set this flag to true to create an external load balancer for TeamCity."
+  default     = true
+}
+
+variable "alb_subnets" {
+  type        = list(string)
+  description = "The subnets in which the ALB will be deployed"
+}
+
+variable "alb_certificate_arn" {
+  type        = string
+  description = "The ARN of the SSL certificate to use for the ALB"
+}
path: modules/teamcity/variables.tf
body: These should be required **if** `create_external_alb = true` - otherwise they aren't required.

[See the docs for deets.](https://developer.hashicorp.com/terraform/language/expressions/custom-conditions#input-variable-validation)
created_at: 2025-03-11T17:22:49Z
author_association: NONE
diff_hunk: @@ -0,0 +1,8 @@
+module "teamcity" {
+  source              = "../../"
+  vpc_id              = aws_vpc.teamcity_vpc.id
+  service_subnets     = aws_subnet.private_subnets[*].id
+  alb_subnets         = aws_subnet.public_subnets[*].id
+  alb_certificate_arn = aws_acm_certificate.teamcity.arn
+  debug               = true
path: modules/teamcity/examples/simple/main.tf
user: login: cwwalb
body: I forgot to delete that. I will delete I think its best kept in the documentation
created_at: 2025-03-11T17:58:59Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,595 @@
+###################################
+# ECS Cluster for TeamCity Server #
+###################################
+# If cluster name is provided use a data source to access existing resource
+data "aws_ecs_cluster" "teamcity_cluster" {
+  count        = var.cluster_name != null ? 1 : 0
+  cluster_name = var.cluster_name
+}
+resource "aws_ecs_cluster" "teamcity_cluster" {
+  count = var.cluster_name != null ? 0 : 1
+  name  = "${local.name_prefix}-cluster"
+
+  setting {
+    name  = "containerInsights"
+    value = "enabled"
+  }
+
+  tags = local.tags
+}
+
+# TeamCity Task Definition
+resource "aws_ecs_task_definition" "teamcity_task_definition" {
+  family                   = var.name
+  requires_compatibilities = ["FARGATE"]
+  network_mode             = "awsvpc"
+  cpu                      = var.container_cpu
+  memory                   = var.container_memory
+
+  container_definitions = jsonencode([
+    {
+      name      = var.container_name
+      image     = local.image
+      cpu       = var.container_cpu
+      memory    = var.container_memory
+      essential = true
+
+      portMappings = [
+        {
+          containerPort = var.container_port
+          hostPort      = var.container_port
+        }
+      ]
+      logConfiguration = {
+        logDriver = "awslogs"
+        options = {
+          awslogs-group         = aws_cloudwatch_log_group.teamcity_log_group.name
+          awslogs-region        = data.aws_region.current.name
+          awslogs-stream-prefix = "[APP]"
+        }
+      }
+
+      mountPoints = [
+        {
+          sourceVolume  = "teamcity_data",
+          containerPath = "/data/teamcity_server/datadir"
+          readOnly      = false
+        }
+      ]
+
+      # Combine the lists
+      environment = concat(local.base_env, local.password_env)
+
+      secrets = var.database_connection_string == null ? [
+        {
+          name      = "TEAMCITY_DB_PASSWORD"
+          valueFrom = "${aws_rds_cluster.teamcity_db_cluster[0].master_user_secret[0].secret_arn}:password::"
+        }
+      ] : []
+    }
+  ])
+  tags = {
+    Name = var.name
+  }
+  task_role_arn      = aws_iam_role.teamcity_default_role.arn
+  execution_role_arn = aws_iam_role.teamcity_task_execution_role.arn
+
+  volume {
+    name = "teamcity_data"
+    efs_volume_configuration {
+      file_system_id          = local.efs_file_system_id
+      transit_encryption      = "ENABLED"
+      transit_encryption_port = 2999
+      authorization_config {
+        access_point_id = local.efs_access_point_id
+        iam             = "ENABLED"
+      }
+    }
+  }
+}
+
+# ECS Service
+resource "aws_ecs_service" "teamcity" {
+  name                   = local.name_prefix
+  cluster                = var.cluster_name != null ? data.aws_ecs_cluster.teamcity_cluster[0].arn : aws_ecs_cluster.teamcity_cluster[0].arn
+  task_definition        = aws_ecs_task_definition.teamcity_task_definition.arn
+  launch_type            = "FARGATE"
+  desired_count          = var.desired_container_count
+  force_new_deployment   = var.debug
+  enable_execute_command = var.debug
+
+  wait_for_steady_state = false #TODO: make this configurable
+
+  network_configuration {
+    subnets         = var.service_subnets
+    security_groups = [aws_security_group.teamcity_service_sg.id]
+  }
+  dynamic "load_balancer" {
+    for_each = var.create_external_alb ? [1] : []
+    content {
+      target_group_arn = aws_lb_target_group.teamcity_target_group[0].arn
+      container_name   = var.container_name
+      container_port   = var.container_port
+
+    }
+  }
+
+  tags = local.tags
+}
+
+###################################
+# Security Groups                 #
+###################################
+
+# TeamCity service security group
+resource "aws_security_group" "teamcity_service_sg" {
+  name        = "${local.name_prefix}-service-sg"
+  vpc_id      = var.vpc_id
+  description = "TeamCity service security group"
+  tags        = local.tags
+}
+
+# TeamCity EFS security group
+resource "aws_security_group" "teamcity_efs_sg" {
+  count       = var.efs_id == null ? 1 : 0
+  name        = "${local.name_prefix}-efs-sg"
+  description = "TeamCity EFS mount target security group"
+  vpc_id      = var.vpc_id
+  tags        = local.tags
+}
+
+# Ingress rule for NFS traffic from service to EFS
+resource "aws_vpc_security_group_ingress_rule" "service_efs" {
+  count                        = var.efs_id == null ? 1 : 0
+  security_group_id            = aws_security_group.teamcity_efs_sg[0].id
+  referenced_security_group_id = aws_security_group.teamcity_service_sg.id
+  description                  = "Allow inbound access from TeamCity service containers to EFS"
+  ip_protocol                  = "TCP"
+  from_port                    = 2049
+  to_port                      = 2049
+}
+
+# Ingress rule for NFS traffic from EFS to service
+resource "aws_vpc_security_group_ingress_rule" "efs_service" {
+  count                        = var.efs_id == null ? 1 : 0
+  security_group_id            = aws_security_group.teamcity_service_sg.id
+  referenced_security_group_id = aws_security_group.teamcity_efs_sg[0].id
+  description                  = "Allow inbound access from EFS to TeamCity service containers"
+  ip_protocol                  = "TCP"
+  from_port                    = 2049
+  to_port                      = 2049
+}
+
+# TeamCity Aurora Serverless PostgreSQL security group
+resource "aws_security_group" "teamcity_db_sg" {
+  count       = var.database_connection_string == null ? 1 : 0
+  name        = "${local.name_prefix}-db-sg"
+  description = "TeamCity DB security group"
+  vpc_id      = var.vpc_id
+  tags        = local.tags
+}
+
+# Ingress rule for PostgreSQL from service to database cluster
+resource "aws_vpc_security_group_ingress_rule" "service_db" {
+  count                        = var.database_connection_string == null ? 1 : 0
+  security_group_id            = aws_security_group.teamcity_db_sg[0].id
+  referenced_security_group_id = aws_security_group.teamcity_service_sg.id
+  description                  = "Allow inbound access from TeamCity service containers to DB"
+  ip_protocol                  = "TCP"
+  from_port                    = 5432
+  to_port                      = 5432
+}
+
+# Ingress rule for PostgreSQL from database cluster to service
+resource "aws_vpc_security_group_ingress_rule" "db_service" {
+  count                        = var.database_connection_string == null ? 1 : 0
+  security_group_id            = aws_security_group.teamcity_service_sg.id
+  referenced_security_group_id = aws_security_group.teamcity_db_sg[0].id
+  description                  = "Allow inbound access from DB to TeamCity service containers"
+  ip_protocol                  = "TCP"
+  from_port                    = 5432
+  to_port                      = 5432
+}
+
+# Egress rule for database to let all traffic out
+resource "aws_vpc_security_group_egress_rule" "db_outbound" {
+  count             = var.database_connection_string == null ? 1 : 0
+  security_group_id = aws_security_group.teamcity_db_sg[0].id
+  description       = "Allow outbound access from DB to all"
+  cidr_ipv4         = "0.0.0.0/0"
+  ip_protocol       = "-1"
+}
+
+# TeamCity ALB security group
+resource "aws_security_group" "teamcity_alb_sg" {
+  #checkov:skip=CKV2_AWS_5:SG is attached to TeamCity service ALB
+  count       = var.create_external_alb ? 1 : 0
+  name        = "${local.name_prefix}-alb-sg"
+  vpc_id      = var.vpc_id
+  description = "TeamCity ALB security group"
+  tags        = local.tags
+}
+
+# Ingress rule for HTTP traffic from ALB to service
+resource "aws_vpc_security_group_ingress_rule" "service_inbound_alb" {
+  count                        = var.create_external_alb ? 1 : 0
+  security_group_id            = aws_security_group.teamcity_service_sg.id
+  referenced_security_group_id = aws_security_group.teamcity_alb_sg[0].id
+  description                  = "Allow inbound HTTP traffic from ALB to service containers"
+  from_port                    = var.container_port
+  to_port                      = var.container_port
+  ip_protocol                  = "TCP"
+}
+
+# Egress rule for HTTP traffic from ALB to service
+resource "aws_vpc_security_group_egress_rule" "service_outbound_alb" {
+  count                        = var.create_external_alb ? 1 : 0
+  security_group_id            = aws_security_group.teamcity_alb_sg[0].id
+  referenced_security_group_id = aws_security_group.teamcity_service_sg.id
+  description                  = "Allow outbound HTTP traffic from ALB to service containers"
+  from_port                    = var.container_port
+  to_port                      = var.container_port
+  ip_protocol                  = "TCP"
+}
+
+# Grant TeamCity service access to internet
+resource "aws_vpc_security_group_egress_rule" "internet_outbound" {
+  security_group_id = aws_security_group.teamcity_service_sg.id
+  description       = "Allow outbound internet access from TeamCity service containers"
+  cidr_ipv4         = "0.0.0.0/0"
+  ip_protocol       = "-1"
+}
+
+#############################################
+# IAM Roles for TeamCity Module
+#############################################
+
+data "aws_iam_policy_document" "ecs_tasks_trust_relationship" {
+  statement {
+    effect  = "Allow"
+    actions = ["sts:AssumeRole"]
+    principals {
+      type        = "Service"
+      identifiers = ["ecs-tasks.amazonaws.com"]
+    }
+  }
+}
+
+data "aws_iam_policy_document" "teamcity_default_policy" {
+  # ECS
+  statement {
+    sid    = "ECSExec"
+    effect = "Allow"
+    actions = [
+      "ssmmessages:OpenDataChannel",
+      "ssmmessages:OpenControlChannel",
+      "ssmmessages:CreateDataChannel",
+      "ssmmessages:CreateControlChannel",
+    ]
+    resources = [
+      "*"
+    ]
+  }
+
+  #grant the task necessary EFS permissions to be able to modify directories
+  statement {
+    sid    = "EFS"
+    effect = "Allow"
+    actions = [
+      "elasticfilesystem:ClientWrite",
+      "elasticfilesystem:ClientRootAccess",
+      "elasticfilesystem:ClientMount"
+    ]
+    resources = [
+      local.efs_file_system_arn
+    ]
+  }
+}
+
+resource "aws_iam_policy" "teamcity_default_policy" {
+  name        = "teamcity-default-policy"
+  description = "Policy granting permissions for TeamCity."
+  policy      = data.aws_iam_policy_document.teamcity_default_policy.json
+}
+
+resource "aws_iam_role" "teamcity_default_role" {
+  name               = "teamcity-default-role"
+  description        = "Default role for TeamCity ECS task."
+  assume_role_policy = data.aws_iam_policy_document.ecs_tasks_trust_relationship.json
+  managed_policy_arns = [
+    aws_iam_policy.teamcity_default_policy.arn
+  ]
+
+  tags = local.tags
+}
+resource "aws_iam_role" "teamcity_task_execution_role" {
+  name = "teamcity-task-execution-role"
+
+  assume_role_policy  = data.aws_iam_policy_document.ecs_tasks_trust_relationship.json
+  managed_policy_arns = ["arn:aws:iam::aws:policy/service-role/AmazonECSTaskExecutionRolePolicy"]
+
+  dynamic "inline_policy" {
+    for_each = var.database_connection_string == null ? [1] : []
+    content {
+      name = "teamcity-secrets-access"
+      policy = jsonencode({
+        Version = "2012-10-17"
+        Statement = [
+          {
+            Sid    = "SecretsManager"
+            Effect = "Allow"
+            Action = [
+              "secretsmanager:GetSecretValue"
+            ]
+            Resource = [
+              aws_rds_cluster.teamcity_db_cluster[0].master_user_secret[0].secret_arn
+            ]
+          }
+        ]
+      })
+    }
+  }
+
+}
+
+# CloudWatch Logs
+resource "aws_cloudwatch_log_group" "teamcity_log_group" {
+  #checkov:skip=CKV_AWS_158: KMS Encryption disabled by default
+  name              = "${local.name_prefix}-log-group"
+  retention_in_days = var.teamcity_cloudwatch_log_retention_in_days
+  tags              = local.tags
+}
+
+# Load Balancer for TeamCity Service
+resource "aws_lb" "teamcity_external_lb" {
+  count              = var.create_external_alb ? 1 : 0
+  name               = "${local.name_prefix}-lb"
+  security_groups    = [aws_security_group.teamcity_alb_sg[0].id]
+  load_balancer_type = "application"
+  internal           = false
+  subnets            = var.alb_subnets
+
+
+  dynamic "access_logs" {
+    for_each = var.enable_teamcity_alb_access_logs ? [1] : []
+    content {
+      enabled = var.enable_teamcity_alb_access_logs
+      bucket  = var.teamcity_alb_access_logs_bucket != null ? var.teamcity_alb_access_logs_bucket : aws_s3_bucket.teamcity_alb_access_logs_bucket[0].id
+      prefix  = var.teamcity_alb_access_logs_prefix != null ? var.teamcity_alb_access_logs_prefix : "${local.name_prefix}-alb"
+    }
+  }
+  #checkov:skip=CKV_AWS_150:Deletion protection disabled by default
+  enable_deletion_protection = var.enable_teamcity_alb_deletion_protection
+
+  #checkov:skip=CKV2_AWS_28: ALB access is managed with SG allow listing
+
+  drop_invalid_header_fields = true
+  tags                       = local.tags
+}
+
+# TeamCity target group for ALB
+resource "aws_lb_target_group" "teamcity_target_group" {
+  #checkov:skip=CKV_AWS_378: Using ALB for TLS termination
+  count       = var.create_external_alb ? 1 : 0
+  name        = "${local.name_prefix}-tg"
+  port        = var.container_port
+  protocol    = "HTTP"
+  vpc_id      = var.vpc_id
+  target_type = "ip"
+
+  health_check {
+    path                = "/healthCheck/healthy"
+    interval            = 30
+    timeout             = 5
+    healthy_threshold   = 5
+    unhealthy_threshold = 2
+    port                = var.container_port
+    protocol            = "HTTP"
+    matcher             = "200"
+  }
+
+  tags = local.tags
+}
+
+# ALB HTTPS Listener
+resource "aws_lb_listener" "teamcity_listener" {
+  count             = var.create_external_alb ? 1 : 0
+  load_balancer_arn = aws_lb.teamcity_external_lb[0].arn
+  port              = "443"
+  protocol          = "HTTPS"
+  ssl_policy        = "ELBSecurityPolicy-TLS13-1-2-2021-06"
+  certificate_arn   = var.alb_certificate_arn
+  default_action {
+    type             = "forward"
+    target_group_arn = aws_lb_target_group.teamcity_target_group[0].arn
+  }
+  tags = local.tags
+}
+
+# #######################################
+# # TeamCity Aurora Serverless Database #
+# #######################################
+# Subnet group
+resource "aws_db_subnet_group" "teamcity_db_subnet_group" {
+  count      = var.database_connection_string == null ? 1 : 0
+  name       = "${local.name_prefix}-db-subnet-group"
+  subnet_ids = var.service_subnets
+  tags       = local.tags
+}
+
+# # RDS instance with Aurora serverless engine
+resource "aws_rds_cluster" "teamcity_db_cluster" {
+  #checkov:skip=CKV2_AWS_27:not enabling query logging by design
+  #checkov:skip=CKV2_AWS_8: TODO: add rds backup plan
+  count                       = var.database_connection_string == null ? 1 : 0
+  cluster_identifier          = "teamcity-cluster"
+  engine                      = "aurora-postgresql"
+  engine_mode                 = "provisioned"
+  engine_version              = "16.6" #check for latest as option
+  database_name               = "teamcity"
+  master_username             = "teamcity"
+  manage_master_user_password = true #using AWS Secrets Manager
+  storage_encrypted           = true
+  skip_final_snapshot         = var.aurora_skip_final_snapshot
+  db_subnet_group_name        = aws_db_subnet_group.teamcity_db_subnet_group[0].id
+  vpc_security_group_ids = [
+    aws_security_group.teamcity_db_sg[0].id
+  ]
+
+  serverlessv2_scaling_configuration {
+    max_capacity             = 1.0
+    min_capacity             = 0.0
+    seconds_until_auto_pause = 3600
+  }
+}
+
+resource "aws_rds_cluster_instance" "teamcity_db_cluster_instance" {
+  count              = var.database_connection_string == null ? var.aurora_instance_count : 0
+  cluster_identifier = aws_rds_cluster.teamcity_db_cluster[0].id
+  instance_class     = "db.serverless"
+  engine             = aws_rds_cluster.teamcity_db_cluster[0].engine
+  engine_version     = aws_rds_cluster.teamcity_db_cluster[0].engine_version
+}
+
+# ################
+# # TeamCity EFS #
+# ################
+
+# File system for teamcity
+resource "aws_efs_file_system" "teamcity_efs_file_system" {
+  count            = var.efs_id != null ? 0 : 1
+  creation_token   = "${local.name_prefix}-efs-file-system"
+  performance_mode = var.teamcity_efs_performance_mode
+  throughput_mode  = var.teamcity_efs_throughput_mode
+
+  #TODO: Parameterize encryption and customer managed key creation
+  encrypted = false
path: modules/teamcity/main.tf
user: login: cwwalb
body: yes
created_at: 2025-03-11T19:57:43Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,595 @@
+###################################
+# ECS Cluster for TeamCity Server #
+###################################
+# If cluster name is provided use a data source to access existing resource
+data "aws_ecs_cluster" "teamcity_cluster" {
+  count        = var.cluster_name != null ? 1 : 0
+  cluster_name = var.cluster_name
+}
+resource "aws_ecs_cluster" "teamcity_cluster" {
+  count = var.cluster_name != null ? 0 : 1
+  name  = "${local.name_prefix}-cluster"
+
+  setting {
+    name  = "containerInsights"
+    value = "enabled"
+  }
+
+  tags = local.tags
+}
+
+# TeamCity Task Definition
+resource "aws_ecs_task_definition" "teamcity_task_definition" {
+  family                   = var.name
+  requires_compatibilities = ["FARGATE"]
+  network_mode             = "awsvpc"
+  cpu                      = var.container_cpu
+  memory                   = var.container_memory
+
+  container_definitions = jsonencode([
+    {
+      name      = var.container_name
+      image     = local.image
+      cpu       = var.container_cpu
+      memory    = var.container_memory
+      essential = true
+
+      portMappings = [
+        {
+          containerPort = var.container_port
+          hostPort      = var.container_port
+        }
+      ]
+      logConfiguration = {
+        logDriver = "awslogs"
+        options = {
+          awslogs-group         = aws_cloudwatch_log_group.teamcity_log_group.name
+          awslogs-region        = data.aws_region.current.name
+          awslogs-stream-prefix = "[APP]"
+        }
+      }
+
+      mountPoints = [
+        {
+          sourceVolume  = "teamcity_data",
+          containerPath = "/data/teamcity_server/datadir"
+          readOnly      = false
+        }
+      ]
+
+      # Combine the lists
+      environment = concat(local.base_env, local.password_env)
+
+      secrets = var.database_connection_string == null ? [
+        {
+          name      = "TEAMCITY_DB_PASSWORD"
+          valueFrom = "${aws_rds_cluster.teamcity_db_cluster[0].master_user_secret[0].secret_arn}:password::"
+        }
+      ] : []
+    }
+  ])
+  tags = {
+    Name = var.name
+  }
+  task_role_arn      = aws_iam_role.teamcity_default_role.arn
+  execution_role_arn = aws_iam_role.teamcity_task_execution_role.arn
+
+  volume {
+    name = "teamcity_data"
+    efs_volume_configuration {
+      file_system_id          = local.efs_file_system_id
+      transit_encryption      = "ENABLED"
+      transit_encryption_port = 2999
+      authorization_config {
+        access_point_id = local.efs_access_point_id
+        iam             = "ENABLED"
+      }
+    }
+  }
+}
+
+# ECS Service
+resource "aws_ecs_service" "teamcity" {
+  name                   = local.name_prefix
+  cluster                = var.cluster_name != null ? data.aws_ecs_cluster.teamcity_cluster[0].arn : aws_ecs_cluster.teamcity_cluster[0].arn
+  task_definition        = aws_ecs_task_definition.teamcity_task_definition.arn
+  launch_type            = "FARGATE"
+  desired_count          = var.desired_container_count
+  force_new_deployment   = var.debug
+  enable_execute_command = var.debug
+
+  wait_for_steady_state = false #TODO: make this configurable
+
+  network_configuration {
+    subnets         = var.service_subnets
+    security_groups = [aws_security_group.teamcity_service_sg.id]
+  }
+  dynamic "load_balancer" {
+    for_each = var.create_external_alb ? [1] : []
+    content {
+      target_group_arn = aws_lb_target_group.teamcity_target_group[0].arn
+      container_name   = var.container_name
+      container_port   = var.container_port
+
+    }
+  }
+
+  tags = local.tags
+}
+
+###################################
+# Security Groups                 #
+###################################
+
+# TeamCity service security group
+resource "aws_security_group" "teamcity_service_sg" {
+  name        = "${local.name_prefix}-service-sg"
+  vpc_id      = var.vpc_id
+  description = "TeamCity service security group"
+  tags        = local.tags
+}
+
+# TeamCity EFS security group
+resource "aws_security_group" "teamcity_efs_sg" {
+  count       = var.efs_id == null ? 1 : 0
+  name        = "${local.name_prefix}-efs-sg"
+  description = "TeamCity EFS mount target security group"
+  vpc_id      = var.vpc_id
+  tags        = local.tags
+}
+
+# Ingress rule for NFS traffic from service to EFS
+resource "aws_vpc_security_group_ingress_rule" "service_efs" {
+  count                        = var.efs_id == null ? 1 : 0
+  security_group_id            = aws_security_group.teamcity_efs_sg[0].id
+  referenced_security_group_id = aws_security_group.teamcity_service_sg.id
+  description                  = "Allow inbound access from TeamCity service containers to EFS"
+  ip_protocol                  = "TCP"
+  from_port                    = 2049
+  to_port                      = 2049
+}
+
+# Ingress rule for NFS traffic from EFS to service
+resource "aws_vpc_security_group_ingress_rule" "efs_service" {
+  count                        = var.efs_id == null ? 1 : 0
+  security_group_id            = aws_security_group.teamcity_service_sg.id
+  referenced_security_group_id = aws_security_group.teamcity_efs_sg[0].id
+  description                  = "Allow inbound access from EFS to TeamCity service containers"
+  ip_protocol                  = "TCP"
+  from_port                    = 2049
+  to_port                      = 2049
+}
+
+# TeamCity Aurora Serverless PostgreSQL security group
+resource "aws_security_group" "teamcity_db_sg" {
+  count       = var.database_connection_string == null ? 1 : 0
+  name        = "${local.name_prefix}-db-sg"
+  description = "TeamCity DB security group"
+  vpc_id      = var.vpc_id
+  tags        = local.tags
+}
+
+# Ingress rule for PostgreSQL from service to database cluster
+resource "aws_vpc_security_group_ingress_rule" "service_db" {
+  count                        = var.database_connection_string == null ? 1 : 0
+  security_group_id            = aws_security_group.teamcity_db_sg[0].id
+  referenced_security_group_id = aws_security_group.teamcity_service_sg.id
+  description                  = "Allow inbound access from TeamCity service containers to DB"
+  ip_protocol                  = "TCP"
+  from_port                    = 5432
+  to_port                      = 5432
+}
+
+# Ingress rule for PostgreSQL from database cluster to service
+resource "aws_vpc_security_group_ingress_rule" "db_service" {
+  count                        = var.database_connection_string == null ? 1 : 0
+  security_group_id            = aws_security_group.teamcity_service_sg.id
+  referenced_security_group_id = aws_security_group.teamcity_db_sg[0].id
+  description                  = "Allow inbound access from DB to TeamCity service containers"
+  ip_protocol                  = "TCP"
+  from_port                    = 5432
+  to_port                      = 5432
+}
+
+# Egress rule for database to let all traffic out
+resource "aws_vpc_security_group_egress_rule" "db_outbound" {
+  count             = var.database_connection_string == null ? 1 : 0
+  security_group_id = aws_security_group.teamcity_db_sg[0].id
+  description       = "Allow outbound access from DB to all"
+  cidr_ipv4         = "0.0.0.0/0"
+  ip_protocol       = "-1"
+}
+
+# TeamCity ALB security group
+resource "aws_security_group" "teamcity_alb_sg" {
+  #checkov:skip=CKV2_AWS_5:SG is attached to TeamCity service ALB
+  count       = var.create_external_alb ? 1 : 0
+  name        = "${local.name_prefix}-alb-sg"
+  vpc_id      = var.vpc_id
+  description = "TeamCity ALB security group"
+  tags        = local.tags
+}
+
+# Ingress rule for HTTP traffic from ALB to service
+resource "aws_vpc_security_group_ingress_rule" "service_inbound_alb" {
+  count                        = var.create_external_alb ? 1 : 0
+  security_group_id            = aws_security_group.teamcity_service_sg.id
+  referenced_security_group_id = aws_security_group.teamcity_alb_sg[0].id
+  description                  = "Allow inbound HTTP traffic from ALB to service containers"
+  from_port                    = var.container_port
+  to_port                      = var.container_port
+  ip_protocol                  = "TCP"
+}
+
+# Egress rule for HTTP traffic from ALB to service
+resource "aws_vpc_security_group_egress_rule" "service_outbound_alb" {
+  count                        = var.create_external_alb ? 1 : 0
+  security_group_id            = aws_security_group.teamcity_alb_sg[0].id
+  referenced_security_group_id = aws_security_group.teamcity_service_sg.id
+  description                  = "Allow outbound HTTP traffic from ALB to service containers"
+  from_port                    = var.container_port
+  to_port                      = var.container_port
+  ip_protocol                  = "TCP"
+}
+
+# Grant TeamCity service access to internet
+resource "aws_vpc_security_group_egress_rule" "internet_outbound" {
+  security_group_id = aws_security_group.teamcity_service_sg.id
+  description       = "Allow outbound internet access from TeamCity service containers"
+  cidr_ipv4         = "0.0.0.0/0"
+  ip_protocol       = "-1"
+}
+
+#############################################
+# IAM Roles for TeamCity Module
+#############################################
+
+data "aws_iam_policy_document" "ecs_tasks_trust_relationship" {
+  statement {
+    effect  = "Allow"
+    actions = ["sts:AssumeRole"]
+    principals {
+      type        = "Service"
+      identifiers = ["ecs-tasks.amazonaws.com"]
+    }
+  }
+}
+
+data "aws_iam_policy_document" "teamcity_default_policy" {
+  # ECS
+  statement {
+    sid    = "ECSExec"
+    effect = "Allow"
+    actions = [
+      "ssmmessages:OpenDataChannel",
+      "ssmmessages:OpenControlChannel",
+      "ssmmessages:CreateDataChannel",
+      "ssmmessages:CreateControlChannel",
+    ]
+    resources = [
+      "*"
+    ]
+  }
+
+  #grant the task necessary EFS permissions to be able to modify directories
+  statement {
+    sid    = "EFS"
+    effect = "Allow"
+    actions = [
+      "elasticfilesystem:ClientWrite",
+      "elasticfilesystem:ClientRootAccess",
+      "elasticfilesystem:ClientMount"
+    ]
+    resources = [
+      local.efs_file_system_arn
+    ]
+  }
+}
path: modules/teamcity/main.tf
user: login: cwwalb
body: yes it does

created_at: 2025-03-11T19:57:53Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,595 @@
+###################################
+# ECS Cluster for TeamCity Server #
+###################################
+# If cluster name is provided use a data source to access existing resource
+data "aws_ecs_cluster" "teamcity_cluster" {
+  count        = var.cluster_name != null ? 1 : 0
+  cluster_name = var.cluster_name
+}
+resource "aws_ecs_cluster" "teamcity_cluster" {
+  count = var.cluster_name != null ? 0 : 1
+  name  = "${local.name_prefix}-cluster"
+
+  setting {
+    name  = "containerInsights"
+    value = "enabled"
+  }
+
+  tags = local.tags
+}
+
+# TeamCity Task Definition
+resource "aws_ecs_task_definition" "teamcity_task_definition" {
+  family                   = var.name
+  requires_compatibilities = ["FARGATE"]
+  network_mode             = "awsvpc"
+  cpu                      = var.container_cpu
+  memory                   = var.container_memory
+
+  container_definitions = jsonencode([
+    {
+      name      = var.container_name
+      image     = local.image
+      cpu       = var.container_cpu
+      memory    = var.container_memory
+      essential = true
+
+      portMappings = [
+        {
+          containerPort = var.container_port
+          hostPort      = var.container_port
+        }
+      ]
+      logConfiguration = {
+        logDriver = "awslogs"
+        options = {
+          awslogs-group         = aws_cloudwatch_log_group.teamcity_log_group.name
+          awslogs-region        = data.aws_region.current.name
+          awslogs-stream-prefix = "[APP]"
+        }
+      }
+
+      mountPoints = [
+        {
+          sourceVolume  = "teamcity_data",
+          containerPath = "/data/teamcity_server/datadir"
+          readOnly      = false
+        }
+      ]
+
+      # Combine the lists
+      environment = concat(local.base_env, local.password_env)
+
+      secrets = var.database_connection_string == null ? [
+        {
+          name      = "TEAMCITY_DB_PASSWORD"
+          valueFrom = "${aws_rds_cluster.teamcity_db_cluster[0].master_user_secret[0].secret_arn}:password::"
+        }
+      ] : []
+    }
+  ])
+  tags = {
+    Name = var.name
+  }
+  task_role_arn      = aws_iam_role.teamcity_default_role.arn
+  execution_role_arn = aws_iam_role.teamcity_task_execution_role.arn
+
+  volume {
+    name = "teamcity_data"
+    efs_volume_configuration {
+      file_system_id          = local.efs_file_system_id
+      transit_encryption      = "ENABLED"
+      transit_encryption_port = 2999
+      authorization_config {
+        access_point_id = local.efs_access_point_id
+        iam             = "ENABLED"
+      }
+    }
+  }
+}
+
+# ECS Service
+resource "aws_ecs_service" "teamcity" {
+  name                   = local.name_prefix
+  cluster                = var.cluster_name != null ? data.aws_ecs_cluster.teamcity_cluster[0].arn : aws_ecs_cluster.teamcity_cluster[0].arn
+  task_definition        = aws_ecs_task_definition.teamcity_task_definition.arn
+  launch_type            = "FARGATE"
+  desired_count          = var.desired_container_count
+  force_new_deployment   = var.debug
+  enable_execute_command = var.debug
+
+  wait_for_steady_state = false #TODO: make this configurable
+
+  network_configuration {
+    subnets         = var.service_subnets
+    security_groups = [aws_security_group.teamcity_service_sg.id]
+  }
+  dynamic "load_balancer" {
+    for_each = var.create_external_alb ? [1] : []
+    content {
+      target_group_arn = aws_lb_target_group.teamcity_target_group[0].arn
+      container_name   = var.container_name
+      container_port   = var.container_port
+
+    }
+  }
+
+  tags = local.tags
+}
+
+###################################
+# Security Groups                 #
+###################################
+
+# TeamCity service security group
+resource "aws_security_group" "teamcity_service_sg" {
+  name        = "${local.name_prefix}-service-sg"
+  vpc_id      = var.vpc_id
+  description = "TeamCity service security group"
+  tags        = local.tags
+}
+
+# TeamCity EFS security group
+resource "aws_security_group" "teamcity_efs_sg" {
+  count       = var.efs_id == null ? 1 : 0
+  name        = "${local.name_prefix}-efs-sg"
+  description = "TeamCity EFS mount target security group"
+  vpc_id      = var.vpc_id
+  tags        = local.tags
+}
+
+# Ingress rule for NFS traffic from service to EFS
+resource "aws_vpc_security_group_ingress_rule" "service_efs" {
+  count                        = var.efs_id == null ? 1 : 0
+  security_group_id            = aws_security_group.teamcity_efs_sg[0].id
+  referenced_security_group_id = aws_security_group.teamcity_service_sg.id
+  description                  = "Allow inbound access from TeamCity service containers to EFS"
+  ip_protocol                  = "TCP"
+  from_port                    = 2049
+  to_port                      = 2049
+}
+
+# Ingress rule for NFS traffic from EFS to service
+resource "aws_vpc_security_group_ingress_rule" "efs_service" {
+  count                        = var.efs_id == null ? 1 : 0
+  security_group_id            = aws_security_group.teamcity_service_sg.id
+  referenced_security_group_id = aws_security_group.teamcity_efs_sg[0].id
+  description                  = "Allow inbound access from EFS to TeamCity service containers"
+  ip_protocol                  = "TCP"
+  from_port                    = 2049
+  to_port                      = 2049
+}
+
+# TeamCity Aurora Serverless PostgreSQL security group
+resource "aws_security_group" "teamcity_db_sg" {
+  count       = var.database_connection_string == null ? 1 : 0
+  name        = "${local.name_prefix}-db-sg"
+  description = "TeamCity DB security group"
+  vpc_id      = var.vpc_id
+  tags        = local.tags
+}
+
+# Ingress rule for PostgreSQL from service to database cluster
+resource "aws_vpc_security_group_ingress_rule" "service_db" {
+  count                        = var.database_connection_string == null ? 1 : 0
+  security_group_id            = aws_security_group.teamcity_db_sg[0].id
+  referenced_security_group_id = aws_security_group.teamcity_service_sg.id
+  description                  = "Allow inbound access from TeamCity service containers to DB"
+  ip_protocol                  = "TCP"
+  from_port                    = 5432
+  to_port                      = 5432
+}
+
+# Ingress rule for PostgreSQL from database cluster to service
+resource "aws_vpc_security_group_ingress_rule" "db_service" {
+  count                        = var.database_connection_string == null ? 1 : 0
+  security_group_id            = aws_security_group.teamcity_service_sg.id
+  referenced_security_group_id = aws_security_group.teamcity_db_sg[0].id
+  description                  = "Allow inbound access from DB to TeamCity service containers"
+  ip_protocol                  = "TCP"
+  from_port                    = 5432
+  to_port                      = 5432
+}
+
+# Egress rule for database to let all traffic out
+resource "aws_vpc_security_group_egress_rule" "db_outbound" {
+  count             = var.database_connection_string == null ? 1 : 0
+  security_group_id = aws_security_group.teamcity_db_sg[0].id
+  description       = "Allow outbound access from DB to all"
+  cidr_ipv4         = "0.0.0.0/0"
+  ip_protocol       = "-1"
+}
path: modules/teamcity/main.tf
user: login: cwwalb
body: I see just confusion on when egress vs ingress rules were needed on my end
created_at: 2025-03-11T22:04:11Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,595 @@
+###################################
+# ECS Cluster for TeamCity Server #
+###################################
+# If cluster name is provided use a data source to access existing resource
+data "aws_ecs_cluster" "teamcity_cluster" {
+  count        = var.cluster_name != null ? 1 : 0
+  cluster_name = var.cluster_name
+}
+resource "aws_ecs_cluster" "teamcity_cluster" {
+  count = var.cluster_name != null ? 0 : 1
+  name  = "${local.name_prefix}-cluster"
+
+  setting {
+    name  = "containerInsights"
+    value = "enabled"
+  }
+
+  tags = local.tags
+}
+
+# TeamCity Task Definition
+resource "aws_ecs_task_definition" "teamcity_task_definition" {
+  family                   = var.name
+  requires_compatibilities = ["FARGATE"]
+  network_mode             = "awsvpc"
+  cpu                      = var.container_cpu
+  memory                   = var.container_memory
+
+  container_definitions = jsonencode([
+    {
+      name      = var.container_name
+      image     = local.image
+      cpu       = var.container_cpu
+      memory    = var.container_memory
+      essential = true
+
+      portMappings = [
+        {
+          containerPort = var.container_port
+          hostPort      = var.container_port
+        }
+      ]
+      logConfiguration = {
+        logDriver = "awslogs"
+        options = {
+          awslogs-group         = aws_cloudwatch_log_group.teamcity_log_group.name
+          awslogs-region        = data.aws_region.current.name
+          awslogs-stream-prefix = "[APP]"
+        }
+      }
+
+      mountPoints = [
+        {
+          sourceVolume  = "teamcity_data",
+          containerPath = "/data/teamcity_server/datadir"
+          readOnly      = false
+        }
+      ]
+
+      # Combine the lists
+      environment = concat(local.base_env, local.password_env)
+
+      secrets = var.database_connection_string == null ? [
+        {
+          name      = "TEAMCITY_DB_PASSWORD"
+          valueFrom = "${aws_rds_cluster.teamcity_db_cluster[0].master_user_secret[0].secret_arn}:password::"
+        }
+      ] : []
+    }
+  ])
+  tags = {
+    Name = var.name
+  }
+  task_role_arn      = aws_iam_role.teamcity_default_role.arn
+  execution_role_arn = aws_iam_role.teamcity_task_execution_role.arn
+
+  volume {
+    name = "teamcity_data"
+    efs_volume_configuration {
+      file_system_id          = local.efs_file_system_id
+      transit_encryption      = "ENABLED"
+      transit_encryption_port = 2999
+      authorization_config {
+        access_point_id = local.efs_access_point_id
+        iam             = "ENABLED"
+      }
+    }
+  }
+}
+
+# ECS Service
+resource "aws_ecs_service" "teamcity" {
+  name                   = local.name_prefix
+  cluster                = var.cluster_name != null ? data.aws_ecs_cluster.teamcity_cluster[0].arn : aws_ecs_cluster.teamcity_cluster[0].arn
+  task_definition        = aws_ecs_task_definition.teamcity_task_definition.arn
+  launch_type            = "FARGATE"
+  desired_count          = var.desired_container_count
+  force_new_deployment   = var.debug
+  enable_execute_command = var.debug
+
+  wait_for_steady_state = false #TODO: make this configurable
+
+  network_configuration {
+    subnets         = var.service_subnets
+    security_groups = [aws_security_group.teamcity_service_sg.id]
+  }
+  dynamic "load_balancer" {
+    for_each = var.create_external_alb ? [1] : []
+    content {
+      target_group_arn = aws_lb_target_group.teamcity_target_group[0].arn
+      container_name   = var.container_name
+      container_port   = var.container_port
+
+    }
+  }
+
+  tags = local.tags
+}
+
+###################################
+# Security Groups                 #
+###################################
+
+# TeamCity service security group
+resource "aws_security_group" "teamcity_service_sg" {
+  name        = "${local.name_prefix}-service-sg"
+  vpc_id      = var.vpc_id
+  description = "TeamCity service security group"
+  tags        = local.tags
+}
+
+# TeamCity EFS security group
+resource "aws_security_group" "teamcity_efs_sg" {
+  count       = var.efs_id == null ? 1 : 0
+  name        = "${local.name_prefix}-efs-sg"
+  description = "TeamCity EFS mount target security group"
+  vpc_id      = var.vpc_id
+  tags        = local.tags
+}
+
+# Ingress rule for NFS traffic from service to EFS
+resource "aws_vpc_security_group_ingress_rule" "service_efs" {
+  count                        = var.efs_id == null ? 1 : 0
+  security_group_id            = aws_security_group.teamcity_efs_sg[0].id
+  referenced_security_group_id = aws_security_group.teamcity_service_sg.id
+  description                  = "Allow inbound access from TeamCity service containers to EFS"
+  ip_protocol                  = "TCP"
+  from_port                    = 2049
+  to_port                      = 2049
+}
+
+# Ingress rule for NFS traffic from EFS to service
+resource "aws_vpc_security_group_ingress_rule" "efs_service" {
+  count                        = var.efs_id == null ? 1 : 0
+  security_group_id            = aws_security_group.teamcity_service_sg.id
+  referenced_security_group_id = aws_security_group.teamcity_efs_sg[0].id
+  description                  = "Allow inbound access from EFS to TeamCity service containers"
+  ip_protocol                  = "TCP"
+  from_port                    = 2049
+  to_port                      = 2049
+}
+
+# TeamCity Aurora Serverless PostgreSQL security group
+resource "aws_security_group" "teamcity_db_sg" {
+  count       = var.database_connection_string == null ? 1 : 0
+  name        = "${local.name_prefix}-db-sg"
+  description = "TeamCity DB security group"
+  vpc_id      = var.vpc_id
+  tags        = local.tags
+}
+
+# Ingress rule for PostgreSQL from service to database cluster
+resource "aws_vpc_security_group_ingress_rule" "service_db" {
+  count                        = var.database_connection_string == null ? 1 : 0
+  security_group_id            = aws_security_group.teamcity_db_sg[0].id
+  referenced_security_group_id = aws_security_group.teamcity_service_sg.id
+  description                  = "Allow inbound access from TeamCity service containers to DB"
+  ip_protocol                  = "TCP"
+  from_port                    = 5432
+  to_port                      = 5432
+}
+
+# Ingress rule for PostgreSQL from database cluster to service
+resource "aws_vpc_security_group_ingress_rule" "db_service" {
+  count                        = var.database_connection_string == null ? 1 : 0
+  security_group_id            = aws_security_group.teamcity_service_sg.id
+  referenced_security_group_id = aws_security_group.teamcity_db_sg[0].id
+  description                  = "Allow inbound access from DB to TeamCity service containers"
+  ip_protocol                  = "TCP"
+  from_port                    = 5432
+  to_port                      = 5432
+}
path: modules/teamcity/main.tf
user: login: cwwalb
body: I actually think it is necessary. When I remove it the connection times out and I get a Postgres database unreachable error or a password auth error, but when I put the security group rule back it works. I think its needed as part of the initial connection
created_at: 2025-03-11T22:39:58Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,595 @@
+###################################
+# ECS Cluster for TeamCity Server #
+###################################
+# If cluster name is provided use a data source to access existing resource
+data "aws_ecs_cluster" "teamcity_cluster" {
+  count        = var.cluster_name != null ? 1 : 0
+  cluster_name = var.cluster_name
+}
+resource "aws_ecs_cluster" "teamcity_cluster" {
+  count = var.cluster_name != null ? 0 : 1
+  name  = "${local.name_prefix}-cluster"
+
+  setting {
+    name  = "containerInsights"
+    value = "enabled"
+  }
+
+  tags = local.tags
+}
+
+# TeamCity Task Definition
+resource "aws_ecs_task_definition" "teamcity_task_definition" {
+  family                   = var.name
+  requires_compatibilities = ["FARGATE"]
+  network_mode             = "awsvpc"
+  cpu                      = var.container_cpu
+  memory                   = var.container_memory
+
+  container_definitions = jsonencode([
+    {
+      name      = var.container_name
+      image     = local.image
+      cpu       = var.container_cpu
+      memory    = var.container_memory
+      essential = true
+
+      portMappings = [
+        {
+          containerPort = var.container_port
+          hostPort      = var.container_port
+        }
+      ]
+      logConfiguration = {
+        logDriver = "awslogs"
+        options = {
+          awslogs-group         = aws_cloudwatch_log_group.teamcity_log_group.name
+          awslogs-region        = data.aws_region.current.name
+          awslogs-stream-prefix = "[APP]"
+        }
+      }
+
+      mountPoints = [
+        {
+          sourceVolume  = "teamcity_data",
+          containerPath = "/data/teamcity_server/datadir"
+          readOnly      = false
+        }
+      ]
+
+      # Combine the lists
+      environment = concat(local.base_env, local.password_env)
+
+      secrets = var.database_connection_string == null ? [
+        {
+          name      = "TEAMCITY_DB_PASSWORD"
+          valueFrom = "${aws_rds_cluster.teamcity_db_cluster[0].master_user_secret[0].secret_arn}:password::"
+        }
+      ] : []
+    }
+  ])
+  tags = {
+    Name = var.name
+  }
+  task_role_arn      = aws_iam_role.teamcity_default_role.arn
+  execution_role_arn = aws_iam_role.teamcity_task_execution_role.arn
+
+  volume {
+    name = "teamcity_data"
+    efs_volume_configuration {
+      file_system_id          = local.efs_file_system_id
+      transit_encryption      = "ENABLED"
+      transit_encryption_port = 2999
+      authorization_config {
+        access_point_id = local.efs_access_point_id
+        iam             = "ENABLED"
+      }
+    }
+  }
+}
+
+# ECS Service
+resource "aws_ecs_service" "teamcity" {
+  name                   = local.name_prefix
+  cluster                = var.cluster_name != null ? data.aws_ecs_cluster.teamcity_cluster[0].arn : aws_ecs_cluster.teamcity_cluster[0].arn
+  task_definition        = aws_ecs_task_definition.teamcity_task_definition.arn
+  launch_type            = "FARGATE"
+  desired_count          = var.desired_container_count
+  force_new_deployment   = var.debug
+  enable_execute_command = var.debug
+
+  wait_for_steady_state = false #TODO: make this configurable
+
+  network_configuration {
+    subnets         = var.service_subnets
+    security_groups = [aws_security_group.teamcity_service_sg.id]
+  }
+  dynamic "load_balancer" {
+    for_each = var.create_external_alb ? [1] : []
+    content {
+      target_group_arn = aws_lb_target_group.teamcity_target_group[0].arn
+      container_name   = var.container_name
+      container_port   = var.container_port
+
+    }
+  }
+
+  tags = local.tags
+}
+
+###################################
+# Security Groups                 #
+###################################
+
+# TeamCity service security group
+resource "aws_security_group" "teamcity_service_sg" {
+  name        = "${local.name_prefix}-service-sg"
+  vpc_id      = var.vpc_id
+  description = "TeamCity service security group"
+  tags        = local.tags
+}
+
+# TeamCity EFS security group
+resource "aws_security_group" "teamcity_efs_sg" {
+  count       = var.efs_id == null ? 1 : 0
+  name        = "${local.name_prefix}-efs-sg"
+  description = "TeamCity EFS mount target security group"
+  vpc_id      = var.vpc_id
+  tags        = local.tags
+}
+
+# Ingress rule for NFS traffic from service to EFS
+resource "aws_vpc_security_group_ingress_rule" "service_efs" {
+  count                        = var.efs_id == null ? 1 : 0
+  security_group_id            = aws_security_group.teamcity_efs_sg[0].id
+  referenced_security_group_id = aws_security_group.teamcity_service_sg.id
+  description                  = "Allow inbound access from TeamCity service containers to EFS"
+  ip_protocol                  = "TCP"
+  from_port                    = 2049
+  to_port                      = 2049
+}
+
+# Ingress rule for NFS traffic from EFS to service
+resource "aws_vpc_security_group_ingress_rule" "efs_service" {
+  count                        = var.efs_id == null ? 1 : 0
+  security_group_id            = aws_security_group.teamcity_service_sg.id
+  referenced_security_group_id = aws_security_group.teamcity_efs_sg[0].id
+  description                  = "Allow inbound access from EFS to TeamCity service containers"
+  ip_protocol                  = "TCP"
+  from_port                    = 2049
+  to_port                      = 2049
+}
+
+# TeamCity Aurora Serverless PostgreSQL security group
+resource "aws_security_group" "teamcity_db_sg" {
+  count       = var.database_connection_string == null ? 1 : 0
+  name        = "${local.name_prefix}-db-sg"
+  description = "TeamCity DB security group"
+  vpc_id      = var.vpc_id
+  tags        = local.tags
+}
+
+# Ingress rule for PostgreSQL from service to database cluster
+resource "aws_vpc_security_group_ingress_rule" "service_db" {
+  count                        = var.database_connection_string == null ? 1 : 0
+  security_group_id            = aws_security_group.teamcity_db_sg[0].id
+  referenced_security_group_id = aws_security_group.teamcity_service_sg.id
+  description                  = "Allow inbound access from TeamCity service containers to DB"
+  ip_protocol                  = "TCP"
+  from_port                    = 5432
+  to_port                      = 5432
+}
+
+# Ingress rule for PostgreSQL from database cluster to service
+resource "aws_vpc_security_group_ingress_rule" "db_service" {
+  count                        = var.database_connection_string == null ? 1 : 0
+  security_group_id            = aws_security_group.teamcity_service_sg.id
+  referenced_security_group_id = aws_security_group.teamcity_db_sg[0].id
+  description                  = "Allow inbound access from DB to TeamCity service containers"
+  ip_protocol                  = "TCP"
+  from_port                    = 5432
+  to_port                      = 5432
+}
+
+# Egress rule for database to let all traffic out
+resource "aws_vpc_security_group_egress_rule" "db_outbound" {
+  count             = var.database_connection_string == null ? 1 : 0
+  security_group_id = aws_security_group.teamcity_db_sg[0].id
+  description       = "Allow outbound access from DB to all"
+  cidr_ipv4         = "0.0.0.0/0"
+  ip_protocol       = "-1"
+}
+
+# TeamCity ALB security group
+resource "aws_security_group" "teamcity_alb_sg" {
+  #checkov:skip=CKV2_AWS_5:SG is attached to TeamCity service ALB
+  count       = var.create_external_alb ? 1 : 0
+  name        = "${local.name_prefix}-alb-sg"
+  vpc_id      = var.vpc_id
+  description = "TeamCity ALB security group"
+  tags        = local.tags
+}
+
+# Ingress rule for HTTP traffic from ALB to service
+resource "aws_vpc_security_group_ingress_rule" "service_inbound_alb" {
+  count                        = var.create_external_alb ? 1 : 0
+  security_group_id            = aws_security_group.teamcity_service_sg.id
+  referenced_security_group_id = aws_security_group.teamcity_alb_sg[0].id
+  description                  = "Allow inbound HTTP traffic from ALB to service containers"
+  from_port                    = var.container_port
+  to_port                      = var.container_port
+  ip_protocol                  = "TCP"
+}
+
+# Egress rule for HTTP traffic from ALB to service
+resource "aws_vpc_security_group_egress_rule" "service_outbound_alb" {
+  count                        = var.create_external_alb ? 1 : 0
+  security_group_id            = aws_security_group.teamcity_alb_sg[0].id
+  referenced_security_group_id = aws_security_group.teamcity_service_sg.id
+  description                  = "Allow outbound HTTP traffic from ALB to service containers"
+  from_port                    = var.container_port
+  to_port                      = var.container_port
+  ip_protocol                  = "TCP"
+}
+
+# Grant TeamCity service access to internet
+resource "aws_vpc_security_group_egress_rule" "internet_outbound" {
+  security_group_id = aws_security_group.teamcity_service_sg.id
+  description       = "Allow outbound internet access from TeamCity service containers"
+  cidr_ipv4         = "0.0.0.0/0"
+  ip_protocol       = "-1"
+}
+
+#############################################
+# IAM Roles for TeamCity Module
+#############################################
+
+data "aws_iam_policy_document" "ecs_tasks_trust_relationship" {
+  statement {
+    effect  = "Allow"
+    actions = ["sts:AssumeRole"]
+    principals {
+      type        = "Service"
+      identifiers = ["ecs-tasks.amazonaws.com"]
+    }
+  }
+}
+
+data "aws_iam_policy_document" "teamcity_default_policy" {
+  # ECS
+  statement {
+    sid    = "ECSExec"
+    effect = "Allow"
+    actions = [
+      "ssmmessages:OpenDataChannel",
+      "ssmmessages:OpenControlChannel",
+      "ssmmessages:CreateDataChannel",
+      "ssmmessages:CreateControlChannel",
+    ]
+    resources = [
+      "*"
+    ]
+  }
+
+  #grant the task necessary EFS permissions to be able to modify directories
+  statement {
+    sid    = "EFS"
+    effect = "Allow"
+    actions = [
+      "elasticfilesystem:ClientWrite",
+      "elasticfilesystem:ClientRootAccess",
+      "elasticfilesystem:ClientMount"
+    ]
+    resources = [
+      local.efs_file_system_arn
+    ]
+  }
+}
+
+resource "aws_iam_policy" "teamcity_default_policy" {
+  name        = "teamcity-default-policy"
+  description = "Policy granting permissions for TeamCity."
+  policy      = data.aws_iam_policy_document.teamcity_default_policy.json
+}
+
+resource "aws_iam_role" "teamcity_default_role" {
+  name               = "teamcity-default-role"
+  description        = "Default role for TeamCity ECS task."
+  assume_role_policy = data.aws_iam_policy_document.ecs_tasks_trust_relationship.json
+  managed_policy_arns = [
+    aws_iam_policy.teamcity_default_policy.arn
+  ]
+
+  tags = local.tags
+}
+resource "aws_iam_role" "teamcity_task_execution_role" {
+  name = "teamcity-task-execution-role"
+
+  assume_role_policy  = data.aws_iam_policy_document.ecs_tasks_trust_relationship.json
+  managed_policy_arns = ["arn:aws:iam::aws:policy/service-role/AmazonECSTaskExecutionRolePolicy"]
+
+  dynamic "inline_policy" {
+    for_each = var.database_connection_string == null ? [1] : []
+    content {
+      name = "teamcity-secrets-access"
+      policy = jsonencode({
+        Version = "2012-10-17"
+        Statement = [
+          {
+            Sid    = "SecretsManager"
+            Effect = "Allow"
+            Action = [
+              "secretsmanager:GetSecretValue"
+            ]
+            Resource = [
+              aws_rds_cluster.teamcity_db_cluster[0].master_user_secret[0].secret_arn
+            ]
+          }
+        ]
+      })
+    }
+  }
+
+}
+
+# CloudWatch Logs
+resource "aws_cloudwatch_log_group" "teamcity_log_group" {
+  #checkov:skip=CKV_AWS_158: KMS Encryption disabled by default
+  name              = "${local.name_prefix}-log-group"
+  retention_in_days = var.teamcity_cloudwatch_log_retention_in_days
+  tags              = local.tags
+}
+
+# Load Balancer for TeamCity Service
+resource "aws_lb" "teamcity_external_lb" {
+  count              = var.create_external_alb ? 1 : 0
+  name               = "${local.name_prefix}-lb"
+  security_groups    = [aws_security_group.teamcity_alb_sg[0].id]
+  load_balancer_type = "application"
+  internal           = false
+  subnets            = var.alb_subnets
+
+
+  dynamic "access_logs" {
+    for_each = var.enable_teamcity_alb_access_logs ? [1] : []
+    content {
+      enabled = var.enable_teamcity_alb_access_logs
+      bucket  = var.teamcity_alb_access_logs_bucket != null ? var.teamcity_alb_access_logs_bucket : aws_s3_bucket.teamcity_alb_access_logs_bucket[0].id
+      prefix  = var.teamcity_alb_access_logs_prefix != null ? var.teamcity_alb_access_logs_prefix : "${local.name_prefix}-alb"
+    }
+  }
+  #checkov:skip=CKV_AWS_150:Deletion protection disabled by default
+  enable_deletion_protection = var.enable_teamcity_alb_deletion_protection
+
+  #checkov:skip=CKV2_AWS_28: ALB access is managed with SG allow listing
+
+  drop_invalid_header_fields = true
+  tags                       = local.tags
+}
+
+# TeamCity target group for ALB
+resource "aws_lb_target_group" "teamcity_target_group" {
+  #checkov:skip=CKV_AWS_378: Using ALB for TLS termination
+  count       = var.create_external_alb ? 1 : 0
+  name        = "${local.name_prefix}-tg"
+  port        = var.container_port
+  protocol    = "HTTP"
+  vpc_id      = var.vpc_id
+  target_type = "ip"
+
+  health_check {
+    path                = "/healthCheck/healthy"
+    interval            = 30
+    timeout             = 5
+    healthy_threshold   = 5
+    unhealthy_threshold = 2
+    port                = var.container_port
+    protocol            = "HTTP"
+    matcher             = "200"
+  }
+
+  tags = local.tags
+}
+
+# ALB HTTPS Listener
+resource "aws_lb_listener" "teamcity_listener" {
+  count             = var.create_external_alb ? 1 : 0
+  load_balancer_arn = aws_lb.teamcity_external_lb[0].arn
+  port              = "443"
+  protocol          = "HTTPS"
+  ssl_policy        = "ELBSecurityPolicy-TLS13-1-2-2021-06"
+  certificate_arn   = var.alb_certificate_arn
+  default_action {
+    type             = "forward"
+    target_group_arn = aws_lb_target_group.teamcity_target_group[0].arn
+  }
+  tags = local.tags
+}
+
+# #######################################
+# # TeamCity Aurora Serverless Database #
+# #######################################
+# Subnet group
+resource "aws_db_subnet_group" "teamcity_db_subnet_group" {
+  count      = var.database_connection_string == null ? 1 : 0
+  name       = "${local.name_prefix}-db-subnet-group"
+  subnet_ids = var.service_subnets
+  tags       = local.tags
+}
+
+# # RDS instance with Aurora serverless engine
+resource "aws_rds_cluster" "teamcity_db_cluster" {
+  #checkov:skip=CKV2_AWS_27:not enabling query logging by design
+  #checkov:skip=CKV2_AWS_8: TODO: add rds backup plan
+  count                       = var.database_connection_string == null ? 1 : 0
+  cluster_identifier          = "teamcity-cluster"
+  engine                      = "aurora-postgresql"
+  engine_mode                 = "provisioned"
+  engine_version              = "16.6" #check for latest as option
+  database_name               = "teamcity"
+  master_username             = "teamcity"
+  manage_master_user_password = true #using AWS Secrets Manager
+  storage_encrypted           = true
+  skip_final_snapshot         = var.aurora_skip_final_snapshot
+  db_subnet_group_name        = aws_db_subnet_group.teamcity_db_subnet_group[0].id
+  vpc_security_group_ids = [
+    aws_security_group.teamcity_db_sg[0].id
+  ]
+
+  serverlessv2_scaling_configuration {
+    max_capacity             = 1.0
+    min_capacity             = 0.0
+    seconds_until_auto_pause = 3600
+  }
+}
+
+resource "aws_rds_cluster_instance" "teamcity_db_cluster_instance" {
+  count              = var.database_connection_string == null ? var.aurora_instance_count : 0
+  cluster_identifier = aws_rds_cluster.teamcity_db_cluster[0].id
+  instance_class     = "db.serverless"
+  engine             = aws_rds_cluster.teamcity_db_cluster[0].engine
+  engine_version     = aws_rds_cluster.teamcity_db_cluster[0].engine_version
+}
+
+# ################
+# # TeamCity EFS #
+# ################
+
+# File system for teamcity
+resource "aws_efs_file_system" "teamcity_efs_file_system" {
+  count            = var.efs_id != null ? 0 : 1
+  creation_token   = "${local.name_prefix}-efs-file-system"
+  performance_mode = var.teamcity_efs_performance_mode
+  throughput_mode  = var.teamcity_efs_throughput_mode
+
+  #TODO: Parameterize encryption and customer managed key creation
+  encrypted = false
path: modules/teamcity/main.tf
user: login: cwwalb
body: Actually scratch that its working and parameterized
created_at: 2025-03-12T20:07:43Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,10 @@
+data "aws_availability_zones" "available" {}
+
+locals {
path: modules/teamcity/examples/simple/local.tf
user: login: novekm
body: We may want to consider abstracting this away to a data source or something in parameter store eventually. Ideally when we integrate with Terraform Test for automated tests, we should always reference the same VPC that will be in whichever AWS account we use for pipeline tests (values stored in SSM Parameter store or something). 

This will also help ensure customers don't run into issue if deploying quickly using two examples (e.g. TeamCity and Perforce) with overlapping IP address space. I think both examples currently use the same range.
created_at: 2025-03-12T20:45:51Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,7 @@
+module "teamcity" {
+  source              = "../../"
path: modules/teamcity/examples/simple/main.tf
user: login: novekm
body: question for later - as we scale do we want to keep all of these TF modules local to the context of the repo, or do we want to start hosting these in the Terraform Registry and referencing that source? E.g. `source = aws-games/teamcity/aws`
created_at: 2025-03-12T20:47:56Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,10 @@
+terraform {
+  required_version = ">= 1.0"
path: modules/teamcity/examples/simple/versions.tf
user: login: novekm
body: We should determine how/if we want to globally standardize and set these vales (Terraform version, AWS/AWSCC Provider version, etc.) and validate for all modules in the toolkit. Is there something in `v1.9` that may cause a breaking change and that's why you want to test that? Related, what is the oldest TF version we want to support? Open to discussion.
created_at: 2025-03-12T20:51:56Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,123 @@
+resource "aws_vpc" "teamcity_vpc" {
+  cidr_block = local.vpc_cidr_block
+  tags = merge(local.tags,
+    {
+      Name = "teamcity-vpc"
+    }
+  )
+  enable_dns_hostnames = true
+  #checkov:skip=CKV2_AWS_11: VPC flow logging disabled by design
+}
+
+# Set default SG to restrict all traffic
+resource "aws_default_security_group" "default" {
+  vpc_id = aws_vpc.teamcity_vpc.id
+}
+
+resource "aws_subnet" "public_subnets" {
+  count             = length(local.public_subnet_cidrs)
+  vpc_id            = aws_vpc.teamcity_vpc.id
+  cidr_block        = element(local.public_subnet_cidrs, count.index)
+  availability_zone = element(local.azs, count.index)
+
+  tags = merge(local.tags,
+    {
+      Name = "pub-subnet-${count.index + 1}"
+    }
+  )
+}
+
+resource "aws_subnet" "private_subnets" {
+  count             = length(local.private_subnet_cidrs)
+  vpc_id            = aws_vpc.teamcity_vpc.id
+  cidr_block        = element(local.private_subnet_cidrs, count.index)
+  availability_zone = element(local.azs, count.index)
+
+  tags = merge(local.tags,
+    {
+      Name = "pvt-subnet-${count.index + 1}"
+    }
+  )
+}
+
+##########################################
+# Internet Gateway
+##########################################
+
+resource "aws_internet_gateway" "igw" {
+  vpc_id = aws_vpc.teamcity_vpc.id
+  tags = merge(local.tags,
+    {
+      Name = "teamcity-igw"
+    }
+  )
+}
+
+##########################################
+# Route Tables & NAT Gateway
+##########################################
+
+resource "aws_route_table" "public_rt" {
+  vpc_id = aws_vpc.teamcity_vpc.id
+
+  # public route to the internet
+  route {
+    cidr_block = "0.0.0.0/0"
+    gateway_id = aws_internet_gateway.igw.id
+  }
+
+  tags = merge(local.tags,
+    {
+      Name = "teamcity-public-rt"
+    }
+  )
+}
+
+resource "aws_route_table_association" "public_rt_asso" {
path: modules/teamcity/examples/simple/vpc.tf
user: login: novekm
body: the "o" saved this resource naming 😂
created_at: 2025-03-12T20:54:06Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,123 @@
+resource "aws_vpc" "teamcity_vpc" {
+  cidr_block = local.vpc_cidr_block
+  tags = merge(local.tags,
+    {
+      Name = "teamcity-vpc"
+    }
+  )
+  enable_dns_hostnames = true
+  #checkov:skip=CKV2_AWS_11: VPC flow logging disabled by design
+}
+
+# Set default SG to restrict all traffic
+resource "aws_default_security_group" "default" {
+  vpc_id = aws_vpc.teamcity_vpc.id
+}
+
+resource "aws_subnet" "public_subnets" {
+  count             = length(local.public_subnet_cidrs)
+  vpc_id            = aws_vpc.teamcity_vpc.id
+  cidr_block        = element(local.public_subnet_cidrs, count.index)
+  availability_zone = element(local.azs, count.index)
+
+  tags = merge(local.tags,
+    {
+      Name = "pub-subnet-${count.index + 1}"
+    }
+  )
+}
+
+resource "aws_subnet" "private_subnets" {
+  count             = length(local.private_subnet_cidrs)
+  vpc_id            = aws_vpc.teamcity_vpc.id
+  cidr_block        = element(local.private_subnet_cidrs, count.index)
+  availability_zone = element(local.azs, count.index)
+
+  tags = merge(local.tags,
+    {
+      Name = "pvt-subnet-${count.index + 1}"
+    }
+  )
+}
+
+##########################################
+# Internet Gateway
+##########################################
+
+resource "aws_internet_gateway" "igw" {
+  vpc_id = aws_vpc.teamcity_vpc.id
+  tags = merge(local.tags,
+    {
+      Name = "teamcity-igw"
+    }
+  )
+}
+
+##########################################
+# Route Tables & NAT Gateway
+##########################################
+
+resource "aws_route_table" "public_rt" {
+  vpc_id = aws_vpc.teamcity_vpc.id
+
+  # public route to the internet
+  route {
+    cidr_block = "0.0.0.0/0"
+    gateway_id = aws_internet_gateway.igw.id
+  }
+
+  tags = merge(local.tags,
+    {
+      Name = "teamcity-public-rt"
+    }
+  )
+}
+
+resource "aws_route_table_association" "public_rt_asso" {
+  count          = length(aws_subnet.public_subnets)
+  route_table_id = aws_route_table.public_rt.id
+  subnet_id      = aws_subnet.public_subnets[count.index].id
+}
+
+resource "aws_eip" "nat_gateway_eip" {
+  depends_on = [aws_internet_gateway.igw]
+  #checkov:skip=CKV2_AWS_19:EIP associated with NAT Gateway through association ID
+  tags = merge(local.tags,
+    {
+      Name = "teamcity-nat-eip"
path: modules/teamcity/examples/simple/vpc.tf
user: login: novekm
body: I wonder if we should by default create a tag (at the module level) that also is like `ModuleSource = (path to module either in TF registry or in toolkit)` - could help with ease of navigation as we scale to more modules over time
created_at: 2025-03-12T20:56:22Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,50 @@
+locals {
+  name_prefix = "teamcity"
+  tags = merge(var.tags, {
+    "environment" = var.environment
+  })
+
+  # Database information
+  database_connection_string = var.database_connection_string != null ? var.database_connection_string : "jdbc:postgresql://${aws_rds_cluster.teamcity_db_cluster[0].endpoint}/teamcity"
+  database_master_username   = var.database_master_username != null ? var.database_master_username : aws_rds_cluster.teamcity_db_cluster[0].master_username
path: modules/teamcity/local.tf
user: login: novekm
body: eventually we should evaluate potential applications for using Terraform ephemeral resources for resources that include sensitive values such as usernames and passwords. 

More here: https://developer.hashicorp.com/terraform/language/resources/ephemeral
created_at: 2025-03-12T20:59:29Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,50 @@
+locals {
+  name_prefix = "teamcity"
+  tags = merge(var.tags, {
+    "environment" = var.environment
+  })
+
+  # Database information
+  database_connection_string = var.database_connection_string != null ? var.database_connection_string : "jdbc:postgresql://${aws_rds_cluster.teamcity_db_cluster[0].endpoint}/teamcity"
+  database_master_username   = var.database_master_username != null ? var.database_master_username : aws_rds_cluster.teamcity_db_cluster[0].master_username
+  database_master_password   = var.database_master_password != null ? var.database_master_password : null
+
+  # Docker image to use for TeamCity Server
+  image = "jetbrains/teamcity-server"
+
+  # EFS information
+  efs_file_system_id  = var.efs_id != null ? var.efs_id : aws_efs_file_system.teamcity_efs_file_system[0].id
+  efs_file_system_arn = var.efs_id != null ? data.aws_efs_file_system.efs_file_system[0].arn : aws_efs_file_system.teamcity_efs_file_system[0].arn
+  efs_access_point_id = var.efs_access_point_id != null ? var.efs_access_point_id : aws_efs_access_point.teamcity_efs_data_access_point[0].id
+
+  # TeamCity Server Information
+  # Set environment variables
+  base_env = [
+    {
+      name  = "TEAMCITY_DB_URL"
+      value = local.database_connection_string
path: modules/teamcity/local.tf
user: login: novekm
body: Where are these locals at? Is there another `locals` block somewhere?
created_at: 2025-03-12T21:01:04Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,10 @@
+data "aws_availability_zones" "available" {}
+
+locals {
path: modules/teamcity/examples/simple/local.tf
body: WHat do you mean? For examples, we likely want to showcase VPC resource creation. Otherwise we're placing burden on end user to pre-create VPC.

If an end user wants to use a single VPC and deploy multiple modules, they should modify or augment one of our examples to suit their needs. Or just use the Toolkit as a submodule to pull our modules into existing TF.
created_at: 2025-03-13T05:44:06Z
author_association: NONE
diff_hunk: @@ -0,0 +1,7 @@
+module "teamcity" {
+  source              = "../../"
path: modules/teamcity/examples/simple/main.tf
body: Split this out into a discussion. Resolving the comment here.
created_at: 2025-03-13T05:44:31Z
author_association: NONE
diff_hunk: @@ -0,0 +1,123 @@
+resource "aws_vpc" "teamcity_vpc" {
+  cidr_block = local.vpc_cidr_block
+  tags = merge(local.tags,
+    {
+      Name = "teamcity-vpc"
+    }
+  )
+  enable_dns_hostnames = true
+  #checkov:skip=CKV2_AWS_11: VPC flow logging disabled by design
+}
+
+# Set default SG to restrict all traffic
+resource "aws_default_security_group" "default" {
+  vpc_id = aws_vpc.teamcity_vpc.id
+}
+
+resource "aws_subnet" "public_subnets" {
+  count             = length(local.public_subnet_cidrs)
+  vpc_id            = aws_vpc.teamcity_vpc.id
+  cidr_block        = element(local.public_subnet_cidrs, count.index)
+  availability_zone = element(local.azs, count.index)
+
+  tags = merge(local.tags,
+    {
+      Name = "pub-subnet-${count.index + 1}"
+    }
+  )
+}
+
+resource "aws_subnet" "private_subnets" {
+  count             = length(local.private_subnet_cidrs)
+  vpc_id            = aws_vpc.teamcity_vpc.id
+  cidr_block        = element(local.private_subnet_cidrs, count.index)
+  availability_zone = element(local.azs, count.index)
+
+  tags = merge(local.tags,
+    {
+      Name = "pvt-subnet-${count.index + 1}"
+    }
+  )
+}
+
+##########################################
+# Internet Gateway
+##########################################
+
+resource "aws_internet_gateway" "igw" {
+  vpc_id = aws_vpc.teamcity_vpc.id
+  tags = merge(local.tags,
+    {
+      Name = "teamcity-igw"
+    }
+  )
+}
+
+##########################################
+# Route Tables & NAT Gateway
+##########################################
+
+resource "aws_route_table" "public_rt" {
+  vpc_id = aws_vpc.teamcity_vpc.id
+
+  # public route to the internet
+  route {
+    cidr_block = "0.0.0.0/0"
+    gateway_id = aws_internet_gateway.igw.id
+  }
+
+  tags = merge(local.tags,
+    {
+      Name = "teamcity-public-rt"
+    }
+  )
+}
+
+resource "aws_route_table_association" "public_rt_asso" {
+  count          = length(aws_subnet.public_subnets)
+  route_table_id = aws_route_table.public_rt.id
+  subnet_id      = aws_subnet.public_subnets[count.index].id
+}
+
+resource "aws_eip" "nat_gateway_eip" {
+  depends_on = [aws_internet_gateway.igw]
+  #checkov:skip=CKV2_AWS_19:EIP associated with NAT Gateway through association ID
+  tags = merge(local.tags,
+    {
+      Name = "teamcity-nat-eip"
path: modules/teamcity/examples/simple/vpc.tf
body: Also good idea. Maybe propose in issue or in discussion. Lets keep these sorts of ideas out of the PR though to simplify review for Christina :)
created_at: 2025-03-13T05:45:27Z
author_association: NONE
path: modules/teamcity/main.tf
body: @cwwalb I would create an issue to track the work of splitting this file into separate files for readability.
created_at: 2025-03-13T05:46:10Z
author_association: NONE
diff_hunk: @@ -0,0 +1,50 @@
+locals {
+  name_prefix = "teamcity"
+  tags = merge(var.tags, {
+    "environment" = var.environment
+  })
+
+  # Database information
+  database_connection_string = var.database_connection_string != null ? var.database_connection_string : "jdbc:postgresql://${aws_rds_cluster.teamcity_db_cluster[0].endpoint}/teamcity"
+  database_master_username   = var.database_master_username != null ? var.database_master_username : aws_rds_cluster.teamcity_db_cluster[0].master_username
+  database_master_password   = var.database_master_password != null ? var.database_master_password : null
+
+  # Docker image to use for TeamCity Server
+  image = "jetbrains/teamcity-server"
+
+  # EFS information
+  efs_file_system_id  = var.efs_id != null ? var.efs_id : aws_efs_file_system.teamcity_efs_file_system[0].id
+  efs_file_system_arn = var.efs_id != null ? data.aws_efs_file_system.efs_file_system[0].arn : aws_efs_file_system.teamcity_efs_file_system[0].arn
+  efs_access_point_id = var.efs_access_point_id != null ? var.efs_access_point_id : aws_efs_access_point.teamcity_efs_data_access_point[0].id
+
+  # TeamCity Server Information
+  # Set environment variables
+  base_env = [
+    {
+      name  = "TEAMCITY_DB_URL"
+      value = local.database_connection_string
path: modules/teamcity/local.tf
user: login: cwwalb
body: locals.tf in the module
created_at: 2025-03-13T15:55:05Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,50 @@
+locals {
+  name_prefix = "teamcity"
+  tags = merge(var.tags, {
+    "environment" = var.environment
+  })
+
+  # Database information
+  database_connection_string = var.database_connection_string != null ? var.database_connection_string : "jdbc:postgresql://${aws_rds_cluster.teamcity_db_cluster[0].endpoint}/teamcity"
+  database_master_username   = var.database_master_username != null ? var.database_master_username : aws_rds_cluster.teamcity_db_cluster[0].master_username
+  database_master_password   = var.database_master_password != null ? var.database_master_password : null
+
+  # Docker image to use for TeamCity Server
+  image = "jetbrains/teamcity-server"
+
+  # EFS information
+  efs_file_system_id  = var.efs_id != null ? var.efs_id : aws_efs_file_system.teamcity_efs_file_system[0].id
+  efs_file_system_arn = var.efs_id != null ? data.aws_efs_file_system.efs_file_system[0].arn : aws_efs_file_system.teamcity_efs_file_system[0].arn
+  efs_access_point_id = var.efs_access_point_id != null ? var.efs_access_point_id : aws_efs_access_point.teamcity_efs_data_access_point[0].id
+
+  # TeamCity Server Information
+  # Set environment variables
+  base_env = [
+    {
+      name  = "TEAMCITY_DB_URL"
+      value = local.database_connection_string
path: modules/teamcity/local.tf
user: login: novekm
body: Ah, I see it now - ty
created_at: 2025-03-13T17:18:41Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,50 @@
+locals {
+  name_prefix = "teamcity"
+  tags = merge(var.tags, {
+    "environment" = var.environment
+  })
+
+  # Database information
+  database_connection_string = var.database_connection_string != null ? var.database_connection_string : "jdbc:postgresql://${aws_rds_cluster.teamcity_db_cluster[0].endpoint}/teamcity"
+  database_master_username   = var.database_master_username != null ? var.database_master_username : aws_rds_cluster.teamcity_db_cluster[0].master_username
path: modules/teamcity/local.tf
user: login: cwwalb
body: great idea lets discuss this separately
created_at: 2025-03-13T19:39:37Z
author_association: CONTRIBUTOR

PR comments:
user: login: kylesomers
created_at: 2025-02-22T18:47:50Z
body: Review this - https://github.com/JetBrains/teamcity-amazon-ecs-plugin

Worth incorporating/extending?
author_association: CONTRIBUTOR
user: login: ghost
created_at: 2025-02-24T17:15:22Z
body: > Review this - https://github.com/JetBrains/teamcity-amazon-ecs-plugin
> 
> Worth incorporating/extending?

Yes - 100%.
author_association: NONE
user: login: ghost
created_at: 2025-03-13T05:48:29Z
body: @cwwalb This looks great. Get any final changes you need in, rebase main to get up to date, and then re-request a review from me and I'll re-approve assuming everything deploys. @novekm is going to use this module as a guinea pig for Terraform test framework, so he'll give the other approval. Kevon, can you please check that everything deploys before we merge this as well? Just the single example Christina has provided - we can worry about edge cases and parameterized deployments (no ALB, existing DocDB, etc...) after. Thanks all!
author_association: NONE
user: login: awsbilal
created_at: 2025-03-13T21:29:22Z
body: Terraform Destroy fails to delete resource "teamcity_alb_access_logs_bucket" due to items still in the bucket.
Adding "force_destroy = true" to the resource in main.tf seems to fix this issue.

resource "aws_s3_bucket" "teamcity_alb_access_logs_bucket" {
  count  = var.enable_teamcity_alb_access_logs && var.teamcity_alb_access_logs_bucket == null ? 1 : 0
  bucket = "${local.name_prefix}-alb-access-logs-${random_string.teamcity_alb_access_logs_bucket_suffix[0].result}"
  force_destroy = true
  tags = merge(local.tags, {
    Name = "${local.name_prefix}-alb-access-logs-${random_string.teamcity_alb_access_logs_bucket_suffix[0].result}"
  })
}
author_association: CONTRIBUTOR