@aws-cdk/core#CfnOutput TypeScript Examples

The following examples show how to use @aws-cdk/core#CfnOutput. You can vote up the ones you like or vote down the ones you don't like, and go to the original project or source file by following the links above each example. You may check out the related API usage on the sidebar.
Example #1
Source File: integ.default.ts    From aws-cdk-for-k3scluster with MIT License 6 votes vote down vote up
constructor() {
    const app = new App();
    const env = {
      region: process.env.CDK_DEFAULT_REGION,
      account: process.env.CDK_DEFAULT_ACCOUNT,
    };

    const stack = new Stack(app, 'testing-stack', { env });

    const vpc = k3s.VpcProvider.getOrCreate(stack);

    const cluster = new k3s.Cluster(stack, 'Cluster', {
      vpc,
      spotWorkerNodes: true,
      workerMinCapacity: 1,
      workerInstanceType: new ec2.InstanceType('m6g.medium'),
      controlPlaneInstanceType: new ec2.InstanceType('m6g.medium'),
      bucketRemovalPolicy: RemovalPolicy.DESTROY,
    });

    new CfnOutput(stack, 'EndpointURI', { value: cluster.endpointUri });
    new CfnOutput(stack, 'Region', { value: Stack.of(stack).region });
    this.stack = [stack];
  }
Example #2
Source File: mainDatabase.ts    From aws-boilerplate with MIT License 6 votes vote down vote up
private createDbInstance(props: MainDatabaseProps) {
        const securityGroup = this.createSecurityGroup(props);

        const instance = new DatabaseInstance(this, "Instance", {
            instanceIdentifier: `${props.envSettings.projectEnvName}-main`,
            vpc: props.vpc,
            engine: DatabaseInstanceEngine.POSTGRES,
            instanceType: InstanceType.of(InstanceClass.T2, InstanceSize.MICRO),
            masterUsername: 'root',
            databaseName: 'main',
            securityGroups: [securityGroup],
            deletionProtection: true,
        });

        if (instance.secret) {
            new CfnOutput(this, "SecretOutput", {
                exportName: MainDatabase.getDatabaseSecretArnOutputExportName(props.envSettings),
                value: instance.secret.secretArn,
            });
        }

        return instance;
    }
Example #3
Source File: mainDatabase.ts    From aws-boilerplate with MIT License 6 votes vote down vote up
private createProxyRole(instance: DatabaseInstance, props: MainDatabaseProps) {
        const role = new Role(this, "DBProxyRole", {
            assumedBy: new ServicePrincipal('rds.amazonaws.com'),
        });

        role.addToPolicy(new PolicyStatement({
            effect: Effect.ALLOW,
            actions: [
                "secretsmanager:GetRandomPassword",
                "secretsmanager:CreateSecret",
                "secretsmanager:ListSecrets",
            ],
            resources: ['*'],
        }));

        if (instance.secret) {
            role.addToPolicy(new PolicyStatement({
                effect: Effect.ALLOW,
                actions: ['secretsmanager:*'],
                resources: [instance.secret.secretArn]
            }));
        }

        new CfnOutput(this, "DbProxyRoleArn", {
            exportName: MainDatabase.getDatabaseProxyRoleArnOutputExportName(props.envSettings),
            value: role.roleArn,
        });
    }
Example #4
Source File: mainCertificates.ts    From aws-boilerplate with MIT License 6 votes vote down vote up
constructor(scope: Construct, id: string, props: MainCertificateProps) {
        super(scope, id);

        const hostedZone = PublicHostedZone.fromHostedZoneAttributes(this, "DomainZone", {
            hostedZoneId: props.envSettings.hostedZone.id,
            zoneName: props.envSettings.hostedZone.name,
        });

        const domainName = `${props.envSettings.envStage}.${props.envSettings.hostedZone.name}`
        this.certificate = new DnsValidatedCertificate(this, "AppsCertificate", {
            domainName,
            subjectAlternativeNames: [`*.${domainName}`],
            hostedZone,
        });

        this.cloudFrontCertificate = new DnsValidatedCertificate(this, "CloudFrontCertificate", {
            region: 'us-east-1',
            domainName,
            subjectAlternativeNames: [`*.${domainName}`],
            hostedZone,
        });

        new CfnOutput(this, "AppCertificateArnOutput", {
            value: this.certificate.certificateArn,
            exportName: MainCertificates.geCertificateArnOutputExportName(props.envSettings),
        });

        new CfnOutput(this, "CloudFrontCertificateArnOutput", {
            value: this.cloudFrontCertificate.certificateArn,
            exportName: MainCertificates.geCloudFrontCertificateArnOutputExportName(props.envSettings),
        });
    }
Example #5
Source File: mainEcsCluster.ts    From aws-boilerplate with MIT License 6 votes vote down vote up
private createFargateSecurityGroup(props: MainECSClusterProps): SecurityGroup {
        const sg = new SecurityGroup(this, "FargateContainerSecurityGroup", {
            vpc: props.vpc,
            allowAllOutbound: true,
            description: `${props.envSettings.projectName} Fargate container security group`,
        });

        sg.addIngressRule(sg, Port.allTcp());

        new CfnOutput(this, "FargateContainerSecurityGroupIdOutput", {
            exportName: MainECSCluster.getFargateContainerSecurityGroupIdOutputExportName(props.envSettings),
            value: sg.securityGroupId,
        });

        return sg;
    }
Example #6
Source File: mainKmsKey.ts    From aws-boilerplate with MIT License 6 votes vote down vote up
constructor(scope: Construct, id: string, props: MainKmsKeyProps) {
        super(scope, id);

        this.envSettings = props.envSettings;

        this.key = new Key(this, "Key", {
            alias: `alias/${MainKmsKey.getKeyAlias(this.envSettings)}`
        });

        this.key.addToResourcePolicy(new PolicyStatement({
            actions: [
                "kms:Encrypt",
                "kms:Decrypt",
            ],
            principals: [new AccountRootPrincipal()],
            resources: ["*"],
        }));

        new CfnOutput(this, "MainKmsKeyArnOutput", {
            exportName: MainKmsKey.getMainKmsOutputExportName(this.envSettings),
            value: this.key.keyArn,
        });
    }
Example #7
Source File: mainLambdaConfig.ts    From aws-boilerplate with MIT License 6 votes vote down vote up
private createLambdaSecurityGroup(props: MainLambdaConfigProps): SecurityGroup {
        const sg = new SecurityGroup(this, "LambdaSecurityGroup", {
            vpc: props.mainVpc.vpc,
            allowAllOutbound: true,
            description: `${props.envSettings.projectName} Lambda function security group`,
        });

        props.mainVpc.secretsManagerVpcEndpoint.connections.allowFrom(sg, Port.allTcp());

        new CfnOutput(this, "LambdaSecurityGroupIdOutput", {
            exportName: MainLambdaConfig.getLambdaSecurityGroupIdOutputExportName(props.envSettings),
            value: sg.securityGroupId,
        });

        return sg;
    }
Example #8
Source File: mainVpc.ts    From aws-boilerplate with MIT License 6 votes vote down vote up
private createOutputs(props: MainVpcProps) {
        new CfnOutput(this, "PublicSubnetOneIdOutput", {
            exportName: MainVpc.getPublicSubnetOneIdOutputExportName(props.envSettings),
            value: this.vpc.publicSubnets[0].subnetId,
        });

        new CfnOutput(this, "PublicSubnetTwoIdOutput", {
            exportName: MainVpc.getPublicSubnetTwoIdOutputExportName(props.envSettings),
            value: this.vpc.publicSubnets[1].subnetId,
        });

        new CfnOutput(this, "MainVPCOutput", {
            exportName: MainVpc.getVpcArnOutputExportName(props.envSettings),
            value: this.vpc.vpcId,
        });
    }
Example #9
Source File: globalCodeCommit.ts    From aws-boilerplate with MIT License 6 votes vote down vote up
constructor(scope: Construct, id: string, props: EnvConstructProps) {
        super(scope, id);

        this.repository = new Repository(this, 'CodeRepo', {
            repositoryName: GlobalCodeCommit.getCodeRepositoryName(props.envSettings),
            description: `${props.envSettings.projectName} code mirror repository used to source CodePipeline`
        });

        const user = new User(this, 'CodeRepoUser', {
            userName: `${props.envSettings.projectName}-code`
        });
        this.repository.grantPullPush(user);

        new CfnOutput(this, "CodeRepoUserName", {
            exportName: GlobalCodeCommit.getCodeRepoUserNameOutputExportName(props.envSettings),
            value: user.userName,
        });

        new CfnOutput(this, "CodeRepoCloneUrlHttp", {
            exportName: GlobalCodeCommit.getCodeRepoCloneUrlHttpOutputExportName(props.envSettings),
            value: this.repository.repositoryCloneUrlHttp,
        });
    }
Example #10
Source File: stack.ts    From aws-boilerplate with MIT License 5 votes vote down vote up
constructor(scope: core.App, id: string, props: SshBastionStackProps) {
        super(scope, id, props);

        const {envSettings} = props;

        if (!props.envSettings.sshBastion.sshPublicKey) {
            return;
        }

        const resources = new FargateServiceResources(this, "Resources", props);
        const securityGroup = new SecurityGroup(this, "SecurityGroup", {
            vpc: resources.mainVpc,
            allowAllOutbound: true,
        });
        securityGroup.addIngressRule(Peer.anyIpv4(), Port.tcp(22));
        securityGroup.addIngressRule(securityGroup, Port.allTcp());

        const containerName = 'sshBastion';
        const dbSecretArn = Fn.importValue(MainDatabase.getDatabaseSecretArnOutputExportName(envSettings));
        const taskRole = this.createTaskRole(props);

        const taskDefinition = new FargateTaskDefinition(this, "TaskDefinition", {
            taskRole,
            cpu: 256,
            memoryLimitMiB: 512,
        });

        const container = taskDefinition.addContainer(containerName, {
            image: ContainerImage.fromEcrRepository(resources.backendRepository, 'ssh-bastion'),
            logging: this.createAWSLogDriver(this.node.id),
            environment: {
                "CHAMBER_SERVICE_NAME": this.getChamberServiceName(envSettings),
                "CHAMBER_KMS_KEY_ALIAS": MainKmsKey.getKeyAlias(envSettings),
                "SSH_PUBLIC_KEY": props.envSettings.sshBastion.sshPublicKey,
                "WORKERS_EVENT_BUS_NAME": EnvComponentsStack.getWorkersEventBusName(props.envSettings),
            },
            secrets: {
                "DB_CONNECTION": EcsSecret.fromSecretsManager(
                    Secret.fromSecretArn(this, "DbSecret", dbSecretArn)),
            },
            command: ["sh", "-c", "/bin/chamber exec $CHAMBER_SERVICE_NAME -- ./scripts/run-ssh-bastion.sh"]
        });

        container.addPortMappings({
            protocol: Protocol.TCP,
            hostPort: 22,
            containerPort: 22,
        });

        new CfnOutput(this, "TaskDefinitionArnOutput", {
            exportName: SshBastionStack.getTaskDefinitionArn(props.envSettings),
            value: taskDefinition.taskDefinitionArn,
        });

        new CfnOutput(this, "SecurityGroupIdOutput", {
            exportName: SshBastionStack.getSecurityGroupId(props.envSettings),
            value: securityGroup.securityGroupId,
        });
    }
Example #11
Source File: rds-stack.ts    From awsmug-serverless-graphql-api with MIT License 5 votes vote down vote up
readonly rdsDatabaseOutput: CfnOutput;
Example #12
Source File: rds-stack.ts    From awsmug-serverless-graphql-api with MIT License 5 votes vote down vote up
readonly rdsUsernameOutput: CfnOutput;
Example #13
Source File: rds-stack.ts    From awsmug-serverless-graphql-api with MIT License 5 votes vote down vote up
readonly rdsEndpointOutput: CfnOutput;
Example #14
Source File: rds-stack.ts    From awsmug-serverless-graphql-api with MIT License 5 votes vote down vote up
constructor(scope: App, id: string, props: RDSStackProps) {
    super(scope, id, props);

    this.rdsPassword = Secret.fromSecretNameV2(
      this,
      "rdsPassword",
      "rdsPassword"
    );

    this.postgresRDSInstance = new DatabaseInstance(
      this,
      "postgres-rds-instance",
      {
        engine: DatabaseInstanceEngine.postgres({
          version: PostgresEngineVersion.VER_10_15,
        }),
        vpc: props.vpc,
        credentials: {
          username: this.rdsDbUser,
          password: this.rdsPassword.secretValue,
        },
        securityGroups: [props.securityGroup],
        vpcPlacement: { subnetType: SubnetType.ISOLATED },
        storageEncrypted: true,
        multiAz: false,
        allocatedStorage: 25,
        storageType: StorageType.GP2,
        databaseName: this.rdsDbName,
        port: this.rdsPort,
      }
    );
    this.rdsEndpointOutput = new CfnOutput(this, "rdsEndpoint", {
      value: this.postgresRDSInstance.instanceEndpoint.socketAddress,
      description: "Endpoint to access RDS instance",
    });

    this.rdsUsernameOutput = new CfnOutput(this, "rdsUsername", {
      value: this.rdsDbUser,
      description: "Root user of RDS instance",
    });

    this.rdsDatabaseOutput = new CfnOutput(this, "rdsDatabase", {
      value: this.rdsDbName,
      description: "Default database of RDS instance",
    });
  }
Example #15
Source File: cicd-stack.ts    From awsmug-serverless-graphql-api with MIT License 5 votes vote down vote up
readonly rdsDatabase: CfnOutput;
Example #16
Source File: cicd-stack.ts    From awsmug-serverless-graphql-api with MIT License 5 votes vote down vote up
readonly rdsUsername: CfnOutput;
Example #17
Source File: cicd-stack.ts    From awsmug-serverless-graphql-api with MIT License 5 votes vote down vote up
readonly rdsEndpoint: CfnOutput;
Example #18
Source File: cicd-stack.ts    From awsmug-serverless-graphql-api with MIT License 5 votes vote down vote up
readonly apiPath: CfnOutput;
Example #19
Source File: api-stack.ts    From awsmug-serverless-graphql-api with MIT License 5 votes vote down vote up
readonly apiPathOutput: CfnOutput;
Example #20
Source File: api-stack.ts    From awsmug-serverless-graphql-api with MIT License 5 votes vote down vote up
constructor(scope: Construct, id: string, props: LambdaStackProps) {
    super(scope, id, props);

    this.rdsPassword = Secret.fromSecretNameV2(
      this,
      "rdsPassword",
      "rdsPassword"
    );

    this.handler = new Function(this, "graphql", {
      runtime: Runtime.NODEJS_14_X,
      code: Code.fromAsset("app"),
      handler: "build/src/graphql.handler",
      vpc: props.vpc,
      vpcSubnets: {
        subnetType: SubnetType.ISOLATED,
      },
      securityGroup: SecurityGroup.fromSecurityGroupId(
        this,
        "inboundDbAccessSecurityGroup" + "rdsLambda",
        props.inboundDbAccessSecurityGroup
      ),

      environment: {
        TYPEORM_URL: `postgres://${
          props.rdsDbUser
        }:${this.rdsPassword.secretValue.toString()}@${props.rdsEndpoint}:${
          props.rdsPort
        }/${props.rdsDbName}`,
        TYPEORM_SYNCHRONIZE: "true",
        TYPEORM_LOGGING: "true",
        TYPEORM_ENTITIES: "./build/src/entity/*.entity.js",
      },
    });

    this.api = new LambdaRestApi(this, "graphql-api", {
      handler: this.handler,
      proxy: false,
    });

    this.graphql = this.api.root.addResource("graphql");
    this.graphql.addMethod("ANY");

    this.apiPathOutput = new CfnOutput(this, "apiPath", {
      value: this.api.root.path,
      description: "Path of the API",
    });
  }
