From daddff57b5b5158f37212a9aa720c04584c16a11 Mon Sep 17 00:00:00 2001 From: Adam Moussa Date: Wed, 10 Jun 2026 13:51:52 -0400 Subject: [PATCH] Add seahaven-lambda-execution-boundary managed policy MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 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 --- oidc-deploy-roles.yaml | 193 +++++++++++++++++++++++++++++++++++++++++ 1 file changed, 193 insertions(+) diff --git a/oidc-deploy-roles.yaml b/oidc-deploy-roles.yaml index fbb3279..2754733 100644 --- a/oidc-deploy-roles.yaml +++ b/oidc-deploy-roles.yaml @@ -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: