Skip to content

Fix Bedrock IAM for cross-region inference profiles#47

Merged
Alexanderamiri merged 2 commits into
mainfrom
fix/bedrock-cross-region-iam
Mar 12, 2026
Merged

Fix Bedrock IAM for cross-region inference profiles#47
Alexanderamiri merged 2 commits into
mainfrom
fix/bedrock-cross-region-iam

Conversation

@Alexanderamiri
Copy link
Copy Markdown
Member

Summary

Cross-region inference profiles (eu.anthropic.*) route to foundation models in EU destination regions (eu-central-1, eu-north-1, eu-west-1, eu-west-3). IAM requires explicit access to the destination foundation model ARNs, not just the inference profile.

This was hidden by the old *:* infra role and has been a latent bug on the Lambda roles (slack-alert, cost-report) — they'd fail if Bedrock routed to eu-north-1.

Adds a second IAM statement with foundation model ARNs for all EU destination regions, scoped via bedrock:InferenceProfileArn condition so they only work through our inference profiles.

Fixes: ci-infra-plan role, slack-alert Lambda role, cost-report Lambda role.

Test plan

  • Plan job succeeds with LLM review (Bedrock call works)
  • Apply creates the IAM policy changes on Lambda roles

Cross-region inference profiles (eu.anthropic.*) route to foundation
models in eu-central-1, eu-north-1, eu-west-1, eu-west-3. IAM requires
explicit access to the destination model ARNs, not just the inference
profile. Condition scopes foundation model access to only work via our
inference profiles.

Fixes plan role, slack-alert role, and cost-report role.
@github-actions
Copy link
Copy Markdown

Terraform Plan

Changes detected — review required.