Example #21
Source File: mainEcsCluster.ts    From aws-boilerplate with MIT License 5 votes vote down vote up
private createPublicLoadBalancer(props: MainECSClusterProps): ApplicationLoadBalancer {
        const securityGroup = new SecurityGroup(this, "ALBSecurityGroup", {
            vpc: props.vpc,
        });
        securityGroup.addIngressRule(Peer.anyIpv4(), Port.allTraffic());

        const publicLoadBalancer = new ApplicationLoadBalancer(this, "ALB", {
            vpc: props.vpc,
            internetFacing: true,
            securityGroup: securityGroup,
            idleTimeout: Duration.seconds(30),
            vpcSubnets: {subnetType: SubnetType.PUBLIC, onePerAz: true},
        });

        const httpsListener = publicLoadBalancer.addListener("HttpsListener", {
            protocol: ApplicationProtocol.HTTPS,
            open: true,
            port: 443,
            defaultTargetGroups: [new ApplicationTargetGroup(this, 'DummyTargetGroup', {
                vpc: props.vpc,
                port: 80,
                targetGroupName: `${props.envSettings.projectEnvName}-dtg`,
                targetType: TargetType.IP,
            })]
        });

        httpsListener.addCertificates("Certificate", [
            ListenerCertificate.fromCertificateManager(props.certificate),
        ]);

        new CfnOutput(this, "PublicLoadBalancerSecurityGroupIdOutput", {
            exportName: MainECSCluster.getPublicLoadBalancerSecurityGroupIdOutputExportName(props.envSettings),
            value: securityGroup.securityGroupId,
        });

        new CfnOutput(this, "PublicLoadBalancerDnsNameOutput", {
            exportName: MainECSCluster.getLoadBalancerDnsNameOutput(props.envSettings),
            value: publicLoadBalancer.loadBalancerDnsName,
        });

        new CfnOutput(this, "PublicLoadBalancerArnOutput", {
            exportName: MainECSCluster.getLoadBalancerArnOutputExportName(props.envSettings),
            value: publicLoadBalancer.loadBalancerArn,
        });

        new CfnOutput(this, "PublicLoadBalancerCanonicalHostedZoneIdOutput", {
            exportName: MainECSCluster.getLoadBalancerCanonicalHostedZoneIdOutputExportName(props.envSettings),
            value: publicLoadBalancer.loadBalancerCanonicalHostedZoneId,
        });

        new CfnOutput(this, "PublicLoadBalancerHttpsListenerArn", {
            exportName: MainECSCluster.getLoadBalancerHttpsListenerArnOutputExportName(props.envSettings),
            value: httpsListener.listenerArn,
        })

        return publicLoadBalancer;
    }
Example #22
Source File: cdk-stack.ts    From laravel-on-aws-ecs-workshops with Apache License 2.0 4 votes vote down vote up
constructor(scope: cdk.Construct, id: string, props?: cdk.StackProps) {
    super(scope, id, props);


    //---------------------------------------------------------------------------
    // VPC
    const vpc = new ec2.Vpc(this, generateName('VPC'), {
      maxAzs: 2,
      natGateways: 1,
      subnetConfiguration: [
        {
          cidrMask: 24,
          name: generateName('ingress'),
          subnetType: ec2.SubnetType.PUBLIC,
        },
        {
          cidrMask: 24,
          name: generateName('application'),
          subnetType: ec2.SubnetType.PRIVATE,
        },
        {
          cidrMask: 24,
          name: generateName('database'),
          subnetType: ec2.SubnetType.ISOLATED,
        },
      ],
    });

    //---------------------------------------------------------------------------
    // ECS

    // ECS Cluster
    const ecsCluster = new ecs.Cluster(this, 'LaravelWorkshopCluster', {
      vpc: vpc
    });
    const asg = ecsCluster.addCapacity('DefaultAutoScalingGroup', {
      instanceType: ec2.InstanceType.of(ec2.InstanceClass.T3, ec2.InstanceSize.SMALL),
      maxCapacity: 4,
      minCapacity: 2,
    });

    // TODO: T4g

    // Task Definition
    const ec2TaskDefinition = new ecs.Ec2TaskDefinition(this, 'DefaultTaskDef');

    const ecrRepo = ecr.Repository.fromRepositoryName(this, 'DefaultRepo', deploymentEnv.ecrRepoName);

    const kmsKey = kms.Key.fromKeyArn(
      this, 
      generateName('kmsKey'), 
      'arn:aws:kms:' + props?.env?.region + ':' + props?.env?.account + ':key/' + deploymentEnv.kmsKeyId
    );

    const container = ec2TaskDefinition.addContainer('defaultContainer', {
      image: ecs.ContainerImage.fromEcrRepository(ecrRepo), 
      memoryLimitMiB: 512,
      cpu: 256,
      logging: new ecs.AwsLogDriver({
        streamPrefix: deploymentEnv.logStreamPrefix,
        logRetention: logs.RetentionDays.SIX_MONTHS,
      }),
      secrets: { // Retrieved from AWS Secrets Manager or AWS Systems Manager Parameter Store at container start-up.
        'APP_NAME'          : ecsValueFromSsmParameterString(this, 'APP_NAME'),
        'APP_ENV'           : ecsValueFromSsmParameterString(this, 'APP_ENV'),
        'APP_KEY'           : ecsValueFromSsmParameterSecureString(this, 'APP_KEY', kmsKey),
        'APP_DEBUG'         : ecsValueFromSsmParameterString(this, 'APP_DEBUG'),
        'APP_URL'           : ecsValueFromSsmParameterString(this, 'APP_URL'),

        'LOG_CHANNEL'       : ecsValueFromSsmParameterString(this, 'LOG_CHANNEL'),

        'DB_CONNECTION'     : ecsValueFromSsmParameterString(this, 'DB_CONNECTION'),
        'DB_HOST'           : ecsValueFromSsmParameterString(this, 'DB_HOST'),
        'DB_PORT'           : ecsValueFromSsmParameterString(this, 'DB_PORT'),
        'DB_DATABASE'       : ecsValueFromSsmParameterString(this, 'DB_DATABASE'),
        'DB_USERNAME'       : ecsValueFromSsmParameterSecureString(this, 'DB_USERNAME', kmsKey),
        'DB_PASSWORD'       : ecsValueFromSsmParameterSecureString(this, 'DB_PASSWORD', kmsKey),

        'BROADCAST_DRIVER'  : ecsValueFromSsmParameterString(this, 'BROADCAST_DRIVER'),
        'CACHE_DRIVER'      : ecsValueFromSsmParameterString(this, 'CACHE_DRIVER'),
        'QUEUE_CONNECTION'  : ecsValueFromSsmParameterString(this, 'QUEUE_CONNECTION'),
        'SESSION_DRIVER'    : ecsValueFromSsmParameterString(this, 'SESSION_DRIVER'),
        'SESSION_LIFETIME'  : ecsValueFromSsmParameterString(this, 'SESSION_LIFETIME'),

        'REDIS_HOST'        : ecsValueFromSsmParameterString(this, 'REDIS_HOST'),
        'REDIS_PASSWORD'    : ecsValueFromSsmParameterSecureString(this, 'REDIS_PASSWORD', kmsKey),
        'REDIS_PORT'        : ecsValueFromSsmParameterString(this, 'REDIS_PORT'),

        'MAIL_DRIVER'       : ecsValueFromSsmParameterString(this, 'MAIL_DRIVER'),
        'MAIL_HOST'         : ecsValueFromSsmParameterString(this, 'MAIL_HOST'),
        'MAIL_PORT'         : ecsValueFromSsmParameterString(this, 'MAIL_PORT'),
        'MAIL_USERNAME'     : ecsValueFromSsmParameterSecureString(this, 'MAIL_USERNAME', kmsKey),
        'MAIL_PASSWORD'     : ecsValueFromSsmParameterSecureString(this, 'MAIL_PASSWORD', kmsKey),
        'MAIL_ENCRYPTION'   : ecsValueFromSsmParameterString(this, 'MAIL_ENCRYPTION'),
        'MAIL_FROM_ADDRESS' : ecsValueFromSsmParameterString(this, 'MAIL_FROM_ADDRESS'),
        'MAIL_FROM_NAME'    : ecsValueFromSsmParameterString(this, 'MAIL_FROM_NAME'),
        
        'ZZZ_KEY'           : ecsValueFromSsmParameterSecureString(this, 'ZZZ_KEY', kmsKey),
      },      
    });

    container.addPortMappings({
      containerPort: 80,
      protocol: ecs.Protocol.TCP
    });

    // ECS Service
    const ecsService = new ecs.Ec2Service(this, 'DefaultService', {
      cluster: ecsCluster,
      taskDefinition: ec2TaskDefinition,
      desiredCount: 2,
    });

    //---------------------------------------------------------------------------
    // Cert
    const domainAlternativeName = '*.' + deploymentEnv.domainName;
    const cert = new acm.Certificate(this, generateName('Cert'), {
      domainName: deploymentEnv.domainName,
      subjectAlternativeNames: [domainAlternativeName],
      validation: acm.CertificateValidation.fromDns(), // Records must be added manually
    });

    //---------------------------------------------------------------------------
    // ALB
    const alb = new elbv2.ApplicationLoadBalancer(this, generateName('ALB'), {
      vpc,
      internetFacing: true,
    });

    const listener = alb.addListener('Listener', {
      open: true,
      certificates: [cert],
      protocol: elbv2.ApplicationProtocol.HTTPS,
    });

    // Connect ecsService to TargetGroup
    const targetGroup = listener.addTargets(generateName('LaravelTargetGroup'), {
      protocol: elbv2.ApplicationProtocol.HTTP,
      targets: [ecsService]
    });

    new cdk.CfnOutput(this, generateName('AlbDnsName'), {
      exportName: generateName('AlbDnsName'),
      value: alb.loadBalancerDnsName,
    });

    new cdk.CfnOutput(this, generateName('ActionCname'), {
      exportName: generateName('ActionCname'),
      value: 'Please setup a CNAME record mylaravel.' + deploymentEnv.domainName + ' to ' + alb.loadBalancerDnsName,
    });    

    new cdk.CfnOutput(this, generateName('ActionVisit'), {
      exportName: generateName('ActionVisit'),
      value: 'Visit https://mylaravel.' + deploymentEnv.domainName,
    });      

    //---------------------------------------------------------------------------
    // ecsService: Application Auto Scaling    
    const scaling = ecsService.autoScaleTaskCount({ 
      minCapacity: 2,
      maxCapacity: 10 
    });

    scaling.scaleOnCpuUtilization('CpuScaling', {
      targetUtilizationPercent: 50
    });
    
    scaling.scaleOnRequestCount('RequestScaling', {
      requestsPerTarget: 30,
      targetGroup: targetGroup
    });
  }
Example #23
Source File: cdk-stack.ts    From laravel-on-aws-ecs-workshops with Apache License 2.0 4 votes vote down vote up
constructor(scope: cdk.Construct, id: string, props?: cdk.StackProps) {
    super(scope, id, props);


    //---------------------------------------------------------------------------
    // VPC
    const vpc = new ec2.Vpc(this, generateName('VPC'), {
      maxAzs: 2,
      natGateways: 1,
      subnetConfiguration: [
        {
          cidrMask: 24,
          name: generateName('ingress'),
          subnetType: ec2.SubnetType.PUBLIC,
        },
        {
          cidrMask: 24,
          name: generateName('application'),
          subnetType: ec2.SubnetType.PRIVATE,
        },
        {
          cidrMask: 24,
          name: generateName('database'),
          subnetType: ec2.SubnetType.ISOLATED,
        },
      ],
    });

    //---------------------------------------------------------------------------
    // ECS

    // ECS Cluster
    const ecsCluster = new ecs.Cluster(this, 'LaravelWorkshopCluster', {
      vpc: vpc
    });
    const asg = ecsCluster.addCapacity('DefaultAutoScalingGroup', {
      instanceType: ec2.InstanceType.of(ec2.InstanceClass.T3, ec2.InstanceSize.SMALL),
      maxCapacity: 4,
      minCapacity: 2,
    });

    // TODO: T4g

    // Task Definition
    const ec2TaskDefinition = new ecs.Ec2TaskDefinition(this, 'DefaultTaskDef');

    const ecrRepo = ecr.Repository.fromRepositoryName(this, 'DefaultRepo', 'my-laravel-on-aws-ecs-workshop-dev');

    const container = ec2TaskDefinition.addContainer('defaultContainer', {
      image: ecs.ContainerImage.fromEcrRepository(ecrRepo), 
      memoryLimitMiB: 512,
      cpu: 256,
      logging: new ecs.AwsLogDriver({
        streamPrefix: 'MyLaravel',
        logRetention: logs.RetentionDays.SIX_MONTHS,
      }),
    });

    container.addPortMappings({
      containerPort: 80,
      protocol: ecs.Protocol.TCP
    });

    // ECS Service
    const ecsService = new ecs.Ec2Service(this, 'DefaultService', {
      cluster: ecsCluster,
      taskDefinition: ec2TaskDefinition,
      desiredCount: 2,
    });

    //---------------------------------------------------------------------------
    // Cert
    const domainAlternativeName = '*.' + deploymentEnv.domainName;
    const cert = new acm.Certificate(this, generateName('Cert'), {
      domainName: deploymentEnv.domainName,
      subjectAlternativeNames: [domainAlternativeName],
      validation: acm.CertificateValidation.fromDns(), // Records must be added manually
    });

    //---------------------------------------------------------------------------
    // ALB
    const alb = new elbv2.ApplicationLoadBalancer(this, generateName('ALB'), {
      vpc,
      internetFacing: true,
    });

    const listener = alb.addListener('Listener', {
      open: true,
      certificates: [cert],
      protocol: elbv2.ApplicationProtocol.HTTPS,
    });

    // Connect ecsService to TargetGroup
    const targetGroup = listener.addTargets(generateName('LaravelTargetGroup'), {
      protocol: elbv2.ApplicationProtocol.HTTP,
      targets: [ecsService]
    });

    new cdk.CfnOutput(this, generateName('AlbDnsName'), {
      exportName: generateName('AlbDnsName'),
      value: alb.loadBalancerDnsName,
    });

    new cdk.CfnOutput(this, generateName('ActionCname'), {
      exportName: generateName('ActionCname'),
      value: 'Please setup a CNAME record mylaravel.' + deploymentEnv.domainName + ' to ' + alb.loadBalancerDnsName,
    });    

    new cdk.CfnOutput(this, generateName('ActionVisit'), {
      exportName: generateName('ActionVisit'),
      value: 'Visit https://mylaravel.' + deploymentEnv.domainName,
    });      

    //---------------------------------------------------------------------------
    // ecsService: Application Auto Scaling    
    const scaling = ecsService.autoScaleTaskCount({ 
      minCapacity: 2,
      maxCapacity: 10 
    });

    scaling.scaleOnCpuUtilization('CpuScaling', {
      targetUtilizationPercent: 50
    });
    
    scaling.scaleOnRequestCount('RequestScaling', {
      requestsPerTarget: 30,
      targetGroup: targetGroup
    });
  }
