diff --git a/.github/actions/spelling/expect.txt b/.github/actions/spelling/expect.txt index 3ca7f2b9..71b46f6c 100644 --- a/.github/actions/spelling/expect.txt +++ b/.github/actions/spelling/expect.txt @@ -119,6 +119,7 @@ FCr fcrdns fediverse ffprobe +fhdr financials finfos Firecrawl @@ -156,6 +157,7 @@ grw gzw Hashcash hashrate +hdr headermap healthcheck healthz @@ -165,6 +167,7 @@ Hetzner hmc homelab hostable +HSTS htmlc htmx httpdebug @@ -331,6 +334,8 @@ stackoverflow startprecmd stoppostcmd storetest +srcip +strcmp subgrid subr subrequest @@ -355,6 +360,7 @@ Timpibot TLog traefik trunc +txn uberspace Unbreak unbreakdocker diff --git a/docs/docs/CHANGELOG.md b/docs/docs/CHANGELOG.md index 27644146..fac15263 100644 --- a/docs/docs/CHANGELOG.md +++ b/docs/docs/CHANGELOG.md @@ -16,6 +16,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Fix honeypot and imprint links missing `BASE_PREFIX` when deployed behind a path prefix ([#1402](https://github.com/TecharoHQ/anubis/issues/1402)) - Add ANEXIA Sponsor logo to docs ([#1409](https://github.com/TecharoHQ/anubis/pull/1409)) - Improve idle performance in memory storage +- Add HAProxy Configurations to Docs ([#1424](https://github.com/TecharoHQ/anubis/pull/1424)) diff --git a/docs/docs/admin/environments/haproxy.mdx b/docs/docs/admin/environments/haproxy.mdx new file mode 100644 index 00000000..24b0bb34 --- /dev/null +++ b/docs/docs/admin/environments/haproxy.mdx @@ -0,0 +1,99 @@ +# HAProxy + +import CodeBlock from "@theme/CodeBlock"; + +To use Anubis with HAProxy, you have two variants: + - simple - stick Anubis between HAProxy and your application backend (simple) + - perfect if you only have a single application in general + - advanced - force Anubis challenge by default and route to the application backend by HAProxy if the challenge is correct + - useful for complex setups + - routing can be done in HAProxy + - define ACLs in HAProxy for domains, paths etc which are required/excluded regarding Anubis + - HAProxy 3.0 recommended + +## Simple Variant + +```mermaid +--- +title: HAProxy with simple config +--- +flowchart LR + T(User Traffic) + HAProxy(HAProxy Port 80/443) + Anubis + Application + + T --> HAProxy + HAProxy --> Anubis + Anubis --> |Happy Traffic| Application +``` + +Your Anubis env file configuration may look like this: + +import simpleAnubis from "!!raw-loader!./haproxy/simple-config.env"; + +{simpleAnubis} + +The important part is that `TARGET` points to your actual application and if Anubis and HAProxy are on the same machine, a UNIX socket can be used. + +Your frontend and backend configuration of HAProxy may look like the following: + +import simpleHAProxy from "!!raw-loader!./haproxy/simple-haproxy.cfg"; + +{simpleHAProxy} + +This simply enables SSL offloading, sets some useful and required headers and routes to Anubis directly. + +## Advanced Variant + +Due to the fact that HAProxy can decode JWT, we are able to verify the Anubis token directly in HAProxy and route the traffic to the specific backends ourselves. + +In this example are three applications behind one HAProxy frontend. Only App1 and App2 are secured via Anubis; App3 is open for everyone. The path `/excluded/path` can also be accessed by anyone. + +```mermaid +--- +title: HAProxy with advanced config +--- + +flowchart LR + T(User Traffic) + HAProxy(HAProxy Port 80/443) + B1(App1) + B2(App2) + B3(App3) + Anubis + + T --> HAProxy + HAProxy --> |Traffic for App1 and App2 without valid challenge| Anubis + HAProxy --> |app1.example.com | B1 + HAProxy --> |app2.example.com| B2 + HAProxy --> |app3.example.com| B3 +``` + +:::note + +For an improved JWT decoding performance, it's recommended to use HAProxy version 3.0 or above. + +::: + +Your Anubis env file configuration may look like this: + +import advancedAnubis from "!!raw-loader!./haproxy/advanced-config.env"; + +{advancedAnubis} + +It's important to use `HS512_SECRET` which HAProxy understands. Please replace `` with your own secret string (alphanumerical string with 128 characters recommended). + +You can set Anubis to force a challenge for every request using the following policy file: + +import advancedAnubisPolicy from "!!raw-loader!./haproxy/advanced-config-policy.yml"; + +{advancedAnubisPolicy} + +The HAProxy config file may look like this: + +import advancedHAProxy from "!!raw-loader!./haproxy/advanced-haproxy.cfg"; + +{advancedHAProxy} + +Please replace `` with the same secret from the Anubis config. diff --git a/docs/docs/admin/environments/haproxy/advanced-config-policy.yml b/docs/docs/admin/environments/haproxy/advanced-config-policy.yml new file mode 100644 index 00000000..93c06dc3 --- /dev/null +++ b/docs/docs/admin/environments/haproxy/advanced-config-policy.yml @@ -0,0 +1,15 @@ +# /etc/anubis/challenge-any.yml + +bots: + - name: any + action: CHALLENGE + user_agent_regex: .* + +status_codes: + CHALLENGE: 403 + DENY: 403 + +thresholds: [] + +dnsbl: false + diff --git a/docs/docs/admin/environments/haproxy/advanced-config.env b/docs/docs/admin/environments/haproxy/advanced-config.env new file mode 100644 index 00000000..712fdeeb --- /dev/null +++ b/docs/docs/admin/environments/haproxy/advanced-config.env @@ -0,0 +1,11 @@ +# /etc/anubis/default.env + +BIND=/run/anubis/default.sock +BIND_NETWORK=unix +DIFFICULTY=4 +METRICS_BIND=:9090 +# target is irrelevant here, backend routing happens in HAProxy +TARGET=http://0.0.0.0 +HS512_SECRET= +COOKIE_DYNAMIC_DOMAIN=True +POLICY_FNAME=/etc/anubis/challenge-any.yml diff --git a/docs/docs/admin/environments/haproxy/advanced-haproxy.cfg b/docs/docs/admin/environments/haproxy/advanced-haproxy.cfg new file mode 100644 index 00000000..1540067b --- /dev/null +++ b/docs/docs/admin/environments/haproxy/advanced-haproxy.cfg @@ -0,0 +1,59 @@ +# /etc/haproxy/haproxy.cfg + +frontend FE-multiple-applications + mode http + bind :80 + # ssl offloading on port 443 using a certificate from /etc/haproxy/ssl/ directory + bind :443 ssl crt /etc/haproxy/ssl/ alpn h2,http/1.1 ssl-min-ver TLSv1.2 no-tls-tickets + + # set X-Real-IP header required for Anubis + http-request set-header X-Real-IP "%[src]" + + # redirect HTTP to HTTPS + http-request redirect scheme https code 301 unless { ssl_fc } + # add HSTS header + http-response set-header Strict-Transport-Security "max-age=31536000; includeSubDomains; preload" + + # only force Anubis challenge for app1 and app2 + acl acl_anubis_required hdr(host) -i "app1.example.com" + acl acl_anubis_required hdr(host) -i "app2.example.com" + + # exclude Anubis for a specific path + acl acl_anubis_ignore path /excluded/path + + # use Anubis if auth cookie not found + use_backend BE-anubis if acl_anubis_required !acl_anubis_ignore !{ req.cook(techaro.lol-anubis-auth) -m found } + + # get payload of the JWT such as algorithm, expire time, restrictions + http-request set-var(txn.anubis_jwt_alg) req.cook(techaro.lol-anubis-auth),jwt_header_query('$.alg') if acl_anubis_required !acl_anubis_ignore + http-request set-var(txn.anubis_jwt_exp) cook(techaro.lol-anubis-auth),jwt_payload_query('$.exp','int') if acl_anubis_required !acl_anubis_ignore + http-request set-var(txn.anubis_jwt_res) cook(techaro.lol-anubis-auth),jwt_payload_query('$.restriction') if acl_anubis_required !acl_anubis_ignore + http-request set-var(txn.srcip) req.fhdr(X-Real-IP) if acl_anubis_required !acl_anubis_ignore + http-request set-var(txn.now) date() if acl_anubis_required !acl_anubis_ignore + + # use Anubis if JWT has wrong algorithm, is expired, restrictions don't match or isn't signed with the correct key + use_backend BE-anubis if acl_anubis_required !acl_anubis_ignore !{ var(txn.anubis_jwt_alg) -m str HS512 } + use_backend BE-anubis if acl_anubis_required !acl_anubis_ignore { var(txn.anubis_jwt_exp),sub(txn.now) -m int lt 0 } + use_backend BE-anubis if acl_anubis_required !acl_anubis_ignore !{ var(txn.srcip),digest(sha256),hex,lower,strcmp(txn.anubis_jwt_res) eq 0 } + use_backend BE-anubis if acl_anubis_required !acl_anubis_ignore !{ cook(techaro.lol-anubis-auth),jwt_verify(txn.anubis_jwt_alg,"") -m int 1 } + + # custom routing in HAProxy + use_backend BE-app1 if { hdr(host) -i "app1.example.com" } + use_backend BE-app2 if { hdr(host) -i "app2.example.com" } + use_backend BE-app3 if { hdr(host) -i "app3.example.com" } + +backend BE-app1 + mode http + server app1-server 127.0.0.1:3000 + +backend BE-app2 + mode http + server app2-server 127.0.0.1:4000 + +backend BE-app3 + mode http + server app3-server 127.0.0.1:5000 + +BE-anubis + mode http + server anubis /run/anubis/default.sock diff --git a/docs/docs/admin/environments/haproxy/simple-config.env b/docs/docs/admin/environments/haproxy/simple-config.env new file mode 100644 index 00000000..02a3f667 --- /dev/null +++ b/docs/docs/admin/environments/haproxy/simple-config.env @@ -0,0 +1,10 @@ +# /etc/anubis/default.env + +BIND=/run/anubis/default.sock +BIND_NETWORK=unix +SOCKET_MODE=0666 +DIFFICULTY=4 +METRICS_BIND=:9090 +COOKIE_DYNAMIC_DOMAIN=true +# address and port of the actual application +TARGET=http://localhost:3000 diff --git a/docs/docs/admin/environments/haproxy/simple-haproxy.cfg b/docs/docs/admin/environments/haproxy/simple-haproxy.cfg new file mode 100644 index 00000000..445d0788 --- /dev/null +++ b/docs/docs/admin/environments/haproxy/simple-haproxy.cfg @@ -0,0 +1,22 @@ +# /etc/haproxy/haproxy.cfg + +frontend FE-application + mode http + bind :80 + # ssl offloading on port 443 using a certificate from /etc/haproxy/ssl/ directory + bind :443 ssl crt /etc/haproxy/ssl/ alpn h2,http/1.1 ssl-min-ver TLSv1.2 no-tls-tickets + + # set X-Real-IP header required for Anubis + http-request set-header X-Real-IP "%[src]" + + # redirect HTTP to HTTPS + http-request redirect scheme https code 301 unless { ssl_fc } + # add HSTS header + http-response set-header Strict-Transport-Security "max-age=31536000; includeSubDomains; preload" + + # route to Anubis backend by default + default_backend BE-anubis-application + +BE-anubis-application + mode http + server anubis /run/anubis/default.sock diff --git a/docs/docs/admin/installation.mdx b/docs/docs/admin/installation.mdx index 6ecaa5a1..7000afef 100644 --- a/docs/docs/admin/installation.mdx +++ b/docs/docs/admin/installation.mdx @@ -203,6 +203,7 @@ To get Anubis filtering your traffic, you need to make sure it's added to your H - [Kubernetes](./environments/kubernetes.mdx) - [Nginx](./environments/nginx.mdx) - [Traefik](./environments/traefik.mdx) +- [HAProxy](./environments/haproxy.mdx) :::note diff --git a/docs/docs/admin/native-install.mdx b/docs/docs/admin/native-install.mdx index bdda5954..9abafa91 100644 --- a/docs/docs/admin/native-install.mdx +++ b/docs/docs/admin/native-install.mdx @@ -143,3 +143,4 @@ For more details on particular reverse proxies, see here: - [Apache](./environments/apache.mdx) - [Nginx](./environments/nginx.mdx) +- [HAProxy](./environments/haproxy.mdx)