Plan output
module.lambdas.data.archive_file.compliance_reporter: Reading...
module.lambdas.data.archive_file.daily_cost_check: Reading...
module.lambdas.data.archive_file.apply_gate: Reading...
module.lambdas.data.archive_file.cost_report: Reading...
module.lambdas.data.archive_file.override_cleanup: Reading...
module.lambdas.data.archive_file.compliance_reporter: Read complete after 0s [id=323449bf04f46a46a9d9c440212010f551146129]
module.lambdas.data.archive_file.slack_alert: Reading...
module.lambdas.data.archive_file.apply_gate: Read complete after 0s [id=064e560a455ec8b2a30253cd815a820ad002376f]
module.lambdas.data.archive_file.team_provisioner: Reading...
module.lambdas.data.archive_file.override_cleanup: Read complete after 0s [id=08e23a8ca152c50d8321f7b9f15d3ebbdc97849d]
module.lambdas.data.archive_file.cost_report: Read complete after 0s [id=9844b77d6a3a4efa27510589543ad38c835cc662]
module.lambdas.data.archive_file.daily_cost_check: Read complete after 0s [id=da9c5f6e85719534eb3d93b02eca8a30fbbfeb34]
module.lambdas.data.archive_file.slack_alert: Read complete after 0s [id=556fd3d4c8de13c380a237a4f9f9d04ccbee0d4b]
module.lambdas.data.archive_file.team_provisioner: Read complete after 0s [id=22dd605c5fa5b4949ce29a38af706d5b282e1688]
module.compute.aws_ecs_cluster.main: Refreshing state... [id=arn:aws:ecs:eu-central-1:553637109631:cluster/javabin-platform]
module.identity.aws_cognito_user_pool.external: Refreshing state... [id=eu-central-1_gdFOsE4EM]
module.iam.aws_iam_role.ecs_execution: Refreshing state... [id=javabin-ecs-execution]
module.iam.aws_iam_role_policy.ci_registry_lambda: Refreshing state... [id=javabin-ci-registry:invoke-team-provisioner]
module.networking.aws_vpc.main: Refreshing state... [id=vpc-0cd3502de2527a310]
module.monitoring.aws_cloudwatch_event_rule.guardduty_findings: Refreshing state... [id=javabin-guardduty-findings]
module.networking.data.aws_availability_zones.available: Reading...
module.ingress.aws_acm_certificate.wildcard: Refreshing state... [id=arn:aws:acm:eu-central-1:553637109631:certificate/9b79f56a-3719-4c62-8970-6f08985a7e5b]
module.identity.aws_cognito_user_pool.internal: Refreshing state... [id=eu-central-1_Icikv3dtD]
module.networking.aws_eip.nat: Refreshing state... [id=eipalloc-0764f0a1a3c80dce1]
module.monitoring.aws_guardduty_detector.main: Refreshing state... [id=f1df02cf279e4b5986ce1e9bcb3af9c5]
module.monitoring.aws_s3_bucket.config_logs: Refreshing state... [id=javabin-config-553637109631]
module.monitoring.aws_dynamodb_table.alert_dedup: Refreshing state... [id=javabin-alert-dedup]
module.networking.data.aws_availability_zones.available: Read complete after 0s [id=eu-central-1]
module.monitoring.aws_cloudwatch_event_rule.resource_creation: Refreshing state... [id=javabin-resource-creation]
module.lambdas.aws_cloudwatch_event_rule.daily_cost_check_schedule: Refreshing state... [id=javabin-daily-cost-check-schedule]
module.monitoring.aws_sns_topic.alerts: Refreshing state... [id=arn:aws:sns:eu-central-1:553637109631:javabin-alerts]
module.monitoring.aws_sns_topic.security: Refreshing state... [id=arn:aws:sns:eu-central-1:553637109631:javabin-security]
module.monitoring.aws_cloudwatch_event_rule.securityhub_findings: Refreshing state... [id=javabin-securityhub-findings]
module.lambdas.aws_iam_role.daily_cost_check: Refreshing state... [id=javabin-daily-cost-check]
module.monitoring.aws_iam_role.config_role: Refreshing state... [id=javabin-config-role]
module.lambdas.aws_iam_role.team_provisioner: Refreshing state... [id=javabin-team-provisioner]
module.lambdas.aws_iam_role.apply_gate: Refreshing state... [id=javabin-apply-gate]
module.lambdas.aws_cloudwatch_event_rule.securityhub_summary_schedule: Refreshing state... [id=javabin-securityhub-summary-schedule]
module.monitoring.aws_cloudwatch_event_rule.config_compliance: Refreshing state... [id=javabin-config-compliance-change]
module.monitoring.aws_cloudwatch_event_rule.iam_changes: Refreshing state... [id=javabin-iam-changes]
module.iam.aws_iam_policy.developer_boundary: Refreshing state... [id=arn:aws:iam::553637109631:policy/javabin-developer-boundary]
module.lambdas.aws_cloudwatch_event_rule.cost_report_schedule: Refreshing state... [id=javabin-cost-report-schedule]
module.monitoring.aws_securityhub_account.main: Refreshing state... [id=553637109631]
module.lambdas.aws_cloudwatch_event_rule.compliance_reporter_trigger: Refreshing state... [id=javabin-compliance-reporter-trigger]
module.lambdas.aws_iam_role.compliance_reporter: Refreshing state... [id=javabin-compliance-reporter]
module.lambdas.aws_iam_role.slack_alert: Refreshing state... [id=javabin-slack-alert]
module.monitoring.aws_cloudwatch_event_rule.console_login: Refreshing state... [id=javabin-console-login]
module.monitoring.aws_ce_anomaly_monitor.main: Refreshing state... [id=arn:aws:ce::553637109631:anomalymonitor/3609b3f1-c834-444e-a218-02ac6da1cb4d]
module.iam.data.aws_iam_openid_connect_provider.github: Reading...
module.ingress.data.aws_route53_zone.main: Reading...
module.monitoring.aws_cloudwatch_event_rule.resource_modification: Refreshing state... [id=javabin-resource-modification]
module.iam.data.aws_iam_openid_connect_provider.github: Read complete after 0s [id=arn:aws:iam::553637109631:oidc-provider/token.actions.githubusercontent.com]
module.lambdas.aws_iam_role.cost_report: Refreshing state... [id=javabin-cost-report]
module.lambdas.aws_cloudwatch_event_rule.override_cleanup_schedule: Refreshing state... [id=javabin-override-cleanup-schedule]
module.lambdas.aws_iam_role.override_cleanup: Refreshing state... [id=javabin-override-cleanup]
module.compute.aws_ecr_repository.ci["jvm"]: Refreshing state... [id=javabin-ci-jvm]
module.compute.aws_ecr_repository.ci["ts"]: Refreshing state... [id=javabin-ci-ts]
module.compute.aws_ecr_repository.ci["platform"]: Refreshing state... [id=javabin-ci-platform]
module.iam.aws_iam_role_policy.ecs_execution_secrets: Refreshing state... [id=javabin-ecs-execution:secrets-read]
module.iam.aws_iam_role_policy_attachment.ecs_execution_base: Refreshing state... [id=javabin-ecs-execution-20260307162856804400000004]
module.compute.aws_ecs_cluster_capacity_providers.main: Refreshing state... [id=javabin-platform]
module.monitoring.aws_guardduty_detector_feature.runtime_monitoring: Refreshing state... [id=f1df02cf279e4b5986ce1e9bcb3af9c5/RUNTIME_MONITORING]
module.identity.aws_cognito_user_pool_domain.internal: Refreshing state... [id=javabin-internal]
module.monitoring.aws_iam_role_policy_attachment.config_role: Refreshing state... [id=javabin-config-role-20260307162900971300000009]
module.ingress.data.aws_route53_zone.main: Read complete after 1s [id=Z09335963LMV0Z5QB9L45]
module.monitoring.aws_config_configuration_recorder.main: Refreshing state... [id=javabin-recorder]
module.lambdas.aws_iam_role_policy_attachment.daily_cost_check_logs: Refreshing state... [id=javabin-daily-cost-check-20260307162856210400000002]
module.lambdas.aws_iam_role_policy.daily_cost_check: Refreshing state... [id=javabin-daily-cost-check:javabin-daily-cost-check]
module.lambdas.aws_lambda_function.daily_cost_check: Refreshing state... [id=javabin-daily-cost-check]
module.lambdas.aws_iam_role_policy_attachment.team_provisioner_logs: Refreshing state... [id=javabin-team-provisioner-20260307162856464600000003]
module.monitoring.aws_sns_topic_policy.alerts: Refreshing state... [id=arn:aws:sns:eu-central-1:553637109631:javabin-alerts]
module.lambdas.aws_iam_role_policy_attachment.apply_gate_logs: Refreshing state... [id=javabin-apply-gate-20260310000556680800000001]
module.lambdas.aws_iam_role_policy.apply_gate: Refreshing state... [id=javabin-apply-gate:javabin-apply-gate]
module.lambdas.aws_lambda_function.apply_gate: Refreshing state... [id=javabin-apply-gate]
module.monitoring.aws_cloudwatch_event_target.securityhub_findings_sns: Refreshing state... [id=javabin-securityhub-findings-send-to-security-sns]
module.monitoring.aws_cloudwatch_event_target.resource_creation_sns: Refreshing state... [id=javabin-resource-creation-send-to-security-sns]
module.monitoring.aws_cloudwatch_event_target.guardduty_findings_sns: Refreshing state... [id=javabin-guardduty-findings-send-to-security-sns]
module.monitoring.aws_sns_topic_policy.security: Refreshing state... [id=arn:aws:sns:eu-central-1:553637109631:javabin-security]
module.monitoring.aws_cloudwatch_event_target.config_compliance_sns: Refreshing state... [id=javabin-config-compliance-change-send-to-security-sns]
module.monitoring.aws_cloudwatch_event_target.iam_changes_sns: Refreshing state... [id=javabin-iam-changes-send-to-security-sns]
module.lambdas.aws_iam_role_policy_attachment.slack_alert_logs: Refreshing state... [id=javabin-slack-alert-20260307162858376500000008]
module.iam.aws_iam_role.ci_infra: Refreshing state... [id=javabin-ci-infra]
module.iam.aws_iam_role.ci_infra_plan: Preparing import... [id=javabin-ci-infra-plan]
module.iam.aws_iam_role.ci_infra_plan: Refreshing state... [id=javabin-ci-infra-plan]
module.iam.aws_iam_role.ci_override_approver: Refreshing state... [id=javabin-ci-override-approver]
module.iam.aws_iam_role.ci_apply_gate: Refreshing state... [id=javabin-ci-apply-gate]
module.iam.aws_iam_role.ci_registry: Refreshing state... [id=javabin-ci-registry]
module.iam.aws_iam_role.ci_deploy["platform-test-app"]: Refreshing state... [id=javabin-ci-deploy-platform-test-app]
module.lambdas.aws_iam_role_policy_attachment.compliance_reporter_logs: Refreshing state... [id=javabin-compliance-reporter-20260307162857302300000005]
module.lambdas.aws_lambda_function.compliance_reporter: Refreshing state... [id=javabin-compliance-reporter]
module.lambdas.aws_iam_role_policy.compliance_reporter: Refreshing state... [id=javabin-compliance-reporter:javabin-compliance-reporter]
module.monitoring.aws_cloudwatch_event_target.console_login_sns: Refreshing state... [id=javabin-console-login-send-to-security-sns]
module.lambdas.aws_iam_role_policy.cost_report: Refreshing state... [id=javabin-cost-report:javabin-cost-report]
module.lambdas.aws_lambda_function.cost_report: Refreshing state... [id=javabin-cost-report]
module.lambdas.aws_iam_role_policy_attachment.cost_report_logs: Refreshing state... [id=javabin-cost-report-20260307162857662100000006]
module.lambdas.aws_lambda_function.override_cleanup: Refreshing state... [id=javabin-override-cleanup]
module.lambdas.aws_iam_role_policy.override_cleanup: Refreshing state... [id=javabin-override-cleanup:javabin-override-cleanup]
module.lambdas.aws_iam_role_policy_attachment.override_cleanup_logs: Refreshing state... [id=javabin-override-cleanup-20260307162858005200000007]
module.monitoring.aws_ce_anomaly_subscription.alerts: Refreshing state... [id=arn:aws:ce::553637109631:anomalysubscription/f6b079c9-5174-43b7-85f3-dde533995482]
module.networking.aws_internet_gateway.main: Refreshing state... [id=igw-07b193bea823a7f69]
module.networking.aws_subnet.public_b: Refreshing state... [id=subnet-0eb818326ee94a266]
module.networking.aws_security_group.ecs_tasks: Refreshing state... [id=sg-0df9a0a3a22548c62]
module.networking.aws_subnet.private_b: Refreshing state... [id=subnet-09ee21336f809f3c9]
module.networking.aws_subnet.private_a: Refreshing state... [id=subnet-0329ad20dc025c693]
module.networking.aws_subnet.public_a: Refreshing state... [id=subnet-0f6bfec917146b856]
module.networking.aws_security_group.alb: Refreshing state... [id=sg-061000c0fa68a41b7]
module.monitoring.aws_cloudwatch_event_target.resource_modification_sns: Refreshing state... [id=javabin-resource-modification-send-to-security-sns]
module.ingress.aws_route53_record.acm_validation["*.javazone.no"]: Refreshing state... [id=Z09335963LMV0Z5QB9L45__b68529ef50ff68d6cf320ff0e9c5c80a.javazone.no._CNAME]
module.monitoring.aws_securityhub_standards_subscription.aws_foundational: Refreshing state... [id=arn:aws:securityhub:eu-central-1:553637109631:subscription/aws-foundational-security-best-practices/v/1.0.0]
module.monitoring.aws_config_config_rule.required_tags: Refreshing state... [id=javabin-required-tags]
module.monitoring.aws_s3_bucket_server_side_encryption_configuration.config_logs: Refreshing state... [id=javabin-config-553637109631]
module.monitoring.aws_config_delivery_channel.main: Refreshing state... [id=javabin-config-channel]
module.monitoring.aws_s3_bucket_policy.config_logs: Refreshing state... [id=javabin-config-553637109631]
module.monitoring.aws_s3_bucket_public_access_block.config_logs: Refreshing state... [id=javabin-config-553637109631]
module.iam.aws_iam_role_policy.ci_infra_allow: Refreshing state... [id=javabin-ci-infra:infra-management]
module.iam.aws_iam_role_policy.ci_infra_deny: Refreshing state... [id=javabin-ci-infra:deny-dangerous-operations]
module.compute.aws_ecr_lifecycle_policy.ci["platform"]: Refreshing state... [id=javabin-ci-platform]
module.compute.aws_ecr_lifecycle_policy.ci["jvm"]: Refreshing state... [id=javabin-ci-jvm]
module.compute.aws_ecr_lifecycle_policy.ci["ts"]: Refreshing state... [id=javabin-ci-ts]
module.iam.aws_iam_role_policy.ci_override_approver: Refreshing state... [id=javabin-ci-override-approver:invoke-apply-gate]
module.iam.aws_iam_role_policy.ci_apply_gate: Refreshing state... [id=javabin-ci-apply-gate:invoke-gate-and-read-plans]
module.lambdas.aws_cloudwatch_event_target.daily_cost_check: Refreshing state... [id=javabin-daily-cost-check-schedule-invoke-daily-cost-check]
module.lambdas.aws_lambda_permission.daily_cost_check_schedule: Refreshing state... [id=AllowEventBridge]
module.iam.aws_iam_role_policy.ci_deploy_ecs["platform-test-app"]: Refreshing state... [id=javabin-ci-deploy-platform-test-app:ecs-deploy]
module.iam.aws_iam_role_policy.ci_deploy_ssm["platform-test-app"]: Refreshing state... [id=javabin-ci-deploy-platform-test-app:ssm-read-overrides]
module.iam.aws_iam_role_policy.ci_deploy_logs["platform-test-app"]: Refreshing state... [id=javabin-ci-deploy-platform-test-app:cloudwatch-logs]
module.iam.aws_iam_role_policy.ci_deploy_ecr["platform-test-app"]: Refreshing state... [id=javabin-ci-deploy-platform-test-app:ecr-push]
module.networking.aws_vpc_security_group_egress_rule.ecs_all: Refreshing state... [id=sgr-0266cfa56e8feab14]
module.networking.aws_route_table.public: Refreshing state... [id=rtb-01c9642f019d36b1f]
module.lambdas.aws_iam_role_policy.team_provisioner: Refreshing state... [id=javabin-team-provisioner:javabin-team-provisioner]
module.lambdas.aws_iam_role_policy.slack_alert: Refreshing state... [id=javabin-slack-alert:javabin-slack-alert]
module.lambdas.aws_lambda_function.team_provisioner: Refreshing state... [id=javabin-team-provisioner]
module.iam.aws_iam_role.ci_app["platform-test-app"]: Refreshing state... [id=javabin-ci-app-platform-test-app]
module.lambdas.aws_cloudwatch_event_target.compliance_reporter: Refreshing state... [id=javabin-compliance-reporter-trigger-invoke-compliance-reporter]
module.lambdas.aws_lambda_permission.compliance_reporter_eventbridge: Refreshing state... [id=AllowEventBridge]
module.lambdas.aws_lambda_function.securityhub_summary: Refreshing state... [id=javabin-securityhub-summary]
module.lambdas.aws_lambda_function.slack_alert: Refreshing state... [id=javabin-slack-alert]
module.ingress.aws_acm_certificate_validation.wildcard: Refreshing state... [id=2026-03-07 16:29:14.551 +0000 UTC]
module.lambdas.aws_lambda_permission.cost_report_schedule: Refreshing state... [id=AllowEventBridge]
module.lambdas.aws_cloudwatch_event_target.cost_report: Refreshing state... [id=javabin-cost-report-schedule-invoke-cost-report]
module.networking.aws_nat_gateway.main: Refreshing state... [id=nat-0e9cc9e27cc6598db]
module.lambdas.aws_lambda_permission.override_cleanup_schedule: Refreshing state... [id=AllowEventBridge]
module.lambdas.aws_cloudwatch_event_target.override_cleanup: Refreshing state... [id=javabin-override-cleanup-schedule-invoke-override-cleanup]
module.monitoring.aws_config_configuration_recorder_status.main: Refreshing state... [id=javabin-recorder]
module.networking.aws_vpc_security_group_ingress_rule.ecs_from_alb: Refreshing state... [id=sgr-064d01025000f601e]
module.networking.aws_vpc_security_group_ingress_rule.alb_http: Refreshing state... [id=sgr-07c58f16ef7496031]
module.networking.aws_vpc_security_group_ingress_rule.alb_https: Refreshing state... [id=sgr-00b490b07c35193b7]
module.networking.aws_vpc_security_group_egress_rule.alb_all: Refreshing state... [id=sgr-021faee81305c6e28]
module.networking.aws_route_table_association.public_a: Refreshing state... [id=rtbassoc-07ff2e0bfa1578067]
module.networking.aws_route_table_association.public_b: Refreshing state... [id=rtbassoc-0186c3a7f0279e344]
module.iam.aws_iam_role_policy.ci_app_allow["platform-test-app"]: Refreshing state... [id=javabin-ci-app-platform-test-app:app-management]
module.iam.aws_iam_role_policy.ci_app_deny["platform-test-app"]: Refreshing state... [id=javabin-ci-app-platform-test-app:deny-platform-operations]
module.networking.aws_route_table.private: Refreshing state... [id=rtb-0b0b4c643592a7db0]
module.ingress.aws_lb.main: Refreshing state... [id=arn:aws:elasticloadbalancing:eu-central-1:553637109631:loadbalancer/app/javabin-platform-alb/bec1dd43ab8341b9]
module.networking.aws_route_table_association.private_a: Refreshing state... [id=rtbassoc-0b9248495de9f7316]
module.networking.aws_route_table_association.private_b: Refreshing state... [id=rtbassoc-005259f36758e089e]
module.lambdas.aws_cloudwatch_event_target.securityhub_summary: Refreshing state... [id=javabin-securityhub-summary-schedule-invoke-securityhub-summary]
module.lambdas.aws_sns_topic_subscription.slack_alert_security: Refreshing state... [id=arn:aws:sns:eu-central-1:553637109631:javabin-security:0bda8a22-7a50-4a9d-9285-6b1fc1f75376]
module.lambdas.aws_sns_topic_subscription.slack_alert_alerts: Refreshing state... [id=arn:aws:sns:eu-central-1:553637109631:javabin-alerts:380384a2-0cac-48c9-b2d9-2a0aae6968cd]
module.lambdas.aws_lambda_permission.securityhub_summary_schedule: Refreshing state... [id=AllowEventBridge]
module.lambdas.aws_lambda_permission.slack_alert_security: Refreshing state... [id=AllowSNSSecurity]
module.lambdas.aws_lambda_permission.slack_alert_alerts: Refreshing state... [id=AllowSNSAlerts]
module.ingress.aws_lb_listener.https: Refreshing state... [id=arn:aws:elasticloadbalancing:eu-central-1:553637109631:listener/app/javabin-platform-alb/bec1dd43ab8341b9/500c9c2b4186bf45]
module.ingress.aws_lb_listener.http_redirect: Refreshing state... [id=arn:aws:elasticloadbalancing:eu-central-1:553637109631:listener/app/javabin-platform-alb/bec1dd43ab8341b9/1d92e19ae75aa59b]

