Securing Infrastructure: Build a Bastion Host
Tags: bastion, aws, pulumi
Published: 2021-05-16 13:28:36

Protecting your infrastructure begins with controlling access to cloud resources. You can reduce the attack surface area of your infrastructure by creating a single point of entry called either a jump box or a bastion host. These servers provide access to a private network from an external network. However, bastion hosts also present an attack surface and must be configured to harden them from attack. This article demonstrates how to set up a bastion host and configure it to assist with regulatory requirements for your organization.

## The Basics

The architecture for implementing a bastion host begins with a server that runs on the public subnet of a virtual private cloud and cloud resources that run on a private subnet that is not publicly accessible. Cloud resources are available through a security group that allows SSH access from the bastion host's security group, as shown in the diagram below.

![image1](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/l741ym091awfhlk39jdg.png)
 

We'll use AWS for this example, but the same principles apply to other cloud service providers. The first thing to do is to either use a default VPC or create a new VPC. The default AWS VPC has four subnets which are all public, we'll get the id of the first subnet to make it a private subnet for the bastion host.

```typescript
const vpc = aws.ec2.getVpc({ default: true });
const vpcId = vpc.then(it => it.id);
const vpcCidrBlock = vpc.then(it => it.cidrBlock);

const subnets = vpc.then(it => aws.ec2.getSubnetIds({ vpcId: it.id }));
const allSubnetIds = subnets.then(it => it.ids);
const firstSubnetId = subnets.then(it => it.ids[0]);
```

Next, we'll provision the bastion host with an ec2 instance and set up a security group that defines the ingress and egress rules. Note that we can add a public key when we create the bastion, but it must be created beforehand and added to the Pulumi.dev.yaml configuration file.

```typescript
const sshSg = new aws.ec2.SecurityGroup(`bastion`, {
    vpcId: vpcId,
    ingress: [{ protocol: "tcp", fromPort: 22, toPort: 22, cidrBlocks: ["0.0.0.0/0"] }],
    egress: [{ protocol: "-1", fromPort: 0, toPort: 0, cidrBlocks: ["0.0.0.0/0"] }],
    tags
});

const sshKey = new aws.ec2.KeyPair("bastion", { publicKey: config.require("publicKey") });

const bastion = new aws.ec2.Instance("bastion", {
    instanceType: aws.ec2.InstanceTypes.T3_Small,
    ami: amiId,
    subnetId: firstSubnetId,
    vpcSecurityGroupIds: [sshSg.id],
    associatePublicIpAddress: true,
    keyName: sshKey.keyName,
    tags,
});
```

And that's it for setting up a basic bastion host. Run `pulumi up` to start the bastion host.

```bash
View Live: https://app.pulumi.com/spara/bastion/dev/updates/9

     Type                      Name         Status
 +   pulumi:pulumi:Stack       bastion-dev  created
 +   ├─ aws:ec2:KeyPair        bastion      created
 +   ├─ aws:ec2:SecurityGroup  bastion      created
 +   └─ aws:ec2:Instance       bastion      created

Outputs:
    bastionHost    : "18.236.227.161"
    bastionHostName: "ec2-18-236-227-161.us-west-2.compute.amazonaws.com"

Resources:
    + 4 created

Duration: 23
```

You can ssh securely into your VPC.

```bash
$ ssh -i id_rsa ubuntu@ec2-18-236-227-161.us-west-2.compute.amazonaws.com
```

With this basic setup you can add more layers of security such as logging for auditing.