Bastion host - SoftupTechnologies/infrastructure-components GitHub Wiki

Path: /lib/bastion-host/index.ts

Exports: BastionHostServices

Required construct packages: @aws-cdk/aws-ec2, @aws-cdk/aws-s3, @aws-cdk/aws-iam, @aws-cdk/aws-s3-assets

Bastion host (BH) will serve us as a tunnel to access instances or databases which are in the private subnets. The BH we have constructed is composed by an ec2 instance and s3 bucket. The bucket serves to store the users' public keys in the format name.pub. The instance polls the bucket every 3 minutes to check if there are new keys or removed keys, and based on this it creates new users or removes existing ones. The instance logic is on a shell script (/lib/bastion-host/user_data.sh) which is loaded as user data, and executed at instance creation time. The ec2 instance uses an Amazon Linux 2 AMI. To access the s3 bucket we have created a role for our instance which allows it to interact with the s3 bucket and also put logs in CloudWatch.

Usage

import * as cdk from '@aws-cdk/core';
import { MyVpc } from './vpc';
import { BastionHostServices } from './bastion-host';
import { Envs } from '../types/envs';

export class ServerlessInfrastructureCdkStack extends cdk.Stack {
  constructor(scope: cdk.App, id: string, props: StackProps) {
    super(scope, id);

    const { vpc } = new MyVpc(this, 'MyAwesomeVpc', {
      vpcCidr: '10.0.0.0/16',
      publicSubnetsNo: 2,
      maxAzs: 2,
      privateSubnetsNo: 1,
    });

    const bastionHost = new BastionHostServices(this, 'BastionHost', {
      ...props,
      vpc,
      subnets: [vpc.publicSubnets[0]],
      instanceName: 'my-instance',
      keyName: 'my-instance-key.pem',
    });

    // We create a database in vpc private subnets and attach the bastion host security group to the database instance so it can accept connections from the bastion host.

    const db = new RdsInfrastructure(this, 'MyCoolDbService', {
      ...props,
      dbMasterUserName: 'coolUsername',
      vpc,
      databaseName: 'coolDatabase',
      dbAllocatedStorage: 10,
      dbBackupRetention: 30,
      dbSubnets: vpc.privateSubnets,
      ingressSgs: [bastionHost.bastionHostSecurityGroup]
    });
  }
}

After the stack is created you can go on the s3 bucket and upload your public key name.pub. After 3 minutes you can access the instance ssh name@instance-public-ip-or-dns.

To use the aws cli to upload the pub key you can run:

aws s3 cp ~/.ssh/id_rsa.pub s3://created-bucket-name/public-keys/name.pub. It must have the public-keys prefix when you upload it since that is where the instance looks for keys.

If you don't have a public key, you can follow the steps here, how to generate one.

Construct props

Name Type Required Default Description
projectName string true undefined Project name
clientName string true undefined Client name
env Envs { dev, stage, prod } true undefined Environment
subnets ec2.ISubnet[] true undefined Subnets in which the bastion host is placed.
vpc ec2.Vpc true undefined Vpc in which the bastion host is created.
instanceName string true undefined Bastion host instance name. The given name will be composed with projectName, clientName and env. Ex: ${projectName}-${instanceName}-bastion-host-${env}.
instanceType ec2.InstanceType false T2 MICRO Bastion host instance type.
keyName string false undefined Name of SSH keypair to grant access to instance.

Properties

Name Type Description
bastionHostInstance ec2.Instance Created ec2 bastion host instance.
bastionHostSecurityGroup ec2.SecurityGroup Instance security group.