Add seahaven-lambda-execution-boundary managed policy

Lambda execution roles auto-generated by SAM have no ceiling today —
a misconfigured Policies block could grant excessive permissions that
persist at runtime. This boundary caps every SAM function execution
role at the union of what the five stacks actually need, so the
effective permissions are always the intersection of the role's own
policies and this document.

The policy is a deliberate superset rather than exact-minimum: being
slightly broad is safer than a boundary that breaks functions at
runtime. Per-service scoping will tighten in follow-up work.

SAM template agents: add
  PermissionsBoundary: arn:aws:iam::328440206208:policy/seahaven-lambda-execution-boundary
to Globals.Function in all five stacks after this stack deploys.

Refs: INFRA-103
This commit is contained in:
Adam Moussa 2026-06-10 13:51:52 -04:00
parent 7f84f9cfde
commit daddff57b5

View file

@ -31,6 +31,192 @@ Resources:
ThumbprintList:
- 6938fd4d98bab03faadb97b34396831e3780aea1
# ---------------------------------------------------------------------------
# Lambda execution permissions boundary (INFRA-103)
#
# This managed policy is the CEILING for every Lambda execution role that the
# five SAM stacks auto-generate via AWS::Serverless::Function. Applying it as
# PermissionsBoundary on those roles means the effective permissions are the
# intersection of the role's own policies and this boundary, so a misconfigured
# SAM role can never exceed what is listed here.
#
# The boundary is intentionally a SUPERSET of the union of all runtime
# permissions currently granted across the five stacks. Being slightly broad
# is the correct trade-off at this stage — a boundary that is too tight will
# break Lambda functions at runtime after deploy, which is worse than a slightly
# loose boundary that is tightened in a follow-up.
#
# Permission sources per stack:
#
# afterhours-shift-manager
# - DynamoDB CRUD (afterhours-shifts table)
# - secretsmanager:GetSecretValue (afterhours-shift-manager/*)
# - ses:SendEmail (SES identity)
# - CloudWatch Logs (all functions)
#
# payments-dashboard
# - DynamoDB CRUD / Read (PaymentsDashboard table)
# - S3 GetObject (payroll-emails, payments-csv buckets)
# - secretsmanager:GetSecretValue (payments-dashboard/*)
# - sqs:SendMessage + sqs:ReceiveMessage + sqs:DeleteMessage etc.
# (PayrollBatchQueue + DLQs)
# - lambda:InvokeFunction (ExpenseReceiver → ExpenseProcessor)
# - ec2:CreateNetworkInterface / DescribeNetworkInterfaces /
# DeleteNetworkInterface (VPC-attached functions)
# - CloudWatch Logs
#
# meal-order-manager
# - DynamoDB CRUD / Read (meal-order-manager-orders table)
# - S3 CRUD (ReportsBucket) + s3:GetObject (ReportsBucket presigned URLs)
# - secretsmanager:GetSecretValue (meal-order-manager/*)
# - ssm:GetParameter (/meal-order-manager/*)
# - lambda:InvokeFunction (submit-order → slack-notifier,
# close-form → aggregate-orders)
# - ses:SendRawEmail
# - CloudWatch Logs
#
# front-integrations
# - DynamoDB CRUD (front-sla-alerts table)
# - secretsmanager:GetSecretValue (by ARN, various)
# - CloudWatch Logs
#
# afi-backup-monitor
# - secretsmanager:GetSecretValue (by ARN)
# - CloudWatch Logs
#
# ---------------------------------------------------------------------------
LambdaExecutionBoundary:
Type: AWS::IAM::ManagedPolicy
Properties:
ManagedPolicyName: seahaven-lambda-execution-boundary
Description: >-
Permissions boundary ceiling for all SAM-managed Lambda execution roles.
Applied via PermissionsBoundary on every Globals.Function in the five
SAM stacks (INFRA-103). Effective permissions are the intersection of
this policy and the role's own inline policies.
PolicyDocument:
Version: "2012-10-17"
Statement:
# ── CloudWatch Logs (every Lambda) ──────────────────────────────────
- Sid: CloudWatchLogs
Effect: Allow
Action:
- logs:CreateLogGroup
- logs:CreateLogStream
- logs:PutLogEvents
- logs:DescribeLogGroups
- logs:DescribeLogStreams
Resource: "*"
# ── X-Ray tracing (standard Lambda execution) ────────────────────
- Sid: XRay
Effect: Allow
Action:
- xray:PutTraceSegments
- xray:PutTelemetryRecords
Resource: "*"
# ── VPC / ENI management (payments-dashboard VPC functions) ────────
# ec2:Describe* are required even for non-VPC Lambdas as the ENI
# lifecycle actions need them.
- Sid: Ec2Eni
Effect: Allow
Action:
- ec2:CreateNetworkInterface
- ec2:DescribeNetworkInterfaces
- ec2:DeleteNetworkInterface
- ec2:AssignPrivateIpAddresses
- ec2:UnassignPrivateIpAddresses
Resource: "*"
# ── DynamoDB (afterhours, payments, meal-order, front-integrations) ─
- Sid: DynamoDB
Effect: Allow
Action:
- dynamodb:GetItem
- dynamodb:PutItem
- dynamodb:UpdateItem
- dynamodb:DeleteItem
- dynamodb:Query
- dynamodb:Scan
- dynamodb:BatchGetItem
- dynamodb:BatchWriteItem
- dynamodb:DescribeTable
- dynamodb:ConditionCheckItem
Resource:
- !Sub "arn:aws:dynamodb:us-east-1:${AWS::AccountId}:table/*"
# ── S3 (payments-dashboard read, meal-order-manager CRUD) ──────────
- Sid: S3
Effect: Allow
Action:
- s3:GetObject
- s3:PutObject
- s3:DeleteObject
- s3:ListBucket
- s3:GetBucketLocation
- s3:GetObjectVersion
- s3:GetObjectTagging
- s3:PutObjectTagging
Resource:
- !Sub "arn:aws:s3:::*-${AWS::AccountId}"
- !Sub "arn:aws:s3:::*-${AWS::AccountId}/*"
# meal-order-manager ReportsBucket (non-AccountId suffix pattern)
- !Sub "arn:aws:s3:::meal-order-manager-reports-${AWS::AccountId}"
- !Sub "arn:aws:s3:::meal-order-manager-reports-${AWS::AccountId}/*"
- !Sub "arn:aws:s3:::meal-order-manager-form-${AWS::AccountId}"
- !Sub "arn:aws:s3:::meal-order-manager-form-${AWS::AccountId}/*"
# ── Secrets Manager (all stacks) ──────────────────────────────────
- Sid: SecretsManager
Effect: Allow
Action:
- secretsmanager:GetSecretValue
- secretsmanager:DescribeSecret
Resource:
- !Sub "arn:aws:secretsmanager:us-east-1:${AWS::AccountId}:secret:*"
# ── SSM Parameter Store (meal-order-manager, afterhours) ──────────
- Sid: SSMParameterRead
Effect: Allow
Action:
- ssm:GetParameter
- ssm:GetParameters
- ssm:GetParametersByPath
Resource:
- !Sub "arn:aws:ssm:us-east-1:${AWS::AccountId}:parameter/*"
# ── SQS (payments-dashboard batch queues) ─────────────────────────
- Sid: SQS
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}:*"
# ── Lambda invocation (payments, meal-order inter-function calls) ──
- Sid: LambdaInvoke
Effect: Allow
Action:
- lambda:InvokeFunction
Resource:
- !Sub "arn:aws:lambda:us-east-1:${AWS::AccountId}:function:*"
# ── SES (afterhours weekly-post, meal-order email-report) ──────────
- Sid: SES
Effect: Allow
Action:
- ses:SendEmail
- ses:SendRawEmail
Resource:
- !Sub "arn:aws:ses:us-east-1:${AWS::AccountId}:identity/*"
# ---------------------------------------------------------------------------
# Shared CloudFormation execution role (SAM stacks)
# ---------------------------------------------------------------------------
@ -540,6 +726,13 @@ Resources:
- !Sub arn:aws:iam::${AWS::AccountId}:role/cdk-hnb659fds-*
Outputs:
LambdaExecutionBoundaryArn:
Value: !Ref LambdaExecutionBoundary
Description: >-
ARN of the Lambda execution permissions boundary. Set this as
PermissionsBoundary on Globals.Function in all five SAM stacks.
Export:
Name: seahaven-lambda-execution-boundary-arn
SamCfnExecutionRoleArn:
Value: !GetAtt SamCfnExecutionRole.Arn
Export: