README.md
Rama main · réplica de lectura del repositorio original.
<p align="center">
<img src="https://github.com/user-attachments/assets/382bd99f-9408-4baa-ba02-7646876a44be" width="590" alt="CrowdSec AbuseIPDB Bouncer Logo" style="max-width: 100%; height: auto;">
</p>
# CrowdSec AbuseIPDB Bouncer
[](https://github.com/developingchet/cs-abuseipdb-bouncer/actions/workflows/release.yml)
[](https://github.com/developingchet/cs-abuseipdb-bouncer/actions/workflows/ci.yml)
[](https://scorecard.dev/viewer/?uri=github.com/developingchet/cs-abuseipdb-bouncer)
[](https://github.com/developingchet/cs-abuseipdb-bouncer)
[](https://hub.docker.com/r/developingchet/cs-abuseipdb-bouncer)
[](LICENSE)
A production-ready, security-hardened CrowdSec bouncer that automatically reports malicious IP addresses to AbuseIPDB in real-time.
## Overview
This bouncer connects directly to the CrowdSec Local API and reports new ban decisions to AbuseIPDB's threat intelligence database. It is implemented as a single static Go binary running in a distroless container with no shell, no root privileges, and a read-only filesystem.
**Key capabilities:**
- Real-time reporting via the CrowdSec LAPI decision stream
- Concurrent worker pool — multiple HTTP round-trips to AbuseIPDB run in parallel, so high-frequency ban waves no longer stall the event loop
- Intelligent scenario-to-category mapping (covers 22 of 23 AbuseIPDB categories; category 2 DNS Poisoning is not generated by current scenario patterns)
- Per-IP cooldown enforcement (15-minute default, matching AbuseIPDB's deduplication window)
- Daily quota tracking with UTC midnight reset and disk persistence
- Optional pre-check to skip AbuseIPDB-whitelisted IPs before consuming report quota
- Exponential backoff retry; failed reports (network errors, 5xx, 429 rate limits) are persisted and retried, honouring AbuseIPDB's `Retry-After` / `X-RateLimit-Reset` headers; duplicate-report 429s and invalid-parameter 422s are dropped, not retried
- Health endpoints (`/healthz`, `/readyz`) driven by the last successful LAPI pull and AbuseIPDB call — probes never touch `state.db` and never spend API quota
- State persists across container restarts via named Docker volume
- Structured JSON logging compatible with Loki, Splunk, and other SIEM tools
- Zero PII in reports -- only scenario names are transmitted
**Security properties:**
- Runs as UID 65532 (nonroot) inside a distroless container
- No shell, no package manager, no OS utilities in the runtime image
- `cap_drop: ALL` and `read_only: true` by default in docker-compose.yml
- Seccomp syscall allowlist (`security/seccomp-bouncer.json`) blocks all syscalls not required by the binary
- TLS 1.2+ enforced on all outbound connections
- API keys loaded from environment variables or `*_FILE` secrets; 80-character hex keys, Bearer tokens and `X-Api-Key` headers are automatically redacted from log output
- Docker image is signed with [Cosign](https://docs.sigstore.dev/cosign/overview/) (keyless OIDC) and accompanied by a CycloneDX SBOM on every release
## Compliance
This bouncer follows the CrowdSec Remediation Component Specification for Stream Mode, User-Agent identification, and LAPI Telemetry.
This repository is a reporter-only outbound sink:
- AppSec forwarding is intentionally omitted.
- CAPTCHA/remediation HTML is intentionally omitted.
- No inline traffic enforcement is implemented (no reverse proxy, firewall, or routing logic).
LAPI requests identify the component as `cs-abuseipdb-bouncer/<VERSION>`.
## How It Works
```
┌──────────────────────────────┐
│ CrowdSec LAPI │
│ (port 8080 / 8443) │
└──────────────┬───────────────┘
│ polls every POLL_INTERVAL (default 10s;
│ the bundled docker-compose.yml sets 30s)
│ GET /v1/decisions/stream
│
v
┌─────────────────────────────────────────────────────┐
│ cs-abuseipdb-bouncer (main event loop) │
│ │
│ Pre-queue filters (stateless, no I/O): │
│ 1. action = "add" │
│ 2. exclude impossible-travel scenarios │
│ 3. origin = crowdsec / cscli │
│ 4. scope = ip │
│ 5. value is present │
│ 6. value is a single valid IP address │
│ 7. reject private IPs │
│ 8. reject IP_WHITELIST matches (only when set) │
│ 9. min duration check │
│ │
│ │ (non-blocking enqueue; drop → │
│ │ buffer_full metric on overflow) │
│ v │
│ ┌─────────────────────────┐ │
│ │ job queue (default 256)│ │
│ └──────┬────────┬─────────┘ │
│ │ │ (N workers, default 4) │
│ v v │
│ ┌──────────┐ ┌──────────┐ │
│ │ Worker 1 │ │ Worker N │ 10. cooldown check │
│ │ atomic │ │ atomic │ 11. quota check │
│ │ bbolt tx │ │ bbolt tx │ (single transaction) │
│ └──────────┘ └──────────┘ │
│ │
│ Janitor goroutine (every 5m): │
│ - prune expired cooldown entries from state.db │
│ - update cs_abuseipdb_bbolt_db_size_bytes gauge │
└─────────────────────────────────────────────────────┘
│ ┌──────────────────────────┐
│ HTTPS POST │ Prometheus :9090 │
│ /api/v2/report │ GET /metrics │
│ retry: 3 attempts │ GET /healthz │
│ 5s/10s backoff │ GET /readyz │
│ └──────────────────────────┘
v
┌──────────────────────────────┐
│ AbuseIPDB v2 API │
│ /report endpoint │
└──────────────────────────────┘
```
New decisions stream continuously from the LAPI. Each decision passes through eight stateless pre-queue filters (nine when `IP_WHITELIST` is set), then is handed off to a concurrent worker pool. Workers perform the quota and cooldown checks atomically (single bbolt transaction each) before dispatching to the AbuseIPDB sink. State is persisted to a named Docker volume so daily counts and per-IP cooldowns survive container restarts.
In parallel, a background usage-metrics worker aggregates successful AbuseIPDB submissions and pushes telemetry to `POST /v1/usage-metrics` every `30m` by default (`USAGE_METRICS_INTERVAL`, minimum `10m`).
## Quick Start
**Prerequisites:**
- Docker with Compose v2
- Running CrowdSec instance with accessible LAPI
- AbuseIPDB account (free tier: 1000 reports/day)
**Steps:**
```bash
# Register the bouncer with CrowdSec and save the printed key
docker exec crowdsec cscli bouncers add abuseipdb-bouncer
# Clone the repository
git clone https://github.com/developingchet/cs-abuseipdb-bouncer.git
cd cs-abuseipdb-bouncer
# Configure environment
cp .env.example .env
chmod 600 .env
nano .env # Set CROWDSEC_LAPI_KEY and ABUSEIPDB_API_KEY
# Build and start
docker compose build
docker compose up -d
# Verify startup
docker logs -f abuseipdb-bouncer
docker exec crowdsec cscli bouncers list
```
> If the container exits immediately with `permission denied` on `/data/state.db`, see [Volume Permissions](docs/TROUBLESHOOTING.md#volume-permission-denied-at-startup) in the troubleshooting guide.
Expected startup log (JSON format):
```json
{"level":"info","limit":1000,"used_today":0,"cooldown":"15m0s","precheck":false,"min_duration":"0s","log_level":"info","workers":4,"message":"bouncer started"}
```
## Documentation
- **[Setup Guide](docs/SETUP.md)** - Complete installation procedures
- **[Configuration Reference](docs/CONFIGURATION.md)** - All environment variables explained
- **[Troubleshooting](docs/TROUBLESHOOTING.md)** - Common issues and solutions
- **[Design Rationale](docs/DESIGN.md)** - Architecture decisions and security analysis
- **[External References](docs/REFERENCES.md)** - Links to official documentation
- **[Compliance Checklist](COMPLIANCE.md)** - Technical mapping to CrowdSec remediation-component requirements
## Repository Structure
```
.
├── .github/
│ ├── workflows/ # CI, release, image scan, Scorecard, Socket, Docker Hub description
│ ├── tools/ # Pinned CI tool versions (govulncheck)
│ ├── requirements/ # Pinned Socket CLI requirements
│ ├── CODEOWNERS
│ └── dependabot.yml
├── cmd/
│ └── bouncer/
│ ├── main.go # CLI entry point (Cobra)
│ ├── main_test.go
│ └── healthcheck.go # `bouncer healthcheck` subcommand (Docker HEALTHCHECK)
├── internal/
│ ├── bouncer/
│ │ ├── bouncer.go # Main run loop + pre-queue filter pipeline
│ │ ├── bouncer_test.go
│ │ ├── health.go # /healthz and /readyz state and handlers
│ │ ├── health_test.go
│ │ ├── janitor.go # Background cooldown pruner + DB size metric
│ │ ├── janitor_test.go
│ │ ├── pool.go # Concurrent worker pool
│ │ ├── pool_test.go
│ │ ├── pool_outcome_test.go
│ │ ├── retry_test.go
│ │ ├── runtime_test.go
│ │ └── testmain_test.go
│ ├── config/
│ │ ├── config.go # Env-based configuration
│ │ └── config_test.go
│ ├── decision/
│ │ ├── filter.go # Filter pipeline
│ │ ├── filter_test.go
│ │ ├── filter_error_test.go
│ │ ├── fuzz_test.go
│ │ ├── ip.go # Private IP detection
│ │ └── ip_test.go
│ ├── logger/
│ │ ├── logrus.go # Forwards go-cs-bouncer's logrus output into zerolog
│ │ ├── logrus_test.go
│ │ ├── redact.go # RedactWriter: masks API keys and Bearer tokens in logs
│ │ ├── redact_test.go
│ │ ├── fuzz_test.go
│ │ └── testdata/ # Fuzz corpus
│ ├── sink/
│ │ ├── sink.go # Sink interface
│ │ ├── sink_test.go
│ │ ├── errors.go # Typed sink errors (duplicate, rate limit, permanent, unauthorized)
│ │ └── abuseipdb/
│ │ ├── categories.go # Category ID constants
│ │ ├── categories_test.go
│ │ ├── client.go # HTTP client (sync.Pool for response buffers)
│ │ ├── client_test.go
│ │ ├── client_metrics_test.go
│ │ ├── mapper.go # Scenario-to-category mapping
│ │ └── mapper_test.go
│ ├── metrics/
│ │ ├── metrics.go # Prometheus metric definitions
│ │ ├── metrics_test.go
│ │ └── register_test.go
│ ├── telemetry/
│ │ ├── payload.go # LAPI /usage-metrics payload builder
│ │ ├── payload_test.go
│ │ ├── counter.go # In-memory processed counter for telemetry windows
│ │ ├── counter_test.go
│ │ ├── sender.go # Background telemetry push worker
│ │ └── sender_test.go
│ └── storage/
│ ├── store.go # Storage interface
│ ├── bbolt.go # ACID bbolt persistence (quota, cooldown and retry queue, atomic ops)
│ ├── bbolt_test.go
│ ├── bbolt_concurrent_test.go # Concurrent correctness tests (-race)
│ ├── mock.go # In-memory store for testing
│ └── mock_test.go
├── scripts/
│ └── validate-seccomp.sh # Static check of the seccomp profile's required syscalls
├── security/
│ └── seccomp-bouncer.json # OCI seccomp profile (minimal syscall allowlist)
├── docs/
│ ├── CONFIGURATION.md
│ ├── DESIGN.md
│ ├── REFERENCES.md
│ ├── SETUP.md
│ └── TROUBLESHOOTING.md
├── .env.example # Environment variable template
├── .gitignore
├── .golangci.yml # golangci-lint configuration
├── COMPLIANCE.md # CrowdSec remediation-component compliance checklist
├── CONTRIBUTING.md
├── DOCKERHUB.md # Docker Hub repository description
├── Dockerfile # Multi-stage: golang -> distroless
├── docker-compose.yml
├── go.mod
├── go.sum
├── LICENSE
├── Makefile # Test, coverage, lint and build targets
├── SECURITY.md # Vulnerability disclosure policy
├── socket.yml # Socket.dev supply-chain scan configuration
└── README.md
```
## Configuration Summary
All configuration is via environment variables. Required:
```bash
CROWDSEC_LAPI_URL=http://crowdsec:8080 # URL of the CrowdSec LAPI
ABUSEIPDB_API_KEY=<from abuseipdb.com/account/api>
# LAPI auth mode A (API key)
CROWDSEC_LAPI_KEY=<from cscli bouncers add>
# LAPI auth mode B (mTLS)
# CROWDSEC_LAPI_TLS_CERT_PATH=/etc/crowdsec/client.crt
# CROWDSEC_LAPI_TLS_KEY_PATH=/etc/crowdsec/client.key
# CROWDSEC_LAPI_TLS_CA_CERT_PATH=/etc/crowdsec/ca.crt
```
Optional (all have defaults):
```bash
ABUSEIPDB_DAILY_LIMIT=1000 # Free=1000, Webmaster=3000, Premium=50000
ABUSEIPDB_PRECHECK=false # Pre-check /check endpoint before reporting
ABUSEIPDB_MIN_DURATION=0 # Skip decisions shorter than N seconds
COOLDOWN_DURATION=15m # Per-IP cooldown window
POLL_INTERVAL=10s # LAPI polling frequency (min 2s; the bundled docker-compose.yml sets 30s)
LOG_LEVEL=info # trace, debug, info, warn, error
LOG_FORMAT=json # json or text
TLS_SKIP_VERIFY=false # Skip TLS verification (self-signed certs)
DATA_DIR=/data # Directory for state.db (mount a named volume here)
METRICS_ADDR=127.0.0.1:9090 # Address for /metrics, /healthz, /readyz (empty = disabled; the Docker image uses :9090)
USAGE_METRICS_ENABLED=true # Enable periodic LAPI /usage-metrics push
USAGE_METRICS_INTERVAL=30m # Telemetry push interval (min 10m)
CONFIG_FILE= # Optional path to YAML config file
# Concurrent worker pool (v2.0)
WORKER_COUNT=4 # Goroutines sending reports concurrently (1–64)
WORKER_BUFFER=256 # In-memory job queue size (1–10000)
JANITOR_INTERVAL=5m # How often to prune state.db and update DB size metric (min 30s)
RETRY_CHECK_INTERVAL=30s # How often to resend queued (failed / rate-limited) decisions (min 10s)
LAPI_TIMEOUT=10s # HTTP timeout for LAPI requests (min 200ms)
```
See [CONFIGURATION.md](docs/CONFIGURATION.md) for complete details.
## Scenario Mapping
CrowdSec scenario names are mapped to AbuseIPDB categories via ordered substring matching. The author prefix is stripped and the name is lowercased before matching.
| Scenario Pattern | AbuseIPDB Categories | Description |
|------------------|---------------------|-------------|
| `*ssh*` | 22, 18 | SSH brute-force |
| `*sqli*`, `*sql-inj*` | 16, 21 | SQL injection |
| `*wordpress*`, `*drupal*` | 18, 21 | CMS attacks |
| `*http-dos*`, `*ddos*` | 4 | Denial of service |
| `*cve*`, `*log4*`, `*rce*` | 21, 20 | CVE exploitation |
| `*brute*`, `*-bf*`, `*rdp*` | 18 | Generic brute-force |
| (fallback) | 15 | Hacking |
Full 27-rule mapping table in [CONFIGURATION.md](docs/CONFIGURATION.md#scenario-category-mapping).
## Filtered Decisions
These decisions are intentionally never reported:
- **impossible-travel scenarios** - Account compromise heuristics, not IP-based abuse
- **CAPI and lists origins** - Community blocklist IPs that are already globally known
- **Private / reserved IP addresses** - RFC1918, loopback (127.0.0.0/8), link-local, CGNAT (100.64.0.0/10), benchmarking (198.18.0.0/15), multicast, 240.0.0.0/4, and IPv6 loopback/link-local/ULA/multicast — IPv4-mapped IPv6 (`::ffff:10.0.0.1`) is checked as IPv4
- **Non-IP scopes** - Ranges, ASNs, country-level decisions (AbuseIPDB only accepts single IPs)
- **Malformed IP values** - Anything other than a bare address or a `/32` / `/128` prefix, including wider networks and zoned IPv6 addresses (skip reason `invalid_ip`)
- **Whitelisted IPs** - Addresses matching `IP_WHITELIST`, when it is set (skip reason `whitelist`)
- **Short decisions** - Bans shorter than `ABUSEIPDB_MIN_DURATION`, when it is set (skip reason `min_duration`)
- **IPs within the cooldown window** - Already reported within the past 15 minutes
- **Decisions exceeding the daily quota** - Once the limit is reached, no further reports are sent that UTC day
## State Persistence
State is stored in a single `state.db` file (bbolt embedded database) inside `DATA_DIR` (default `/data`, mounted as a named Docker volume):
```
/data/
└── state.db # bbolt database with three buckets:
├── quota # JSON record: {"count":42,"date":"2026-02-17"}
├── cooldown # Per-IP binary Unix timestamps (big-endian int64)
│ # key: 203.0.113.42 (IPv4, unchanged)
│ # key: 2001_db8__1 (IPv6, colons → underscores)
└── retry # Per-IP JSON record, same keys as cooldown:
# {"ip":"203.0.113.42","scenario":"crowdsecurity/ssh-bf",
# "retry_after":1739837130,"attempts":1}
```
Volume mount: `bouncer-state:/data`
The daily quota record resets automatically at UTC midnight. Cooldown entries are pruned by the background janitor (every 5 minutes by default, configurable via `JANITOR_INTERVAL`). The retry bucket holds decisions waiting for another delivery attempt. All three survive container restarts via the named Docker volume.
## Observability
The HTTP server (enabled by default at `127.0.0.1:9090`; the Docker image listens on `:9090` inside the container) exposes three unauthenticated endpoints, so publish or bind it only to loopback or a private network (the bouncer logs a warning at startup when `METRICS_ADDR` is not a loopback address):
| Endpoint | Description |
|----------|-------------|
| `GET /metrics` | Prometheus metrics in text exposition format |
| `GET /healthz` | Liveness — HTTP 200 while the LAPI has been polled successfully within `max(5 × POLL_INTERVAL, 2m)` and fewer than 5 consecutive AbuseIPDB calls have failed; 503 otherwise. The same `max(5 × POLL_INTERVAL, 2m)` applies as a startup grace before the first pull. |
| `GET /readyz` | Readiness — as `/healthz`, and additionally 503 until the first successful LAPI pull |
Both health endpoints return a JSON body (`status`, `reason`, `last_lapi_pull`, `last_abuseipdb_ok`, `last_abuseipdb_failure`, `consecutive_abuseipdb_failures`) and are served from memory: they never open `state.db` and never call AbuseIPDB. The Docker `HEALTHCHECK` runs `bouncer healthcheck`, which GETs `/healthz` on `METRICS_ADDR` — so it requires `METRICS_ENABLED=true`.
Prometheus metrics:
| Metric | Type | Labels | Description |
|--------|------|--------|-------------|
| `cs_abuseipdb_decisions_processed_total` | Counter | — | All decisions received from the LAPI stream |
| `cs_abuseipdb_reports_sent_total` | Counter | — | Successful reports sent to AbuseIPDB |
| `cs_abuseipdb_decisions_skipped_total` | Counter | `filter` | Decisions dropped, by filter or outcome (`cooldown`, `quota`, `duplicate`, `rejected`, `retry_exhausted`, `buffer_full`, …) |
| `cs_abuseipdb_api_errors_total` | Counter | `type` | API errors: `rate_limit`, `auth`, `validation`, `network`, `timeout` |
| `cs_abuseipdb_quota_remaining` | Gauge | — | Remaining daily quota (resets at UTC midnight) |
| `cs_abuseipdb_bbolt_db_size_bytes` | Gauge | — | Size of `state.db` in bytes, updated by the janitor |
| `cs_abuseipdb_retry_queue_size` | Gauge | — | Decisions waiting in the persistent retry queue |
| `cs_abuseipdb_retry_queue_enqueued_total` | Counter | — | Decisions queued for retry (429 or transient failure) |
| `cs_abuseipdb_retry_attempts_total` | Counter | — | Retry attempts submitted from the queue |
| `cs_abuseipdb_last_lapi_pull_timestamp_seconds` | Gauge | — | Unix time of the last successful LAPI poll |
| `cs_abuseipdb_last_report_success_timestamp_seconds` | Gauge | — | Unix time of the last successful AbuseIPDB report |
```bash
curl http://localhost:9090/metrics | grep cs_abuseipdb
```
## Logging
JSON format (default):
```json
{"level":"info","ip":"203.0.113.42","sink":"abuseipdb","time":1739836530,"message":"reported"}
{"level":"debug","ip":"192.168.1.1","filter":"private_ip","detail":"ip=192.168.1.1 is private/reserved","time":1739836531,"message":"decision filtered (pre-queue)"}
```
Human-readable format (`LOG_FORMAT=text`):
```
12:15PM INF reported ip=203.0.113.42 sink=abuseipdb
12:15PM DBG decision filtered (pre-queue) detail="ip=192.168.1.1 is private/reserved" filter=private_ip ip=192.168.1.1
```
Enable debug logging:
```bash
# In .env:
LOG_LEVEL=debug
docker compose up -d --force-recreate abuseipdb-bouncer
```
## Supply-Chain Verification
Every release is signed with [Cosign](https://docs.sigstore.dev/cosign/overview/) using keyless OIDC (no stored private key). Verify the image signature:
```bash
cosign verify developingchet/cs-abuseipdb-bouncer:latest \
--certificate-identity-regexp="https://github.com/developingchet/cs-abuseipdb-bouncer/.github/workflows/release.yml@refs/tags/.*" \
--certificate-oidc-issuer="https://token.actions.githubusercontent.com"
```
A CycloneDX SBOM is attached to every GitHub Release and embedded as a Cosign attestation on the Docker image.
The image is also published to GitHub Container Registry as `ghcr.io/developingchet/cs-abuseipdb-bouncer`, with the same tags and digest. It carries its own signature and SBOM attestation there, so the command above works with the `ghcr.io` name too.
## Building
The binary compiles to approximately 13MB. Docker is required (Go does not need to be installed locally):
```bash
docker compose build
```
To run tests directly (requires Go 1.27+; `-race` also needs a C compiler / CGO):
```bash
go test -race ./... -count=1 -timeout=120s
```
## Upgrade notes
### Upgrading to v2.5.0
**Metrics listener on loopback by default.** From v2.5.0, `METRICS_ADDR` defaults to `127.0.0.1:9090` instead of `:9090`. The Docker image sets `METRICS_ADDR=:9090`, so container deployments are unaffected. A native install (systemd, Windows) that is scraped by Prometheus from another host stops answering that scraper after upgrading to v2.5.0, and no warning is logged because the new default is a loopback address. Set `METRICS_ADDR` explicitly, for example `0.0.0.0:9090` or the private interface address the scraper uses, and keep the port behind a firewall since the endpoints are unauthenticated. The bouncer logs a warning at startup whenever `METRICS_ADDR` is not a loopback address.
**New `invalid_ip` skip reason.** A decision whose value is not a single IP address (malformed text, a network wider than `/32` or `/128`, or a zoned IPv6 address) is now dropped before the cooldown and quota checks and counted under `filter="invalid_ip"` in `cs_abuseipdb_decisions_skipped_total`. Earlier versions spent a cooldown entry and a quota unit on such values before AbuseIPDB refused them. Dashboards and alerts that enumerate `filter` label values should include the new one.
### Upgrading to v2.5.3
**`POLL_INTERVAL` now defaults to `10s`.** The built-in default changes from `2s` to `10s`, matching CrowdSec's recommended pull period and cutting default LAPI request traffic by 80%. The configurable minimum remains `2s`. Reporting latency follows the interval: a decision can now reach AbuseIPDB up to 10 s after CrowdSec issues it, instead of 2 s.
Deployments that never set `POLL_INTERVAL` and run the binary directly (systemd, Windows, bare Docker without the bundled compose file) move from 2-second to 10-second polling. The bundled `docker-compose.yml` is unaffected — it sets `POLL_INTERVAL=30s` when the variable is unset. To keep the previous behaviour, set `POLL_INTERVAL=2s` explicitly.
Dashboards that assumed per-poll latencies (e.g. alerting on `cs_abuseipdb_last_lapi_pull_timestamp_seconds` staleness) are also unaffected: `/healthz` marks upstream stale after `max(5 × POLL_INTERVAL, 2m)`, which was already 2 minutes and does not change.
## Migration from v1.x
| Aspect | v1.x | v2.0 |
|--------|------|-------|
| State env var | `STATE_DIR` | `DATA_DIR` |
| Default path | `/tmp/cs-abuseipdb` | `/data` |
| Storage format | `daily` file + `cooldown/` directory | `state.db` (bbolt embedded database) |
| Volume mount | `bouncer-state:/tmp/cs-abuseipdb` | `bouncer-state:/data` |
| New in v2.0 | — | `METRICS_ENABLED`, `METRICS_ADDR`, `CONFIG_FILE`, `WORKER_COUNT`, `WORKER_BUFFER`, `JANITOR_INTERVAL`, `RETRY_CHECK_INTERVAL`, `USAGE_METRICS_ENABLED`, `USAGE_METRICS_INTERVAL`, `LAPI_TIMEOUT`, `IP_WHITELIST` |
| Supply-chain | — | Cosign keyless signing + CycloneDX SBOM |
| Runtime hardening | — | Seccomp syscall allowlist + log key redaction |
**No migration script is required.** On first start v2.0 creates a fresh `state.db`. The quota counter resets at UTC midnight anyway; cooldowns rebuild within one 15-minute window.
Update your compose file:
1. Change `STATE_DIR=/tmp/cs-abuseipdb` → `DATA_DIR=/data`
2. Change `- bouncer-state:/tmp/cs-abuseipdb` → `- bouncer-state:/data`
3. Optionally add `METRICS_ADDR=:9090` and expose port 9090
All new variables (`WORKER_COUNT`, `WORKER_BUFFER`, `JANITOR_INTERVAL`) have sensible defaults and require no changes to existing deployments.
## Contributing
Contributions are welcome. See [CONTRIBUTING.md](CONTRIBUTING.md) for guidelines.
Areas where help is needed:
- Additional scenario pattern mappings
- Integration guides (Kubernetes, Proxmox, Synology)
## License
MIT License. See [LICENSE](LICENSE) for full text.
## Support
- **Issues:** https://github.com/developingchet/cs-abuseipdb-bouncer/issues
- **Discussions:** https://github.com/developingchet/cs-abuseipdb-bouncer/discussions
- **CrowdSec Discord:** https://discord.gg/crowdsec
## Acknowledgments
Thanks to the CrowdSec team for the `go-cs-bouncer` library and to AbuseIPDB for maintaining the IP reputation database.