Example #24
Source File: backend-stack.ts    From flect-chime-sdk-demo with Apache License 2.0 4 votes vote down vote up
constructor(scope: cdk.Construct, id: string, props?: cdk.StackProps) {
        super(scope, id, props);

        ///////////////////////////////
        //// Authentication
        ///////////////////////////////
        ////  (1) Cognito User Pool
        const userPool = new UserPool(this, `${id}_UserPool`, {
            userPoolName: `${id}_UserPool`,
            selfSignUpEnabled: true,
            autoVerify: {
                email: true,
            },
            passwordPolicy: {
                minLength: 6,
                requireSymbols: false,
            },
            signInAliases: {
                email: true,
            },
        });

        const userPoolClient = new UserPoolClient(this, id + "_UserPool_Client", {
            userPoolClientName: `${id}_UserPoolClient`,
            userPool: userPool,
            accessTokenValidity: Duration.minutes(1440),
            idTokenValidity: Duration.minutes(1440),
            refreshTokenValidity: Duration.days(30),
        });

        //// (2) Policy Statement
        const statement = new PolicyStatement({
            effect: Effect.ALLOW,
        });
        statement.addActions(
            "cognito-idp:GetUser",
            "cognito-idp:AdminGetUser",

            "chime:CreateMeeting",
            "chime:DeleteMeeting",
            "chime:GetMeeting",
            "chime:ListMeetings",
            "chime:BatchCreateAttendee",
            "chime:CreateAttendee",
            "chime:DeleteAttendee",
            "chime:GetAttendee",
            "chime:ListAttendees",
            "chime:StartMeetingTranscription",
            "chime:StopMeetingTranscription",

            "execute-api:ManageConnections",

            "ecs:RunTask",
            "ecs:DescribeTasks",
            "ecs:UpdateService",
            "ecs:DescribeServices",

            `ec2:DescribeNetworkInterfaces`,

            "iam:PassRole"
        );
        statement.addResources(userPool.userPoolArn);
        statement.addResources("arn:*:chime::*:meeting/*");
        statement.addResources("arn:aws:execute-api:*:*:**/@connections/*");
        statement.addResources("arn:aws:ecs:*");
        statement.addResources("*");
        statement.addResources("arn:aws:iam::*:*");

        //////////////////////////////////////
        //// Storage Resources (S3)
        //////////////////////////////////////
        const bucket = new s3.Bucket(this, "StaticSiteBucket", {
            bucketName: `${id}-Bucket`.toLowerCase(),
            removalPolicy: cdk.RemovalPolicy.DESTROY,
            publicReadAccess: true,
        });

        let cdn: cloudfront.CloudFrontWebDistribution;
        if (USE_CDN) {
            const oai = new cloudfront.OriginAccessIdentity(this, "my-oai");

            const myBucketPolicy = new iam.PolicyStatement({
                effect: iam.Effect.ALLOW,
                actions: ["s3:GetObject"],
                principals: [new iam.CanonicalUserPrincipal(oai.cloudFrontOriginAccessIdentityS3CanonicalUserId)],
                resources: [bucket.bucketArn + "/*"],
            });
            bucket.addToResourcePolicy(myBucketPolicy);

            // Create CloudFront WebDistribution
            cdn = new cloudfront.CloudFrontWebDistribution(this, "WebsiteDistribution", {
                viewerCertificate: {
                    aliases: [],
                    props: {
                        cloudFrontDefaultCertificate: true,
                    },
                },
                priceClass: cloudfront.PriceClass.PRICE_CLASS_ALL,
                originConfigs: [
                    {
                        s3OriginSource: {
                            s3BucketSource: bucket,
                            originAccessIdentity: oai,
                        },
                        behaviors: [
                            {
                                isDefaultBehavior: true,
                                minTtl: cdk.Duration.seconds(0),
                                maxTtl: cdk.Duration.days(365),
                                defaultTtl: cdk.Duration.days(1),
                                pathPattern: "my-contents/*",
                            },
                        ],
                    },
                ],
                errorConfigurations: [
                    {
                        errorCode: 403,
                        responsePagePath: "/index.html",
                        responseCode: 200,
                        errorCachingMinTtl: 0,
                    },
                    {
                        errorCode: 404,
                        responsePagePath: "/index.html",
                        responseCode: 200,
                        errorCachingMinTtl: 0,
                    },
                ],
            });
        }

        //////////////////////////////////////
        //// Storage Resources (DynamoDB)
        //////////////////////////////////////
        //// (1) Meeting Table
        const meetingTable = new Table(this, "meetingTable", {
            tableName: `${id}_MeetingTable`,
            partitionKey: {
                name: "MeetingName",
                type: AttributeType.STRING,
            },
            readCapacity: 2,
            writeCapacity: 2,
            removalPolicy: cdk.RemovalPolicy.DESTROY, // NOT recommended for production code
        });

        meetingTable.addGlobalSecondaryIndex({
            indexName: "MeetingId",
            partitionKey: {
                name: "MeetingId",
                type: AttributeType.STRING,
            },
            projectionType: ProjectionType.ALL,
            readCapacity: 2,
            writeCapacity: 2,
        });

        //// (2) Attendee Table
        const attendeeTable = new Table(this, "attendeeTable", {
            tableName: `${id}_AttendeeTable`,
            partitionKey: {
                name: "AttendeeId",
                type: AttributeType.STRING,
            },
            readCapacity: 2,
            writeCapacity: 2,
            removalPolicy: cdk.RemovalPolicy.DESTROY, // NOT recommended for production code
        });

        //// (3) Connection Table
        const connectionTable = new Table(this, "connectionTable", {
            tableName: `${id}_ConnectionTable`,
            partitionKey: {
                name: "MeetingId",
                type: AttributeType.STRING,
            },
            sortKey: {
                name: "AttendeeId",
                type: AttributeType.STRING,
            },
            readCapacity: 2,
            writeCapacity: 2,
            removalPolicy: cdk.RemovalPolicy.DESTROY, // NOT recommended for production code
        });

        //// (x) SlackFederationAccounts Table
        const slackFederationAuthsTable = new Table(this, "slackFederationAuthsTable", {
            tableName: `${id}_SlackFederationAuthsTable`,
            partitionKey: {
                name: "TeamId",
                type: AttributeType.STRING,
            },
            readCapacity: 2,
            writeCapacity: 2,
            removalPolicy: cdk.RemovalPolicy.DESTROY, // NOT recommended for production code
        });

        /////////////////////
        /// Fargate
        /////////////////////
        const vpc = new ec2.Vpc(this, `${id}_vpc`, {
            maxAzs: 2,
            subnetConfiguration: [
                {
                    cidrMask: 24,
                    name: `${id}_pub`,
                    subnetType: ec2.SubnetType.PUBLIC,
                },
            ],
        });
        const cluster = new ecs.Cluster(this, `${id}_cluster`, { vpc });
        const logGroup = new logs.LogGroup(this, `${id}_logGroup`, {
            logGroupName: `/${id}-fargate`,
            removalPolicy: cdk.RemovalPolicy.DESTROY,
        });

        // create a task definition with CloudWatch Logs
        const logging = new ecs.AwsLogDriver({
            logGroup: logGroup,
            streamPrefix: "fargate",
        });

        const taskDefinition = new ecs.FargateTaskDefinition(this, `${id}_fargate_task`, {
            family: this.node.tryGetContext("serviceName"),
            cpu: 2048,
            // memoryLimitMiB: 5120,
            memoryLimitMiB: 8192,
        });

        if (USE_DOCKER) {
            const container = taskDefinition.addContainer("DefaultContainer", {
                containerName: `${id}_manager_container`,
                image: ecs.ContainerImage.fromAsset("lib/manager"),
                cpu: 2048,
                // memoryLimitMiB: 4096,
                memoryLimitMiB: 8192,
                logging: logging,
            });
        } else {
            const image = ecs.ContainerImage.fromRegistry(`dannadori/hmm:latest`);
            const container = taskDefinition.addContainer("DefaultContainer", {
                containerName: `${id}_manager_container`,
                image: image,
                cpu: 2048,
                memoryLimitMiB: 8192,
                // memoryLimitMiB: 5120,
                logging: logging,
            });
        }

        bucket.grantReadWrite(taskDefinition.taskRole);

        const securityGroup = new ec2.SecurityGroup(this, "SecurityGroup", {
            vpc: vpc,
        });
        securityGroup.addIngressRule(ec2.Peer.ipv4("0.0.0.0/0"), ec2.Port.tcp(3000));

        ///////////////////////////////
        //// Lambda Layers
        ///////////////////////////////
        // ( - ) Lambda Layer
        const nodeModulesLayer = new lambda.LayerVersion(this, "NodeModulesLayer", {
            layerVersionName: `${id}_LambdaLayer`,
            code: lambda.AssetCode.fromAsset(`${__dirname}/layer`),
            compatibleRuntimes: [lambda.Runtime.NODEJS_14_X],
        });

        // ( - ) Utility
        const addCommonSetting = (f: lambda.Function) => {
            meetingTable.grantFullAccess(f);
            attendeeTable.grantFullAccess(f);
            connectionTable.grantFullAccess(f);
            slackFederationAuthsTable.grantFullAccess(f);
            f.addToRolePolicy(statement);

            f.addEnvironment("MEETING_TABLE_NAME", meetingTable.tableName);
            f.addEnvironment("ATTENDEE_TABLE_NAME", attendeeTable.tableName);
            f.addEnvironment("CONNECTION_TABLE_NAME", connectionTable.tableName);
            f.addEnvironment("SLACK_FEDERATION_AUTHS_TABLE_NAME", slackFederationAuthsTable.tableName);

            f.addEnvironment("USER_POOL_ID", userPool.userPoolId);
            f.addEnvironment("VPC_ID", vpc.vpcId);
            f.addEnvironment("SUBNET_ID", vpc.publicSubnets[0].subnetId);
            f.addEnvironment("CLUSTER_ARN", cluster.clusterArn);
            f.addEnvironment("TASK_DIFINITION_ARN_MANAGER", taskDefinition.taskDefinitionArn);
            f.addEnvironment("BUCKET_DOMAIN_NAME", bucket.bucketDomainName);
            f.addEnvironment("MANAGER_CONTAINER_NAME", `${id}_manager_container`);
            f.addEnvironment("BUCKET_ARN", bucket.bucketArn);
            f.addEnvironment("BUCKET_NAME", bucket.bucketName);
            f.addEnvironment("SECURITY_GROUP_NAME", securityGroup.securityGroupName);
            f.addEnvironment("SECURITY_GROUP_ID", securityGroup.securityGroupId);

            f.addEnvironment("USERPOOL_ID", userPool.userPoolId);
            f.addEnvironment("USERPOOL_CLIENT_ID", userPoolClient.userPoolClientId);

            // Slack-Chime Connector (slack access)
            f.addEnvironment("SLACK_CLIENT_ID", SLACK_CLIENT_ID);
            f.addEnvironment("SLACK_CLIENT_SECRET", SLACK_CLIENT_SECRET);
            f.addEnvironment("SLACK_SIGNING_SECRET", SLACK_SIGNING_SECRET);
            f.addEnvironment("SLACK_STATE_SECRET", SLACK_STATE_SECRET);
            // Slack-Chime Connector (db access)
            f.addEnvironment("SLACK_APP_DB_PASSWORD", SLACK_APP_DB_PASSWORD);
            f.addEnvironment("SLACK_APP_DB_SALT", SLACK_APP_DB_SALT);
            f.addEnvironment("SLACK_APP_DB_SECRET", SLACK_APP_DB_SECRET);

            //// DEMO URL
            if (USE_CDN) {
                f.addEnvironment("DEMO_ENDPOINT", `https://${cdn!.distributionDomainName}`);
            } else {
                f.addEnvironment("DEMO_ENDPOINT", `https://${bucket.bucketDomainName}`);
            }

            f.addLayers(nodeModulesLayer);
        };

        // ( - ) auth
        const lambdaFuncRestAPIAuth = new lambda.Function(this, "ChimeRESTAPIAuth", {
            code: lambda.Code.fromAsset(`${__dirname}/dist`),
            handler: "rest_auth.authorize",
            runtime: lambda.Runtime.NODEJS_14_X,
            timeout: Duration.seconds(5),
            memorySize: 256,
        });
        addCommonSetting(lambdaFuncRestAPIAuth);

        const restAPIRole = new Role(this, `ChimeRESTAPIRole`, {
            assumedBy: new ServicePrincipal("apigateway.amazonaws.com"),
        });
        const restAPIPolicy = new PolicyStatement({
            effect: Effect.ALLOW,
            resources: [lambdaFuncRestAPIAuth.functionArn],
            actions: ["lambda:InvokeFunction"],
        });
        restAPIRole.addToPolicy(restAPIPolicy);

        //// (1) Function For RestAPI
        const lambdaFunctionForRestAPI: lambda.Function = new lambda.Function(this, "funcHelloWorld", {
            functionName: `${id}_getRoot`,
            runtime: lambda.Runtime.NODEJS_14_X,
            code: lambda.Code.fromAsset(`${__dirname}/dist`),
            handler: "index.handler",
            memorySize: 256,
            timeout: cdk.Duration.seconds(5),
        });
        addCommonSetting(lambdaFunctionForRestAPI);

        //// (2) Function For SlackFederation API
        const lambdaFunctionForSlackFederationRestAPI: lambda.Function = new lambda.Function(this, "funcSlackFederation", {
            functionName: `${id}_slackFederationRoot`,
            runtime: lambda.Runtime.NODEJS_14_X,
            code: lambda.Code.fromAsset(`${__dirname}/dist/federation/slack`),
            handler: "slack.handler",
            memorySize: 256,
            timeout: cdk.Duration.seconds(3),
        });
        addCommonSetting(lambdaFunctionForSlackFederationRestAPI);

        ///////////////////////////////
        //// API Gateway
        ///////////////////////////////
        //// ( - ) Utility
        // https://github.com/aws/aws-cdk/issues/906
        const addCorsOptions = (apiResource: IResource) => {
            let origin;
            if (FRONTEND_LOCAL_DEV) {
                origin = "'https://localhost:3000'";
                // origin = "'https://192.168.0.4:3000'";
            } else {
                if (USE_CDN) {
                    origin = `'https://${cdn!.distributionDomainName}'`;
                } else {
                    origin = `'https://${bucket.bucketDomainName}'`;
                }
            }
            apiResource.addMethod(
                "OPTIONS",
                new MockIntegration({
                    integrationResponses: [
                        {
                            statusCode: "200",
                            responseParameters: {
                                "method.response.header.Access-Control-Allow-Headers": "'Content-Type,X-Amz-Date,Authorization,X-Api-Key,X-Amz-Security-Token,X-Amz-User-Agent,X-Flect-Access-Token'",
                                "method.response.header.Access-Control-Allow-Origin": origin,
                                "method.response.header.Access-Control-Allow-Credentials": "'true'",
                                "method.response.header.Access-Control-Allow-Methods": "'OPTIONS,GET,PUT,POST,DELETE,PATCH,HEAD'",
                            },
                        },
                    ],
                    passthroughBehavior: PassthroughBehavior.WHEN_NO_MATCH,
                    requestTemplates: {
                        "application/json": '{"statusCode": 200}',
                    },
                }),
                {
                    methodResponses: [
                        {
                            statusCode: "200",
                            responseParameters: {
                                "method.response.header.Access-Control-Allow-Headers": true,
                                "method.response.header.Access-Control-Allow-Methods": true,
                                "method.response.header.Access-Control-Allow-Credentials": true,
                                "method.response.header.Access-Control-Allow-Origin": true,
                            },
                        },
                    ],
                }
            );
        };
        //// ( - ) Rest API
        const restApi: RestApi = new RestApi(this, "ChimeAPI", {
            restApiName: `${id}_restApi`,
        });
        //lambdaFunctionPostAttendeeOperation.addEnvironment("RESTAPI_ENDPOINT", restApi.url) ///// Exception(Circular dependency between resources) -> generate from request context in lambda.

        //// ( - ) Authorizer
        const authorizer2 = new CfnAuthorizer(this, "apiAuthorizerLambda", {
            name: `${id}_authorizerLamda`,
            type: "TOKEN",
            // identitySource: "method.request.header.Authorization",
            identitySource: "method.request.header.X-Flect-Access-Token",
            restApiId: restApi.restApiId,
            authorizerCredentials: restAPIRole.roleArn,
            authorizerUri: `arn:aws:apigateway:${this.region}:lambda:path/2015-03-31/functions/${lambdaFuncRestAPIAuth.functionArn}/invocations`,
            authorizerResultTtlInSeconds: 0,
        });

        //// (1) Get Root
        const root = restApi.root;
        addCorsOptions(root);
        const addRoot = root.addMethod("GET", new LambdaIntegration(lambdaFunctionForRestAPI), {
            operationName: `${id}_getRoot`,
            // authorizationType: AuthorizationType.CUSTOM,
            // authorizer: {
            //   authorizerId: authorizer2.ref,
            // },
        });

        //// (2) Meeting
        const apiMeetings = restApi.root.addResource("meetings");
        const apiMeeting = apiMeetings.addResource("{meetingName}");
        addCorsOptions(apiMeetings);
        addCorsOptions(apiMeeting);
        //// (2-1) Get Meetings
        // apiMeetings.addMethod("GET", new LambdaIntegration(lambdaFunctionGetMeetings), {
        apiMeetings.addMethod("GET", new LambdaIntegration(lambdaFunctionForRestAPI), {
            operationName: `${id}_getMeetings`,
            authorizationType: AuthorizationType.CUSTOM,
            authorizer: {
                authorizerId: authorizer2.ref,
            },
        });

        //// (2-2) Post Meeting
        // apiMeetings.addMethod("POST", new LambdaIntegration(lambdaFunctionPostMeeting), {
        apiMeetings.addMethod("POST", new LambdaIntegration(lambdaFunctionForRestAPI), {
            operationName: `${id}_postMeeting`,
            authorizationType: AuthorizationType.CUSTOM,
            authorizer: {
                authorizerId: authorizer2.ref,
            },
        });

        //// (2-3) Delete Meeting
        // apiMeeting.addMethod("DELETE", new LambdaIntegration(lambdaFunctionDeleteMeeting), {
        apiMeeting.addMethod("DELETE", new LambdaIntegration(lambdaFunctionForRestAPI), {
            operationName: `${id}_deleteMeeting`,
            authorizationType: AuthorizationType.CUSTOM,
            authorizer: {
                authorizerId: authorizer2.ref,
            },
        });

        //// (2-4) Get Meeting
        // apiMeeting.addMethod("GET", new LambdaIntegration(lambdaFunctionGetMeeting), {
        apiMeeting.addMethod("GET", new LambdaIntegration(lambdaFunctionForRestAPI), {
            operationName: `${id}_getMeeting`,
            authorizationType: AuthorizationType.CUSTOM,
            authorizer: {
                authorizerId: authorizer2.ref,
            },
        });

        ///// (3) Attendee
        const apiAttendees = apiMeeting.addResource("attendees");
        const apiAttendee = apiAttendees.addResource("{attendeeId}");
        addCorsOptions(apiAttendees);
        addCorsOptions(apiAttendee);

        //// (3-1) Get Attendee
        // apiAttendee.addMethod("GET", new LambdaIntegration(lambdaFunctionGetAttendee), {
        apiAttendee.addMethod("GET", new LambdaIntegration(lambdaFunctionForRestAPI), {
            operationName: `${id}_getAttendee`,
            authorizationType: AuthorizationType.CUSTOM,
            authorizer: {
                authorizerId: authorizer2.ref,
            },
        });

        //// (3-2) Post Attendee
        // apiAttendees.addMethod("POST", new LambdaIntegration(lambdaFunctionPostAttendee), {
        apiAttendees.addMethod("POST", new LambdaIntegration(lambdaFunctionForRestAPI), {
            operationName: `${id}_postAttendee`,
            authorizationType: AuthorizationType.CUSTOM,
            authorizer: {
                authorizerId: authorizer2.ref,
            },
        });
        //// (3-3) List Attendees
        // apiAttendees.addMethod("GET", new LambdaIntegration(lambdaFunctionGetAttendees), {
        apiAttendees.addMethod("GET", new LambdaIntegration(lambdaFunctionForRestAPI), {
            operationName: `${id}_getAttendees`,
            authorizationType: AuthorizationType.CUSTOM,
            authorizer: {
                authorizerId: authorizer2.ref,
            },
        });

        ///// (4) Attendee Operations // Operation under Meeting
        const apiAttendeeOperations = apiAttendee.addResource("operations");
        const apiAttendeeOperation = apiAttendeeOperations.addResource("{operation}");
        addCorsOptions(apiAttendeeOperations);
        addCorsOptions(apiAttendeeOperation);

        //// (4-1) Post Attendee Operation
        // apiAttendeeOperation.addMethod("POST", new LambdaIntegration(lambdaFunctionPostAttendeeOperation), {
        apiAttendeeOperation.addMethod("POST", new LambdaIntegration(lambdaFunctionForRestAPI), {
            operationName: `${id}_postAttendeeOperation`,
            authorizationType: AuthorizationType.CUSTOM,
            authorizer: {
                authorizerId: authorizer2.ref,
            },
        });
        // lambdaFunctionPostAttendeeOperation.addEnvironment("RESTAPI_ENDPOINT", restApi.url) ///// Exception(Circular dependency between resources) -> generate from request context in lambda.

        //// (5) Log
        const apiLogs = restApi.root.addResource("logs");
        addCorsOptions(apiLogs);
        //// (5-1) Post Log
        // apiLogs.addMethod("POST", new LambdaIntegration(lambdaFunctionPostLog), {
        apiLogs.addMethod("POST", new LambdaIntegration(lambdaFunctionForRestAPI), {
            operationName: `${id}_postLog`,
        });

        //// (a) Operation  // Global Operation(before signin)
        const apiOperations = restApi.root.addResource("operations");
        const apiOperation = apiOperations.addResource("{operation}");
        addCorsOptions(apiOperations);
        addCorsOptions(apiOperation);
        //// (a-1) Post Onetime Code Signin Request
        // apiOperation.addMethod("POST", new LambdaIntegration(lambdaFunctionPostOperation), {
        apiOperation.addMethod("POST", new LambdaIntegration(lambdaFunctionForRestAPI), {
            operationName: `${id}_postOperation`,
        });

        ///// (b) slack federation
        const apiSlack = restApi.root.addResource("slack");
        const apiSlackOAuthRedirect = apiSlack.addResource("oauth_redirect");
        const apiSlackInstall = apiSlack.addResource("install");
        const apiSlackEvents = apiSlack.addResource("events");
        const apiSlackApi = apiSlack.addResource("api");
        const apiSlackOperation = apiSlackApi.addResource("{operation}");
        addCorsOptions(apiSlackOAuthRedirect);
        addCorsOptions(apiSlackInstall);
        addCorsOptions(apiSlackEvents);
        addCorsOptions(apiSlackOperation);
        apiSlackOAuthRedirect.addMethod("GET", new LambdaIntegration(lambdaFunctionForSlackFederationRestAPI), {
            operationName: `${id}_slackOAuthRedirect`,
        });
        apiSlackInstall.addMethod("GET", new LambdaIntegration(lambdaFunctionForSlackFederationRestAPI), {
            operationName: `${id}_slackInstall`,
        });
        apiSlackEvents.addMethod("POST", new LambdaIntegration(lambdaFunctionForSlackFederationRestAPI), {
            operationName: `${id}_slackEvents`,
        });
        apiSlackOperation.addMethod("POST", new LambdaIntegration(lambdaFunctionForSlackFederationRestAPI), {
            operationName: `${id}_slackApi`,
        });

        /////////
        // WebSocket
        // https://github.com/aws-samples/aws-cdk-examples/pull/325/files
        ////////

        // API Gateway
        const webSocketApi = new v2.CfnApi(this, "ChimeMessageAPI", {
            name: "ChimeMessageAPI",
            protocolType: "WEBSOCKET",
            routeSelectionExpression: "$request.body.action",
        });

        //// Lambda Function
        // (1) connect
        const lambdaFuncMessageConnect = new lambda.Function(this, "ChimeMessageAPIConnect", {
            code: lambda.Code.fromAsset(`${__dirname}/dist`),
            handler: "message.connect",
            runtime: lambda.Runtime.NODEJS_14_X,
            timeout: Duration.seconds(30),
            memorySize: 256,
        });
        addCommonSetting(lambdaFuncMessageConnect);

        // (2) disconnect
        const lambdaFuncMessageDisconnect = new lambda.Function(this, "ChimeMessageAPIDisconnect", {
            code: lambda.Code.fromAsset(`${__dirname}/dist`),
            handler: "message.disconnect",
            runtime: lambda.Runtime.NODEJS_14_X,
            timeout: Duration.seconds(30),
            memorySize: 256,
        });
        addCommonSetting(lambdaFuncMessageDisconnect);

        // (3) message
        const lambdaFuncMessageMessage = new lambda.Function(this, "ChimeMessageAPIMessage", {
            code: lambda.Code.fromAsset(`${__dirname}/dist`),
            handler: "message.message",
            runtime: lambda.Runtime.NODEJS_14_X,
            timeout: Duration.seconds(30),
            memorySize: 256,
        });
        addCommonSetting(lambdaFuncMessageMessage);

        // (4) auth
        const lambdaFuncMessageAuth = new lambda.Function(this, "ChimeMessageAPIAuth", {
            code: lambda.Code.fromAsset(`${__dirname}/dist`),
            handler: "message.authorize",
            runtime: lambda.Runtime.NODEJS_14_X,
            timeout: Duration.seconds(30),
            memorySize: 256,
        });
        addCommonSetting(lambdaFuncMessageAuth);

        const policy = new PolicyStatement({
            effect: Effect.ALLOW,
            resources: [lambdaFuncMessageConnect.functionArn, lambdaFuncMessageDisconnect.functionArn, lambdaFuncMessageMessage.functionArn, lambdaFuncMessageAuth.functionArn],
            actions: ["lambda:InvokeFunction"],
        });

        const role = new Role(this, `ChimeMessageAPIRole`, {
            assumedBy: new ServicePrincipal("apigateway.amazonaws.com"),
        });
        role.addToPolicy(policy);

        const messageAuthorizer = new v2.CfnAuthorizer(this, "messageAuthorizer", {
            name: `${id}_authorizer`,
            authorizerType: "REQUEST",
            identitySource: [],
            apiId: webSocketApi.ref,
            authorizerUri: `arn:aws:apigateway:${this.region}:lambda:path/2015-03-31/functions/${lambdaFuncMessageAuth.functionArn}/invocations`,
            authorizerCredentialsArn: role.roleArn,
        });

        //// Integration
        const connectIntegration = new v2.CfnIntegration(this, "ChimeMessageAPIConnectIntegration", {
            apiId: webSocketApi.ref,
            integrationType: "AWS_PROXY",
            integrationUri: "arn:aws:apigateway:" + this.region + ":lambda:path/2015-03-31/functions/" + lambdaFuncMessageConnect.functionArn + "/invocations",
            credentialsArn: role.roleArn,
        });

        const disconnectIntegration = new v2.CfnIntegration(this, "ChimeMessageAPIDisconnectIntegration", {
            apiId: webSocketApi.ref,
            integrationType: "AWS_PROXY",
            integrationUri: "arn:aws:apigateway:" + this.region + ":lambda:path/2015-03-31/functions/" + lambdaFuncMessageDisconnect.functionArn + "/invocations",
            credentialsArn: role.roleArn,
        });

        const messageIntegration = new v2.CfnIntegration(this, "ChimeMessageAPIMessageIntegration", {
            apiId: webSocketApi.ref,
            integrationType: "AWS_PROXY",
            integrationUri: "arn:aws:apigateway:" + this.region + ":lambda:path/2015-03-31/functions/" + lambdaFuncMessageMessage.functionArn + "/invocations",
            credentialsArn: role.roleArn,
        });

        // const messageAuthorizer = new CfnAuthorizer(this, 'cfnMessageAuth', {
        //   name: `${id}_messageAuthorizer`,
        //   type: "TOKEN",
        //   restApiId: restApi.restApiId,
        //   identitySource: 'method.request.header.Authorization',
        //   authorizerCredentials: role.roleArn,
        //   authorizerUri: `arn:aws:apigateway:${this.region}:lambda:path/2015-03-31/functions/${lambdaFuncMessageAuth.functionArn}/invocations`,
        // })

        //// Route
        const connectRoute = new v2.CfnRoute(this, "connectRoute", {
            apiId: webSocketApi.ref,
            routeKey: "$connect",
            authorizationType: AuthorizationType.CUSTOM,
            //authorizationType: "NONE",
            target: "integrations/" + connectIntegration.ref,
            authorizerId: messageAuthorizer.ref,
        });

        const disconnectRoute = new v2.CfnRoute(this, "disconnectRoute", {
            apiId: webSocketApi.ref,
            routeKey: "$disconnect",
            authorizationType: "NONE",
            target: "integrations/" + disconnectIntegration.ref,
        });

        const messageRoute = new v2.CfnRoute(this, "messageRoute", {
            apiId: webSocketApi.ref,
            routeKey: "sendmessage",
            authorizationType: "NONE",
            target: "integrations/" + messageIntegration.ref,
        });

        //// Deploy
        const deployment = new v2.CfnDeployment(this, "ChimeMessageAPIDep", {
            apiId: webSocketApi.ref,
        });

        const stage = new v2.CfnStage(this, `ChimeMessageAPIStage`, {
            apiId: webSocketApi.ref,
            autoDeploy: true,
            deploymentId: deployment.ref,
            stageName: "Prod",
        });

        const dependencies = new ConcreteDependable();
        dependencies.add(connectRoute);
        dependencies.add(disconnectRoute);
        dependencies.add(messageRoute);
        deployment.node.addDependency(dependencies);

        /////////////////////////////////////////////
        /// add env info after api gateway
        ////////////////////////////////////////////
        // lambdaFunctionForSlackFederationRestAPI.addEnvironment("RESTAPI_ENDPOINT", restApi.url); /// Exception(Circular dependency between resources) -> generate from request context in lambda.

        ///////////////////////////////
        //// Output
        ///////////////////////////////
        new CfnOutput(this, "UserPoolId", {
            description: "UserPoolId",
            value: userPool.userPoolId,
        });

        new CfnOutput(this, "UserPoolClientId", {
            description: "UserPoolClientId",
            value: userPoolClient.userPoolClientId,
        });

        new CfnOutput(this, "Bucket", {
            description: "Bucket",
            value: bucket.bucketName,
        });

        new CfnOutput(this, "BucketWebsiteDomainName", {
            description: "BucketWebsiteDomainName",
            value: bucket.bucketWebsiteDomainName,
        });

        new CfnOutput(this, "BucketDomainName", {
            description: "BucketDomainName",
            value: bucket.bucketDomainName,
        });

        new CfnOutput(this, "RestAPIEndpoint", {
            description: "RestAPIEndpoint",
            value: restApi.url,
        });

        new CfnOutput(this, "WebSocketEndpoint", {
            description: "WebSocketEndpoint",
            value: webSocketApi.attrApiEndpoint,
        });

        if (USE_CDN) {
            new CfnOutput(this, "CDNComainName", {
                description: "CDNComainName",
                value: cdn!.distributionDomainName,
            });
        }

        //// DEMO URL
        if (USE_CDN) {
            new CfnOutput(this, "DemoEndpoint", {
                description: "DemoEndpoint",
                value: `https://${cdn!.distributionDomainName}`,
            });
            new CfnOutput(this, "DistributionId", {
                description: "DistributionId",
                value: cdn!.distributionId,
            });
        } else {
            new CfnOutput(this, "DemoEndpoint", {
                description: "DemoEndpoint",
                value: `https://${bucket.bucketDomainName}`,
            });
        }

        // new cdk.CfnOutput(this, "AmongLoadBalancerDNS", {
        //   value: lb_among.loadBalancerDnsName
        // });
        // new cdk.CfnOutput(this, "AmongServiceArn", {
        //   value: ecsService_among.serviceArn
        // });
        // new cdk.CfnOutput(this, "AmongServiceName", {
        //   value: ecsService_among.serviceName
        // });
    }
