Skip to content

Commit

Permalink
Merge pull request #2 from ivov/provision-with-terraform
Browse files Browse the repository at this point in the history
ci: Provision infrastructure with Terraform
  • Loading branch information
ivov authored Nov 10, 2024
2 parents 8b89f32 + 0e239a3 commit 0006b4d
Show file tree
Hide file tree
Showing 16 changed files with 407 additions and 2 deletions.
5 changes: 4 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
@@ -1,3 +1,6 @@
.DS_Store
bin/*
!**/.gitkeep
!**/.gitkeep
*.tfstate
*.tfstate.*
.terraform/
4 changes: 3 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,8 @@ Features:

Deployment stack:

- Metrics with expvar, Prometheus, node exporter, cAdvisor
- Provisioning with Terraform
- Metrics with Prometheus
- Logging with zap, Promtail, Loki
- Monitoring with Grafana
- Caddy as reverse proxy
Expand All @@ -28,6 +29,7 @@ Deployment stack:

## Docs

- [`provision.md`](docs/provision.md)
- [`develop.md`](docs/develop.md)
- [`release.md`](docs/release.md)
- [`deploy.md`](docs/deploy.md)
Expand Down
88 changes: 88 additions & 0 deletions docs/provision.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,88 @@
# Provisioning

This guide explains how to provision the requisite infrastructure for `n8n-shortlink`:

- **Server**: CAX11 on Hetzner Cloud. ARM64 with 2 vCPU, 4 GiB RAM, 40 GiB disk, running Ubuntu 22.04, located at `nbg1-dc3` (Nuremberg) data center. Ingress rules configured at network level for TCP ports 22 (SSH with source IP restriction), 80 (HTTP), and 443 (HTTPS)
- **Backup**: AWS S3 bucket for backup storage with 10-day retention policy. Dedicated IAM user with programmatic access via access key pair, constrained by least-privilege policy granting bucket-specific read (`GetObject`, `ListBucket`) and write (`PutObject`) permissions.

## Setup

1. Install Terraform:

```sh
brew install terraform
terraform --version # >= 1.9.8
```

2. Create an SSH key pair:

```sh
ssh-keygen -t ed25519 -C "[email protected]" -f ~/.ssh/id_ed25519_shortlink_via_terraform
```

