Skip to content

SOCFortress WAF: new service - #321

Open
ChillBill77 wants to merge 10 commits into
tailscale-dev:mainfrom
ChillBill77:socfortress-waf-ts
Open

ChillBill77 wants to merge 10 commits into
tailscale-dev:mainfrom
ChillBill77:socfortress-waf-ts

Conversation

@ChillBill77

Copy link
Copy Markdown
Contributor

SOCFortress WAF: new service (Tailscale-served admin UI)

Description

Adds the SOCFortress WAF Management Platform
as a new service. Unlike the single-app template, this is a multi-container stack
(Caddy+Coraza WAF engine, FastAPI admin API, React/Nginx admin UI, PostgreSQL,
Redis, demo upstream). A Tailscale sidecar serves the admin UI privately over
the Tailnet via Tailscale Serve (Funnel disabled); the WAF data plane
(caddy-waf) stays published on host 80/443 so per-site Let's Encrypt and real
client IPs keep working.

Because it is a multi-service stack, network_mode: service:tailscale is not
usable (it collapses the namespace and breaks Docker DNS between the services).
Instead the Tailscale container runs as a normal peer on the stack's internal
network and reverse-proxies to admin-ui:8080 by container name using the
https+insecure:// scheme (the UI serves self-signed HTTPS internally; the
public *.ts.net cert is valid).

Related Issues

  • None.

Verification

  • docker compose config --quiet → exit 0 (schema, interpolation, and merge valid; Compose v2).
  • YAML and the embedded Serve JSON parse-checked; all volume / network / config / depends_on references resolve.
  • Live docker compose up -d: stack starts and the admin UI is reachable

Checklist

  • I have performed a self-review of my code and followed the templates structure.
  • I have added verification that the stack works as expected.
  • I have updated necessary documentation (e.g. frontpage README.md ).

Additional Context

Intentional deviations from templates/service-template, with rationale:

  • No network_mode: service:tailscale — multi-service stack; Tailscale is a
    peer on the internal network proxying to admin-ui:8080 by DNS name.
  • TS_USERSPACE=true (no /dev/net/tun, no cap_add: net_admin) — only the
    admin UI is served, so kernel networking isn't needed; least privilege.
  • https+insecure:// backend — admin UI is self-signed HTTPS internally.
  • Data plane exposed on host 80/443 — required; the WAF must receive real
    public traffic. Only the admin UI is Tailnet-only.
  • Funnel disabled — would collapse multi-site hosting to one *.ts.net
    hostname, break per-site ACME, and hide real client IPs (degrading GeoIP).

User gotchas:

  • Enable HTTPS/MagicDNS in the tailnet or Serve can't provision a cert.
  • Set ALLOWED_ORIGINS=https://<host>.<tailnet>.ts.net (CORS, no wildcard) or login fails.
  • Supply your own GeoLite2-City.mmdb (MaxMind licensing).

@jackspiering

Copy link
Copy Markdown
Collaborator

@jackspiering jackspiering mentioned this pull request Oct 3, 2026
5 tasks done
@jackspiering

Copy link
Copy Markdown
Collaborator

Hi @ChillBill77, thanks for the SOCFortress WAF stack and for documenting why it deviates from the template. Here is a concrete list of what's needed before we can merge. Please use CONTRIBUTING.md and the service template as the reference. I did not run this stack locally: it binds host ports 80/443 and needs a MaxMind GeoIP file, and the branch has to be split first (point 1).

1. Split the PR

This branch contains all Homebridge commits from #319, because both PRs come from your fork's main. Please create a new branch from the latest upstream main with only the WAF changes (services/socfortress-waf/ and its root README row). Merging main also clears the root README lint errors (broken anchors, double blank line), which come from the old base.

2. Make the README match the files

The README describes a different setup than the PR contains:

  • It says to drop four files into a cloned waf-platform checkout.
  • It refers to compose.tailscale.yml and an include of the upstream docker-compose.yml.
  • It contains cp .env .env.

The PR's compose.yml is a self-contained stack, so please rewrite the steps for that. Also fix the lint errors that fail CI:

  • MD031: blank lines around code blocks. rumdl fmt --config .markdownlint.yml <file> fixes these.
  • MD051: the anchor #linting--validation has no matching heading.

3. Working default configuration

  • TS_HOSTNAME=socfortressWAF and ALLOWED_ORIGINS=https://waf-admin.<your-tailnet>.ts.net use different names, so login fails with the defaults (CORS). Please use one lowercase name for both.
  • ./GeoLite2-City.mmdb must exist before the first start. Otherwise Docker creates a directory with that name and admin-api cannot read it. Please say this in the setup steps.

4. Template conventions

The network design (Tailscale as a peer on waf-internal instead of network_mode: service:tailscale) is an exception we can accept if it's needed. Please explain it in a short "Differences from the template" section in the README. Everything else should follow the template:

  • Rename compose.yml to compose.yaml.
  • Use SERVICE instead of TS_HOSTNAME and set hostname: ${SERVICE}.
  • Name containers tailscale-${SERVICE} and app-${SERVICE}-<part> (for example app-${SERVICE}-postgres). The generic names postgres, redis, admin-api, and caddy-waf collide with other stacks on the same host.
  • Keep the template's Tailscale health check: TS_ENABLE_HEALTH_CHECK=true, TS_LOCAL_ADDR_PORT=127.0.0.1:41234, and the wget ... /healthz test. Also keep TS_AUTH_ONCE=true.
  • PostgreSQL health check: use TCP, pg_isready -h 127.0.0.1 ... (CONTRIBUTING step 5).
  • .env:
    • start from the template's .env, with the #version/#URL header, the TZ line, and the comment style
    • leave TS_AUTHKEY= and the passwords empty instead of tskey-auth-XXXX/CHANGE_ME placeholders
    • add a final newline
  • Optional: use bind mounts under ./${SERVICE}-data/ like the other services, so the data is easy to back up.

5. Verification

After the update, please add to the PR description what you checked: container status, the admin UI login through https://<name>.<tailnet>.ts.net, and a request through the WAF.

Thanks! Happy to review again once the branch is split.

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.

2 participants