mirror of
https://github.com/Sea-Haven-Industries/seahaven-org-baseline.git
synced 2026-10-01 22:07:57 +00:00
feat(iam): add hcptf roles/boundary widen - afi-backup-monitor (PLAT-56) (#76)
* feat(iam): add hcptf roles and boundary widen for afi-backup-monitor Provision plan/apply OIDC roles for workspace afi-backup-monitor-prod and widen the prod Lambda boundary with the two exact secret ARNs. * fix(iam): split DescribeLogGroups and allow afi artifact bucket logs:DescribeLogGroups cannot be resource-scoped; grant it on *. Add S3 permissions for the HCP Lambda artifact bucket used by PLAT-56.
This commit is contained in:
parent
517f41eb7f
commit
4e1cf4c0bd
2 changed files with 195 additions and 66 deletions
|
|
@ -158,6 +158,9 @@ Parameters:
|
|||
|
||||
Conditions:
|
||||
ShouldCreateOIDCProvider: !Equals [!Ref CreateOIDCProvider, "true"]
|
||||
# Exact prod secret ARNs for afi-backup-monitor (PLAT-56) must only widen the
|
||||
# seahaven-prod boundary. The same template deploys to seahaven-dev.
|
||||
IsProdAccount: !Equals [!Ref "AWS::AccountId", "011934824531"]
|
||||
|
||||
Resources:
|
||||
|
||||
|
|
@ -349,13 +352,15 @@ Resources:
|
|||
# stack's template as of 2026-07-30
|
||||
#
|
||||
# afi-backup-monitor (functions: afi-*)
|
||||
# - secretsmanager:GetSecretValue on TWO bare, unprefixed secrets:
|
||||
# afi-api-key and afi-slack-webhook. CORRECTED 2026-07-30: this block
|
||||
# previously named "afi-backup-monitor/slack-webhook-url", which does
|
||||
# not exist. Both ARNs are deploy PARAMETERS in that stack
|
||||
# (AfiApiKeySecretArn / SlackWebhookSecretArn), so no name is
|
||||
# discoverable from its template — the names are in its README:48-49.
|
||||
# - CloudWatch Logs
|
||||
# - secretsmanager:GetSecretValue on TWO exact prod secret ARNs
|
||||
# (PLAT-56, created 2026-08-05 in seahaven-prod; no name patterns):
|
||||
# arn:aws:secretsmanager:us-east-1:011934824531:secret:afi-api-key-w0E02a
|
||||
# arn:aws:secretsmanager:us-east-1:011934824531:secret:afi-slack-webhook-T4oR3G
|
||||
# LIVE MGMT (328440206208) still uses afi-api-key-BD122x and
|
||||
# afi-backup-monitor/slack-webhook-url-NtYGf1 — the 2026-07-30 note
|
||||
# that the webhook name "does not exist" was wrong for mgmt; prod
|
||||
# intentionally uses the ticket names afi-api-key / afi-slack-webhook.
|
||||
# - CloudWatch Logs (covered by fleet floor)
|
||||
# - nothing else
|
||||
#
|
||||
# ---------------------------------------------------------------------------
|
||||
|
|
@ -487,70 +492,40 @@ Resources:
|
|||
- ec2:DescribeVpcs
|
||||
Resource: "*"
|
||||
|
||||
# ── NO PER-WORKLOAD DATA-PLANE STATEMENTS — BY DESIGN ───────────────
|
||||
# The statements above are the FLEET-WIDE FLOOR: what every Lambda
|
||||
# execution role needs regardless of which workload it belongs to.
|
||||
# There are deliberately NO DynamoDB, S3, Secrets Manager, SSM, SQS,
|
||||
# SES, KMS, lambda:InvokeFunction or scheduler statements here.
|
||||
# ── afi-backup-monitor (PLAT-56) — prod-only exact secret ARNs ───────
|
||||
# Derived from live stack parameters + secrets created in seahaven-prod
|
||||
# 2026-08-05. No secret:afi-* patterns. Omitted in seahaven-dev via
|
||||
# IsProdAccount (same template deploys to both accounts).
|
||||
- !If
|
||||
- IsProdAccount
|
||||
- Sid: AfiBackupMonitorSecrets
|
||||
Effect: Allow
|
||||
Action:
|
||||
- secretsmanager:GetSecretValue
|
||||
Resource:
|
||||
- arn:aws:secretsmanager:us-east-1:011934824531:secret:afi-api-key-w0E02a
|
||||
- arn:aws:secretsmanager:us-east-1:011934824531:secret:afi-slack-webhook-T4oR3G
|
||||
- !Ref AWS::NoValue
|
||||
|
||||
# ── FURTHER PER-WORKLOAD DATA-PLANE STATEMENTS ──────────────────────
|
||||
# Floor above + afi-backup-monitor widening (PLAT-56). Additional
|
||||
# stacks add their own statements here, derived from THEIR OWN
|
||||
# template, in their own PR, deployed to UPDATE_COMPLETE before first
|
||||
# workload deploy. WIDENING PATH in the header still governs.
|
||||
#
|
||||
# WHY (decided 2026-07-30, Adam):
|
||||
# WHY the floor stayed empty of data-plane (decided 2026-07-30, Adam):
|
||||
# The security win of INFRA-186 comes from DELETION, not enumeration.
|
||||
# Removing the account-wide secret:*, table/*, function:* and sqs:*
|
||||
# wildcards is what closes the amplifier — the ability of a principal
|
||||
# who can write an inline policy onto a boundary-carrying role to read
|
||||
# every secret in the account. Per-workload prefixes add no security;
|
||||
# they exist only to keep a workload FUNCTIONAL once it arrives.
|
||||
#
|
||||
# An earlier revision of this branch PRE-LOADED prefixes for all five
|
||||
# mgmt SAM stacks before any of them had migrated. That required
|
||||
# predicting five stacks' permission needs from the permission-source
|
||||
# comment block above, and the /sh-security-review pass found SIX
|
||||
# errors in the result — three of which would have failed SILENTLY at
|
||||
# first migration (afterhours' SES config-set, its holiday scheduler
|
||||
# behind a bare except, and afi's webhook secret under an invented
|
||||
# name). The block is a secondary record and is not a substitute for
|
||||
# reading the owning repo's template.
|
||||
#
|
||||
# WIDENING IS THE SAFE DIRECTION. Adding a resource to a boundary can
|
||||
# never break a running Lambda; only tightening can. So there is no
|
||||
# cost to deferring per-workload scope to the migration PR that has
|
||||
# the real template open in front of it — and a large cost to
|
||||
# guessing it years ahead of the migration.
|
||||
#
|
||||
# CONSEQUENCE FOR EVERY MIGRATION PR (mandatory, see WIDENING PATH in
|
||||
# the header): a stack landing in prod or dev MUST add its own
|
||||
# data-plane statements here, derived from ITS OWN template, in its
|
||||
# own PR to THIS repo, deployed to UPDATE_COMPLETE before the
|
||||
# workload's first deploy from its own repo (the workload PR and the
|
||||
# widening PR cannot be the same PR — they live in different repos).
|
||||
# Without them its Lambdas get AccessDenied
|
||||
# at first invoke. The permission-source block above is the starting
|
||||
# point, NOT the authority — verify every entry against the stack.
|
||||
#
|
||||
# Prod/dev boundary usage is 0 (verified 2026-07-30), so this floor
|
||||
# currently constrains nothing that exists. Fleet-wide statements that
|
||||
# genuinely cannot be scoped (Logs, X-Ray, ENI) stay above with their
|
||||
# justifications; they are also the SILENT-failure classes, which is
|
||||
# why they belong in the floor rather than in per-workload widenings.
|
||||
#
|
||||
# KMS is absent deliberately: zero boundary-carrying roles exist, so
|
||||
# nothing bounded decrypts anything yet. (Log-group CMKs are the one
|
||||
# KMS case that never hits an execution role — CloudWatch Logs
|
||||
# decrypts via the key policy's grant to the Logs service principal —
|
||||
# so log-group CMK state is not the gate here. seahaven-prod already
|
||||
# hosts the seahaven-dynamodb-cmk table key; the first migrating
|
||||
# stack touching that table must cover it.) A workload bringing a
|
||||
# CMK-encrypted resource adds a KMS statement with the matching
|
||||
# kms:ViaService principal in its own migration PR — a missing
|
||||
# ViaService entry denies, and for env-var encryption it fails at
|
||||
# cold-start INIT.
|
||||
# wildcards is what closes the amplifier. Per-workload prefixes add no
|
||||
# security; they exist only to keep a workload FUNCTIONAL once it
|
||||
# arrives. WIDENING IS THE SAFE DIRECTION.
|
||||
#
|
||||
# The end-state fix for the shared-ceiling residual (one boundary =
|
||||
# every SAM workload reaches every other's data plane once they land)
|
||||
# is per-workload boundaries — tracked as INFRA-187. Do not improvise
|
||||
# it: the load-bearing problem there is that both guardrail policies
|
||||
# pin ONE literal boundary ARN inside StringEquals conditions, and
|
||||
# loosening that to a wildcard weakens the gate.
|
||||
# every SAM/Terraform workload reaches every other's data plane once
|
||||
# they land) is per-workload boundaries — tracked as INFRA-187. Do not
|
||||
# improvise it: both guardrail policies pin ONE literal boundary ARN
|
||||
# inside StringEquals conditions, and loosening that to a wildcard
|
||||
# weakens the gate.
|
||||
# ---------------------------------------------------------------------------
|
||||
# Shared CloudFormation execution role (SAM stacks) — INFRA-97 scoped
|
||||
#
|
||||
|
|
|
|||
|
|
@ -118,6 +118,10 @@ Parameters:
|
|||
|
||||
Conditions:
|
||||
ShouldCreateOIDCProvider: !Equals [!Ref CreateOIDCProvider, "true"]
|
||||
# Per-workspace hcptf-* roles for afi-backup-monitor-prod (PLAT-56) must only
|
||||
# exist in seahaven-prod. The same template deploys to seahaven-dev; creating
|
||||
# prod-workspace trust there would leave dead credentials in the wrong account.
|
||||
IsProdAccount: !Equals [!Ref "AWS::AccountId", "011934824531"]
|
||||
|
||||
Resources:
|
||||
|
||||
|
|
@ -367,3 +371,153 @@ Resources:
|
|||
Condition:
|
||||
StringEquals:
|
||||
"iam:PassedToService": "lambda.amazonaws.com"
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Per-workspace roles: afi-backup-monitor-prod (PLAT-56)
|
||||
#
|
||||
# First HCP Terraform workload. Trust subs are exact StringEquals on
|
||||
# organization/project/workspace/run_phase — never StringLike, never a
|
||||
# wildcarded run_phase. Plan role: ViewOnlyAccess only (never ReadOnlyAccess,
|
||||
# which grants secretsmanager:GetSecretValue). Apply role: attaches the
|
||||
# shared guardrail plus stack-scoped Lambda / layer / EventBridge / Logs.
|
||||
# Prod-only (IsProdAccount): this template also deploys to seahaven-dev.
|
||||
# ---------------------------------------------------------------------------
|
||||
HcptfAfiBackupMonitorPlanRole:
|
||||
Type: AWS::IAM::Role
|
||||
Condition: IsProdAccount
|
||||
Properties:
|
||||
RoleName: hcptf-afi-backup-monitor-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:afi-backup-monitor-prod:run_phase:plan
|
||||
ManagedPolicyArns:
|
||||
- arn:aws:iam::aws:policy/job-function/ViewOnlyAccess
|
||||
|
||||
HcptfAfiBackupMonitorApplyRole:
|
||||
Type: AWS::IAM::Role
|
||||
Condition: IsProdAccount
|
||||
Properties:
|
||||
RoleName: hcptf-afi-backup-monitor
|
||||
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:afi-backup-monitor-prod:run_phase:apply
|
||||
ManagedPolicyArns:
|
||||
- !Ref HcptfIamManagementPolicy
|
||||
Policies:
|
||||
- PolicyName: afi-backup-monitor-services
|
||||
PolicyDocument:
|
||||
Version: "2012-10-17"
|
||||
Statement:
|
||||
- Sid: LambdaFunctions
|
||||
Effect: Allow
|
||||
Action:
|
||||
- lambda:CreateFunction
|
||||
- lambda:DeleteFunction
|
||||
- lambda:GetFunction
|
||||
- lambda:GetFunctionConfiguration
|
||||
- lambda:UpdateFunctionCode
|
||||
- lambda:UpdateFunctionConfiguration
|
||||
- lambda:ListVersionsByFunction
|
||||
- lambda:PublishVersion
|
||||
- lambda:TagResource
|
||||
- lambda:UntagResource
|
||||
- lambda:ListTags
|
||||
- lambda:AddPermission
|
||||
- lambda:RemovePermission
|
||||
- lambda:GetPolicy
|
||||
- lambda:InvokeFunction
|
||||
Resource:
|
||||
- !Sub "arn:aws:lambda:us-east-1:${AWS::AccountId}:function:afi-*"
|
||||
- Sid: LambdaLayers
|
||||
Effect: Allow
|
||||
Action:
|
||||
- lambda:PublishLayerVersion
|
||||
- lambda:DeleteLayerVersion
|
||||
- lambda:GetLayerVersion
|
||||
- lambda:ListLayerVersions
|
||||
Resource:
|
||||
- !Sub "arn:aws:lambda:us-east-1:${AWS::AccountId}:layer:afi-shared*"
|
||||
- Sid: LambdaList
|
||||
Effect: Allow
|
||||
Action:
|
||||
- lambda:ListFunctions
|
||||
- lambda:ListLayers
|
||||
- lambda:GetAccountSettings
|
||||
Resource: "*"
|
||||
- Sid: EventBridgeRules
|
||||
Effect: Allow
|
||||
Action:
|
||||
- events:PutRule
|
||||
- events:DeleteRule
|
||||
- events:DescribeRule
|
||||
- events:EnableRule
|
||||
- events:DisableRule
|
||||
- events:PutTargets
|
||||
- events:RemoveTargets
|
||||
- events:ListTargetsByRule
|
||||
- events:TagResource
|
||||
- events:UntagResource
|
||||
- events:ListTagsForResource
|
||||
Resource:
|
||||
- !Sub "arn:aws:events:us-east-1:${AWS::AccountId}:rule/afi-*"
|
||||
- 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/afi-*"
|
||||
# logs:DescribeLogGroups is a collection action — AWS authorises it
|
||||
# against "*" only. Scoping it to a log-group ARN is a silent no-op
|
||||
# grant (same pitfall documented on LambdaExecutionBoundary).
|
||||
- Sid: CloudWatchLogsDescribe
|
||||
Effect: Allow
|
||||
Action:
|
||||
- logs:DescribeLogGroups
|
||||
Resource: "*"
|
||||
# Artifact bucket for HCP plan/apply split: zip bytes travel in the
|
||||
# plan via aws_s3_object content_base64 (local archive_file paths
|
||||
# from the plan worker are not on the apply worker).
|
||||
- Sid: LambdaArtifactsBucket
|
||||
Effect: Allow
|
||||
Action:
|
||||
- s3:CreateBucket
|
||||
- s3:DeleteBucket
|
||||
- s3:GetBucketLocation
|
||||
- s3:GetBucketPolicy
|
||||
- s3:PutBucketPolicy
|
||||
- s3:DeleteBucketPolicy
|
||||
- s3:GetBucketVersioning
|
||||
- s3:PutBucketVersioning
|
||||
- s3:GetBucketPublicAccessBlock
|
||||
- s3:PutBucketPublicAccessBlock
|
||||
- s3:GetBucketTagging
|
||||
- s3:PutBucketTagging
|
||||
- s3:ListBucket
|
||||
- s3:GetObject
|
||||
- s3:PutObject
|
||||
- s3:DeleteObject
|
||||
Resource:
|
||||
- !Sub "arn:aws:s3:::afi-backup-monitor-artifacts-${AWS::AccountId}"
|
||||
- !Sub "arn:aws:s3:::afi-backup-monitor-artifacts-${AWS::AccountId}/*"
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue