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