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.
This commit is contained in:
Adam Moussa 2026-10-02 00:01:21 +00:00 • committed by GitHub
parent 42781f743e
commit 54c81dedc9
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
2 changed files with 187 additions and 22 deletions

View file

@ -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. |

View file

@ -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,
});
}