Terraform used the selected providers to generate the following execution
plan. Resource actions are indicated with the following symbols:
  + create
  ~ update in-place
  - destroy

Terraform will perform the following actions:

  # module.iam.aws_iam_role.ci_infra will be updated in-place
  ~ resource "aws_iam_role" "ci_infra" {
      ~ assume_role_policy    = jsonencode(
          ~ {
              ~ Statement = [
                  ~ {
                      ~ Condition = {
                          ~ StringLike   = {
                              ~ "token.actions.githubusercontent.com:sub" = "repo:javaBin/platform:*" -> "repo:javaBin/platform:ref:refs/heads/main"
                            }
                            # (1 unchanged attribute hidden)
                        }
                        # (3 unchanged attributes hidden)
                    },
                ]
                # (1 unchanged attribute hidden)
            }
        )
        id                    = "javabin-ci-infra"
        name                  = "javabin-ci-infra"
        tags                  = {
            "Name" = "javabin-ci-infra"
        }
        # (9 unchanged attributes hidden)

        # (2 unchanged blocks hidden)
    }

  # module.iam.aws_iam_role.ci_infra_plan will be updated in-place
  # (imported from "javabin-ci-infra-plan")
  ~ resource "aws_iam_role" "ci_infra_plan" {
        arn                   = "arn:aws:iam::553637109631:role/javabin-ci-infra-plan"
        assume_role_policy    = jsonencode(
            {
                Statement = [
                    {
                        Action    = "sts:AssumeRoleWithWebIdentity"
                        Condition = {
                            StringEquals = {
                                "token.actions.githubusercontent.com:aud" = "sts.amazonaws.com"
                            }
                            StringLike   = {
                                "token.actions.githubusercontent.com:sub" = "repo:javaBin/platform:*"
                            }
                        }
                        Effect    = "Allow"
                        Principal = {
                            Federated = "arn:aws:iam::553637109631:oidc-provider/token.actions.githubusercontent.com"
                        }
                    },
                ]
                Version   = "2012-10-17"
            }
        )
        create_date           = "2026-03-12T14:55:43Z"
        force_detach_policies = false
        id                    = "javabin-ci-infra-plan"
        managed_policy_arns   = [
            "arn:aws:iam::aws:policy/ReadOnlyAccess",
        ]
        max_session_duration  = 3600
        name                  = "javabin-ci-infra-plan"
        path                  = "/"
        permissions_boundary  = "arn:aws:iam::553637109631:policy/javabin-developer-boundary"
        tags                  = {
            "Name" = "javabin-ci-infra-plan"
        }
      ~ tags_all              = {
            "Name"        = "javabin-ci-infra-plan"
          + "environment" = "production"
            "managed-by"  = "terraform"
            "project"     = "javabin"
          + "team"        = "javabin"
        }
        unique_id             = "AROAYBZ2X3573YMSL64A2"

        inline_policy {
            name   = "plan-specific-writes"
            policy = jsonencode(
                {
                    Statement = [
                        {
                            Action   = [
                                "dynamodb:GetItem",
                                "dynamodb:PutItem",
                                "dynamodb:DeleteItem",
                            ]
                            Effect   = "Allow"
                            Resource = "arn:aws:dynamodb:eu-central-1:553637109631:table/javabin-terraform-infra-lock"
                            Sid      = "TerraformStateLocking"
                        },
                        {
                            Action   = [
                                "s3:PutObject",
                            ]
                            Effect   = "Allow"
                            Resource = "arn:aws:s3:::javabin-ci-plan-artifacts-553637109631/*"
                            Sid      = "UploadPlanArtifacts"
                        },
                        {
                            Action   = [
                                "bedrock:InvokeModel",
                                "bedrock:Converse",
                            ]
                            Effect   = "Allow"
                            Resource = "arn:aws:bedrock:eu-central-1:553637109631:inference-profile/eu.anthropic.*"
                            Sid      = "BedrockInferenceProfile"
                        },
                        {
                            Action    = [
                                "bedrock:InvokeModel",
                                "bedrock:Converse",
                            ]
                            Condition = {
                                StringLike = {
                                    "bedrock:InferenceProfileArn" = "arn:aws:bedrock:eu-central-1:553637109631:inference-profile/eu.anthropic.*"
                                }
                            }
                            Effect    = "Allow"
                            Resource  = [
                                "arn:aws:bedrock:eu-central-1::foundation-model/anthropic.*",
                                "arn:aws:bedrock:eu-north-1::foundation-model/anthropic.*",
                                "arn:aws:bedrock:eu-west-1::foundation-model/anthropic.*",
                                "arn:aws:bedrock:eu-west-3::foundation-model/anthropic.*",
                            ]
                            Sid       = "BedrockFoundationModels"
                        },
                        {
                            Action   = "ssm:GetParameter"
                            Effect   = "Allow"
                            Resource = "arn:aws:ssm:eu-central-1:553637109631:parameter/javabin/slack/*"
                            Sid      = "SSMReadSlackWebhook"
                        },
                        {
                            Action   = "pricing:GetProducts"
                            Effect   = "Allow"
                            Resource = "*"
                            Sid      = "PricingRead"
                        },
                    ]
                    Version   = "2012-10-17"
                }
            )
        }
    }

  # module.iam.aws_iam_role_policy.ci_infra_plan_extras will be created
  + resource "aws_iam_role_policy" "ci_infra_plan_extras" {
      + id          = (known after apply)
      + name        = "plan-specific-writes"
      + name_prefix = (known after apply)
      + policy      = jsonencode(
            {
              + Statement = [
                  + {
                      + Action   = [
                          + "dynamodb:GetItem",
                          + "dynamodb:PutItem",
                          + "dynamodb:DeleteItem",
                        ]
                      + Effect   = "Allow"
                      + Resource = "arn:aws:dynamodb:eu-central-1:553637109631:table/javabin-terraform-infra-lock"
                      + Sid      = "TerraformStateLocking"
                    },
                  + {
                      + Action   = [
                          + "s3:PutObject",
                        ]
                      + Effect   = "Allow"
                      + Resource = "arn:aws:s3:::javabin-ci-plan-artifacts-553637109631/*"
                      + Sid      = "UploadPlanArtifacts"
                    },
                  + {
                      + Action   = [
                          + "bedrock:InvokeModel",
                          + "bedrock:Converse",
                        ]
                      + Effect   = "Allow"
                      + Resource = "arn:aws:bedrock:eu-central-1:553637109631:inference-profile/eu.anthropic.*"
                      + Sid      = "BedrockInferenceProfile"
                    },
                  + {
                      + Action    = [
                          + "bedrock:InvokeModel",
                          + "bedrock:Converse",
                        ]
                      + Condition = {
                          + StringLike = {
                              + "bedrock:InferenceProfileArn" = "arn:aws:bedrock:eu-central-1:553637109631:inference-profile/eu.anthropic.*"
                            }
                        }
                      + Effect    = "Allow"
                      + Resource  = [
                          + "arn:aws:bedrock:eu-central-1::foundation-model/anthropic.*",
                          + "arn:aws:bedrock:eu-north-1::foundation-model/anthropic.*",
                          + "arn:aws:bedrock:eu-west-1::foundation-model/anthropic.*",
                          + "arn:aws:bedrock:eu-west-3::foundation-model/anthropic.*",
                        ]
                      + Sid       = "BedrockFoundationModels"
                    },
                  + {
                      + Action   = "ssm:GetParameter"
                      + Effect   = "Allow"
                      + Resource = "arn:aws:ssm:eu-central-1:553637109631:parameter/javabin/slack/*"
                      + Sid      = "SSMReadSlackWebhook"
                    },
                  + {
                      + Action   = "pricing:GetProducts"
                      + Effect   = "Allow"
                      + Resource = "*"
                      + Sid      = "PricingRead"
                    },
                ]
              + Version   = "2012-10-17"
            }
        )
      + role        = "javabin-ci-infra-plan"
    }

  # module.iam.aws_iam_role_policy.ci_registry will be created
  + resource "aws_iam_role_policy" "ci_registry" {
      + id          = (known after apply)
      + name        = "registry-operations"
      + name_prefix = (known after apply)
      + policy      = jsonencode(
            {
              + Statement = [
                  + {
                      + Action   = "lambda:InvokeFunction"
                      + Effect   = "Allow"
                      + Resource = "arn:aws:lambda:eu-central-1:553637109631:function:javabin-team-provisioner"
                      + Sid      = "InvokeTeamProvisioner"
                    },
                  + {
                      + Action   = "ssm:GetParameter"
                      + Effect   = "Allow"
                      + Resource = [
                          + "arn:aws:ssm:eu-central-1:553637109631:parameter/javabin/platform/google-admin-sa",
                          + "arn:aws:ssm:eu-central-1:553637109631:parameter/javabin/platform/google-admin-email",
                        ]
                      + Sid      = "SSMReadGoogleCreds"
                    },
                ]
              + Version   = "2012-10-17"
            }
        )
      + role        = "javabin-ci-registry"
    }

  # module.iam.aws_iam_role_policy.ci_registry_lambda will be destroyed
  # (because aws_iam_role_policy.ci_registry_lambda is not in configuration)
  - resource "aws_iam_role_policy" "ci_registry_lambda" {
      - id     = "javabin-ci-registry:invoke-team-provisioner" -> null
      - name   = "invoke-team-provisioner" -> null
      - policy = jsonencode(
            {
              - Statement = [
                  - {
                      - Action   = "lambda:InvokeFunction"
                      - Effect   = "Allow"
                      - Resource = "arn:aws:lambda:eu-central-1:553637109631:function:javabin-team-provisioner"
                    },
                ]
              - Version   = "2012-10-17"
            }
        ) -> null
      - role   = "javabin-ci-registry" -> null
    }

  # module.iam.aws_iam_role_policy_attachment.ci_infra_plan_readonly will be created
  + resource "aws_iam_role_policy_attachment" "ci_infra_plan_readonly" {
      + id         = (known after apply)
      + policy_arn = "arn:aws:iam::aws:policy/ReadOnlyAccess"
      + role       = "javabin-ci-infra-plan"
    }

  # module.lambdas.aws_iam_role_policy.cost_report will be updated in-place
  ~ resource "aws_iam_role_policy" "cost_report" {
        id     = "javabin-cost-report:javabin-cost-report"
        name   = "javabin-cost-report"
      ~ policy = jsonencode(
          ~ {
              ~ Statement = [
                    # (1 unchanged element hidden)
                    {
                        Action   = [
                            "ce:GetCostAndUsage",
                        ]
                        Effect   = "Allow"
                        Resource = "*"
                        Sid      = "CostExplorer"
                    },
                  ~ {
                      ~ Sid      = "Bedrock" -> "BedrockInferenceProfile"
                        # (3 unchanged attributes hidden)
                    },
                  + {
                      + Action    = [
                          + "bedrock:InvokeModel",
                          + "bedrock:Converse",
                        ]
                      + Condition = {
                          + StringLike = {
                              + "bedrock:InferenceProfileArn" = "arn:aws:bedrock:eu-central-1:553637109631:inference-profile/eu.anthropic.*"
                            }
                        }
                      + Effect    = "Allow"
                      + Resource  = [
                          + "arn:aws:bedrock:eu-central-1::foundation-model/anthropic.*",
                          + "arn:aws:bedrock:eu-north-1::foundation-model/anthropic.*",
                          + "arn:aws:bedrock:eu-west-1::foundation-model/anthropic.*",
                          + "arn:aws:bedrock:eu-west-3::foundation-model/anthropic.*",
                        ]
                      + Sid       = "BedrockFoundationModels"
                    },
                ]
                # (1 unchanged attribute hidden)
            }
        )
        # (1 unchanged attribute hidden)
    }

  # module.lambdas.aws_iam_role_policy.slack_alert will be updated in-place
  ~ resource "aws_iam_role_policy" "slack_alert" {
        id     = "javabin-slack-alert:javabin-slack-alert"
        name   = "javabin-slack-alert"
      ~ policy = jsonencode(
          ~ {
              ~ Statement = [
                    {
                        Action   = "ssm:GetParameter"
                        Effect   = "Allow"
                        Resource = [
                            "arn:aws:ssm:eu-central-1:553637109631:parameter/javabin/slack/*",
                        ]
                        Sid      = "SSMRead"
                    },
                  ~ {
                      ~ Sid      = "Bedrock" -> "BedrockInferenceProfile"
                        # (3 unchanged attributes hidden)
                    },
                  + {
                      + Action    = [
                          + "bedrock:InvokeModel",
                          + "bedrock:Converse",
                        ]
                      + Condition = {
                          + StringLike = {
                              + "bedrock:InferenceProfileArn" = "arn:aws:bedrock:eu-central-1:553637109631:inference-profile/eu.anthropic.*"
                            }
                        }
                      + Effect    = "Allow"
                      + Resource  = [
                          + "arn:aws:bedrock:eu-central-1::foundation-model/anthropic.*",
                          + "arn:aws:bedrock:eu-north-1::foundation-model/anthropic.*",
                          + "arn:aws:bedrock:eu-west-1::foundation-model/anthropic.*",
                          + "arn:aws:bedrock:eu-west-3::foundation-model/anthropic.*",
                        ]
                      + Sid       = "BedrockFoundationModels"
                    },
                    {
                        Action   = "pricing:GetProducts"
                        Effect   = "Allow"
                        Resource = "*"
                        Sid      = "PricingRead"
                    },
                    # (2 unchanged elements hidden)
                ]
                # (1 unchanged attribute hidden)
            }
        )
        # (1 unchanged attribute hidden)
    }

