Skip to content

Commit 5b22073

Browse files
committed
[major] Prefer multi-layer routing for v2
Breaking changes: - Requires Traefik v3.6 or newer so deployments can use multi-layer routing parentRefs. - Removes `ipv4subnetMask`, `ipv6subnetMask`, and `rateLimit`. Given the distributed nature of bots these config values have become additional config/logic that adds no real value. Most crawlers at this point are issuing a small amount of requests per IP across a wide range of subnets. - Removes `enableStateReconciliation` and all state reconciliation code. `persistentStateFile` is now restart persistence only, not multi-instance coordination. - Makes Traefik multi-layer routing the preferred multi-service protection model: attach captcha-protect to one parent router and route accepted traffic through child routers. Implementation notes: - Adds first-class CI coverage for the documented file-provider multi-layer routing config across Traefik v3.6, v3.7, and latest. - Drops older Traefik integration targets below v3.6. - Keeps Docker label smoke coverage for single-router provider configuration. - Updates README and plugin metadata for the v2 behavior and Traefik baseline. Closes #95
1 parent 89cec5a commit 5b22073

16 files changed

Lines changed: 289 additions & 2434 deletions

File tree

.github/workflows/lint-test.yml

Lines changed: 26 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -53,43 +53,59 @@ jobs:
5353
env:
5454
CODECOV_TOKEN: ${{ secrets.CODECOV_TOKEN }}
5555

56-
integration-test-latest:
56+
integration-test-labels:
5757
needs: [run]
5858
permissions:
5959
contents: read
6060
runs-on: ubuntu-24.04
61+
strategy:
62+
fail-fast: false
63+
matrix:
64+
traefik: [v3.6, v3.7, latest]
65+
env:
66+
TRAEFIK_TAG: ${{ matrix.traefik }}
6167
steps:
6268
- uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7
6369

6470
- name: run
6571
run: go run test.go
6672
working-directory: ./ci
67-
env:
68-
TRAEFIK_TAG: latest
73+
74+
- name: capture logs
75+
if: ${{ always() }}
76+
run: docker compose logs --tail 100 nginx nginx2 traefik
77+
working-directory: ./ci
6978

7079
- name: cleanup
7180
if: ${{ always() }}
72-
run: docker compose logs --tail 100 nginx nginx2 traefik && docker compose down
81+
run: docker compose down
7382
working-directory: ./ci
7483

75-
integration-test:
76-
needs: [integration-test-latest]
84+
integration-test-multilayer-routing:
85+
needs: [run]
7786
permissions:
7887
contents: read
7988
runs-on: ubuntu-24.04
8089
strategy:
90+
fail-fast: false
8191
matrix:
82-
traefik: [v2.11, v3.0, v3.1, v3.2, v3.3, v3.4, v3.5, v3.6, v3.7]
92+
traefik: [v3.6, v3.7, latest]
93+
env:
94+
COMPOSE_FILE: docker-compose.multilayer.yml
95+
TRAEFIK_TAG: ${{ matrix.traefik }}
8396
steps:
8497
- uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7
8598

8699
- name: run
87100
run: go run test.go
88101
working-directory: ./ci
89-
env:
90-
TRAEFIK_TAG: ${{ matrix.traefik }}
102+
103+
- name: capture logs
104+
if: ${{ always() }}
105+
run: docker compose logs --tail 100 nginx nginx2 traefik
106+
working-directory: ./ci
91107

92108
- name: cleanup
93109
if: ${{ always() }}
94-
run: docker compose logs --tail 100 nginx nginx2 traefik && docker compose down
110+
run: docker compose down
95111
working-directory: ./ci

.traefik.yml

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,12 @@
11
displayName: Captcha Protect
2-
summary: Add an anti-bot challenge to individual IPs in a subnet when traffic spikes are detected from that subnet
2+
summary: Add an anti-bot challenge to protected routes
33
type: middleware
44
import: github.com/libops/captcha-protect
55
testData:
66
ProtectRoutes: /
77
ChallengeTmpl: challenge.tmpl.html
88
Window: 86400
9-
RateLimit: 20
109
IPForwardedHeader: "X-Forwarded-For"
1110
CaptchaProvider: turnstile
1211
SiteKey: 1x00000000000000000000AA
1312
SecretKey: 1x0000000000000000000000000000000AA
14-
EnableStateReconciliation: "false"

