AWSTemplateFormatVersion: "2010-09-09" Description: >- GitHub Actions OIDC deploy roles for Sea Haven Industries repos. Each repo gets a scoped IAM role that GitHub Actions assumes via OIDC. Parameters: GitHubOrg: Type: String Default: Sea-Haven-Industries # No glob metacharacters: this value is interpolated into StringLike trust # conditions, where a '*' override would silently open every role's trust # to any GitHub org with a same-named repo. AllowedPattern: "^[A-Za-z0-9-]+$" CreateOIDCProvider: Type: String Default: "false" AllowedValues: ["true", "false"] Description: Set to true only if the GitHub OIDC provider does not already exist in this account Conditions: ShouldCreateOIDCProvider: !Equals [!Ref CreateOIDCProvider, "true"] Resources: # --------------------------------------------------------------------------- # OIDC Provider (conditional — already exists for seahaven-site) # --------------------------------------------------------------------------- GitHubOIDCProvider: Type: AWS::IAM::OIDCProvider Condition: ShouldCreateOIDCProvider Properties: Url: https://token.actions.githubusercontent.com ClientIdList: - sts.amazonaws.com 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/*" # --------------------------------------------------------------------------- # IAM role lifecycle for the CFN execution role — BOUNDARY-GATED # (attached managed policy) # # Ported verbatim from seahaven-org-baseline # lib/deploy-substrate/deploy-substrate.template.yaml (stack # seahaven-deploy-substrate, deployed and verified in seahaven-prod and # seahaven-dev 2026-07-27). Keep the two copies in lockstep. # # Why a MANAGED policy and not inline on the role: this role's inline # policies total ~10,006 bytes against IAM's hard 10,240-byte per-role # inline limit — about 234 bytes of headroom. The Deny statements below # do not fit inline (the equivalent attempt in prod/dev failed with # ServiceLimitExceeded). Attached managed policies carry their own # separate 6,144-byte budget. # # SECURITY: iam:DeleteRolePermissionsBoundary is deliberately ABSENT from # the Allow below, and explicitly Denied further down. Granting it under # the StringEquals iam:PermissionsBoundary condition is self-defeating: # for a delete, that condition key reflects the boundary CURRENTLY # attached to the target role, so it matches exactly the roles the gate # protects — letting this role create a boundary-gated role with an # inline *:* policy, strip the boundary, and pass the now-unbounded role # to Lambda. Verified live on this very role 2026-07-27 via # simulate-principal-policy (returned: allowed). # # THIS IS THE ONLY COPY. It was introduced alongside an inline # iam-role-management-boundary-gated policy that carried the older, # vulnerable version of these statements; that inline copy was removed once # this one was deployed and verified. Consolidating here also freed the # role's inline budget from 10,006 to 8,261 of the 10,240-byte limit — # prefer adding future statements here rather than inline. # # OPERATIONAL NOTES # - Detaching or deleting this policy does not just drop the Deny backstops, # it drops every IAM permission the role has, so SAM deploys stop working # immediately and loudly rather than silently becoming less safe. The role # cannot do it to itself (DenySelfMutation below), but an administrator # can — treat detach/delete as a break-glass action, not a cleanup step. # - A managed policy keeps at most 5 versions. CloudFormation creates a new # version on every change to this document, so if an update ever fails with # LimitExceeded, prune old versions (list-policy-versions / # delete-policy-version) rather than assuming the template is wrong. # --------------------------------------------------------------------------- SamCfnIamManagementPolicy: Type: AWS::IAM::ManagedPolicy Properties: # Fixed name: changing it makes CloudFormation create a replacement policy # and detach this one, which briefly drops the role's IAM permissions # mid-update. Treat a rename as a coordinated migration, not an edit. This # is the role's FIRST attached managed policy (per-role quota is 10). ManagedPolicyName: seahaven-cfn-exec-iam-management Description: >- Boundary-gated IAM role lifecycle for github-cfn-execution-role, plus the explicit Deny backstops that keep the permissions boundary from being detached or rewritten. Separated from the role's inline policies to stay under IAM's 10,240-byte inline limit. PolicyDocument: Version: "2012-10-17" Statement: # Create role — MUST attach boundary - Sid: IAMCreateRoleWithBoundary Effect: Allow Action: - iam:CreateRole Resource: - !Sub "arn:aws:iam::${AWS::AccountId}:role/*" Condition: StringEquals: "iam:PermissionsBoundary": !Sub "arn:aws:iam::${AWS::AccountId}:policy/seahaven-lambda-execution-boundary" # Attach managed policies — MUST have boundary already on role - Sid: IAMAttachPolicyWithBoundary Effect: Allow Action: - iam:AttachRolePolicy Resource: - !Sub "arn:aws:iam::${AWS::AccountId}:role/*" Condition: StringEquals: "iam:PermissionsBoundary": !Sub "arn:aws:iam::${AWS::AccountId}:policy/seahaven-lambda-execution-boundary" # Put inline policy — MUST have boundary already on role - Sid: IAMPutRolePolicyWithBoundary Effect: Allow Action: - iam:PutRolePolicy Resource: - !Sub "arn:aws:iam::${AWS::AccountId}:role/*" Condition: StringEquals: "iam:PermissionsBoundary": !Sub "arn:aws:iam::${AWS::AccountId}:policy/seahaven-lambda-execution-boundary" # Boundary management — SET the boundary only. DELETE is NOT # granted: for a delete, the iam:PermissionsBoundary condition key # reflects the boundary CURRENTLY attached to the target role, so # a StringEquals condition on the boundary ARN MATCHES exactly the # roles the gate protects. Granting delete under that condition # lets this role create a boundary-gated role with an inline *:* # policy, strip the boundary, and pass the now-unbounded role to # Lambda — defeating the primary escalation control. Verified live # against the mgmt copy 2026-07-27 (simulate-principal-policy: # iam:DeleteRolePermissionsBoundary = allowed). # # OPERATIONAL CONSEQUENCE — read before debugging a stuck stack. # SAM does not need the delete for the common paths: it SETS the # boundary on roles it creates, and stack teardown calls DeleteRole. # But there IS one path that now fails by design: updating an # existing AWS::IAM::Role to REMOVE its PermissionsBoundary property # makes CloudFormation call DeleteRolePermissionsBoundary, which is # denied. The stack update fails and rolls back, and because cd-sam's # pre-flight hard-fails on *ROLLBACK_COMPLETE, that repo's deploys # stay blocked until it is cleared. Recovery is an out-of-band admin # action (remove the boundary directly, or replace the role by # renaming its logical id) — not a pipeline retry. Removing the # boundary from a SAM function is a security regression anyway, so # failing loudly here is the intent. - Sid: IAMPutPermissionsBoundary Effect: Allow Action: - iam:PutRolePermissionsBoundary Resource: - !Sub "arn:aws:iam::${AWS::AccountId}:role/*" Condition: StringEquals: "iam:PermissionsBoundary": !Sub "arn:aws:iam::${AWS::AccountId}:policy/seahaven-lambda-execution-boundary" # Explicit Deny backstop (AWS's documented NoBoundaryPolicyEdit / # NoBoundaryDelete delegation pattern). A Deny is required, not # merely omitting the Allow: without it, any future Allow added to # this role — or a broader managed policy attached to it — silently # reopens the escalation. Covers both removing a boundary from a # role and rewriting the boundary POLICY DOCUMENT itself (the # latter is only implicitly denied today). - Sid: DenyBoundaryTampering Effect: Deny Action: - iam:DeleteRolePermissionsBoundary - iam:DeleteUserPermissionsBoundary Resource: - !Sub "arn:aws:iam::${AWS::AccountId}:role/*" - !Sub "arn:aws:iam::${AWS::AccountId}:user/*" # Scoped to the whole seahaven-* policy family, not just the boundary: # this policy carries the Deny statements, so it is now a # higher-value target than the boundary it protects. Safe to scope # broadly — the role holds no iam:CreatePolicy anywhere and no SAM # stack manages a managed policy through it (both verified # 2026-07-27), so nothing legitimate writes policy versions here. - Sid: DenyBoundaryPolicyEdit Effect: Deny Action: - iam:CreatePolicyVersion - iam:SetDefaultPolicyVersion - iam:DeletePolicyVersion - iam:DeletePolicy Resource: - !Sub "arn:aws:iam::${AWS::AccountId}:policy/seahaven-*" # Self-protection. Without this the whole control is one API call # from being undone: IAMRoleReadAndDelete below grants # iam:DetachRolePolicy on Resource "*" with no condition, so this # role could detach the very policy carrying these Denies from # itself and reinstate the escalation. Verified live 2026-07-27: # simulate-principal-policy returned "allowed" for DetachRolePolicy, # DeleteRolePolicy and DeleteRole against this role's own ARN and # against githubdeploy-* roles. # # Also closes a denial-of-service and a self-elevation precondition: # iam:PutRolePermissionsBoundary is condition-pinned to the Lambda # boundary ARN but NOT scoped by target, so this role could apply # that runtime boundary to itself or to a githubdeploy-* role — # bricking the pipelines, unrecoverable without an admin because # removing a boundary is denied above, and making the otherwise-inert # AttachRolePolicy/PutRolePolicy self-elevation conditions start # matching. # # Costs nothing operationally: the deploy substrate's own roles are # managed by THIS stack, which is deployed manually with # administrator credentials (no --role-arn), so CloudFormation never # exercises these actions against them as this role. SAM-generated # roles are named -Role- and are unaffected. - Sid: DenySelfMutation Effect: Deny Action: - iam:AttachRolePolicy - iam:DeleteRole - iam:DeleteRolePolicy - iam:DeleteRolePermissionsBoundary - iam:DetachRolePolicy - iam:PutRolePolicy - iam:PutRolePermissionsBoundary - iam:UpdateAssumeRolePolicy - iam:UpdateRole - iam:UpdateRoleDescription Resource: - !Sub "arn:aws:iam::${AWS::AccountId}:role/github-cfn-execution-role" - !Sub "arn:aws:iam::${AWS::AccountId}:role/githubdeploy-*" # Read / tag / delete role and policy — no boundary condition needed - Sid: IAMRoleReadAndDelete Effect: Allow Action: - iam:DeleteRole - iam:DeleteRolePolicy - iam:DetachRolePolicy - iam:GetRole - iam:GetRolePolicy - iam:ListAttachedRolePolicies - iam:ListRolePolicies - iam:ListRoles - iam:TagRole - iam:UntagRole - iam:UpdateRole - iam:UpdateRoleDescription - iam:UpdateAssumeRolePolicy - iam:GetPolicy - iam:GetPolicyVersion - iam:ListPolicies - iam:ListPolicyVersions Resource: "*" # PassRole — CloudFormation passes the Lambda execution role # to the Lambda service. Scoped to SAM-generated role pattern. - Sid: IAMPassRole Effect: Allow Action: - iam:PassRole Resource: - !Sub "arn:aws:iam::${AWS::AccountId}:role/*" Condition: StringEquals: "iam:PassedToService": "lambda.amazonaws.com" # --------------------------------------------------------------------------- # Shared CloudFormation execution role (SAM stacks) — INFRA-97 scoped # # Replaces the previous blanket managed-policy set (IAMFullAccess + # *FullAccess) with per-service inline statements that cover exactly # what the five SAM stacks need during a CloudFormation deploy/update. # # PRIMARY ESCALATION CONTROL # iam:CreateRole and iam:AttachRolePolicy / iam:PutRolePolicy are # conditioned on iam:PermissionsBoundary StringEquals the boundary ARN # (seahaven-lambda-execution-boundary, created in INFRA-103). That # condition is what prevents the CFN execution role from minting an # unconstrained admin role. # # SAM RolePath deviation note # The original cross-review suggestion mentioned scoping IAM role # creation to a specific path (/cfn-managed/). AWS::Serverless::Function # does NOT support a custom RolePath on auto-generated execution roles — # the PermissionsBoundary property is supported, but the role always lands # at path /. Relying on a path condition (iam:ResourceTag or path-prefix) # would therefore exclude the SAM auto-roles and break every deploy. # The iam:PermissionsBoundary condition achieves the same security goal # without requiring a path. For any explicit AWS::IAM::Role resources # in SAM templates (e.g. AdminAuthorizerInvokeRole in meal-order-manager) # where we can control the path, path scoping can be added in a follow-up. # # DEPLOY ORDER DEPENDENCY # This role references the boundary ARN by literal value, so the # seahaven-lambda-execution-boundary managed policy must exist before this # stack is deployed. It is created by this same stack above, and is not # modified by the Phase A change. # --------------------------------------------------------------------------- SamCfnExecutionRole: Type: AWS::IAM::Role Properties: RoleName: github-cfn-execution-role ManagedPolicyArns: - !Ref SamCfnIamManagementPolicy AssumeRolePolicyDocument: Version: "2012-10-17" Statement: - Effect: Allow Principal: Service: cloudformation.amazonaws.com Action: sts:AssumeRole Policies: # ── CloudFormation transforms (SAM macro) ───────────────────────── - PolicyName: cloudformation-transforms PolicyDocument: Version: "2012-10-17" Statement: - Sid: AllowSAMTransform Effect: Allow Action: - cloudformation:CreateChangeSet Resource: - arn:aws:cloudformation:us-east-1:aws:transform/* # ── Lambda management ───────────────────────────────────────────── # Covers function create/update/delete, aliases, event source # mappings, and Lambda layers — all needed for SAM deploys. - PolicyName: lambda-management PolicyDocument: Version: "2012-10-17" Statement: - Sid: LambdaFunctions Effect: Allow Action: - lambda:AddPermission - lambda:CreateFunction - lambda:DeleteFunction - lambda:GetFunction - lambda:GetFunctionConfiguration - lambda:ListFunctions - lambda:RemovePermission - lambda:UpdateFunctionCode - lambda:UpdateFunctionConfiguration - lambda:UpdateFunctionEventInvokeConfig - lambda:PutFunctionEventInvokeConfig - lambda:DeleteFunctionEventInvokeConfig - lambda:GetFunctionEventInvokeConfig - lambda:ListTags - lambda:TagResource - lambda:UntagResource - lambda:GetPolicy - lambda:ListVersionsByFunction - lambda:PublishVersion - lambda:CreateAlias - lambda:DeleteAlias - lambda:UpdateAlias - lambda:GetAlias Resource: - !Sub "arn:aws:lambda:us-east-1:${AWS::AccountId}:function:*" - Sid: LambdaLayers Effect: Allow Action: - lambda:PublishLayerVersion - lambda:DeleteLayerVersion - lambda:GetLayerVersion - lambda:ListLayerVersions - lambda:ListLayers - lambda:AddLayerVersionPermission - lambda:RemoveLayerVersionPermission Resource: - !Sub "arn:aws:lambda:us-east-1:${AWS::AccountId}:layer:*" - Sid: LambdaEventSourceMappings Effect: Allow Action: - lambda:CreateEventSourceMapping - lambda:DeleteEventSourceMapping - lambda:GetEventSourceMapping - lambda:ListEventSourceMappings - lambda:UpdateEventSourceMapping Resource: "*" # ── API Gateway (HTTP APIs + REST APIs) ─────────────────────────── - PolicyName: apigateway-management PolicyDocument: Version: "2012-10-17" Statement: - Sid: ApiGateway Effect: Allow Action: - apigateway:GET - apigateway:POST - apigateway:PUT - apigateway:PATCH - apigateway:DELETE Resource: - "arn:aws:apigateway:us-east-1::*" # ── DynamoDB ────────────────────────────────────────────────────── - PolicyName: dynamodb-management PolicyDocument: Version: "2012-10-17" Statement: - Sid: DynamoDBTables Effect: Allow Action: - dynamodb:CreateTable - dynamodb:DeleteTable - dynamodb:DescribeTable - dynamodb:UpdateTable - dynamodb:ListTables - dynamodb:TagResource - dynamodb:UntagResource - dynamodb:DescribeTimeToLive - dynamodb:UpdateTimeToLive - dynamodb:DescribeContinuousBackups - dynamodb:UpdateContinuousBackups Resource: - !Sub "arn:aws:dynamodb:us-east-1:${AWS::AccountId}:table/*" # ── S3 ──────────────────────────────────────────────────────────── # Covers bucket create/configure + object operations for SAM # artifact buckets and application buckets. - PolicyName: s3-management PolicyDocument: Version: "2012-10-17" Statement: - Sid: S3BucketOps Effect: Allow Action: - s3:CreateBucket - s3:DeleteBucket - s3:GetBucketLocation - s3:GetBucketPolicy - s3:PutBucketPolicy - s3:DeleteBucketPolicy - s3:GetBucketTagging - s3:PutBucketTagging - s3:GetBucketVersioning - s3:PutBucketVersioning - s3:GetLifecycleConfiguration - s3:PutLifecycleConfiguration - s3:GetBucketPublicAccessBlock - s3:PutBucketPublicAccessBlock # Explicit BucketEncryption blocks (first: payments-dashboard # BoaRawBucket, 2026-07-22) need the encryption config pair. - s3:GetEncryptionConfiguration - s3:PutEncryptionConfiguration - s3:GetBucketNotification - s3:PutBucketNotification - s3:GetBucketWebsite - s3:PutBucketWebsite - s3:DeleteBucketWebsite - s3:GetBucketAcl - s3:PutBucketAcl Resource: - "arn:aws:s3:::*" - Sid: S3ObjectOps Effect: Allow Action: - s3:GetObject - s3:PutObject - s3:DeleteObject - s3:ListBucket - s3:ListBucketVersions - s3:GetObjectVersion Resource: - "arn:aws:s3:::*" - "arn:aws:s3:::*/*" # ── CloudWatch Logs ─────────────────────────────────────────────── - PolicyName: cloudwatch-logs-management PolicyDocument: Version: "2012-10-17" Statement: - Sid: CWLogs Effect: Allow Action: - logs:CreateLogGroup - logs:DeleteLogGroup - logs:DescribeLogGroups - logs:PutRetentionPolicy - logs:DeleteRetentionPolicy - logs:ListTagsLogGroup - logs:TagLogGroup - logs:UntagLogGroup - logs:ListTagsForResource - logs:TagResource - logs:UntagResource - logs:CreateLogDelivery - logs:GetLogDelivery - logs:UpdateLogDelivery - logs:DeleteLogDelivery - logs:ListLogDeliveries - logs:PutResourcePolicy - logs:DescribeResourcePolicies - logs:PutDestination - logs:DeleteDestination - logs:DescribeDestinations - logs:AssociateKmsKey - logs:DisassociateKmsKey # Reconciles drift: these three exist on the DEPLOYED role # (added out-of-band 2026-06-29) but were never back-ported # here. afterhours-shift-manager creates an # AWS::Logs::MetricFilter through this role, so omitting them # risks a future write-back silently stripping them. - logs:PutMetricFilter - logs:DeleteMetricFilter - logs:DescribeMetricFilters Resource: "*" # ── EventBridge / CloudWatch Events (scheduled Lambdas) ─────────── - PolicyName: eventbridge-management PolicyDocument: Version: "2012-10-17" Statement: - Sid: EventBridge Effect: Allow Action: - events:DeleteRule - events:DescribeRule - events:EnableRule - events:DisableRule - events:ListRules - events:ListTargetsByRule - events:PutRule - events:PutTargets - events:RemoveTargets - events:TagResource - events:UntagResource - events:ListTagsForResource - events:PutPermission - events:RemovePermission Resource: "*" # ── SES (afterhours weekly-post, meal-order email-report) ───────── - PolicyName: ses-management PolicyDocument: Version: "2012-10-17" Statement: - Sid: SESRules Effect: Allow Action: - ses:CreateReceiptRule - ses:DeleteReceiptRule - ses:DescribeReceiptRule - ses:UpdateReceiptRule - ses:CreateReceiptRuleSet - ses:DescribeActiveReceiptRuleSet - ses:DescribeReceiptRuleSet - ses:SetActiveReceiptRuleSet - ses:ReorderReceiptRuleSet - ses:GetIdentityVerificationAttributes - ses:ListIdentities Resource: "*" # ── SQS (payments-dashboard queues + DLQs) ──────────────────────── - PolicyName: sqs-management PolicyDocument: Version: "2012-10-17" Statement: - Sid: SQSQueues Effect: Allow Action: - sqs:CreateQueue - sqs:DeleteQueue - sqs:GetQueueAttributes - sqs:SetQueueAttributes - sqs:GetQueueUrl - sqs:ListQueues - sqs:TagQueue - sqs:UntagQueue - sqs:ListQueueTags - sqs:AddPermission - sqs:RemovePermission Resource: - !Sub "arn:aws:sqs:us-east-1:${AWS::AccountId}:*" # ── SNS (validation / alarm notifications) ──────────────────────── - PolicyName: sns-management PolicyDocument: Version: "2012-10-17" Statement: - Sid: SNS Effect: Allow Action: - sns:CreateTopic - sns:DeleteTopic - sns:GetTopicAttributes - sns:SetTopicAttributes - sns:Subscribe - sns:Unsubscribe - sns:ListSubscriptionsByTopic - sns:ListTopics - sns:TagResource - sns:UntagResource Resource: - !Sub "arn:aws:sns:us-east-1:${AWS::AccountId}:*" # ── CloudWatch Alarms ───────────────────────────────────────────── - PolicyName: cloudwatch-alarms-management PolicyDocument: Version: "2012-10-17" Statement: - Sid: CWAlarms Effect: Allow Action: - cloudwatch:PutMetricAlarm - cloudwatch:DeleteAlarms - cloudwatch:DescribeAlarms - cloudwatch:EnableAlarmActions - cloudwatch:DisableAlarmActions - cloudwatch:ListTagsForResource - cloudwatch:TagResource - cloudwatch:UntagResource Resource: "*" # ── EC2 / VPC / NAT / EIP / Security Groups ─────────────────────── # payments-dashboard deploys a VPC, NAT gateway, EIP, route tables, # subnets, security groups, and gateway VPC endpoints. - PolicyName: ec2-vpc-management PolicyDocument: Version: "2012-10-17" Statement: - Sid: EC2VPC Effect: Allow Action: - ec2:AllocateAddress - ec2:AssociateRouteTable - ec2:AttachInternetGateway - ec2:AuthorizeSecurityGroupEgress - ec2:AuthorizeSecurityGroupIngress - ec2:CreateInternetGateway - ec2:CreateNatGateway - ec2:CreateRoute - ec2:CreateRouteTable - ec2:CreateSecurityGroup - ec2:CreateSubnet - ec2:CreateVpc - ec2:CreateVpcEndpoint - ec2:CreateTags - ec2:DeleteInternetGateway - ec2:DeleteNatGateway - ec2:DeleteRoute - ec2:DeleteRouteTable - ec2:DeleteSecurityGroup - ec2:DeleteSubnet - ec2:DeleteVpc - ec2:DeleteVpcEndpoints - ec2:DescribeAddresses - ec2:DescribeAvailabilityZones - ec2:DescribeInternetGateways - ec2:DescribeNatGateways - ec2:DescribeRouteTables - ec2:DescribeSecurityGroups - ec2:DescribeSubnets - ec2:DescribeVpcEndpoints - ec2:DescribeVpcs - ec2:DescribePrefixLists - ec2:DetachInternetGateway - ec2:DisassociateAddress - ec2:DisassociateRouteTable - ec2:ModifySubnetAttribute - ec2:ModifyVpcAttribute - ec2:ModifyVpcEndpoint - ec2:ReleaseAddress - ec2:RevokeSecurityGroupEgress - ec2:RevokeSecurityGroupIngress - ec2:UpdateSecurityGroupRuleDescriptionsEgress - ec2:UpdateSecurityGroupRuleDescriptionsIngress Resource: "*" # ── CloudFront + OAC (meal-order-manager form distribution) ─────── - PolicyName: cloudfront-management PolicyDocument: Version: "2012-10-17" Statement: - Sid: CloudFront Effect: Allow Action: - cloudfront:CreateDistribution - cloudfront:DeleteDistribution - cloudfront:GetDistribution - cloudfront:GetDistributionConfig - cloudfront:UpdateDistribution - cloudfront:TagResource - cloudfront:UntagResource - cloudfront:ListTagsForResource - cloudfront:CreateOriginAccessControl - cloudfront:DeleteOriginAccessControl - cloudfront:GetOriginAccessControl - cloudfront:GetOriginAccessControlConfig - cloudfront:UpdateOriginAccessControl - cloudfront:ListOriginAccessControls - cloudfront:CreateInvalidation - cloudfront:GetInvalidation Resource: "*" # ── SSM Parameter Store (meal-order-manager, afterhours) ────────── # Write is needed because meal-order-manager creates # /meal-order-manager/slack-channel-id via AWS::SSM::Parameter. - PolicyName: ssm-management PolicyDocument: Version: "2012-10-17" Statement: - Sid: SSMParameters Effect: Allow Action: - ssm:GetParameter - ssm:GetParameters - ssm:GetParametersByPath - ssm:PutParameter - ssm:DeleteParameter - ssm:DeleteParameters - ssm:DescribeParameters - ssm:AddTagsToResource - ssm:RemoveTagsFromResource - ssm:ListTagsForResource Resource: - !Sub "arn:aws:ssm:us-east-1:${AWS::AccountId}:parameter/*" # WAF association needs SSM parameter read at deploy time # (/seahaven/waf/app-web-acl-arn value lookup) - Sid: SSMParameterDescribe Effect: Allow Action: - ssm:DescribeParameters Resource: "*" # ── WAF (meal-order-manager CloudFront WebACL association) ──────── - PolicyName: waf-management PolicyDocument: Version: "2012-10-17" Statement: - Sid: WAF Effect: Allow Action: - wafv2:GetWebACL - wafv2:GetWebACLForResource - wafv2:ListWebACLs - wafv2:AssociateWebACL - wafv2:DisassociateWebACL - wafv2:ListResourcesForWebACL Resource: "*" # --------------------------------------------------------------------------- # SAM deploy roles (4 repos) # --------------------------------------------------------------------------- AfterhoursShiftManagerDeployRole: Type: AWS::IAM::Role Properties: RoleName: githubdeploy-afterhours-shift-manager AssumeRolePolicyDocument: Version: "2012-10-17" Statement: - Effect: Allow Principal: Federated: !Sub arn:aws:iam::${AWS::AccountId}:oidc-provider/token.actions.githubusercontent.com Action: sts:AssumeRoleWithWebIdentity Condition: StringEquals: token.actions.githubusercontent.com:aud: sts.amazonaws.com StringLike: token.actions.githubusercontent.com:sub: !Sub repo:${GitHubOrg}/afterhours-shift-manager:ref:refs/heads/main Policies: - PolicyName: sam-deploy PolicyDocument: Version: "2012-10-17" Statement: - Effect: Allow Action: - cloudformation:CreateChangeSet - cloudformation:DeleteChangeSet - cloudformation:DescribeChangeSet - cloudformation:DescribeStackEvents - cloudformation:DescribeStacks - cloudformation:ExecuteChangeSet - cloudformation:GetTemplate - cloudformation:ListStackResources - cloudformation:UpdateStack - cloudformation:CreateStack - cloudformation:TagResource Resource: - !Sub arn:aws:cloudformation:us-east-1:${AWS::AccountId}:stack/afterhours-shift-manager/* - Effect: Allow Action: - cloudformation:GetTemplateSummary Resource: "*" - Effect: Allow Action: - cloudformation:DescribeStacks - cloudformation:CreateChangeSet - cloudformation:DescribeChangeSet - cloudformation:ExecuteChangeSet - cloudformation:CreateStack Resource: - !Sub arn:aws:cloudformation:us-east-1:${AWS::AccountId}:stack/aws-sam-cli-managed-default/* - Effect: Allow Action: - s3:PutObject - s3:GetObject - s3:ListBucket - s3:GetBucketLocation - s3:CreateBucket - s3:PutBucketPolicy - s3:GetBucketPolicy - s3:PutLifecycleConfiguration - s3:PutBucketVersioning - s3:DeleteObject Resource: - arn:aws:s3:::aws-sam-cli-managed-default-samclisourcebucket-* - arn:aws:s3:::aws-sam-cli-managed-default-samclisourcebucket-*/* - Effect: Allow Action: - iam:PassRole Resource: - !GetAtt SamCfnExecutionRole.Arn FrontIntegrationsDeployRole: Type: AWS::IAM::Role Properties: RoleName: githubdeploy-front-integrations AssumeRolePolicyDocument: Version: "2012-10-17" Statement: - Effect: Allow Principal: Federated: !Sub arn:aws:iam::${AWS::AccountId}:oidc-provider/token.actions.githubusercontent.com Action: sts:AssumeRoleWithWebIdentity Condition: StringEquals: token.actions.githubusercontent.com:aud: sts.amazonaws.com StringLike: token.actions.githubusercontent.com:sub: !Sub repo:${GitHubOrg}/front-integrations:ref:refs/heads/main Policies: - PolicyName: sam-deploy PolicyDocument: Version: "2012-10-17" Statement: - Effect: Allow Action: - cloudformation:CreateChangeSet - cloudformation:DeleteChangeSet - cloudformation:DescribeChangeSet - cloudformation:DescribeStackEvents - cloudformation:DescribeStacks - cloudformation:ExecuteChangeSet - cloudformation:GetTemplate - cloudformation:ListStackResources - cloudformation:UpdateStack - cloudformation:CreateStack - cloudformation:TagResource Resource: - !Sub arn:aws:cloudformation:us-east-1:${AWS::AccountId}:stack/front-integrations/* - Effect: Allow Action: - cloudformation:GetTemplateSummary Resource: "*" - Effect: Allow Action: - cloudformation:DescribeStacks - cloudformation:CreateChangeSet - cloudformation:DescribeChangeSet - cloudformation:ExecuteChangeSet - cloudformation:CreateStack Resource: - !Sub arn:aws:cloudformation:us-east-1:${AWS::AccountId}:stack/aws-sam-cli-managed-default/* - Effect: Allow Action: - s3:PutObject - s3:GetObject - s3:ListBucket - s3:GetBucketLocation - s3:CreateBucket - s3:PutBucketPolicy - s3:GetBucketPolicy - s3:PutLifecycleConfiguration - s3:PutBucketVersioning - s3:DeleteObject Resource: - arn:aws:s3:::aws-sam-cli-managed-default-samclisourcebucket-* - arn:aws:s3:::aws-sam-cli-managed-default-samclisourcebucket-*/* - Effect: Allow Action: - iam:PassRole Resource: - !GetAtt SamCfnExecutionRole.Arn AfiBackupMonitorDeployRole: Type: AWS::IAM::Role Properties: RoleName: githubdeploy-afi-backup-monitor AssumeRolePolicyDocument: Version: "2012-10-17" Statement: - Effect: Allow Principal: Federated: !Sub arn:aws:iam::${AWS::AccountId}:oidc-provider/token.actions.githubusercontent.com Action: sts:AssumeRoleWithWebIdentity Condition: StringEquals: token.actions.githubusercontent.com:aud: sts.amazonaws.com StringLike: token.actions.githubusercontent.com:sub: !Sub repo:${GitHubOrg}/afi-backup-monitor:ref:refs/heads/main Policies: - PolicyName: sam-deploy PolicyDocument: Version: "2012-10-17" Statement: - Effect: Allow Action: - cloudformation:CreateChangeSet - cloudformation:DeleteChangeSet - cloudformation:DescribeChangeSet - cloudformation:DescribeStackEvents - cloudformation:DescribeStacks - cloudformation:ExecuteChangeSet - cloudformation:GetTemplate - cloudformation:ListStackResources - cloudformation:UpdateStack - cloudformation:CreateStack - cloudformation:TagResource Resource: - !Sub arn:aws:cloudformation:us-east-1:${AWS::AccountId}:stack/afi-backup-monitor/* - Effect: Allow Action: - cloudformation:GetTemplateSummary Resource: "*" - Effect: Allow Action: - cloudformation:DescribeStacks - cloudformation:CreateChangeSet - cloudformation:DescribeChangeSet - cloudformation:ExecuteChangeSet - cloudformation:CreateStack Resource: - !Sub arn:aws:cloudformation:us-east-1:${AWS::AccountId}:stack/aws-sam-cli-managed-default/* - Effect: Allow Action: - s3:PutObject - s3:GetObject - s3:ListBucket - s3:GetBucketLocation - s3:CreateBucket - s3:PutBucketPolicy - s3:GetBucketPolicy - s3:PutLifecycleConfiguration - s3:PutBucketVersioning - s3:DeleteObject Resource: - arn:aws:s3:::aws-sam-cli-managed-default-samclisourcebucket-* - arn:aws:s3:::aws-sam-cli-managed-default-samclisourcebucket-*/* - Effect: Allow Action: - iam:PassRole Resource: - !GetAtt SamCfnExecutionRole.Arn PaymentsDashboardDeployRole: Type: AWS::IAM::Role Properties: RoleName: githubdeploy-payments-dashboard AssumeRolePolicyDocument: Version: "2012-10-17" Statement: - Effect: Allow Principal: Federated: !Sub arn:aws:iam::${AWS::AccountId}:oidc-provider/token.actions.githubusercontent.com Action: sts:AssumeRoleWithWebIdentity Condition: StringEquals: token.actions.githubusercontent.com:aud: sts.amazonaws.com StringLike: token.actions.githubusercontent.com:sub: !Sub repo:${GitHubOrg}/payments-dashboard:ref:refs/heads/main Policies: - PolicyName: sam-deploy PolicyDocument: Version: "2012-10-17" Statement: - Effect: Allow Action: - cloudformation:CreateChangeSet - cloudformation:DeleteChangeSet - cloudformation:DescribeChangeSet - cloudformation:DescribeStackEvents - cloudformation:DescribeStacks - cloudformation:ExecuteChangeSet - cloudformation:GetTemplate - cloudformation:ListStackResources - cloudformation:UpdateStack - cloudformation:CreateStack - cloudformation:TagResource Resource: - !Sub arn:aws:cloudformation:us-east-1:${AWS::AccountId}:stack/payments-dashboard/* - Effect: Allow Action: - cloudformation:GetTemplateSummary Resource: "*" - Effect: Allow Action: - cloudformation:DescribeStacks - cloudformation:CreateChangeSet - cloudformation:DescribeChangeSet - cloudformation:ExecuteChangeSet - cloudformation:CreateStack Resource: - !Sub arn:aws:cloudformation:us-east-1:${AWS::AccountId}:stack/aws-sam-cli-managed-default/* - Effect: Allow Action: - s3:PutObject - s3:GetObject - s3:ListBucket - s3:GetBucketLocation - s3:CreateBucket - s3:PutBucketPolicy - s3:GetBucketPolicy - s3:PutLifecycleConfiguration - s3:PutBucketVersioning - s3:DeleteObject Resource: - arn:aws:s3:::aws-sam-cli-managed-default-samclisourcebucket-* - arn:aws:s3:::aws-sam-cli-managed-default-samclisourcebucket-*/* - Effect: Allow Action: - iam:PassRole Resource: - !GetAtt SamCfnExecutionRole.Arn # --------------------------------------------------------------------------- # CDK deploy roles (4 repos) # --------------------------------------------------------------------------- ExecAideDeployRole: Type: AWS::IAM::Role Properties: RoleName: githubdeploy-exec-aide AssumeRolePolicyDocument: Version: "2012-10-17" Statement: - Effect: Allow Principal: Federated: !Sub arn:aws:iam::${AWS::AccountId}:oidc-provider/token.actions.githubusercontent.com Action: sts:AssumeRoleWithWebIdentity Condition: StringEquals: token.actions.githubusercontent.com:aud: sts.amazonaws.com StringLike: token.actions.githubusercontent.com:sub: !Sub repo:${GitHubOrg}/exec-aide:ref:refs/heads/main Policies: - PolicyName: cdk-deploy PolicyDocument: Version: "2012-10-17" Statement: - Effect: Allow Action: - sts:AssumeRole Resource: - !Sub arn:aws:iam::${AWS::AccountId}:role/cdk-hnb659fds-* SeahavenDoorUnlockApiDeployRole: Type: AWS::IAM::Role Properties: RoleName: githubdeploy-seahaven-door-unlock-api AssumeRolePolicyDocument: Version: "2012-10-17" Statement: - Effect: Allow Principal: Federated: !Sub arn:aws:iam::${AWS::AccountId}:oidc-provider/token.actions.githubusercontent.com Action: sts:AssumeRoleWithWebIdentity Condition: StringEquals: token.actions.githubusercontent.com:aud: sts.amazonaws.com StringLike: token.actions.githubusercontent.com:sub: !Sub repo:${GitHubOrg}/seahaven-door-unlock-api:ref:refs/heads/main Policies: - PolicyName: cdk-deploy PolicyDocument: Version: "2012-10-17" Statement: - Effect: Allow Action: - sts:AssumeRole Resource: - !Sub arn:aws:iam::${AWS::AccountId}:role/cdk-hnb659fds-* ProcurementIngestDeployRole: Type: AWS::IAM::Role Properties: RoleName: githubdeploy-procurement-ingest AssumeRolePolicyDocument: Version: "2012-10-17" Statement: - Effect: Allow Principal: Federated: !Sub arn:aws:iam::${AWS::AccountId}:oidc-provider/token.actions.githubusercontent.com Action: sts:AssumeRoleWithWebIdentity Condition: StringEquals: token.actions.githubusercontent.com:aud: sts.amazonaws.com StringLike: token.actions.githubusercontent.com:sub: !Sub repo:${GitHubOrg}/procurement-ingest:ref:refs/heads/main Policies: - PolicyName: cdk-deploy PolicyDocument: Version: "2012-10-17" Statement: - Effect: Allow Action: - sts:AssumeRole Resource: - !Sub arn:aws:iam::${AWS::AccountId}:role/cdk-hnb659fds-* ApmWoAnalysisDeployRole: Type: AWS::IAM::Role Properties: RoleName: githubdeploy-apm-wo-analysis AssumeRolePolicyDocument: Version: "2012-10-17" Statement: - Effect: Allow Principal: Federated: !Sub arn:aws:iam::${AWS::AccountId}:oidc-provider/token.actions.githubusercontent.com Action: sts:AssumeRoleWithWebIdentity Condition: StringEquals: token.actions.githubusercontent.com:aud: sts.amazonaws.com StringLike: token.actions.githubusercontent.com:sub: !Sub repo:${GitHubOrg}/apm-wo-analysis:ref:refs/heads/main Policies: - PolicyName: cdk-deploy PolicyDocument: Version: "2012-10-17" Statement: - Effect: Allow Action: - sts:AssumeRole Resource: - !Sub arn:aws:iam::${AWS::AccountId}:role/cdk-hnb659fds-* SeahavenAccountBaselineDeployRole: Type: AWS::IAM::Role Properties: RoleName: githubdeploy-seahaven-account-baseline AssumeRolePolicyDocument: Version: "2012-10-17" Statement: - Effect: Allow Principal: Federated: !Sub arn:aws:iam::${AWS::AccountId}:oidc-provider/token.actions.githubusercontent.com Action: sts:AssumeRoleWithWebIdentity Condition: StringEquals: token.actions.githubusercontent.com:aud: sts.amazonaws.com StringLike: token.actions.githubusercontent.com:sub: !Sub repo:${GitHubOrg}/seahaven-org-baseline:ref:refs/heads/main Policies: - PolicyName: cdk-deploy PolicyDocument: Version: "2012-10-17" Statement: - Effect: Allow Action: - sts:AssumeRole Resource: - !Sub arn:aws:iam::${AWS::AccountId}:role/cdk-hnb659fds-* # Scoped runtime role for the meal-order-manager weekly-menu workflow # (Monday scrape + order-form publish). Deliberately narrower than the # repo's deploy role: the scheduled job reads stack outputs and app config, # writes menu items and the published form, and invalidates the form's # CloudFront path. It deploys nothing, so it gets no CloudFormation write # actions, no PassRole, and no access outside the form bucket. MealOrderManagerWeeklyMenuRole: Type: AWS::IAM::Role Properties: RoleName: github-meal-order-manager-weekly-menu AssumeRolePolicyDocument: Version: "2012-10-17" Statement: - Effect: Allow Principal: Federated: !Sub arn:aws:iam::${AWS::AccountId}:oidc-provider/token.actions.githubusercontent.com Action: sts:AssumeRoleWithWebIdentity Condition: # StringEquals (not the sibling roles' StringLike): no wildcard is # intended, and job_workflow_ref pins this runtime role to the ONE # workflow it serves — unlike the deploy roles, any main-branch # workflow must NOT be able to mint these credentials. StringEquals: token.actions.githubusercontent.com:aud: sts.amazonaws.com token.actions.githubusercontent.com:sub: !Sub repo:${GitHubOrg}/meal-order-manager:ref:refs/heads/main token.actions.githubusercontent.com:job_workflow_ref: !Sub ${GitHubOrg}/meal-order-manager/.github/workflows/weekly-menu.yml@refs/heads/main Policies: - PolicyName: weekly-menu-publish PolicyDocument: Version: "2012-10-17" Statement: - Effect: Allow Action: - cloudformation:DescribeStacks Resource: - !Sub arn:aws:cloudformation:us-east-1:${AWS::AccountId}:stack/meal-order-manager/* - Effect: Allow Action: - secretsmanager:GetSecretValue # Secrets Manager appends a random 6-char suffix to every secret # ARN, so a name-based match needs a glob — but exactly six '?' # (one char each), NOT '-*', which would also match any future # secret extending the name (e.g. form-api-key-backup). Resource: - !Sub arn:aws:secretsmanager:us-east-1:${AWS::AccountId}:secret:meal-order-manager/form-api-key-?????? - !Sub arn:aws:secretsmanager:us-east-1:${AWS::AccountId}:secret:meal-order-manager/slack-bot-token-?????? - Effect: Allow Action: - ssm:GetParameter Resource: - !Sub arn:aws:ssm:us-east-1:${AWS::AccountId}:parameter/meal-order-manager/google-client-id - !Sub arn:aws:ssm:us-east-1:${AWS::AccountId}:parameter/meal-order-manager/slack-channel-id - Effect: Allow Action: - dynamodb:GetItem - dynamodb:PutItem Resource: - !Sub arn:aws:dynamodb:us-east-1:${AWS::AccountId}:table/meal-order-manager-orders - Effect: Allow Action: - s3:PutObject Resource: - !Sub arn:aws:s3:::meal-order-manager-form-${AWS::AccountId}/index.html - !Sub arn:aws:s3:::meal-order-manager-form-${AWS::AccountId}/archive/*.html - Effect: Allow Action: - cloudfront:CreateInvalidation # Distribution ID = the meal-order-manager stack's DistributionId # output (stable for the life of the distribution). Resource: - !Sub arn:aws:cloudfront::${AWS::AccountId}:distribution/E314J1CJJ9ZTRA 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: Name: github-cfn-execution-role-arn AfterhoursShiftManagerDeployRoleArn: Value: !GetAtt AfterhoursShiftManagerDeployRole.Arn FrontIntegrationsDeployRoleArn: Value: !GetAtt FrontIntegrationsDeployRole.Arn AfiBackupMonitorDeployRoleArn: Value: !GetAtt AfiBackupMonitorDeployRole.Arn PaymentsDashboardDeployRoleArn: Value: !GetAtt PaymentsDashboardDeployRole.Arn # SeahavenSlackBotDeployRoleArn removed 2026-07-27: the role was deleted # out-of-band on 2026-07-23, so this !GetAtt failed as a live IAM read and # broke every stack update. Nothing imported it (the Output had no # ExportName, and no stack imports any export from this stack). ExecAideDeployRoleArn: Value: !GetAtt ExecAideDeployRole.Arn SeahavenDoorUnlockApiDeployRoleArn: Value: !GetAtt SeahavenDoorUnlockApiDeployRole.Arn ProcurementIngestDeployRoleArn: Value: !GetAtt ProcurementIngestDeployRole.Arn ApmWoAnalysisDeployRoleArn: Value: !GetAtt ApmWoAnalysisDeployRole.Arn SeahavenAccountBaselineDeployRoleArn: Value: !GetAtt SeahavenAccountBaselineDeployRole.Arn MealOrderManagerWeeklyMenuRoleArn: Value: !GetAtt MealOrderManagerWeeklyMenuRole.Arn