Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
19 commits
Select commit Hold shift + click to select a range
19055c0
add the bot detection feature to the crowdsec traefik bouncer
sabban Jun 23, 2026
7c73cb3
✨ Add parameter to configure the ban page Content-Type response heade…
highpingblorg Jun 28, 2026
21895fb
:arrow_up: Bump actions/cache from 5 to 6 (#344)
dependabot[bot] Jun 29, 2026
1c1672c
✨ Consider 502, 503 and 504 as unavaible for appsec (#338)
dani Jun 30, 2026
d32f271
🐛 appsec: do not buffer unreadable (gRPC/HTTP2) request bodies (#332)
mathieuHa Jul 1, 2026
26ce12f
🤖 chore: adopt Renovate for automated dependency updates (#330)
mathieuHa Jul 3, 2026
5a2998b
⬆️ renovate: Update all (#345)
maxlerebourg Jul 3, 2026
be13c49
🐛 ci(renovate): fix OOM crash and stop using Mend's default gitAuthor…
mathieuHa Jul 5, 2026
1c98c70
⬆️ renovate: Update all (#349)
maxlerebourg Jul 5, 2026
f852865
Merge remote-tracking branch 'origin/main' into crowdsec/bot/detection
sabban Jul 16, 2026
3187426
🐛 Do not consider body unreadable when it's http.NoBody (#352)
dani Jul 24, 2026
bb44aef
✨ feat: Allow cache reading from replicas (#342)
samtoxie Jul 26, 2026
e98b8ed
✨ Renovate update version.go (#360)
maxlerebourg Jul 26, 2026
9daba97
⬆️ renovate: Update all (#350)
maxlerebourg Jul 26, 2026
f6ef95c
🐛 fix default value for CrowdsecAppsecUnreadableBodyBlock (#361)
maxlerebourg Jul 27, 2026
ed4a9e8
⬆️ renovate: Update all (#362)
maxlerebourg Jul 27, 2026
99cf971
✨ cicd: bump the version before tagging so releases report their own …
mathieuHa Jul 30, 2026
bef5dfa
🔖 release v1.7.1 (#367)
github-actions[bot] Jul 31, 2026
2930dcb
Merge branch 'main' into crowdsec/bot/detection
sabban Jul 31, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
24 changes: 0 additions & 24 deletions .github/dependabot.yml

This file was deleted.

9 changes: 7 additions & 2 deletions .github/workflows/main.yml
Original file line number Diff line number Diff line change
Expand Up @@ -15,8 +15,13 @@ jobs:
name: Main Process
runs-on: ubuntu-latest
env:
GO_VERSION: 1.23
# Keep in sync with go.mod. Capped at 1.22 because the plugin is run by
# yaegi (bundled in Traefik) and even Traefik v3.7.1 ships yaegi v0.16.1,
# which only supports Go 1.22. Building on the floor makes go build / go
# test reject newer stdlib before yaegi_test does.
GO_VERSION: 1.22
GOLANGCI_LINT_VERSION: v1.63.4
# yaegi_test guard — pin to the version current Traefik bundles.
YAEGI_VERSION: v0.16.1
CGO_ENABLED: 0
defaults:
Expand All @@ -40,7 +45,7 @@ jobs:

# https://github.com/marketplace/actions/cache
- name: Cache Go modules
uses: actions/cache@v5
uses: actions/cache@v6
with:
path: ${{ github.workspace }}/go/pkg/mod
key: ${{ runner.os }}-go-${{ hashFiles('**/go.sum') }}
Expand Down
83 changes: 83 additions & 0 deletions .github/workflows/release-prepare.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
name: Release (1/2) Prepare

# Step 1 of the release process: bump pluginVersion *before* the tag exists.
#
# The version reported to the Crowdsec LAPI lives in version.go, so it has to
# be correct in the very commit the tag points at. Anything that patches
# version.go after the release is published is too late: Traefik's plugin
# service caches the plugin archive per module+version, so users keep the
# source that was there when the tag was first resolved (see #322, #363).
#
# This workflow opens a "release" PR containing only that bump. Merging it
# triggers Release (2/2) Publish, which creates the tag and the GitHub release
# on the merged commit.

on:
workflow_dispatch:
inputs:
version:
description: "Version to release, e.g. v1.7.1 or v1.8.0-alpha"
required: true
type: string

permissions:
contents: write
pull-requests: write

jobs:
prepare:
name: Open release PR for ${{ inputs.version }}
runs-on: ubuntu-latest
steps:
- name: Check out main
uses: actions/checkout@v7
with:
ref: main
fetch-depth: 0

- name: Validate version
env:
VERSION: ${{ inputs.version }}
run: |
if ! [[ "$VERSION" =~ ^v[0-9]+\.[0-9]+\.[0-9]+(-[0-9A-Za-z.]+)?$ ]]; then
echo "::error::'$VERSION' is not a vX.Y.Z / vX.Y.Z-suffix version"
exit 1
fi
if git rev-parse -q --verify "refs/tags/$VERSION" >/dev/null; then
echo "::error::tag $VERSION already exists"
exit 1
fi

- name: Bump version.go
env:
VERSION: ${{ inputs.version }}
run: |
sed -i 's/pluginVersion = "[^"]*"/pluginVersion = "'"$VERSION"'"/' version.go
cat version.go
if git diff --quiet -- version.go; then
echo "::error::version.go already reads $VERSION, nothing to release"
exit 1
fi

- name: Push release branch and open PR
env:
GH_TOKEN: ${{ github.token }}
VERSION: ${{ inputs.version }}
run: |
git config user.name "github-actions[bot]"
git config user.email "github-actions[bot]@users.noreply.github.com"
git switch -c "release/$VERSION"
git commit -am "🔖 release $VERSION"
git push -u origin "release/$VERSION"

cat > /tmp/pr-body.md <<EOF
Bumps \`pluginVersion\` to \`$VERSION\` so the tag carries the version
the plugin reports to the Crowdsec LAPI.

Merging this PR tags \`$VERSION\` on the resulting commit and publishes
the GitHub release automatically.

> Keep the PR title as-is: **Release (2/2) Publish** matches on it.
EOF

gh pr create --base main --head "release/$VERSION" --title "🔖 release $VERSION" --body-file /tmp/pr-body.md
53 changes: 53 additions & 0 deletions .github/workflows/release-publish.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
name: Release (2/2) Publish

# Step 2 of the release process: tag and publish the commit prepared by
# Release (1/2) Prepare.
#
# Triggered by the release PR landing on main. The tag is created on that
# commit, so version.go inside the released source always matches the tag —
# no post-release patching, no force-moved tags.

on:
push:
branches: [main]
paths: ["version.go"]

permissions:
contents: write

jobs:
publish:
name: Tag and publish
runs-on: ubuntu-latest
steps:
- name: Check out the pushed commit
uses: actions/checkout@v7
with:
fetch-depth: 0

- name: Resolve release version
id: resolve
run: |
version="$(git log -1 --format='%B' | grep -oP '🔖 release \Kv[0-9]+\.[0-9]+\.[0-9]+(-[0-9A-Za-z.]+)?' || true)"
[ -z "$version" ] && { echo "version.go changed outside a release commit, nothing to do"; echo "release=false" >> "$GITHUB_OUTPUT"; exit 0; }

in_source="$(sed -n 's/.*pluginVersion = "\([^"]*\)".*/\1/p' version.go)"
[ "$in_source" != "$version" ] && { echo "::error::commit says $version but version.go reads $in_source"; exit 1; }
git rev-parse -q --verify "refs/tags/$version" >/dev/null && { echo "::error::tag $version already exists"; exit 1; }

echo "release=true" >> "$GITHUB_OUTPUT"
echo "version=$version" >> "$GITHUB_OUTPUT"
echo "prerelease=$([[ "$version" == *-* ]] && echo '--prerelease')" >> "$GITHUB_OUTPUT"

- name: Tag and create the GitHub release
if: steps.resolve.outputs.release == 'true'
env:
GH_TOKEN: ${{ github.token }}
VERSION: ${{ steps.resolve.outputs.version }}
PRERELEASE: ${{ steps.resolve.outputs.prerelease }}
run: |
git config user.name "github-actions[bot]"
git config user.email "github-actions[bot]@users.noreply.github.com"
git tag -a "$VERSION" -m "$VERSION"
git push origin "$VERSION"
gh release create "$VERSION" --title "$VERSION" --generate-notes $PRERELEASE
46 changes: 0 additions & 46 deletions .github/workflows/release.yml

This file was deleted.

41 changes: 41 additions & 0 deletions .github/workflows/renovate.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
name: Renovate

# Self-hosted Renovate: opens dependency-update PRs on a daily schedule.
# Config lives in /renovate.json. Requires a repo/org secret RENOVATE_TOKEN
# (a PAT with `repo` + `workflow` scope, or a fine-grained token with
# contents:write + pull-requests:write) so Renovate can push branches and open
# PRs. Trigger manually from the Actions tab via "Run workflow" to test.

on:
schedule:
- cron: "0 4 * * *" # every day at 04:00 UTC
workflow_dispatch:
inputs:
logLevel:
description: "Renovate log level"
required: false
default: "info"

permissions:
contents: read

concurrency:
group: renovate
cancel-in-progress: false

jobs:
renovate:
runs-on: ubuntu-latest
steps:
- name: Run Renovate
uses: renovatebot/github-action@v46.1.21
with:
token: ${{ secrets.RENOVATE_TOKEN }}
env:
RENOVATE_REPOSITORIES: ${{ github.repository }}
RENOVATE_ONBOARDING: "false"
RENOVATE_REQUIRE_CONFIG: "required"
# The grouped "all" branch holds many upgrades; changelog/PR-body
# rendering for it blew the default 4GB V8 heap (exit 134 OOM).
NODE_OPTIONS: "--max-old-space-size=8192"
LOG_LEVEL: ${{ github.event.inputs.logLevel || 'info' }}
3 changes: 2 additions & 1 deletion .golangci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ linters-settings:
disable:
- fieldalignment
gocyclo:
min-complexity: 15
min-complexity: 20
goconst:
min-len: 5
min-occurrences: 4
Expand Down Expand Up @@ -41,6 +41,7 @@ linters-settings:
- $test
allow:
- $gostd
- github.com/maxlerebourg/simpleredis
- github.com/maxlerebourg/crowdsec-bouncer-traefik-plugin/pkg/logger
- github.com/maxlerebourg/crowdsec-bouncer-traefik-plugin/pkg/ip
- github.com/maxlerebourg/crowdsec-bouncer-traefik-plugin/pkg/configuration
Expand Down
5 changes: 2 additions & 3 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ export GO111MODULE=on

# Binary/mock suite (Traefik binary + mock LAPI). This is what CI runs.
# The local Docker suite (make e2e) lives in a separate PR/branch.
E2E_MOCK_SCENARIOS := stream-mode live-mode none-mode trusted-ips custom-ban-page captcha appsec tls-system-ca
E2E_MOCK_SCENARIOS := $(notdir $(wildcard tests/e2e/mock/scenarios/*))

default: lint test

Expand All @@ -20,7 +20,7 @@ yaegi_test:
e2e_mock: $(addprefix e2e_mock_,$(E2E_MOCK_SCENARIOS))

e2e_mock_%:
./tests/e2e/mock/scenarios/$*/run.sh
bash ./tests/e2e/mock/scenarios/$*/run.sh

vendor:
go mod vendor
Expand Down Expand Up @@ -124,4 +124,3 @@ show_metrics:

show_decisions:
docker exec crowdsec cscli decisions list

25 changes: 19 additions & 6 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -382,6 +382,10 @@ make run
- int64
- default: 10485760 (= 10MB)
- Transmit only the first number of bytes to Crowdsec Appsec Server.
- CrowdsecAppsecUnreadableBodyBlock
- bool
- default: true
- Behaviour when the request body cannot be buffered for inspection (HTTP/2 or HTTP/3 request without a `Content-Length`, typically a bidirectional gRPC stream). When `false` the request is forwarded to the Appsec Server with headers only (the body is left to stream through untouched). When `true` the request is blocked outright. Mirrors the reference bouncers' `APPSEC_DROP_UNREADABLE_BODY` option.
- CrowdsecAppsecKey
- string
- default: value of `CrowdsecLapiKey`
Expand Down Expand Up @@ -440,7 +444,12 @@ make run
- RedisCacheHost
- string
- default: "redis:6379"
- hostname and port for the Redis service
- hostname and port for the Redis write host (primary)
- RedisCacheReadHosts
- []string
- default: []
- List of Redis replica hostnames (host:port) to use for read operations. Reads are distributed round-robin across replicas. Falls back to RedisCacheHost when empty.
- Note: when set, reads are not retried against RedisCacheHost (the primary) if the replicas are unreachable. With RedisCacheUnreachableBlock at its default (true), a replica outage will therefore block/delay requests even though the primary is healthy.
- RedisCachePassword
- string
- default: ""
Expand Down Expand Up @@ -513,14 +522,14 @@ make run
- int64
- default: 1800 (= 30 minutes)
- Period after validation of a captcha before a new validation is required if Crowdsec decision is still valid
- CaptchaHTMLFilePath
- CaptchaFilePath
- string
- default: /captcha.html
- Path where the captcha template is stored
- BanHTMLFilePath
- Path where the captcha template is stored. The Content-Type header is automatically inferred from the file extension.
- BanFilePath
- string
- default: ""
- Path where the ban html file is stored (default empty ""=disabled)
- Path where the ban file is stored (default empty ""=disabled). The Content-Type header is automatically inferred from the file extension.
- TraceHeadersCustomName
- string
- default: ""
Expand Down Expand Up @@ -616,6 +625,7 @@ http:
crowdsecAppsecFailureBlock: true
crowdsecAppsecUnreachableBlock: true
crowdsecAppsecBodyLimit: 10485760
crowdsecAppsecUnreadableBodyBlock: false
crowdsecLapiKey: privateKey-foo
crowdsecLapiScheme: http
crowdsecLapiHost: crowdsec:8080
Expand All @@ -635,7 +645,10 @@ http:
forwardedHeadersCustomName: X-Custom-Header
remediationHeadersCustomName: cs-remediation
redisCacheEnabled: false
redisCacheHost: "redis:6379"
redisCacheHost: "redis-primary:6379"
redisCacheReadHosts:
- "redis-replica-1:6379"
- "redis-replica-2:6379"
redisCachePassword: password
redisCacheDatabase: "5"
redisCacheUnreachableBlock: true
Expand Down
Loading
Loading