Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
34 changes: 34 additions & 0 deletions terraform/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
*.auto.tfvars
.terraform.lock.hcl
terraform.state.*

# Local .terraform directories (contains provider plugins and modules)
.terraform/
.terraform.lock.hcl.tmp

# Terraform state files (may contain sensitive data/passwords)
*.tfstate
*.tfstate.*
*.tfstate.backup

# Crash logs
crash.log
crash.*.log

# Variable files containing sensitive values / secrets
# (Uncomment the line below if you keep non-sensitive examples like 'terraform.tfvars.example' in git)
*.tfvars
*.tfvars.json

# Override files (used for local development overrides)
override.tf
override.tf.json
*_override.tf
*_override.tf.json

# CLI configuration files
.terraformrc
terraform.rc

# Execution plan files (generated by 'terraform plan -out=...')
*.tfplan
4 changes: 4 additions & 0 deletions terraform/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
# Terraform

Sandox for learning terraform and hold onto any modules that are easy recipes that I may use other places and willing to share.

96 changes: 96 additions & 0 deletions terraform/home-ec2/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,96 @@
# Home EC2

Simple Module to create an ec2 instance for you to login to from your home network and only you can access. This can be used for tunneling to various resources in your VPC from your local box.

## Variables

| Variable Name | Description | Type | Default Value | Required? |
| --- | --- | --- | --- | --- |
| `aws_profile_name` | Name of your local aws profile | `string` | *None* | **Yes** |
| `aws_key_pair_name` | Name of your AWS key pair that will be used for ssh | `string` | *None* | **Yes** |
| `aws_ami_image_id` | image id for the ami. | `string` | *None* | **Yes** |
| `aws_instance_type` | Size of the image to stand up | `string` | `"t3a.nano"` | No |
| `aws_additional_security_groups` | Additional security groups on top of the ssh only one created by module | `list(string)` | `[]` | No |
| `aws_instance_startup_script` | Startup script for instance. | `string` | `null` | No |
| `aws_subnet_id` | id for the subnet | `string` | `null` | No |
| `aws_ec2_instance_name` | Name for the EC2 Instance | `string` | `"home-ec2"` | No |

## Outputs

| Output Name | Description | Type |
| --- | --- | --- |
| `ssh_command` | SSH Command you can run to access the image | `string` |

## Commands

### Create the EC2

```bash
MacBookPro:home-ec2 mkwyche$ terraform apply
data.external.force_ipv4: Reading...
data.external.local_user: Reading...
data.http.my_public_ip: Reading...
data.external.local_user: Read complete after 0s [id=-]
data.external.force_ipv4: Read complete after 0s [id=-]
data.http.my_public_ip: Read complete after 0s [id=https://ifconfig.me/ip]
data.aws_key_pair.key_pair: Reading...
data.aws_ami.ami: Reading...
data.aws_key_pair.key_pair: Read complete after 0s [id=key-0aacf6008840baa11]
data.aws_ami.ami: Read complete after 0s [id=ami-01edba92f9036f76e]

Apply complete! Resources: 6 added, 0 changed, 0 destroyed.
```

### SSH into the EC2

If your key has been added locally to ssh(`ssh-add -l`) you can now easily ssh in.

```bash
ssh ec2-user@ec2-13-220-44-159.compute-1.amazonaws.com
The authenticity of host 'ec2-13-220-44-159.compute-1.amazonaws.com (13.220.44.159)' can't be established.
Are you sure you want to continue connecting (yes/no/[fingerprint])? yes
Warning: Permanently added 'ec2-13-220-44-159.compute-1.amazonaws.com' (ED25519) to the list of known hosts.
** WARNING: connection is not using a post-quantum key exchange algorithm.
** This session may be vulnerable to "store now, decrypt later" attacks.
** The server may need to be upgraded. See https://openssh.com/pq.html
, #_
~\_ ####_ Amazon Linux 2023
~~ \_#####\
~~ \###|
~~ \#/ ___ https://aws.amazon.com/linux/amazon-linux-2023
~~ V~' '->
~~~ /
~~._. _/
_/ _/
_/m/'
[ec2-user@ip-172-31-13-45 ~]$
```

### Destroy the EC2

