Skip to content

feat(auth): validate tokens against the platform pool with a validate-only client - #258

Merged
Alexanderamiri merged 2 commits into
mainfrom
feat/platform-auth
Oct 23, 2026
Merged

Alexanderamiri merged 2 commits into
mainfrom
feat/platform-auth

Conversation

@Alexanderamiri

Copy link
Copy Markdown
Member

Why

The platform now supports validate-only auth (client: none) and env aliases in app.yaml auth: (javaBin/platform#138). cupcake-backend only verifies tokens that frosting's public client issued, so it doesn't need a Cognito client of its own. And the pool's discovery URL no longer has to be hardcoded in environment.

What

  • auth: internal (which asked for a confidential client with a secret the backend never used) becomes pool: internal, client: none.
  • auth.env: {COGNITO_WELL_KNOWN_URL: OIDC_WELL_KNOWN_URL}. The app reads OIDC_WELL_KNOWN_URL through application.conf, so no code changes.
  • Removed the hardcoded OIDC_WELL_KNOWN_URL from environment.

Authorization doesn't change. Security.kt already validates the issuer and requires helter in cognito:groups. That check matters more now: a client: none API accepts tokens issued to any client in the pool. Internal-pool tokens now carry registry groups in cognito:groups, so helter members pass.

No compute overrides. The image runs fine as uid 1000 with a read-only root: the JRE and app files only need read access, and the JVM's temp files go to /tmp.

Checks

  • ./gradlew check (Temurin 25.0.5 via mise): pass.
  • docker build then docker run --read-only --tmpfs /tmp --user 1000 with OIDC_WELL_KNOWN_URL set to the pool: starts and loads discovery/JWKS. /health returns 200, and /api/me with no token returns 401. One harmless startup warning: jansi can't load its native lib because Docker mounts --tmpfs noexec. It falls back to plain console output, and the platform's /tmp volume is not a noexec tmpfs.
  • Platform generator (scripts/generate-modules.py from platform main) accepts the new app.yaml. The generated main.tf has client_type = "none" and, in environment, COGNITO_USER_POOL_ID, COGNITO_DOMAIN, COGNITO_ISSUER_URL, COGNITO_WELL_KNOWN_URL and OIDC_WELL_KNOWN_URL = module.auth.well_known_url. As expected for none, there is no client ID or secret, and the only secrets entries are the existing SP/Bring ones.

Depends on

🤖 Generated with Claude Code

Alexanderamiri and others added 2 commits October 23, 2026 18:08
…-only client

- app.yaml auth: pool internal, client none. cupcake-backend only
  verifies tokens issued to frosting's client, so it needs no Cognito
  client of its own.
- auth.env aliases COGNITO_WELL_KNOWN_URL to OIDC_WELL_KNOWN_URL, so the
  hardcoded pool URL is removed from environment.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
@github-actions

Copy link
Copy Markdown

Terraform Plan

🚧 Changes detected — Plan: 22 to add, 0 to change, 0 to destroy.

Plan output
Acquiring state lock. This may take a few moments...

Terraform used the selected providers to generate the following execution
plan. Resource actions are indicated with the following symbols:
  + create
 <= read (data resources)

Terraform will perform the following actions:

  # module.alarms.aws_cloudwatch_metric_alarm.cpu_high will be created
  + resource "aws_cloudwatch_metric_alarm" "cpu_high" {
      + actions_enabled                       = true
      + alarm_actions                         = [
          + "arn:aws:sns:eu-central-1:553637109631:javabin-alerts",
        ]
      + alarm_description                     = "CPU utilization above 80% for cupcake-backend"
      + alarm_name                            = "cupcake-cupcake-backend-cpu-high"
      + arn                                   = (known after apply)
      + comparison_operator                   = "GreaterThanThreshold"
      + dimensions                            = {
          + "ClusterName" = "javabin-platform"
          + "ServiceName" = "cupcake-backend"
        }
      + evaluate_low_sample_count_percentiles = (known after apply)
      + evaluation_periods                    = 3
      + id                                    = (known after apply)
      + metric_name                           = "CPUUtilization"
      + namespace                             = "AWS/ECS"
      + ok_actions                            = [
          + "arn:aws:sns:eu-central-1:553637109631:javabin-alerts",
        ]
      + period                                = 60
      + statistic                             = "Average"
      + tags                                  = {
          + "Name"    = "cupcake-cupcake-backend-cpu-high"
          + "service" = "cupcake-backend"
        }
      + tags_all                              = {
          + "Name"        = "cupcake-cupcake-backend-cpu-high"
          + "environment" = "production"
          + "managed-by"  = "terraform"
          + "repo"        = "javaBin/cupcake"
          + "service"     = "cupcake-backend"
          + "team"        = "cupcake"
        }
      + threshold                             = 80
      + treat_missing_data                    = "missing"
    }

  # module.alarms.aws_cloudwatch_metric_alarm.memory_high will be created
  + resource "aws_cloudwatch_metric_alarm" "memory_high" {
      + actions_enabled                       = true
      + alarm_actions                         = [
          + "arn:aws:sns:eu-central-1:553637109631:javabin-alerts",
        ]
      + alarm_description                     = "Memory utilization above 80% for cupcake-backend"
      + alarm_name                            = "cupcake-cupcake-backend-memory-high"
      + arn                                   = (known after apply)
      + comparison_operator                   = "GreaterThanThreshold"
      + dimensions                            = {
          + "ClusterName" = "javabin-platform"
          + "ServiceName" = "cupcake-backend"
        }
      + evaluate_low_sample_count_percentiles = (known after apply)
      + evaluation_periods                    = 3
      + id                                    = (known after apply)
      + metric_name                           = "MemoryUtilization"
      + namespace                             = "AWS/ECS"
      + ok_actions                            = [
          + "arn:aws:sns:eu-central-1:553637109631:javabin-alerts",
        ]
      + period                                = 60
      + statistic                             = "Average"
      + tags                                  = {
          + "Name"    = "cupcake-cupcake-backend-memory-high"
          + "service" = "cupcake-backend"
        }
      + tags_all                              = {
          + "Name"        = "cupcake-cupcake-backend-memory-high"
          + "environment" = "production"
          + "managed-by"  = "terraform"
          + "repo"        = "javaBin/cupcake"
          + "service"     = "cupcake-backend"
          + "team"        = "cupcake"
        }
      + threshold                             = 80
      + treat_missing_data                    = "missing"
    }

  # module.alarms.aws_cloudwatch_metric_alarm.target_5xx[0] will be created
  + resource "aws_cloudwatch_metric_alarm" "target_5xx" {
      + actions_enabled                       = true
      + alarm_actions                         = [
          + "arn:aws:sns:eu-central-1:553637109631:javabin-alerts",
        ]
      + alarm_description                     = "5xx errors above 10 in 5min for cupcake-backend"
      + alarm_name                            = "cupcake-cupcake-backend-5xx"
      + arn                                   = (known after apply)
      + comparison_operator                   = "GreaterThanThreshold"
      + dimensions                            = (known after apply)
      + evaluate_low_sample_count_percentiles = (known after apply)
      + evaluation_periods                    = 2
      + id                                    = (known after apply)
      + metric_name                           = "HTTPCode_Target_5XX_Count"
      + namespace                             = "AWS/ApplicationELB"
      + ok_actions                            = [
          + "arn:aws:sns:eu-central-1:553637109631:javabin-alerts",
        ]
      + period                                = 300
      + statistic                             = "Sum"
      + tags                                  = {
          + "Name"    = "cupcake-cupcake-backend-5xx"
          + "service" = "cupcake-backend"
        }
      + tags_all                              = {
          + "Name"        = "cupcake-cupcake-backend-5xx"
          + "environment" = "production"
          + "managed-by"  = "terraform"
          + "repo"        = "javaBin/cupcake"
          + "service"     = "cupcake-backend"
          + "team"        = "cupcake"
        }
      + threshold                             = 10
      + treat_missing_data                    = "notBreaching"
    }

  # module.alarms.aws_cloudwatch_metric_alarm.unhealthy_targets[0] will be created
  + resource "aws_cloudwatch_metric_alarm" "unhealthy_targets" {
      + actions_enabled                       = true
      + alarm_actions                         = [
          + "arn:aws:sns:eu-central-1:553637109631:javabin-alerts",
        ]
      + alarm_description                     = "Unhealthy targets detected for cupcake-backend"
      + alarm_name                            = "cupcake-cupcake-backend-unhealthy-targets"
      + arn                                   = (known after apply)
      + comparison_operator                   = "GreaterThanThreshold"
      + dimensions                            = (known after apply)
      + evaluate_low_sample_count_percentiles = (known after apply)
      + evaluation_periods                    = 2
      + id                                    = (known after apply)
      + metric_name                           = "UnHealthyHostCount"
      + namespace                             = "AWS/ApplicationELB"
      + ok_actions                            = [
          + "arn:aws:sns:eu-central-1:553637109631:javabin-alerts",
        ]
      + period                                = 60
      + statistic                             = "Maximum"
      + tags                                  = {
          + "Name"    = "cupcake-cupcake-backend-unhealthy-targets"
          + "service" = "cupcake-backend"
        }
      + tags_all                              = {
          + "Name"        = "cupcake-cupcake-backend-unhealthy-targets"
          + "environment" = "production"
          + "managed-by"  = "terraform"
          + "repo"        = "javaBin/cupcake"
          + "service"     = "cupcake-backend"
          + "team"        = "cupcake"
        }
      + threshold                             = 0
      + treat_missing_data                    = "missing"
    }

  # module.ecr.aws_ecr_lifecycle_policy.this will be created
  + resource "aws_ecr_lifecycle_policy" "this" {
      + id          = (known after apply)
      + policy      = jsonencode(
            {
              + rules = [
                  + {
                      + action       = {
                          + type = "expire"
                        }
                      + description  = "Keep last 5 semver releases"
                      + rulePriority = 1
                      + selection    = {
                          + countNumber   = 5
                          + countType     = "imageCountMoreThan"
                          + tagPrefixList = [
                              + "v",
                            ]
                          + tagStatus     = "tagged"
                        }
                    },
                  + {
                      + action       = {
                          + type = "expire"
                        }
                      + description  = "Keep last 3 main builds"
                      + rulePriority = 2
                      + selection    = {
                          + countNumber   = 3
                          + countType     = "imageCountMoreThan"
                          + tagPrefixList = [
                              + "main-",
                              + "master-",
                            ]
                          + tagStatus     = "tagged"
                        }
                    },
                  + {
                      + action       = {
                          + type = "expire"
                        }
                      + description  = "Expire untagged images after 1 day(s)"
                      + rulePriority = 3
                      + selection    = {
                          + countNumber = 1
                          + countType   = "sinceImagePushed"
                          + countUnit   = "days"
                          + tagStatus   = "untagged"
                        }
                    },
                  + {
                      + action       = {
                          + type = "expire"
                        }
                      + description  = "Expire other tags after 7 days"
                      + rulePriority = 4
                      + selection    = {
                          + countNumber = 7
                          + countType   = "sinceImagePushed"
                          + countUnit   = "days"
                          + tagStatus   = "any"
                        }
                    },
                ]
            }
        )
      + registry_id = (known after apply)
      + repository  = "cupcake-cupcake-backend"
    }

  # module.ecr.aws_ecr_repository.this will be created
  + resource "aws_ecr_repository" "this" {
      + arn                  = (known after apply)
      + id                   = (known after apply)
      + image_tag_mutability = "MUTABLE"
      + name                 = "cupcake-cupcake-backend"
      + registry_id          = (known after apply)
      + repository_url       = (known after apply)
      + tags                 = {
          + "Name" = "cupcake-cupcake-backend"
        }
      + tags_all             = {
          + "Name"        = "cupcake-cupcake-backend"
          + "environment" = "production"
          + "managed-by"  = "terraform"
          + "repo"        = "javaBin/cupcake"
          + "service"     = "cupcake-backend"
          + "team"        = "cupcake"
        }

      + encryption_configuration {
          + encryption_type = "AES256"
          + kms_key         = (known after apply)
        }

      + image_scanning_configuration {
          + scan_on_push = true
        }
    }

  # module.routing.aws_lb_listener_rule.this will be created
  + resource "aws_lb_listener_rule" "this" {
      + arn          = (known after apply)
      + id           = (known after apply)
      + listener_arn = "arn:aws:elasticloadbalancing:eu-central-1:553637109631:listener/app/javabin-platform-alb/bec1dd43ab8341b9/500c9c2b4186bf45"
      + priority     = 101
      + tags_all     = {
          + "environment" = "production"
          + "managed-by"  = "terraform"
          + "repo"        = "javaBin/cupcake"
          + "service"     = "cupcake-backend"
          + "team"        = "cupcake"
        }

      + action {
          + order            = (known after apply)
          + target_group_arn = (known after apply)
          + type             = "forward"
        }

      + condition {
          + host_header {
              + values = [
                  + "cupcake-backend.javazone.no",
                ]
            }
        }
    }

  # module.routing.aws_lb_target_group.this will be created
  + resource "aws_lb_target_group" "this" {
      + arn                                = (known after apply)
      + arn_suffix                         = (known after apply)
      + connection_termination             = (known after apply)
      + deregistration_delay               = "30"
      + id                                 = (known after apply)
      + ip_address_type                    = (known after apply)
      + lambda_multi_value_headers_enabled = false
      + load_balancer_arns                 = (known after apply)
      + load_balancing_algorithm_type      = "round_robin"
      + load_balancing_anomaly_mitigation  = (known after apply)
      + load_balancing_cross_zone_enabled  = (known after apply)
      + name                               = "cupcake-cupcake-backend"
      + name_prefix                        = (known after apply)
      + port                               = 8080
      + preserve_client_ip                 = (known after apply)
      + protocol                           = "HTTP"
      + protocol_version                   = (known after apply)
      + proxy_protocol_v2                  = false
      + slow_start                         = 0
      + tags                               = {
          + "Name" = "cupcake-cupcake-backend"
        }
      + tags_all                           = {
          + "Name"        = "cupcake-cupcake-backend"
          + "environment" = "production"
          + "managed-by"  = "terraform"
          + "repo"        = "javaBin/cupcake"
          + "service"     = "cupcake-backend"
          + "team"        = "cupcake"
        }
      + target_type                        = "ip"
      + vpc_id                             = "vpc-0cd3502de2527a310"

      + health_check {
          + enabled             = true
          + healthy_threshold   = 2
          + interval            = 10
          + matcher             = "200"
          + path                = "/health"
          + port                = "traffic-port"
          + protocol            = "HTTP"
          + timeout             = 5
          + unhealthy_threshold = 2
        }
    }

  # module.routing.aws_route53_record.this will be created
  + resource "aws_route53_record" "this" {
      + allow_overwrite = (known after apply)
      + fqdn            = (known after apply)
      + id              = (known after apply)
      + name            = "cupcake-backend.javazone.no"
      + type            = "A"
      + zone_id         = "Z09335963LMV0Z5QB9L45"

      + alias {
          + evaluate_target_health = true
          + name                   = "javabin-platform-alb-375406315.eu-central-1.elb.amazonaws.com"
          + zone_id                = "Z215JYRZR1TBD5"
        }
    }

  # module.secret_bring_api_key.data.aws_iam_policy_document.access will be read during apply
  # (config refers to values not yet known)
 <= data "aws_iam_policy_document" "access" {
      + id            = (known after apply)
      + json          = (known after apply)
      + minified_json = (known after apply)

      + statement {
          + actions   = [
              + "ssm:GetParameter",
              + "ssm:GetParameters",
            ]
          + effect    = "Allow"
          + resources = [
              + (known after apply),
            ]
          + sid       = "SSMSecretRead"
        }
    }

  # module.secret_bring_api_key.aws_ssm_parameter.this will be created
  + resource "aws_ssm_parameter" "this" {
      + arn            = (known after apply)
      + data_type      = (known after apply)
      + has_value_wo   = (known after apply)
      + id             = (known after apply)
      + insecure_value = (known after apply)
      + key_id         = (known after apply)
      + name           = "/javabin/apps/cupcake/cupcake-backend/bring_api_key"
      + tags           = {
          + "Name"    = "cupcake-bring_api_key"
          + "service" = "cupcake-backend"
        }
      + tags_all       = {
          + "Name"        = "cupcake-bring_api_key"
          + "environment" = "production"
          + "managed-by"  = "terraform"
          + "repo"        = "javaBin/cupcake"
          + "service"     = "cupcake-backend"
          + "team"        = "cupcake"
        }
      + tier           = (known after apply)
      + type           = "SecureString"
      + value          = (sensitive value)
      + version        = (known after apply)
    }

  # module.secret_bring_api_user.data.aws_iam_policy_document.access will be read during apply
  # (config refers to values not yet known)
 <= data "aws_iam_policy_document" "access" {
      + id            = (known after apply)
      + json          = (known after apply)
      + minified_json = (known after apply)

      + statement {
          + actions   = [
              + "ssm:GetParameter",
              + "ssm:GetParameters",
            ]
          + effect    = "Allow"
          + resources = [
              + (known after apply),
            ]
          + sid       = "SSMSecretRead"
        }
    }

  # module.secret_bring_api_user.aws_ssm_parameter.this will be created
  + resource "aws_ssm_parameter" "this" {
      + arn            = (known after apply)
      + data_type      = (known after apply)
      + has_value_wo   = (known after apply)
      + id             = (known after apply)
      + insecure_value = (known after apply)
      + key_id         = (known after apply)
      + name           = "/javabin/apps/cupcake/cupcake-backend/bring_api_user"
      + tags           = {
          + "Name"    = "cupcake-bring_api_user"
          + "service" = "cupcake-backend"
        }
      + tags_all       = {
          + "Name"        = "cupcake-bring_api_user"
          + "environment" = "production"
          + "managed-by"  = "terraform"
          + "repo"        = "javaBin/cupcake"
          + "service"     = "cupcake-backend"
          + "team"        = "cupcake"
        }
      + tier           = (known after apply)
      + type           = "SecureString"
      + value          = (sensitive value)
      + version        = (known after apply)
    }

  # module.secret_sp_password.data.aws_iam_policy_document.access will be read during apply
  # (config refers to values not yet known)
 <= data "aws_iam_policy_document" "access" {
      + id            = (known after apply)
      + json          = (known after apply)
      + minified_json = (known after apply)

      + statement {
          + actions   = [
              + "ssm:GetParameter",
              + "ssm:GetParameters",
            ]
          + effect    = "Allow"
          + resources = [
              + (known after apply),
            ]
          + sid       = "SSMSecretRead"
        }
    }

  # module.secret_sp_password.aws_ssm_parameter.this will be created
  + resource "aws_ssm_parameter" "this" {
      + arn            = (known after apply)
      + data_type      = (known after apply)
      + has_value_wo   = (known after apply)
      + id             = (known after apply)
      + insecure_value = (known after apply)
      + key_id         = (known after apply)
      + name           = "/javabin/apps/cupcake/cupcake-backend/sp_password"
      + tags           = {
          + "Name"    = "cupcake-sp_password"
          + "service" = "cupcake-backend"
        }
      + tags_all       = {
          + "Name"        = "cupcake-sp_password"
          + "environment" = "production"
          + "managed-by"  = "terraform"
          + "repo"        = "javaBin/cupcake"
          + "service"     = "cupcake-backend"
          + "team"        = "cupcake"
        }
      + tier           = (known after apply)
      + type           = "SecureString"
      + value          = (sensitive value)
      + version        = (known after apply)
    }

  # module.secret_sp_user.data.aws_iam_policy_document.access will be read during apply
  # (config refers to values not yet known)
 <= data "aws_iam_policy_document" "access" {
      + id            = (known after apply)
      + json          = (known after apply)
      + minified_json = (known after apply)

      + statement {
          + actions   = [
              + "ssm:GetParameter",
              + "ssm:GetParameters",
            ]
          + effect    = "Allow"
          + resources = [
              + (known after apply),
            ]
          + sid       = "SSMSecretRead"
        }
    }

  # module.secret_sp_user.aws_ssm_parameter.this will be created
  + resource "aws_ssm_parameter" "this" {
      + arn            = (known after apply)
      + data_type      = (known after apply)
      + has_value_wo   = (known after apply)
      + id             = (known after apply)
      + insecure_value = (known after apply)
      + key_id         = (known after apply)
      + name           = "/javabin/apps/cupcake/cupcake-backend/sp_user"
      + tags           = {
          + "Name"    = "cupcake-sp_user"
          + "service" = "cupcake-backend"
        }
      + tags_all       = {
          + "Name"        = "cupcake-sp_user"
          + "environment" = "production"
          + "managed-by"  = "terraform"
          + "repo"        = "javaBin/cupcake"
          + "service"     = "cupcake-backend"
          + "team"        = "cupcake"
        }
      + tier           = (known after apply)
      + type           = "SecureString"
      + value          = (sensitive value)
      + version        = (known after apply)
    }

  # module.service.aws_cloudwatch_log_group.this will be created
  + resource "aws_cloudwatch_log_group" "this" {
      + arn               = (known after apply)
      + id                = (known after apply)
      + log_group_class   = (known after apply)
      + name              = "/ecs/cupcake/cupcake-backend"
      + name_prefix       = (known after apply)
      + retention_in_days = 30
      + skip_destroy      = false
      + tags_all          = {
          + "environment" = "production"
          + "managed-by"  = "terraform"
          + "repo"        = "javaBin/cupcake"
          + "service"     = "cupcake-backend"
          + "team"        = "cupcake"
        }
    }

  # module.service.aws_ecs_service.this will be created
  + resource "aws_ecs_service" "this" {
      + availability_zone_rebalancing      = "DISABLED"
      + cluster                            = "arn:aws:ecs:eu-central-1:553637109631:cluster/javabin-platform"
      + deployment_maximum_percent         = 200
      + deployment_minimum_healthy_percent = 100
      + desired_count                      = 1
      + enable_ecs_managed_tags            = true
      + enable_execute_command             = false
      + iam_role                           = (known after apply)
      + id                                 = (known after apply)
      + launch_type                        = "FARGATE"
      + name                               = "cupcake-cupcake-backend"
      + platform_version                   = (known after apply)
      + propagate_tags                     = "SERVICE"
      + scheduling_strategy                = "REPLICA"
      + tags_all                           = {
          + "environment" = "production"
          + "managed-by"  = "terraform"
          + "repo"        = "javaBin/cupcake"
          + "service"     = "cupcake-backend"
          + "team"        = "cupcake"
        }
      + task_definition                    = (known after apply)
      + triggers                           = (known after apply)
      + wait_for_steady_state              = false

      + deployment_circuit_breaker {
          + enable   = true
          + rollback = true
        }

      + network_configuration {
          + assign_public_ip = false
          + security_groups  = [
              + "sg-0df9a0a3a22548c62",
            ]
          + subnets          = [
              + "subnet-0329ad20dc025c693",
              + "subnet-09ee21336f809f3c9",
            ]
        }
    }

  # module.service.aws_ecs_task_definition.this will be created
  + resource "aws_ecs_task_definition" "this" {
      + arn                      = (known after apply)
      + arn_without_revision     = (known after apply)
      + container_definitions    = (known after apply)
      + cpu                      = "512"
      + enable_fault_injection   = (known after apply)
      + execution_role_arn       = "arn:aws:iam::553637109631:role/javabin-ecs-exec-cupcake"
      + family                   = "cupcake-cupcake-backend"
      + id                       = (known after apply)
      + memory                   = "1024"
      + network_mode             = "awsvpc"
      + requires_compatibilities = [
          + "FARGATE",
        ]
      + revision                 = (known after apply)
      + skip_destroy             = false
      + tags_all                 = {
          + "environment" = "production"
          + "managed-by"  = "terraform"
          + "repo"        = "javaBin/cupcake"
          + "service"     = "cupcake-backend"
          + "team"        = "cupcake"
        }
      + task_role_arn            = (known after apply)
      + track_latest             = false

      + volume {
          + configure_at_launch = (known after apply)
          + name                = "tmp"
        }
    }

  # module.task_role.aws_iam_role.this will be created
  + resource "aws_iam_role" "this" {
      + arn                   = (known after apply)
      + assume_role_policy    = jsonencode(
            {
              + Statement = [
                  + {
                      + Action    = "sts:AssumeRole"
                      + Effect    = "Allow"
                      + Principal = {
                          + Service = [
                              + "ecs-tasks.amazonaws.com",
                            ]
                        }
                    },
                ]
              + Version   = "2012-10-17"
            }
        )
      + create_date           = (known after apply)
      + force_detach_policies = false
      + id                    = (known after apply)
      + managed_policy_arns   = (known after apply)
      + max_session_duration  = 3600
      + name                  = "cupcake-cupcake-backend"
      + name_prefix           = (known after apply)
      + path                  = "/"
      + permissions_boundary  = "arn:aws:iam::553637109631:policy/javabin-developer-boundary"
      + tags                  = {
          + "Name" = "cupcake-cupcake-backend"
        }
      + tags_all              = {
          + "Name"        = "cupcake-cupcake-backend"
          + "environment" = "production"
          + "managed-by"  = "terraform"
          + "repo"        = "javaBin/cupcake"
          + "service"     = "cupcake-backend"
          + "team"        = "cupcake"
        }
      + unique_id             = (known after apply)
    }

  # module.task_role.aws_iam_role_policy.additional["secret_bring_api_key"] will be created
  + resource "aws_iam_role_policy" "additional" {
      + id          = (known after apply)
      + name        = "secret_bring_api_key"
      + name_prefix = (known after apply)
      + policy      = (known after apply)
      + role        = (known after apply)
    }

  # module.task_role.aws_iam_role_policy.additional["secret_bring_api_user"] will be created
  + resource "aws_iam_role_policy" "additional" {
      + id          = (known after apply)
      + name        = "secret_bring_api_user"
      + name_prefix = (known after apply)
      + policy      = (known after apply)
      + role        = (known after apply)
    }

  # module.task_role.aws_iam_role_policy.additional["secret_sp_password"] will be created
  + resource "aws_iam_role_policy" "additional" {
      + id          = (known after apply)
      + name        = "secret_sp_password"
      + name_prefix = (known after apply)
      + policy      = (known after apply)
      + role        = (known after apply)
    }

  # module.task_role.aws_iam_role_policy.additional["secret_sp_user"] will be created
  + resource "aws_iam_role_policy" "additional" {
      + id          = (known after apply)
      + name        = "secret_sp_user"
      + name_prefix = (known after apply)
      + policy      = (known after apply)
      + role        = (known after apply)
    }

  # module.task_role.aws_iam_role_policy.logs will be created
  + resource "aws_iam_role_policy" "logs" {
      + id          = (known after apply)
      + name        = "cloudwatch-logs"
      + name_prefix = (known after apply)
      + policy      = jsonencode(
            {
              + Statement = [
                  + {
                      + Action   = [
                          + "logs:CreateLogStream",
                          + "logs:PutLogEvents",
                        ]
                      + Effect   = "Allow"
                      + Resource = "arn:aws:logs:eu-central-1:553637109631:log-group:/ecs/cupcake/cupcake-backend:*"
                    },
                ]
              + Version   = "2012-10-17"
            }
        )
      + role        = (known after apply)
    }

Plan: 22 to add, 0 to change, 0 to destroy.

Changes to Outputs:
  + ecr_url       = (known after apply)
  + log_group     = "/ecs/cupcake/cupcake-backend"
  + service_name  = "cupcake-cupcake-backend"
  + service_url   = "https://cupcake-backend.javazone.no"
  + task_role_arn = (known after apply)

─────────────────────────────────────────────────────────────────────────────

Saved the plan to: tfplan

To perform exactly these actions, run the following command to apply:
    terraform apply "tfplan"

LLM Review

Risk: 🟢 LOW

Plan creates 22 new resources (no changes or destroys) for the cupcake-backend service: ECS Fargate service and task definition, ECR repo, ALB target group, listener rule and Route53 record, SSM SecureString parameters, IAM task role, CloudWatch log group and alarms.

  • 💥 [destruction] No resources are changed or destroyed; all 22 actions are creates, so there is no data loss or force-replacement risk.
  • 🔒 [security] ECS service is not assigned a public IP (assign_public_ip = false) and runs in private subnets behind the ALB. The target group is HTTP on port 8080 inside the VPC, and the ALB listener rule routes by host header. The security group is not shown in detail, so its ingress rules should be checked to confirm it only admits traffic from the ALB.
  • 🔒 [security] The task role uses the permissions boundary javabin-developer-boundary, which is good. Its inline policies are limited to CloudWatch Logs for one log group and SSM GetParameter/GetParameters (Allow) on four cupcake SSM parameters. No broad or wildcard grants are visible.
  • 🔒 [security] Four SecureString SSM parameters are created with values supplied as sensitive inputs. Make sure those values are not committed in code and are supplied through a secure source. The ECR repository uses MUTABLE image tags, which allows tags to be overwritten; consider IMMUTABLE for release tags.
  • 💰 [cost] Ongoing costs are modest: one Fargate task (512 CPU / 1024 MB, desired count 1), a new ALB target group and listener rule (the ALB itself already exists), ECR storage (lifecycle policies are in place), CloudWatch logs with 30-day retention, and six CloudWatch alarms. No NAT gateway, RDS or new load balancer is created.

@Alexanderamiri
Alexanderamiri merged commit 73d9d07 into main Oct 23, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant