seahaven-org-baseline/lib/engineering-access-stack.ts
Adam Moussa 54c81dedc9
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.
2026-10-02 00:01:21 +00:00

287 lines
8.6 KiB
TypeScript

import * as cdk from "aws-cdk-lib";
import * as identitystore from "aws-cdk-lib/aws-identitystore";
import * as sso from "aws-cdk-lib/aws-sso";
import { Construct } from "constructs";
const IDENTITY_CENTER_INSTANCE_ARN =
"arn:aws:sso:::instance/ssoins-722321f42ca610e4";
const IDENTITY_STORE_ID = "d-9067ec8e26";
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/*`,
];
/**
* 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: "DenySecretAndPaymentReads",
Effect: "Deny",
Action: [
"secretsmanager:GetSecretValue",
"secretsmanager:BatchGetSecretValue",
"kms:Decrypt",
"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,
},
],
};
export interface EngineeringAccessStackProps extends cdk.StackProps {
devAccountId: string;
prodAccountId: string;
}
/**
* Identity Center group and permission sets for the engineering team
* (PLAT-235, scoped in PLAT-236).
*
* 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.
*
* 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) {
super(scope, id, props);
const group = new identitystore.CfnGroup(this, "EngineeringGroup", {
identityStoreId: IDENTITY_STORE_ID,
displayName: "engineering",
description:
"Engineering team. Prod view of payments-dashboard and seahaven-site. No secret or payment-data reads.",
});
const devPermissionSet = this.permissionSet(
"EngineeringDevPermissionSet",
"EngineeringDev",
"No day-1 project resources in seahaven-dev.",
DATA_PLANE_DENY,
);
const prodPermissionSet = this.permissionSet(
"EngineeringProdPermissionSet",
"EngineeringProd",
"Read payments-dashboard configuration and the seahaven-site bucket and distribution.",
PROD_PROJECT_VIEW,
);
this.assignment(
"EngineeringDevAssignment",
devPermissionSet,
group,
props.devAccountId,
);
this.assignment(
"EngineeringProdAssignment",
prodPermissionSet,
group,
props.prodAccountId,
);
}
private permissionSet(
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: [],
inlinePolicy,
});
}
private assignment(
id: string,
permissionSet: sso.CfnPermissionSet,
group: identitystore.CfnGroup,
targetId: string,
): void {
new sso.CfnAssignment(this, id, {
instanceArn: IDENTITY_CENTER_INSTANCE_ARN,
permissionSetArn: permissionSet.attrPermissionSetArn,
principalId: group.attrGroupId,
principalType: "GROUP",
targetId,
targetType: "AWS_ACCOUNT",
});
}
}