```bash
aws_vpc_security_group_ingress_rule.ssh_ingress_v4: Destroying... [id=sgr-0de0b6d7878d047ff]
aws_vpc_security_group_egress_rule.all_egress_v4: Destroying... [id=sgr-00fe6cc6be11c98e3]
aws_vpc_security_group_ingress_rule.ssh_ingress_v6[0]: Destroying... [id=sgr-014f4d42101b6e6c1]
aws_vpc_security_group_egress_rule.all_egress_v6: Destroying... [id=sgr-0f4ddbc6d5e168af3]
aws_instance.ec2_instance: Destroying... [id=i-03edc197398ff1491]
aws_vpc_security_group_egress_rule.all_egress_v6: Destruction complete after 1s
aws_vpc_security_group_ingress_rule.ssh_ingress_v6[0]: Destruction complete after 1s
aws_vpc_security_group_ingress_rule.ssh_ingress_v4: Destruction complete after 1s
aws_vpc_security_group_egress_rule.all_egress_v4: Destruction complete after 1s
aws_instance.ec2_instance: Still destroying... [id=i-03edc197398ff1491, 00m10s elapsed]
aws_instance.ec2_instance: Still destroying... [id=i-03edc197398ff1491, 00m20s elapsed]
aws_instance.ec2_instance: Still destroying... [id=i-03edc197398ff1491, 00m30s elapsed]
aws_instance.ec2_instance: Destruction complete after 30s
aws_security_group.ssh_restricted: Destroying... [id=sg-08ad998def9119e33]
aws_security_group.ssh_restricted: Destruction complete after 1s

Destroy complete! Resources: 6 destroyed.
```


## TODO

- Figure out SSH from ipv6 instead of needing to force ipv4
- Add a default user so you're not dependent on the default user in the AMI the issue I have with this is it makes passing down userdata a bit more complex but I'm sure it can be handled.
- Add dynamic filtering to just provide a OS name and it'll pull latest AMI for that OS.
142 changes: 142 additions & 0 deletions terraform/home-ec2/main.tf
Original file line number Diff line number Diff line change
@@ -0,0 +1,142 @@
# Pulls the AWS Subnet if provided by the user.
data "aws_subnet" "subnet" {
count = var.aws_subnet_id != null ? 1 : 0
id = var.aws_subnet_id
}

locals {
current_timestamp = timestamp()
formatted_timestamp = formatdate("YYYY-MM-DD'T'hh:mmZ", local.current_timestamp)
}

locals {
# Just pull out the first default subnet
provided_subnet = var.aws_subnet_id != null ? data.aws_subnet.subnet[0].id : null
}

data "external" "local_user" {
program = ["sh", "-c", "echo \"{\\\"user\\\": \\\"$(whoami)\\\"}\""]
}

data "external" "force_ipv4" {
program = ["sh", "-c", "echo \"{\\\"ipv4\\\": \\\"$(curl -4 ifconfig.me)\\\"}\""]
}

# Automatically query your current local workstation's public IP
data "http" "my_public_ip" {
url = "https://ifconfig.me/ip"
}

locals {
is_ipv6 = can(regex(":", data.http.my_public_ip.response_body))
}

# 1. Base Security Group Container (Always created)
resource "aws_security_group" "ssh_restricted" {
name = "ssh-only-my-ip-test"
description = "Block all inbound SSH access except for the deployer workspace"
}

# ==========================================
# IPv4 Rules (Created when local.is_ipv6 = false)
# ==========================================

# Ingress: SSH via IPv4
# TODO: figure out how to make this work.
resource "aws_vpc_security_group_ingress_rule" "ssh_ingress_v4" {
# TODO: If you can figure out the ipv6 rule hanging then you can make this conditional
# count = !local.is_ipv6 ? 1 : 0
security_group_id = aws_security_group.ssh_restricted.id
description = "SSH from my workstation (IPv4)"

ip_protocol = "tcp"
from_port = 22
to_port = 22
cidr_ipv4 = "${chomp(data.external.force_ipv4.result["ipv4"])}/32"
}

# Ingress: SSH via IPv6
# TODO: Below does not work need to figure out proper way to set this up...
resource "aws_vpc_security_group_ingress_rule" "ssh_ingress_v6" {
count = local.is_ipv6 ? 1 : 0
security_group_id = aws_security_group.ssh_restricted.id
description = "SSH from my workstation (IPv6)"

ip_protocol = "tcp"
from_port = 22
to_port = 22
cidr_ipv6 = "${chomp(data.http.my_public_ip.response_body)}/128"
}

