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]
}
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
sudo vim /etc/nginx/sites-available/tunnel.yourdomain.com
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;
    }
}
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
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.
    
Top comments (0)