From 17e77015138f03565bff94f21d8d0476e5639d7f Mon Sep 17 00:00:00 2001 From: Jye Cusch Date: Thu, 25 Sep 2025 11:08:39 +1000 Subject: [PATCH 1/3] ci: add terraform linting, validation and sec scanning --- .github/workflows/test.yml | 68 ++++++++++++++++++++++++++++++++++++++ .gitignore | 3 ++ .tflint.hcl | 64 +++++++++++++++++++++++++++++++++++ .trivyignore | 12 +++++++ DEVELOPERS.md | 16 +++++++++ Makefile | 47 ++++++++++++++++++++++++++ 6 files changed, 210 insertions(+) create mode 100644 .github/workflows/test.yml create mode 100644 .tflint.hcl create mode 100644 .trivyignore create mode 100644 DEVELOPERS.md create mode 100644 Makefile diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml new file mode 100644 index 0000000..7f93968 --- /dev/null +++ b/.github/workflows/test.yml @@ -0,0 +1,68 @@ +name: "Test" + +on: + pull_request: + paths: + - "**/*.tf" + - "**/*.tfvars" + - ".github/workflows/test.yml" + workflow_dispatch: + +jobs: + terraform-test: + name: Test Terraform + runs-on: ubuntu-latest + + steps: + - name: Checkout code + uses: actions/checkout@v4 + + - name: Setup Go + uses: actions/setup-go@v5 + with: + go-version: '1.25' + + - name: Setup Terraform + uses: hashicorp/setup-terraform@v3 + with: + terraform_version: "~1.8.0" + + - name: Install TFLint + uses: terraform-linters/setup-tflint@v4 + with: + tflint_version: latest + + - name: Install Trivy + run: | + curl -sfL https://raw.githubusercontent.com/aquasecurity/trivy/main/contrib/install.sh | sh -s -- -b /usr/local/bin + + - name: Run all tests + run: make test + + + - name: Summary + if: always() + run: | + echo "## Terraform Lint Summary" >> $GITHUB_STEP_SUMMARY + echo "" >> $GITHUB_STEP_SUMMARY + echo "### Modules Checked" >> $GITHUB_STEP_SUMMARY + modules=$(find . -name "*.tf" -type f | xargs dirname | sort -u) + echo "The following Terraform modules were validated:" >> $GITHUB_STEP_SUMMARY + echo "" >> $GITHUB_STEP_SUMMARY + for module in $modules; do + echo "- \`$module\`" >> $GITHUB_STEP_SUMMARY + done + echo "" >> $GITHUB_STEP_SUMMARY + echo "### Checks Performed" >> $GITHUB_STEP_SUMMARY + echo "- ✅ Terraform Format Check" >> $GITHUB_STEP_SUMMARY + echo "- ✅ Terraform Validation" >> $GITHUB_STEP_SUMMARY + echo "- ✅ TFLint Analysis" >> $GITHUB_STEP_SUMMARY + echo "- ✅ Security Scan (Trivy)" >> $GITHUB_STEP_SUMMARY + echo "" >> $GITHUB_STEP_SUMMARY + echo "### Local Development" >> $GITHUB_STEP_SUMMARY + echo "Run the same checks locally with:" >> $GITHUB_STEP_SUMMARY + echo '```bash' >> $GITHUB_STEP_SUMMARY + echo "make test # Run all tests" >> $GITHUB_STEP_SUMMARY + echo "make format # Auto-format files" >> $GITHUB_STEP_SUMMARY + echo "make scan # Run security scan" >> $GITHUB_STEP_SUMMARY + echo '```' >> $GITHUB_STEP_SUMMARY diff --git a/.gitignore b/.gitignore index a2d9c02..8d8bafd 100644 --- a/.gitignore +++ b/.gitignore @@ -11,6 +11,9 @@ # Output of the go coverage tool, specifically when used with LiteIDE *.out +.terraform/ +.terraform.lock.hcl + # Dependency directories (remove the comment below to include it) # vendor/ diff --git a/.tflint.hcl b/.tflint.hcl new file mode 100644 index 0000000..9ee3ef0 --- /dev/null +++ b/.tflint.hcl @@ -0,0 +1,64 @@ +config { + # Set minimum Terraform version to support provider functions + terraform_version = "~> 1.8.0" +} + +plugin "terraform" { + enabled = true + preset = "recommended" + version = "0.9.1" + source = "github.com/terraform-linters/tflint-ruleset-terraform" +} + +plugin "google" { + enabled = true +} + +rule "terraform_comment_syntax" { + enabled = true +} + +rule "terraform_deprecated_index" { + enabled = true +} + +rule "terraform_deprecated_interpolation" { + enabled = false # Disable to allow provider function syntax +} + +rule "terraform_documented_outputs" { + enabled = true +} + +rule "terraform_documented_variables" { + enabled = true +} + +rule "terraform_naming_convention" { + enabled = true + format = "snake_case" +} + +rule "terraform_required_providers" { + enabled = true +} + +rule "terraform_required_version" { + enabled = true +} + +rule "terraform_standard_module_structure" { + enabled = true +} + +rule "terraform_typed_variables" { + enabled = true +} + +rule "terraform_unused_declarations" { + enabled = true +} + +rule "terraform_unused_required_providers" { + enabled = true +} \ No newline at end of file diff --git a/.trivyignore b/.trivyignore new file mode 100644 index 0000000..d336173 --- /dev/null +++ b/.trivyignore @@ -0,0 +1,12 @@ +# Trivy ignore file for Suga GCP Plugins +# https://aquasecurity.github.io/trivy/latest/docs/configuration/filtering/ + +# Example: Ignore specific checks that may not apply to plugin modules +# AVD-GCP-0011 # Uncomment to ignore specific GCP checks +# AVD-GCP-0001 # Uncomment to ignore public access warnings if intentional + +# Ignore directories +.git/ +.terraform/ +node_modules/ +security-reports/ \ No newline at end of file diff --git a/DEVELOPERS.md b/DEVELOPERS.md new file mode 100644 index 0000000..f8034aa --- /dev/null +++ b/DEVELOPERS.md @@ -0,0 +1,16 @@ +# Developer Guide + +## Prerequisites + +- [Terraform](https://www.terraform.io/downloads) >= 1.5.0 +- [Docker](https://www.docker.com/get-started) (for tflint and trivy) + +## Usage + +```bash +make format # Format all Terraform files +make test # Run all tests (format-check, validate, lint, scan) +make clean # Clean up temp files +``` + +Tools run in Docker containers - no local installation needed. \ No newline at end of file diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..562acdc --- /dev/null +++ b/Makefile @@ -0,0 +1,47 @@ +.PHONY: help format lint clean +.DEFAULT_GOAL := help + +## TODO: include MEDIUM severity in security scanning. +TRIVY_SEVERITY := HIGH,CRITICAL + +help: ## Show available commands + @grep -E '^[a-zA-Z_-]+:.*?## .*$$' $(MAKEFILE_LIST) | awk 'BEGIN {FS = ":.*?## "}; {printf " %-10s %s\n", $$1, $$2}' + +trivy-severity: ## Output the Trivy severity levels for use in scripts + @echo $(TRIVY_SEVERITY) + +format: ## Format all Terraform files + @find . -name "*.tf" -type f | xargs dirname | sort -u | xargs -I {} terraform fmt {} + +format-check: ## Check formatting of all Terraform files + @echo "Checking format..." + @find . -name "*.tf" -type f | xargs dirname | sort -u | while read dir; do \ + terraform fmt -check=true -diff=true "$$dir" || exit 1; \ + done + +validate: ## Validate all Terraform files + @echo "Validating..." + @find . -name "*.tf" -type f | xargs dirname | sort -u | while read dir; do \ + echo " $$dir"; \ + cd "$$dir" && terraform init -backend=false -get=true -upgrade=false >/dev/null && terraform validate && cd - >/dev/null || exit 1; \ + done + +lint: ## Lint using tflint + @echo "Running tflint..." + @find . -name "*.tf" -type f | xargs dirname | sort -u | while read dir; do \ + echo " $$dir"; \ + docker run --rm -v "$$(pwd)/$$dir:/data" -t ghcr.io/terraform-linters/tflint --format=compact --minimum-failure-severity=error; \ + done + +scan: ## Run security scan using Trivy + @echo "Running security scan..." + @docker run --rm -v "$$(pwd):/work" -w /work ghcr.io/aquasecurity/trivy:latest config . --format=table --quiet --exit-code 1 --severity $(TRIVY_SEVERITY) + +test: format-check validate lint scan ## Run all tests: format-check, validate, lint, and scan + @echo "All tests passed!" + +clean: ## Clean up .terraform directories and temp files + @find . -type d -name ".terraform" -exec rm -rf {} + 2>/dev/null || true + @find . -name "*.tfplan" -delete 2>/dev/null || true + @find . -name "*.tfstate*" -delete 2>/dev/null || true + @find . -name ".terraform.lock.hcl" -delete 2>/dev/null || true \ No newline at end of file From 05365069e163834e73c5f85c53dc1d9188580019 Mon Sep 17 00:00:00 2001 From: Jye Cusch Date: Thu, 25 Sep 2025 11:08:51 +1000 Subject: [PATCH 2/3] chore: fix .tf file formats --- fargate/module/main.tf | 60 ++++++++++++++++---------------- fargate/module/variables.tf | 2 +- iamrole/module/outputs.tf | 6 ++-- iamrole/module/variables.tf | 2 +- lambda/module/main.tf | 16 ++++----- lambda/module/variables.tf | 10 +++--- loadbalancer/module/variables.tf | 6 ++-- s3/module/main.tf | 10 +++--- s3/module/outputs.tf | 24 ++++++------- s3/module/variables.tf | 4 +-- sgrule/module/main.tf | 16 ++++----- sgrule/module/variables.tf | 42 +++++++++++----------- vpc/module/outputs.tf | 20 +++++------ vpc/module/variables.tf | 46 ++++++++++++------------ 14 files changed, 132 insertions(+), 132 deletions(-) diff --git a/fargate/module/main.tf b/fargate/module/main.tf index fbaf3a0..af590c1 100644 --- a/fargate/module/main.tf +++ b/fargate/module/main.tf @@ -110,33 +110,33 @@ resource "aws_ecs_task_definition" "service" { environment = concat([ { - name = "SUGA_SERVICE_NAME" + name = "SUGA_SERVICE_NAME" value = var.suga.name }, { - name = "SUGA_STACK_ID" + name = "SUGA_STACK_ID" value = var.suga.stack_id }, { - name = "PORT" - value = "9001" + name = "PORT" + value = "9001" }, { - name = "FARGATE_PROXY_PORT" + name = "FARGATE_PROXY_PORT" value = "${tostring(var.container_port)}" } - ], - [ - for k, v in var.environment : { - name = k - value = "${tostring(v)}" - } - ], - [ - for k, v in var.suga.env : { - name = k - value = "${tostring(v)}" - } + ], + [ + for k, v in var.environment : { + name = k + value = "${tostring(v)}" + } + ], + [ + for k, v in var.suga.env : { + name = k + value = "${tostring(v)}" + } ]) logConfiguration = { @@ -165,14 +165,14 @@ resource "aws_ecs_cluster" "cluster" { # Create an ESC service for the above task definition resource "aws_ecs_service" "service" { - name = "${var.suga.stack_id}-${var.suga.name}" - cluster = aws_ecs_cluster.cluster.id + name = "${var.suga.stack_id}-${var.suga.name}" + cluster = aws_ecs_cluster.cluster.id task_definition = aws_ecs_task_definition.service.arn - desired_count = 1 - launch_type = "FARGATE" + desired_count = 1 + launch_type = "FARGATE" network_configuration { - subnets = var.subnets + subnets = var.subnets security_groups = concat([var.alb_security_group], var.security_groups) } load_balancer { @@ -184,17 +184,17 @@ resource "aws_ecs_service" "service" { # Create target group resource "aws_lb_target_group" "service" { - name = local.sanitized_target_group_name - port = var.container_port - protocol = "HTTP" - vpc_id = var.vpc_id + name = local.sanitized_target_group_name + port = var.container_port + protocol = "HTTP" + vpc_id = var.vpc_id target_type = "ip" health_check { - path = "/${var.suga.name}/x-suga-health" - interval = 30 - timeout = 10 + path = "/${var.suga.name}/x-suga-health" + interval = 30 + timeout = 10 healthy_threshold = 2 } } @@ -202,7 +202,7 @@ resource "aws_lb_target_group" "service" { # Reference the shared HTTP listener created by the loadbalancer module data "aws_lb_listener" "shared_http" { load_balancer_arn = var.alb_arn - port = 80 + port = 80 } # Create listener rule for this service's target group diff --git a/fargate/module/variables.tf b/fargate/module/variables.tf index 119e327..18c71a7 100644 --- a/fargate/module/variables.tf +++ b/fargate/module/variables.tf @@ -14,7 +14,7 @@ variable "container_port" { } variable "alb_arn" { - type = string + type = string } variable "alb_security_group" { diff --git a/iamrole/module/outputs.tf b/iamrole/module/outputs.tf index bedbd6e..01c938a 100644 --- a/iamrole/module/outputs.tf +++ b/iamrole/module/outputs.tf @@ -1,9 +1,9 @@ output "suga" { value = { exports = { - "aws_iam_role" = aws_iam_role.role.arn - "aws_iam_role:id" = aws_iam_role.role.id - "aws_iam_role:arn" = aws_iam_role.role.arn + "aws_iam_role" = aws_iam_role.role.arn + "aws_iam_role:id" = aws_iam_role.role.id + "aws_iam_role:arn" = aws_iam_role.role.arn "aws_iam_role:name" = aws_iam_role.role.name } } diff --git a/iamrole/module/variables.tf b/iamrole/module/variables.tf index 9e91827..a6b776c 100644 --- a/iamrole/module/variables.tf +++ b/iamrole/module/variables.tf @@ -1,6 +1,6 @@ variable "suga" { type = object({ - name = string + name = string stack_id = string }) } diff --git a/lambda/module/main.tf b/lambda/module/main.tf index cacecea..400a901 100644 --- a/lambda/module/main.tf +++ b/lambda/module/main.tf @@ -7,8 +7,8 @@ locals { # Apply the either-or operator rule: if DOM is *, set DOW to ? transformed_cron_expression = { for key, fields in local.split_cron_expression : key => [ - for i, field in fields : - (i == 4 && fields[2] == "*" && field == "*") ? "?" : field + for i, field in fields : + (i == 4 && fields[2] == "*" && field == "*") ? "?" : field ] } @@ -19,10 +19,10 @@ locals { convert_cron_to_aws = { for key, schedule in var.suga.schedules : key => { cron_expression = schedule.cron_expression - path = schedule.path + path = schedule.path # Convert the standard cron expression to an AWS CloudWatch cron expression # Apply the either-or operator rule and add year field - aws_cron = "cron(${join(" ", local.transformed_cron_expression[key])} *)" + aws_cron = "cron(${join(" ", local.transformed_cron_expression[key])} *)" } } } @@ -81,7 +81,7 @@ resource "aws_lambda_function" "function" { environment { variables = merge(var.environment, var.suga.env, { SUGA_STACK_ID = var.suga.stack_id - PORT = "8080", + PORT = "8080", }) } @@ -136,8 +136,8 @@ resource "aws_iam_role_policy" "role_policy" { Version = "2012-10-17", Statement = [ { - Effect = "Allow", - Action = "lambda:InvokeFunction", + Effect = "Allow", + Action = "lambda:InvokeFunction", Resource = aws_lambda_function.function.arn } ] @@ -158,7 +158,7 @@ resource "aws_scheduler_schedule" "schedule" { role_arn = aws_iam_role.role.arn input = jsonencode({ - "path" = each.value.path + "path" = each.value.path }) } } diff --git a/lambda/module/variables.tf b/lambda/module/variables.tf index 1cb3905..b6b9588 100644 --- a/lambda/module/variables.tf +++ b/lambda/module/variables.tf @@ -1,13 +1,13 @@ variable "suga" { type = object({ - name = string - stack_id = string - image_id = string - schedules = optional(map(object({ + name = string + stack_id = string + image_id = string + schedules = optional(map(object({ cron_expression = string path = string })), {}) - env = map(string) + env = map(string) identities = map(object({ exports = map(string) })) diff --git a/loadbalancer/module/variables.tf b/loadbalancer/module/variables.tf index 495dd54..b37d074 100644 --- a/loadbalancer/module/variables.tf +++ b/loadbalancer/module/variables.tf @@ -4,7 +4,7 @@ variable "load_balancer_type" { } variable "name" { - type = string + type = string } variable "listener_port" { @@ -17,11 +17,11 @@ variable "internal" { } variable "security_groups" { - type = list(string) + type = list(string) } variable "subnets" { - type = list(string) + type = list(string) } variable "cidr_blocks" { diff --git a/s3/module/main.tf b/s3/module/main.tf index 3a0f822..8de172a 100644 --- a/s3/module/main.tf +++ b/s3/module/main.tf @@ -21,19 +21,19 @@ locals { "s3:DeleteObject", ] relative_content_path = "${path.root}/../../../${var.suga.content_path}" - content_files = var.suga.content_path != "" ? fileset(local.relative_content_path, "**/*") : [] + content_files = var.suga.content_path != "" ? fileset(local.relative_content_path, "**/*") : [] } # Upload each file to S3 (only if files exist) resource "aws_s3_object" "files" { for_each = toset(local.content_files) - + bucket = aws_s3_bucket.bucket.bucket key = each.value source = "${local.relative_content_path}/${each.value}" - + etag = filemd5("${local.relative_content_path}/${each.value}") - + content_type = lookup({ "html" = "text/html" "css" = "text/css" @@ -68,7 +68,7 @@ resource "aws_iam_role_policy" "access_policy" { contains(each.value.actions, "delete") ? local.delete_actions : [] ) ) - Effect = "Allow" + Effect = "Allow" Resource = [ aws_s3_bucket.bucket.arn, "${aws_s3_bucket.bucket.arn}/*" diff --git a/s3/module/outputs.tf b/s3/module/outputs.tf index 44e991c..3965585 100644 --- a/s3/module/outputs.tf +++ b/s3/module/outputs.tf @@ -1,15 +1,15 @@ output "suga" { - value = { - id = aws_s3_bucket.bucket.arn - domain_name = aws_s3_bucket.bucket.bucket_regional_domain_name - exports = { - # Export env variables to be mapped to all services that access this resource - # TODO: May need a per service mapping as well - env = {} - # Export resources to be read into other modules - resources = { - "aws_s3_bucket" = aws_s3_bucket.bucket.arn - } - } + value = { + id = aws_s3_bucket.bucket.arn + domain_name = aws_s3_bucket.bucket.bucket_regional_domain_name + exports = { + # Export env variables to be mapped to all services that access this resource + # TODO: May need a per service mapping as well + env = {} + # Export resources to be read into other modules + resources = { + "aws_s3_bucket" = aws_s3_bucket.bucket.arn + } } + } } \ No newline at end of file diff --git a/s3/module/variables.tf b/s3/module/variables.tf index 1942654..a13ffbc 100644 --- a/s3/module/variables.tf +++ b/s3/module/variables.tf @@ -1,7 +1,7 @@ variable "suga" { type = object({ - name = string - stack_id = string + name = string + stack_id = string content_path = string services = map(object({ actions = list(string) diff --git a/sgrule/module/main.tf b/sgrule/module/main.tf index 93c49b7..c0bd660 100644 --- a/sgrule/module/main.tf +++ b/sgrule/module/main.tf @@ -5,13 +5,13 @@ data "aws_ec2_managed_prefix_list" "prefix_lists" { # Add a security group rule that allows self ingres traffic to allow the ALB to access the health check service resource "aws_security_group_rule" "rules" { - count = length(var.security_group_ids) + count = length(var.security_group_ids) security_group_id = var.security_group_ids[count.index] - from_port = var.from_port - to_port = var.to_port - protocol = var.protocol - type = var.type - self = var.self - cidr_blocks = var.cidr_blocks - prefix_list_ids = length(data.aws_ec2_managed_prefix_list.prefix_lists) > 0 ? [for pl in data.aws_ec2_managed_prefix_list.prefix_lists : pl.id] : null + from_port = var.from_port + to_port = var.to_port + protocol = var.protocol + type = var.type + self = var.self + cidr_blocks = var.cidr_blocks + prefix_list_ids = length(data.aws_ec2_managed_prefix_list.prefix_lists) > 0 ? [for pl in data.aws_ec2_managed_prefix_list.prefix_lists : pl.id] : null } \ No newline at end of file diff --git a/sgrule/module/variables.tf b/sgrule/module/variables.tf index 053b9b4..60eab11 100644 --- a/sgrule/module/variables.tf +++ b/sgrule/module/variables.tf @@ -1,47 +1,47 @@ variable "security_group_ids" { - type = list(string) + type = list(string) description = "List of security group IDs to which the rule will be applied" } variable "from_port" { - type = number + type = number description = "The starting port of the port range for the TCP and UDP protocols, or an ICMP type." } variable "to_port" { - type = number + type = number description = "The ending port of the port range for the TCP and UDP protocols, or an ICMP code." } variable "protocol" { - type = string - default = "tcp" - description = "The protocol. Use -1 to specify all protocols." + type = string + default = "tcp" + description = "The protocol. Use -1 to specify all protocols." } variable "type" { - type = string - default = "ingress" - description = "The type of rule, either ingress (inbound) or egress (outbound)." + type = string + default = "ingress" + description = "The type of rule, either ingress (inbound) or egress (outbound)." } variable "self" { - type = bool - default = null - nullable = true - description = "Whether to allow traffic to/from the security group itself." + type = bool + default = null + nullable = true + description = "Whether to allow traffic to/from the security group itself." } variable "cidr_blocks" { - type = list(string) - default = null - nullable = true - description = "List of CIDR blocks to allow or deny, in CIDR notation." + type = list(string) + default = null + nullable = true + description = "List of CIDR blocks to allow or deny, in CIDR notation." } variable "prefix_list_names" { - type = list(string) - nullable = true - default = null - description = "List of AWS managed prefix list names to allow access (e.g., 'com.amazonaws.global.cloudfront.origin-facing')" + type = list(string) + nullable = true + default = null + description = "List of AWS managed prefix list names to allow access (e.g., 'com.amazonaws.global.cloudfront.origin-facing')" } \ No newline at end of file diff --git a/vpc/module/outputs.tf b/vpc/module/outputs.tf index 719ffba..aab800c 100644 --- a/vpc/module/outputs.tf +++ b/vpc/module/outputs.tf @@ -1,24 +1,24 @@ output "vpc_id" { - value = module.vpc.vpc_id - description = "ID of the created VPC" + value = module.vpc.vpc_id + description = "ID of the created VPC" } output "private_subnets" { - value = module.vpc.private_subnets - description = "Private subnet IDs" + value = module.vpc.private_subnets + description = "Private subnet IDs" } output "public_subnets" { - value = module.vpc.public_subnets - description = "Public subnet IDs" + value = module.vpc.public_subnets + description = "Public subnet IDs" } output "subnets" { - value = concat(module.vpc.private_subnets, module.vpc.public_subnets) - description = "All subnet IDs" + value = concat(module.vpc.private_subnets, module.vpc.public_subnets) + description = "All subnet IDs" } output "default_security_group_id" { - value = module.vpc.default_security_group_id - description = "Default security group ID" + value = module.vpc.default_security_group_id + description = "Default security group ID" } \ No newline at end of file diff --git a/vpc/module/variables.tf b/vpc/module/variables.tf index 5de2745..169f847 100644 --- a/vpc/module/variables.tf +++ b/vpc/module/variables.tf @@ -1,41 +1,41 @@ variable "name" { - type = string - description = "The name of the VPC" + type = string + description = "The name of the VPC" } variable "networking" { - type = object({ - cidr_block = string - private_subnets = list(string) - public_subnets = list(string) - }) - default = { - cidr_block = "10.0.0.0/16" - private_subnets = ["10.0.1.0/24", "10.0.2.0/24", "10.0.3.0/24"] - public_subnets = ["10.0.101.0/24", "10.0.102.0/24", "10.0.103.0/24"] - } + type = object({ + cidr_block = string + private_subnets = list(string) + public_subnets = list(string) + }) + default = { + cidr_block = "10.0.0.0/16" + private_subnets = ["10.0.1.0/24", "10.0.2.0/24", "10.0.3.0/24"] + public_subnets = ["10.0.101.0/24", "10.0.102.0/24", "10.0.103.0/24"] + } } variable "azs" { - type = list(string) - nullable = true - default = null + type = list(string) + nullable = true + default = null } variable "enable_nat_gateway" { - type = bool - default = false + type = bool + default = false } variable "enable_vpn_gateway" { - type = bool - default = false + type = bool + default = false } variable "single_nat_gateway" { - type = bool - default = false + type = bool + default = false } variable "tags" { - type = map(string) - default = {} + type = map(string) + default = {} } \ No newline at end of file From ef0466b98de64d5df12a900fa258272d9b6f85e0 Mon Sep 17 00:00:00 2001 From: Jye Cusch Date: Thu, 25 Sep 2025 11:54:06 +1000 Subject: [PATCH 3/3] fix: fix initial sec scan issues --- .trivyignore | 18 ++++++++++++++---- cloudfront/module/main.tf | 1 + lambda/manifest.yaml | 3 +++ lambda/module/main.tf | 3 +++ lambda/module/variables.tf | 5 +++++ loadbalancer/module/main.tf | 2 ++ loadbalancer/module/variables.tf | 2 +- s3/module/main.tf | 9 +++++++++ 8 files changed, 38 insertions(+), 5 deletions(-) diff --git a/.trivyignore b/.trivyignore index d336173..4285d37 100644 --- a/.trivyignore +++ b/.trivyignore @@ -1,12 +1,22 @@ # Trivy ignore file for Suga GCP Plugins # https://aquasecurity.github.io/trivy/latest/docs/configuration/filtering/ -# Example: Ignore specific checks that may not apply to plugin modules -# AVD-GCP-0011 # Uncomment to ignore specific GCP checks -# AVD-GCP-0001 # Uncomment to ignore public access warnings if intentional +# AVD-AWS-0030: Image scanning is not enabled +# We've added a variable to control this, which default to `true` +AVD-AWS-0030 + +# TODO: we'll need to avoid use the `latest` tag, then enable this +AVD-AWS-0031 + +# TODO: Enable bucket encryption and add a key resource/plugin +AVD-AWS-0132 +AVD-AWS-0088 + +# TODO: Enable load balancer HTTPS +AVD-AWS-0054 # Ignore directories .git/ .terraform/ node_modules/ -security-reports/ \ No newline at end of file +security-reports/ diff --git a/cloudfront/module/main.tf b/cloudfront/module/main.tf index 4f6550b..6c7d446 100644 --- a/cloudfront/module/main.tf +++ b/cloudfront/module/main.tf @@ -190,6 +190,7 @@ resource "aws_lambda_permission" "allow_cloudfront_origin_request" { action = "lambda:GetFunction" function_name = aws_lambda_function.origin_request[0].function_name principal = "edgelambda.amazonaws.com" + source_arn = aws_cloudfront_distribution.distribution.arn } resource "aws_wafv2_web_acl" "cloudfront_waf" { diff --git a/lambda/manifest.yaml b/lambda/manifest.yaml index b2f7d7e..a66a2ef 100644 --- a/lambda/manifest.yaml +++ b/lambda/manifest.yaml @@ -18,6 +18,9 @@ inputs: timeout: type: number description: "Maximum execution time in seconds 1-900 (e.g. `300`)" + image_scan_on_push: + type: bool + description: "Scan the image for vulnerabilities after it is pushed to Amazon ECR (e.g. `true`)" memory: type: number description: "Amount of memory in MB 128-10240 (e.g. `1024`)" diff --git a/lambda/module/main.tf b/lambda/module/main.tf index 400a901..f181910 100644 --- a/lambda/module/main.tf +++ b/lambda/module/main.tf @@ -30,6 +30,9 @@ locals { # Create an ECR repository resource "aws_ecr_repository" "repo" { name = var.suga.name + image_scanning_configuration { + scan_on_push = var.image_scan_on_push + } } data "aws_ecr_authorization_token" "ecr_auth" { diff --git a/lambda/module/variables.tf b/lambda/module/variables.tf index b6b9588..105985b 100644 --- a/lambda/module/variables.tf +++ b/lambda/module/variables.tf @@ -53,3 +53,8 @@ variable "security_group_ids" { type = list(string) default = [] } + +variable "image_scan_on_push" { + type = bool + default = true +} \ No newline at end of file diff --git a/loadbalancer/module/main.tf b/loadbalancer/module/main.tf index 7248dfd..104554c 100644 --- a/loadbalancer/module/main.tf +++ b/loadbalancer/module/main.tf @@ -4,6 +4,8 @@ resource "aws_lb" "lb" { load_balancer_type = var.load_balancer_type security_groups = var.security_groups subnets = var.subnets + + drop_invalid_header_fields = true } # Get managed prefix lists by name diff --git a/loadbalancer/module/variables.tf b/loadbalancer/module/variables.tf index b37d074..48a4ead 100644 --- a/loadbalancer/module/variables.tf +++ b/loadbalancer/module/variables.tf @@ -13,7 +13,7 @@ variable "listener_port" { } variable "internal" { type = bool - default = false + default = true } variable "security_groups" { diff --git a/s3/module/main.tf b/s3/module/main.tf index 8de172a..acac2a6 100644 --- a/s3/module/main.tf +++ b/s3/module/main.tf @@ -9,6 +9,15 @@ resource "aws_s3_bucket" "bucket" { tags = var.tags } +resource "aws_s3_bucket_public_access_block" "block_public" { + bucket = aws_s3_bucket.bucket.id + + restrict_public_buckets = true + ignore_public_acls = true + block_public_acls = true + block_public_policy = true +} + locals { read_actions = [ "s3:GetObject",