"use strict";
/*
 *  Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
 *  SPDX-License-Identifier: Apache-2.0
 */
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
    if (k2 === undefined) k2 = k;
    var desc = Object.getOwnPropertyDescriptor(m, k);
    if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
      desc = { enumerable: true, get: function() { return m[k]; } };
    }
    Object.defineProperty(o, k2, desc);
}) : (function(o, m, k, k2) {
    if (k2 === undefined) k2 = k;
    o[k2] = m[k];
}));
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
    Object.defineProperty(o, "default", { enumerable: true, value: v });
}) : function(o, v) {
    o["default"] = v;
});
var __importStar = (this && this.__importStar) || function (mod) {
    if (mod && mod.__esModule) return mod;
    var result = {};
    if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k);
    __setModuleDefault(result, mod);
    return result;
};
var __importDefault = (this && this.__importDefault) || function (mod) {
    return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.RSWStack = void 0;
/* eslint-disable @typescript-eslint/ban-types */
/* eslint-disable no-new */
const path_1 = require("path");
const workbench_core_infrastructure_1 = require("@aws/workbench-core-infrastructure");
const aws_cdk_lib_1 = require("aws-cdk-lib");
const aws_apigateway_1 = require("aws-cdk-lib/aws-apigateway");
const aws_certificatemanager_1 = require("aws-cdk-lib/aws-certificatemanager");
const aws_dynamodb_1 = require("aws-cdk-lib/aws-dynamodb");
const aws_elasticloadbalancingv2_1 = require("aws-cdk-lib/aws-elasticloadbalancingv2");
const aws_elasticloadbalancingv2_targets_1 = require("aws-cdk-lib/aws-elasticloadbalancingv2-targets");
const aws_events_1 = require("aws-cdk-lib/aws-events");
const targets = __importStar(require("aws-cdk-lib/aws-events-targets"));
const aws_iam_1 = require("aws-cdk-lib/aws-iam");
const aws_lambda_1 = require("aws-cdk-lib/aws-lambda");
const aws_logs_1 = require("aws-cdk-lib/aws-logs");
const aws_route53_1 = require("aws-cdk-lib/aws-route53");
const aws_route53_targets_1 = require("aws-cdk-lib/aws-route53-targets");
const aws_s3_1 = require("aws-cdk-lib/aws-s3");
const lodash_1 = __importDefault(require("lodash"));
const constants_1 = require("./constants");
const workflow_1 = __importDefault(require("./environment/workflow"));
const RSWApplicationLoadBalancer_1 = require("./infra/RSWApplicationLoadBalancer");
const RSWVpc_1 = require("./infra/RSWVpc");
class RSWStack extends aws_cdk_lib_1.Stack {
    constructor(app, props) {
        var _a, _b, _c, _d;
        const { STAGE, AWS_REGION, AWS_REGION_SHORT_NAME, S3_ACCESS_LOGS_BUCKET_NAME_OUTPUT_KEY, S3_ACCESS_BUCKET_PREFIX, S3_ARTIFACT_BUCKET_ARN_OUTPUT_KEY, S3_DATASETS_BUCKET_ARN_OUTPUT_KEY, LAUNCH_CONSTRAINT_ROLE_OUTPUT_KEY, STACK_NAME, SSM_DOC_OUTPUT_KEY_SUFFIX, AMI_IDS_TO_SHARE, ACCT_HANDLER_ARN_OUTPUT_KEY, API_HANDLER_ARN_OUTPUT_KEY, STATUS_HANDLER_ARN_OUTPUT_KEY, STATUS_HANDLER_ROLE_ARN_OUTPUT_KEY, SC_PORTFOLIO_NAME, ALLOWED_ORIGINS, COGNITO_DOMAIN, USER_POOL_CLIENT_NAME, USER_POOL_NAME, WEBSITE_URLS, USER_POOL_ID, CLIENT_ID, CLIENT_SECRET, VPC_ID, S3_DATASETS_ENCRYPTION_KEY_ARN_OUTPUT_KEY, S3_ARTIFACT_ENCRYPTION_KEY_ARN_OUTPUT_KEY, MAIN_ACCT_ALB_ARN_OUTPUT_KEY, SWB_DOMAIN_NAME_OUTPUT_KEY, MAIN_ACCT_ALB_LISTENER_ARN_OUTPUT_KEY, VPC_ID_OUTPUT_KEY, ALB_SUBNET_IDS, ECS_SUBNET_IDS, ECS_SUBNET_IDS_OUTPUT_KEY, ECS_SUBNET_AZS_OUTPUT_KEY, HOSTED_ZONE_ID, DOMAIN_NAME, ALB_INTERNET_FACING, FIELDS_TO_MASK_WHEN_AUDITING, USER_AGENT_STRING
        // In solutions pipeline build, resolve region and account to token value to be resolved on CF deployment
         } = (0, constants_1.getConstants)((0, constants_1.isSolutionsBuild)() ? aws_cdk_lib_1.Aws.REGION : undefined);
        const stackProps = Object.assign({ description: `(${props.solutionId}) - ${props.solutionName} Deployment. Version: ${props.solutionVersion}`, env: {
                account: (0, constants_1.isSolutionsBuild)() ? aws_cdk_lib_1.Aws.ACCOUNT_ID : process.env.CDK_DEFAULT_ACCOUNT,
                region: AWS_REGION
            }, synthesizer: new aws_cdk_lib_1.DefaultStackSynthesizer({
                generateBootstrapVersionRule: (0, constants_1.isSolutionsBuild)() ? false : true
            }) }, props);
        super(app, STACK_NAME, stackProps);
        this._isSolutionsBuild = (0, constants_1.isSolutionsBuild)();
        this._API_LIMIT = 200;
        if (this._isSolutionsBuild) {
            new aws_cdk_lib_1.CfnParameter(this, 'RequiredStackName', {
                type: 'String',
                default: STACK_NAME,
                description: 'Please copy the following value into the "Stack name" field at the top of this page. Warning: Do not change this value.'
            });
        }
        const cognitoDomainToUse = this._isSolutionsBuild
            ? new aws_cdk_lib_1.CfnParameter(this, 'CognitoDomainPrefix', {
                type: 'String',
                minLength: 1,
                maxLength: 63,
                description: 'Please provide a string for your Cognito domain name prefix. Cognito domain names must be globally unique, so be creative.'
            }).valueAsString
            : COGNITO_DOMAIN;
        const workbenchCognito = this._createCognitoResources(cognitoDomainToUse, WEBSITE_URLS, USER_POOL_NAME, USER_POOL_CLIENT_NAME);
        let cognitoDomain;
        let clientId;
        let clientSecret;
        let userPoolId;
        if (process.env.LOCAL_DEVELOPMENT === 'true') {
            cognitoDomain = `https://${COGNITO_DOMAIN}.auth.${AWS_REGION}.amazoncognito.com`;
            clientId = CLIENT_ID;
            clientSecret = CLIENT_SECRET;
            userPoolId = USER_POOL_ID;
        }
        else {
            cognitoDomain = workbenchCognito.cognitoDomain;
            clientId = workbenchCognito.userPoolClientId;
            clientSecret = workbenchCognito.userPoolClientSecret.unsafeUnwrap();
            userPoolId = workbenchCognito.userPoolId;
        }
        // We extract a subset of constants required to be set on Lambda
        // Note: AWS_REGION cannot be set since it's a reserved env variable
        const MAIN_ACCT_ID = `${this.account}`;
        this._createInitialOutputs(MAIN_ACCT_ID, AWS_REGION, AWS_REGION_SHORT_NAME);
        this._s3AccessLogsPrefix = S3_ACCESS_BUCKET_PREFIX;
        this._swbDomainNameOutputKey = SWB_DOMAIN_NAME_OUTPUT_KEY;
        this._mainAccountLoadBalancerListenerArnOutputKey = MAIN_ACCT_ALB_LISTENER_ARN_OUTPUT_KEY;
        this._accessLogsBucket = this._createAccessLogsBucket(S3_ACCESS_LOGS_BUCKET_NAME_OUTPUT_KEY);
        const S3DatasetsEncryptionKey = new workbench_core_infrastructure_1.WorkbenchEncryptionKeyWithRotation(this, S3_DATASETS_ENCRYPTION_KEY_ARN_OUTPUT_KEY);
        const datasetBucket = this._createS3DatasetsBuckets(S3_DATASETS_BUCKET_ARN_OUTPUT_KEY, S3DatasetsEncryptionKey.key);
        this.lambdaEnvVars = {
            STAGE,
            STACK_NAME,
            SSM_DOC_OUTPUT_KEY_SUFFIX,
            AMI_IDS_TO_SHARE,
            LAUNCH_CONSTRAINT_ROLE_OUTPUT_KEY,
            S3_ARTIFACT_BUCKET_ARN_OUTPUT_KEY,
            S3_DATASETS_BUCKET_ARN_OUTPUT_KEY,
            S3_DATASETS_BUCKET_NAME: datasetBucket.bucketName,
            ACCT_HANDLER_ARN_OUTPUT_KEY,
            API_HANDLER_ARN_OUTPUT_KEY,
            STATUS_HANDLER_ARN_OUTPUT_KEY,
            STATUS_HANDLER_ROLE_ARN_OUTPUT_KEY,
            SC_PORTFOLIO_NAME,
            ALLOWED_ORIGINS,
            COGNITO_DOMAIN: cognitoDomain,
            CLIENT_ID: clientId,
            CLIENT_SECRET: clientSecret,
            USER_POOL_ID: userPoolId,
            S3_DATASETS_ENCRYPTION_KEY_ARN_OUTPUT_KEY,
            S3_ARTIFACT_ENCRYPTION_KEY_ARN_OUTPUT_KEY,
            MAIN_ACCT_ALB_ARN_OUTPUT_KEY,
            MAIN_ACCT_ID,
            USER_AGENT_STRING
        };
        const S3ArtifactEncryptionKey = new workbench_core_infrastructure_1.WorkbenchEncryptionKeyWithRotation(this, S3_ARTIFACT_ENCRYPTION_KEY_ARN_OUTPUT_KEY);
        const artifactS3Bucket = this._createS3ArtifactsBuckets(S3_ARTIFACT_BUCKET_ARN_OUTPUT_KEY, S3ArtifactEncryptionKey.key);
        const lcRole = this._createLaunchConstraintIAMRole(LAUNCH_CONSTRAINT_ROLE_OUTPUT_KEY, artifactS3Bucket);
        const createAccountHandler = this._createAccountHandlerLambda(lcRole, artifactS3Bucket, AMI_IDS_TO_SHARE);
        const statusHandler = this._createStatusHandlerLambda(datasetBucket);
        const apiLambda = this._createAPILambda(datasetBucket, artifactS3Bucket, FIELDS_TO_MASK_WHEN_AUDITING, S3DatasetsEncryptionKey.key, S3ArtifactEncryptionKey.key);
        // Application DynamoDB Encryption Key
        const applicationDDBTableEncryptionKey = new workbench_core_infrastructure_1.WorkbenchEncryptionKeyWithRotation(this, `${this.stackName}-applicationDDBTableEncryptionKey`);
        // Create Application DynamoDB Table
        this._createApplicationDDBTable(applicationDDBTableEncryptionKey.key, apiLambda, statusHandler, createAccountHandler);
        // DynamicAuth DynamoDB Encryption Key
        const dynamicAuthDynamodbEncryptionKey = new workbench_core_infrastructure_1.WorkbenchEncryptionKeyWithRotation(this, `${this.stackName}-dynamicAuthDynamodbEncryptionKey`);
        // Create DynamicAuth DynamoDB Table
        const dynamicAuthTable = this._createDynamicAuthDDBTable(dynamicAuthDynamodbEncryptionKey.key, apiLambda, statusHandler, createAccountHandler);
        const revokedTokensDDBTableEncryptionKey = new workbench_core_infrastructure_1.WorkbenchEncryptionKeyWithRotation(this, `${this.stackName}-revokedTokensDDBTableEncryptionKey`);
        const revokedTokensDDBTable = this._createRevokedTokensDDBTable(revokedTokensDDBTableEncryptionKey.key, apiLambda, statusHandler, createAccountHandler);
        // Add DynamicAuth DynamoDB Table name to lambda environment variable
        lodash_1.default.map([apiLambda, statusHandler, createAccountHandler], (lambda) => {
            lambda.addEnvironment('DYNAMIC_AUTH_DDB_TABLE_NAME', dynamicAuthTable.tableName);
        });
        lodash_1.default.map([apiLambda, statusHandler, createAccountHandler], (lambda) => {
            lambda.addEnvironment('REVOKED_TOKENS_DDB_TABLE_NAME', revokedTokensDDBTable.tableName);
        });
        const apiGwUrl = this._createRestApi(apiLambda);
        [apiLambda, statusHandler, createAccountHandler].forEach((lambda) => {
            if (!lodash_1.default.isUndefined(lambda.node.findChild('ServiceRole').node.tryFindChild('DefaultPolicy')) &&
                !lodash_1.default.isUndefined(lambda.node.findChild('ServiceRole').node.findChild('DefaultPolicy').node.defaultChild)) {
                const cfnPolicy = lambda.node.findChild('ServiceRole').node.findChild('DefaultPolicy').node
                    .defaultChild;
                cfnPolicy.addMetadata('cfn_nag', {
                    // eslint-disable-next-line @typescript-eslint/naming-convention
                    rules_to_suppress: [
                        {
                            id: 'W76',
                            reason: 'Reviewed the autogenerated default policy. SPCM complexity greater then 25 is appropriate for this policy'
                        }
                    ]
                });
            }
        });
        const metadatanode = this.node.findChild('AWS679f53fac002430cb0da5b7982bd2287').node
            .defaultChild;
        metadatanode.addMetadata('cfn_nag', {
            // eslint-disable-next-line @typescript-eslint/naming-convention
            rules_to_suppress: [
                {
                    id: 'W58',
                    reason: "Lambda created by using AWSCustomResource and is managed by CDK internals. We don't want to jeopardize it's functionality"
                },
                {
                    id: 'W89',
                    reason: "Lambda created by using AWSCustomResource and is managed by CDK internals. We don't want to jeopardize it's functionality"
                },
                {
                    id: 'W92',
                    reason: "Lambda created by using AWSCustomResource and is managed by CDK internals. We don't want to jeopardize it's functionality"
                }
            ]
        });
        const workflow = new workflow_1.default(this);
        workflow.createSSMDocuments();
        const rswVpc = this._createVpc(VPC_ID, ALB_SUBNET_IDS, ECS_SUBNET_IDS);
        new aws_cdk_lib_1.CfnOutput(this, VPC_ID_OUTPUT_KEY, {
            value: rswVpc.vpc.vpcId
        });
        let childMetadataNode = rswVpc.node.findChild('VpcFlowLogGroup').node.defaultChild;
        childMetadataNode.addMetadata('cfn_nag', {
            // eslint-disable-next-line @typescript-eslint/naming-convention
            rules_to_suppress: [
                {
                    id: 'W84',
                    reason: 'TODO: CloudWatchLogs LogGroup should specify a KMS Key Id to encrypt the log data'
                }
            ]
        });
        if (!lodash_1.default.isUndefined(rswVpc.node.findChild('MainVPC').node.tryFindChild('PublicSubnet1')) &&
            !lodash_1.default.isUndefined(rswVpc.node.findChild('MainVPC').node.findChild('PublicSubnet1').node.defaultChild)) {
            childMetadataNode = rswVpc.node.findChild('MainVPC').node.findChild('PublicSubnet1').node
                .defaultChild;
            childMetadataNode.addMetadata('cfn_nag', {
                // eslint-disable-next-line @typescript-eslint/naming-convention
                rules_to_suppress: [
                    {
                        id: 'W33',
                        reason: 'TODO: EC2 Subnet should not have MapPublicIpOnLaunch set to true'
                    }
                ]
            });
        }
        if (!lodash_1.default.isUndefined(rswVpc.node.findChild('MainVPC').node.tryFindChild('PublicSubnet2')) &&
            !lodash_1.default.isUndefined(rswVpc.node.findChild('MainVPC').node.findChild('PublicSubnet2').node.defaultChild)) {
            childMetadataNode = rswVpc.node.findChild('MainVPC').node.findChild('PublicSubnet2').node
                .defaultChild;
            childMetadataNode.addMetadata('cfn_nag', {
                // eslint-disable-next-line @typescript-eslint/naming-convention
                rules_to_suppress: [
                    {
                        id: 'W33',
                        reason: 'TODO: EC2 Subnet should not have MapPublicIpOnLaunch set to true'
                    }
                ]
            });
        }
        if (!lodash_1.default.isUndefined(rswVpc.node.findChild('MainVPC').node.tryFindChild('PublicSubnet3')) &&
            !lodash_1.default.isUndefined(rswVpc.node.findChild('MainVPC').node.findChild('PublicSubnet3').node.defaultChild)) {
            childMetadataNode = rswVpc.node.findChild('MainVPC').node.findChild('PublicSubnet3').node
                .defaultChild;
            childMetadataNode.addMetadata('cfn_nag', {
                // eslint-disable-next-line @typescript-eslint/naming-convention
                rules_to_suppress: [
                    {
                        id: 'W33',
                        reason: 'TODO: EC2 Subnet should not have MapPublicIpOnLaunch set to true'
                    }
                ]
            });
        }
        new aws_cdk_lib_1.CfnOutput(this, ECS_SUBNET_IDS_OUTPUT_KEY, {
            value: ((_b = (_a = rswVpc.ecsSubnetSelection.subnets) === null || _a === void 0 ? void 0 : _a.map((subnet) => subnet.subnetId)) !== null && _b !== void 0 ? _b : []).join(',')
        });
        new aws_cdk_lib_1.CfnOutput(this, ECS_SUBNET_AZS_OUTPUT_KEY, {
            value: ((_d = (_c = rswVpc.vpc.availabilityZones) === null || _c === void 0 ? void 0 : _c.map((az) => az)) !== null && _d !== void 0 ? _d : []).join(',')
        });
        const hostedZoneId = this._isSolutionsBuild
            ? new aws_cdk_lib_1.CfnParameter(this, 'HostedZoneId', {
                type: 'String',
                default: HOSTED_ZONE_ID,
                description: 'The Route 53 Hosted Zone ID linked to your custom domain name.'
            }).valueAsString
            : HOSTED_ZONE_ID;
        const domainName = this._isSolutionsBuild
            ? new aws_cdk_lib_1.CfnParameter(this, 'DomainName', {
                type: 'String',
                default: DOMAIN_NAME,
                description: 'A custom domain name that you own. TLS certificates will be generated at the time of application deployment.'
            }).valueAsString
            : DOMAIN_NAME;
        this._createLoadBalancer(rswVpc, apiGwUrl, domainName, hostedZoneId, ALB_INTERNET_FACING, this._accessLogsBucket);
    }
    _createVpc(vpcId, albSubnetIds, ecsSubnetIds) {
        const rswVpc = new RSWVpc_1.RSWVpc(this, 'RSWVpc', {
            vpcId,
            albSubnetIds,
            ecsSubnetIds
        });
        return rswVpc;
    }
    _createLoadBalancer(rswVpc, apiGwUrl, domainName, hostedZoneId, internetFacing, accessLogsBucket) {
        const alb = new RSWApplicationLoadBalancer_1.RSWApplicationLoadBalancer(this, 'RSWApplicationLoadBalancer', {
            vpc: rswVpc.vpc,
            subnets: rswVpc.albSubnetSelection,
            internetFacing,
            accessLogsBucket
        });
        const zone = aws_route53_1.HostedZone.fromHostedZoneAttributes(this, 'HostedZone', {
            zoneName: domainName,
            hostedZoneId: hostedZoneId
        });
        // Add a Route 53 alias with the Load Balancer as the target
        new aws_route53_1.ARecord(this, 'AliasRecord', {
            zone,
            target: aws_route53_1.RecordTarget.fromAlias(new aws_route53_targets_1.LoadBalancerTarget(alb.applicationLoadBalancer))
        });
        const proxyLambda = new aws_lambda_1.Function(this, 'LambdaProxy', {
            handler: 'proxyHandlerLambda.handler',
            code: aws_lambda_1.Code.fromAsset((0, path_1.join)(__dirname, '../../build/proxyHandler')),
            runtime: aws_lambda_1.Runtime.NODEJS_18_X,
            reservedConcurrentExecutions: this._API_LIMIT,
            environment: Object.assign(Object.assign({}, this.lambdaEnvVars), { API_GW_URL: apiGwUrl }),
            timeout: aws_cdk_lib_1.Duration.seconds(60),
            memorySize: 256
        });
        const childMetadataNode = proxyLambda.node.defaultChild;
        childMetadataNode.addMetadata('cfn_nag', {
            // eslint-disable-next-line @typescript-eslint/naming-convention
            rules_to_suppress: [
                {
                    id: 'W58',
                    reason: 'TODO: Lambda functions require permission to write CloudWatch Logs'
                },
                {
                    id: 'W89',
                    reason: 'TODO: Lambda functions should be deployed inside a VPC'
                },
                {
                    id: 'W92',
                    reason: 'TODO: Lambda functions should define ReservedConcurrentExecutions to reserve simultaneous executions'
                }
            ]
        });
        new aws_lambda_1.Alias(this, 'LiveProxyLambdaAlias', {
            aliasName: 'live',
            version: proxyLambda.currentVersion,
            provisionedConcurrentExecutions: 1
        });
        // Add a listener on port 443 for and use the certificate for HTTPS
        const certificate = new aws_certificatemanager_1.Certificate(this, 'SWBCertificate', {
            domainName: domainName,
            validation: aws_certificatemanager_1.CertificateValidation.fromDns(zone)
        });
        const httpsListener = alb.applicationLoadBalancer.addListener('HTTPSListener', {
            port: 443,
            certificates: [certificate],
            sslPolicy: aws_elasticloadbalancingv2_1.SslPolicy.RECOMMENDED_TLS
        });
        const targetGroup = new aws_elasticloadbalancingv2_1.ApplicationTargetGroup(this, 'proxyLambdaTargetGroup', {
            targetType: aws_elasticloadbalancingv2_1.TargetType.LAMBDA,
            targets: [new aws_elasticloadbalancingv2_targets_1.LambdaTarget(proxyLambda)]
        });
        targetGroup.setAttribute('lambda.multi_value_headers.enabled', 'true');
        httpsListener.addTargetGroups('addProxyLambdaTargetGroup', {
            priority: 1,
            conditions: [aws_elasticloadbalancingv2_1.ListenerCondition.pathPatterns(['/api/*'])],
            targetGroups: [targetGroup]
        });
        httpsListener.addTargetGroups('addDefaultTargetGroup', {
            targetGroups: [targetGroup]
        });
        new aws_cdk_lib_1.CfnOutput(this, this.lambdaEnvVars.MAIN_ACCT_ALB_ARN_OUTPUT_KEY, {
            value: alb.applicationLoadBalancer.loadBalancerArn
        });
        new aws_cdk_lib_1.CfnOutput(this, this._swbDomainNameOutputKey, {
            value: domainName
        });
        new aws_cdk_lib_1.CfnOutput(this, this._mainAccountLoadBalancerListenerArnOutputKey, {
            value: alb.applicationLoadBalancer.listeners[0].listenerArn
        });
        new aws_cdk_lib_1.CfnOutput(this, 'apiUrlOutput', {
            value: `https://${domainName}/api/`
        });
    }
    _createInitialOutputs(accountId, awsRegion, awsRegionName) {
        new aws_cdk_lib_1.CfnOutput(this, 'accountId', {
            value: accountId
        });
        new aws_cdk_lib_1.CfnOutput(this, 'awsRegion', {
            value: awsRegion
        });
        new aws_cdk_lib_1.CfnOutput(this, 'awsRegionShortName', {
            value: awsRegionName
        });
    }
    _createLaunchConstraintIAMRole(launchConstraintRoleNameOutput, artifactS3Bucket) {
        const commonScManagement = new aws_iam_1.PolicyDocument({
            statements: [
                new aws_iam_1.PolicyStatement({
                    actions: [
                        'iam:GetRole',
                        'iam:GetRolePolicy',
                        'iam:*TagRole*',
                        'iam:PassRole',
                        'iam:DeleteRole',
                        'iam:PutRolePolicy',
                        'iam:DeleteRolePolicy',
                        'iam:DetachRolePolicy',
                        'iam:AttachRolePolicy',
                        'iam:CreateRole'
                    ],
                    resources: [
                        'arn:aws:iam::*:role/analysis-*',
                        'arn:aws:iam::*:role/SC-*-ServiceRole-*',
                        'arn:aws:iam::*:role/*-sagemaker-notebook-role'
                    ]
                }),
                new aws_iam_1.PolicyStatement({
                    actions: [
                        'iam:AddRoleToInstanceProfile',
                        'iam:CreateInstanceProfile',
                        'iam:GetInstanceProfile',
                        'iam:DeleteInstanceProfile',
                        'iam:RemoveRoleFromInstanceProfile'
                    ],
                    resources: [
                        'arn:aws:iam::*:instance-profile/analysis-*',
                        'arn:aws:iam::*:instance-profile/SC-*-InstanceProfile-*'
                    ]
                }),
                new aws_iam_1.PolicyStatement({
                    actions: ['iam:GetPolicy', 'iam:CreatePolicy', 'iam:ListPolicyVersions', 'iam:DeletePolicy'],
                    resources: ['arn:aws:iam::*:policy/*-permission-boundary']
                }),
                new aws_iam_1.PolicyStatement({
                    actions: [
                        'cloudformation:CreateStack',
                        'cloudformation:DescribeStacks',
                        'cloudformation:DescribeStackEvents',
                        'cloudformation:DeleteStack'
                    ],
                    resources: ['arn:aws:cloudformation:*:*:stack/SC-*/*']
                }),
                new aws_iam_1.PolicyStatement({
                    actions: ['cloudformation:GetTemplateSummary'],
                    resources: ['*'] // Needed to update SC Product. Must be wildcard to cover all possible templates the product can deploy in different accounts, which we don't know at time of creation
                }),
                new aws_iam_1.PolicyStatement({
                    actions: ['s3:GetObject', 's3:GetObjectVersion'],
                    resources: ['arn:aws:s3:::sc-*']
                }),
                new aws_iam_1.PolicyStatement({
                    actions: [
                        'ec2:AuthorizeSecurityGroupIngress',
                        'ec2:AuthorizeSecurityGroupEgress',
                        'ec2:RevokeSecurityGroupEgress',
                        'ec2:CreateSecurityGroup',
                        'ec2:DeleteSecurityGroup',
                        'ec2:CreateTags',
                        'ec2:DescribeTags',
                        'ec2:DescribeKeyPairs',
                        'ec2:DescribeSecurityGroups',
                        'ec2:DescribeSubnets',
                        'ec2:DescribeVpcs'
                    ],
                    resources: ['*'] // DescribeTags, DescrbeKeyPairs, DescribeSecurityGroups, DescribeSubnets, DescribeVpcs do not allow for resource-based permissions
                }),
                new aws_iam_1.PolicyStatement({
                    actions: ['kms:CreateGrant'],
                    resources: ['*'] // Must be wildcard because we do not know the keys at the time of creation of this policy
                })
            ]
        });
        const sagemakerNotebookPolicy = new aws_iam_1.PolicyDocument({
            statements: [
                new aws_iam_1.PolicyStatement({
                    actions: [
                        'sagemaker:DescribeNotebookInstanceLifecycleConfig',
                        'sagemaker:CreateNotebookInstanceLifecycleConfig',
                        'sagemaker:DeleteNotebookInstanceLifecycleConfig'
                    ],
                    resources: [
                        'arn:aws:sagemaker:*:*:notebook-instance-lifecycle-config/?asic?otebook?nstance?ifecycle?onfig-*'
                    ]
                }),
                new aws_iam_1.PolicyStatement({
                    actions: [
                        'sagemaker:DescribeNotebookInstance',
                        'sagemaker:CreateNotebookInstance',
                        'sagemaker:AddTags',
                        'sagemaker:StopNotebookInstance',
                        'sagemaker:StopNotebookInstance',
                        'sagemaker:DeleteNotebookInstance'
                    ],
                    resources: ['arn:aws:sagemaker:*:*:notebook-instance/?asic?otebook?nstance-*']
                }),
                new aws_iam_1.PolicyStatement({
                    actions: ['s3:GetObject', 's3:GetObjectVersion'],
                    resources: [`${artifactS3Bucket.bucketArn}/*`]
                }),
                new aws_iam_1.PolicyStatement({
                    actions: [
                        'cloudformation:CreateStack',
                        'cloudformation:DeleteStack',
                        'cloudformation:DescribeStackEvents',
                        'cloudformation:DescribeStacks',
                        'cloudformation:GetTemplateSummary',
                        'cloudformation:SetStackPolicy',
                        'cloudformation:ValidateTemplate',
                        'cloudformation:UpdateStack'
                    ],
                    resources: ['arn:aws:cloudformation:*:*:stack/SC-*']
                }),
                new aws_iam_1.PolicyStatement({
                    actions: [
                        'ec2:DescribeNetworkInterfaces',
                        'ec2:CreateNetworkInterface',
                        'ec2:DeleteNetworkInterface'
                    ],
                    resources: ['*'] // DescribeNetworkInterfaces does not allow resource-level permissions
                })
            ]
        });
        const iamRole = new aws_iam_1.Role(this, 'LaunchConstraint', {
            assumedBy: new aws_iam_1.ServicePrincipal('servicecatalog.amazonaws.com'),
            description: 'Launch constraint role for Service Catalog products',
            inlinePolicies: {
                sagemakerNotebookLaunchPermissions: sagemakerNotebookPolicy,
                commonScManagement
            }
        });
        new aws_cdk_lib_1.CfnOutput(this, launchConstraintRoleNameOutput, {
            value: iamRole.roleName
        });
        const metadatanode = iamRole.node.defaultChild;
        metadatanode.addMetadata('cfn_nag', {
            // eslint-disable-next-line @typescript-eslint/naming-convention
            rules_to_suppress: [
                {
                    id: 'W28',
                    reason: 'TODO:triage Resource found with an explicit name, this disallows updates that require replacement of this resource'
                },
                {
                    id: 'W11',
                    reason: 'TODO:triage IAM role should not allow * resource on its permissions policy'
                }
            ]
        });
        return iamRole;
    }
    /**
     * Create bucket for S3 access logs.
     * Note this bucket does not have sigv4/https policies because these restrict access log delivery.
     * Note this bucket uses S3 Managed encryption as a requirement for access logging.
     * @param bucketNameOutput - Name of Access Logs Bucket.
     * @returns S3Bucket
     */
    _createAccessLogsBucket(bucketNameOutput) {
        const s3Bucket = new aws_s3_1.Bucket(this, 's3-access-logs', {
            blockPublicAccess: aws_s3_1.BlockPublicAccess.BLOCK_ALL,
            encryption: aws_s3_1.BucketEncryption.S3_MANAGED,
            versioned: true,
            objectOwnership: aws_s3_1.ObjectOwnership.OBJECT_WRITER
        });
        s3Bucket.addToResourcePolicy(new aws_iam_1.PolicyStatement({
            effect: aws_iam_1.Effect.ALLOW,
            principals: [new aws_iam_1.ServicePrincipal('logging.s3.amazonaws.com')],
            actions: ['s3:PutObject'],
            resources: [`${s3Bucket.bucketArn}/${this._s3AccessLogsPrefix}*`],
            conditions: {
                StringEquals: {
                    'aws:SourceAccount': this._isSolutionsBuild ? aws_cdk_lib_1.Aws.ACCOUNT_ID : process.env.CDK_DEFAULT_ACCOUNT
                }
            }
        }));
        s3Bucket.addToResourcePolicy(new aws_iam_1.PolicyStatement({
            sid: 'Deny requests that do not use TLS/HTTPS',
            effect: aws_iam_1.Effect.DENY,
            principals: [new aws_iam_1.AnyPrincipal()],
            actions: ['s3:*'],
            resources: [s3Bucket.bucketArn, s3Bucket.arnForObjects('*')],
            conditions: {
                Bool: {
                    'aws:SecureTransport': 'false'
                }
            }
        }));
        new aws_cdk_lib_1.CfnOutput(this, bucketNameOutput, {
            value: s3Bucket.bucketName,
            exportName: bucketNameOutput
        });
        const metadatanode = s3Bucket.node.defaultChild;
        metadatanode.addMetadata('cfn_nag', {
            // eslint-disable-next-line @typescript-eslint/naming-convention
            rules_to_suppress: [
                {
                    id: 'W35',
                    reason: 'TODO:triage S3 Bucket should have access logging configured'
                }
            ]
        });
        return s3Bucket;
    }
    _addS3TLSSigV4BucketPolicy(s3Bucket) {
        s3Bucket.addToResourcePolicy(new aws_iam_1.PolicyStatement({
            sid: 'Deny requests that do not use TLS/HTTPS',
            effect: aws_iam_1.Effect.DENY,
            principals: [new aws_iam_1.AnyPrincipal()],
            actions: ['s3:*'],
            resources: [s3Bucket.bucketArn, s3Bucket.arnForObjects('*')],
            conditions: {
                Bool: {
                    'aws:SecureTransport': 'false'
                }
            }
        }));
        s3Bucket.addToResourcePolicy(new aws_iam_1.PolicyStatement({
            sid: 'Deny requests that do not use SigV4',
            effect: aws_iam_1.Effect.DENY,
            principals: [new aws_iam_1.AnyPrincipal()],
            actions: ['s3:*'],
            resources: [s3Bucket.arnForObjects('*')],
            conditions: {
                StringNotEquals: {
                    's3:signatureversion': 'AWS4-HMAC-SHA256'
                }
            }
        }));
    }
    _addAccessPointDelegationStatement(s3Bucket) {
        var _a;
        s3Bucket.addToResourcePolicy(new aws_iam_1.PolicyStatement({
            effect: aws_iam_1.Effect.ALLOW,
            principals: [new aws_iam_1.AnyPrincipal()],
            actions: ['s3:*'],
            resources: [s3Bucket.bucketArn, s3Bucket.arnForObjects('*')],
            conditions: {
                StringEquals: {
                    's3:DataAccessPointAccount': this.account
                }
            }
        }));
        const metadatanode = (_a = s3Bucket.policy) === null || _a === void 0 ? void 0 : _a.node.defaultChild;
        metadatanode.addMetadata('cfn_nag', {
            // eslint-disable-next-line @typescript-eslint/naming-convention
            rules_to_suppress: [
                {
                    id: 'F15',
                    reason: 'TODO:triage S3 Bucket policy should not allow * action'
                },
                {
                    id: 'F16',
                    reason: 'TODO:triage S3 Bucket policy should not allow * principal'
                }
            ]
        });
    }
    _createS3ArtifactsBuckets(s3ArtifactName, s3ArtifactsEncryptionKey) {
        return this._createSecureS3Bucket('s3-artifacts', s3ArtifactName, s3ArtifactsEncryptionKey);
    }
    _createS3DatasetsBuckets(s3DatasetsName, s3DatasetsEncryptionKey) {
        const bucket = this._createSecureS3Bucket('s3-datasets', s3DatasetsName, s3DatasetsEncryptionKey);
        this._addAccessPointDelegationStatement(bucket);
        const metadatanode = bucket.node.defaultChild;
        metadatanode.addMetadata('cfn_nag', {
            // eslint-disable-next-line @typescript-eslint/naming-convention
            rules_to_suppress: [
                {
                    id: 'W35',
                    reason: 'TODO:triage S3 Bucket should have access logging configured'
                }
            ]
        });
        new aws_cdk_lib_1.CfnOutput(this, 'DataSetsBucketName', {
            value: bucket.bucketName
        });
        return bucket;
    }
    _createSecureS3Bucket(s3BucketId, s3OutputId, encryptionKey) {
        const s3Bucket = new aws_s3_1.Bucket(this, s3BucketId, {
            blockPublicAccess: aws_s3_1.BlockPublicAccess.BLOCK_ALL,
            serverAccessLogsBucket: this._accessLogsBucket,
            serverAccessLogsPrefix: this._s3AccessLogsPrefix,
            encryption: aws_s3_1.BucketEncryption.KMS,
            encryptionKey: encryptionKey,
            versioned: true,
            objectOwnership: aws_s3_1.ObjectOwnership.OBJECT_WRITER
        });
        this._addS3TLSSigV4BucketPolicy(s3Bucket);
        new aws_cdk_lib_1.CfnOutput(this, s3OutputId, {
            value: s3Bucket.bucketArn
        });
        return s3Bucket;
    }
    _createStatusHandlerLambda(datasetBucket) {
        var _a;
        const statusHandlerLambda = new aws_lambda_1.Function(this, 'statusHandlerLambda', {
            code: aws_lambda_1.Code.fromAsset((0, path_1.join)(__dirname, '../../build/statusHandler')),
            handler: 'statusHandlerLambda.handler',
            runtime: aws_lambda_1.Runtime.NODEJS_18_X,
            reservedConcurrentExecutions: 100,
            environment: this.lambdaEnvVars,
            timeout: aws_cdk_lib_1.Duration.seconds(60),
            memorySize: 256
        });
        statusHandlerLambda.addPermission('RouteHostEvents', {
            action: 'lambda:InvokeFunction',
            principal: new aws_iam_1.ServicePrincipal('events.amazonaws.com')
        });
        (_a = statusHandlerLambda.role) === null || _a === void 0 ? void 0 : _a.attachInlinePolicy(new aws_iam_1.Policy(this, 'statusHandlerLambdaPolicy', {
            statements: [
                new aws_iam_1.PolicyStatement({
                    actions: ['sts:AssumeRole'],
                    resources: ['arn:aws:iam::*:role/*env-mgmt'],
                    sid: 'AssumeRole'
                }),
                new aws_iam_1.PolicyStatement({
                    sid: 'datasetS3Access',
                    actions: [
                        's3:GetObject',
                        's3:GetObjectVersion',
                        's3:GetObjectTagging',
                        's3:GetObjectVersionTagging',
                        's3:AbortMultipartUpload',
                        's3:ListMultipartUploadParts',
                        's3:PutObject',
                        's3:PutObjectAcl',
                        's3:PutObjectVersionAcl',
                        's3:PutObjectTagging',
                        's3:PutObjectVersionTagging',
                        's3:ListBucket',
                        's3:ListBucketVersions',
                        's3:PutAccessPointPolicy',
                        's3:GetAccessPointPolicy'
                    ],
                    resources: [
                        datasetBucket.bucketArn,
                        `${datasetBucket.bucketArn}/*`,
                        `arn:aws:s3:${this.region}:${this.account}:accesspoint/*`
                    ]
                })
            ]
        }));
        new aws_cdk_lib_1.CfnOutput(this, 'StatusHandlerLambdaRoleOutput', {
            value: statusHandlerLambda.role.roleArn
        });
        new aws_cdk_lib_1.CfnOutput(this, 'StatusHandlerLambdaArnOutput', {
            value: statusHandlerLambda.functionArn
        });
        const metadatanode = statusHandlerLambda.node.defaultChild;
        metadatanode.addMetadata('cfn_nag', {
            // eslint-disable-next-line @typescript-eslint/naming-convention
            rules_to_suppress: [
                {
                    id: 'W58',
                    reason: 'TODO:triage (statusHandlerLambda) Lambda functions require permission to write CloudWatch Logs'
                },
                {
                    id: 'W89',
                    reason: 'TODO:triage (statusHandlerLambda) Lambda functions should be deployed inside a VPC'
                },
                {
                    id: 'W92',
                    reason: 'TODO:triage (statusHandlerLambda) Lambda functions should define ReservedConcurrentExecutions to reserve simultaneous executions'
                }
            ]
        });
        return statusHandlerLambda;
    }
    _createAccountHandlerLambda(launchConstraintRole, artifactS3Bucket, amiIdsToShare) {
        var _a;
        const lambda = new aws_lambda_1.Function(this, 'accountHandlerLambda', {
            code: aws_lambda_1.Code.fromAsset((0, path_1.join)(__dirname, '../../build/accountHandler')),
            handler: 'accountHandlerLambda.handler',
            runtime: aws_lambda_1.Runtime.NODEJS_18_X,
            reservedConcurrentExecutions: 1,
            environment: this.lambdaEnvVars,
            memorySize: 256,
            timeout: aws_cdk_lib_1.Duration.minutes(4)
        });
        const amiIdsList = JSON.parse(amiIdsToShare);
        const lambdaPolicy = new aws_iam_1.Policy(this, 'accountHandlerPolicy', {
            statements: [
                new aws_iam_1.PolicyStatement({
                    sid: 'CreatePortfolioShare',
                    actions: ['servicecatalog:CreatePortfolioShare'],
                    resources: [`arn:aws:catalog:${this.region}:${this.account}:portfolio/*`]
                }),
                // Allows accountHandler to get portfolioId based on portfolioName
                // '*/*' is the minimum permission required because ListPortfolios API does not allow filtering
                new aws_iam_1.PolicyStatement({
                    sid: 'ListPortfolios',
                    actions: ['servicecatalog:ListPortfolios'],
                    resources: [`arn:aws:servicecatalog:${this.region}:${this.account}:*/*`]
                }),
                new aws_iam_1.PolicyStatement({
                    sid: 'SearchProductsAsAdmin',
                    actions: ['servicecatalog:SearchProductsAsAdmin'],
                    resources: [`arn:aws:catalog:${this.region}:${this.account}:portfolio/*`]
                }),
                new aws_iam_1.PolicyStatement({
                    sid: 'ListProvisioningArtifacts',
                    actions: ['servicecatalog:ListProvisioningArtifacts'],
                    resources: [`arn:aws:catalog:${this.region}:${this.account}:product/*`]
                }),
                new aws_iam_1.PolicyStatement({
                    sid: 'DescribeProvisioningArtifact',
                    actions: ['servicecatalog:DescribeProvisioningArtifact'],
                    resources: [`arn:aws:catalog:${this.region}:${this.account}:product/*`]
                }),
                new aws_iam_1.PolicyStatement({
                    sid: 'GetObject',
                    actions: ['s3:GetObject', 's3:GetObjectVersion'],
                    resources: [`arn:aws:s3:::${this.lambdaEnvVars.STACK_NAME}*`]
                }),
                new aws_iam_1.PolicyStatement({
                    actions: ['kms:Decrypt', 'kms:GenerateDataKey', 'kms:GetKeyPolicy', 'kms:PutKeyPolicy'],
                    resources: [`arn:aws:kms:${this.region}:${this.account}:key/*`],
                    sid: 'KMSAccess'
                }),
                new aws_iam_1.PolicyStatement({
                    sid: 'AssumeRole',
                    actions: ['sts:AssumeRole'],
                    // Confirm the suffix `hosting-account-role` matches with the suffix in `onboard-account.cfn.yaml`
                    resources: ['arn:aws:iam::*:role/*hosting-account-role']
                }),
                new aws_iam_1.PolicyStatement({
                    sid: 'GetLaunchConstraint',
                    actions: [
                        'iam:GetRole',
                        'iam:GetRolePolicy',
                        'iam:ListRolePolicies',
                        'iam:ListAttachedRolePolicies'
                    ],
                    resources: [launchConstraintRole.roleArn]
                }),
                new aws_iam_1.PolicyStatement({
                    sid: 'ShareSSM',
                    actions: ['ssm:ModifyDocumentPermission'],
                    resources: [
                        this.formatArn({ service: 'ssm', resource: 'document', resourceName: `${this.stackName}-*` })
                    ]
                }),
                new aws_iam_1.PolicyStatement({
                    sid: 'Cloudformation',
                    actions: ['cloudformation:DescribeStacks'],
                    resources: [this.stackId]
                }),
                new aws_iam_1.PolicyStatement({
                    sid: 'S3Bucket',
                    actions: ['s3:GetObject', 's3:GetObjectVersion'],
                    resources: [`${artifactS3Bucket.bucketArn}/*`]
                })
            ]
        });
        const lambdaPolicyMetaDataNode = lambdaPolicy.node.defaultChild;
        lambdaPolicyMetaDataNode.addMetadata('cfn_nag', {
            // eslint-disable-next-line @typescript-eslint/naming-convention
            rules_to_suppress: [
                {
                    id: 'W76',
                    reason: 'TODO:triage (AccountHandlerLambdaPolicy) SPCM for IAM policy document is higher than 25'
                }
            ]
        });
        if (!lodash_1.default.isEmpty(amiIdsList)) {
            lambdaPolicy.addStatements(new aws_iam_1.PolicyStatement({
                sid: 'ShareAmi',
                actions: ['ec2:ModifyImageAttribute'],
                resources: amiIdsList
            }));
        }
        (_a = lambda.role) === null || _a === void 0 ? void 0 : _a.attachInlinePolicy(lambdaPolicy);
        new aws_cdk_lib_1.CfnOutput(this, 'AccountHandlerLambdaRoleOutput', {
            value: lambda.role.roleArn
        });
        // Run lambda function every 5 minutes
        const eventRule = new aws_events_1.Rule(this, 'scheduleRule', {
            schedule: aws_events_1.Schedule.cron({ minute: '0/5' })
        });
        eventRule.addTarget(new targets.LambdaFunction(lambda));
        const lambdaMetadataNode = lambda.node.defaultChild;
        lambdaMetadataNode.addMetadata('cfn_nag', {
            // eslint-disable-next-line @typescript-eslint/naming-convention
            rules_to_suppress: [
                {
                    id: 'W58',
                    reason: 'TODO:triage (AccountHandlerLambda) Lambda functions require permission to write CloudWatch Logs'
                },
                {
                    id: 'W89',
                    reason: 'TODO:triage (AccountHandlerLambda) Lambda functions should be deployed inside a VPC'
                },
                {
                    id: 'W92',
                    reason: 'TODO:triage (AccountHandlerLambda) Lambda functions should define ReservedConcurrentExecutions to reserve simultaneous executions'
                }
            ]
        });
        return lambda;
    }
    _createAPILambda(datasetBucket, artifactS3Bucket, fieldsToMaskWhenAuditing, datasetsEncryptionKey, artifactEncryptionKey) {
        var _a;
        const apiLambda = new aws_lambda_1.Function(this, 'apiLambda', {
            code: aws_lambda_1.Code.fromAsset((0, path_1.join)(__dirname, '../../build/backendAPI')),
            handler: 'backendAPILambda.handler',
            runtime: aws_lambda_1.Runtime.NODEJS_18_X,
            reservedConcurrentExecutions: this._API_LIMIT,
            environment: Object.assign(Object.assign({}, this.lambdaEnvVars), { FIELDS_TO_MASK_WHEN_AUDITING: JSON.stringify(fieldsToMaskWhenAuditing) }),
            timeout: aws_cdk_lib_1.Duration.seconds(29),
            memorySize: 832
        });
        (_a = apiLambda.role) === null || _a === void 0 ? void 0 : _a.attachInlinePolicy(new aws_iam_1.Policy(this, 'apiLambdaPolicy', {
            statements: [
                new aws_iam_1.PolicyStatement({
                    actions: ['events:DescribeRule', 'events:Put*'],
                    resources: [`arn:aws:events:${this.region}:${this.account}:event-bus/default`],
                    sid: 'EventBridgeAccess'
                }),
                new aws_iam_1.PolicyStatement({
                    actions: ['cloudformation:DescribeStacks', 'cloudformation:DescribeStackEvents'],
                    resources: [`arn:aws:cloudformation:${this.region}:*:stack/${this.stackName}*`],
                    sid: 'CfnAccess'
                }),
                new aws_iam_1.PolicyStatement({
                    actions: ['servicecatalog:ListLaunchPaths'],
                    resources: [`arn:aws:catalog:${this.region}:*:product/*`],
                    sid: 'ScAccess'
                }),
                new aws_iam_1.PolicyStatement({
                    actions: ['cognito-idp:DescribeUserPoolClient'],
                    resources: [`arn:aws:cognito-idp:${this.region}:${this.account}:userpool/*`],
                    sid: 'CognitoAccess'
                }),
                new aws_iam_1.PolicyStatement({
                    actions: ['sts:AssumeRole'],
                    resources: ['arn:aws:iam::*:role/*env-mgmt', 'arn:aws:iam::*:role/*hosting-account-role'],
                    sid: 'AssumeRole'
                }),
                new aws_iam_1.PolicyStatement({
                    actions: [
                        'kms:GetKeyPolicy',
                        'kms:PutKeyPolicy',
                        'kms:GenerateDataKey',
                        'kms:Decrypt',
                        'kms:Encrypt',
                        'kms:DescribeKey',
                        'kms:ReEncrypt*'
                    ],
                    resources: [datasetsEncryptionKey.keyArn, artifactEncryptionKey.keyArn],
                    sid: 'KMSAccess'
                }),
                new aws_iam_1.PolicyStatement({
                    actions: ['events:DescribeRule', 'events:Put*', 'events:RemovePermission'],
                    resources: ['*'],
                    sid: 'EventbridgeAccess'
                }),
                new aws_iam_1.PolicyStatement({
                    actions: ['logs:CreateLogGroup', 'logs:CreateLogStream', 'logs:PutLogEvents'],
                    resources: ['*']
                }),
                new aws_iam_1.PolicyStatement({
                    sid: 'datasetS3Access',
                    actions: [
                        's3:GetObject',
                        's3:GetObjectVersion',
                        's3:GetObjectTagging',
                        's3:GetObjectVersionTagging',
                        's3:AbortMultipartUpload',
                        's3:ListMultipartUploadParts',
                        's3:GetBucketPolicy',
                        's3:PutBucketPolicy',
                        's3:PutObject',
                        's3:PutObjectAcl',
                        's3:PutObjectVersionAcl',
                        's3:PutObjectTagging',
                        's3:PutObjectVersionTagging',
                        's3:ListBucket',
                        's3:ListBucketVersions',
                        's3:PutAccessPointPolicy',
                        's3:GetAccessPointPolicy',
                        's3:CreateAccessPoint',
                        's3:DeleteAccessPoint'
                    ],
                    resources: [
                        datasetBucket.bucketArn,
                        `${datasetBucket.bucketArn}/*`,
                        `arn:aws:s3:${this.region}:${this.account}:accesspoint/*`
                    ]
                }),
                new aws_iam_1.PolicyStatement({
                    sid: 'environmentBootstrapS3Access',
                    actions: ['s3:GetObject', 's3:GetObjectVersion', 's3:GetBucketPolicy', 's3:PutBucketPolicy'],
                    resources: [artifactS3Bucket.bucketArn, `${artifactS3Bucket.bucketArn}/*`]
                }),
                new aws_iam_1.PolicyStatement({
                    sid: 'cognitoAccess',
                    actions: [
                        'cognito-idp:AdminAddUserToGroup',
                        'cognito-idp:AdminCreateUser',
                        'cognito-idp:AdminDeleteUser',
                        'cognito-idp:AdminGetUser',
                        'cognito-idp:AdminEnableUser',
                        'cognito-idp:AdminDisableUser',
                        'cognito-idp:AdminListGroupsForUser',
                        'cognito-idp:AdminRemoveUserFromGroup',
                        'cognito-idp:AdminUpdateUserAttributes',
                        'cognito-idp:CreateGroup',
                        'cognito-idp:DeleteGroup',
                        'cognito-idp:ListGroups',
                        'cognito-idp:ListUsers',
                        'cognito-idp:ListUsersInGroup'
                    ],
                    resources: ['*']
                })
            ]
        }));
        new aws_cdk_lib_1.CfnOutput(this, 'ApiLambdaRoleOutput', {
            value: apiLambda.role.roleArn
        });
        let childMetadataNode = apiLambda.node.defaultChild;
        childMetadataNode.addMetadata('cfn_nag', {
            // eslint-disable-next-line @typescript-eslint/naming-convention
            rules_to_suppress: [
                {
                    id: 'W58',
                    reason: 'TODO:triage (ApiLambda) Lambda functions require permission to write CloudWatch Logs'
                },
                {
                    id: 'W89',
                    reason: 'TODO:triage (ApiLambda) Lambda functions should be deployed inside a VPC'
                },
                {
                    id: 'W92',
                    reason: 'TODO:triage (ApiLambda) Lambda functions should define ReservedConcurrentExecutions to reserve simultaneous executions'
                }
            ]
        });
        childMetadataNode = this.node.findChild('apiLambdaPolicy').node.defaultChild;
        childMetadataNode.addMetadata('cfn_nag', {
            // eslint-disable-next-line @typescript-eslint/naming-convention
            rules_to_suppress: [
                {
                    id: 'W12',
                    reason: 'TODO: IAM policy should not allow * resource'
                },
                {
                    id: 'W76',
                    reason: 'TODO: SPCM for IAM policy document is higher than 25'
                }
            ]
        });
        return apiLambda;
    }
    // API Gateway
    _createRestApi(apiLambda) {
        const logGroup = new aws_logs_1.LogGroup(this, 'APIGatewayAccessLogs', { retention: aws_logs_1.RetentionDays.TEN_YEARS });
        const metadatanode = logGroup.node.defaultChild;
        metadatanode.addMetadata('cfn_nag', {
            // eslint-disable-next-line @typescript-eslint/naming-convention
            rules_to_suppress: [
                {
                    id: 'W84',
                    reason: 'TODO:triage CloudWatchLogs LogGroup should specify a KMS Key Id to encrypt the log data'
                }
            ]
        });
        const API = new aws_apigateway_1.RestApi(this, `API-Gateway API`, {
            restApiName: this.stackName,
            description: 'SWB API',
            deployOptions: {
                stageName: 'dev',
                accessLogDestination: new aws_apigateway_1.LogGroupLogDestination(logGroup),
                throttlingRateLimit: this._API_LIMIT,
                throttlingBurstLimit: this._API_LIMIT,
                accessLogFormat: aws_apigateway_1.AccessLogFormat.custom(JSON.stringify({
                    stage: '$context.stage',
                    requestTime: '$context.requestTime',
                    requestId: '$context.requestId',
                    integrationRequestId: '$context.integration.requestId',
                    status: '$context.status',
                    apiId: '$context.apiId',
                    resourcePath: '$context.resourcePath',
                    path: '$context.path',
                    resourceId: '$context.resourceId',
                    httpMethod: '$context.httpMethod',
                    sourceIp: '$context.identity.sourceIp',
                    userAgent: '$context.identity.userAgent'
                }))
            },
            defaultCorsPreflightOptions: {
                allowHeaders: ['Content-Type', 'X-Amz-Date', 'Authorization', 'X-Api-Key', 'CSRF-Token'],
                allowMethods: ['OPTIONS', 'GET', 'POST', 'PUT', 'PATCH', 'DELETE'],
                allowCredentials: true,
                allowOrigins: JSON.parse(this.lambdaEnvVars.ALLOWED_ORIGINS || '[]')
            }
        });
        let childMetadataNode = API.node.findChild('DeploymentStage.dev').node.defaultChild;
        childMetadataNode.addMetadata('cfn_nag', {
            // eslint-disable-next-line @typescript-eslint/naming-convention
            rules_to_suppress: [
                {
                    id: 'W64',
                    reason: 'TODO:triage resources should be associated with an AWS::ApiGateway::UsagePlan.'
                },
                {
                    id: 'W59',
                    reason: 'AuthN with Congnito + JWT and AuthZ with CASL is implemented.'
                }
            ]
        });
        childMetadataNode = API.node.findChild('Deployment').node.defaultChild;
        childMetadataNode.addMetadata('cfn_nag', {
            // eslint-disable-next-line @typescript-eslint/naming-convention
            rules_to_suppress: [
                {
                    id: 'W68',
                    reason: 'TODO: Enable on Usage plan for API Gateway'
                }
            ]
        });
        if (process.env.LOCAL_DEVELOPMENT === 'true') {
            // SAM local start-api doesn't work with ALIAS so this is the workaround to allow us to run the code locally
            // https://github.com/aws/aws-sam-cli/issues/2227
            API.root.addProxy({
                defaultIntegration: new aws_apigateway_1.LambdaIntegration(apiLambda)
            });
        }
        else {
            const alias = new aws_lambda_1.Alias(this, 'LiveAlias', {
                aliasName: 'live',
                version: apiLambda.currentVersion,
                provisionedConcurrentExecutions: 1
            });
            API.root.addProxy({
                defaultIntegration: new aws_apigateway_1.LambdaIntegration(alias)
            });
        }
        childMetadataNode = API.node.findChild('Default').node.findChild('ANY').node.defaultChild;
        childMetadataNode.addMetadata('cfn_nag', {
            // eslint-disable-next-line @typescript-eslint/naming-convention
            rules_to_suppress: [
                {
                    id: 'W59',
                    reason: "TODO:triage should not have AuthorizationType set to 'NONE' unless it is of HttpMethod: OPTIONS.."
                }
            ]
        });
        childMetadataNode = API.node.findChild('Default').node.findChild('{proxy+}').node.findChild('ANY').node
            .defaultChild;
        childMetadataNode.addMetadata('cfn_nag', {
            // eslint-disable-next-line @typescript-eslint/naming-convention
            rules_to_suppress: [
                {
                    id: 'W59',
                    reason: "TODO:triage should not have AuthorizationType set to 'NONE' unless it is of HttpMethod: OPTIONS.."
                }
            ]
        });
        return API.url;
    }
    //DynamicAuth DynamoDB Table
    // Create DynamicAuthDDBTable
    _createDynamicAuthDDBTable(encryptionKey, apiLambda, statusHandler, createAccountHandler) {
        const dynamicAuthDDBTable = new workbench_core_infrastructure_1.WorkbenchDynamodb(this, `${this.stackName}-dynamic-auth`, {
            partitionKey: { name: 'pk', type: aws_dynamodb_1.AttributeType.STRING },
            sortKey: { name: 'sk', type: aws_dynamodb_1.AttributeType.STRING },
            encryptionKey: encryptionKey,
            lambdas: [apiLambda, statusHandler, createAccountHandler],
            gsis: [
                {
                    indexName: 'getIdentityPermissionsByIdentity',
                    partitionKey: { name: 'identity', type: aws_dynamodb_1.AttributeType.STRING },
                    sortKey: { name: 'pk', type: aws_dynamodb_1.AttributeType.STRING }
                }
            ],
            timeToLiveAttribute: 'expirationTime'
        });
        new aws_cdk_lib_1.CfnOutput(this, 'dynamicAuthDDBTableArn', {
            value: dynamicAuthDDBTable.table.tableArn
        });
        new aws_cdk_lib_1.CfnOutput(this, 'dynamicAuthDDBTableName', {
            value: dynamicAuthDDBTable.table.tableName
        });
        return dynamicAuthDDBTable.table;
    }
    _createRevokedTokensDDBTable(encryptionKey, apiLambda, statusHandler, createAccountHandler) {
        const revokedTokensDDBTable = new workbench_core_infrastructure_1.WorkbenchDynamodb(this, `${this.stackName}-revoked-tokens`, {
            partitionKey: { name: 'pk', type: aws_dynamodb_1.AttributeType.STRING },
            sortKey: { name: 'sk', type: aws_dynamodb_1.AttributeType.STRING },
            encryptionKey: encryptionKey,
            lambdas: [apiLambda, statusHandler, createAccountHandler],
            timeToLiveAttribute: 'ttl'
        });
        new aws_cdk_lib_1.CfnOutput(this, 'revokedTokensDDBTableArn', {
            value: revokedTokensDDBTable.table.tableArn
        });
        new aws_cdk_lib_1.CfnOutput(this, 'revokedTokensDDBTableName', {
            value: revokedTokensDDBTable.table.tableName
        });
        return revokedTokensDDBTable.table;
    }
    // Application DynamoDB Table
    _createApplicationDDBTable(encryptionKey, apiLambda, statusHandler, createAccountHandler) {
        const tableName = `${this.stackName}`;
        const table = new aws_dynamodb_1.Table(this, tableName, {
            partitionKey: { name: 'pk', type: aws_dynamodb_1.AttributeType.STRING },
            sortKey: { name: 'sk', type: aws_dynamodb_1.AttributeType.STRING },
            tableName: tableName,
            billingMode: aws_dynamodb_1.BillingMode.PAY_PER_REQUEST,
            pointInTimeRecovery: true,
            encryption: aws_dynamodb_1.TableEncryption.CUSTOMER_MANAGED,
            encryptionKey: encryptionKey
        });
        // Add GSI for get resource by name
        table.addGlobalSecondaryIndex({
            indexName: 'getResourceByName',
            partitionKey: { name: 'resourceType', type: aws_dynamodb_1.AttributeType.STRING },
            sortKey: { name: 'name', type: aws_dynamodb_1.AttributeType.STRING }
        });
        // Add GSI for get resource by status
        table.addGlobalSecondaryIndex({
            indexName: 'getResourceByStatus',
            partitionKey: { name: 'resourceType', type: aws_dynamodb_1.AttributeType.STRING },
            sortKey: { name: 'status', type: aws_dynamodb_1.AttributeType.STRING }
        });
        // Add GSI for get resource by createdAt
        table.addGlobalSecondaryIndex({
            indexName: 'getResourceByCreatedAt',
            partitionKey: { name: 'resourceType', type: aws_dynamodb_1.AttributeType.STRING },
            sortKey: { name: 'createdAt', type: aws_dynamodb_1.AttributeType.STRING }
        });
        // Add GSI for get resource by dependency
        table.addGlobalSecondaryIndex({
            indexName: 'getResourceByDependency',
            partitionKey: { name: 'resourceType', type: aws_dynamodb_1.AttributeType.STRING },
            sortKey: { name: 'dependency', type: aws_dynamodb_1.AttributeType.STRING }
        });
        // Add GSI for get resource by owner
        table.addGlobalSecondaryIndex({
            indexName: 'getResourceByOwner',
            partitionKey: { name: 'resourceType', type: aws_dynamodb_1.AttributeType.STRING },
            sortKey: { name: 'owner', type: aws_dynamodb_1.AttributeType.STRING }
        });
        // TODO Add GSI for get resource by cost
        // Add GSI for get resource by type
        table.addGlobalSecondaryIndex({
            indexName: 'getResourceByType',
            partitionKey: { name: 'resourceType', type: aws_dynamodb_1.AttributeType.STRING },
            sortKey: { name: 'type', type: aws_dynamodb_1.AttributeType.STRING }
        });
        // Grant the Lambda Functions read access to the DynamoDB table
        table.grantReadWriteData(apiLambda);
        table.grantReadWriteData(statusHandler);
        table.grantReadWriteData(createAccountHandler);
        new aws_cdk_lib_1.CfnOutput(this, 'dynamoDBTableOutput', { value: table.tableArn });
        const metadatanode = table.node.defaultChild;
        metadatanode.addMetadata('cfn_nag', {
            // eslint-disable-next-line @typescript-eslint/naming-convention
            rules_to_suppress: [
                {
                    id: 'W28',
                    reason: 'TODO:triage Resource found with an explicit name, this disallows updates that require replacement of this resource'
                },
                {
                    id: 'W74',
                    reason: 'TODO:triage DynamoDB table should have encryption enabled using a CMK stored in KMS'
                },
                {
                    id: 'W78',
                    reason: 'TODO:triage DynamoDB table should have backup enabled, should be set using PointInTimeRecoveryEnabled'
                }
            ]
        });
        return table;
    }
    _createCognitoResources(domainPrefix, websiteUrls, userPoolName, userPoolClientName) {
        const props = {
            domainPrefix: domainPrefix,
            websiteUrls: websiteUrls,
            userPoolName: userPoolName,
            userPoolClientName: userPoolClientName,
            oidcIdentityProviders: [],
            accessTokenValidity: aws_cdk_lib_1.Duration.minutes(60) // Extend access token expiration to 60 minutes to allow integration tests to run successfully. Once MAFoundation-310 has been implemented to allow multiple clientIds, we'll create a separate client for integration tests and the "main" client access token expiration time can be return to 15 minutes
        };
        const workbenchCognito = new workbench_core_infrastructure_1.WorkbenchCognito(this, 'ServiceWorkbenchCognito', props);
        new aws_cdk_lib_1.CfnOutput(this, 'cognitoUserPoolId', {
            value: workbenchCognito.userPoolId
        });
        new aws_cdk_lib_1.CfnOutput(this, 'cognitoUserPoolClientId', {
            value: workbenchCognito.userPoolClientId
        });
        new aws_cdk_lib_1.CfnOutput(this, 'cognitoDomainName', {
            value: workbenchCognito.cognitoDomain
        });
        return workbenchCognito;
    }
}
exports.RSWStack = RSWStack;
//# sourceMappingURL=RSWStack.js.map