# Egress: All Outbound via IPv4
resource "aws_vpc_security_group_egress_rule" "all_egress_v4" {
security_group_id = aws_security_group.ssh_restricted.id
description = "Allow all outbound IPv4 traffic"

ip_protocol = "-1"
cidr_ipv4 = "0.0.0.0/0"
}

# ==========================================
# IPv6 Rules (Created when local.is_ipv6 = true)
# ==========================================

# Egress: All Outbound via IPv6
resource "aws_vpc_security_group_egress_rule" "all_egress_v6" {
security_group_id = aws_security_group.ssh_restricted.id
description = "Allow all outbound IPv6 traffic"

ip_protocol = "-1"
cidr_ipv6 = "::/0"
}

# Key pair ssh
data "aws_key_pair" "key_pair" {
key_name = var.aws_key_pair_name
}

# Try filtering things out
data "aws_ami" "ami" {
most_recent = true
owners = ["amazon"] # Official Canonical AWS Account ID

filter {
name = "image-id"
values = [var.aws_ami_image_id]
}

filter {
name = "virtualization-type"
values = ["hvm"]
}
}

# 2. Infer default username from AMI name
# TODO: Eventually just add the user but below will work in the mean time.
locals {
ami_name = lower(data.aws_ami.ami.name)

default_user = (
can(regex("ubuntu", local.ami_name)) ? "ubuntu" :
can(regex("amzn|amazon", local.ami_name)) ? "ec2-user" :
can(regex("centos", local.ami_name)) ? "centos" :
can(regex("rhel|redhat", local.ami_name)) ? "ec2-user" :
can(regex("debian", local.ami_name)) ? "admin" :
can(regex("fedora", local.ami_name)) ? "fedora" :
can(regex("suse|sles", local.ami_name)) ? "ec2-user" :
can(regex("arch", local.ami_name)) ? "arch" :
"ec2-user" # Safe fallback for most custom/Linux AMIs
)
}

resource "aws_instance" "ec2_instance" {
ami = data.aws_ami.ami.id
instance_type = var.aws_instance_type
vpc_security_group_ids = concat([aws_security_group.ssh_restricted.id], var.aws_additional_security_groups)
key_name = data.aws_key_pair.key_pair.key_name
user_data = var.aws_instance_startup_script
subnet_id = local.provided_subnet
tags = {
Name = var.aws_ec2_instance_name
}
}
14 changes: 14 additions & 0 deletions terraform/home-ec2/outputs.tf
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
# output "home_ip" {
# description = "IP of your local workstation"
# value = data.http.my_public_ip.response_body
# }

# TODO: Bring this back once you add the user on your own.
# output "local_os_username" {
# value = data.external.local_user.result["user"]
# }

output "ssh_command" {
description = "SSH Command you can run to access the image"
value = "ssh ${local.default_user}@${aws_instance.ec2_instance.public_dns}"
}
18 changes: 18 additions & 0 deletions terraform/home-ec2/providers.tf
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
terraform {
required_providers {
aws = {
source = "hashicorp/aws"
version = "~> 6.0"
}
external = {
source = "hashicorp/external"
version = "~> 2.3"
}
}
}

provider "aws" {
profile = var.aws_profile_name
}

provider "external" {}
44 changes: 44 additions & 0 deletions terraform/home-ec2/variables.tf
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
variable "aws_profile_name" {
description = "Name of your local aws profile"
type = string
}

variable "aws_key_pair_name" {
description = "Name of your AWS key pair that will be used for ssh"
type = string
}

variable "aws_ami_image_id" {
description = "image id for the ami."
type = string
}

variable "aws_instance_type" {
description = "Size of the image to stand up"
type = string
default = "t3a.nano"
}

variable "aws_additional_security_groups" {
description = "Additional security groups on top of the ssh only one created by module"
type = list(string)
default = []
}

variable "aws_instance_startup_script" {
description = "Startup script for instance."
type = string
default = null
}

variable "aws_subnet_id" {
description = "id for the subnet"
type = string
default = null
}

variable "aws_ec2_instance_name" {
description = "Name for the EC2 Instance"
type = string
default = "home-ec2"
}
Loading