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)