Plan: 1 to import, 3 to add, 4 to change, 1 to destroy.

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

Saved the plan to: tfplan

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

@github-actions
Copy link
Copy Markdown

LLM Plan Review

AWS CLI error:
aws: [ERROR]: An error occurred (AccessDeniedException) when calling the Converse operation: User: arn:aws:sts::553637109631:assumed-role/javabin-ci-infra-plan/javabin-platform-plan-23008957873 is not authorized to perform: bedrock:InvokeModel on resource: arn:aws:bedrock:eu-south-1::foundation-model/anthropic.claude-haiku-4-5-20251001-v1:0 because no identity-based policy allows the bedrock:InvokeModel action

LLM returned no structured output.

…error

- Use eu-* wildcard for foundation model ARNs instead of listing
  specific regions — Bedrock routes to any EU region dynamically.
- Fail the pipeline when LLM review returns FAILED instead of
  silently passing through to the apply job.
@github-actions
Copy link
Copy Markdown

Terraform Plan

Changes detected — review required.

Plan output
module.lambdas.data.archive_file.daily_cost_check: Reading...
module.lambdas.data.archive_file.apply_gate: Reading...
module.lambdas.data.archive_file.cost_report: Reading...
module.lambdas.data.archive_file.daily_cost_check: Read complete after 0s [id=da9c5f6e85719534eb3d93b02eca8a30fbbfeb34]
module.lambdas.data.archive_file.compliance_reporter: Reading...
module.lambdas.data.archive_file.apply_gate: Read complete after 0s [id=064e560a455ec8b2a30253cd815a820ad002376f]
module.lambdas.data.archive_file.override_cleanup: Reading...
module.lambdas.data.archive_file.slack_alert: Reading...
module.lambdas.data.archive_file.team_provisioner: Reading...
module.lambdas.data.archive_file.cost_report: Read complete after 0s [id=9844b77d6a3a4efa27510589543ad38c835cc662]
module.lambdas.data.archive_file.override_cleanup: Read complete after 0s [id=08e23a8ca152c50d8321f7b9f15d3ebbdc97849d]
module.lambdas.data.archive_file.compliance_reporter: Read complete after 0s [id=323449bf04f46a46a9d9c440212010f551146129]
module.lambdas.data.archive_file.slack_alert: Read complete after 0s [id=556fd3d4c8de13c380a237a4f9f9d04ccbee0d4b]
module.lambdas.data.archive_file.team_provisioner: Read complete after 0s [id=22dd605c5fa5b4949ce29a38af706d5b282e1688]
module.monitoring.aws_cloudwatch_event_rule.config_compliance: Refreshing state... [id=javabin-config-compliance-change]
module.monitoring.aws_ce_anomaly_monitor.main: Refreshing state... [id=arn:aws:ce::553637109631:anomalymonitor/3609b3f1-c834-444e-a218-02ac6da1cb4d]
module.lambdas.aws_cloudwatch_event_rule.daily_cost_check_schedule: Refreshing state... [id=javabin-daily-cost-check-schedule]
module.monitoring.aws_cloudwatch_event_rule.iam_changes: Refreshing state... [id=javabin-iam-changes]
module.monitoring.aws_sns_topic.alerts: Refreshing state... [id=arn:aws:sns:eu-central-1:553637109631:javabin-alerts]
module.iam.aws_iam_policy.developer_boundary: Refreshing state... [id=arn:aws:iam::553637109631:policy/javabin-developer-boundary]
module.iam.aws_iam_role.ecs_execution: Refreshing state... [id=javabin-ecs-execution]
module.lambdas.aws_cloudwatch_event_rule.cost_report_schedule: Refreshing state... [id=javabin-cost-report-schedule]
module.iam.aws_iam_role_policy.ci_registry_lambda: Refreshing state... [id=javabin-ci-registry:invoke-team-provisioner]
module.identity.aws_cognito_user_pool.external: Refreshing state... [id=eu-central-1_gdFOsE4EM]
module.iam.data.aws_iam_openid_connect_provider.github: Reading...
module.iam.data.aws_iam_openid_connect_provider.github: Read complete after 0s [id=arn:aws:iam::553637109631:oidc-provider/token.actions.githubusercontent.com]
module.networking.data.aws_availability_zones.available: Reading...
module.ingress.aws_acm_certificate.wildcard: Refreshing state... [id=arn:aws:acm:eu-central-1:553637109631:certificate/9b79f56a-3719-4c62-8970-6f08985a7e5b]
module.compute.aws_ecr_repository.ci["jvm"]: Refreshing state... [id=javabin-ci-jvm]
module.compute.aws_ecr_repository.ci["platform"]: Refreshing state... [id=javabin-ci-platform]
module.compute.aws_ecr_repository.ci["ts"]: Refreshing state... [id=javabin-ci-ts]
module.lambdas.aws_iam_role.team_provisioner: Refreshing state... [id=javabin-team-provisioner]
module.identity.aws_cognito_user_pool.internal: Refreshing state... [id=eu-central-1_Icikv3dtD]
module.lambdas.aws_cloudwatch_event_rule.compliance_reporter_trigger: Refreshing state... [id=javabin-compliance-reporter-trigger]
module.monitoring.aws_securityhub_account.main: Refreshing state... [id=553637109631]
module.networking.data.aws_availability_zones.available: Read complete after 0s [id=eu-central-1]
module.lambdas.aws_iam_role.slack_alert: Refreshing state... [id=javabin-slack-alert]
module.monitoring.aws_cloudwatch_event_rule.resource_modification: Refreshing state... [id=javabin-resource-modification]
module.lambdas.aws_iam_role.daily_cost_check: Refreshing state... [id=javabin-daily-cost-check]
module.networking.aws_vpc.main: Refreshing state... [id=vpc-0cd3502de2527a310]
module.networking.aws_eip.nat: Refreshing state... [id=eipalloc-0764f0a1a3c80dce1]
module.monitoring.aws_cloudwatch_event_rule.securityhub_findings: Refreshing state... [id=javabin-securityhub-findings]
module.lambdas.aws_iam_role.compliance_reporter: Refreshing state... [id=javabin-compliance-reporter]
module.monitoring.aws_cloudwatch_event_rule.resource_creation: Refreshing state... [id=javabin-resource-creation]
module.ingress.data.aws_route53_zone.main: Reading...
module.compute.aws_ecs_cluster.main: Refreshing state... [id=arn:aws:ecs:eu-central-1:553637109631:cluster/javabin-platform]
module.lambdas.aws_cloudwatch_event_rule.securityhub_summary_schedule: Refreshing state... [id=javabin-securityhub-summary-schedule]
module.monitoring.aws_dynamodb_table.alert_dedup: Refreshing state... [id=javabin-alert-dedup]
module.monitoring.aws_cloudwatch_event_rule.console_login: Refreshing state... [id=javabin-console-login]
module.lambdas.aws_cloudwatch_event_rule.override_cleanup_schedule: Refreshing state... [id=javabin-override-cleanup-schedule]
module.monitoring.aws_guardduty_detector.main: Refreshing state... [id=f1df02cf279e4b5986ce1e9bcb3af9c5]
module.monitoring.aws_sns_topic.security: Refreshing state... [id=arn:aws:sns:eu-central-1:553637109631:javabin-security]
module.lambdas.aws_iam_role.override_cleanup: Refreshing state... [id=javabin-override-cleanup]
module.monitoring.aws_iam_role.config_role: Refreshing state... [id=javabin-config-role]
module.monitoring.aws_s3_bucket.config_logs: Refreshing state... [id=javabin-config-553637109631]
module.lambdas.aws_iam_role.cost_report: Refreshing state... [id=javabin-cost-report]
module.lambdas.aws_iam_role.apply_gate: Refreshing state... [id=javabin-apply-gate]
module.ingress.data.aws_route53_zone.main: Read complete after 1s [id=Z09335963LMV0Z5QB9L45]
module.monitoring.aws_cloudwatch_event_rule.guardduty_findings: Refreshing state... [id=javabin-guardduty-findings]
module.iam.aws_iam_role.ci_infra_plan: Preparing import... [id=javabin-ci-infra-plan]
module.iam.aws_iam_role.ci_infra_plan: Refreshing state... [id=javabin-ci-infra-plan]
module.iam.aws_iam_role.ci_registry: Refreshing state... [id=javabin-ci-registry]
module.iam.aws_iam_role.ci_apply_gate: Refreshing state... [id=javabin-ci-apply-gate]
module.iam.aws_iam_role.ci_infra: Refreshing state... [id=javabin-ci-infra]
module.iam.aws_iam_role.ci_deploy["platform-test-app"]: Refreshing state... [id=javabin-ci-deploy-platform-test-app]
module.iam.aws_iam_role.ci_override_approver: Refreshing state... [id=javabin-ci-override-approver]
module.iam.aws_iam_role_policy.ecs_execution_secrets: Refreshing state... [id=javabin-ecs-execution:secrets-read]
module.iam.aws_iam_role_policy_attachment.ecs_execution_base: Refreshing state... [id=javabin-ecs-execution-20260307162856804400000004]
module.monitoring.aws_sns_topic_policy.alerts: Refreshing state... [id=arn:aws:sns:eu-central-1:553637109631:javabin-alerts]
module.monitoring.aws_ce_anomaly_subscription.alerts: Refreshing state... [id=arn:aws:ce::553637109631:anomalysubscription/f6b079c9-5174-43b7-85f3-dde533995482]
module.lambdas.aws_iam_role_policy_attachment.team_provisioner_logs: Refreshing state... [id=javabin-team-provisioner-20260307162856464600000003]
module.lambdas.aws_iam_role_policy_attachment.slack_alert_logs: Refreshing state... [id=javabin-slack-alert-20260307162858376500000008]
module.lambdas.aws_iam_role_policy.daily_cost_check: Refreshing state... [id=javabin-daily-cost-check:javabin-daily-cost-check]
module.lambdas.aws_lambda_function.daily_cost_check: Refreshing state... [id=javabin-daily-cost-check]
module.lambdas.aws_iam_role_policy_attachment.daily_cost_check_logs: Refreshing state... [id=javabin-daily-cost-check-20260307162856210400000002]
module.monitoring.aws_securityhub_standards_subscription.aws_foundational: Refreshing state... [id=arn:aws:securityhub:eu-central-1:553637109631:subscription/aws-foundational-security-best-practices/v/1.0.0]
module.compute.aws_ecr_lifecycle_policy.ci["jvm"]: Refreshing state... [id=javabin-ci-jvm]
module.compute.aws_ecr_lifecycle_policy.ci["platform"]: Refreshing state... [id=javabin-ci-platform]
module.compute.aws_ecr_lifecycle_policy.ci["ts"]: Refreshing state... [id=javabin-ci-ts]
module.lambdas.aws_iam_role_policy_attachment.compliance_reporter_logs: Refreshing state... [id=javabin-compliance-reporter-20260307162857302300000005]
module.lambdas.aws_lambda_function.compliance_reporter: Refreshing state... [id=javabin-compliance-reporter]
module.lambdas.aws_iam_role_policy.compliance_reporter: Refreshing state... [id=javabin-compliance-reporter:javabin-compliance-reporter]
module.identity.aws_cognito_user_pool_domain.internal: Refreshing state... [id=javabin-internal]
module.lambdas.aws_iam_role_policy.override_cleanup: Refreshing state... [id=javabin-override-cleanup:javabin-override-cleanup]
module.lambdas.aws_iam_role_policy_attachment.override_cleanup_logs: Refreshing state... [id=javabin-override-cleanup-20260307162858005200000007]
module.ingress.aws_route53_record.acm_validation["*.javazone.no"]: Refreshing state... [id=Z09335963LMV0Z5QB9L45__b68529ef50ff68d6cf320ff0e9c5c80a.javazone.no._CNAME]
module.lambdas.aws_lambda_function.override_cleanup: Refreshing state... [id=javabin-override-cleanup]
module.monitoring.aws_cloudwatch_event_target.iam_changes_sns: Refreshing state... [id=javabin-iam-changes-send-to-security-sns]
module.monitoring.aws_cloudwatch_event_target.resource_modification_sns: Refreshing state... [id=javabin-resource-modification-send-to-security-sns]
module.monitoring.aws_cloudwatch_event_target.resource_creation_sns: Refreshing state... [id=javabin-resource-creation-send-to-security-sns]
module.monitoring.aws_cloudwatch_event_target.console_login_sns: Refreshing state... [id=javabin-console-login-send-to-security-sns]
module.monitoring.aws_sns_topic_policy.security: Refreshing state... [id=arn:aws:sns:eu-central-1:553637109631:javabin-security]
module.monitoring.aws_cloudwatch_event_target.config_compliance_sns: Refreshing state... [id=javabin-config-compliance-change-send-to-security-sns]
module.monitoring.aws_cloudwatch_event_target.securityhub_findings_sns: Refreshing state... [id=javabin-securityhub-findings-send-to-security-sns]
module.monitoring.aws_iam_role_policy_attachment.config_role: Refreshing state... [id=javabin-config-role-20260307162900971300000009]
module.monitoring.aws_config_configuration_recorder.main: Refreshing state... [id=javabin-recorder]
module.compute.aws_ecs_cluster_capacity_providers.main: Refreshing state... [id=javabin-platform]
module.lambdas.aws_iam_role_policy_attachment.cost_report_logs: Refreshing state... [id=javabin-cost-report-20260307162857662100000006]
module.lambdas.aws_lambda_function.cost_report: Refreshing state... [id=javabin-cost-report]
module.lambdas.aws_iam_role_policy.cost_report: Refreshing state... [id=javabin-cost-report:javabin-cost-report]
module.lambdas.aws_iam_role_policy_attachment.apply_gate_logs: Refreshing state... [id=javabin-apply-gate-20260310000556680800000001]
module.lambdas.aws_iam_role_policy.apply_gate: Refreshing state... [id=javabin-apply-gate:javabin-apply-gate]
module.lambdas.aws_lambda_function.apply_gate: Refreshing state... [id=javabin-apply-gate]
module.monitoring.aws_cloudwatch_event_target.guardduty_findings_sns: Refreshing state... [id=javabin-guardduty-findings-send-to-security-sns]
module.monitoring.aws_guardduty_detector_feature.runtime_monitoring: Refreshing state... [id=f1df02cf279e4b5986ce1e9bcb3af9c5/RUNTIME_MONITORING]
module.iam.aws_iam_role_policy.ci_apply_gate: Refreshing state... [id=javabin-ci-apply-gate:invoke-gate-and-read-plans]
module.iam.aws_iam_role_policy.ci_override_approver: Refreshing state... [id=javabin-ci-override-approver:invoke-apply-gate]
module.iam.aws_iam_role_policy.ci_infra_deny: Refreshing state... [id=javabin-ci-infra:deny-dangerous-operations]
module.iam.aws_iam_role_policy.ci_infra_allow: Refreshing state... [id=javabin-ci-infra:infra-management]
module.iam.aws_iam_role_policy.ci_deploy_ssm["platform-test-app"]: Refreshing state... [id=javabin-ci-deploy-platform-test-app:ssm-read-overrides]
module.iam.aws_iam_role_policy.ci_deploy_ecs["platform-test-app"]: Refreshing state... [id=javabin-ci-deploy-platform-test-app:ecs-deploy]
module.iam.aws_iam_role_policy.ci_deploy_logs["platform-test-app"]: Refreshing state... [id=javabin-ci-deploy-platform-test-app:cloudwatch-logs]
module.iam.aws_iam_role_policy.ci_deploy_ecr["platform-test-app"]: Refreshing state... [id=javabin-ci-deploy-platform-test-app:ecr-push]
module.networking.aws_internet_gateway.main: Refreshing state... [id=igw-07b193bea823a7f69]
module.networking.aws_subnet.public_b: Refreshing state... [id=subnet-0eb818326ee94a266]
module.networking.aws_subnet.public_a: Refreshing state... [id=subnet-0f6bfec917146b856]
module.networking.aws_security_group.alb: Refreshing state... [id=sg-061000c0fa68a41b7]
module.networking.aws_subnet.private_a: Refreshing state... [id=subnet-0329ad20dc025c693]
module.networking.aws_security_group.ecs_tasks: Refreshing state... [id=sg-0df9a0a3a22548c62]
module.networking.aws_subnet.private_b: Refreshing state... [id=subnet-09ee21336f809f3c9]
module.ingress.aws_acm_certificate_validation.wildcard: Refreshing state... [id=2026-03-07 16:29:14.551 +0000 UTC]
module.lambdas.aws_cloudwatch_event_target.daily_cost_check: Refreshing state... [id=javabin-daily-cost-check-schedule-invoke-daily-cost-check]
module.lambdas.aws_lambda_permission.override_cleanup_schedule: Refreshing state... [id=AllowEventBridge]
module.lambdas.aws_cloudwatch_event_target.override_cleanup: Refreshing state... [id=javabin-override-cleanup-schedule-invoke-override-cleanup]
module.lambdas.aws_lambda_permission.daily_cost_check_schedule: Refreshing state... [id=AllowEventBridge]
module.lambdas.aws_lambda_permission.compliance_reporter_eventbridge: Refreshing state... [id=AllowEventBridge]
module.lambdas.aws_cloudwatch_event_target.compliance_reporter: Refreshing state... [id=javabin-compliance-reporter-trigger-invoke-compliance-reporter]
module.lambdas.aws_iam_role_policy.team_provisioner: Refreshing state... [id=javabin-team-provisioner:javabin-team-provisioner]
module.lambdas.aws_lambda_function.team_provisioner: Refreshing state... [id=javabin-team-provisioner]
module.networking.aws_route_table.public: Refreshing state... [id=rtb-01c9642f019d36b1f]
module.monitoring.aws_config_config_rule.required_tags: Refreshing state... [id=javabin-required-tags]
module.iam.aws_iam_role.ci_app["platform-test-app"]: Refreshing state... [id=javabin-ci-app-platform-test-app]
module.lambdas.aws_iam_role_policy.slack_alert: Refreshing state... [id=javabin-slack-alert:javabin-slack-alert]
module.lambdas.aws_lambda_permission.cost_report_schedule: Refreshing state... [id=AllowEventBridge]
module.lambdas.aws_cloudwatch_event_target.cost_report: Refreshing state... [id=javabin-cost-report-schedule-invoke-cost-report]
module.networking.aws_nat_gateway.main: Refreshing state... [id=nat-0e9cc9e27cc6598db]
module.networking.aws_vpc_security_group_egress_rule.alb_all: Refreshing state... [id=sgr-021faee81305c6e28]
module.networking.aws_vpc_security_group_ingress_rule.alb_https: Refreshing state... [id=sgr-00b490b07c35193b7]
module.lambdas.aws_lambda_function.securityhub_summary: Refreshing state... [id=javabin-securityhub-summary]
module.lambdas.aws_lambda_function.slack_alert: Refreshing state... [id=javabin-slack-alert]
module.networking.aws_vpc_security_group_ingress_rule.alb_http: Refreshing state... [id=sgr-07c58f16ef7496031]
module.networking.aws_vpc_security_group_ingress_rule.ecs_from_alb: Refreshing state... [id=sgr-064d01025000f601e]
module.networking.aws_vpc_security_group_egress_rule.ecs_all: Refreshing state... [id=sgr-0266cfa56e8feab14]
module.networking.aws_route_table_association.public_a: Refreshing state... [id=rtbassoc-07ff2e0bfa1578067]
module.networking.aws_route_table_association.public_b: Refreshing state... [id=rtbassoc-0186c3a7f0279e344]
module.iam.aws_iam_role_policy.ci_app_allow["platform-test-app"]: Refreshing state... [id=javabin-ci-app-platform-test-app:app-management]
module.iam.aws_iam_role_policy.ci_app_deny["platform-test-app"]: Refreshing state... [id=javabin-ci-app-platform-test-app:deny-platform-operations]
module.networking.aws_route_table.private: Refreshing state... [id=rtb-0b0b4c643592a7db0]
module.ingress.aws_lb.main: Refreshing state... [id=arn:aws:elasticloadbalancing:eu-central-1:553637109631:loadbalancer/app/javabin-platform-alb/bec1dd43ab8341b9]
module.monitoring.aws_config_delivery_channel.main: Refreshing state... [id=javabin-config-channel]
module.monitoring.aws_s3_bucket_server_side_encryption_configuration.config_logs: Refreshing state... [id=javabin-config-553637109631]
module.monitoring.aws_s3_bucket_public_access_block.config_logs: Refreshing state... [id=javabin-config-553637109631]
module.monitoring.aws_s3_bucket_policy.config_logs: Refreshing state... [id=javabin-config-553637109631]
module.monitoring.aws_config_configuration_recorder_status.main: Refreshing state... [id=javabin-recorder]
module.networking.aws_route_table_association.private_a: Refreshing state... [id=rtbassoc-0b9248495de9f7316]
module.networking.aws_route_table_association.private_b: Refreshing state... [id=rtbassoc-005259f36758e089e]
module.lambdas.aws_lambda_permission.securityhub_summary_schedule: Refreshing state... [id=AllowEventBridge]
module.lambdas.aws_cloudwatch_event_target.securityhub_summary: Refreshing state... [id=javabin-securityhub-summary-schedule-invoke-securityhub-summary]
module.lambdas.aws_sns_topic_subscription.slack_alert_security: Refreshing state... [id=arn:aws:sns:eu-central-1:553637109631:javabin-security:0bda8a22-7a50-4a9d-9285-6b1fc1f75376]
module.lambdas.aws_lambda_permission.slack_alert_alerts: Refreshing state... [id=AllowSNSAlerts]
module.lambdas.aws_lambda_permission.slack_alert_security: Refreshing state... [id=AllowSNSSecurity]
module.lambdas.aws_sns_topic_subscription.slack_alert_alerts: Refreshing state... [id=arn:aws:sns:eu-central-1:553637109631:javabin-alerts:380384a2-0cac-48c9-b2d9-2a0aae6968cd]
module.ingress.aws_lb_listener.http_redirect: Refreshing state... [id=arn:aws:elasticloadbalancing:eu-central-1:553637109631:listener/app/javabin-platform-alb/bec1dd43ab8341b9/1d92e19ae75aa59b]
module.ingress.aws_lb_listener.https: Refreshing state... [id=arn:aws:elasticloadbalancing:eu-central-1:553637109631:listener/app/javabin-platform-alb/bec1dd43ab8341b9/500c9c2b4186bf45]

