diff --git a/oidc-deploy-roles.yaml b/oidc-deploy-roles.yaml index fbb3279..27d01b5 100644 --- a/oidc-deploy-roles.yaml +++ b/oidc-deploy-roles.yaml @@ -31,6 +31,216 @@ 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) ──────── + # Matches AWSLambdaVPCAccessExecutionRole exactly. + # AssignPrivateIpAddresses / UnassignPrivateIpAddresses are for EFA + # and secondary IPs — not part of the Lambda ENI lifecycle — omitted. + - Sid: Ec2Eni + Effect: Allow + Action: + - ec2:CreateNetworkInterface + - ec2:DescribeNetworkInterfaces + - ec2:DeleteNetworkInterface + - ec2:DescribeSubnets + - ec2:DescribeSecurityGroups + - ec2:DescribeVpcs + Resource: "*" + + # ── DynamoDB (afterhours, payments, meal-order, front-integrations) ─ + # Table/* covers base-table operations; table/*/index/* is required for + # Query/Scan on Global Secondary Indexes. + - 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/*" + - !Sub "arn:aws:dynamodb:us-east-1:${AWS::AccountId}:table/*/index/*" + + # ── 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/*" + - !Sub "arn:aws:ses:us-east-1:${AWS::AccountId}:configuration-set/*" + + # ── KMS (CMK-encrypted resources) ───────────────────────────────── + # Required for Lambda functions that read/write CMK-encrypted AWS + # resources. Verified live state: + # - PaymentsDashboard DynamoDB table: CMK key/0b660af3 (KMS:ENABLED) + # - payments-dashboard CloudWatch log groups: CMK key/b748750c + # Secrets Manager + SQS queues in these stacks use AWS-managed keys + # (aws/secretsmanager, aws/sqs) which do not require explicit kms:* + # actions in the execution role policy. The CMK keys are scoped to + # this account to prevent cross-account KMS calls. + - Sid: KMS + Effect: Allow + Action: + - kms:Decrypt + - kms:GenerateDataKey + - kms:DescribeKey + Resource: + - !Sub "arn:aws:kms:us-east-1:${AWS::AccountId}:key/*" + # --------------------------------------------------------------------------- # Shared CloudFormation execution role (SAM stacks) # --------------------------------------------------------------------------- @@ -540,6 +750,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: