AWS CDK: Create EC2 Bastion with Ec2 Instance Connect

Dennis Groß's photo
Dennis Groß
·Aug 1, 2022·

2 min read

Subscribe to my newsletter and never miss my upcoming articles

Play this article

An EC2 Bastion instance is a security-hardened virtual machine that you can use as a jump box.

A jump box is always useful when you have AWS running in a closed environment like a private VPC subnet but still have to access resources from your computer or the AWS CloudShell.

Create a Bastion EC2 Instance

const machineImage = aws_ec2.MachineImage.latestAmazonLinux({
  generation: aws_ec2.AmazonLinuxGeneration.AMAZON_LINUX_2,
});

// define instance type for the bastion EC2 instance
const instanceType = new aws_ec2.InstanceType('t3.micro');

// select a public subnet so we can access the Bastion from
// the internet
const publicSubnets = vpc.selectSubnets({
  subnetType: aws_ec2.SubnetType.PUBLIC,
});

// each EC2 instance must be deployed with a key pair
const keyName = new aws_ec2.CfnKeyPair(this, 'BastionKeyPair', {
  keyName: 'BastionKeyPair',
}).keyName;

// create service principal role
const role = new aws_iam.Role(this, 'BastionSericeRole', {
  assumedBy: new aws_iam.ServicePrincipal('ec2.amazonaws.com'),
});

// launch EC2 bastion instance
const bastion = new aws_ec2.Instance(this, 'Bastion', {
  vpc: vpc,
  vpcSubnets: publicSubnets,
  role: role,
  securityGroup: this.securityGroup,
  instanceName: 'Bastion',
  machineImage: machineImage,
  instanceType: instanceType,
  keyName: keyName,
  // enables detailed monitoring to Cloudwatch, 
  // not necessary but recommended
  detailedMonitoring: true,
});

Find out how you can create a VPC with the AWS CDK here

Allow EC2 Instance Connect for User Group

We have our Bastion instance running but still need to grant our users enough IAM permissions to ssh into the instance.

We create an IAM User Group for this, so you are still flexible in the future an can easily add/remove IAM users from the group to grant/revoke ec2 instance connect access to the Bastion.


const region = Stack.of(this).region;
const accountId = Stack.of(this).account;

const bastionArn = `arn:aws:ec2:${region}:${accountId}:instance/${bastion.instanceId}`;

// create inline policy that allows EC2 connect
const instanceConnectPolicy = new aws_iam.Policy(this, 'BastionEc2ConnectPolicy', {
  statements: [
    new aws_iam.PolicyStatement({
      effect: aws_iam.Effect.ALLOW,
      actions: ['ec2-instance-connect:SendSSHPublicKey'],
      resources: [bastionArn],
      conditions: {
        StringEquals: {
          'ec2:osuser': 'ec2-user',
        },
      },
    }),
    new aws_iam.PolicyStatement({
      effect: aws_iam.Effect.ALLOW,
      actions: ['ec2:DescribeInstances'],
      resources: [bastionArn],
    }),
  ],
});

// create the user group, attach the inline policy
const userGroup = new aws_iam.Group(this, 'AccManagementGroup');
userGroup.attachInlinePolicy(instanceConnectPolicy);

// add a user to the group
const user = aws_iam.User.fromUserArn(this, 'BastionConnectUser', '<my-user-arn>');
userGroup.addUser(user);

The policy in the code snippet forces you to ssh with the 'ec2-user' into the bastion

Expose EKS Kubernetes API to Bastion

coming soon

Add User Data Script with Template Substitution

coming soon

Did you find this article valuable?

Support Dennis Groß by becoming a sponsor. Any amount is appreciated!

See recent sponsors Learn more about Hashnode Sponsors
 
Share this