README.md

Lines changed: 15 additions & 317 deletions
Large diffs are not rendered by default.

ci/.env

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
TRAEFIK_TAG=v3.5
1+
TRAEFIK_TAG=v3.6
22
NGINX_TAG=1.27.4-alpine3.21
33
TURNSTILE_SITE_KEY=1x00000000000000000000AA
44
TURNSTILE_SECRET_KEY=1x0000000000000000000000000000000AA

ci/conf/traefik/multilayer.yml

Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
http:
2+
routers:
3+
protected-parent:
4+
entryPoints:
5+
- http
6+
rule: "Host(`localhost`)"
7+
middlewares:
8+
- captcha-protect
9+
10+
nginx:
11+
rule: "PathPrefix(`/`)"
12+
priority: 1
13+
service: nginx
14+
parentRefs:
15+
- protected-parent
16+
17+
nginx2:
18+
rule: "PathPrefix(`/app2`)"
19+
priority: 100
20+
service: nginx2
21+
parentRefs:
22+
- protected-parent
23+
24+
services:
25+
nginx:
26+
loadBalancer:
27+
servers:
28+
- url: "http://nginx:80"
29+
nginx2:
30+
loadBalancer:
31+
servers:
32+
- url: "http://nginx2:80"
33+
34+
middlewares:
35+
captcha-protect:
36+
plugin:
37+
captcha-protect:
38+
captchaProvider: turnstile
39+
window: 120
40+
siteKey: 1x00000000000000000000AA
41+
secretKey: 1x0000000000000000000000000000000AA
42+
enableStatsPage: "true"
43+
ipForwardedHeader: X-Forwarded-For
44+
logLevel: DEBUG
45+
protectParameters: "false"
46+
goodBots: []
47+
enableGooglebotIPCheck: "false"
48+
enableUptimeRobotBypass: "false"
49+
mode: regex
50+
protectRoutes:
51+
- "^/"
52+
excludeRoutes:
53+
- "\\/oai\\/request"
54+
- "\\/node\\/\\d+\\/(book-)?manifest"

ci/docker-compose.multilayer.yml

Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,60 @@
1+
networks:
2+
default:
3+
4+
services:
5+
nginx:
6+
image: nginx:${NGINX_TAG}
7+
healthcheck:
8+
test: curl -fs http://localhost/healthz | grep -q OK || exit 1
9+
start_period: 5s
10+
volumes:
11+
- ./conf/nginx/default.conf:/etc/nginx/conf.d/default.conf:r
12+
networks:
13+
default:
14+
aliases:
15+
- nginx
16+
17+
nginx2:
18+
image: nginx:${NGINX_TAG}
19+
healthcheck:
20+
test: curl -fs http://localhost/healthz | grep -q OK || exit 1
21+
start_period: 5s
22+
volumes:
23+
- ./conf/nginx/default.conf:/etc/nginx/conf.d/default.conf:r
24+
networks:
25+
default:
26+
aliases:
27+
- nginx2
28+
29+
traefik:
30+
image: traefik:${TRAEFIK_TAG}
31+
command: >-
32+
--api.insecure=true
33+
--api.dashboard=true
34+
--api.debug=true
35+
--ping=true
36+
--entryPoints.http.address=:80
37+
--entryPoints.http.forwardedHeaders.insecure=true
38+
--entryPoints.http.forwardedHeaders.trustedIPs=127.0.0.1/32,::1/128,10.0.0.0/8,172.16.0.0/12,192.168.0.0/16
39+
--providers.file.filename=/etc/traefik/dynamic/multilayer.yml
40+
--experimental.localPlugins.captcha-protect.moduleName=github.com/libops/captcha-protect
41+
volumes:
42+
- ./conf/traefik/multilayer.yml:/etc/traefik/dynamic/multilayer.yml:r
43+
- ./../:/plugins-local/src/github.com/libops/captcha-protect:r
44+
ports:
45+
- "80:80"
46+
- "8080:8080"
47+
mem_limit: 256m
48+
mem_reservation: 128m
49+
networks:
50+
default:
51+
aliases:
52+
- traefik
53+
healthcheck:
54+
test: traefik healthcheck --ping
55+
start_period: 5s
56+
depends_on:
57+
nginx:
58+
condition: service_healthy
59+
nginx2:
60+
condition: service_healthy

