Skip to content

Network & Connectivity

Osotechie edited this page Jun 22, 2026 · 2 revisions

Network & Connectivity

This page covers how GitHub Actions runners connect to the NAS sitting on a private home network — and the security decisions behind it.

The Problem

GitHub-hosted runners are ephemeral VMs on Microsoft's infrastructure. My NAS sits on a private network behind a router. How do we bridge that gap securely?

The Solution: WireGuard VPN

Each workflow run establishes a temporary WireGuard tunnel from the runner to my home network. This gives the runner a routable path to the NAS without exposing any services to the public internet.

flowchart LR
    subgraph GitHub Actions Runner
        WF[Workflow<br/>ephemeral]
    end

    WF <-->|WireGuard<br/>encrypted tunnel| WGP

    subgraph Home Network
        WGP[WG Peer<br/>router/endpoint] -->|LAN| NAS[NAS<br/>10.1.1.118]
    end
Loading

Why WireGuard?

  • Simple — single config file, minimal dependencies
  • Fast — connects in milliseconds (critical for CI/CD where every second counts)
  • Secure — modern cryptography, minimal attack surface
  • Stateless — no "session" to keep alive between workflow runs

🔗 WireGuard official docs | WireGuard whitepaper

How It Works in the Workflow

- name: Connect to Wireguard
  run: |
    sudo apt-get install -y wireguard resolvconf
    echo "${{ secrets.WG_CONFIG_FILE }}" > wg0.conf
    sudo wg-quick up ./wg0.conf

- name: Add Routes
  run: |
    sudo ip route add ${{ vars.HOST_IP }} dev wg0

The full WireGuard configuration (interface private key, peer public key, endpoint, allowed IPs) is stored as a single GitHub Secret (WG_CONFIG_FILE). This keeps the config atomic — you either have access or you don't.

SSH Connectivity

Once the tunnel is up, the workflow connects to the NAS via SSH for:

  • nas-as-code: Running Ansible playbooks
  • nas-containers-as-code: Docker-over-SSH (remote Docker context)

Host Fingerprint Pinning

Rather than using ssh-keyscan (which trusts whatever host key the network hands it — a MITM risk), I pin the NAS's SSH host fingerprint as a GitHub Secret (HOST_FINGERPRINT).

      - name: Add SSH Key
        run: |
          mkdir -p ~/.ssh
          echo "${{ secrets.SSH_PRIVATE_KEY }}" > ~/.ssh/id_rsa
          chmod 600 ~/.ssh/id_rsa
          echo "${{ secrets.HOST_FINGERPRINT }}" > ~/.ssh/known_hosts
          chmod 644 ~/.ssh/known_hosts

⚠️ If the NAS is ever rebuilt and its SSH host keys regenerate, you must update the HOST_FINGERPRINT secret or the workflow will refuse to connect. This is intentional — it forces you to consciously acknowledge the new host identity.

SSH Multiplexing

For the container deployment (which makes many Docker API calls over SSH), I enable SSH connection multiplexing to avoid hitting the NAS's MaxSessions limit:

Host *
  ControlMaster auto
  ControlPath ~/.ssh/control-%C
  ControlPersist 10m

This keeps a single SSH connection open and multiplexes all subsequent commands over it. Without this, the media stack (13+ services) would blow past the default MaxSessions=10 and fail with "Session open refused by peer".

🔗 Docker SSH tips | OpenSSH multiplexing

Security Summary

Layer Mechanism Protects Against
Network WireGuard tunnel Eavesdropping, unauthorised access
Host Identity Pinned fingerprints MITM / host impersonation
Authentication SSH private key (in GitHub Secret) Unauthorised SSH access
Session Ephemeral runner No persistent credentials on disk

Nothing is exposed to the public internet. The WireGuard endpoint is the only inbound port, and it only responds to peers with the correct pre-shared key.

Clone this wiki locally