Skip to content

Fix apply: permission boundary on new Lambda roles, defer cost tags#78

Merged
Alexanderamiri merged 1 commit into
mainfrom
fix/apply-boundary-and-tags
Mar 17, 2026
Merged

Fix apply: permission boundary on new Lambda roles, defer cost tags#78
Alexanderamiri merged 1 commit into
mainfrom
fix/apply-boundary-and-tags

Conversation

@Alexanderamiri
Copy link
Copy Markdown
Member

Summary

Fixes apply failure from #77.

  • Permission boundary: budget-enforcer and resource-tagger IAM roles were missing permissions_boundary. The self-replicating boundary on ci-infra blocks role creation without it.
  • Cost allocation tags: repo, created-by, commit tags don't exist on any billed resource yet — AWS rejects activation. Defer to phase 2/3 (activate after resources with those tags exist).

Test plan

  • terraform plan shows 2 role modifications (add boundary) + 3 tag removals
  • Apply succeeds

…t tags

- budget-enforcer and resource-tagger roles missing permissions_boundary
  (required by self-replicating boundary on ci-infra role)
- Cost allocation tags: only activate tags already in billing (team, service,
  environment, managed-by). Defer repo/created-by/commit to next apply after
  resources with those tags exist.
@github-actions
Copy link
Copy Markdown

Terraform Plan

🚧 Changes detected — Plan: 12 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

