Skip to content

feat: initial WireGuard#100

Open
tonsV2 wants to merge 26 commits into
development-2.0from
feat/vpn
Open

feat: initial WireGuard#100
tonsV2 wants to merge 26 commits into
development-2.0from
feat/vpn

Conversation

@tonsV2

@tonsV2 tonsV2 commented May 11, 2026

Copy link
Copy Markdown
Contributor

No description provided.

tonsV2 and others added 7 commits May 12, 2026 06:55
Grafana is no longer publicly accessible. It's now only reachable at
https://grafana.internal for clients connected to the WireGuard VPN.

- Add stacks/traefik/conf.d/internal.yml with VPN-only routes for
  grafana.internal and glowroot.internal (served with mkcert wildcard cert)
- Mount overlays/wireguard/certs/ into stacks/traefik so certs are shared
  between the wireguard and traefik compose projects
- Fix wireguard overlay incompatibilities with multi-instance approach:
  use bind-mount for certs instead of named volume, change network from
  `frontend` to `proxy`, remove orphaned traefik service override that
  referenced the now-deleted single-instance dynamic.yml
- Add service definitions to overlays/wireguard/traefik/internal.yml
  (grafana-internal → monitoring-grafana:3000, glowroot-internal → app:4000)
- Remove stacks/traefik/conf.d/monitoring.yml.template and the
  start-monitoring Makefile step that generated it (public Grafana route)
- Remove GRAFANA_HOSTNAME from monitoring .env.template and generate script;
  hardcode GF_SERVER_ROOT_URL=https://grafana.internal in docker-compose.yml
- Gitignore overlays/wireguard/certs/ and config/ (contain private keys)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Adds wireguard-proxy service that listens on 10.8.0.1:443 (wg0 IP) inside
the WireGuard container's network namespace and forwards to traefik:443.
Docker DNS resolves 'traefik' per connection, so it handles Traefik
container restarts without needing hardcoded IPs or iptables DNAT rules.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Comment thread overlays/wireguard/docker-compose.yml Outdated
Comment thread overlays/wireguard/docker-compose.yml Outdated
Comment thread server-tools/inventory.ini Outdated
Comment thread overlays/wireguard/traefik/internal.yml Outdated
Comment thread overlays/wireguard/scripts/mkcert.sh Outdated
Comment thread Makefile
Comment thread docs/vpn.md
tonsV2 added 8 commits May 19, 2026 04:13
Unreferenced near-duplicate of stacks/traefik/conf.d/internal.yml,
which is the file Traefik actually loads.
`make get-vpn-ca` writes the root CA to the working tree so clients
can install it; it should not land in commits.
The mkcert container that writes the key and the Traefik container
that reads it both run as root, so the key does not need to be
world-readable.
The `domain: "*.internal"` env var was never consumed: the compose
file overrides the image entrypoint with `command: sh /mkcert.sh`,
and the script hardcodes the two host names. The generated cert is
already a 2-SAN cert, not a wildcard.
Sweep em dashes out of files added or modified on this branch.
Pre-existing em dashes in master (README intro and footer) are left
alone as out of scope.
The vishnunair/docker-mkcert image only publishes a `latest` tag, so
pinning by digest is the only stable reference available.
Previously mkcert.sh produced a single 2-SAN cert (grafana.internal +
glowroot.internal) shared by both routes. Switch to one cert per host
and list both in Traefik's tls.certificates so SNI selects the right
one.

Existing deployments must clear the wireguard-certs volume to drop the
old internal.crt/internal.key on first run after this change.
@tonsV2 tonsV2 requested a review from radnov May 18, 2026 21:31
Replaces the pinned vishnunair/docker-mkcert digest with a minimal custom Dockerfile that fetches mkcert directly from the upstream release URL, removing the dependency on a third-party image.
@tonsV2 tonsV2 marked this pull request as ready for review May 19, 2026 21:24

@radnov radnov left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It was a PITA to test this on an M1 Mac, but I think it works as expected, with a few caveats that I've added as comments.

FROM alpine:3.23

ARG MKCERT_VERSION=v1.4.4
ARG TARGETARCH=amd64

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The explicit default overrides Buildkit's auto-populated TARGETARCH, so the build always downloads the amd64 mkcert binary.

On an M* Mac it then runs under Rosetta and crashes with the following error during cert generation:
runtime: lfstack.push invalid packing

Suggested change
ARG TARGETARCH=amd64
ARG TARGETARCH

Comment on lines +88 to +89
wireguard-certs:
name: wireguard-certs

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

wireguard-certs volume is declared in both stacks/traefik and overlays/wireguard, producing

volume "wireguard-certs" already exists but was created for project "traefik" (expected "wireguard")

It's just a warning, but worth declaring it with external: true here.

mkcert "${host}"
mv "${host}.pem" "/certs/${host}.crt"
mv "${host}-key.pem" "/certs/${host}.key"
chmod 600 "/certs/${host}.key"

@radnov radnov May 26, 2026

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I asked if this can be 600 instead of 644 but seems like it's the following error:

ERR error="open /etc/traefik/certs/grafana.internal.key: permission denied" filename=internal.yml providerName=file

and causes Traefik to silently fall back to its built-in default cert.

From a VPN client:

$ curl -kv --resolve grafana.internal:443:10.8.0.1 https://grafana.internal/ 2>&1 | grep subject
  - subject: CN=TRAEFIK DEFAULT CERT

Suggested fix (verified end-to-end on a fresh deploy - Traefik then serves the mkcert cert and https://grafana.internal verifies cleanly):

Suggested change
chmod 600 "/certs/${host}.key"
chown nobody:nobody "/certs/${host}.key"
chmod 640 "/certs/${host}.key"

Comment thread docs/vpn.md

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

On MacOs with Docker Desktop and the "Use kernel networking for UDP" Networking option enabled (the default), inbound UDP packets to a published container port have their source IP rewritten before delivery.

We should add a note somewhere in this doc that this setting should be disabled for the VPN to work on MacOs.

Comment on lines +36 to +38
volumes:
- ./config:/config
- ./coredns/Corefile:/config/coredns/Corefile:ro

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

FYI

Nested "file-on-dir" bind mounts fail on MacOs with VirtioFS (default) - reported at docker/desktop-feedback#420

"dir-on-dir" nesting was fixed in docker/desktop-feedback#264

I changed the default VirtioFS to gRPC FUSE as a workaround. Maybe it's worth adding a note about this in the docs until this is fixed (or not), too.

Co-authored-by: radnov <radoslav@dhis2.org>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants