|||

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. cdk-home

cdk logo


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}/*`],
        },
      ],
    };
  },
};

Up next A sample hashicorp packer project to provision a new AWS AMI with node, pm2 and mongodb
Latest posts How to samples with AWS CDK A sample hashicorp packer project to provision a new AWS AMI with node, pm2 and mongodb Some notes on Zeebe (A scalable process orchestrator) Running docker compose in AWS ECS (ec2 type) with EFS volume mounts for persistent storage Domain Driven Design Core Principles Apple Push Notifications With Amazon SNS AWS VPC Notes Building and Deploying apps using VSTS and HockeyApp - Part 3 : Windows Phone Building and Deploying apps using VSTS and HockeyApp - Part 2 : Android Building and Deploying apps using VSTS and HockeyApp - Part 1 : iOS How I diagnosed High CPU usage using Windbg WCF service NETBIOS name resolution woes The troublesome Git-Svn Marriage GTD (Getting things done) — A simplified view Javascript Refresher Sharing common connection strings between projects A simple image carousel prototype using Asp.net webforms and SignalR Simple logging with NLog Application logger SVN Externals — Share common assembly code between solutions Simple async in .net 2.0 & Winforms Clean sources Plus Console 2 — A tabbed console window