Terraform will perform the following actions:

  # module.lambdas.aws_cloudwatch_event_target.resource_tagger will be created
  + resource "aws_cloudwatch_event_target" "resource_tagger" {
      + arn            = (known after apply)
      + event_bus_name = "default"
      + force_destroy  = false
      + id             = (known after apply)
      + rule           = "javabin-resource-tagger-trigger"
      + target_id      = "invoke-resource-tagger"
    }

  # module.lambdas.aws_iam_role.budget_enforcer will be created
  + resource "aws_iam_role" "budget_enforcer" {
      + arn                   = (known after apply)
      + assume_role_policy    = jsonencode(
            {
              + Statement = [
                  + {
                      + Action    = "sts:AssumeRole"
                      + Effect    = "Allow"
                      + Principal = {
                          + Service = "lambda.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                  = "javabin-budget-enforcer"
      + name_prefix           = (known after apply)
      + path                  = "/"
      + permissions_boundary  = "arn:aws:iam::553637109631:policy/javabin-developer-boundary"
      + tags_all              = {
          + "environment" = "production"
          + "managed-by"  = "terraform"
          + "repo"        = "javaBin/platform"
          + "service"     = "platform"
          + "team"        = "javabin"
        }
      + unique_id             = (known after apply)
    }

  # module.lambdas.aws_iam_role.resource_tagger will be created
  + resource "aws_iam_role" "resource_tagger" {
      + arn                   = (known after apply)
      + assume_role_policy    = jsonencode(
            {
              + Statement = [
                  + {
                      + Action    = "sts:AssumeRole"
                      + Effect    = "Allow"
                      + Principal = {
                          + Service = "lambda.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                  = "javabin-resource-tagger"
      + name_prefix           = (known after apply)
      + path                  = "/"
      + permissions_boundary  = "arn:aws:iam::553637109631:policy/javabin-developer-boundary"
      + tags_all              = {
          + "environment" = "production"
          + "managed-by"  = "terraform"
          + "repo"        = "javaBin/platform"
          + "service"     = "platform"
          + "team"        = "javabin"
        }
      + unique_id             = (known after apply)
    }

  # module.lambdas.aws_iam_role_policy.budget_enforcer will be created
  + resource "aws_iam_role_policy" "budget_enforcer" {
      + id          = (known after apply)
      + name        = "javabin-budget-enforcer"
      + name_prefix = (known after apply)
      + policy      = jsonencode(
            {
              + Statement = [
                  + {
                      + Action   = "ssm:GetParameter"
                      + Effect   = "Allow"
                      + Resource = "arn:aws:ssm:eu-central-1:553637109631:parameter/javabin/slack/*"
                      + Sid      = "SSMRead"
                    },
                  + {
                      + Action    = [
                          + "ecs:ListServices",
                          + "ecs:DescribeServices",
                          + "ecs:ListTagsForResource",
                        ]
                      + Condition = {
                          + StringEquals = {
                              + "ecs:cluster" = "arn:aws:ecs:eu-central-1:553637109631:cluster/javabin-platform"
                            }
                        }
                      + Effect    = "Allow"
                      + Resource  = "*"
                      + Sid       = "ECSListAndDescribe"
                    },
                  + {
                      + Action   = "ecs:ListServices"
                      + Effect   = "Allow"
                      + Resource = "arn:aws:ecs:eu-central-1:553637109631:cluster/javabin-platform"
                      + Sid      = "ECSListServicesUnconstrained"
                    },
                  + {
                      + Action   = "ecs:UpdateService"
                      + Effect   = "Allow"
                      + Resource = "arn:aws:ecs:eu-central-1:553637109631:service/javabin-platform/*"
                      + Sid      = "ECSUpdateService"
                    },
                ]
              + Version   = "2012-10-17"
            }
        )
      + role        = (known after apply)
    }

  # module.lambdas.aws_iam_role_policy.resource_tagger will be created
  + resource "aws_iam_role_policy" "resource_tagger" {
      + id          = (known after apply)
      + name        = "javabin-resource-tagger"
      + name_prefix = (known after apply)
      + policy      = jsonencode(
            {
              + Statement = [
                  + {
                      + Action   = [
                          + "tag:TagResources",
                          + "tag:GetResources",
                        ]
                      + Effect   = "Allow"
                      + Resource = "*"
                      + Sid      = "TagResources"
                    },
                ]
              + Version   = "2012-10-17"
            }
        )
      + role        = (known after apply)
    }

  # module.lambdas.aws_iam_role_policy_attachment.budget_enforcer_logs will be created
  + resource "aws_iam_role_policy_attachment" "budget_enforcer_logs" {
      + id         = (known after apply)
      + policy_arn = "arn:aws:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole"
      + role       = "javabin-budget-enforcer"
    }

  # module.lambdas.aws_iam_role_policy_attachment.resource_tagger_logs will be created
  + resource "aws_iam_role_policy_attachment" "resource_tagger_logs" {
      + id         = (known after apply)
      + policy_arn = "arn:aws:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole"
      + role       = "javabin-resource-tagger"
    }

  # module.lambdas.aws_lambda_function.budget_enforcer will be created
  + resource "aws_lambda_function" "budget_enforcer" {
      + architectures                  = (known after apply)
      + arn                            = (known after apply)
      + code_sha256                    = (known after apply)
      + filename                       = "lambdas/builds/budget_enforcer.zip"
      + function_name                  = "javabin-budget-enforcer"
      + handler                        = "handler.handler"
      + id                             = (known after apply)
      + invoke_arn                     = (known after apply)
      + last_modified                  = (known after apply)
      + memory_size                    = 128
      + package_type                   = "Zip"
      + publish                        = false
      + qualified_arn                  = (known after apply)
      + qualified_invoke_arn           = (known after apply)
      + reserved_concurrent_executions = -1
      + role                           = (known after apply)
      + runtime                        = "python3.12"
      + signing_job_arn                = (known after apply)
      + signing_profile_version_arn    = (known after apply)
      + skip_destroy                   = false
      + source_code_hash               = "i3E4tI5mT0qCS601izSv52QOgwAndKfcZ3JkLzxTNis="
      + source_code_size               = (known after apply)
      + tags_all                       = {
          + "environment" = "production"
          + "managed-by"  = "terraform"
          + "repo"        = "javaBin/platform"
          + "service"     = "platform"
          + "team"        = "javabin"
        }
      + timeout                        = 60
      + version                        = (known after apply)

      + environment {
          + variables = {
              + "BUDGET_NAME_PREFIX" = "javabin-team-"
              + "COST_WEBHOOK_PARAM" = "/javabin/slack/platform-cost-alerts-webhook"
              + "ECS_CLUSTER"        = "javabin-platform"
            }
        }
    }

  # module.lambdas.aws_lambda_function.resource_tagger will be created
  + resource "aws_lambda_function" "resource_tagger" {
      + architectures                  = (known after apply)
      + arn                            = (known after apply)
      + code_sha256                    = (known after apply)
      + filename                       = "lambdas/builds/resource_tagger.zip"
      + function_name                  = "javabin-resource-tagger"
      + handler                        = "handler.handler"
      + id                             = (known after apply)
      + invoke_arn                     = (known after apply)
      + last_modified                  = (known after apply)
      + memory_size                    = 128
      + package_type                   = "Zip"
      + publish                        = false
      + qualified_arn                  = (known after apply)
      + qualified_invoke_arn           = (known after apply)
      + reserved_concurrent_executions = -1
      + role                           = (known after apply)
      + runtime                        = "python3.12"
      + signing_job_arn                = (known after apply)
      + signing_profile_version_arn    = (known after apply)
      + skip_destroy                   = false
      + source_code_hash               = "lk/mypHvtYyrMSzY8lPGOhrKtJnu0FawUhidl+Nm+2Q="
      + source_code_size               = (known after apply)
      + tags_all                       = {
          + "environment" = "production"
          + "managed-by"  = "terraform"
          + "repo"        = "javaBin/platform"
          + "service"     = "platform"
          + "team"        = "javabin"
        }
      + timeout                        = 30
      + version                        = (known after apply)

      + environment {
          + variables = {
              + "AWS_ACCOUNT_ID" = "553637109631"
            }
        }
    }

  # module.lambdas.aws_lambda_permission.budget_enforcer_sns will be created
  + resource "aws_lambda_permission" "budget_enforcer_sns" {
      + action              = "lambda:InvokeFunction"
      + function_name       = "javabin-budget-enforcer"
      + id                  = (known after apply)
      + principal           = "sns.amazonaws.com"
      + source_arn          = "arn:aws:sns:eu-central-1:553637109631:javabin-budget-enforcement"
      + statement_id        = "AllowSNSBudgetEnforcement"
      + statement_id_prefix = (known after apply)
    }

  # module.lambdas.aws_lambda_permission.resource_tagger_eventbridge will be created
  + resource "aws_lambda_permission" "resource_tagger_eventbridge" {
      + action              = "lambda:InvokeFunction"
      + function_name       = "javabin-resource-tagger"
      + id                  = (known after apply)
      + principal           = "events.amazonaws.com"
      + source_arn          = "arn:aws:events:eu-central-1:553637109631:rule/javabin-resource-tagger-trigger"
      + statement_id        = "AllowEventBridge"
      + statement_id_prefix = (known after apply)
    }

  # module.lambdas.aws_sns_topic_subscription.budget_enforcer will be created
  + resource "aws_sns_topic_subscription" "budget_enforcer" {
      + arn                             = (known after apply)
      + confirmation_timeout_in_minutes = 1
      + confirmation_was_authenticated  = (known after apply)
      + endpoint                        = (known after apply)
      + endpoint_auto_confirms          = false
      + filter_policy_scope             = (known after apply)
      + id                              = (known after apply)
      + owner_id                        = (known after apply)
      + pending_confirmation            = (known after apply)
      + protocol                        = "lambda"
      + raw_message_delivery            = false
      + topic_arn                       = "arn:aws:sns:eu-central-1:553637109631:javabin-budget-enforcement"
    }

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

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

Saved the plan to: tfplan

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

LLM Review

Risk: 🟢 LOW

Adding two new Lambda functions (budget_enforcer and resource_tagger) with associated IAM roles, policies, and event integrations for cost management and resource tagging automation.

  • [routine] Creating two new Lambda functions (budget_enforcer and resource_tagger) with standard Python 3.12 runtime, basic execution roles, and appropriate timeout/memory settings
  • [routine] IAM roles for both Lambdas include permissions_boundary set to javabin-developer-boundary, enforcing least privilege and preventing privilege escalation
  • [routine] budget_enforcer Lambda has scoped permissions: SSM parameter read for Slack webhooks, ECS service listing/describing/updating limited to javabin-platform cluster
  • [routine] resource_tagger Lambda has broad tag:TagResources and tag:GetResources permissions on all resources, but this is appropriate for a resource tagging automation function
  • 💰 [cost] Adding two new Lambda functions will incur minimal additional costs (128MB memory, infrequent invocations via EventBridge and SNS triggers)

@Alexanderamiri Alexanderamiri merged commit 3641f24 into main Mar 17, 2026
3 checks passed
@Alexanderamiri Alexanderamiri deleted the fix/apply-boundary-and-tags branch March 17, 2026 19:25
Alexanderamiri added a commit that referenced this pull request May 9, 2026
)

## Summary
Fixes apply failure from #77.

- **Permission boundary**: `budget-enforcer` and `resource-tagger` IAM
roles were missing `permissions_boundary`. The self-replicating boundary
on `ci-infra` blocks role creation without it.
- **Cost allocation tags**: `repo`, `created-by`, `commit` tags don't
exist on any billed resource yet — AWS rejects activation. Defer to
phase 2/3 (activate after resources with those tags exist).

## Test plan
- [ ] `terraform plan` shows 2 role modifications (add boundary) + 3 tag
removals
- [ ] Apply succeeds
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