From 689ec147a3557a70abb492517f8c62a2f28dee8e Mon Sep 17 00:00:00 2001 From: Adam Moussa <166072409+amoussa1229@users.noreply.github.com> Date: Thu, 27 Aug 2026 21:26:27 +0000 Subject: [PATCH] feat(iam): add door-unlock-api hcptf roles and boundary (PLAT-76) (#123) * feat(iam): add door-unlock-api hcptf roles and boundary Give HCP Terraform a prod plan/apply pair, a per-workload Lambda boundary with exact SSM and 3CX ARNs, and API access-log delivery so PLAT-76 can leave the mgmt CDK stack. * fix(iam): pin door-unlock apigw domain and ssm reads Stop the apply role from managing every HTTP API custom domain, and keep SecureString door-unlock parameters off HCP plan and apply GetParameter. --- .../deploy-substrate.template.yaml | 45 ++- .../terraform-substrate.template.yaml | 333 ++++++++++++++++++ 2 files changed, 377 insertions(+), 1 deletion(-) diff --git a/lib/deploy-substrate/deploy-substrate.template.yaml b/lib/deploy-substrate/deploy-substrate.template.yaml index cafa75d..2ac9b77 100644 --- a/lib/deploy-substrate/deploy-substrate.template.yaml +++ b/lib/deploy-substrate/deploy-substrate.template.yaml @@ -147,11 +147,12 @@ Description: >- # seahaven-lambda-execution-boundary-procurement-ingest: 3977 / 11 statements # seahaven-lambda-execution-boundary-meal-order-manager: 2380 / 10 statements # seahaven-lambda-execution-boundary-seahaven-site: 1159 / 6 statements +# seahaven-lambda-execution-boundary-seahaven-door-unlock-api: measure after deploy (PLAT-76) # Dev copies are floor-only (691 / 4) via IsProdAccount. # # Guardrail PolicyDocuments also cap at 6,144. Each extra allow-list ARN # is copied into four Sids in EACH of SamCfnIamManagementPolicy and -# HcptfIamManagementPolicy. Measured after this list (6 ARNs): +# HcptfIamManagementPolicy. Measured after this list (7 ARNs, PLAT-76): # seahaven-cfn-exec-iam-management: 4571 / 10 statements (1573 headroom) # seahaven-hcptf-iam-management: 4693 / 10 statements (1451 headroom) # @@ -1104,6 +1105,47 @@ Resources: - !Sub "arn:aws:cloudfront::${AWS::AccountId}:distribution/*" - !Ref AWS::NoValue + DoorUnlockApiBoundary: + Type: AWS::IAM::ManagedPolicy + Properties: + ManagedPolicyName: seahaven-lambda-execution-boundary-seahaven-door-unlock-api + Description: >- + Per-workload permissions boundary for seahaven-door-unlock-api + (PLAT-76 / PLAT-52). Floor plus exact prod SSM parameter ARNs and + 3CX secret ARNs. Shared policy remains the live-role ceiling + until app retarget. + PolicyDocument: + Version: "2012-10-17" + Statement: + # Floor aliases — edit the &lambdaBoundaryFloor* anchors on + # AfiBackupMonitorBoundary only; do not inline a divergent copy. + - *lambdaBoundaryFloorLogsWrite + - *lambdaBoundaryFloorLogsDescribe + - *lambdaBoundaryFloorXRay + - *lambdaBoundaryFloorEc2Eni + - !If + - IsProdAccount + - Sid: DoorUnlockApiSsm + Effect: Allow + Action: + - ssm:GetParameter + Resource: + - arn:aws:ssm:us-east-1:011934824531:parameter/seahaven/door-unlock/elements-api-key + - arn:aws:ssm:us-east-1:011934824531:parameter/seahaven/door-unlock/auth-token + - arn:aws:ssm:us-east-1:011934824531:parameter/seahaven/door-unlock/door-id + - !Ref AWS::NoValue + - !If + - IsProdAccount + - Sid: DoorUnlockApiSecrets + Effect: Allow + Action: + - secretsmanager:GetSecretValue + Resource: + - arn:aws:secretsmanager:us-east-1:011934824531:secret:afterhours-shift-manager/3cx-domain-TPwqWP + - arn:aws:secretsmanager:us-east-1:011934824531:secret:afterhours-shift-manager/3cx-client-id-jzyQXb + - arn:aws:secretsmanager:us-east-1:011934824531:secret:afterhours-shift-manager/3cx-client-secret-jpO476 + - !Ref AWS::NoValue + # --------------------------------------------------------------------------- # Shared CloudFormation execution role (SAM stacks) — INFRA-97 scoped # @@ -1648,6 +1690,7 @@ Resources: - !Sub "arn:aws:iam::${AWS::AccountId}:policy/seahaven-lambda-execution-boundary-procurement-ingest" - !Sub "arn:aws:iam::${AWS::AccountId}:policy/seahaven-lambda-execution-boundary-meal-order-manager" - !Sub "arn:aws:iam::${AWS::AccountId}:policy/seahaven-lambda-execution-boundary-seahaven-site" + - !Sub "arn:aws:iam::${AWS::AccountId}:policy/seahaven-lambda-execution-boundary-seahaven-door-unlock-api" # Attach managed policies — MUST have boundary already on role - Sid: IAMAttachPolicyWithBoundary diff --git a/lib/terraform-substrate/terraform-substrate.template.yaml b/lib/terraform-substrate/terraform-substrate.template.yaml index 8af2c84..43d1bd4 100644 --- a/lib/terraform-substrate/terraform-substrate.template.yaml +++ b/lib/terraform-substrate/terraform-substrate.template.yaml @@ -211,6 +211,7 @@ Resources: - !Sub "arn:aws:iam::${AWS::AccountId}:policy/seahaven-lambda-execution-boundary-procurement-ingest" - !Sub "arn:aws:iam::${AWS::AccountId}:policy/seahaven-lambda-execution-boundary-meal-order-manager" - !Sub "arn:aws:iam::${AWS::AccountId}:policy/seahaven-lambda-execution-boundary-seahaven-site" + - !Sub "arn:aws:iam::${AWS::AccountId}:policy/seahaven-lambda-execution-boundary-seahaven-door-unlock-api" # Attach managed policies — MUST have boundary already on role - Sid: IAMAttachPolicyWithBoundary @@ -1980,3 +1981,335 @@ Resources: } ] } + + # --------------------------------------------------------------------------- + # seahaven-door-unlock-api-prod (PLAT-76) — HttpApi + 5 Lambdas + EventBridge + # + ACM custom domain + alarms. Plan role: ViewOnly + plan-refresh sidecar. + # Apply role: HcptfIamManagement + prefix-scoped service wildcards. + # --------------------------------------------------------------------------- + HcptfDoorUnlockApiPlanRole: + Type: AWS::IAM::Role + Condition: IsProdAccount + Properties: + RoleName: hcptf-seahaven-door-unlock-api-plan + AssumeRolePolicyDocument: + Version: "2012-10-17" + Statement: + - Effect: Allow + Principal: + Federated: !Sub "arn:aws:iam::${AWS::AccountId}:oidc-provider/app.terraform.io" + Action: sts:AssumeRoleWithWebIdentity + Condition: + StringEquals: + "app.terraform.io:aud": aws.workload.identity + "app.terraform.io:sub": organization:seahaven:project:seahaven-prod:workspace:seahaven-door-unlock-api-prod:run_phase:plan + ManagedPolicyArns: + - arn:aws:iam::aws:policy/job-function/ViewOnlyAccess + Policies: + - PolicyName: seahaven-door-unlock-api-plan-refresh + PolicyDocument: + Version: "2012-10-17" + Statement: + - Sid: RefreshIamRoles + Effect: Allow + Action: + - iam:GetRole + - iam:GetRolePolicy + - iam:ListRolePolicies + - iam:ListAttachedRolePolicies + - iam:ListRoleTags + Resource: + - !Sub "arn:aws:iam::${AWS::AccountId}:role/tf-managed/door-unlock-api-*" + - Sid: RefreshManagedPolicies + Effect: Allow + Action: + - iam:GetPolicy + - iam:GetPolicyVersion + Resource: "*" + - Sid: RefreshEventBridge + Effect: Allow + Action: + - events:DescribeRule + - events:ListTargetsByRule + - events:ListTagsForResource + Resource: + - !Sub "arn:aws:events:us-east-1:${AWS::AccountId}:rule/door-unlock-api-*" + - Sid: RefreshLambda + Effect: Allow + Action: + - lambda:Get* + - lambda:List* + Resource: + - !Sub "arn:aws:lambda:us-east-1:${AWS::AccountId}:function:door-unlock-api-*" + - Sid: RefreshBuckets + Effect: Allow + Action: + - s3:Get* + - s3:ListBucket + Resource: + - !Sub "arn:aws:s3:::door-unlock-api-artifacts-${AWS::AccountId}" + - !Sub "arn:aws:s3:::door-unlock-api-artifacts-${AWS::AccountId}/*" + - Sid: RefreshLogs + Effect: Allow + Action: + - logs:DescribeLogGroups + - logs:ListTagsForResource + Resource: "*" + - Sid: RefreshAcm + Effect: Allow + Action: + - acm:DescribeCertificate + - acm:ListCertificates + - acm:ListTagsForCertificate + - acm:GetCertificate + Resource: "*" + # String door-id only. SecureString auth-token / elements-api-key + # stay off the plan role so speculative runs cannot render them. + - Sid: RefreshDoorUnlockSsm + Effect: Allow + Action: + - ssm:GetParameter + - ssm:GetParameters + Resource: + - !Sub "arn:aws:ssm:us-east-1:${AWS::AccountId}:parameter/seahaven/door-unlock/door-id" + - Sid: RefreshDoorUnlockSsmTags + Effect: Allow + Action: + - ssm:ListTagsForResource + Resource: + - !Sub "arn:aws:ssm:us-east-1:${AWS::AccountId}:parameter/seahaven/door-unlock/*" + - Sid: RefreshSsmDescribeParameters + Effect: Allow + Action: + - ssm:DescribeParameters + Resource: "*" + - Sid: RefreshHttpApi + Effect: Allow + Action: + - apigateway:GET + Resource: + - !Sub "arn:aws:apigateway:us-east-1::/apis/*" + - arn:aws:apigateway:us-east-1::/domainnames/doorunlock.seahaven.com + - arn:aws:apigateway:us-east-1::/domainnames/doorunlock.seahaven.com/* + - !Sub "arn:aws:apigateway:us-east-1::/tags/*" + - Sid: RefreshAlarms + Effect: Allow + Action: + - cloudwatch:DescribeAlarms + - cloudwatch:ListTagsForResource + Resource: "*" + - Sid: RefreshThreeCxSecrets + Effect: Allow + Action: + - secretsmanager:DescribeSecret + Resource: + - arn:aws:secretsmanager:us-east-1:011934824531:secret:afterhours-shift-manager/3cx-domain-TPwqWP + - arn:aws:secretsmanager:us-east-1:011934824531:secret:afterhours-shift-manager/3cx-client-id-jzyQXb + - arn:aws:secretsmanager:us-east-1:011934824531:secret:afterhours-shift-manager/3cx-client-secret-jpO476 + + HcptfDoorUnlockApiApplyRole: + Type: AWS::IAM::Role + Condition: IsProdAccount + Properties: + RoleName: hcptf-seahaven-door-unlock-api + AssumeRolePolicyDocument: + Version: "2012-10-17" + Statement: + - Effect: Allow + Principal: + Federated: !Sub "arn:aws:iam::${AWS::AccountId}:oidc-provider/app.terraform.io" + Action: sts:AssumeRoleWithWebIdentity + Condition: + StringEquals: + "app.terraform.io:aud": aws.workload.identity + "app.terraform.io:sub": organization:seahaven:project:seahaven-prod:workspace:seahaven-door-unlock-api-prod:run_phase:apply + ManagedPolicyArns: + - !Ref HcptfIamManagementPolicy + Policies: + - PolicyName: seahaven-door-unlock-api-services + PolicyDocument: + Version: "2012-10-17" + Statement: + - Sid: LambdaAll + Effect: Allow + Action: + - lambda:* + Resource: + - !Sub "arn:aws:lambda:us-east-1:${AWS::AccountId}:function:door-unlock-api-*" + - Sid: LambdaList + Effect: Allow + Action: + - lambda:ListFunctions + - lambda:GetAccountSettings + Resource: "*" + - Sid: EventBridgeRules + Effect: Allow + Action: + - events:* + Resource: + - !Sub "arn:aws:events:us-east-1:${AWS::AccountId}:rule/door-unlock-api-*" + - Sid: CloudWatchLogs + Effect: Allow + Action: + - logs:CreateLogGroup + - logs:DeleteLogGroup + - logs:PutRetentionPolicy + - logs:DeleteRetentionPolicy + - logs:TagResource + - logs:UntagResource + - logs:ListTagsForResource + Resource: + - !Sub "arn:aws:logs:us-east-1:${AWS::AccountId}:log-group:/aws/lambda/door-unlock-api-*" + - !Sub "arn:aws:logs:us-east-1:${AWS::AccountId}:log-group:/aws/apigateway/door-unlock-api*" + - Sid: CloudWatchLogsDescribe + Effect: Allow + Action: + - logs:DescribeLogGroups + Resource: "*" + - Sid: DoorUnlockApiGwAccessLogDelivery + Effect: Allow + Action: + - logs:CreateLogDelivery + - logs:GetLogDelivery + - logs:UpdateLogDelivery + - logs:DeleteLogDelivery + - logs:ListLogDeliveries + - logs:DescribeResourcePolicies + Resource: "*" + - Sid: StackBuckets + Effect: Allow + Action: + - s3:* + Resource: + - !Sub "arn:aws:s3:::door-unlock-api-artifacts-${AWS::AccountId}" + - !Sub "arn:aws:s3:::door-unlock-api-artifacts-${AWS::AccountId}/*" + # HTTP API ids are allocated at create (same as meal-order). + # Custom domain is hostname-pinned like procurement-api. + - Sid: HttpApiManage + Effect: Allow + Action: + - apigateway:* + Resource: + - !Sub "arn:aws:apigateway:us-east-1::/apis" + - !Sub "arn:aws:apigateway:us-east-1::/apis/*" + - !Sub "arn:aws:apigateway:us-east-1::/tags/*" + - Sid: HttpApiDomain + Effect: Allow + Action: + - apigateway:* + Resource: + - arn:aws:apigateway:us-east-1::/domainnames/doorunlock.seahaven.com + - arn:aws:apigateway:us-east-1::/domainnames/doorunlock.seahaven.com/* + - Sid: AcmCreate + Effect: Allow + Action: + - acm:RequestCertificate + Resource: "*" + Condition: + StringEquals: + "aws:RequestTag/Project": seahaven-door-unlock-api + - Sid: AcmList + Effect: Allow + Action: + - acm:ListCertificates + - acm:ListTagsForCertificate + Resource: "*" + - Sid: AcmManageTagged + Effect: Allow + Action: + - acm:DescribeCertificate + - acm:GetCertificate + - acm:DeleteCertificate + - acm:AddTagsToCertificate + - acm:RemoveTagsFromCertificate + - acm:RenewCertificate + Resource: "*" + Condition: + StringEquals: + "aws:ResourceTag/Project": seahaven-door-unlock-api + # HCP reads the String door-id data source only. Lambda execution + # roles (not this apply role) GetParameter the SecureStrings. + - Sid: DoorUnlockSsm + Effect: Allow + Action: + - ssm:GetParameter + - ssm:GetParameters + Resource: + - !Sub "arn:aws:ssm:us-east-1:${AWS::AccountId}:parameter/seahaven/door-unlock/door-id" + - Sid: DoorUnlockSsmTags + Effect: Allow + Action: + - ssm:ListTagsForResource + Resource: + - !Sub "arn:aws:ssm:us-east-1:${AWS::AccountId}:parameter/seahaven/door-unlock/*" + - Sid: DoorUnlockSsmDescribeParameters + Effect: Allow + Action: + - ssm:DescribeParameters + Resource: "*" + - Sid: DescribeThreeCxSecrets + Effect: Allow + Action: + - secretsmanager:DescribeSecret + Resource: + - arn:aws:secretsmanager:us-east-1:011934824531:secret:afterhours-shift-manager/3cx-domain-TPwqWP + - arn:aws:secretsmanager:us-east-1:011934824531:secret:afterhours-shift-manager/3cx-client-id-jzyQXb + - arn:aws:secretsmanager:us-east-1:011934824531:secret:afterhours-shift-manager/3cx-client-secret-jpO476 + - Sid: DoorUnlockPassRoleApiGateway + Effect: Allow + Action: + - iam:PassRole + Resource: + - !Sub "arn:aws:iam::${AWS::AccountId}:role/tf-managed/door-unlock-api-*" + Condition: + StringEquals: + "iam:PassedToService": "apigateway.amazonaws.com" + - Sid: CloudWatchAlarms + Effect: Allow + Action: + - cloudwatch:PutMetricAlarm + - cloudwatch:DeleteAlarms + - cloudwatch:DescribeAlarms + - cloudwatch:TagResource + - cloudwatch:UntagResource + - cloudwatch:ListTagsForResource + Resource: + - !Sub "arn:aws:cloudwatch:us-east-1:${AWS::AccountId}:alarm:door-unlock-api-*" + - Sid: SnsPublishSiteAlerts + Effect: Allow + Action: + - sns:Publish + - sns:GetTopicAttributes + - sns:ListTagsForResource + Resource: + - !Sub "arn:aws:sns:us-east-1:${AWS::AccountId}:site-alerts" + + DoorUnlockApiAccessLogResourcePolicy: + Type: AWS::Logs::ResourcePolicy + Condition: IsProdAccount + Properties: + PolicyName: DoorUnlockApiAccessLogDelivery + PolicyDocument: !Sub | + { + "Version": "2012-10-17", + "Statement": [ + { + "Sid": "AWSLogDeliveryWrite", + "Effect": "Allow", + "Principal": { "Service": "delivery.logs.amazonaws.com" }, + "Action": [ + "logs:CreateLogStream", + "logs:PutLogEvents" + ], + "Resource": [ + "arn:aws:logs:us-east-1:${AWS::AccountId}:log-group:/aws/apigateway/door-unlock-api", + "arn:aws:logs:us-east-1:${AWS::AccountId}:log-group:/aws/apigateway/door-unlock-api:*" + ], + "Condition": { + "StringEquals": { + "aws:SourceAccount": "${AWS::AccountId}" + } + } + } + ] + }