Example #25
Source File: hasura-stack.ts    From hasura-cdk with MIT License 4 votes vote down vote up
constructor(scope: Construct, id: string, props: HasuraStackProps) {
        super(scope, id, props);

        const hostedZone = PublicHostedZone.fromHostedZoneAttributes(this, 'HasuraHostedZone', {
            hostedZoneId: props.hostedZoneId,
            zoneName: props.hostedZoneName,
        });


        const hasuraDatabaseName = props.appName;

        const hasuraDatabase = new DatabaseInstance(this, 'HasuraDatabase', {
            instanceIdentifier: props.appName,
            databaseName: hasuraDatabaseName,
            engine: DatabaseInstanceEngine.POSTGRES,
            instanceType: InstanceType.of(InstanceClass.BURSTABLE3, InstanceSize.MICRO),
            masterUsername: 'syscdk',
            storageEncrypted: true,
            allocatedStorage: 20,
            maxAllocatedStorage: 100,
            vpc: props.vpc,
            deletionProtection: false,
            multiAz: props.multiAz,
            removalPolicy: RemovalPolicy.DESTROY,
        });

        const hasuraUsername = 'hasura';

        const hasuraUserSecret = new DatabaseSecret(this, 'HasuraDatabaseUser', {
            username: hasuraUsername,
            masterSecret: hasuraDatabase.secret,

        });
        hasuraUserSecret.attach(hasuraDatabase); // Adds DB connections information in the secret

        // Output the Endpoint Address so it can be used in post-deploy
        new CfnOutput(this, 'HasuraDatabaseUserSecretArn', {
            value: hasuraUserSecret.secretArn,
        });

        new CfnOutput(this, 'HasuraDatabaseMasterSecretArn', {
            // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
            value: hasuraDatabase.secret!.secretArn,
        });

        const hasuraDatabaseUrlSecret = new Secret(this, 'HasuraDatabaseUrlSecret', {
            secretName: `${props.appName}-HasuraDatabaseUrl`,
        });


        new CfnOutput(this, 'HasuraDatabaseUrlSecretArn', {
            value: hasuraDatabaseUrlSecret.secretArn,
        });

        const hasuraAdminSecret = new Secret(this, 'HasuraAdminSecret', {
            secretName: `${props.appName}-HasuraAdminSecret`,
        });

        new CfnOutput(this, 'HasuraAdminSecretArn', {
            value: hasuraAdminSecret.secretArn,
        });

        const hasuraJwtSecret = new Secret(this, 'HasuraJwtSecret', {
            secretName: `${props.appName}-HasuraJWTSecret`,
        });

        new CfnOutput(this, 'HasuraJwtSecretArn', {
            value: hasuraJwtSecret.secretArn,
        });


        // Create a load-balanced Fargate service and make it public
        const fargate = new ApplicationLoadBalancedFargateService(this, 'HasuraFargateService', {
            serviceName: props.appName,
            vpc: props.vpc,
            cpu: 256,
            desiredCount: props.multiAz ? 2 : 1,
            taskImageOptions: {
                image: ContainerImage.fromRegistry('hasura/graphql-engine:v1.2.1'),
                containerPort: 8080,
                enableLogging: true,
                environment: {
                    HASURA_GRAPHQL_ENABLE_CONSOLE: 'true',
                    HASURA_GRAPHQL_PG_CONNECTIONS: '100',
                    HASURA_GRAPHQL_LOG_LEVEL: 'debug',
                },
                secrets: {
                    HASURA_GRAPHQL_DATABASE_URL: ECSSecret.fromSecretsManager(hasuraDatabaseUrlSecret),
                    HASURA_GRAPHQL_ADMIN_SECRET: ECSSecret.fromSecretsManager(hasuraAdminSecret),
                    HASURA_GRAPHQL_JWT_SECRET: ECSSecret.fromSecretsManager(hasuraJwtSecret),
                },
            },
            memoryLimitMiB: 512,
            publicLoadBalancer: true, // Default is false
            certificate: props.certificates.hasura,
            domainName: props.hasuraHostname,
            domainZone: hostedZone,
            assignPublicIp: true,
        });

        fargate.targetGroup.configureHealthCheck({
            enabled: true,
            path: '/healthz',
            healthyHttpCodes: '200',
        });

        hasuraDatabase.connections.allowFrom(fargate.service, new Port({
            protocol: Protocol.TCP,
            stringRepresentation: 'Postgres Port',
            fromPort: 5432,
            toPort: 5432,
        }));
    }