3. Sign up for [Hetzner Cloud](https://www.hetzner.com/cloud/), create a project `n8n-shortlink`, and create an API token for the project.

4. Sign up for [HCP Terraform](https://www.hashicorp.com/products/terraform), create an organization `ivov` and a workspace `n8n-shortlink`. In workspace settings, set execution mode to `local`, so that apply occurs locally and HCP Terraform is used only to store state. Set these workspace variables:

- `ssh_public_key`: Content of `~/.ssh/id_ed25519_shortlink_via_terraform.pub` from step 2. Mark as sensitive.
- `hcloud_token`: API token from step 3. Mark as sensitive.
- `allowed_ssh_ips`: `["your-ip-address"]`, i.e. string array in CIDR notation. Mark as sensitive and _as HCL-type variable_.

5. Sign up for [AWS](https://aws.amazon.com/console/), create an IAM policy `n8n-shortlink-terraform-automation-policy`, create an IAM user `n8n-shortlink-terraform-automation-user` (disallow AWS Management Console access) attaching the policy to this user, generate access keys for this user (select "Third-party service") and store them in HCP Terraform:

- `tf_automation_aws_access_key_id`: Access key ID for `terraform-automation` IAM user. Mark as sensitive.
- `tf_automation_aws_secret_access_key`. Secret access key for `terraform-automation` IAM user. Mark as sensitive.

```jsonc
// n8n-shortlink-terraform-automation-policy
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Action": "iam:*",
"Resource": "arn:aws:iam::*:user/n8n-shortlink-terraform-backup-user",
},
{
"Effect": "Allow",
"Action": "s3:*",
"Resource": [
"arn:aws:s3:::n8n-shortlink-terraform-backup-bucket",
"arn:aws:s3:::n8n-shortlink-terraform-backup-bucket/*",
],
},
],
}
```

## Provision

1. Log in to HCP Terraform:

```sh
terraform login
```

2. Initialize Terraform:

```sh
cd infra/provision
terraform init
```

3. Plan and apply:

```sh
terraform plan
terraform apply
```

4. Note down IP address and retrieve credentials from state:

```sh
Outputs:
backup_credentials = (sensitive value)
server_ip = "87.148.121.19"

terraform output -json backup_credentials
```
47 changes: 47 additions & 0 deletions infra/provision/.terraform.lock.hcl

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

15 changes: 15 additions & 0 deletions infra/provision/main.tf
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
module "server" {
source = "./modules/server"
project_name = var.project_name
server_type = "cax11"
location = "nbg1"
hcloud_token = var.hcloud_token
allowed_ssh_ips = var.allowed_ssh_ips
ssh_public_key = var.ssh_public_key
}

module "backup" {
source = "./modules/backup"
project_name = var.project_name
bucket_name = "${var.project_name}-backup-bucket"
}
51 changes: 51 additions & 0 deletions infra/provision/modules/backup/main.tf
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
resource "aws_s3_bucket" "backup" {
bucket = var.bucket_name
}

resource "aws_s3_bucket_lifecycle_configuration" "backup" {
bucket = aws_s3_bucket.backup.id

rule {
id = "cleanup"
status = "Enabled"

filter {
prefix = "${var.project_name}-backup/"
}

expiration {
days = 10
}
}
}

resource "aws_iam_user" "backup" {
name = "${var.project_name}-backup-user"
}

resource "aws_iam_access_key" "backup" {
user = aws_iam_user.backup.name
}

resource "aws_iam_user_policy" "backup" {
name = "${var.project_name}-backup-policy"
user = aws_iam_user.backup.name

policy = jsonencode({
Version = "2012-10-17"
Statement = [
{
Effect = "Allow"
Action = [
"s3:PutObject",
"s3:GetObject",
"s3:ListBucket"
]
Resource = [
aws_s3_bucket.backup.arn,
"${aws_s3_bucket.backup.arn}/*"
]
}
]
})
}
10 changes: 10 additions & 0 deletions infra/provision/modules/backup/outputs.tf
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
output "backup_user_access_key" {
description = "AWS access key for backup user"
value = aws_iam_access_key.backup.id
}

output "backup_user_secret_key" {
description = "AWS secret key for backup user"
value = aws_iam_access_key.backup.secret
sensitive = true
}
9 changes: 9 additions & 0 deletions infra/provision/modules/backup/variables.tf
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
variable "project_name" {
description = "Project name for resource naming"
type = string
}

variable "bucket_name" {
description = "Name of the S3 bucket for backups"
type = string
}
43 changes: 43 additions & 0 deletions infra/provision/modules/server/main.tf
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
resource "hcloud_ssh_key" "main" {
name = "${var.project_name}-key"
public_key = var.ssh_public_key
}

resource "hcloud_firewall" "main" {
name = "${var.project_name}-fw"

rule {
direction = "in"
protocol = "tcp"
port = "22"
source_ips = var.allowed_ssh_ips
}

rule {
direction = "in"
protocol = "tcp"
port = "80"
source_ips = [
"0.0.0.0/0"
]
}

rule {
direction = "in"
protocol = "tcp"
port = "443"
source_ips = [
"0.0.0.0/0"
]
}
}

resource "hcloud_server" "main" {
name = var.project_name
server_type = var.server_type
image = "ubuntu-22.04"
location = var.location
ssh_keys = [hcloud_ssh_key.main.id]

firewall_ids = [hcloud_firewall.main.id]
}
4 changes: 4 additions & 0 deletions infra/provision/modules/server/outputs.tf
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
output "server_ip" {
description = "Public IP of the server"
value = hcloud_server.main.ipv4_address
}
8 changes: 8 additions & 0 deletions infra/provision/modules/server/providers.tf
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
terraform {
required_providers {
hcloud = {
source = "hetznercloud/hcloud"
version = "~> 1.45.0"
}
}
}
31 changes: 31 additions & 0 deletions infra/provision/modules/server/variables.tf
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
variable "project_name" {
description = "Project name for resource naming"
type = string
}

variable "hcloud_token" {
description = "Hetzner Cloud API Token"
type = string
sensitive = true
}

variable "server_type" {
description = "Hetzner server type"
type = string
}

variable "location" {
description = "Hetzner datacenter location"
type = string
}

variable "ssh_public_key" {
description = "SSH public key content"
type = string
}

variable "allowed_ssh_ips" {
description = "List of CIDR IP addresses allowed to connect via SSH"
type = list(string)
sensitive = true # prevent IPs from showing in logs
}
13 changes: 13 additions & 0 deletions infra/provision/outputs.tf
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
output "server_ip" {
description = "Public IP of the Hetzner server"
value = module.server.server_ip
}

output "backup_credentials" {
description = "AWS credentials for backup user"
value = {
access_key = module.backup.backup_user_access_key
secret_key = module.backup.backup_user_secret_key
}
sensitive = true
}
9 changes: 9 additions & 0 deletions infra/provision/providers.tf
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
provider "hcloud" {
token = var.hcloud_token
}

provider "aws" {
region = var.aws_region
access_key = var.tf_automation_aws_access_key_id
secret_key = var.tf_automation_aws_secret_access_key
}
Loading

0 comments on commit 0006b4d

Please sign in to comment.