fix(iam): shrink shared lambda boundary to the four-statement floor (PLAT-52)

PermissionsBoundaryUsageCount is 0 in prod and dev, so the shared
seahaven-lambda-execution-boundary drops the packed IsProdAccount
data-plane statements and keeps CloudWatchLogsWrite,
CloudWatchLogsDescribe, XRay, and Ec2Eni.

Co-authored-by: Adam Moussa <amoussa1229@users.noreply.github.com>
This commit is contained in:
Cursor Agent 2026-09-28 00:47:16 +00:00
parent d80295c005
commit 06c0be8314
No known key found for this signature in database

View file

@ -134,9 +134,11 @@ Description: >-
# on the synthesized PolicyDocument with ${AWS::AccountId} resolved, and UPDATE
# THE NUMBERS in the same edit.
#
# Shared LambdaExecutionBoundary (legacy ceiling; do not widen): 5986
# characters / 15 statements as of 2026-08-10 (PLAT-100). Headroom 158.
# Leave it unchanged until live roles retarget (PLAT-52 phase 2).
# Shared LambdaExecutionBoundary (floor only; do not widen): 708
# characters / 4 statements as of 2026-09-28 (PLAT-52). Headroom 5436.
# Statements: CloudWatchLogsWrite, CloudWatchLogsDescribe, XRay, Ec2Eni.
# Packed IsProdAccount data-plane statements removed once
# PermissionsBoundaryUsageCount was 0 in prod and dev.
#
# Per-workload policies (floor + own data plane). Compact sizes recorded
# after synth (prod, ${AWS::AccountId}=011934824531):
@ -523,223 +525,11 @@ Resources:
- ec2:DescribeVpcs
Resource: "*"
# ── Shared workload data-plane (PLAT-93 PolicySize consolidation) ───
# Per-workload secret/DDB/S3 SIDs were merged so meal-order-manager
# (PLAT-70) can fit under the 6,144-character managed-policy cap
# without introducing new action wildcards. Exact secret ARNs only.
# End-state isolation remains PLAT-52 / INFRA-187.
#
# WorkloadSecrets covers: afi-backup-monitor (PLAT-56),
# front-integrations (PLAT-72), procurement-ingest (PLAT-86),
# meal-order-manager (PLAT-70).
- !If
- IsProdAccount
- Sid: WorkloadSecrets
Effect: Allow
Action:
- secretsmanager:GetSecretValue
Resource:
- arn:aws:secretsmanager:us-east-1:011934824531:secret:afi-api-key-w0E02a
- arn:aws:secretsmanager:us-east-1:011934824531:secret:afi-slack-webhook-T4oR3G
- arn:aws:secretsmanager:us-east-1:011934824531:secret:front-integrations/front-api-token-UXKv0U
- arn:aws:secretsmanager:us-east-1:011934824531:secret:front-integrations/slack-bot-token-giGfA7
- arn:aws:secretsmanager:us-east-1:011934824531:secret:front-integrations/google-service-account-dqv3Bo
- arn:aws:secretsmanager:us-east-1:011934824531:secret:procurement-ingest/web-ui-auth-token-ApAMmr
- arn:aws:secretsmanager:us-east-1:011934824531:secret:workorder-ingest/shoc-webhook-hmac-puYTcB
- arn:aws:secretsmanager:us-east-1:011934824531:secret:meal-order-manager/slack-bot-token-ZGmSGw
- !Ref AWS::NoValue
# WorkloadDynamoDB: enumerated union of front-integrations CRUD +
# procurement-ingest CRUD/stream actions. Meal-order table ARNs appended.
# Stream actions on non-stream tables are inert at the ceiling.
- !If
- IsProdAccount
- Sid: WorkloadDynamoDB
Effect: Allow
Action:
- dynamodb:GetItem
- dynamodb:PutItem
- dynamodb:UpdateItem
- dynamodb:DeleteItem
- dynamodb:Query
- dynamodb:Scan
- dynamodb:BatchGetItem
- dynamodb:BatchWriteItem
- dynamodb:DescribeTable
- dynamodb:ConditionCheckItem
- dynamodb:GetRecords
- dynamodb:GetShardIterator
- dynamodb:DescribeStream
# ListStreams is a collection API (Resource "*"); ARN-scoping
# it is a silent no-op. Runtime stream consumers use the
# stream ARN via DescribeStream/GetRecords above.
Resource:
- !Sub "arn:aws:dynamodb:us-east-1:${AWS::AccountId}:table/front-sla-alerts"
- !Sub "arn:aws:dynamodb:us-east-1:${AWS::AccountId}:table/front-sla-alerts/index/*"
- !Sub "arn:aws:dynamodb:us-east-1:${AWS::AccountId}:table/purchase-orders"
- !Sub "arn:aws:dynamodb:us-east-1:${AWS::AccountId}:table/purchase-orders/*"
- !Sub "arn:aws:dynamodb:us-east-1:${AWS::AccountId}:table/verified-sites"
- !Sub "arn:aws:dynamodb:us-east-1:${AWS::AccountId}:table/verified-sites/*"
- !Sub "arn:aws:dynamodb:us-east-1:${AWS::AccountId}:table/pending-site-review"
- !Sub "arn:aws:dynamodb:us-east-1:${AWS::AccountId}:table/pending-site-review/*"
- !Sub "arn:aws:dynamodb:us-east-1:${AWS::AccountId}:table/work-orders"
- !Sub "arn:aws:dynamodb:us-east-1:${AWS::AccountId}:table/work-orders/*"
- !Sub "arn:aws:dynamodb:us-east-1:${AWS::AccountId}:table/work-order-comments"
- !Sub "arn:aws:dynamodb:us-east-1:${AWS::AccountId}:table/work-order-comments/*"
- !Sub "arn:aws:dynamodb:us-east-1:${AWS::AccountId}:table/meal-order-manager-orders"
- !Sub "arn:aws:dynamodb:us-east-1:${AWS::AccountId}:table/meal-order-manager-orders/index/*"
- !Ref AWS::NoValue
# ── procurement-ingest remaining data plane + meal-order S3 (PLAT-86/70) ─
# WorkloadS3 keeps the prior ProcurementIngestS3 action list and appends
# meal-order form/reports bucket ARNs (same object CRUD shape).
- !If
- IsProdAccount
- Sid: WorkloadS3
Effect: Allow
Action:
- s3:GetObject*
- s3:GetBucket*
- s3:List*
- s3:PutObject*
- s3:DeleteObject*
- s3:AbortMultipartUpload
Resource:
- !Sub "arn:aws:s3:::po-ingest-emails-${AWS::AccountId}"
- !Sub "arn:aws:s3:::po-ingest-emails-${AWS::AccountId}/*"
- !Sub "arn:aws:s3:::workorder-ingest-emails-${AWS::AccountId}"
- !Sub "arn:aws:s3:::workorder-ingest-emails-${AWS::AccountId}/*"
- !Sub "arn:aws:s3:::meal-order-manager-form-${AWS::AccountId}"
- !Sub "arn:aws:s3:::meal-order-manager-form-${AWS::AccountId}/*"
- !Sub "arn:aws:s3:::meal-order-manager-reports-${AWS::AccountId}"
- !Sub "arn:aws:s3:::meal-order-manager-reports-${AWS::AccountId}/*"
- !Ref AWS::NoValue
- !If
- IsProdAccount
- Sid: ProcurementIngestSqs
Effect: Allow
Action:
- sqs:SendMessage
- sqs:ReceiveMessage
- sqs:DeleteMessage
- sqs:GetQueueAttributes
- sqs:GetQueueUrl
- sqs:ChangeMessageVisibility
Resource:
- !Sub "arn:aws:sqs:us-east-1:${AWS::AccountId}:po-ingest-*"
- !Sub "arn:aws:sqs:us-east-1:${AWS::AccountId}:WorkorderIngestStack-*"
- !Sub "arn:aws:sqs:us-east-1:${AWS::AccountId}:workorder-shoc-emitter-*"
- !Ref AWS::NoValue
- !If
- IsProdAccount
- Sid: ProcurementIngestKms
Effect: Allow
Action:
- kms:Decrypt
- kms:DescribeKey
- kms:Encrypt
- kms:GenerateDataKey*
- kms:ReEncrypt*
Resource:
- arn:aws:kms:us-east-1:011934824531:key/be5fa4cb-c546-40fe-a13d-c7bec79f5d12
- arn:aws:kms:us-east-1:011934824531:key/d10fd1f0-a61a-4405-8568-85e9fd11ba18
- !Ref AWS::NoValue
- !If
- IsProdAccount
- Sid: ProcurementIngestBedrock
Effect: Allow
Action:
- bedrock:InvokeModel
- bedrock:InvokeModelWithResponseStream
Resource:
- !Sub "arn:aws:bedrock:us-east-1:${AWS::AccountId}:inference-profile/us.anthropic.claude-haiku-4-5-20251001-v1:0"
- arn:aws:bedrock:us-east-1::foundation-model/anthropic.claude-haiku-4-5-20251001-v1:0
- arn:aws:bedrock:us-east-2::foundation-model/anthropic.claude-haiku-4-5-20251001-v1:0
- arn:aws:bedrock:us-west-2::foundation-model/anthropic.claude-haiku-4-5-20251001-v1:0
- !Ref AWS::NoValue
# site-alerts publish shared by procurement-ingest alarms and
# meal-order-manager (PLAT-70); no separate MealOrder SNS statement.
- !If
- IsProdAccount
- Sid: ProcurementIngestSns
Effect: Allow
Action:
- sns:Publish
Resource:
- !Sub "arn:aws:sns:us-east-1:${AWS::AccountId}:site-alerts"
- !Ref AWS::NoValue
# ── seahaven-site (PLAT-91) — content-deploy role data plane ────────
# TF creates githubdeploy-seahaven-site under /tf-managed/ with this
# boundary as ceiling. Role policy is S3 sync + CloudFront invalidate
# only; no Lambda. Exact origin bucket + distribution-scoped invalidate.
- !If
- IsProdAccount
- Sid: SeahavenSiteOriginS3
Effect: Allow
Action:
- s3:GetObject
- s3:PutObject
- s3:DeleteObject
- s3:GetObjectTagging
- s3:PutObjectTagging
- s3:ListBucket
- s3:GetBucketLocation
Resource:
- arn:aws:s3:::seahaven-site-prod
- arn:aws:s3:::seahaven-site-prod/*
- !Ref AWS::NoValue
- !If
- IsProdAccount
- Sid: SeahavenSiteCloudFrontInvalidate
Effect: Allow
Action:
- cloudfront:CreateInvalidation
- cloudfront:GetInvalidation
Resource:
- !Sub "arn:aws:cloudfront::${AWS::AccountId}:distribution/*"
- !Ref AWS::NoValue
# ── meal-order-manager (PLAT-70 / PLAT-100) — not covered above ─────
# Secrets → WorkloadSecrets; DynamoDB → WorkloadDynamoDB; S3 → WorkloadS3;
# SNS → ProcurementIngestSns. SSM + Lambda Invoke + execute-api share
# one Sid (scoped Resources only) so PolicySize stays under 6,144 —
# a standalone execute-api Sid is ~243 chars against 241 headroom and
# would fail UPDATE with LimitExceeded. SES stays in its own Sid:
# Resource:"*" must not share a statement with execute-api:Invoke
# (that would allow Invoke on every API in the account).
# Weekly-menu OIDC identity policy pins the API id; boundary pins
# method/path only.
- !If
- IsProdAccount
- Sid: MealOrderManager
Effect: Allow
Action:
- ssm:GetParameter
- lambda:InvokeFunction
- execute-api:Invoke
Resource:
- !Sub "arn:aws:ssm:us-east-1:${AWS::AccountId}:parameter/meal-order-manager/*"
- !Sub "arn:aws:lambda:us-east-1:${AWS::AccountId}:function:meal-order-manager-*"
- !Sub "arn:aws:execute-api:us-east-1:${AWS::AccountId}:*/*/GET/api/publish/settings"
- !Sub "arn:aws:execute-api:us-east-1:${AWS::AccountId}:*/*/POST/api/publish/menu"
- !Ref AWS::NoValue
- !If
- IsProdAccount
- Sid: MealOrderManagerSes
Effect: Allow
Action:
- ses:SendRawEmail
Resource: "*"
- !Ref AWS::NoValue
# ── FURTHER PER-WORKLOAD DATA-PLANE ────────────────────────────────
# Do not add statements here. Remaining SAM stacks: create
# seahaven-lambda-execution-boundary-<stack> below and append its ARN
# to SamCfnIamManagementPolicy only (WIDENING PATH). New HCP stacks
# do not append here. This shared document stays unchanged until live
# roles retarget (PLAT-52 phase 2) and PermissionsBoundaryUsageCount
# reaches 0.
# Do not add statements here. The shared document is the four-statement
# floor (PLAT-52). Remaining SAM stacks use
# seahaven-lambda-execution-boundary-<stack> below. New HCP stacks
# do not append here.
# ---------------------------------------------------------------------------
# Per-workload Lambda execution boundaries (PLAT-52 phase 1)
#