Example #26
Source File: aws-serverless-wordpress-stack.ts    From aws-serverless-wordpress with Apache License 2.0 4 votes vote down vote up
constructor(scope: cdk.Construct, id: string, props: StackProps) {
        super(scope, id, props);

        if (!props.cloudFrontHashHeader) props.cloudFrontHashHeader = Buffer.from(`${this.stackName}.${props.domainName}`).toString('base64');

        const globalTagKey = 'aws-config:cloudformation:stack-name';
        const globalTagValue = Buffer.from(this.stackName).toString('base64');

        const awsManagedSnsKmsKey = Alias.fromAliasName(this, 'AwsManagedSnsKmsKey', 'alias/aws/sns');

        const publicHostedZone = PublicHostedZone.fromLookup(this, 'ExistingPublicHostedZone', {domainName: props.domainName});

        const acmCertificate = new Certificate(this, 'Certificate', {
            domainName: props.hostname,
            subjectAlternativeNames: props.alternativeHostname,
            validation: CertificateValidation.fromDns(publicHostedZone)
        });

        const staticContentBucket = new Bucket(this, 'StaticContentBucket', {
            encryption: BucketEncryption.S3_MANAGED,
            versioned: true,
            removalPolicy: props.removalPolicy
        });

        const loggingBucket = new Bucket(this, 'LoggingBucket', {
            encryption: BucketEncryption.S3_MANAGED,
            removalPolicy: props.removalPolicy,
            lifecycleRules: [
                {
                    enabled: true,
                    transitions: [
                        {
                            storageClass: StorageClass.INFREQUENT_ACCESS,
                            transitionAfter: Duration.days(30)
                        },
                        {
                            storageClass: StorageClass.DEEP_ARCHIVE,
                            transitionAfter: Duration.days(90)
                        }
                    ]
                }
            ]
        });
        loggingBucket.addToResourcePolicy(new PolicyStatement({
            principals: [new ServicePrincipal('delivery.logs.amazonaws.com'),],
            actions: ['s3:PutObject'],
            resources: [
                `${loggingBucket.bucketArn}/vpc-flow-log/AWSLogs/${this.account}/*`,
                `${loggingBucket.bucketArn}/application-load-balancer/AWSLogs/${this.account}/*`,
                `${loggingBucket.bucketArn}/vpn-admin-application-load-balancer/AWSLogs/${this.account}/*`
            ],
            conditions: {
                StringEquals: {
                    's3:x-amz-acl': 'bucket-owner-full-control'
                }
            }
        }));
        loggingBucket.addToResourcePolicy(new PolicyStatement({
            principals: [new ServicePrincipal('delivery.logs.amazonaws.com'),],
            actions: ['s3:GetBucketAcl'],
            resources: [loggingBucket.bucketArn],
        }));
        loggingBucket.addToResourcePolicy(new PolicyStatement({
            principals: [new ArnPrincipal(`arn:aws:iam::${props.loadBalancerAccountId}:root`)],
            actions: ['s3:PutObject'],
            resources: [
                `${loggingBucket.bucketArn}/application-load-balancer/AWSLogs/${this.account}/*`,
                `${loggingBucket.bucketArn}/vpn-admin-application-load-balancer/AWSLogs/${this.account}/*`
            ]
        }));
        loggingBucket.addToResourcePolicy(new PolicyStatement({
            principals: [new AccountRootPrincipal()],
            actions: ['s3:GetBucketAcl', 's3:PutBucketAcl'],
            resources: [loggingBucket.bucketArn]
        }));

        if (props.removalPolicy === RemovalPolicy.DESTROY) {
            const serviceToken = CustomResourceProvider.getOrCreate(this, 'Custom::EmptyLoggingBucket', {
                codeDirectory: path.join(__dirname, 'custom-resource'),
                runtime: CustomResourceProviderRuntime.NODEJS_12,
                timeout: Duration.minutes(3),
                policyStatements: [(new PolicyStatement({
                    actions: ['s3:ListBucket', 's3:DeleteObject'],
                    resources: [loggingBucket.bucketArn, `${loggingBucket.bucketArn}/*`]
                })).toStatementJson()],
                environment: {
                    LOGGING_BUCKET_NAME: loggingBucket.bucketName
                }
            });
            new CustomResource(this, 'EmptyLoggingBucket', {
                resourceType: 'Custom::EmptyLoggingBucket',
                serviceToken
            });
        }

        const vpc = new Vpc(this, 'Vpc', {
            natGateways: 3,
            maxAzs: 3,
            cidr: '172.16.0.0/16',
            subnetConfiguration: [
                {
                    name: 'Public',
                    subnetType: SubnetType.PUBLIC
                },
                {
                    name: 'Private',
                    subnetType: SubnetType.PRIVATE

                },
                {
                    name: 'Isolated',
                    subnetType: SubnetType.ISOLATED
                }
            ],
            enableDnsHostnames: true,
            enableDnsSupport: true
        });

        const nacl = new NetworkAcl(this, 'NetworkAcl', {vpc});
        nacl.addEntry('AllowAllHttpsFromIpv4', {
            ruleNumber: 100,
            cidr: AclCidr.anyIpv4(),
            traffic: AclTraffic.tcpPort(443),
            direction: TrafficDirection.INGRESS,
            ruleAction: Action.ALLOW
        });
        nacl.addEntry('AllowAllHttpsFromIpv6', {
            ruleNumber: 101,
            cidr: AclCidr.anyIpv6(),
            traffic: AclTraffic.tcpPort(443),
            direction: TrafficDirection.INGRESS,
            ruleAction: Action.ALLOW
        });
        nacl.addEntry('AllowResponseToHttpsRequestToIpv4', {
            ruleNumber: 100,
            cidr: AclCidr.anyIpv4(),
            traffic: AclTraffic.tcpPortRange(1024, 65535),
            direction: TrafficDirection.EGRESS,
            ruleAction: Action.ALLOW
        });
        nacl.addEntry('AllowResponseToHttpsRequestToIpv6', {
            ruleNumber: 101,
            cidr: AclCidr.anyIpv6(),
            traffic: AclTraffic.tcpPortRange(1024, 65535),
            direction: TrafficDirection.EGRESS,
            ruleAction: Action.ALLOW
        });

        new CfnFlowLog(this, 'CfnVpcFlowLog', {
            resourceId: vpc.vpcId,
            resourceType: 'VPC',
            trafficType: 'ALL',
            logDestinationType: 's3',
            logDestination: `${loggingBucket.bucketArn}/vpc-flow-log`
        });

        const privateHostedZone = new PrivateHostedZone(this, 'PrivateHostedZone', {
            vpc,
            zoneName: `${props.hostname}.private`
        });

        const applicationLoadBalancerSecurityGroup = new SecurityGroup(this, 'ApplicationLoadBalancerSecurityGroup', {vpc});
        const vpnApplicationLoadBalancerSecurityGroup = new SecurityGroup(this, 'VpcApplicationLoadBalancer', {vpc});
        const elastiCacheMemcachedSecurityGroup = new SecurityGroup(this, 'ElastiCacheMemcachedSecurityGroup', {vpc});
        const rdsAuroraClusterSecurityGroup = new SecurityGroup(this, 'RdsAuroraClusterSecurityGroup', {vpc});
        const ecsFargateServiceSecurityGroup = new SecurityGroup(this, 'EcsFargateServiceSecurityGroup', {vpc});
        const efsFileSystemSecurityGroup = new SecurityGroup(this, 'EfsFileSystemSecurityGroup', {vpc});
        const elasticsearchDomainSecurityGroup = new SecurityGroup(this, 'ElasticsearchDomainSecurityGroup', {vpc});
        const bastionHostSecurityGroup = new SecurityGroup(this, 'BastionHostSecurityGroup', {vpc});
        const clientVpnSecurityGroup = new SecurityGroup(this, 'ClientVpnSecurityGroup', {vpc});

        applicationLoadBalancerSecurityGroup.addIngressRule(Peer.anyIpv4(), Port.tcp(443));
        vpnApplicationLoadBalancerSecurityGroup.addIngressRule(clientVpnSecurityGroup, Port.tcp(443));
        ecsFargateServiceSecurityGroup.addIngressRule(applicationLoadBalancerSecurityGroup, Port.tcp(80));
        ecsFargateServiceSecurityGroup.addIngressRule(vpnApplicationLoadBalancerSecurityGroup, Port.tcp(80));
        elastiCacheMemcachedSecurityGroup.addIngressRule(ecsFargateServiceSecurityGroup, Port.tcp(11211));
        rdsAuroraClusterSecurityGroup.addIngressRule(ecsFargateServiceSecurityGroup, Port.tcp(3306));
        efsFileSystemSecurityGroup.addIngressRule(ecsFargateServiceSecurityGroup, Port.tcp(2049));
        elasticsearchDomainSecurityGroup.addIngressRule(ecsFargateServiceSecurityGroup, Port.tcp(9300));
        clientVpnSecurityGroup.addIngressRule(Peer.anyIpv4(), Port.udp(1194));

        efsFileSystemSecurityGroup.addIngressRule(bastionHostSecurityGroup, Port.tcp(2049));
        elastiCacheMemcachedSecurityGroup.addIngressRule(bastionHostSecurityGroup, Port.tcp(11211));
        rdsAuroraClusterSecurityGroup.addIngressRule(bastionHostSecurityGroup, Port.tcp(3306));
        elasticsearchDomainSecurityGroup.addIngressRule(bastionHostSecurityGroup, Port.tcp(9300));

        const clientVpn = new CfnClientVpnEndpoint(this, 'ClientVpn', {
            clientCidrBlock: '172.16.252.0/22',
            serverCertificateArn: props.certificate.server,
            connectionLogOptions: {enabled: false},
            transportProtocol: 'udp',
            vpcId: vpc.vpcId,
            vpnPort: 1194,
            securityGroupIds: [clientVpnSecurityGroup.securityGroupId],
            authenticationOptions: [{
                type: 'certificate-authentication',
                mutualAuthentication: {clientRootCertificateChainArn: props.certificate.client}
            }]
        });
        vpc.publicSubnets.forEach((subnet, i) => {
            const _vpnTargetNetworkAssociation = new CfnClientVpnTargetNetworkAssociation(this, `ClientVpnPublicTargetNetworkAssociation${i}`, {
                clientVpnEndpointId: clientVpn.ref,
                subnetId: subnet.subnetId
            });
            const _vpnPublicRoute = new CfnClientVpnRoute(this, `ClientVpnPublicRoute${i}`, {
                clientVpnEndpointId: clientVpn.ref,
                destinationCidrBlock: '0.0.0.0/0',
                targetVpcSubnetId: subnet.subnetId
            });
            _vpnPublicRoute.addDependsOn(_vpnTargetNetworkAssociation);
        });
        new CfnClientVpnAuthorizationRule(this, 'ClientVpnAuthorizationRuleForInternetAccess', {
            clientVpnEndpointId: clientVpn.ref,
            authorizeAllGroups: true,
            targetNetworkCidr: '0.0.0.0/0'
        });
        new CfnClientVpnAuthorizationRule(this, 'ClientVpnAuthorizationRuleForVpcAccess', {
            clientVpnEndpointId: clientVpn.ref,
            authorizeAllGroups: true,
            targetNetworkCidr: vpc.vpcCidrBlock
        });

        const rdsAuroraClusterPasswordSecret = new Secret(this, 'RdsAuroraClusterPasswordSecret', {
            removalPolicy: props.removalPolicy,
            generateSecretString: {excludeCharacters: ` ;+%{}` + `@'"\`/\\#`}
        });

        const rdsAuroraCluster = new ServerlessCluster(this, 'RdsAuroraServerlessCluster', {
            vpc,
            vpcSubnets: {subnetType: SubnetType.ISOLATED},
            securityGroups: [rdsAuroraClusterSecurityGroup],
            engine: DatabaseClusterEngine.AURORA_MYSQL,
            credentials: {
                username: props.databaseCredential.username,
                password: SecretValue.secretsManager(rdsAuroraClusterPasswordSecret.secretArn)
            },
            defaultDatabaseName: props.databaseCredential.defaultDatabaseName,
            deletionProtection: props.resourceDeletionProtection,
            removalPolicy: props.removalPolicy,
            scaling: {
                minCapacity: AuroraCapacityUnit.ACU_1,
                maxCapacity: AuroraCapacityUnit.ACU_16
            },
            backupRetention: Duration.days(7)
        })

        const rdsAuroraClusterPrivateDnsRecord = new CnameRecord(this, 'RdsAuroraClusterPrivateDnsRecord', {
            zone: privateHostedZone,
            recordName: `database.${privateHostedZone.zoneName}`,
            domainName: rdsAuroraCluster.clusterEndpoint.hostname,
            ttl: Duration.hours(1)
        });

        const elastiCacheMemcachedCluster = new CfnCacheCluster(this, 'ElastiCacheMemcachedCluster', {
            cacheNodeType: 'cache.t3.micro',
            engine: 'memcached',
            azMode: 'cross-az',
            numCacheNodes: 3,
            cacheSubnetGroupName: new CfnSubnetGroup(this, 'ElastiCacheMemcachedClusterSubnetGroup', {
                description: 'ElastiCacheMemcachedClusterSubnetGroup',
                subnetIds: vpc.isolatedSubnets.map(subnet => subnet.subnetId)
            }).ref,
            vpcSecurityGroupIds: [elastiCacheMemcachedSecurityGroup.securityGroupId]
        });

        const elastiCacheMemcachedClusterPrivateDnsRecord = new CnameRecord(this, 'ElastiCacheMemcachedClusterPrivateDnsRecord', {
            zone: privateHostedZone,
            recordName: `cache.${privateHostedZone.zoneName}`,
            domainName: elastiCacheMemcachedCluster.attrConfigurationEndpointAddress,
            ttl: Duration.hours(1)
        });

        const elasticsearchServiceLinkRole = new CfnServiceLinkedRole(this, 'CfnElasticsearchServiceLinkRole', {
            awsServiceName: 'es.amazonaws.com'
        });

        const elasticsearchDomain = new Domain(this, 'ElasticsearchDomain', {
            version: ElasticsearchVersion.V7_7,
            capacity: {dataNodes: 3, dataNodeInstanceType: 't3.small.elasticsearch'},
            zoneAwareness: {enabled: true, availabilityZoneCount: 3},
            encryptionAtRest: {enabled: true},
            nodeToNodeEncryption: true,
            ebs: {volumeSize: 10},
            enforceHttps: true,
            vpcOptions: {
                subnets: vpc.isolatedSubnets,
                securityGroups: [elasticsearchDomainSecurityGroup]
            }
        });
        (elasticsearchDomain.node.defaultChild as CfnDomain).addDependsOn(elasticsearchServiceLinkRole);

        const elasticsearchDomainPrivateDnsRecord = new CnameRecord(this, 'ElasticsearchDomainPrivateDnsRecord', {
            zone: privateHostedZone,
            recordName: `search.${privateHostedZone.zoneName}`,
            domainName: elasticsearchDomain.domainEndpoint,
            ttl: Duration.hours(1)
        });

        const fileSystem = new FileSystem(this, 'FileSystem', {
            vpc,
            vpcSubnets: {
                subnetType: SubnetType.ISOLATED
            },
            securityGroup: efsFileSystemSecurityGroup,
            performanceMode: PerformanceMode.GENERAL_PURPOSE,
            lifecyclePolicy: LifecyclePolicy.AFTER_30_DAYS,
            throughputMode: ThroughputMode.BURSTING,
            encrypted: true,
            removalPolicy: props.removalPolicy
        });

        const fileSystemAccessPoint = fileSystem.addAccessPoint('AccessPoint');

        const fileSystemEndpointPrivateDnsRecord = new CnameRecord(this, 'FileSystemEndpointPrivateDnsRecord', {
            zone: privateHostedZone,
            recordName: `nfs.${privateHostedZone.zoneName}`,
            domainName: `${fileSystem.fileSystemId}.efs.${this.region}.amazonaws.com`,
            ttl: Duration.hours(1)
        });

        const bastionHost = new BastionHostLinux(this, 'BastionHost', {
            vpc,
            securityGroup: bastionHostSecurityGroup
        });
        bastionHost.instance.addUserData('mkdir -p /mnt/efs');
        bastionHost.instance.addUserData(`mount -t nfs4 -o nfsvers=4.1,rsize=1048576,wsize=1048576,hard,timeo=600,retrans=2,noresvport ${fileSystemEndpointPrivateDnsRecord.domainName}:/ /mnt/efs `);

        const ecsCluster = new Cluster(this, 'EcsCluster', {
            containerInsights: true,
            vpc
        });
        const _ecsCluster = ecsCluster.node.defaultChild as CfnCluster;
        _ecsCluster.capacityProviders = ['FARGATE', 'FARGATE_SPOT'];
        _ecsCluster.defaultCapacityProviderStrategy = [
            {
                capacityProvider: 'FARGATE',
                weight: 2,
                base: 3
            },
            {
                capacityProvider: 'FARGATE_SPOT',
                weight: 1
            }
        ];

        const applicationLoadBalancer = new ApplicationLoadBalancer(this, 'ApplicationLoadBalancer', {
            vpc,
            deletionProtection: props.resourceDeletionProtection,
            http2Enabled: true,
            internetFacing: true,
            securityGroup: applicationLoadBalancerSecurityGroup,
            vpcSubnets: {subnetType: SubnetType.PUBLIC}
        });
        applicationLoadBalancer.setAttribute('routing.http.drop_invalid_header_fields.enabled', 'true');
        applicationLoadBalancer.setAttribute('access_logs.s3.enabled', 'true');
        applicationLoadBalancer.setAttribute('access_logs.s3.bucket', loggingBucket.bucketName);
        applicationLoadBalancer.setAttribute('access_logs.s3.prefix', 'application-load-balancer');
        applicationLoadBalancer.addListener('HttpListener', {
            port: 80,
            protocol: ApplicationProtocol.HTTP,
            defaultAction: ListenerAction.redirect({protocol: 'HTTPS', port: '443'})
        });

        const httpsListener = applicationLoadBalancer.addListener('HttpsListener', {
            port: 443,
            protocol: ApplicationProtocol.HTTPS,
            certificates: [acmCertificate]
        });

        const vpnAdminApplicationLoadBalancer = new ApplicationLoadBalancer(this, 'VpnAdminApplicationLoadBalancer', {
            vpc,
            deletionProtection: props.resourceDeletionProtection,
            http2Enabled: true,
            internetFacing: false,
            securityGroup: vpnApplicationLoadBalancerSecurityGroup,
            vpcSubnets: {subnetType: SubnetType.PRIVATE}
        });
        vpnAdminApplicationLoadBalancer.setAttribute('routing.http.drop_invalid_header_fields.enabled', 'true');
        vpnAdminApplicationLoadBalancer.setAttribute('access_logs.s3.enabled', 'true');
        vpnAdminApplicationLoadBalancer.setAttribute('access_logs.s3.bucket', loggingBucket.bucketName);
        vpnAdminApplicationLoadBalancer.setAttribute('access_logs.s3.prefix', 'vpn-admin-application-load-balancer');
        vpnAdminApplicationLoadBalancer.addListener('VpnAdminHttpListener', {
            port: 80,
            protocol: ApplicationProtocol.HTTP,
            defaultAction: ListenerAction.redirect({protocol: 'HTTPS', port: '443'})
        });

        const vpnAdminHttpsListener = vpnAdminApplicationLoadBalancer.addListener('VpnAdminHttpsListener', {
            port: 443,
            protocol: ApplicationProtocol.HTTPS,
            certificates: [acmCertificate]
        });

        const wordPressFargateTaskExecutionRole = new Role(this, 'WordpressFargateTaskExecutionRole', {
            assumedBy: new ServicePrincipal('ecs-tasks.amazonaws.com'),
            managedPolicies: [ManagedPolicy.fromAwsManagedPolicyName('service-role/AmazonECSTaskExecutionRolePolicy')]
        });
        const wordPressFargateTaskRole = new Role(this, 'WordpressFargateTaskRole', {
            assumedBy: new ServicePrincipal('ecs-tasks.amazonaws.com'),
            managedPolicies: [ManagedPolicy.fromManagedPolicyArn(this, 'XRayDaemonWriteAccess', 'arn:aws:iam::aws:policy/AWSXRayDaemonWriteAccess')],
            inlinePolicies: {
                _: new PolicyDocument({
                    statements: [
                        new PolicyStatement({
                            actions: ['s3:GetBucketLocation'],
                            resources: [staticContentBucket.bucketArn]
                        })
                    ]
                })
            }
        });
        staticContentBucket.grantReadWrite(wordPressFargateTaskRole);

        const wordPressFargateTaskDefinition = new FargateTaskDefinition(this, 'WordpressFargateTaskDefinition', {
            memoryLimitMiB: 512,
            cpu: 256,
            executionRole: wordPressFargateTaskExecutionRole,
            taskRole: wordPressFargateTaskRole,
        });
        wordPressFargateTaskDefinition.addVolume({
            name: 'WordPressEfsVolume',
            efsVolumeConfiguration: {
                fileSystemId: fileSystem.fileSystemId,
                transitEncryption: 'ENABLED',
                authorizationConfig: {
                    accessPointId: fileSystemAccessPoint.accessPointId
                }
            }
        });

        const wordPressDockerImageAsset = new DockerImageAsset(this, 'WordPressDockerImageAsset', {directory: path.join(__dirname, 'images/wordpress')});
        const nginxDockerImageAsset = new DockerImageAsset(this, 'NginxDockerImageAsset', {directory: path.join(__dirname, 'images/nginx')});

        const wordPressContainer = wordPressFargateTaskDefinition.addContainer('WordPress', {
            image: ContainerImage.fromDockerImageAsset(wordPressDockerImageAsset),
            environment: {
                WORDPRESS_DB_HOST: rdsAuroraClusterPrivateDnsRecord.domainName,
                WORDPRESS_DB_USER: props.databaseCredential.username,
                WORDPRESS_DB_NAME: props.databaseCredential.defaultDatabaseName,
            },
            secrets: {
                WORDPRESS_DB_PASSWORD: EcsSecret.fromSecretsManager(rdsAuroraClusterPasswordSecret)
            },
            logging: LogDriver.awsLogs({
                streamPrefix: `${this.stackName}WordPressContainerLog`,
                logRetention: RetentionDays.ONE_MONTH
            })
        });
        wordPressContainer.addMountPoints({
            readOnly: false,
            containerPath: '/var/www/html',
            sourceVolume: 'WordPressEfsVolume'
        });

        const nginxContainer = wordPressFargateTaskDefinition.addContainer('Nginx', {
            image: ContainerImage.fromDockerImageAsset(nginxDockerImageAsset),
            logging: LogDriver.awsLogs({
                streamPrefix: `${this.stackName}NginxContainerLog`,
                logRetention: RetentionDays.ONE_MONTH
            }),
            environment: {
                SERVER_NAME: props.hostname,
                MEMCACHED_HOST: elastiCacheMemcachedClusterPrivateDnsRecord.domainName,
                NGINX_ENTRYPOINT_QUIET_LOGS: '1'
            }
        });
        nginxContainer.addPortMappings({
            hostPort: 80,
            containerPort: 80,
            protocol: Protocol.TCP
        });
        nginxContainer.addMountPoints({
            readOnly: false,
            containerPath: '/var/www/html',
            sourceVolume: 'WordPressEfsVolume'
        });

        const xrayContainer = wordPressFargateTaskDefinition.addContainer('XRay', {
            image: ContainerImage.fromRegistry('amazon/aws-xray-daemon'),
            logging: LogDriver.awsLogs({
                streamPrefix: `${this.stackName}XRayContainerLog`,
                logRetention: RetentionDays.ONE_MONTH
            }),
            entryPoint: ['/usr/bin/xray', '-b', '127.0.0.1:2000', '-l', 'dev', '-o'],
            user: '1337'
        });
        xrayContainer.addPortMappings({
            containerPort: 2000,
            protocol: Protocol.UDP
        })

        const _wordPressFargateServiceTargetGroup = new CfnTargetGroup(this, 'CfnWordPressFargateServiceTargetGroup', {
            matcher: {
                httpCode: '200,301,302'
            },
            port: 80,
            protocol: 'HTTP',
            targetGroupAttributes: [
                {
                    key: 'stickiness.enabled',
                    value: 'true'
                },
                {
                    key: 'stickiness.type',
                    value: 'lb_cookie'
                },
                {
                    key: 'stickiness.lb_cookie.duration_seconds',
                    value: '604800'
                }
            ],
            targetType: 'ip',
            vpcId: vpc.vpcId,
            unhealthyThresholdCount: 5,
            healthCheckTimeoutSeconds: 45,
            healthCheckIntervalSeconds: 60,
        });

        const wordPressFargateServiceTargetGroup = ApplicationTargetGroup.fromTargetGroupAttributes(this, 'WordPressFargateServiceTargetGroup', {
            loadBalancerArns: applicationLoadBalancer.loadBalancerArn,
            targetGroupArn: _wordPressFargateServiceTargetGroup.ref
        });
        httpsListener.addTargetGroups('WordPress', {targetGroups: [wordPressFargateServiceTargetGroup]});

        const _wordPressVpnAdminFargateServiceTargetGroup = new CfnTargetGroup(this, 'CfnWordPressVpnAdminFargateServiceTargetGroup', {
            matcher: {
                httpCode: '200,301,302'
            },
            port: 80,
            protocol: 'HTTP',
            targetGroupAttributes: [
                {
                    key: 'stickiness.enabled',
                    value: 'true'
                },
                {
                    key: 'stickiness.type',
                    value: 'lb_cookie'
                },
                {
                    key: 'stickiness.lb_cookie.duration_seconds',
                    value: '604800'
                }
            ],
            targetType: 'ip',
            vpcId: vpc.vpcId,
            unhealthyThresholdCount: 5,
            healthCheckTimeoutSeconds: 45,
            healthCheckIntervalSeconds: 60,
        });

        const wordPressVpnAdminFargateServiceTargetGroup = ApplicationTargetGroup.fromTargetGroupAttributes(this, 'WordPressVpnAdminFargateServiceTargetGroup', {
            loadBalancerArns: vpnAdminApplicationLoadBalancer.loadBalancerArn,
            targetGroupArn: _wordPressVpnAdminFargateServiceTargetGroup.ref
        });
        vpnAdminHttpsListener.addTargetGroups('WordPressVpnAdmin', {targetGroups: [wordPressVpnAdminFargateServiceTargetGroup]});

        const _wordPressFargateService = new CfnService(this, 'CfnWordPressFargateService', {
            cluster: ecsCluster.clusterArn,
            desiredCount: 3,
            deploymentConfiguration: {
                maximumPercent: 200,
                minimumHealthyPercent: 50
            },
            deploymentController: {
                type: 'ECS'
            },
            healthCheckGracePeriodSeconds: 60,
            loadBalancers: [
                {
                    containerName: nginxContainer.containerName,
                    containerPort: 80,
                    targetGroupArn: wordPressFargateServiceTargetGroup.targetGroupArn
                },
                {
                    containerName: nginxContainer.containerName,
                    containerPort: 80,
                    targetGroupArn: wordPressVpnAdminFargateServiceTargetGroup.targetGroupArn
                }
            ],
            networkConfiguration: {
                awsvpcConfiguration: {
                    assignPublicIp: 'DISABLED',
                    securityGroups: [ecsFargateServiceSecurityGroup.securityGroupId],
                    subnets: vpc.privateSubnets.map(subnet => subnet.subnetId)
                }
            },
            platformVersion: '1.4.0',
            taskDefinition: wordPressFargateTaskDefinition.taskDefinitionArn
        });
        _wordPressFargateService.addOverride('DependsOn', [
            this.getLogicalId(httpsListener.node.defaultChild as CfnListener),
            this.getLogicalId(vpnAdminHttpsListener.node.defaultChild as CfnListener)
        ]);

        const wordPressServiceScaling = new ScalableTarget(this, 'WordPressFargateServiceScaling', {
            scalableDimension: 'ecs:service:DesiredCount',
            minCapacity: 3,
            maxCapacity: 300,
            serviceNamespace: ServiceNamespace.ECS,
            resourceId: `service/${ecsCluster.clusterName}/${_wordPressFargateService.attrName}`
        });

        wordPressServiceScaling.scaleToTrackMetric('RequestCountPerTarget', {
            predefinedMetric: PredefinedMetric.ALB_REQUEST_COUNT_PER_TARGET,
            resourceLabel: `${applicationLoadBalancer.loadBalancerFullName}/${_wordPressFargateServiceTargetGroup.attrTargetGroupFullName}`,
            targetValue: 4096,
            scaleInCooldown: Duration.minutes(5),
            scaleOutCooldown: Duration.minutes(5)
        });

        wordPressServiceScaling.scaleToTrackMetric('TargetResponseTime', {
            customMetric: applicationLoadBalancer.metricTargetResponseTime(),
            targetValue: 4,
            scaleInCooldown: Duration.minutes(3),
            scaleOutCooldown: Duration.minutes(3)
        });

        const adminWhitelistIpSet = new CfnIPSet(this, 'AdminWhitelistIpSet', {
            addresses: [...props.whitelistIpAddress],
            scope: 'REGIONAL',
            ipAddressVersion: 'IPV4'
        });

        const wordPressCloudFrontDistributionWafWebAcl = new CfnWebACL(this, 'WordPressCloudFrontDistributionWafWebAcl', {
            defaultAction: {allow: {}},
            scope: 'CLOUDFRONT',
            visibilityConfig: {
                sampledRequestsEnabled: true,
                cloudWatchMetricsEnabled: true,
                metricName: 'CloudFrontDistributionWebAclMetric'
            },
            rules: [
                {
                    name: 'RuleWithAWSManagedRulesCommonRuleSet',
                    priority: 0,
                    overrideAction: {none: {}},
                    visibilityConfig: {
                        sampledRequestsEnabled: true,
                        cloudWatchMetricsEnabled: true,
                        metricName: 'CommonRuleSetMetric'
                    },
                    statement: {
                        managedRuleGroupStatement: {
                            vendorName: 'AWS',
                            name: 'AWSManagedRulesCommonRuleSet',
                            excludedRules: [{name: 'SizeRestrictions_BODY'}, {name: 'GenericRFI_BODY'}, {name: 'GenericRFI_URIPATH'}, {name: 'GenericRFI_QUERYARGUMENTS'}]
                        }
                    }
                },
                {
                    name: 'RuleWithAWSManagedRulesKnownBadInputsRuleSet',
                    priority: 1,
                    overrideAction: {none: {}},
                    visibilityConfig: {
                        sampledRequestsEnabled: true,
                        cloudWatchMetricsEnabled: true,
                        metricName: 'KnownBadInputsRuleSetMetric'
                    },
                    statement: {
                        managedRuleGroupStatement: {
                            vendorName: 'AWS',
                            name: 'AWSManagedRulesKnownBadInputsRuleSet',
                            excludedRules: []
                        }
                    }
                },
                {
                    name: 'RuleWithAWSManagedRulesWordPressRuleSet',
                    priority: 2,
                    overrideAction: {none: {}},
                    visibilityConfig: {
                        sampledRequestsEnabled: true,
                        cloudWatchMetricsEnabled: true,
                        metricName: 'WordPressRuleSetMetric'
                    },
                    statement: {
                        managedRuleGroupStatement: {
                            vendorName: 'AWS',
                            name: 'AWSManagedRulesWordPressRuleSet',
                            excludedRules: []
                        }
                    }
                },
                {
                    name: 'RuleWithAWSManagedRulesPHPRuleSet',
                    priority: 3,
                    overrideAction: {none: {}},
                    visibilityConfig: {
                        sampledRequestsEnabled: true,
                        cloudWatchMetricsEnabled: true,
                        metricName: 'PHPRuleSetMetric'
                    },
                    statement: {
                        managedRuleGroupStatement: {
                            vendorName: 'AWS',
                            name: 'AWSManagedRulesPHPRuleSet',
                            excludedRules: []
                        }
                    }
                },
                {
                    name: 'RuleWithAWSManagedRulesSQLiRuleSet',
                    priority: 4,
                    overrideAction: {none: {}},
                    visibilityConfig: {
                        sampledRequestsEnabled: true,
                        cloudWatchMetricsEnabled: true,
                        metricName: 'AWSManagedRulesSQLiRuleSetMetric'
                    },
                    statement: {
                        managedRuleGroupStatement: {
                            vendorName: 'AWS',
                            name: 'AWSManagedRulesSQLiRuleSet',
                            excludedRules: []
                        }
                    }
                },
                {
                    name: 'RuleWithAWSManagedRulesAmazonIpReputationList',
                    priority: 5,
                    overrideAction: {none: {}},
                    visibilityConfig: {
                        sampledRequestsEnabled: true,
                        cloudWatchMetricsEnabled: true,
                        metricName: 'AmazonIpReputationListMetric'
                    },
                    statement: {
                        managedRuleGroupStatement: {
                            vendorName: 'AWS',
                            name: 'AWSManagedRulesAmazonIpReputationList',
                            excludedRules: []
                        }
                    }
                }
            ]
        });

        const applicationLoadBalancerWebAcl = new CfnWebACL(this, 'ApplicationLoadBalancerWafWebAcl', {
            defaultAction: {block: {}},
            scope: 'REGIONAL',
            visibilityConfig: {
                sampledRequestsEnabled: true,
                cloudWatchMetricsEnabled: true,
                metricName: 'ApplicationLoadBalancerWebAclMetric'
            },
            rules: [
                {
                    name: 'RuleToAllowNonAdminRequest',
                    priority: 0,
                    action: {allow: {}},
                    visibilityConfig: {
                        sampledRequestsEnabled: true,
                        cloudWatchMetricsEnabled: true,
                        metricName: 'RuleToAllowNonAdminRequestMetric'
                    },
                    statement: {
                        andStatement: {
                            statements: [
                                {
                                    notStatement: {
                                        statement: {
                                            byteMatchStatement: {
                                                fieldToMatch: {uriPath: {}},
                                                positionalConstraint: "STARTS_WITH",
                                                searchString: '/wp-admin',
                                                textTransformations: [{type: 'NONE', priority: 0}]
                                            }
                                        }
                                    }
                                },
                                {
                                    byteMatchStatement: {
                                        fieldToMatch: {
                                            singleHeader: {
                                                Name: 'X_Request_From_CloudFront'
                                            }
                                        },
                                        positionalConstraint: 'EXACTLY',
                                        searchString: props.cloudFrontHashHeader,
                                        textTransformations: [{type: 'NONE', priority: 0}]
                                    }
                                }
                            ]
                        }
                    }
                },
                {
                    name: 'RuleToAllowRequestWhitelistedIpSourceToAdminPage',
                    priority: 1,
                    action: {allow: {}},
                    visibilityConfig: {
                        sampledRequestsEnabled: true,
                        cloudWatchMetricsEnabled: true,
                        metricName: 'RuleToAllowRequestWhitelistedIpSourceToAdminPageMetric'
                    },
                    statement: {
                        andStatement: {
                            statements: [
                                {
                                    byteMatchStatement: {
                                        fieldToMatch: {
                                            singleHeader: {
                                                Name: 'X_Request_From_CloudFront'
                                            }
                                        },
                                        positionalConstraint: 'EXACTLY',
                                        searchString: props.cloudFrontHashHeader,
                                        textTransformations: [{type: 'NONE', priority: 0}]
                                    }
                                },
                                {
                                    byteMatchStatement: {
                                        fieldToMatch: {uriPath: {}},
                                        positionalConstraint: "STARTS_WITH",
                                        searchString: '/wp-admin',
                                        textTransformations: [{type: 'NONE', priority: 0}]
                                    }
                                },
                                {
                                    ipSetReferenceStatement: {
                                        arn: adminWhitelistIpSet.attrArn,
                                        ipSetForwardedIpConfig: {
                                            headerName: 'X-Forwarded-For',
                                            position: 'ANY',
                                            fallbackBehavior: 'NO_MATCH'
                                        }
                                    }
                                }
                            ]
                        }
                    }
                }
            ]
        });

        new CfnWebACLAssociation(this, 'ApplicationLoadBalancerWafWebAclAssociation', {
            resourceArn: applicationLoadBalancer.loadBalancerArn,
            webAclArn: applicationLoadBalancerWebAcl.attrArn
        });

        const wordPressDistribution = new CloudFrontWebDistribution(this, 'WordPressDistribution', {
            originConfigs: [
                {
                    customOriginSource: {
                        domainName: applicationLoadBalancer.loadBalancerDnsName,
                        originProtocolPolicy: OriginProtocolPolicy.HTTPS_ONLY,
                        originReadTimeout: Duration.minutes(1),
                        originHeaders: {
                            'X_Request_From_CloudFront': props.cloudFrontHashHeader
                        }
                    },
                    behaviors: [
                        {
                            isDefaultBehavior: true,
                            forwardedValues: {
                                queryString: true,
                                cookies: {
                                    forward: 'whitelist',
                                    whitelistedNames: [
                                        'comment_*',
                                        'wordpress_*',
                                        'wp-settings-*'
                                    ]
                                },
                                headers: [
                                    'Host',
                                    'CloudFront-Forwarded-Proto',
                                    'CloudFront-Is-Mobile-Viewer',
                                    'CloudFront-Is-Tablet-Viewer',
                                    'CloudFront-Is-Desktop-Viewer'
                                ]
                            },
                            cachedMethods: CloudFrontAllowedCachedMethods.GET_HEAD_OPTIONS,
                            allowedMethods: CloudFrontAllowedMethods.ALL
                        },
                        {
                            pathPattern: 'wp-admin/*',
                            forwardedValues: {
                                queryString: true,
                                cookies: {
                                    forward: 'all'
                                },
                                headers: ['*']
                            },
                            cachedMethods: CloudFrontAllowedCachedMethods.GET_HEAD_OPTIONS,
                            allowedMethods: CloudFrontAllowedMethods.ALL
                        },
                        {
                            pathPattern: 'wp-login.php',
                            forwardedValues: {
                                queryString: true,
                                cookies: {
                                    forward: 'all'
                                },
                                headers: ['*']
                            },
                            cachedMethods: CloudFrontAllowedCachedMethods.GET_HEAD_OPTIONS,
                            allowedMethods: CloudFrontAllowedMethods.ALL
                        }
                    ]
                }
            ],
            priceClass: PriceClass.PRICE_CLASS_ALL,
            viewerProtocolPolicy: ViewerProtocolPolicy.REDIRECT_TO_HTTPS,
            httpVersion: HttpVersion.HTTP2,
            defaultRootObject: '',
            viewerCertificate: ViewerCertificate.fromAcmCertificate(acmCertificate, {aliases: [props.hostname]}),
            webACLId: wordPressCloudFrontDistributionWafWebAcl.attrArn,
            loggingConfig: {
                bucket: loggingBucket,
                prefix: 'wordpress-distribution'
            }
        });
        (wordPressDistribution.node.defaultChild as CfnDistribution).addDependsOn(wordPressCloudFrontDistributionWafWebAcl);

        const staticContentBucketOriginAccessIdentity = new OriginAccessIdentity(this, 'StaticContentBucketOriginAccessIdentity');
        staticContentBucket.grantRead(staticContentBucketOriginAccessIdentity);

        const staticContentDistribution = new CloudFrontWebDistribution(this, 'StaticContentDistribution', {
            originConfigs: [
                {
                    s3OriginSource: {
                        s3BucketSource: staticContentBucket,
                        originAccessIdentity: staticContentBucketOriginAccessIdentity
                    },
                    behaviors: [
                        {
                            isDefaultBehavior: true,
                            forwardedValues: {
                                queryString: true,
                                cookies: {
                                    forward: 'none'
                                }
                            },
                            cachedMethods: CloudFrontAllowedCachedMethods.GET_HEAD,
                            allowedMethods: CloudFrontAllowedMethods.GET_HEAD
                        }
                    ]
                },
            ],
            priceClass: PriceClass.PRICE_CLASS_ALL,
            viewerProtocolPolicy: ViewerProtocolPolicy.REDIRECT_TO_HTTPS,
            httpVersion: HttpVersion.HTTP2,
            defaultRootObject: '',
            viewerCertificate: ViewerCertificate.fromAcmCertificate(acmCertificate, {aliases: [`static.${props.hostname}`]}),
            loggingConfig: {
                bucket: loggingBucket,
                prefix: 'static-content-distribution'
            }
        });


        const backupVault = new BackupVault(this, 'BackupVault', {
            backupVaultName: 'AwsServerlessWordPressBackupVault',
            removalPolicy: props.removalPolicy
        });

        const backupPlan = BackupPlan.dailyMonthly1YearRetention(this, 'BackupPlan', backupVault);

        backupPlan.addSelection('BackupPlanSelection', {
            resources: [
                BackupResource.fromEfsFileSystem(fileSystem),
                BackupResource.fromArn(this.formatArn({
                    resource: 'cluster',
                    service: 'rds',
                    sep: ':',
                    resourceName: rdsAuroraCluster.clusterIdentifier
                }))
            ]
        });

        const awsConfigOnComplianceSnsTopic = new Topic(this, 'AwsConfigOnComplianceSnsTopic', {masterKey: awsManagedSnsKmsKey});
        props.snsEmailSubscription.forEach(email => awsConfigOnComplianceSnsTopic.addSubscription(new EmailSubscription(email)));

        const configurationRecorderRole = new Role(this, 'ConfigurationRole', {
            assumedBy: new ServicePrincipal('config.amazonaws.com')
        });
        configurationRecorderRole.addToPolicy(new PolicyStatement({
            actions: ['s3:PutObject'],
            resources: [`${loggingBucket.bucketArn}/config/AWSLogs/${this.account}/*`],
            conditions: {
                StringLike: {
                    "s3:x-amz-acl": "bucket-owner-full-control"
                }
            }
        }));
        configurationRecorderRole.addToPolicy(new PolicyStatement({
            actions: ['s3:GetBucketAcl'],
            resources: [loggingBucket.bucketArn]
        }))

        const configurationRecorder = new CfnConfigurationRecorder(this, 'ConfigurationRecorder', {
            recordingGroup: {
                allSupported: true,
                includeGlobalResourceTypes: true
            },
            roleArn: configurationRecorderRole.roleArn
        });

        const deliveryChannel = new CfnDeliveryChannel(this, 'DeliveryChannel', {
            configSnapshotDeliveryProperties: {
                deliveryFrequency: 'One_Hour'
            },
            s3BucketName: loggingBucket.bucketName,
            s3KeyPrefix: 'config'
        });
        const ruleScope = RuleScope.fromTag(globalTagKey, globalTagValue);
        const awsConfigManagesRules = [
            new ManagedRule(this, 'AwsConfigManagedRuleVpcFlowLogsEnabled', {
                identifier: 'VPC_FLOW_LOGS_ENABLED',
                inputParameters: {trafficType: 'ALL'},
                ruleScope
            }),
            new ManagedRule(this, 'AwsConfigManagedRuleVpcSgOpenOnlyToAuthorizedPorts', {
                identifier: 'VPC_SG_OPEN_ONLY_TO_AUTHORIZED_PORTS',
                inputParameters: {authorizedTcpPorts: '443'},
                ruleScope
            }),
            new ManagedRule(this, 'AwsConfigManagedRuleInternetGatewayAuthorizedVpcOnly', {
                identifier: 'INTERNET_GATEWAY_AUTHORIZED_VPC_ONLY',
                inputParameters: {AuthorizedVpcIds: vpc.vpcId},
                ruleScope
            }),
            new ManagedRule(this, 'AwsConfigManagedRuleAcmCertificateExpirationCheck', {
                identifier: 'ACM_CERTIFICATE_EXPIRATION_CHECK',
                inputParameters: {daysToExpiration: 90},
                ruleScope
            }),
            new ManagedRule(this, 'AwsConfigManagedRuleAutoScalingGroupElbHealthcheckRequired', {
                identifier: 'AUTOSCALING_GROUP_ELB_HEALTHCHECK_REQUIRED',
                ruleScope
            }),
            new ManagedRule(this, 'AwsConfigManagedRuleIncomingSshDisabled', {
                identifier: 'INCOMING_SSH_DISABLED',
                ruleScope
            }),
            new ManagedRule(this, 'AwsConfigManagedRuleSnsEncryptedKms', {
                identifier: 'SNS_ENCRYPTED_KMS',
                ruleScope
            }),
            new ManagedRule(this, 'AwsConfigManagedRuleElbDeletionProtection', {
                identifier: 'ELB_DELETION_PROTECTION_ENABLED',
                ruleScope
            }),
            new ManagedRule(this, 'AwsConfigManagedRuleElbLoggingEnabled', {
                identifier: 'ELB_LOGGING_ENABLED',
                inputParameters: {s3BucketNames: loggingBucket.bucketName},
                ruleScope
            }),
            new ManagedRule(this, 'AwsConfigManagedRuleAlbHttpDropInvalidHeaderEnabled', {
                identifier: 'ALB_HTTP_DROP_INVALID_HEADER_ENABLED',
                ruleScope
            }),
            new ManagedRule(this, 'AwsConfigManagedRuleAlbHttpToHttpsRedirectionCheck', {
                identifier: 'ALB_HTTP_TO_HTTPS_REDIRECTION_CHECK',
                ruleScope
            }),
            new ManagedRule(this, 'AwsConfigManagedRuleAlbWafEnabled', {
                identifier: 'ALB_WAF_ENABLED',
                inputParameters: {wafWebAclIds: applicationLoadBalancerWebAcl.attrArn},
                ruleScope
            }),
            new ManagedRule(this, 'AwsConfigManagedRuleCloudFrontOriginAccessIdentityEnabled', {
                identifier: 'CLOUDFRONT_ORIGIN_ACCESS_IDENTITY_ENABLED',
                ruleScope
            }),
            new ManagedRule(this, 'AwsConfigManagedRuleCloudFrontViewerPolicyHttps', {
                identifier: 'CLOUDFRONT_VIEWER_POLICY_HTTPS',
                ruleScope
            }),
            new ManagedRule(this, 'AwsConfigManagedRuleEfsInBackupPlan', {
                identifier: 'EFS_IN_BACKUP_PLAN',
                ruleScope
            }),
            new ManagedRule(this, 'AwsConfigManagedRuleEfsEncryptedCheck', {
                identifier: 'EFS_ENCRYPTED_CHECK',
                ruleScope
            }),
            new ManagedRule(this, 'AwsConfigManagedRuleRdsClusterDeletionProtectionEnabled', {
                identifier: 'RDS_CLUSTER_DELETION_PROTECTION_ENABLED',
                ruleScope
            }),
            new ManagedRule(this, 'AwsConfigManagedRuleEdsInBackupPlan', {
                identifier: 'RDS_IN_BACKUP_PLAN',
                ruleScope
            }),
            new ManagedRule(this, 'AwsConfigManagedRuleS3BucketPublicReadProhibited', {
                identifier: 'S3_BUCKET_PUBLIC_READ_PROHIBITED',
                ruleScope
            }),
            new ManagedRule(this, 'AwsConfigManagedRuleS3BucketPublicWriteProhibited', {
                identifier: 'S3_BUCKET_PUBLIC_WRITE_PROHIBITED',
                ruleScope
            })
        ]
        awsConfigManagesRules.forEach(rule => {
            rule.onComplianceChange('TopicEvent', {target: new SnsTopic(awsConfigOnComplianceSnsTopic)});
            (rule.node.defaultChild as CfnConfigRule).addDependsOn(configurationRecorder);
            (rule.node.defaultChild as CfnConfigRule).addDependsOn(deliveryChannel);
        });

        const awsConfigCloudFormationStackDriftDetectionCheckRule = new CloudFormationStackDriftDetectionCheck(this, 'AwsConfigCloudFormationStackDriftDetectionCheck', {ownStackOnly: true});
        awsConfigCloudFormationStackDriftDetectionCheckRule.onComplianceChange('TopicEvent', {target: new SnsTopic(awsConfigOnComplianceSnsTopic)});
        (awsConfigCloudFormationStackDriftDetectionCheckRule.node.defaultChild as CfnConfigRule).addDependsOn(configurationRecorder);
        (awsConfigCloudFormationStackDriftDetectionCheckRule.node.defaultChild as CfnConfigRule).addDependsOn(deliveryChannel);

        new CfnGroup(this, 'ResourceGroup', {
            name: 'ServerlessWordPressResourceGroup',
            resourceQuery: {
                type: 'TAG_FILTERS_1_0',
                query: {
                    resourceTypeFilters: ['AWS::AllSupported'],
                    tagFilters: [
                        {
                            key: globalTagKey,
                            values: [globalTagValue]
                        }
                    ]
                }
            }
        });

        const rootDnsRecord = new ARecord(this, 'RootDnsRecord', {
            zone: publicHostedZone,
            recordName: props.hostname,
            target: RecordTarget.fromAlias(new CloudFrontTarget(wordPressDistribution))
        });

        const vpnAdminPublicRecord = new ARecord(this, 'VpnAdminPublicDnsRecord', {
            zone: publicHostedZone,
            recordName: `admin.${props.hostname}`,
            target: RecordTarget.fromAlias(new LoadBalancerTarget(vpnAdminApplicationLoadBalancer))
        });

        const vpnAdminPrivateRecord = new ARecord(this, 'VpnAdminPrivateDnsRecord', {
            zone: privateHostedZone,
            recordName: `admin.${privateHostedZone.zoneName}`,
            target: RecordTarget.fromAlias(new LoadBalancerTarget(vpnAdminApplicationLoadBalancer))
        });

        const staticContentDnsRecord = new ARecord(this, 'StaticContentDnsRecord', {
            zone: publicHostedZone,
            recordName: `static.${props.hostname}`,
            target: RecordTarget.fromAlias(new CloudFrontTarget(staticContentDistribution))
        });

        new CfnOutput(this, 'RootHostname', {
            value: rootDnsRecord.domainName
        });

        new CfnOutput(this, 'VpnAdminPublicHostname', {
            value: vpnAdminPublicRecord.domainName
        });

        new CfnOutput(this, 'VpnAdminPrivateHostname', {
            value: vpnAdminPrivateRecord.domainName
        });

        new CfnOutput(this, 'StaticContentHostname', {
            value: staticContentDnsRecord.domainName
        });

        new CfnOutput(this, 'RdsAuroraServerlessClusterPrivateHostname', {
            value: rdsAuroraClusterPrivateDnsRecord.domainName
        });

        new CfnOutput(this, 'ElastiCacheMemcachedClusterPrivateHostname', {
            value: elastiCacheMemcachedClusterPrivateDnsRecord.domainName
        });

        new CfnOutput(this, 'ElasticsearchDomainPrivateHostname', {
            value: elasticsearchDomainPrivateDnsRecord.domainName
        });

        new CfnOutput(this, 'EfsFileSystemPrivateHostname', {
            value: fileSystemEndpointPrivateDnsRecord.domainName
        });
    }
