How to samples with AWS CDK

A repository of CDK resusable typescript code samples to provision AWS infrastructure with code. Some more info on CDK if you are new to it. https://docs.aws.amazon.com/cdk/latest/guide/home.html


Create a Multi-AZ VPC with public and private subnets

import cdk = require("@aws-cdk/core");
import ec2 = require("@aws-cdk/aws-ec2");

export class PublicPrivateVpc extends ec2.Vpc {
  constructor(scope: cdk.Construct, id: string, public readonly props: PublicPrivateStackProps) {
    super(scope, id, {
      maxAzs: props.maximumAzs,
      cidr: props.vpcCidr,
      subnetConfiguration: [
        {
          name: "Public",
          subnetType: ec2.SubnetType.PUBLIC,
          cidrMask: props.publicSubnetCidrMask,
        },
        {
          name: "Private",
          subnetType: ec2.SubnetType.PRIVATE,
          cidrMask: props.privateSubnetCidrMask,
        },
      ],
    });
  }
}

export interface PublicPrivateStackProps {
  vpcCidr: string;
  publicSubnetCidrMask: number;
  privateSubnetCidrMask: number;
  maximumAzs: number;
}

example config

{
    vpcCidr: "10.101.0.0/16",
    publicSubnetCidrMask: 24,
    privateSubnetCidrMask: 24,
    maximumAzs: 2,
}

Create a Bastion host in the public subnet (with key pair name) to ssh into ec2 instances in the private subnet using AWS SSM

import cdk = require("@aws-cdk/core");
import ec2 = require("@aws-cdk/aws-ec2");
import { PublicPrivateVpc } from "./public-private-vpc";

export class BastionHost extends cdk.Construct {
  readonly linuxHost: ec2.BastionHostLinux;

  constructor(scope: cdk.Construct, id: string, props: BastionHostProps) {
    super(scope, id);
    const securityGroupName = "BastionHostSg";
    const securityGroup = new ec2.SecurityGroup(scope, securityGroupName, {
      vpc: props.stackVpc,
      allowAllOutbound: true,
      securityGroupName: securityGroupName,
      description: "Security group for bastion, no inbound open because we should access via AWS SSM",
    });

    this.linuxHost = new ec2.BastionHostLinux(scope, `${id}01`, {
      vpc: props.stackVpc,
      subnetSelection: props.stackVpc.selectSubnets({ subnetType: ec2.SubnetType.PUBLIC }),
      instanceName: `${securityGroup.stack.stackName}/${id}`,
      securityGroup: securityGroup,
      instanceType: ec2.InstanceType.of(ec2.InstanceClass.T4G, ec2.InstanceSize.SMALL),
    });
    this.linuxHost.instance.instance.addPropertyOverride("KeyName", props.keypairName);
    cdk.Tags.of(securityGroup).add("Name", `${securityGroup.stack.stackName}/${securityGroupName}`);
  }
}

export interface BastionHostProps {
  stackVpc: PublicPrivateVpc;
  keypairName: string;
}

note, to access the bastion host using ssh over AWS SSM we need to add to our local ssh config below:

# SSH over Session Manager
host i-* mi-*
    ProxyCommand sh -c "aws ssm start-session --target %h --document-name AWS-StartSSHSession --parameters 'portNumber=%p'"

and makes sure to ssh using -A and the instance id (not the public DNS or ip)

ssh -A [email protected]


Create an Application load balancer with instance type target

import cdk = require("@aws-cdk/core");
import ec2 = require("@aws-cdk/aws-ec2");
import elbv2 = require("@aws-cdk/aws-elasticloadbalancingv2");
import elbv2Targets = require("@aws-cdk/aws-elasticloadbalancingv2-targets");
import { PublicPrivateVpc } from "./public-private-vpc";

export class AppLoadBalancerInstanceType extends cdk.Construct {
  readonly alb: elbv2.ApplicationLoadBalancer;

  constructor(scope: cdk.Construct, id: string, props: AppLoadBalancerInstanceTypeProps) {
    super(scope, id);
    this.alb = new elbv2.ApplicationLoadBalancer(scope, `${id}01`, {
      vpc: props.stackVpc,
      vpcSubnets: props.stackVpc.selectSubnets({ subnetType: ec2.SubnetType.PUBLIC }),
      internetFacing: true,
    });

    const listener = this.alb.addListener(`${id}-Listener`, {
      port: 80,
    });

    listener.addTargets(`${id}-Targets`, {
      port: 8080,
      protocol: elbv2.ApplicationProtocol.HTTP,
      targets: [new elbv2Targets.InstanceTarget(props.instance)],
    });

    listener.connections.allowDefaultPortFromAnyIpv4("Open to the world");
    listener.connections.allowTo(
      new ec2.Connections({
        peer: ec2.Peer.ipv4(props.stackVpc.props.vpcCidr),
      }),
      ec2.Port.tcp(8080),
      "Allow all egress 8080 traffic to be routed to the VPC"
    );

    const firstSecurityGroup = this.alb.connections.securityGroups[0];
    cdk.Tags.of(firstSecurityGroup).add("Name", `${firstSecurityGroup.stack.stackName}/${id}Sg`);
  }
}

