Built a Ngrok replacement

Schuster Braun - Aug 25 - - Dev Community

TL;DR (Not worth it)

I'm building an app that requires an authentication callback. You can't just use localhost. The internet can't route to it. I've used Ngrok before. I wasn't a fan of how the URL keeps changing when you use the Ngrok free tier. Also, there's a limit to Ngrok requests. So I thought to myself I should just build a Ngrok replacement.

What we needed is a reverse proxy server. This server would act as our local app while we develop. That server has a domain name that we could register as a callback url.

So I built a virtual machine in Azure, setup the routing, installed and configured Nginx. Then I stopped because I had to configure my home's network firewall to allow for traffic to stream via an ssh tunnel from laptop to the proxy server. That was way too much for me. I could do it, I've done it before. But I work a lot from other networks and don't have access to those routers and firewalls.

So I came back to Ngrok. Their software is an agent that doesn't require any networking setup. It's pretty cool and now I have a deepr understanding how convenient their product is to use.

Implementation

Wanted to share the code and how to do it here if you're interested in setting up a reverse proxy server.

# main.tf
# We strongly recommend using the required_providers block to set the
# Azure Provider source and version being used
terraform {
  required_providers {
    azurerm = {
      source  = "hashicorp/azurerm"
      version = "~>4.0.1"
    }
  }
}

provider "azurerm" {
  features {}
}

variable NGROK_ALT_UNAME {
  type        = string
  description = "description"
}

variable NGROK_ALT_PWD {
  type        = string
  default     = ""
  description = "description"
}



# Resource Group
resource "azurerm_resource_group" "proxy" {
  name     = "ngrok-alt-rg"
  location = "West Europe"
}

# Virtual Network
resource "azurerm_virtual_network" "proxy" {
  name                = "ngrok-alt-vnet"
  address_space       = ["10.0.0.0/16"]
  location            = azurerm_resource_group.proxy.location
  resource_group_name = azurerm_resource_group.proxy.name
}

# Subnet
resource "azurerm_subnet" "proxy" {
  name                 = "subnet1"
  resource_group_name  = azurerm_resource_group.proxy.name
  virtual_network_name = azurerm_virtual_network.proxy.name
  address_prefixes     = ["10.0.1.0/24"]
}

# Public IP
resource "azurerm_public_ip" "proxy" {
  name                = "ngrok-alt-ip"
  location            = azurerm_resource_group.proxy.location
  resource_group_name = azurerm_resource_group.proxy.name
  allocation_method   = "Static"
}


# Virtual Machine
resource "azurerm_linux_virtual_machine" "proxy" {
  name                = "ngrok-alt-vm"
  resource_group_name = azurerm_resource_group.proxy.name
  location            = azurerm_resource_group.proxy.location
  size                = "Standard_B1s"

  admin_username = var.NGROK_ALT_UNAME
  admin_password = var.NGROK_ALT_PWD  # Use a more secure method in production!

  network_interface_ids = [
    azurerm_network_interface.proxy.id,
  ]

  os_disk {
    caching              = "ReadWrite"
    storage_account_type = "Standard_LRS"
  }

  source_image_reference {
    publisher = "Canonical"
    offer     = "UbuntuServer"
    sku       = "18.04-LTS"
    version   = "latest"
  }

  computer_name  = "ngrok-alt-vm"
  admin_ssh_key {
    username   = var.NGROK_ALT_UNAME
    public_key = file("~/.ssh/ngrok-key.pub")
  }
}

# Network Interface
resource "azurerm_network_interface" "proxy" {
  name                = "ngrok-alt-nic"
  location            = azurerm_resource_group.proxy.location
  resource_group_name = azurerm_resource_group.proxy.name

  ip_configuration {
    name                          = "internal"
    subnet_id                     = azurerm_subnet.proxy.id
    private_ip_address_allocation = "Dynamic"
    public_ip_address_id          = azurerm_public_ip.proxy.id
  }
}

# Network Security Group
resource "azurerm_network_security_group" "proxy" {
  name                = "ngrok-alt-nsg"
  location            = azurerm_resource_group.proxy.location
  resource_group_name = azurerm_resource_group.proxy.name

  security_rule {
    name                       = "AllowSSH"
    priority                   = 1000
    direction                  = "Inbound"
    access                     = "Allow"
    protocol                   = "Tcp"
    source_port_range          = "*"
    destination_port_range     = "22"
    source_address_prefix      = "*"
    destination_address_prefix = "*"
  }
}

resource "azurerm_network_interface_security_group_association" "proxy" {
  network_interface_id      = azurerm_network_interface.proxy.id
  network_security_group_id = azurerm_network_security_group.proxy.id
}

# DNS Zone
resource "azurerm_dns_zone" "proxy" {
  name                = "yourdomain.com"
  resource_group_name = azurerm_resource_group.proxy.name
}

# A Record
resource "azurerm_dns_a_record" "proxy" {
  name                = "tunnel"
  zone_name           = azurerm_dns_zone.proxy.name
  resource_group_name = azurerm_resource_group.proxy.name
  ttl                 = 300
  records             = [azurerm_public_ip.proxy.ip_address]
}
Enter fullscreen mode Exit fullscreen mode

You'll need public a key at ~/.ssh/ngrok-key.pub and /.ssh/ngrok-key.pem.

Set Env var for subscription: export ARM_SUBSCRIPTION_ID=00000000-xxxx-xxxx-xxxx-xxxxxxxxxxxx

terraform apply -var-file local.tfvars

To ssh you'll have to update the permission on the key. Or else ssh will determine the connection insecure and refuse the connection.

chmod 400 ~/.ssh/ngrok-key.pem

ssh -i ~/.ssh/ngrok-key.pem adminuser@[public-ip]

sudo apt update
sudo apt install nginx -y
Enter fullscreen mode Exit fullscreen mode
sudo vim /etc/nginx/sites-available/tunnel.yourdomain.com
Enter fullscreen mode Exit fullscreen mode

Append the below configuration:

server {
    listen 80;
    server_name tunnel.yourdomain.com;

    location / {
        proxy_pass http://127.0.0.1:8080;
        proxy_set_header Host $host;
        proxy_set_header X-Real-IP $remote_addr;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_set_header X-Forwarded-Proto $scheme;
    }
}

Enter fullscreen mode Exit fullscreen mode

The below steps do:

  • Link configuration
  • Test configuration
  • If test successful restart service
sudo ln -s /etc/nginx/sites-available/tunnel.yourdomain.com /etc/nginx/sites-enabled/
sudo nginx -t
sudo systemctl restart nginx
Enter fullscreen mode Exit fullscreen mode

The below setups an ssh tunnel. However, you'll also have to go to your router/firewall and open up your development port so this tunnel would work.
ssh -R <azure-port>:localhost:<local-laptop-port> <laptop-username>@<laptop-public-ip-or-domain>

To make the tunnel more reliable setup with autossh.

.
Terabox Video Player