Example #27
Source File: cdk-stack.ts    From laravel-on-aws-ecs-workshops with Apache License 2.0 4 votes vote down vote up
constructor(scope: cdk.Construct, id: string, props?: cdk.StackProps) {
    super(scope, id, props);


    //---------------------------------------------------------------------------
    // VPC
    const vpc = new ec2.Vpc(this, generateName('VPC'), {
      maxAzs: 2,
      natGateways: 1,
      subnetConfiguration: [
        {
          cidrMask: 24,
          name: generateName('ingress'),
          subnetType: ec2.SubnetType.PUBLIC,
        },
        {
          cidrMask: 24,
          name: generateName('application'),
          subnetType: ec2.SubnetType.PRIVATE,
        },
        {
          cidrMask: 24,
          name: generateName('database'),
          subnetType: ec2.SubnetType.ISOLATED,
        },
      ],
    });

    //---------------------------------------------------------------------------
    // ECS

    // ECS Cluster
    const ecsCluster = new ecs.Cluster(this, 'LaravelWorkshopCluster', {
      vpc: vpc
    });
    const asg = ecsCluster.addCapacity('DefaultAutoScalingGroup', {
      instanceType: ec2.InstanceType.of(ec2.InstanceClass.T3, ec2.InstanceSize.SMALL),
      maxCapacity: 4,
      minCapacity: 2,
    });

    // TODO: T4g

    // Task Definition
    const ec2TaskDefinition = new ecs.Ec2TaskDefinition(this, 'DefaultTaskDef');

    const ecrRepo = ecr.Repository.fromRepositoryName(this, 'DefaultRepo', 'my-laravel-on-aws-ecs-workshop-dev');

    const container = ec2TaskDefinition.addContainer('defaultContainer', {
      image: ecs.ContainerImage.fromEcrRepository(ecrRepo), 
      memoryLimitMiB: 512,
      cpu: 256,
    });

    container.addPortMappings({
      containerPort: 80,
      protocol: ecs.Protocol.TCP
    });

    // ECS Service
    const ecsService = new ecs.Ec2Service(this, 'DefaultService', {
      cluster: ecsCluster,
      taskDefinition: ec2TaskDefinition,
      desiredCount: 2,
    });

    //---------------------------------------------------------------------------
    // Cert
    const domainAlternativeName = '*.' + deploymentEnv.domainName;
    const cert = new acm.Certificate(this, generateName('Cert'), {
      domainName: deploymentEnv.domainName,
      subjectAlternativeNames: [domainAlternativeName],
      validation: acm.CertificateValidation.fromDns(), // Records must be added manually
    });

    //---------------------------------------------------------------------------
    // ALB
    const alb = new elbv2.ApplicationLoadBalancer(this, generateName('ALB'), {
      vpc,
      internetFacing: true,
    });

    const listener = alb.addListener('Listener', {
      open: true,
      certificates: [cert],
      protocol: elbv2.ApplicationProtocol.HTTPS,
    });

    // Connect ecsService to TargetGroup
    const targetGroup = listener.addTargets(generateName('LaravelTargetGroup'), {
      protocol: elbv2.ApplicationProtocol.HTTP,
      targets: [ecsService]
    });

    new cdk.CfnOutput(this, generateName('AlbDnsName'), {
      exportName: generateName('AlbDnsName'),
      value: alb.loadBalancerDnsName,
    });

    new cdk.CfnOutput(this, generateName('ActionCname'), {
      exportName: generateName('ActionCname'),
      value: 'Please setup a CNAME record mylaravel.' + deploymentEnv.domainName + ' to ' + alb.loadBalancerDnsName,
    });    

    new cdk.CfnOutput(this, generateName('ActionVisit'), {
      exportName: generateName('ActionVisit'),
      value: 'Visit https://mylaravel.' + deploymentEnv.domainName,
    });      

    //---------------------------------------------------------------------------
    // ecsService: Application Auto Scaling    
    const scaling = ecsService.autoScaleTaskCount({ 
      minCapacity: 2,
      maxCapacity: 10 
    });

    scaling.scaleOnCpuUtilization('CpuScaling', {
      targetUtilizationPercent: 50
    });
    
    scaling.scaleOnRequestCount('RequestScaling', {
      requestsPerTarget: 30,
      targetGroup: targetGroup
    });
  }
