constructor()

in usecases/guest-webapp-sample/lib/blea-ec2app-stack.ts [18:176]


  constructor(scope: cdk.Construct, id: string, props: BLEAEC2AppStackProps) {
    super(scope, id, props);

    // --- Security Groups ---

    //Security Group of ALB for App
    const securityGroupForAlb = new ec2.SecurityGroup(this, 'SgAlb', {
      vpc: props.myVpc,
      allowAllOutbound: false,
    });
    securityGroupForAlb.addEgressRule(ec2.Peer.anyIpv4(), ec2.Port.allTcp());

    //Security Group for Instance of App
    const securityGroupForApp = new ec2.SecurityGroup(this, 'SgApp', {
      vpc: props.myVpc,
      allowAllOutbound: false,
    });
    securityGroupForApp.addIngressRule(securityGroupForAlb, ec2.Port.tcp(80));
    securityGroupForApp.addEgressRule(ec2.Peer.anyIpv4(), ec2.Port.allTcp());
    this.appServerSecurityGroup = securityGroupForApp;

    // ------------ Application LoadBalancer ---------------

    // ALB for App Server
    const lbForApp = new elbv2.ApplicationLoadBalancer(this, 'Ec2AlbApp', {
      vpc: props.myVpc,
      internetFacing: true,
      securityGroup: securityGroupForAlb,
      vpcSubnets: props.myVpc.selectSubnets({
        subnetGroupName: 'Public',
      }),
    });

    // Enable ALB Access Logging
    const albLogBucket = new s3.Bucket(this, 'alb-log-bucket', {
      accessControl: s3.BucketAccessControl.PRIVATE,
      encryption: s3.BucketEncryption.S3_MANAGED,
      blockPublicAccess: s3.BlockPublicAccess.BLOCK_ALL,
      removalPolicy: cdk.RemovalPolicy.RETAIN,
      enforceSSL: true,
    });

    lbForApp.setAttribute('access_logs.s3.enabled', 'true');
    lbForApp.setAttribute('access_logs.s3.bucket', albLogBucket.bucketName);

    // Permissions for Access Logging
    //    Why don't use bForApp.logAccessLogs(albLogBucket); ?
    //    Because logAccessLogs add wider permission to other account (PutObject*). S3 will become Noncompliant on Security Hub [S3.6]
    //    See: https://docs.aws.amazon.com/securityhub/latest/userguide/securityhub-standards-fsbp-controls.html#fsbp-s3-6
    //    See: https://docs.aws.amazon.com/elasticloadbalancing/latest/application/load-balancer-access-logs.html#access-logging-bucket-permissions
    albLogBucket.addToResourcePolicy(
      new iam.PolicyStatement({
        effect: iam.Effect.ALLOW,
        actions: ['s3:PutObject'],
        // ALB access logging needs S3 put permission from ALB service account for the region
        principals: [new iam.AccountPrincipal(ri.RegionInfo.get(cdk.Stack.of(this).region).elbv2Account)],
        resources: [albLogBucket.arnForObjects(`AWSLogs/${cdk.Stack.of(this).account}/*`)],
      }),
    );
    albLogBucket.addToResourcePolicy(
      new iam.PolicyStatement({
        effect: iam.Effect.ALLOW,
        actions: ['s3:PutObject'],
        principals: [new iam.ServicePrincipal('delivery.logs.amazonaws.com')],
        resources: [albLogBucket.arnForObjects(`AWSLogs/${cdk.Stack.of(this).account}/*`)],
        conditions: {
          StringEquals: {
            's3:x-amz-acl': 'bucket-owner-full-control',
          },
        },
      }),
    );
    albLogBucket.addToResourcePolicy(
      new iam.PolicyStatement({
        effect: iam.Effect.ALLOW,
        actions: ['s3:GetBucketAcl'],
        principals: [new iam.ServicePrincipal('delivery.logs.amazonaws.com')],
        resources: [albLogBucket.bucketArn],
      }),
    );

    // ------------ AppServers (AutoScaling) ---------------

    // InstanceProfile for AppServers
    const ssmInstanceRole = new iam.Role(this, 'ssm-instance-role', {
      assumedBy: new iam.ServicePrincipal('ec2.amazonaws.com'),
      path: '/',
      managedPolicies: [
        { managedPolicyArn: 'arn:aws:iam::aws:policy/AmazonSSMManagedInstanceCore' },
        { managedPolicyArn: 'arn:aws:iam::aws:policy/CloudWatchAgentServerPolicy' },
      ],
    });

    // UserData for AppServer (setup httpd)
    const userDataForApp = ec2.UserData.forLinux({ shebang: '#!/bin/bash' });
    userDataForApp.addCommands(
      'sudo yum -y install httpd',
      'sudo systemctl enable httpd',
      'sudo systemctl start httpd',
      'touch /var/www/html/index.html',
      'chown apache.apache /var/www/html/index.html',
    );

    const subnetAzs = props.myVpc.selectSubnets({
      subnetGroupName: 'Private',
    }).availabilityZones;
    const numAzs = subnetAzs.length;

    const instanceTargetIds: elbv2targets.InstanceIdTarget[] = new Array(0);
    for (let i = 0; i < 2; i++) {
      const instance = new ec2.Instance(this, `AppEc2${i}`, {
        vpc: props.myVpc,
        availabilityZone: subnetAzs[i % numAzs],
        vpcSubnets: props.myVpc.selectSubnets({
          subnetGroupName: 'Private',
        }),
        instanceType: ec2.InstanceType.of(ec2.InstanceClass.T3, ec2.InstanceSize.MICRO),
        machineImage: new ec2.AmazonLinuxImage({
          generation: ec2.AmazonLinuxGeneration.AMAZON_LINUX_2,
        }),
        securityGroup: securityGroupForApp,
        role: ssmInstanceRole,
        userData: userDataForApp,
        blockDevices: [
          {
            deviceName: '/dev/xvda',
            volume: ec2.BlockDeviceVolume.ebs(10, {
              encrypted: true,
            }),
          },
        ],
      });
      // Tags for AppServers
      cdk.Tags.of(instance).add('Name', 'AppServer' + i, { applyToLaunchedInstances: true });
      cdk.Tags.of(instance).add('Role', 'FRA_AppServer', { applyToLaunchedInstances: true });
      instanceTargetIds.push(new elbv2targets.InstanceIdTarget(instance.instanceId, 80));
    }

    // ALB Listener - TargetGroup
    const listener = lbForApp.addListener('Ec2Listerner', {
      port: 80,
    });

    // TargetGroup for App Server
    const lbForAppTargetGroup = listener.addTargets('Ec2App', {
      protocol: elbv2.ApplicationProtocol.HTTP,
      targets: instanceTargetIds,
      deregistrationDelay: cdk.Duration.seconds(30),
    });

    // SAMPLE: Another way, how to set attibute to TargetGroup - example) Modify algorithm type
    // lbForAppTargetGroup.setAttribute('load_balancing.algorithm.type', 'least_outstanding_requests');

    // SAMPLE: Setup HealthCheck for app
    // lbForAppTargetGroup.configureHealthCheck({
    //   path: '/health',
    //   enabled: true,
    // });
  }