DEV Community

Schuster Braun for Soft Ground

Posted on

Built a Ngrok replacement

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.

AWS Security LIVE!

Tune in for AWS Security LIVE!

Join AWS Security LIVE! for expert insights and actionable tips to protect your organization and keep security teams prepared.

Learn More

Top comments (0)

A Workflow Copilot. Tailored to You.

Pieces.app image

Our desktop app, with its intelligent copilot, streamlines coding by generating snippets, extracting code from screenshots, and accelerating problem-solving.

Read the docs

πŸ‘‹ Kindness is contagious

Engage with a sea of insights in this enlightening article, highly esteemed within the encouraging DEV Community. Programmers of every skill level are invited to participate and enrich our shared knowledge.

A simple "thank you" can uplift someone's spirits. Express your appreciation in the comments section!

On DEV, sharing knowledge smooths our journey and strengthens our community bonds. Found this useful? A brief thank you to the author can mean a lot.

Okay