ci/docker-compose.yml

Lines changed: 0 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,6 @@ services:
1212
traefik.http.routers.nginx.middlewares: captcha-protect@docker
1313
traefik.http.middlewares.captcha-protect.plugin.captcha-protect.captchaProvider: turnstile
1414
traefik.http.middlewares.captcha-protect.plugin.captcha-protect.window: 120
15-
traefik.http.middlewares.captcha-protect.plugin.captcha-protect.rateLimit: ${RATE_LIMIT}
1615
traefik.http.middlewares.captcha-protect.plugin.captcha-protect.siteKey: ${TURNSTILE_SITE_KEY}
1716
traefik.http.middlewares.captcha-protect.plugin.captcha-protect.secretKey: ${TURNSTILE_SECRET_KEY}
1817
traefik.http.middlewares.captcha-protect.plugin.captcha-protect.enableStatsPage: "true"
@@ -25,8 +24,6 @@ services:
2524
traefik.http.middlewares.captcha-protect.plugin.captcha-protect.mode: "regex"
2625
traefik.http.middlewares.captcha-protect.plugin.captcha-protect.protectRoutes: "^/"
2726
traefik.http.middlewares.captcha-protect.plugin.captcha-protect.excludeRoutes: "\\/oai\\/request,\\/node\\/\\d+\\/(book-)?manifest"
28-
traefik.http.middlewares.captcha-protect.plugin.captcha-protect.persistentStateFile: "/tmp/state.json"
29-
traefik.http.middlewares.captcha-protect.plugin.captcha-protect.enableStateReconciliation: "true"
3027
healthcheck:
3128
test: curl -fs http://localhost/healthz | grep -q OK || exit 1
3229
start_period: 5s
@@ -47,7 +44,6 @@ services:
4744
traefik.http.routers.nginx2.middlewares: captcha-protect@docker
4845
traefik.http.middlewares.captcha-protect.plugin.captcha-protect.captchaProvider: turnstile
4946
traefik.http.middlewares.captcha-protect.plugin.captcha-protect.window: 120
50-
traefik.http.middlewares.captcha-protect.plugin.captcha-protect.rateLimit: ${RATE_LIMIT}
5147
traefik.http.middlewares.captcha-protect.plugin.captcha-protect.siteKey: ${TURNSTILE_SITE_KEY}
5248
traefik.http.middlewares.captcha-protect.plugin.captcha-protect.secretKey: ${TURNSTILE_SECRET_KEY}
5349
traefik.http.middlewares.captcha-protect.plugin.captcha-protect.enableStatsPage: "true"
@@ -60,8 +56,6 @@ services:
6056
traefik.http.middlewares.captcha-protect.plugin.captcha-protect.mode: "regex"
6157
traefik.http.middlewares.captcha-protect.plugin.captcha-protect.protectRoutes: "^/"
6258
traefik.http.middlewares.captcha-protect.plugin.captcha-protect.excludeRoutes: "\\/oai\\/request,\\/node\\/\\d+\\/(book-)?manifest"
63-
traefik.http.middlewares.captcha-protect.plugin.captcha-protect.persistentStateFile: "/tmp/state.json"
64-
traefik.http.middlewares.captcha-protect.plugin.captcha-protect.enableStateReconciliation: "true"
6559
healthcheck:
6660
test: curl -fs http://localhost/healthz | grep -q OK || exit 1
6761
start_period: 5s
@@ -86,7 +80,6 @@ services:
8680
--experimental.localPlugins.captcha-protect.moduleName=github.com/libops/captcha-protect
8781
volumes:
8882
- /var/run/docker.sock:/var/run/docker.sock:z
89-
- ./tmp:/tmp:rw
9083
- ./../:/plugins-local/src/github.com/libops/captcha-protect:r
9184
ports:
9285
- "80:80"

0 commit comments

Comments
 (0)