Terraform used the selected providers to generate the following execution
plan. Resource actions are indicated with the following symbols:
  + create
  ~ update in-place
  - destroy

Terraform will perform the following actions:

  # module.iam.aws_iam_role.ci_infra will be updated in-place
  ~ resource "aws_iam_role" "ci_infra" {
      ~ assume_role_policy    = jsonencode(
          ~ {
              ~ Statement = [
                  ~ {
                      ~ Condition = {
                          ~ StringLike   = {
                              ~ "token.actions.githubusercontent.com:sub" = "repo:javaBin/platform:*" -> "repo:javaBin/platform:ref:refs/heads/main"
                            }
                            # (1 unchanged attribute hidden)
                        }
                        # (3 unchanged attributes hidden)
                    },
                ]
                # (1 unchanged attribute hidden)
            }
        )
        id                    = "javabin-ci-infra"
        name                  = "javabin-ci-infra"
        tags                  = {
            "Name" = "javabin-ci-infra"
        }
        # (9 unchanged attributes hidden)

        # (2 unchanged blocks hidden)
    }

  # module.iam.aws_iam_role.ci_infra_plan will be updated in-place
  # (imported from "javabin-ci-infra-plan")
  ~ resource "aws_iam_role" "ci_infra_plan" {
        arn                   = "arn:aws:iam::553637109631:role/javabin-ci-infra-plan"
        assume_role_policy    = jsonencode(
            {
                Statement = [
                    {
                        Action    = "sts:AssumeRoleWithWebIdentity"
                        Condition = {
                            StringEquals = {
                                "token.actions.githubusercontent.com:aud" = "sts.amazonaws.com"
                            }
                            StringLike   = {
                                "token.actions.githubusercontent.com:sub" = "repo:javaBin/platform:*"
                            }
                        }
                        Effect    = "Allow"
                        Principal = {
                            Federated = "arn:aws:iam::553637109631:oidc-provider/token.actions.githubusercontent.com"
                        }
                    },
                ]
                Version   = "2012-10-17"
            }
        )
        create_date           = "2026-03-12T14:55:43Z"
        force_detach_policies = false
        id                    = "javabin-ci-infra-plan"
        managed_policy_arns   = [
            "arn:aws:iam::aws:policy/ReadOnlyAccess",
        ]
        max_session_duration  = 3600
        name                  = "javabin-ci-infra-plan"
        path                  = "/"
        permissions_boundary  = "arn:aws:iam::553637109631:policy/javabin-developer-boundary"
        tags                  = {
            "Name" = "javabin-ci-infra-plan"
        }
      ~ tags_all              = {
            "Name"        = "javabin-ci-infra-plan"
          + "environment" = "production"
            "managed-by"  = "terraform"
            "project"     = "javabin"
          + "team"        = "javabin"
        }
        unique_id             = "AROAYBZ2X3573YMSL64A2"

        inline_policy {
            name   = "plan-specific-writes"
            policy = jsonencode(
                {
                    Statement = [
                        {
                            Action   = [
                                "dynamodb:GetItem",
                                "dynamodb:PutItem",
                                "dynamodb:DeleteItem",
                            ]
                            Effect   = "Allow"
                            Resource = "arn:aws:dynamodb:eu-central-1:553637109631:table/javabin-terraform-infra-lock"
                            Sid      = "TerraformStateLocking"
                        },
                        {
                            Action   = [
                                "s3:PutObject",
                            ]
                            Effect   = "Allow"
                            Resource = "arn:aws:s3:::javabin-ci-plan-artifacts-553637109631/*"
                            Sid      = "UploadPlanArtifacts"
                        },
                        {
                            Action   = [
                                "bedrock:InvokeModel",
                                "bedrock:Converse",
                            ]
                            Effect   = "Allow"
                            Resource = "arn:aws:bedrock:eu-central-1:553637109631:inference-profile/eu.anthropic.*"
                            Sid      = "BedrockInferenceProfile"
                        },
                        {
                            Action    = [
                                "bedrock:InvokeModel",
                                "bedrock:Converse",
                            ]
                            Condition = {
                                StringLike = {
                                    "bedrock:InferenceProfileArn" = "arn:aws:bedrock:eu-central-1:553637109631:inference-profile/eu.anthropic.*"
                                }
                            }
                            Effect    = "Allow"
                            Resource  = "arn:aws:bedrock:eu-*::foundation-model/anthropic.*"
                            Sid       = "BedrockFoundationModels"
                        },
                        {
                            Action   = "ssm:GetParameter"
                            Effect   = "Allow"
                            Resource = "arn:aws:ssm:eu-central-1:553637109631:parameter/javabin/slack/*"
                            Sid      = "SSMReadSlackWebhook"
                        },
                        {
                            Action   = "pricing:GetProducts"
                            Effect   = "Allow"
                            Resource = "*"
                            Sid      = "PricingRead"
                        },
                    ]
                    Version   = "2012-10-17"
                }
            )
        }
    }

  # module.iam.aws_iam_role_policy.ci_infra_plan_extras will be created
  + resource "aws_iam_role_policy" "ci_infra_plan_extras" {
      + id          = (known after apply)
      + name        = "plan-specific-writes"
      + name_prefix = (known after apply)
      + policy      = jsonencode(
            {
              + Statement = [
                  + {
                      + Action   = [
                          + "dynamodb:GetItem",
                          + "dynamodb:PutItem",
                          + "dynamodb:DeleteItem",
                        ]
                      + Effect   = "Allow"
                      + Resource = "arn:aws:dynamodb:eu-central-1:553637109631:table/javabin-terraform-infra-lock"
                      + Sid      = "TerraformStateLocking"
                    },
                  + {
                      + Action   = [
                          + "s3:PutObject",
                        ]
                      + Effect   = "Allow"
                      + Resource = "arn:aws:s3:::javabin-ci-plan-artifacts-553637109631/*"
                      + Sid      = "UploadPlanArtifacts"
                    },
                  + {
                      + Action   = [
                          + "bedrock:InvokeModel",
                          + "bedrock:Converse",
                        ]
                      + Effect   = "Allow"
                      + Resource = "arn:aws:bedrock:eu-central-1:553637109631:inference-profile/eu.anthropic.*"
                      + Sid      = "BedrockInferenceProfile"
                    },
                  + {
                      + Action    = [
                          + "bedrock:InvokeModel",
                          + "bedrock:Converse",
                        ]
                      + Condition = {
                          + StringLike = {
                              + "bedrock:InferenceProfileArn" = "arn:aws:bedrock:eu-central-1:553637109631:inference-profile/eu.anthropic.*"
                            }
                        }
                      + Effect    = "Allow"
                      + Resource  = "arn:aws:bedrock:eu-*::foundation-model/anthropic.*"
                      + Sid       = "BedrockFoundationModels"
                    },
                  + {
                      + Action   = "ssm:GetParameter"
                      + Effect   = "Allow"
                      + Resource = "arn:aws:ssm:eu-central-1:553637109631:parameter/javabin/slack/*"
                      + Sid      = "SSMReadSlackWebhook"
                    },
                  + {
                      + Action   = "pricing:GetProducts"
                      + Effect   = "Allow"
                      + Resource = "*"
                      + Sid      = "PricingRead"
                    },
                ]
              + Version   = "2012-10-17"
            }
        )
      + role        = "javabin-ci-infra-plan"
    }

  # module.iam.aws_iam_role_policy.ci_registry will be created
  + resource "aws_iam_role_policy" "ci_registry" {
      + id          = (known after apply)
      + name        = "registry-operations"
      + name_prefix = (known after apply)
      + policy      = jsonencode(
            {
              + Statement = [
                  + {
                      + Action   = "lambda:InvokeFunction"
                      + Effect   = "Allow"
                      + Resource = "arn:aws:lambda:eu-central-1:553637109631:function:javabin-team-provisioner"
                      + Sid      = "InvokeTeamProvisioner"
                    },
                  + {
                      + Action   = "ssm:GetParameter"
                      + Effect   = "Allow"
                      + Resource = [
                          + "arn:aws:ssm:eu-central-1:553637109631:parameter/javabin/platform/google-admin-sa",
                          + "arn:aws:ssm:eu-central-1:553637109631:parameter/javabin/platform/google-admin-email",
                        ]
                      + Sid      = "SSMReadGoogleCreds"
                    },
                ]
              + Version   = "2012-10-17"
            }
        )
      + role        = "javabin-ci-registry"
    }

  # module.iam.aws_iam_role_policy.ci_registry_lambda will be destroyed
  # (because aws_iam_role_policy.ci_registry_lambda is not in configuration)
  - resource "aws_iam_role_policy" "ci_registry_lambda" {
      - id     = "javabin-ci-registry:invoke-team-provisioner" -> null
      - name   = "invoke-team-provisioner" -> null
      - policy = jsonencode(
            {
              - Statement = [
                  - {
                      - Action   = "lambda:InvokeFunction"
                      - Effect   = "Allow"
                      - Resource = "arn:aws:lambda:eu-central-1:553637109631:function:javabin-team-provisioner"
                    },
                ]
              - Version   = "2012-10-17"
            }
        ) -> null
      - role   = "javabin-ci-registry" -> null
    }

  # module.iam.aws_iam_role_policy_attachment.ci_infra_plan_readonly will be created
  + resource "aws_iam_role_policy_attachment" "ci_infra_plan_readonly" {
      + id         = (known after apply)
      + policy_arn = "arn:aws:iam::aws:policy/ReadOnlyAccess"
      + role       = "javabin-ci-infra-plan"
    }

  # module.lambdas.aws_iam_role_policy.cost_report will be updated in-place
  ~ resource "aws_iam_role_policy" "cost_report" {
        id     = "javabin-cost-report:javabin-cost-report"
        name   = "javabin-cost-report"
      ~ policy = jsonencode(
          ~ {
              ~ Statement = [
                    # (1 unchanged element hidden)
                    {
                        Action   = [
                            "ce:GetCostAndUsage",
                        ]
                        Effect   = "Allow"
                        Resource = "*"
                        Sid      = "CostExplorer"
                    },
                  ~ {
                      ~ Sid      = "Bedrock" -> "BedrockInferenceProfile"
                        # (3 unchanged attributes hidden)
                    },
                  + {
                      + Action    = [
                          + "bedrock:InvokeModel",
                          + "bedrock:Converse",
                        ]
                      + Condition = {
                          + StringLike = {
                              + "bedrock:InferenceProfileArn" = "arn:aws:bedrock:eu-central-1:553637109631:inference-profile/eu.anthropic.*"
                            }
                        }
                      + Effect    = "Allow"
                      + Resource  = "arn:aws:bedrock:eu-*::foundation-model/anthropic.*"
                      + Sid       = "BedrockFoundationModels"
                    },
                ]
                # (1 unchanged attribute hidden)
            }
        )
        # (1 unchanged attribute hidden)
    }

  # module.lambdas.aws_iam_role_policy.slack_alert will be updated in-place
  ~ resource "aws_iam_role_policy" "slack_alert" {
        id     = "javabin-slack-alert:javabin-slack-alert"
        name   = "javabin-slack-alert"
      ~ policy = jsonencode(
          ~ {
              ~ Statement = [
                    {
                        Action   = "ssm:GetParameter"
                        Effect   = "Allow"
                        Resource = [
                            "arn:aws:ssm:eu-central-1:553637109631:parameter/javabin/slack/*",
                        ]
                        Sid      = "SSMRead"
                    },
                  ~ {
                      ~ Sid      = "Bedrock" -> "BedrockInferenceProfile"
                        # (3 unchanged attributes hidden)
                    },
                  + {
                      + Action    = [
                          + "bedrock:InvokeModel",
                          + "bedrock:Converse",
                        ]
                      + Condition = {
                          + StringLike = {
                              + "bedrock:InferenceProfileArn" = "arn:aws:bedrock:eu-central-1:553637109631:inference-profile/eu.anthropic.*"
                            }
                        }
                      + Effect    = "Allow"
                      + Resource  = "arn:aws:bedrock:eu-*::foundation-model/anthropic.*"
                      + Sid       = "BedrockFoundationModels"
                    },
                    {
                        Action   = "pricing:GetProducts"
                        Effect   = "Allow"
                        Resource = "*"
                        Sid      = "PricingRead"
                    },
                    # (2 unchanged elements hidden)
                ]
                # (1 unchanged attribute hidden)
            }
        )
        # (1 unchanged attribute hidden)
    }

