Building a robust and scalable network in Azure for a small organisaion can be a challenge, especially when aiming to utilise Azure’s native capabilities and move away from third-party tools like Tailscale.

I’m trying to develop an Azure environment that would mimic what would be deployed into a small organisation. I’ve started attempting this journey previously, but haven’t executed the best solution to this. I’ve had a few VMs in a single Virtual Network, with no subnetting, and utilising Tailscale as my main form of networking between VMs, as I have a few VMs as a part of my own home Lab.

What is Tailscale?

For the uninitiated: Tailscale is a mesh VPN service that simplifies creating secure, private networks between your devices, regardless of their location. Unlike traditional VPNs that typically route all traffic through a central server, Tailscale uses the WireGuard protocol to establish direct, encrypted connections between devices whenever possible, creating a peer-to-peer “tailnet”. This approach often results in lower latency and higher speeds. It’s designed for ease of use with minimal configuration, making it a popular choice for connecting servers, personal devices, and even corporate environments securely and efficiently.

But this approach doesn’t allow me to grow with various tooling, and actually utilising the whole Azure Virtual Networking capabilities, with its VNets, Subnetting, Peerings, etc. My Main aim is to create a whole network, comprising of multiple vnets, and subnets, and have my services all available from this mesh-network, without the orchestration from external services, like Tailscale.

Delta Labs is my own fictional ‘Small Organisation’ that I am using to create this scenario against, so any mention of ‘Delta Labs’, or ‘Delta’, etc are referring to the fictitious business

This is going to be a series of blog posts (I hope) outlining how I’m achieving this goal, and the whole story should be available here.

The First Step - Gateway VM

The first idea I had was to attempt to implement a ‘Gateway’ VM - also could be known as a ‘jump box’ amongst other things. The Architecture I thought would be: This would see the VM having 3 ’entry points’:

  1. Traefik (80/tcp, 443/tcp)
    • Would be used as a reverse proxy for both external users to access any internal resources, but also for me to be able to access my own resources without connection to the VPN (hopefully Soon™)
    • It would also act as the gateway to the WGDashboard which would orchestrate the connections to the two WireGuard interfaces hosted (outlined in points 2. and 3.)
  2. Lab-WG WireGuard Interface (51820/udp)
    • This would be used by myself (and anyone else who needs access to any ‘internal’ resources from The Lab).
  3. Public-WG WireGuard Interface (51821/udp)
    • This would be a WireGuard interface that only allowed outbound traffic back to the internet, so it could be used by friends and family as a modern-day VPN service (similar to ProtonVPN )
The final two points will be implemented in a further blog post

Traefik Setup

Pronounced ‘Traffic’

Traefik would be the main HTTP(S) Gateway into the Delta Labs Network. Yes, I do mean to include HTTPS in that, as I will be implementing SSL Certificates into this scenario.

Deployment

I used Docker for the running of Traefik, as I’ve never used it, and I didn’t see much documentation surrounding the use of the binary itself, therefore I opted for the Docker approach. This also would let me use other docker containers as services later down the line if needed (hint hint ForwardAuth), but lets save that for another blog post ;) .

I used the FileProvider for Traefik, as I was going to be doing lots of forwarding to external servers, so it would be easiest to define these in a .yml, rather than doing some Docker-fu

Configs:

DockerCompose.yml
services:
  traefik:
    image: traefik:v3.3
    container_name: traefik
    volumes:
      - /var/run/docker.sock:/var/run/docker.sock # Docker Events 
      - ./traefik.yml:/etc/traefik/traefik.yml:ro # Traefik *static* Config
      - ./configs:/etc/traefik/configs # Traefik *dynamic* Configs
      - /etc/delta-ssl:/ssl:ro # My SSL Certificates (more later)
    ports:
      - "80:80"
      - "443:443"
    restart: unless-stopped
traefik.yml
global:
  checkNewVersion: true
  sendAnonymousUsage: false

log:
  level: DEBUG

api:
  dashboard: true # Keeps the Dashboard active, however requires connection internally, rather than externally
  #insecure: true # If you want to access the dashboard via :8080 (only use for testing!)

entryPoints:
  web:
    address: :80
  websecure:
    address: :443

providers:
  file:
    directory: "/etc/traefik/configs" # My Traefik Dynamic Config Folder
    watch: true
configs/traefik-dash.yml
http:
  # Add the router
  routers:
    dashboard:
      entryPoints:
        - "websecure"
      middlewares:
      - my-basic-auth
      service: api@internal
      rule: Host(`<<HOST>>`)

  # Add the middleware
  middlewares:
    my-basic-auth:
      basicAuth:
        users:
        - thejmc:$apr1$Srbu4MXR$0f/EjsHJvGgVjE4SeV2Yv. # thejmc/test123
Important

<<HOST>> should be replaced with the Host you are trying to filter on - e.g. service.example.com. There are other rules that can be used. See here for them!

Once all this was set up it was just a simple docker compose up -d to get Traefik running, and working to connect to the Dashboard on <<HOST>> .

SSL/TLS

Getting SSL/TLS setup was slightly harder. As I had a requirement to take Certificates from Azure’s Key Vault rather than storing them locally. To do this, I was able to write a Python Script that utilised the Managed Identity assigned to the Virtual Machine, to call out to Key Vault, to retrieve the Certificate.

The Post & Repo on how I made this tool will be out Soon™

Setup

Once I have the certs in the mounted folder (in my case Host:/etc/delta-ssl mapped to Container:/ssl) I was able to pass the cert to Traefik to serve by adding the below code block to the respective Dynamic Config file (e.g. config/traefik-dash.yml)

http:
  routers:
    <router_name>:
      ...
      tls: true

tls:
  certificates:
    - certFile: /ssl/<<HOST>>/domain.crt
      keyFile: /ssl/<<HOST>>/priv.key
For Example in Context:
http:
  # Add the router
  routers:
    dashboard:
      entryPoints:
        - "websecure"
      middlewares:
      - my-basic-auth
      service: api@internal
      rule: Host(`traefik.d.thejmc.cc`)
      tls: true # <==

  # Add the middleware
  middlewares:
    my-basic-auth:
      basicAuth:
        users:
        - thejmc:$apr1$Srbu4MXR$0f/EjsHJvGgVjE4SeV2Yv. # thejmc/test123

tls: # <==
  certificates:
    - certFile: /ssl/<<HOST>>/domain.crt
      keyFile: /ssl/<<HOST>>/priv.key

Challenges

  • The Dynamic Configs (e.g. configs/*) needs to have the extension of .yml for it to be loaded in. I had an issue with trying to use configs without an extension, and these would not be picked up by Traefik to be loaded.
  • The whole SSL Cert problem - to be outlined in another Blog Post and linked here once complete.

TL;DR

Here I’ve demonstrated how I setup and deployed my Traefik instance, with configs and everything.

Hope you enjoyed this blog post- its been my first one (clearly). I’ll be posting more of the upgrade as and when I am able to do this, as well as writing other random posts about topics I find interesting!