From bf8625411700df89db8bcc82e70a4520c9a54e2e Mon Sep 17 00:00:00 2001 From: Nathan Flurry Date: Sat, 13 Apr 2024 15:48:45 -0700 Subject: [PATCH] feat(infra): add ability to provision dev tunnel --- Taskfile.yaml | 7 +++ docs/getting_started/DEVELOPMENT.md | 6 +++ docs/infrastructure/dev-tunnel/SETUP.md | 71 +++++++++++++++++++++++++ infra/dev-tunnel/Taskfile.yaml | 14 +++++ infra/dev-tunnel/main.tf | 16 ++++++ infra/dev-tunnel/providers.tf | 3 ++ infra/dev-tunnel/server.tf | 69 ++++++++++++++++++++++++ infra/dev-tunnel/tls.tf | 10 ++++ infra/dev-tunnel/tunnel.tf | 59 ++++++++++++++++++++ infra/dev-tunnel/vars.tf | 4 ++ shell.nix | 4 ++ 11 files changed, 263 insertions(+) create mode 100644 Taskfile.yaml create mode 100644 docs/infrastructure/dev-tunnel/SETUP.md create mode 100644 infra/dev-tunnel/Taskfile.yaml create mode 100644 infra/dev-tunnel/main.tf create mode 100644 infra/dev-tunnel/providers.tf create mode 100644 infra/dev-tunnel/server.tf create mode 100644 infra/dev-tunnel/tls.tf create mode 100644 infra/dev-tunnel/tunnel.tf create mode 100644 infra/dev-tunnel/vars.tf diff --git a/Taskfile.yaml b/Taskfile.yaml new file mode 100644 index 000000000..59e3cbf81 --- /dev/null +++ b/Taskfile.yaml @@ -0,0 +1,7 @@ +version: '3' + +includes: + dev-tunnel: + taskfile: infra/dev-tunnel + dir: infra/dev-tunnel + diff --git a/docs/getting_started/DEVELOPMENT.md b/docs/getting_started/DEVELOPMENT.md index 7aff6fc44..2ecf3ce50 100644 --- a/docs/getting_started/DEVELOPMENT.md +++ b/docs/getting_started/DEVELOPMENT.md @@ -109,6 +109,12 @@ and run "Remote-Containers: Reopen in Container". You can now skip to the [Common steps](#common-steps) section. +### Step 5: Setup dev tunnel (optional) + +Rivet needs a publicly accessible IP in order to be able to deploy servers. Without it, you can still run Rivet, but you won't be able to access servers. + +Read the guide on setting up a dev tunnel (similar to ngrok) [here](/docs/infrastructure/dev-tunnel/SETUP.md). + ## Method 2: Virtual Machine This is best if running a small deployment of Rivet on a cloud provider. diff --git a/docs/infrastructure/dev-tunnel/SETUP.md b/docs/infrastructure/dev-tunnel/SETUP.md new file mode 100644 index 000000000..b472bcbac --- /dev/null +++ b/docs/infrastructure/dev-tunnel/SETUP.md @@ -0,0 +1,71 @@ +# Setup Dev Tunnel + +This guide will show you how to set up a dev tunnel (similar to [ngrok](https://ngrok.com/)) for developing Rivet locally. + +This will run a Terraform plan to deploy two components: + +- A server on Linode that will forward traffic to your local machine +- A Docker container that will connect to the remote server over SSH and expose a reverse tunnel + +## Prerequisites + +Make sure to run `nix-shell` for all subsequent commands. + +- Docker +- Linode API Key + +## Step 1: Create Dev Tunnel + +```sh +task dev-tunnel:up +``` + +This will prompt you to past your Linode API token. + +Once complete, this will print an IP to your console like: + +```toml +ip = "1.2.3.4" +``` + +## Step 2: Update public IP + +Open your namespace config in `namespaces/dev.toml`. + +- Update `cluter.single_node.public_ip` to the IP from the last step. By default, the config is generated with `public_ip = "127.0.0.1"`. +- If exists, delete the line that says `api_http_port = 8080`. +- Validate that there are no ports overridden (i.e. `cluter.single_node.api_http_port`). + +If you need your IP again, run `task dev-tunnel:get-ip`. + +## Step 3: Update infrastructure + +To deploy the new DNS records & configs, run: + +```sh +bolt infra up +``` + +## Step 4: Valdiate deployment + +Validate you can reach your local server on the public IP, replace `MY_TUNNEL_IP` with the IP from the last step: + +```sh +curl MY_TUNNEL_IP:80 +``` + +This should return a 404 response: + +``` +404 page not found +``` + +This means your server is now accessible. + +If you have DNS configured, you should be able to reach your server from `api.my + +## Cleanup + +```sh +task dev-tunnel:down +``` diff --git a/infra/dev-tunnel/Taskfile.yaml b/infra/dev-tunnel/Taskfile.yaml new file mode 100644 index 000000000..701747600 --- /dev/null +++ b/infra/dev-tunnel/Taskfile.yaml @@ -0,0 +1,14 @@ +version: '3' + +tasks: + up: + cmds: + - terraform apply + + down: + cmds: + - terraform destroy + + get-ip: + cmds: + - terraform output diff --git a/infra/dev-tunnel/main.tf b/infra/dev-tunnel/main.tf new file mode 100644 index 000000000..8cc33997a --- /dev/null +++ b/infra/dev-tunnel/main.tf @@ -0,0 +1,16 @@ +terraform { + required_providers { + linode = { + source = "linode/linode" + version = "~> 1.23.0" + } + docker = { + source = "kreuzwerker/docker" + version = "~> 2.15.0" + } + } +} + +output "ip" { + value = linode_instance.tunnel.ip_address +} diff --git a/infra/dev-tunnel/providers.tf b/infra/dev-tunnel/providers.tf new file mode 100644 index 000000000..251199e2f --- /dev/null +++ b/infra/dev-tunnel/providers.tf @@ -0,0 +1,3 @@ +provider "linode" { + token = var.linode_token +} \ No newline at end of file diff --git a/infra/dev-tunnel/server.tf b/infra/dev-tunnel/server.tf new file mode 100644 index 000000000..c1ad76440 --- /dev/null +++ b/infra/dev-tunnel/server.tf @@ -0,0 +1,69 @@ +resource "random_password" "password" { + length = 16 + special = true + override_special = "_%@" +} + +resource "linode_instance" "tunnel" { + image = "linode/debian11" + label = "dev-tunnel" + region = "us-west" + type = "g6-nanode-1" + authorized_keys = [trimspace(tls_private_key.ssh_key.public_key_openssh)] + root_pass = random_password.password.result +} + +resource "linode_firewall" "tunnel_firewall" { + label = "dev-tunnel" + + inbound_policy = "DROP" + outbound_policy = "ACCEPT" + + inbound { + label = "ssh" + action = "ACCEPT" + protocol = "TCP" + ports = "22" + ipv4 = ["0.0.0.0/0"] + ipv6 = ["::/0"] + } + + inbound { + label = "http" + action = "ACCEPT" + protocol = "TCP" + ports = "80" + ipv4 = ["0.0.0.0/0"] + ipv6 = ["::/0"] + } + + inbound { + label = "https" + action = "ACCEPT" + protocol = "TCP" + ports = "443" + ipv4 = ["0.0.0.0/0"] + ipv6 = ["::/0"] + } + + inbound { + label = "tunnel" + action = "ACCEPT" + protocol = "TCP" + ports = "5000" + ipv4 = ["0.0.0.0/0"] + ipv6 = ["::/0"] + } + + inbound { + label = "minio" + action = "ACCEPT" + protocol = "TCP" + ports = "9000" + ipv4 = ["0.0.0.0/0"] + ipv6 = ["::/0"] + } + + linodes = [linode_instance.tunnel.id] +} + \ No newline at end of file diff --git a/infra/dev-tunnel/tls.tf b/infra/dev-tunnel/tls.tf new file mode 100644 index 000000000..9ab7579f1 --- /dev/null +++ b/infra/dev-tunnel/tls.tf @@ -0,0 +1,10 @@ +resource "tls_private_key" "ssh_key" { + algorithm = "RSA" + rsa_bits = 2048 +} + +resource "local_file" "ssh_key_file" { + filename = "/tmp/tunnel_id_rsa" + content = tls_private_key.ssh_key.private_key_pem + file_permission = "0600" +} diff --git a/infra/dev-tunnel/tunnel.tf b/infra/dev-tunnel/tunnel.tf new file mode 100644 index 000000000..e8bbfd5a1 --- /dev/null +++ b/infra/dev-tunnel/tunnel.tf @@ -0,0 +1,59 @@ +resource "null_resource" "update_sshd_config" { + depends_on = [linode_instance.tunnel] + triggers = { + override = 2 + } + + connection { + type = "ssh" + user = "root" + private_key = tls_private_key.ssh_key.private_key_pem + host = linode_instance.tunnel.ip_address + } + + provisioner "local-exec" { + command = <<-EOT + # Wait for SSH + while ! nc -z ${linode_instance.tunnel.ip_address} 22; do + echo "Waiting for SSH to be available..." + sleep 2 + done + + # Update config + ssh -o StrictHostKeyChecking=no -i ${local_file.ssh_key_file.filename} root@${linode_instance.tunnel.ip_address} \ + "echo 'GatewayPorts yes' > /etc/ssh/sshd_config.d/dev_tunnel.conf && \ + systemctl restart ssh" + EOT + } +} + +resource "docker_container" "ssh_tunnel" { + depends_on = [ null_resource.update_sshd_config] + + image = "debian:11" + name = "rivet-tunnel" + restart = "unless-stopped" + network_mode = "host" + command = [ + "sh", + "-c", + # StrictHostKeyChecking=no = disables prompting before adding remote host to hosts file + # -v = verbose + # -N = don't execute command + # -T = no TTY + # -R = reverse proxy + <