export interface AppLoadBalancerInstanceTypeProps {
  stackVpc: PublicPrivateVpc;
  instance: ec2.Instance;
}

Create an Linux ec2 instance with 500GB storage that allows SSH and HTTP traffic through security group and has an IAM role that can access a specified S3 bucket

import cdk = require("@aws-cdk/core");
import ec2 = require("@aws-cdk/aws-ec2");
import iam = require("@aws-cdk/aws-iam");
import s3 = require("@aws-cdk/aws-s3");
import { PublicPrivateVpc } from "./public-private-vpc";
import { IamPolicies } from "./iam-policies";

export class AppServer extends cdk.Construct {
  readonly ec2Instance: ec2.Instance;

  constructor(scope: cdk.Construct, id: string, private props: AppServerProps) {
    super(scope, id);
    this.ec2Instance = new ec2.Instance(scope, `${id}01`, {
      vpc: props.stackVpc,
      keyName: props.keypairName,
      vpcSubnets: props.stackVpc.selectSubnets({ subnetType: ec2.SubnetType.PRIVATE }),
      machineImage: props.ami,
      instanceType: ec2.InstanceType.of(ec2.InstanceClass.T3, ec2.InstanceSize.MEDIUM),
      availabilityZone: props.stackVpc.availabilityZones[0],
      role: this.createIamRole(scope),
      securityGroup: this.createSecurityGroup(scope),
      blockDevices: [
        {
          deviceName: "/dev/sda1",
          volume: ec2.BlockDeviceVolume.ebs(500, {
            deleteOnTermination: true,
          }),
        },
      ],
    });
  }

  private createIamRole(scope: cdk.Construct): iam.Role {
    const appServerRole = new iam.Role(scope, "AppServerRole", {
      assumedBy: new iam.ServicePrincipal("ec2.amazonaws.com"),
    });
    const appServerPolicyDoc = iam.PolicyDocument.fromJson(
      IamPolicies.AppServerS3PolicyJson(this.props.s3Bucket.bucketName)
    );
    appServerRole.addManagedPolicy(
      new iam.ManagedPolicy(scope, "AppServerPolicy", {
        document: appServerPolicyDoc,
      })
    );
    return appServerRole;
  }

  private createSecurityGroup(scope: cdk.Construct): ec2.SecurityGroup {
    const securityGroupName = "AppServer01Sg";
    const securityGroup = new ec2.SecurityGroup(scope, securityGroupName, {
      vpc: this.props.stackVpc,
      securityGroupName: securityGroupName,
      description: "Security Group for AppServer01",
      allowAllOutbound: true,
    });

    const ingressPorts = [
      ec2.Port.tcp(22), // SSH
      ec2.Port.tcp(8080), // HTTP
    ];

    for (const ingressPort of ingressPorts) {
      // Only allow ingressPorts from within the VPC
      securityGroup.addIngressRule(ec2.Peer.ipv4(this.props.stackVpc.props.vpcCidr), ingressPort);
    }

    cdk.Tags.of(securityGroup).add("Name", `${securityGroup.stack.stackName}/${securityGroupName}`);
    return securityGroup;
  }
}

export interface AppServerProps {
  stackVpc: PublicPrivateVpc;
  keypairName: string;
  ami: ec2.IMachineImage;
  s3Bucket: s3.Bucket;
}

and the IAM policy for the role

export const IamPolicies = {
  AppServerS3PolicyJson: (bucketName: string) => {
    return {
      Version: "2012-10-17",
      Statement: [
        {
          Sid: "VisualEditor0",
          Effect: "Allow",
          Action: [
            "s3:GetObject",
            "s3:PutObject",
            "s3:DeleteObject",
            "s3:ReplicateObject",
            "s3:ListBucket",
            "s3:ListBucketVersions",
            "s3:GetObjectVersion",
            "s3:DeleteObjectVersion",
          ],
          Resource: [`arn:aws:s3:::${bucketName}`, `arn:aws:s3:::${bucketName}/*`],
        },
      ],
    };
  },
};

April 5, 2021 · AWS · CDK · IaC · Typescript


Previous:A sample hashicorp packer project to provision a new AWS AMI with node, pm2 and mongodb