Plan: 1 to import, 3 to add, 4 to change, 1 to destroy.

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

Saved the plan to: tfplan

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

@github-actions
Copy link
Copy Markdown

LLM Plan Review

Risk: 🟢 LOW

Routine IAM policy updates for CI/CD roles with Bedrock inference profile additions and minor policy restructuring.

  • [routine] GitHub Actions OIDC trust policy for ci_infra role restricted from wildcard to specific main branch (repo:javaBin/platform:ref:refs/heads/main), improving security posture
  • [routine] Bedrock inference profile permissions added to ci_infra_plan, cost_report, and slack_alert roles for AI model invocation capabilities
  • [routine] ci_registry role policy refactored: existing inline policy (invoke-team-provisioner) being replaced with new standalone policy including Google credential SSM access
  • [routine] ci_infra_plan role imported and updated with tags (environment=production, team=javabin) and ReadOnlyAccess policy attachment formalized
  • [routine] All changes are in-place updates or policy restructuring with no resource destruction, data loss, or security group modifications

@Alexanderamiri Alexanderamiri merged commit 7648534 into main Mar 12, 2026
3 checks passed
@Alexanderamiri Alexanderamiri deleted the fix/bedrock-cross-region-iam branch March 12, 2026 15:18
Alexanderamiri added a commit that referenced this pull request May 9, 2026
## Summary

Cross-region inference profiles (`eu.anthropic.*`) route to foundation
models in EU destination regions (eu-central-1, eu-north-1, eu-west-1,
eu-west-3). IAM requires explicit access to the destination foundation
model ARNs, not just the inference profile.

This was hidden by the old `*:*` infra role and has been a latent bug on
the Lambda roles (slack-alert, cost-report) — they'd fail if Bedrock
routed to eu-north-1.

Adds a second IAM statement with foundation model ARNs for all EU
destination regions, scoped via `bedrock:InferenceProfileArn` condition
so they only work through our inference profiles.

Fixes: ci-infra-plan role, slack-alert Lambda role, cost-report Lambda
role.

## Test plan

- [ ] Plan job succeeds with LLM review (Bedrock call works)
- [ ] Apply creates the IAM policy changes on Lambda roles
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