Example #28
Source File: cdk-stack.ts    From laravel-on-aws-ecs-workshops with Apache License 2.0 4 votes vote down vote up
constructor(scope: cdk.Construct, id: string, props?: cdk.StackProps) {
    super(scope, id, props);


    //---------------------------------------------------------------------------
    // VPC
    const vpc = new ec2.Vpc(this, generateName('VPC'), {
      maxAzs: 2,
      natGateways: 1,
      subnetConfiguration: [
        {
          cidrMask: 24,
          name: generateName('ingress'),
          subnetType: ec2.SubnetType.PUBLIC,
        },
        {
          cidrMask: 24,
          name: generateName('application'),
          subnetType: ec2.SubnetType.PRIVATE,
        },
        {
          cidrMask: 24,
          name: generateName('database'),
          subnetType: ec2.SubnetType.ISOLATED,
        },
      ],
    });

    //---------------------------------------------------------------------------
    // ECS

    // ECS Cluster
    const ecsCluster = new ecs.Cluster(this, 'LaravelWorkshopCluster', {
      vpc: vpc
    });
    const asg = ecsCluster.addCapacity('DefaultAutoScalingGroup', {
      instanceType: ec2.InstanceType.of(ec2.InstanceClass.T3, ec2.InstanceSize.SMALL),
      maxCapacity: 4,
      minCapacity: 2,
    });

    // TODO: T4g

    // Task Definition
    const ec2TaskDefinition = new ecs.Ec2TaskDefinition(this, 'DefaultTaskDef');

    const ecrRepo = ecr.Repository.fromRepositoryName(this, 'DefaultRepo', 'my-laravel-on-aws-ecs-workshop-dev');

    const container = ec2TaskDefinition.addContainer('defaultContainer', {
      image: ecs.ContainerImage.fromEcrRepository(ecrRepo), 
      memoryLimitMiB: 512,
      cpu: 256,
    });

    container.addPortMappings({
      containerPort: 80,
      protocol: ecs.Protocol.TCP
    });

    // ECS Service
    const ecsService = new ecs.Ec2Service(this, 'DefaultService', {
      cluster: ecsCluster,
      taskDefinition: ec2TaskDefinition,
      desiredCount: 2,
    });

    //---------------------------------------------------------------------------
    // ALB
    const alb = new elbv2.ApplicationLoadBalancer(this, generateName('ALB'), {
      vpc,
      internetFacing: true,
    });

    const listener = alb.addListener('Listener', {
      port: 80,
      open: true,
    });

    // Connect ecsService to TargetGroup
    const targetGroup = listener.addTargets(generateName('LaravelTargetGroup'), {
      port: 80,
      targets: [ecsService]
    });

    new cdk.CfnOutput(this, generateName('AlbDnsName'), {
      exportName: generateName('AlbDnsName'),
      value: alb.loadBalancerDnsName,
    });

    //---------------------------------------------------------------------------
    // ecsService: Application Auto Scaling    
    const scaling = ecsService.autoScaleTaskCount({ 
      minCapacity: 2,
      maxCapacity: 10 
    });

    scaling.scaleOnCpuUtilization('CpuScaling', {
      targetUtilizationPercent: 50
    });
    
    scaling.scaleOnRequestCount('RequestScaling', {
      requestsPerTarget: 30,
      targetGroup: targetGroup
    });
  }
