From 54c81dedc9937f1a2aacec4321e43c8d46b7a6ab Mon Sep 17 00:00:00 2001 From: Adam Moussa <166072409+amoussa1229@users.noreply.github.com> Date: Fri, 2 Oct 2026 00:01:21 +0000 Subject: [PATCH] feat(iam): scope engineering prod view to day-one projects (PLAT-236) (#170) Limit EngineeringProd to payments-dashboard configuration and the public site, and drop account-wide view in dev where those projects do not run. --- README.md | 2 +- lib/engineering-access-stack.ts | 207 ++++++++++++++++++++++++++++---- 2 files changed, 187 insertions(+), 22 deletions(-) diff --git a/README.md b/README.md index 6f23294..8dfb255 100644 --- a/README.md +++ b/README.md @@ -30,7 +30,7 @@ are noted): | `seahaven-backup` | 328440206208 | us-east-1 | Primary AWS Backup vault + plan + role (C-7) | | `seahaven-backup-offsite` | 328440206208 | us-west-2 | Governance-locked offsite copy vault (C-7) | | `seahaven-org-governance` | 328440206208 | us-east-1 | AWS Organizations OU tree + SCPs (incl. the cdk-imported external-dev guardrails) | -| `seahaven-engineering-access` | 328440206208 | us-east-1 | Identity Center group `engineering` plus view-only permission sets `EngineeringDev` (710827005802) and `EngineeringProd` (011934824531). No members (PLAT-235). | +| `seahaven-engineering-access` | 328440206208 | us-east-1 | Identity Center group `engineering`. `EngineeringProd` reads payments-dashboard configuration and seahaven-site in 011934824531. `EngineeringDev` has no allow (PLAT-235, PLAT-236). | | `seahaven-external-dev-baseline` | 396287094661 | us-east-1 | Member-account baseline: Config, GuardDuty, Security Hub (FSBP + CIS v3.0), Access Analyzer, flow logs, budget | | `seahaven-terraform-substrate` | 011934824531, 710827005802 | us-east-1 | Removed from the CDK app and from CD (PLAT-147). Live stacks remain until `scripts/delete-terraform-substrate-prod-dev.sh`. The six imported prod pairs are already forgotten. | | `seahaven-site-hcptf` | 011934824531 | us-east-1 | Imported `hcptf-seahaven-site` apply and plan roles (PLAT-225). On the prod deploy job. Do not create. | diff --git a/lib/engineering-access-stack.ts b/lib/engineering-access-stack.ts index bde5229..21172bf 100644 --- a/lib/engineering-access-stack.ts +++ b/lib/engineering-access-stack.ts @@ -6,34 +6,195 @@ import { Construct } from "constructs"; const IDENTITY_CENTER_INSTANCE_ARN = "arn:aws:sso:::instance/ssoins-722321f42ca610e4"; const IDENTITY_STORE_ID = "d-9067ec8e26"; -const VIEW_ONLY_ACCESS_ARN = - "arn:aws:iam::aws:policy/job-function/ViewOnlyAccess"; +const PROD_ACCOUNT_ID = "011934824531"; +const REGION = "us-east-1"; + +const SITE_BUCKET = "seahaven-site-prod"; +const SITE_DISTRIBUTION_ID = "E35OCA79OAJ03H"; +const PAYMENTS_API_ID = "srjhpctwb9"; + +const prodArn = (service: string, resource: string): string => + `arn:aws:${service}:${REGION}:${PROD_ACCOUNT_ID}:${resource}`; + +const SITE_OBJECT_ARNS = [`arn:aws:s3:::${SITE_BUCKET}/*`]; +const DEPLOY_PARAMETER_ARNS = [ + `arn:aws:ssm:${REGION}:${PROD_ACCOUNT_ID}:parameter/seahaven-site/deploy/*`, + `arn:aws:ssm:${REGION}:${PROD_ACCOUNT_ID}:parameter/payments-dashboard/deploy/*`, +]; /** - * Blocks secret, object, and item reads if a broader managed policy is - * attached later. ViewOnlyAccess is the allow. ReadOnlyAccess is not used. + * Backstop if a managed policy is attached later. Site objects and the two + * projects' deploy parameters stay readable. Payment items, payment files, + * and secret values do not. */ const DATA_PLANE_DENY = { Version: "2012-10-17", Statement: [ { - Sid: "DenyDataPlaneReads", + Sid: "DenySecretAndPaymentReads", Effect: "Deny", Action: [ "secretsmanager:GetSecretValue", "secretsmanager:BatchGetSecretValue", - "ssm:GetParameter", - "ssm:GetParameters", - "ssm:GetParametersByPath", "kms:Decrypt", - "s3:GetObject", "dynamodb:GetItem", "dynamodb:BatchGetItem", "dynamodb:Query", "dynamodb:Scan", + "sqs:ReceiveMessage", + "sqs:DeleteMessage", + "cloudfront:CreateInvalidation", ], Resource: "*", }, + { + Sid: "DenyObjectReadsExceptSite", + Effect: "Deny", + Action: ["s3:GetObject", "s3:GetObjectVersion"], + NotResource: SITE_OBJECT_ARNS, + }, + { + Sid: "DenyParameterReadsExceptDeploy", + Effect: "Deny", + Action: [ + "ssm:GetParameter", + "ssm:GetParameters", + "ssm:GetParametersByPath", + ], + NotResource: DEPLOY_PARAMETER_ARNS, + }, + ], +}; + +const PROD_PROJECT_VIEW = { + Version: "2012-10-17", + Statement: [ + ...DATA_PLANE_DENY.Statement, + { + Sid: "ListSiteAndPaymentsBuckets", + Effect: "Allow", + Action: [ + "s3:ListBucket", + "s3:GetBucketLocation", + "s3:GetBucketPolicy", + "s3:GetEncryptionConfiguration", + "s3:GetBucketTagging", + "s3:GetBucketVersioning", + "s3:GetLifecycleConfiguration", + "s3:GetBucketPublicAccessBlock", + ], + Resource: [ + `arn:aws:s3:::${SITE_BUCKET}`, + "arn:aws:s3:::payments-dashboard-artifacts-011934824531", + "arn:aws:s3:::seahaven-payments-csv-011934824531", + "arn:aws:s3:::seahaven-payments-boa-raw-011934824531", + ], + }, + { + Sid: "ReadSiteObjects", + Effect: "Allow", + Action: ["s3:GetObject", "s3:GetObjectVersion"], + Resource: SITE_OBJECT_ARNS, + }, + { + Sid: "ReadSiteDistribution", + Effect: "Allow", + Action: [ + "cloudfront:GetDistribution", + "cloudfront:GetDistributionConfig", + "cloudfront:ListTagsForResource", + "cloudfront:GetFunction", + "cloudfront:DescribeFunction", + ], + Resource: [ + `arn:aws:cloudfront::${PROD_ACCOUNT_ID}:distribution/${SITE_DISTRIBUTION_ID}`, + `arn:aws:cloudfront::${PROD_ACCOUNT_ID}:function/seahaven-site-prod-directory-index`, + ], + }, + { + Sid: "ReadPaymentsFunctions", + Effect: "Allow", + Action: [ + "lambda:GetFunction", + "lambda:GetFunctionConfiguration", + "lambda:GetPolicy", + "lambda:ListTags", + "lambda:ListVersionsByFunction", + ], + Resource: prodArn("lambda", "function:payments-*"), + }, + { + Sid: "DescribePaymentsTable", + Effect: "Allow", + Action: [ + "dynamodb:DescribeTable", + "dynamodb:DescribeTimeToLive", + "dynamodb:DescribeContinuousBackups", + "dynamodb:ListTagsOfResource", + ], + Resource: prodArn("dynamodb", "table/PaymentsDashboard"), + }, + { + Sid: "ReadPaymentsRoles", + Effect: "Allow", + Action: [ + "iam:GetRole", + "iam:GetRolePolicy", + "iam:ListRolePolicies", + "iam:ListAttachedRolePolicies", + ], + Resource: [ + `arn:aws:iam::${PROD_ACCOUNT_ID}:role/payments-dashboard-*`, + `arn:aws:iam::${PROD_ACCOUNT_ID}:role/githubdeploy-payments-dashboard`, + `arn:aws:iam::${PROD_ACCOUNT_ID}:role/hcptf-payments-dashboard`, + `arn:aws:iam::${PROD_ACCOUNT_ID}:role/hcptf-payments-dashboard-plan`, + `arn:aws:iam::${PROD_ACCOUNT_ID}:role/platform/hcptf-payments-dashboard`, + `arn:aws:iam::${PROD_ACCOUNT_ID}:role/platform/hcptf-payments-dashboard-plan`, + ], + }, + { + Sid: "ReadPaymentsApi", + Effect: "Allow", + Action: "apigateway:GET", + Resource: [ + `arn:aws:apigateway:${REGION}::/apis/${PAYMENTS_API_ID}`, + `arn:aws:apigateway:${REGION}::/apis/${PAYMENTS_API_ID}/*`, + ], + }, + { + Sid: "ReadPaymentsQueueRulesAndAlarms", + Effect: "Allow", + Action: [ + "sqs:GetQueueAttributes", + "sqs:GetQueueUrl", + "events:DescribeRule", + "events:ListTargetsByRule", + "cloudwatch:DescribeAlarms", + ], + Resource: [ + prodArn("sqs", "payments-processPaymentCsv-async-dlq"), + prodArn("events", "rule/payments-dashboard-daily"), + prodArn("events", "rule/payments-dashboard-intraday"), + prodArn("cloudwatch", "alarm:payments-*"), + ], + }, + { + Sid: "DescribeProjectLogGroups", + Effect: "Allow", + Action: ["logs:DescribeLogGroups", "logs:DescribeLogStreams"], + Resource: [ + prodArn("logs", "log-group:/aws/lambda/payments-*"), + prodArn("logs", "log-group:/aws/lambda/payments-*:*"), + prodArn("logs", "log-group:/aws/apigateway/payments-dashboard"), + prodArn("logs", "log-group:/aws/apigateway/payments-dashboard:*"), + ], + }, + { + Sid: "ReadDeployParameters", + Effect: "Allow", + Action: ["ssm:GetParameter", "ssm:GetParameters"], + Resource: DEPLOY_PARAMETER_ARNS, + }, ], }; @@ -43,15 +204,16 @@ export interface EngineeringAccessStackProps extends cdk.StackProps { } /** - * Identity Center group and view-only permission sets for the engineering - * team (PLAT-235). + * Identity Center group and permission sets for the engineering team + * (PLAT-235, scoped in PLAT-236). * - * Assigned to seahaven-dev and seahaven-prod only. The group has no members. - * People are added after the roster exists, outside this stack. This is not - * an SCP exemption and cannot assume OrganizationAccountAccessRole. + * EngineeringProd can read payments-dashboard configuration and the public + * seahaven-site bucket and distribution. It cannot read payment records, + * payment files, or secrets. EngineeringDev stays assigned. Neither day-1 + * project has resources in seahaven-dev, so that set has no allow. * - * A later SCIM sync of engineering@seahaven.com must adopt this group. A - * second group with display name engineering will collide. + * Group membership is outside this stack. A later SCIM sync of + * engineering@seahaven.com must adopt this group. */ export class EngineeringAccessStack extends cdk.Stack { constructor(scope: Construct, id: string, props: EngineeringAccessStackProps) { @@ -61,18 +223,20 @@ export class EngineeringAccessStack extends cdk.Stack { identityStoreId: IDENTITY_STORE_ID, displayName: "engineering", description: - "Engineering team. View-only in seahaven-dev and seahaven-prod. No members until the roster exists.", + "Engineering team. Prod view of payments-dashboard and seahaven-site. No secret or payment-data reads.", }); const devPermissionSet = this.permissionSet( "EngineeringDevPermissionSet", "EngineeringDev", - "View-only in seahaven-dev. No secret, object, or item reads.", + "No day-1 project resources in seahaven-dev.", + DATA_PLANE_DENY, ); const prodPermissionSet = this.permissionSet( "EngineeringProdPermissionSet", "EngineeringProd", - "View-only in seahaven-prod. No secret, object, or item reads.", + "Read payments-dashboard configuration and the seahaven-site bucket and distribution.", + PROD_PROJECT_VIEW, ); this.assignment( @@ -93,14 +257,15 @@ export class EngineeringAccessStack extends cdk.Stack { id: string, name: string, description: string, + inlinePolicy: { Version: string; Statement: object[] }, ): sso.CfnPermissionSet { return new sso.CfnPermissionSet(this, id, { instanceArn: IDENTITY_CENTER_INSTANCE_ARN, name, description, sessionDuration: "PT8H", - managedPolicies: [VIEW_ONLY_ACCESS_ARN], - inlinePolicy: DATA_PLANE_DENY, + managedPolicies: [], + inlinePolicy, }); }