From 0155c0af6f01f8654667658adc33412bc8394e7b Mon Sep 17 00:00:00 2001 From: ChillBill77 <129118422+ChillBill77@users.noreply.github.com> Date: Wed, 24 Jun 2026 21:40:25 +0200 Subject: [PATCH 1/8] Homebridge feature --- README.md | 1 + services/homebridge/.env | 18 ++++++ services/homebridge/README.md | 77 +++++++++++++++++++++++ services/homebridge/compose.tailscale.yml | 41 ++++++++++++ services/homebridge/compose.yml | 39 ++++++++++++ 5 files changed, 176 insertions(+) create mode 100644 services/homebridge/.env create mode 100644 services/homebridge/README.md create mode 100644 services/homebridge/compose.tailscale.yml create mode 100644 services/homebridge/compose.yml diff --git a/README.md b/README.md index 2a8cb0c..b4cc9a9 100644 --- a/README.md +++ b/README.md @@ -203,6 +203,7 @@ ScaleTail provides ready-to-run [Docker Compose](https://docs.docker.com/compose | ๐Ÿ  Service | ๐Ÿ“ Description | ๐Ÿ”— Link | | -------------------- | ---------------------------------------------------------------------- | ---------------------------------- | | ๐Ÿก **Home Assistant** | An open-source home automation platform for controlling smart devices. | [Details](services/home-assistant) | +| **Home Bridge** | Homebridge is a lightweight Node.js server that emulates the HomeKit API. | [Details](services/homebridge) | ### ๐Ÿ“ฑ Utilities diff --git a/services/homebridge/.env b/services/homebridge/.env new file mode 100644 index 0000000..827da40 --- /dev/null +++ b/services/homebridge/.env @@ -0,0 +1,18 @@ +# ============================================================================= +# Homebridge + Tailscale Sidecar Environment +# Copy to .env and fill values. Keep TS_AUTHKEY secret! +# ============================================================================= + +# --- REQUIRED: Tailscale --- +# Get a reusable or ephemeral auth key from https://login.tailscale.com/admin/settings/keys +# Recommended: auth key with "reusable" + "ephemeral" for containers, tagged if using ACLs +TS_AUTHKEY=tskey-auth-XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX + +# --- REQUIRED: Service identity --- +SERVICE=homebridge +IMAGE_URL=homebridge/homebridge:latest + +# --- Common container settings --- +TZ=Europe/Amsterdam +PUID=1000 +PGID=1000 diff --git a/services/homebridge/README.md b/services/homebridge/README.md new file mode 100644 index 0000000..151d132 --- /dev/null +++ b/services/homebridge/README.md @@ -0,0 +1,77 @@ +# homebridge with Tailscale Sidecar + +This Docker Compose configuration sets up [Homebridge](https://github.com/homebridge/homebridge) with Tailscale as a sidecar container. Homebridge emulates the iOS HomeKit API, enabling HomeKit integration for non-Apple smart home devices. The Homebridge UI is reachable **only over your Tailnet** via HTTPS. + +## โš ๏ธ Critical Limitation + +**Homebridge requires `host` network mode for HomeKit mDNS discovery.** This sidecar configuration isolates Homebridge into a separate network namespace, which **breaks HomeKit discovery entirely**. HomeKit clients will not be able to find or control your devices. + +**What works:** +- Access the Homebridge UI web interface over HTTPS via Tailnet: `https://homebridge.your-tailnet.ts.net` +- Install plugins, edit config, view logs +- Full remote administration + +**What doesn't work:** +- HomeKit device discovery on your local network +- HomeKit app integration from iPhone/iPad +- Siri control via HomeKit + +If you need HomeKit discovery, run Homebridge with `network_mode: host` instead (standard Docker setup, no Tailscale sidecar). If you only need the web UI for administration from Tailnet, proceed with this configuration. + +## About Homebridge + +Homebridge is a lightweight Node.js server that emulates the HomeKit API. It translates between HomeKit and non-HomeKit smart home devices (Philips Hue, IKEA TRADFRI, etc.), allowing them to integrate into the Apple Home ecosystem. This image includes FFmpeg with libfdk-aac for camera streaming support. + +## Architecture + +- `tailscale`: Runs the Tailscale client, obtains a Tailnet IP, serves HTTPS on :443 proxying to Homebridge UI on internal port 8581. +- `homebridge`: Runs in the **same network namespace** as the tailscale container. Configuration and plugins persist in a dedicated volume. + +Access the UI at: `https://homebridge.your-tailnet.ts.net` (after auth and healthy state). + +## Prerequisites + +- Docker & Docker Compose v2.20+ +- Tailscale account + ability to create auth keys +- Host user in `docker` group (or use sudo) +- Pre-create volume directories (recommended): + ```bash + mkdir -p homebridge-data/config ts-homebridge-state + ``` + +## First Run & Setup + +On first boot, Homebridge generates a unique setup code in the logs. Find it with: +```bash +docker compose logs homebridge | grep -i "setup code" +``` + +You'll use this code if setting up HomeKit bridges (only if HomeKit discovery were enabled). For web UI access, no setup code is needed. + +## Configuration + +### Important Notes + +- **ENABLE_AVAHI=0**: Avahi (mDNS daemon) is disabled since the sidecar network prevents proper mDNS broadcasts. HomeKit discovery is not functional in this setup. +- **Tailscale Serve**: The sidecar automatically proxies port 443 to the Homebridge UI on 8581. HTTPS certificates are auto-provisioned by Tailscale. +- **Plugins**: Install via the Homebridge UI. They persist in the mounted volume. + +### Optional Environment Variables + +Add to `compose.yml` under the `homebridge` service `environment:` block if needed: +- `HB_PORT=8581` โ€” Web UI port (change if desired, but update compose.tailscale.yml Proxy port to match) + +## Upstream Documentation + +- [Homebridge GitHub](https://github.com/homebridge/homebridge) +- [Homebridge Docker Guide](https://github.com/homebridge/homebridge/wiki/Install-Homebridge-on-Docker) +- [Homebridge Plugins](https://www.npmjs.com/search?q=homebridge-plugin) +- [Homebridge UI](https://github.com/homebridge/homebridge-config-ui-x) +- [Tailscale Docker Guide](https://tailscale.com/blog/docker-tailscale-guide) + +## Troubleshooting + +- **Container unhealthy**: Check `docker compose logs tailscale` and `docker compose logs homebridge` +- **Can't reach UI over HTTPS**: Verify Tailscale connection with `docker compose exec tailscale tailscale status`. If not logged in, check TS_AUTHKEY. +- **UI timeout or 502**: Ensure Homebridge is running with `docker compose logs homebridge`. Check that internal port 8581 is correct. +- **HomeKit discovery not working**: This is expected. The sidecar breaks mDNS. Run with `network_mode: host` for full HomeKit support. \ No newline at end of file diff --git a/services/homebridge/compose.tailscale.yml b/services/homebridge/compose.tailscale.yml new file mode 100644 index 0000000..0470824 --- /dev/null +++ b/services/homebridge/compose.tailscale.yml @@ -0,0 +1,41 @@ +# ============================================================================= +# Reusable Tailscale Sidecar (shared with other services) +# Serves HTTPS on :443 proxying to app port via Tailscale Serve +# ============================================================================= + +services: + tailscale: + image: tailscale/tailscale:latest + container_name: ts-${SERVICE} + hostname: ${SERVICE} + environment: + - TS_AUTHKEY=${TS_AUTHKEY} + - TS_STATE_DIR=/var/lib/tailscale + - TS_SERVE_CONFIG=/config/serve.json # Tailscale Serve configuration to expose the web interface on your local Tailnet - remove this line if not required + #- TS_USERSPACE=false + - TS_ENABLE_HEALTH_CHECK=true # Enable healthcheck endpoint: "/healthz" + - TS_LOCAL_ADDR_PORT=127.0.0.1:41234 # The : for the healthz endpoint + #- TS_ACCEPT_DNS=true # Uncomment when using MagicDNS + - TS_AUTH_ONCE=true + configs: + - source: ts-serve + target: /config/serve.json + volumes: + - ./config:/config # Config folder used to store Tailscale files - you may need to change the path + - ./ts/state:/var/lib/tailscale # Tailscale requirement - you may need to change the path + devices: + - /dev/net/tun:/dev/net/tun # Network configuration for Tailscale to work + cap_add: + - net_admin + - sys_module + restart: always + ports: + - "5353:5353" + - "443:443" + healthcheck: + test: ["CMD", "wget", "--spider", "-q", "http://127.0.0.1:41234/healthz"] # Check Tailscale has a Tailnet IP and is operational + interval: 1m # How often to perform the check + timeout: 10s # Time to wait for the check to succeed + retries: 3 # Number of retries before marking as unhealthy + start_period: 10s # Time to wait before starting health checks + #entrypoint: sh -c 'tailscaled -tun=userspace-networking & sleep 2 && tailscale up --authkey=${TS_AUTHKEY} --hostname=${SERVICE} && tailscale serve https:443 http://127.0.0.1:8581 & wait' diff --git a/services/homebridge/compose.yml b/services/homebridge/compose.yml new file mode 100644 index 0000000..fab7b53 --- /dev/null +++ b/services/homebridge/compose.yml @@ -0,0 +1,39 @@ +# ============================================================================= +# Homebridge + Tailscale Sidecar Compose +# WARNING: This config breaks HomeKit discovery. See README for details. +# Always validate with: docker compose config +# ============================================================================= +configs: + ts-serve: + content: | + {"TCP":{"443":{"HTTPS":true}}, + "Web":{"$${TS_CERT_DOMAIN}:443": + {"Handlers":{"/": + {"Proxy":"http://127.0.0.1:8581"}}}}, + "AllowFunnel":{"$${TS_CERT_DOMAIN}:443":false}} + +include: + - ./compose.tailscale.yml + +services: + homebridge: + image: ${IMAGE_URL} + network_mode: service:tailscale + container_name: app-${SERVICE} + environment: + - PUID=${PUID} + - PGID=${PGID} + - TZ=${TZ} + - ENABLE_AVAHI=0 + volumes: + - ./${SERVICE}-data/config:/homebridge + depends_on: + tailscale: + condition: service_healthy + healthcheck: + test: ["CMD", "curl", "-f", "http://127.0.0.1:8581"] + interval: 60s + timeout: 10s + retries: 3 + start_period: 30s + restart: always From dda12b4fe13022249459969c1dd3fecd04986b6a Mon Sep 17 00:00:00 2001 From: ChillBill77 <129118422+ChillBill77@users.noreply.github.com> Date: Wed, 24 Jun 2026 21:55:30 +0200 Subject: [PATCH 2/8] Update compose.tailscale.yml --- services/homebridge/compose.tailscale.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/services/homebridge/compose.tailscale.yml b/services/homebridge/compose.tailscale.yml index 0470824..dcd4208 100644 --- a/services/homebridge/compose.tailscale.yml +++ b/services/homebridge/compose.tailscale.yml @@ -30,8 +30,8 @@ services: - sys_module restart: always ports: - - "5353:5353" - - "443:443" + # - "5353:5353" #Receive mDNS but does not do anything for Apple HomeBridge + # - "443:443" #Uncomment for local acces healthcheck: test: ["CMD", "wget", "--spider", "-q", "http://127.0.0.1:41234/healthz"] # Check Tailscale has a Tailnet IP and is operational interval: 1m # How often to perform the check From bbc421d76161dbf2cebe64bdeda36d5fdef1d385 Mon Sep 17 00:00:00 2001 From: ChillBill77 <129118422+ChillBill77@users.noreply.github.com> Date: Sun, 28 Jun 2026 14:03:13 +0200 Subject: [PATCH 3/8] Update compose.yml Removed the Include --- services/homebridge/compose.yml | 38 ++++++++++++++++++++++++++++++--- 1 file changed, 35 insertions(+), 3 deletions(-) diff --git a/services/homebridge/compose.yml b/services/homebridge/compose.yml index fab7b53..7532018 100644 --- a/services/homebridge/compose.yml +++ b/services/homebridge/compose.yml @@ -12,10 +12,42 @@ configs: {"Proxy":"http://127.0.0.1:8581"}}}}, "AllowFunnel":{"$${TS_CERT_DOMAIN}:443":false}} -include: - - ./compose.tailscale.yml - services: + tailscale: + image: tailscale/tailscale:latest + container_name: ts-${SERVICE} + hostname: ${SERVICE} + environment: + - TS_AUTHKEY=${TS_AUTHKEY} + - TS_STATE_DIR=/var/lib/tailscale + - TS_SERVE_CONFIG=/config/serve.json # Tailscale Serve configuration to expose the web interface on your local Tailnet - remove this line if not required + #- TS_USERSPACE=false + - TS_ENABLE_HEALTH_CHECK=true # Enable healthcheck endpoint: "/healthz" + - TS_LOCAL_ADDR_PORT=127.0.0.1:41234 # The : for the healthz endpoint + #- TS_ACCEPT_DNS=true # Uncomment when using MagicDNS + - TS_AUTH_ONCE=true + configs: + - source: ts-serve + target: /config/serve.json + volumes: + - ./config:/config # Config folder used to store Tailscale files - you may need to change the path + - ./ts/state:/var/lib/tailscale # Tailscale requirement - you may need to change the path + devices: + - /dev/net/tun:/dev/net/tun # Network configuration for Tailscale to work + cap_add: + - net_admin + - sys_module + restart: always + ports: + # - "5353:5353" #Receive mDNS but does not do anything for Apple HomeBridge + # - "443:443" #Uncomment for local acces + healthcheck: + test: ["CMD", "wget", "--spider", "-q", "http://127.0.0.1:41234/healthz"] # Check Tailscale has a Tailnet IP and is operational + interval: 1m # How often to perform the check + timeout: 10s # Time to wait for the check to succeed + retries: 3 # Number of retries before marking as unhealthy + start_period: 10s # Time to wait before starting health checks + homebridge: image: ${IMAGE_URL} network_mode: service:tailscale From ded9d3ae0f21a19b2064dede23860da3b381baff Mon Sep 17 00:00:00 2001 From: ChillBill77 <129118422+ChillBill77@users.noreply.github.com> Date: Sun, 28 Jun 2026 14:04:20 +0200 Subject: [PATCH 4/8] Delete services/homebridge/compose.tailscale.yml --- services/homebridge/compose.tailscale.yml | 41 ----------------------- 1 file changed, 41 deletions(-) delete mode 100644 services/homebridge/compose.tailscale.yml diff --git a/services/homebridge/compose.tailscale.yml b/services/homebridge/compose.tailscale.yml deleted file mode 100644 index dcd4208..0000000 --- a/services/homebridge/compose.tailscale.yml +++ /dev/null @@ -1,41 +0,0 @@ -# ============================================================================= -# Reusable Tailscale Sidecar (shared with other services) -# Serves HTTPS on :443 proxying to app port via Tailscale Serve -# ============================================================================= - -services: - tailscale: - image: tailscale/tailscale:latest - container_name: ts-${SERVICE} - hostname: ${SERVICE} - environment: - - TS_AUTHKEY=${TS_AUTHKEY} - - TS_STATE_DIR=/var/lib/tailscale - - TS_SERVE_CONFIG=/config/serve.json # Tailscale Serve configuration to expose the web interface on your local Tailnet - remove this line if not required - #- TS_USERSPACE=false - - TS_ENABLE_HEALTH_CHECK=true # Enable healthcheck endpoint: "/healthz" - - TS_LOCAL_ADDR_PORT=127.0.0.1:41234 # The : for the healthz endpoint - #- TS_ACCEPT_DNS=true # Uncomment when using MagicDNS - - TS_AUTH_ONCE=true - configs: - - source: ts-serve - target: /config/serve.json - volumes: - - ./config:/config # Config folder used to store Tailscale files - you may need to change the path - - ./ts/state:/var/lib/tailscale # Tailscale requirement - you may need to change the path - devices: - - /dev/net/tun:/dev/net/tun # Network configuration for Tailscale to work - cap_add: - - net_admin - - sys_module - restart: always - ports: - # - "5353:5353" #Receive mDNS but does not do anything for Apple HomeBridge - # - "443:443" #Uncomment for local acces - healthcheck: - test: ["CMD", "wget", "--spider", "-q", "http://127.0.0.1:41234/healthz"] # Check Tailscale has a Tailnet IP and is operational - interval: 1m # How often to perform the check - timeout: 10s # Time to wait for the check to succeed - retries: 3 # Number of retries before marking as unhealthy - start_period: 10s # Time to wait before starting health checks - #entrypoint: sh -c 'tailscaled -tun=userspace-networking & sleep 2 && tailscale up --authkey=${TS_AUTHKEY} --hostname=${SERVICE} && tailscale serve https:443 http://127.0.0.1:8581 & wait' From 33bfb21e17db84557296e52debd96378bc3de93d Mon Sep 17 00:00:00 2001 From: ChillBill <129118422+ChillBill77@users.noreply.github.com> Date: Mon, 29 Jun 2026 13:25:38 +0200 Subject: [PATCH 5/8] SOCFortress WAF with Tailscale Protection --- services/socfortress-waf/.env | 55 ++++++ services/socfortress-waf/README.md | 117 +++++++++++++ services/socfortress-waf/compose.yml | 252 +++++++++++++++++++++++++++ 3 files changed, 424 insertions(+) create mode 100644 services/socfortress-waf/.env create mode 100644 services/socfortress-waf/README.md create mode 100644 services/socfortress-waf/compose.yml diff --git a/services/socfortress-waf/.env b/services/socfortress-waf/.env new file mode 100644 index 0000000..017a15f --- /dev/null +++ b/services/socfortress-waf/.env @@ -0,0 +1,55 @@ +# ============================================================================= +# WAF Management Platform + Tailscale sidecar โ€” Environment +# Copy to .env and fill every CHANGE_ME value. Never commit .env. +# ============================================================================= + +# --- Tailscale sidecar (admin UI over your Tailnet) ---------------------- +# Reusable or ephemeral auth key: https://login.tailscale.com/admin/settings/keys +TS_AUTHKEY=tskey-auth-XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX +# MagicDNS hostname -> admin UI lands at https://..ts.net +TS_HOSTNAME=socfortressWAF +# Optional, e.g. if your tailnet uses ACL tags: --advertise-tags=tag:container +# TS_EXTRA_ARGS= + +# --- Image version ------------------------------------------------------- +# Pin a release tag (e.g. v1.0.0) for reproducible deploys; "latest" tracks newest. +WAF_IMAGE_TAG=latest + +# --- GeoIP (MaxMind GeoLite2) -------------------------------------------- +# Host path to your own GeoLite2-City.mmdb (see README -> GeoIP setup). +GEOIP_DB_PATH=./GeoLite2-City.mmdb + +# --- PostgreSQL ---------------------------------------------------------- +POSTGRES_DB=wafdb +POSTGRES_USER=wafuser +POSTGRES_PASSWORD=CHANGE_ME +POSTGRES_HOST=postgres +POSTGRES_PORT=5432 + +# --- Redis --------------------------------------------------------------- +REDIS_URL=redis://redis:6379/0 + +# --- API security -------------------------------------------------------- +# python3 -c "import secrets; print(secrets.token_hex(32))" +SECRET_KEY=CHANGE_ME_at_least_32_random_characters +# python3 -c "from cryptography.fernet import Fernet; print(Fernet.generate_key().decode())" +TOTP_ENCRYPTION_KEY=CHANGE_ME_generate_with_command_above + +# CORS โ€” MUST list the Tailnet URL you log in from (no wildcard allowed). +# Replace with your tailnet name (see README -> Gotchas). +ALLOWED_ORIGINS=https://waf-admin..ts.net + +# --- Caddy Admin API (container-internal) -------------------------------- +CADDY_ADMIN_URL=http://caddy-waf:2019 + +# --- SMTP (optional, for alert notifications) ---------------------------- +SMTP_HOST= +SMTP_PORT=587 +SMTP_USER= +SMTP_PASSWORD= +SMTP_FROM= + +# --- Bootstrap superadmin (seeded on first run only) --------------------- +# Change the password immediately after first login. +BOOTSTRAP_ADMIN_EMAIL=admin@example.com +BOOTSTRAP_ADMIN_PASSWORD=CHANGE_ME \ No newline at end of file diff --git a/services/socfortress-waf/README.md b/services/socfortress-waf/README.md new file mode 100644 index 0000000..20bc454 --- /dev/null +++ b/services/socfortress-waf/README.md @@ -0,0 +1,117 @@ +# SOCFortress WAF with Tailscale (admin UI on your Tailnet) + +This adds a Tailscale sidecar to the [SOCFortress WAF Management Platform](https://github.com/socfortress/waf-platform-public) so the **admin UI is reachable only over your Tailnet** at `https://..ts.net`, with a valid HTTPS certificate. The **WAF data plane is left exactly as upstream ships it** โ€” `caddy-waf` still listens on host ports **80/443** so it can front your protected sites with automatic per-site Let's Encrypt certs and see real client IPs. + +Funnel (public internet exposure) is **intentionally disabled**. See [Why no Funnel](#why-no-funnel). + +## About this setup + +The WAF platform is a six-service stack (Caddy+Coraza engine, FastAPI admin API, React/Nginx admin UI, PostgreSQL, Redis, and a demo upstream). Only one piece โ€” the **admin console** โ€” benefits from being private-by-default, and that is exactly what a WAF operator wants: keep the control panel off the public internet, reach it securely from anywhere over Tailscale. The Tailscale container joins the stack's `waf-internal` network as a normal peer and reverse-proxies to `admin-ui:8080` by its Docker DNS name. Nothing about how the WAF protects traffic changes. + +## Architecture + +- `tailscale` (from `compose.tailscale.yml`): joins your Tailnet, terminates HTTPS on `:443` using the node's `*.ts.net` certificate, and proxies to the admin UI. **Tailnet-only** (`AllowFunnel: false`). +- `admin-ui`: serves the console on internal `:8080` over HTTPS with a self-signed cert. The sidecar reaches it via `https+insecure://admin-ui:8080` (the public-facing `*.ts.net` cert is valid, so your browser sees no warning). +- `caddy-waf` and the rest of the stack: unchanged. Data plane stays on host `80/443`. + +Access the admin UI at: `https://..ts.net` (after first auth + HTTPS enabled in your tailnet). + +## Prerequisites + +- The upstream repo cloned (this is where `docker-compose.yml` lives): + ```bash + git clone https://github.com/socfortress/waf-platform-public.git waf-platform + cd waf-platform + ``` +- Docker โ‰ฅ 24 and **Docker Compose โ‰ฅ 2.24** (needed for inline `configs` content). +- Ports **80**, **443** (data plane) available on the host. Port 8443 is no longer needed once the admin UI is Tailnet-only (see step 4). +- A Tailscale account and an auth key, with **HTTPS enabled** in your tailnet (Admin console โ†’ DNS โ†’ enable MagicDNS and HTTPS Certificates). +- A free MaxMind **GeoLite2-City.mmdb** (optional but recommended; the stack starts without it but GeoIP log enrichment is disabled). + +## Quick Start + +1. Drop these four files into the cloned `waf-platform` directory (next to the upstream `docker-compose.yml`): + - `.env`, `compose.yml`, `compose.tailscale.yml`, `README.md` +2. Copy and edit the env file: + ```bash + cp .env .env # already named .env here; edit it in place + ``` + Fill every `CHANGE_ME`, set a real `TS_AUTHKEY`, and set `ALLOWED_ORIGINS` to your Tailnet URL (see [Gotchas](#important-notes--gotchas)). Generate secrets with: + ```bash + openssl rand -hex 32 # POSTGRES_PASSWORD + python3 -c "import secrets; print(secrets.token_hex(32))" # SECRET_KEY + python3 -c "from cryptography.fernet import Fernet; print(Fernet.generate_key().decode())" # TOTP_ENCRYPTION_KEY + ``` +3. (Recommended) Make the admin UI **Tailnet-only** by removing its host port. In the upstream `docker-compose.yml`, comment out the two `ports:` lines under the `admin-ui` service: + ```yaml + admin-ui: + ... + # ports: + # - "8443:8080" + ``` + Leave them in if you also want LAN access on `https://:8443`. +4. Validate (see [Linting](#linting--validation)), then start everything: + ```bash + docker compose up -d + docker compose ps + docker compose logs -f tailscale # watch for "Logged in as..." and serve config applied + ``` +5. Find your node's URL (`tailscale status` inside the container, or the Tailscale admin console). It will be `https://..ts.net`. +6. Make sure `ALLOWED_ORIGINS` in `.env` matches that exact URL, then apply it: + ```bash + docker compose up -d admin-api + ``` +7. From any device on your Tailnet, open `https://..ts.net`, log in with the bootstrap admin, change the password, and enroll TOTP. + +## Important Notes & Gotchas + +- **CORS is the #1 thing people miss.** The admin API rejects origins not in `ALLOWED_ORIGINS` (no wildcard). When you move login to the Tailnet URL, you **must** set `ALLOWED_ORIGINS=https://..ts.net` and recreate `admin-api`. Symptoms if you forget: the page loads but login/API calls fail. +- **Enable HTTPS in your tailnet** (Admin console โ†’ DNS โ†’ MagicDNS + HTTPS Certificates). Without it, Tailscale Serve cannot provision the `*.ts.net` certificate and the serve config is skipped. Public DNS for a fresh tailnet name can take up to ~10 minutes. +- **Self-signed backend is expected.** The admin UI's internal cert is self-signed, so the sidecar uses `https+insecure://admin-ui:8080`. Your browser still sees a valid Let's Encrypt cert because Tailscale terminates TLS at the edge with the `*.ts.net` cert. +- **The data plane is still public.** `caddy-waf` keeps listening on host `80/443`. Point DNS for the sites you protect at this host as you normally would; Caddy's automatic per-site TLS and GeoIP/client-IP features are unaffected. Tailscale is only in front of the admin UI. +- **Updating:** `docker compose pull && docker compose up -d`. Data lives in named volumes and survives updates. The Tailscale node identity persists in the `tailscale-state` volume. + +### Why no Funnel + +You asked about Funnel originally; here is why this setup leaves it off. Tailscale Funnel can only serve the single `*.ts.net` hostname on ports 443/8443/10000 and terminates TLS at Tailscale's edge. Putting the WAF data plane behind Funnel would (a) collapse all your protected sites to one hostname and break per-site Let's Encrypt, and (b) hide the real client IP, degrading the GeoIP enrichment and IP-based rules the platform is built around. Funneling the admin console would publish your firewall's control panel to the internet. If you ever do want a single site exposed publicly through Tailscale, flip `AllowFunnel` to `true` in `compose.tailscale.yml` for the relevant port โ€” but understand the tradeoffs first. + +## Files + +- `.env` โ€” all WAF variables + the Tailscale section (`TS_AUTHKEY` is critical; keep private). +- `compose.yml` โ€” orchestrator; `include`s the upstream stack and the sidecar. Auto-selected by `docker compose`. +- `compose.tailscale.yml` โ€” the Tailscale sidecar service + inline serve config. +- `docker-compose.yml` โ€” upstream, unchanged (provided by the cloned repo; not in this bundle). + +## Linting & Validation + +Run these from the `waf-platform` directory after editing (requires a filled `.env`): + +```bash +# Schema + interpolation + merge of the included files +docker compose config --quiet +# Silent + exit 0 means good. + +# Optional strict YAML lint +yamllint compose.yml compose.tailscale.yml + +# Full merged config dump (for debugging the include/override) +docker compose config +``` + +## Upstream Documentation + +- [SOCFortress WAF repo](https://github.com/socfortress/waf-platform-public) +- [Tailscale Serve](https://tailscale.com/kb/1242/tailscale-serve) ยท [Tailscale + Docker guide](https://tailscale.com/blog/docker-tailscale-guide) +- [Enabling HTTPS in your tailnet](https://tailscale.com/kb/1153/enabling-https) + +## Troubleshooting + +- **`serve proxy: no serve config ... skipping`** โ€” HTTPS isn't enabled in your tailnet yet, or the config file path is wrong. Enable HTTPS, then `docker compose up -d tailscale`. If it persists, switch from the inline `configs:` block to a bind-mounted file: create `./config/serve.json` with the same JSON (use literal `${TS_CERT_DOMAIN}`), and mount `./config:/config` on the `tailscale` service. +- **Browser cert warning / no page** โ€” the `*.ts.net` cert may still be provisioning (wait a few minutes), or HTTPS isn't enabled in the tailnet. +- **502 / connection refused at the `.ts.net` URL** โ€” `admin-ui` not healthy yet (`docker compose ps`), or its internal port isn't 8080; check `docker compose logs admin-ui`. +- **Login/API fails after the page loads** โ€” `ALLOWED_ORIGINS` doesn't match the Tailnet URL. Fix `.env` and `docker compose up -d admin-api`. +- **No Tailnet IP** โ€” auth key expired/invalid, or ACL/tag restrictions; check `docker compose logs tailscale`. + +--- + +*Generated for the SOCFortress WAF using the Tailscale sidecar templates. Option A: private admin UI, public data plane, no Funnel.* \ No newline at end of file diff --git a/services/socfortress-waf/compose.yml b/services/socfortress-waf/compose.yml new file mode 100644 index 0000000..675b0e8 --- /dev/null +++ b/services/socfortress-waf/compose.yml @@ -0,0 +1,252 @@ +--- +# ============================================================================= +# SOCFortress WAF Management Platform + Tailscale sidecar +# ============================================================================= +# Serve config (inline). $$ escapes the $ so ${TS_CERT_DOMAIN} survives Compose +# interpolation and is expanded to this node's FQDN by the Tailscale container. +# ============================================================================= +configs: + ts_serve: + content: | + { + "TCP": { "443": { "HTTPS": true } }, + "Web": { + "$${TS_CERT_DOMAIN}:443": { + "Handlers": { "/": { "Proxy": "https+insecure://admin-ui:8080" } } + } + }, + "AllowFunnel": { "$${TS_CERT_DOMAIN}:443": false } + } +# ============================================================================= +# Networks +# ============================================================================= +networks: + waf-internal: + driver: bridge +services: + # --------------------------------------------------------------------------- + # Tailscale sidecar โ€” serves the admin UI over your Tailnet (private). + # + # Joins waf-internal as a normal peer and reverse-proxies to admin-ui:8080 + # by Docker DNS name. The UI's internal cert is self-signed, hence the + # https+insecure backend; the public-facing *.ts.net cert is valid. + # Tailnet-only โ€” Funnel is disabled (AllowFunnel: false). + # --------------------------------------------------------------------------- + tailscale: + image: tailscale/tailscale:latest + container_name: ts-${TS_HOSTNAME} + hostname: ${TS_HOSTNAME} # MagicDNS -> ${TS_HOSTNAME}..ts.net + environment: + - TS_AUTHKEY=${TS_AUTHKEY:?Set TS_AUTHKEY in .env} + - TS_HOSTNAME=${TS_HOSTNAME} + - TS_STATE_DIR=/var/lib/tailscale + - TS_USERSPACE=true # no NET_ADMIN / tun device required + - TS_SERVE_CONFIG=/config/serve.json + - TS_EXTRA_ARGS=${TS_EXTRA_ARGS:-} + configs: + - source: ts_serve + target: /config/serve.json + volumes: + - tailscale-state:/var/lib/tailscale + networks: + - waf-internal + depends_on: + admin-ui: + condition: service_started + healthcheck: + test: ["CMD", "tailscale", "status"] + interval: 1m + timeout: 10s + retries: 3 + start_period: 30s + restart: unless-stopped + + # --------------------------------------------------------------------------- + # Caddy + Coraza: WAF engine and reverse proxy (public data plane) + # --------------------------------------------------------------------------- + caddy-waf: + image: ghcr.io/socfortress/waf-caddy:${WAF_IMAGE_TAG:-latest} + container_name: caddy-waf + ports: + - "80:80" + - "443:443" + # Lets the WAF proxy to apps running on the Docker host (e.g. an nginx + # upstream) via http://host.docker.internal:. See demo/README.md. + extra_hosts: + - "host.docker.internal:host-gateway" + volumes: + - coraza-rules:/etc/coraza/rules + - caddy-config:/etc/caddy + - coraza-custom:/etc/coraza/custom + - crs-data:/etc/coraza/crs-rules # live CRS dir (seeded by admin-api) + - coraza-logs:/var/log/coraza + networks: + - waf-internal + depends_on: + http-echo: + condition: service_started + healthcheck: + test: ["CMD", "wget", "-q", "-O-", "http://localhost:2019/config/"] + interval: 30s + timeout: 10s + retries: 3 + start_period: 20s + deploy: + resources: + limits: + cpus: "1.0" + memory: 512M + restart: unless-stopped + + # --------------------------------------------------------------------------- + # Dummy upstream โ€” replace with your real protected app(s) + # --------------------------------------------------------------------------- + http-echo: + image: hashicorp/http-echo + container_name: http-echo + command: ["-text=upstream-ok"] + user: "65534:65534" + networks: + - waf-internal + healthcheck: + test: ["NONE"] + deploy: + resources: + limits: + cpus: "0.25" + memory: 64M + restart: unless-stopped + + # --------------------------------------------------------------------------- + # FastAPI Admin API + # --------------------------------------------------------------------------- + admin-api: + image: ghcr.io/socfortress/waf-admin-api:${WAF_IMAGE_TAG:-latest} + container_name: admin-api + env_file: .env + volumes: + - tls-certs:/certs # shared TLS cert volume (see admin-ui) + # GeoLite2 DB is user-supplied โ€” MaxMind licensing forbids redistribution. + # Point GEOIP_DB_PATH at your downloaded GeoLite2-City.mmdb (see README). + - ${GEOIP_DB_PATH:-./GeoLite2-City.mmdb}:/etc/geoip-bundle/GeoLite2-City.mmdb:ro + - geoip-data:/etc/geoip + - coraza-rules:/etc/coraza/rules + - caddy-config:/etc/caddy + - coraza-custom:/etc/coraza/custom + - crs-data:/etc/coraza/crs-rules # live CRS dir (seeded from image bundle) + - coraza-logs:/var/log/coraza + networks: + - waf-internal + depends_on: + postgres: + condition: service_healthy + redis: + condition: service_healthy + healthcheck: + test: ["CMD", "wget", "-q", "-O-", "http://localhost:8000/health"] + interval: 30s + timeout: 10s + retries: 3 + start_period: 30s + deploy: + resources: + limits: + cpus: "1.0" + memory: 512M + restart: unless-stopped + + # --------------------------------------------------------------------------- + # React Admin UI (Nginx, HTTPS on internal 8080) + # + # Tailnet-only by default: the Tailscale sidecar below serves this UI over + # your Tailnet. Uncomment the ports block to ALSO expose it on the host LAN + # at https://:8443. + # --------------------------------------------------------------------------- + admin-ui: + image: ghcr.io/socfortress/waf-admin-ui:${WAF_IMAGE_TAG:-latest} + container_name: admin-ui + # ports: + # - "8443:8080" + volumes: + - tls-certs:/etc/nginx/certs # shared with admin-api for cert upload + reload + networks: + - waf-internal + depends_on: + admin-api: + condition: service_healthy + healthcheck: + test: ["CMD", "wget", "-q", "--no-check-certificate", "-O-", "https://localhost:8080/"] + interval: 30s + timeout: 10s + retries: 3 + start_period: 30s + deploy: + resources: + limits: + cpus: "0.5" + memory: 128M + restart: unless-stopped + + # --------------------------------------------------------------------------- + # PostgreSQL 16 + # --------------------------------------------------------------------------- + postgres: + image: postgres:16.3-alpine + container_name: postgres + env_file: .env + volumes: + - postgres-data:/var/lib/postgresql/data + networks: + - waf-internal + healthcheck: + test: ["CMD-SHELL", "pg_isready -U $$POSTGRES_USER -d $$POSTGRES_DB"] + interval: 10s + timeout: 5s + retries: 5 + start_period: 10s + deploy: + resources: + limits: + cpus: "1.0" + memory: 512M + restart: unless-stopped + + # --------------------------------------------------------------------------- + # Redis 7 + # --------------------------------------------------------------------------- + redis: + image: redis:7.2.5-alpine + container_name: redis + user: redis + command: ["redis-server", "--appendonly", "yes"] + volumes: + - redis-data:/data + networks: + - waf-internal + healthcheck: + test: ["CMD", "redis-cli", "ping"] + interval: 10s + timeout: 5s + retries: 5 + start_period: 5s + deploy: + resources: + limits: + cpus: "0.5" + memory: 256M + restart: unless-stopped + +# ============================================================================= +# Named volumes +# ============================================================================= +volumes: + coraza-rules: + coraza-custom: + coraza-logs: + caddy-config: + crs-data: + geoip-data: + postgres-data: + redis-data: + tls-certs: + tailscale-state: From 58305475925b38a3f737627c0e355d7445cfa029 Mon Sep 17 00:00:00 2001 From: ChillBill <129118422+ChillBill77@users.noreply.github.com> Date: Mon, 29 Jun 2026 13:30:09 +0200 Subject: [PATCH 6/8] SOCFortress WAF with Tailscale Protection --- README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/README.md b/README.md index e74b317..6adca58 100644 --- a/README.md +++ b/README.md @@ -82,6 +82,7 @@ ScaleTail provides ready-to-run [Docker Compose](https://docs.docker.com/compose | ๐Ÿงฉ **Pi-hole** | A network-level ad blocker that acts as a DNS sinkhole. | [Details](services/pihole) | | ๐Ÿ†” **Pocket ID** | A self-hosted decentralized identity (OIDC) solution for secure authentication. | [Details](services/pocket-id) | | ๐ŸŒ **Rustdesk Server** | RustDesk is an open source remote control alternative for self-hosting and security. | [Details](services/rustdesk-server) | +| ๐Ÿฐ **SOCFortress WAF** | Self-hosted Web Application Firewall with a modern admin UI (using Caddy + Coraza). | [Details](services/socfortress-waf) | | ๐Ÿ”’ **Technitium DNS** | An open-source DNS server that can be used for self-hosted DNS services. | [Details](services/technitium) | | ๐ŸŒ **Traefik** | A modern reverse proxy and load balancer for microservices. | [Details](services/traefik) | | ๐ŸŒ **Tailscale App Connector Node** | Configure a device to act as a App connector node for your Tailscale network. | [Details](services/tailscale-app-connector-node) | From b6fc9a8bdd642d12e844b24cd48a4816d703e8f8 Mon Sep 17 00:00:00 2001 From: ChillBill <129118422+ChillBill77@users.noreply.github.com> Date: Mon, 29 Jun 2026 13:31:38 +0200 Subject: [PATCH 7/8] SOCFortress WAF with Tailscale Protection --- services/socfortress-waf/README.md | 24 +----------------------- 1 file changed, 1 insertion(+), 23 deletions(-) diff --git a/services/socfortress-waf/README.md b/services/socfortress-waf/README.md index 20bc454..16d6180 100644 --- a/services/socfortress-waf/README.md +++ b/services/socfortress-waf/README.md @@ -28,7 +28,7 @@ Access the admin UI at: `https://..ts.net` (after fir - A Tailscale account and an auth key, with **HTTPS enabled** in your tailnet (Admin console โ†’ DNS โ†’ enable MagicDNS and HTTPS Certificates). - A free MaxMind **GeoLite2-City.mmdb** (optional but recommended; the stack starts without it but GeoIP log enrichment is disabled). -## Quick Start +## Quick-ish Start 1. Drop these four files into the cloned `waf-platform` directory (next to the upstream `docker-compose.yml`): - `.env`, `compose.yml`, `compose.tailscale.yml`, `README.md` @@ -71,10 +71,6 @@ Access the admin UI at: `https://..ts.net` (after fir - **The data plane is still public.** `caddy-waf` keeps listening on host `80/443`. Point DNS for the sites you protect at this host as you normally would; Caddy's automatic per-site TLS and GeoIP/client-IP features are unaffected. Tailscale is only in front of the admin UI. - **Updating:** `docker compose pull && docker compose up -d`. Data lives in named volumes and survives updates. The Tailscale node identity persists in the `tailscale-state` volume. -### Why no Funnel - -You asked about Funnel originally; here is why this setup leaves it off. Tailscale Funnel can only serve the single `*.ts.net` hostname on ports 443/8443/10000 and terminates TLS at Tailscale's edge. Putting the WAF data plane behind Funnel would (a) collapse all your protected sites to one hostname and break per-site Let's Encrypt, and (b) hide the real client IP, degrading the GeoIP enrichment and IP-based rules the platform is built around. Funneling the admin console would publish your firewall's control panel to the internet. If you ever do want a single site exposed publicly through Tailscale, flip `AllowFunnel` to `true` in `compose.tailscale.yml` for the relevant port โ€” but understand the tradeoffs first. - ## Files - `.env` โ€” all WAF variables + the Tailscale section (`TS_AUTHKEY` is critical; keep private). @@ -82,22 +78,6 @@ You asked about Funnel originally; here is why this setup leaves it off. Tailsca - `compose.tailscale.yml` โ€” the Tailscale sidecar service + inline serve config. - `docker-compose.yml` โ€” upstream, unchanged (provided by the cloned repo; not in this bundle). -## Linting & Validation - -Run these from the `waf-platform` directory after editing (requires a filled `.env`): - -```bash -# Schema + interpolation + merge of the included files -docker compose config --quiet -# Silent + exit 0 means good. - -# Optional strict YAML lint -yamllint compose.yml compose.tailscale.yml - -# Full merged config dump (for debugging the include/override) -docker compose config -``` - ## Upstream Documentation - [SOCFortress WAF repo](https://github.com/socfortress/waf-platform-public) @@ -113,5 +93,3 @@ docker compose config - **No Tailnet IP** โ€” auth key expired/invalid, or ACL/tag restrictions; check `docker compose logs tailscale`. --- - -*Generated for the SOCFortress WAF using the Tailscale sidecar templates. Option A: private admin UI, public data plane, no Funnel.* \ No newline at end of file From 55f8c2aa90f30e47f785f584c4610ac74033925c Mon Sep 17 00:00:00 2001 From: ChillBill <129118422+ChillBill77@users.noreply.github.com> Date: Mon, 29 Jun 2026 13:32:45 +0200 Subject: [PATCH 8/8] SOCFortress WAF with Tailscale Protection --- services/socfortress-waf/README.md | 14 +------------- 1 file changed, 1 insertion(+), 13 deletions(-) diff --git a/services/socfortress-waf/README.md b/services/socfortress-waf/README.md index 16d6180..b90d177 100644 --- a/services/socfortress-waf/README.md +++ b/services/socfortress-waf/README.md @@ -2,7 +2,7 @@ This adds a Tailscale sidecar to the [SOCFortress WAF Management Platform](https://github.com/socfortress/waf-platform-public) so the **admin UI is reachable only over your Tailnet** at `https://..ts.net`, with a valid HTTPS certificate. The **WAF data plane is left exactly as upstream ships it** โ€” `caddy-waf` still listens on host ports **80/443** so it can front your protected sites with automatic per-site Let's Encrypt certs and see real client IPs. -Funnel (public internet exposure) is **intentionally disabled**. See [Why no Funnel](#why-no-funnel). +Funnel (public internet exposure) is **intentionally disabled**. ## About this setup @@ -16,18 +16,6 @@ The WAF platform is a six-service stack (Caddy+Coraza engine, FastAPI admin API, Access the admin UI at: `https://..ts.net` (after first auth + HTTPS enabled in your tailnet). -## Prerequisites - -- The upstream repo cloned (this is where `docker-compose.yml` lives): - ```bash - git clone https://github.com/socfortress/waf-platform-public.git waf-platform - cd waf-platform - ``` -- Docker โ‰ฅ 24 and **Docker Compose โ‰ฅ 2.24** (needed for inline `configs` content). -- Ports **80**, **443** (data plane) available on the host. Port 8443 is no longer needed once the admin UI is Tailnet-only (see step 4). -- A Tailscale account and an auth key, with **HTTPS enabled** in your tailnet (Admin console โ†’ DNS โ†’ enable MagicDNS and HTTPS Certificates). -- A free MaxMind **GeoLite2-City.mmdb** (optional but recommended; the stack starts without it but GeoIP log enrichment is disabled). - ## Quick-ish Start 1. Drop these four files into the cloned `waf-platform` directory (next to the upstream `docker-compose.yml`):