So you don’t want to port-forward on your home router or have Cloudflare decrypt all your traffic? Check out Towonel.

Most open source Cloudflare Tunnel alternatives involve setting up a VPS, terminating TLS there on a reverse proxy, then setting up a Wireguard tunnel to your server at home.

Towonel is different: it does not decrypt your traffic on the VPS and you can easily share one, so not every self-hoster has to buy and maintain a VPS.

Check it out!

Mastodon link: https://gts.erwanleboucher.dev/@eleboucher/statuses/01KS4YNA2SYMSP0FSKJVNJA155

  • hamFoilHat@lemmy.world
    link
    fedilink
    English
    arrow-up
    2
    ·
    4 hours ago

    Do you have a link to a tutorial or an example setup for that? I’ve wanted that exact setup but couldn’t find how to do it.

    • BakedCatboy@lemmy.ml
      link
      fedilink
      English
      arrow-up
      3
      ·
      4 hours ago

      Not really haha, you could say I followed a tutorial for setting up a wireguard server on a VPS, and then once I had the wireguard container running and my homelab boxes as clients, I started up an haproxy container on the VPS with network_mode: "service:wireguard" so that the wireguard container can also see my homelab boxes through the tunnel, then also added ports 80 and 443 to the wireguard container on the VPS (in addition to the 51820 for incoming wireguard connections) - that has to be on the wireguard container because using network_mode means the haproxy container piggy backs on the wireguard container’s network, then I added a simple haproxy config that listens on 80/443 on the VPSes public IP and proxies it to the appropriate box on the other side of the tunnel.

      For the wireguard config, the key seems to be using mode tcp in any backend or frontend that’s connected to port 443, so that it just proxies raw data without doing termination. With SNI, you can even proxy to different wireguard clients based on domain, because SNI exposes the domain without needing to do termination. So I do that because I have my NAS as well as a NUC connected to the wireguard network hosting different things.

      This is a stripped down version of my haproxy config:

      global
          maxconn     20000
          log         127.0.0.1 local0
          daemon
      
      defaults
          mode http
          timeout connect 10s
          timeout client 1m
          timeout server 1m
          maxconn 8000
          option tcpka
          option tcp-smart-connect
          default-server init-addr last,libc,none
      
      resolvers docker
          parse-resolv-conf
      
      frontend ingress_http
          bind :::80
          bind :80
      
          acl h_secondbox_http hdr(host) -i second.box.example.com
          use_backend secondbox_http if h_secondbox_http
      
          default_backend vault_http
      
      frontend ingress_https
          mode tcp
          bind :::443
          bind :443
          tcp-request inspect-delay 5s
          tcp-request content accept if { req_ssl_hello_type 1 }
      
          acl h_secondbox_https req_ssl_sni -i second.box.example.com
          use_backend secondbox_https if h_secondbox_https
      
          default_backend vault_https
      
      backend vault_http
          server vault_server_http 10.13.13.2:80 send-proxy-v2
      backend vault_https
          mode tcp
          server vault_server_https 10.13.13.2:443 send-proxy-v2
      
      backend secondbox_http
          server secondbox_server_http 10.13.13.3:80 send-proxy-v2
      backend secondbox_https
          mode tcp
          server secondbox_server_https 10.13.13.3:443 send-proxy-v2
      

      The way this is set up, I do have to manually enter every subdomain I want to go to my second box, but the default is to route to my main vault, which is where I host most stuff anyways.

      My docker compose on the VPS is pretty simple:

      services:
        wireguard:
          image: linuxserver/wireguard:latest
          container_name: wireguard
          restart: unless-stopped
          cap_add:
            - NET_ADMIN
            - SYS_MODULE
          environment:
            - PUID=0
            - PGID=0
            - TZ=America/New_York
            - SERVERURL=wg.example.com #optional
            - SERVERPORT=51820 #optional
            - PEERS=vault,secondbox #optional
            - PEERDNS=auto #optional
            - INTERNAL_SUBNET=10.13.13.0 #optional
            - ALLOWEDIPS=10.13.13.1/24 #optional
            - PERSISTENTKEEPALIVE_PEERS=all #optional
            - LOG_CONFS=true #optional
          volumes:
            - ./volumes/wg-config:/config
          ports:
            - 51820:51820/udp
            - 80:80/tcp
            - 443:443/tcp
            - 8090:8090/tcp
          sysctls:
            - net.ipv4.conf.all.src_valid_mark=1
      
        haproxy:
          image: haproxy:lts
          container_name: haproxy
          restart: unless-stopped
          network_mode: "service:wireguard"
          depends_on:
            - wireguard
          volumes:
            - ./volumes/haproxy-config:/usr/local/etc/haproxy
      

      Then on the local side I use the same network_mode: "service:wireguard" trick to link my traefik container to the wireguard container, that way traffic hitting ports 80/443 of the wireguard container which is on the tunnel is also seen by traefik:

      services:
        boringtun:
          image: boringtun
          build: ./boringtun-docker
          container_name: boringtun
          restart: always
          privileged: true
          cap_add:
            - NET_ADMIN
          devices:
            - "/dev/net/tun:/dev/net/tun"
          volumes:
            - "./volumes/wg-config/wg0.conf:/etc/wireguard/wg0.conf"
          logging:
            driver: "json-file"
            options:
              max-size: "400k"
              max-file: "20"
          environment:
            - INTERFACE_NAME=wg0
            - WG_SUDO=1
            - WG_QUICK_USERSPACE_IMPLEMENTATION=/app/boringtun
          entrypoint: /bin/bash
          command: -c "wg-quick up wg0 && sleep infinity"
          extra_hosts: # Allows containers to access the host machine as host.docker.internal, useful for remote access to the host through a container
            - "host.docker.internal:host-gateway"
          networks:
            - ingress
      
        traefik:
          image: traefik:v2.11
          container_name: traefik
          restart: always
          network_mode: "service:boringtun"
          depends_on:
            - boringtun
          command:
            # - "--log.level=DEBUG"
            - "--providers.docker"
            - "--entrypoints.web.address=:80"
            - "--entryPoints.web.proxyProtocol.trustedIPs=10.13.13.1"
            - "--entrypoints.websecure.address=:443"
            - "--entryPoints.websecure.proxyProtocol.trustedIPs=10.13.13.1"
            - "--entrypoints.web.http.redirections.entrypoint.to=websecure"
            - "--entrypoints.web.http.redirections.entrypoint.scheme=https"
            - "--entrypoints.web.http.redirections.entrypoint.priority=100"
            # Timeouts
            - "--entryPoints.websecure.transport.respondingTimeouts.readTimeout=0"
            - "--entryPoints.websecure.transport.respondingTimeouts.writeTimeout=0"
            - "--entryPoints.websecure.transport.respondingTimeouts.idleTimeout=0"
            - "--providers.docker.exposedByDefault=false"
            - "--providers.docker.network=ingress"
            - "--certificatesresolvers.mytlschallenge.acme.tlschallenge=true"
            - "--certificatesresolvers.mytlschallenge.acme.email=youremail@example.com"
            - "--certificatesresolvers.mytlschallenge.acme.storage=/letsencrypt/acme.json"
            - "--serversTransport.forwardingTimeouts.dialTimeout=3m"
            # - "--api.insecure=true"
            # - "--certificatesresolvers.mytlschallenge.acme.caserver=https://acme-staging-v02.api.letsencrypt.org/directory"
          environment:
            - TZ=America/New_York
          volumes:
            - ./volumes/le-data/acme.json:/letsencrypt/acme.json
            - /var/run/docker.sock:/var/run/docker.sock
      

      I only use boringtun on this side because I think synology doesn’t or didn’t have the kernel module for wireguard and using the userspace mode made it work for me, otherwise you could probably just use the regular wireguard container. Also note that my docker network for communicating between traefik and stuff I’m exposing is ingress, which is specified both on the boringtun container as well as passed to traefik as providers.docker.network, I think that’s needed so that traefik can figure out the container IP of the containers you’re exposing. I also haven’t migrated to traefik v3 because I’m lazy.

      Another note, there’s an annoying condition where if you reboot, it may fail to attach the traefik container to wireguard because it linked via network mode to the old container. Just doing compose down and up fixes it by recreating all the containers. But other than that which I haven’t encountered in a while it works really well. I’m not sure if that bug was fixed because I rarely reboot.

    • stratself@lemdro.id
      link
      fedilink
      English
      arrow-up
      1
      ·
      3 hours ago

      Not exactly a tutorial, but I use SNI routing + TLS passthrough with Caddy-L4 (and previously Traefik), and wrote/collect some stuff about it over the years:

      {
          layer4 {
              tcp/:443 {
                  tcp/127.0.0.1:538
              }
          }
      }