Example #29
Source File: cdk-stack.ts    From laravel-on-aws-ecs-workshops with Apache License 2.0 4 votes vote down vote up
constructor(scope: cdk.Construct, id: string, props?: cdk.StackProps) {
    super(scope, id, props);


    //---------------------------------------------------------------------------
    // VPC
    const vpc = new ec2.Vpc(this, generateName('VPC'), {
      maxAzs: 2,
      natGateways: 1,
      subnetConfiguration: [
        {
          cidrMask: 24,
          name: generateName('ingress'),
          subnetType: ec2.SubnetType.PUBLIC,
        },
        {
          cidrMask: 24,
          name: generateName('application'),
          subnetType: ec2.SubnetType.PRIVATE,
        },
        {
          cidrMask: 24,
          name: generateName('database'),
          subnetType: ec2.SubnetType.ISOLATED,
        },
      ],
    });

    //---------------------------------------------------------------------------
    // ECS

    // ECS Cluster
    const ecsCluster = new ecs.Cluster(this, 'LaravelWorkshopCluster', {
      vpc: vpc
    });

    // Task Definition
    const fargateTaskDefinition = new ecs.FargateTaskDefinition(this, 'DefaultTaskDef', {
      memoryLimitMiB: 512,
      cpu: 256
    });

    const ecrRepo = ecr.Repository.fromRepositoryName(this, 'DefaultRepo', 'my-laravel-on-aws-ecs-workshop-dev');

    const container = fargateTaskDefinition.addContainer('defaultContainer', {
      image: ecs.ContainerImage.fromEcrRepository(ecrRepo), 
      memoryLimitMiB: 512,
      cpu: 256,
    });

    container.addPortMappings({
      containerPort: 80,
      protocol: ecs.Protocol.TCP
    });

    // ECS Service
    const ecsService = new ecs.FargateService(this, 'DefaultService', {
      cluster: ecsCluster,
      taskDefinition: fargateTaskDefinition,
      desiredCount: 2,
    });

    //---------------------------------------------------------------------------
    // ALB
    const alb = new elbv2.ApplicationLoadBalancer(this, generateName('ALB'), {
      vpc,
      internetFacing: true,
    });

    const listener = alb.addListener('Listener', {
      port: 80,
      open: true,
    });

    // Connect ecsService to TargetGroup
    const targetGroup = listener.addTargets(generateName('LaravelTargetGroup'), {
      port: 80,
      targets: [ecsService]
    });

    new cdk.CfnOutput(this, generateName('AlbDnsName'), {
      exportName: generateName('AlbDnsName'),
      value: alb.loadBalancerDnsName,
    });

    //---------------------------------------------------------------------------
    // ecsService: Application Auto Scaling    
    const scaling = ecsService.autoScaleTaskCount({ 
      minCapacity: 2,
      maxCapacity: 10 
    });

    scaling.scaleOnCpuUtilization('CpuScaling', {
      targetUtilizationPercent: 50
    });
    
    scaling.scaleOnRequestCount('RequestScaling', {
      requestsPerTarget: 30,
      targetGroup: targetGroup
    });
  }