# Deploying to AWS

This guide describes how to set up an AWS EC2 instance to support
Kamal-based deployment of Rails applications.

## Select a VPC

VMs and other resources in EC2 are created in a "virtual private cloud" (VPC),
providing a controlled network that's isolated from from other AWS users and
the wider internet. AWS creates a default VPC for an account in each region,
which we'll use in this example. To retrieve the ID of your default VPC, run:

```
aws ec2 describe-vpcs  --query 'Vpcs[?(IsDefault==`true`)].VpcId'
```

In this example, I've used the placeholder value `$vpc_id` to represent the ID
taken from output of the command above - the actual ID will be will look
something like `vpc-a1b2c3d4`.

## Set Up Security Groups

Security groups in EC2 provide simple, rule-based control over the network
accessibility of resources in your VPC.

Security group rules come in two types - ingress and egress. Ingress rules
specify the set of IP addresses that are allowed to make connections to the VM,
egress rules specify the addresses to which the VM can make connections[^1].
We'll make two security groups, one for ingress and the other for egress[^2].

### Create the security groups

First, edit [`create_ingress_sg.json`](create_ingress_sg.json), setting the value `VpcId` to your
`$vpc_id`. Now, create the security group with the following command:

```
aws ec2 create-security-group --cli-input-json file://create_ingress_sg.json 
```

If successful, you'll see a JSON document containing the GroupId the new
security group, which I'll refer to in this example as `$ingress_sg_id` - the
actual ID will look something like `sg-0a1b2c3d4e5f67890`

Now edit [`create_egress_sg.json`](create_egress_sg.json), update `VpcId`, and create the egress security
group:

```
aws ec2 create-security-group --cli-input-json file://create_egress_sg.json 
```

Like with the ingress group, a successful result will have the GroupId of the
newly created security group, which I'll refer to as `$egress_sg_id`.

### Authorize the security groups

Now, establish the network rules for the security groups. First, edit [`authorize_ingress_sg.json`](authorize_ingress_sg.json),
updating the value of the `GroupId` to `$ingress_sg_id`. The first two rules
allow traffic from any IPv4 or IPv6 address, to ports 80 and 443,
respectively. The last rule permits ssh from a limited set of IP addresses.
You **must** update these values ones that are appropriate for your scenario,
e.g., to your organization's public network range or your home office.

To apply the rules to the ingress security group, run:

```
aws ec2 authorize-security-group-ingress --cli-input-json file://authorize_ingress_sg.json
```

If successful, the cli will output a JSON response showing the group rules.

Now, update [`authorize_egress_sg.json`](authorize_egress_sg.json), setting the value of `GroupId` to
`$egress_sg_id`, then run:

```
aws ec2 authorize-security-group-egress --cli-input-json file://authorize_egress_sg.json
```

Upon success, the cli will output a JSON response showing the group rules.

**TODO** _explain the purpose of the egress rules in the sample_

## Create a EC2 launch template

EC2 templates provide a standard VM configuration that can be used to deploy one
or more instances based on it.

### Add an SSH key pair

Secure SHell (ssh) will be used for managing the instance and deploying the
application. Establishing an SSH key in EC2 allows AWS to start VM instances
with the specified key installed on the instance, allowing remote login.

If you don't already have an key pair set up in EC22, you can import it
directly via the CLI. For example to import the public key in
id-example-key.pub with the name `my_ssh_key`, run:

```
aws ec2 import-key-pair --key-name my_ssh_key --public-key-material fileb://my-example-key.pub
```

It's also possible to have AWS generate an ssh key for you[^3]:

```
aws --output text ec2 create-key-pair --key-name another_example_key  --query 'KeyMaterial' > another_example_key.pem
```

The resulting file contains a PEM-formatted private key that can be used to
connect to the VM.

Finally, the name given to a key pair must be unique within an AWS account.
Importing/creating a key with an existing name will produce an error. For this
example, I'll refer to the SSH key pair name as `$key_pair_name`.

### Edit the JSON template

Edit [`create_launch_template.json`](create_launch_template.json), setting the values of `$key_pair_name`,
`$ingress_sg_id`, and `$egress_sg_id` to their correct values.

```
aws ec2 create-launch-template --cli-input-json file://create_launch_template.json
```

The output from this command will contain a `LaunchTemplateId` value, to which
I'll refer by the `$launch_template_id`.

### Launch template

Now, you can launch an instance with the new template:

```
aws ec2 run-instances --launch-template LaunchTemplateId=$launch_template_id,Version=1 --query 'Instances[*].InstanceId'
```

NB: don't forget to replace `$launch_template_id` in the command above with the
actual `LaunchTemplateId`. The output from this command should be the instance ID,
which I'll refer to as `$instance_id`

Once VM is launched, it takes a bit for it to come online and won't have a
publicly accessible IP address on startup. To get the IP after launching the VM,
run:

```
aws ec2 describe-instances --instance-ids $instance_id --query 'Reservations[*].Instances[*].PublicIpAddress' --output text
```

You can connect to the instance via ssh thus:

```
ssh ubuntu@IP-ADDRESS
```

If you opted to create a `.pem` file SSH key, you should change the permissions
on `$key_fil.pem` so it's only readable by you:

```
chmod 0400  $key_file.pem 
```

When connecting, you'll need to specify the path to the `.pem` file:

```
ssh -i $key_file.pem ubuntu@IP-ADDRESS
```

## Notes

### Adding a server entry to your SSH configuration

If you'd like to not have to remember to specify the key file path and username when
connecting to your VM, you can add an entry in your SSH configuration file like this:

```
Host myvm.example.edu
  User ubuntu
  IdentityFile /path/to/your/ssh/key_file.pem
```

Your SSH configuration file can be found under your home directory, in
`$HOME/.ssh/config`. Note that you might need to create the `.ssh` directory and
`config` files if they don't exist; if so, make sure their permissions are set
properly:

```
chmod 0700 $HOME/.ssh
chmod 0600 $HOME/.ssh/config
```

[^1]: It's worth pointing out that these rules only need to be given "in one direction," i.e., if you've created an ingress rule from `0.0.0.0/0` to port 443 on the VM, that's all that's needed to allow the VM to respond to HTTPS connections from any IP address; you do **not** need to add a corresponding egress rule.
[^2]: The division into ingress/egress security groups is purely organizational; for example, one could also make a single group with for rules or separate groups by traffic type (e.g., one for HTTP/S, another for SSH).
[^3]: Avoid this option if possible. For information on how to generate your own SSH keys, please see [GitHub's documentation](https://docs.github.com/en/authentication/connecting-to-github-with-ssh/generating-a-new-ssh-key-and-adding-it-to-the-ssh-agent).
