DOCKERHUB.md
Rama main · réplica de lectura del repositorio original.
# cs-abuseipdb-bouncer
A production-ready, security-hardened CrowdSec bouncer that reports malicious IPs to AbuseIPDB in real-time. Single static Go binary, distroless container, Prometheus metrics.
---
## Features
| Feature | Detail |
|---------|--------|
| **Concurrent Worker Pool** | A configurable pool of goroutines sends reports to AbuseIPDB in parallel. High-frequency ban waves no longer stall the main event loop. Backpressure is handled via a bounded channel; overflow is counted in the `buffer_full` filter metric. Failed reports (network errors, 5xx, 429) are persisted and retried, honouring AbuseIPDB's `Retry-After` header. |
| **ACID State** | Per-IP cooldown and daily quota are stored in a [bbolt](https://github.com/etcd-io/bbolt) embedded database (`state.db`). Quota and cooldown checks execute as single atomic transactions — no TOCTOU races, crash-consistent, survives container restarts. |
| **Prometheus Metrics** | Eleven metrics are exported on `GET /metrics` (port 9090 by default): decisions processed, reports sent, decisions skipped (by filter), API errors (by type), daily quota remaining, `state.db` file size, retry-queue size / enqueues / attempts, and last-successful LAPI pull / AbuseIPDB report timestamps. Ready for Grafana / Alertmanager. |
| **Honest Health Checks** | `/healthz` and `/readyz` reflect the last successful LAPI pull and AbuseIPDB call. The Docker `HEALTHCHECK` queries them over HTTP — it never touches `state.db` and never spends API quota. |
| **Distroless Security** | The runtime image is `gcr.io/distroless/static-debian12:nonroot`. No shell, no package manager, no libc. Runs as UID 65532 with zero Linux capabilities and a read-only filesystem. Seccomp syscall allowlist applied by default in the provided `docker-compose.yml`. |
| **Supply-Chain Provenance** | Every release is signed with [Cosign](https://docs.sigstore.dev/cosign/overview/) (keyless OIDC — no stored private key) and accompanied by a CycloneDX SBOM attached as a Cosign attestation and a GitHub Release asset. |
| **Multi-Architecture** | Pre-built images for `linux/amd64` and `linux/arm64` on Docker Hub and GitHub Container Registry (`ghcr.io/developingchet/cs-abuseipdb-bouncer`). Static Go binary — no libc, no CGO. |
| **Zero-Touch Releases** | Every version tag triggers a GitHub Actions pipeline: tests (with `-race`) → Trivy CVE scan (blocks on HIGH/CRITICAL) → multi-arch Docker build → Docker Hub and GHCR push → Cosign sign + SBOM → GitHub Release with binaries and SHA-256 checksums. |
---
## Quick Start
```yaml
# docker-compose.yml (minimal)
services:
abuseipdb-bouncer:
image: developingchet/cs-abuseipdb-bouncer:latest
restart: unless-stopped
environment:
- CROWDSEC_LAPI_URL=http://crowdsec:8080
- CROWDSEC_LAPI_KEY=${CROWDSEC_LAPI_KEY}
- ABUSEIPDB_API_KEY=${ABUSEIPDB_API_KEY}
- DATA_DIR=/data
- METRICS_ADDR=:9090
volumes:
- bouncer-state:/data
ports:
- "127.0.0.1:9090:9090"
tmpfs:
- /tmp:size=10M,uid=65532,gid=65532,mode=1777
read_only: true
cap_drop: [ALL]
security_opt: [no-new-privileges:true]
networks:
- crowdsec-net
volumes:
bouncer-state:
networks:
crowdsec-net:
external: true
```
Get your LAPI key: `docker exec crowdsec cscli bouncers add abuseipdb-bouncer`
---
## Environment Variables
### Required
| Variable | Description |
|----------|-------------|
| `CROWDSEC_LAPI_URL` | URL of the CrowdSec Local API (e.g. `http://crowdsec:8080`) |
| `CROWDSEC_LAPI_KEY` | Bouncer API key from `cscli bouncers add`. Alternatives: `CROWDSEC_LAPI_KEY_FILE` (path to a file holding the key), or mTLS with `CROWDSEC_LAPI_TLS_CERT_PATH` + `CROWDSEC_LAPI_TLS_KEY_PATH` (optional `CROWDSEC_LAPI_TLS_CA_CERT_PATH`) instead of a key — use one auth mode, not both. |
| `ABUSEIPDB_API_KEY` | AbuseIPDB v2 API key from abuseipdb.com/account/api. Alternative: `ABUSEIPDB_API_KEY_FILE` (path to a file holding the key). |
### Optional
| Variable | Default | Description |
|----------|---------|-------------|
| `DATA_DIR` | `/data` | Directory for `state.db` (bbolt database). Mount a named volume here. |
| `METRICS_ENABLED` | `true` | Set to `false` to disable the `/metrics`, `/healthz`, `/readyz` HTTP server entirely (no port opened). Takes precedence over `METRICS_ADDR`. The image `HEALTHCHECK` needs this server — disable the healthcheck too if you turn it off. |
| `METRICS_ADDR` | `:9090` | Address for `/metrics`, `/healthz`, `/readyz`. The image sets `:9090`; the binary on its own defaults to `127.0.0.1:9090`. A warning is logged at startup when the address is not loopback, since the endpoints are unauthenticated — publish the port only to `127.0.0.1` or a private network. Ignored when `METRICS_ENABLED=false`. Set to empty string to disable. |
| `CONFIG_FILE` | _(none)_ | Optional path to a YAML config file (alternative / supplement to env vars). |
| `ABUSEIPDB_DAILY_LIMIT` | `1000` | Daily report quota (free=1000, webmaster=3000, premium=50000). |
| `ABUSEIPDB_PRECHECK` | `false` | Pre-check each IP with `/check` before reporting (skips whitelisted IPs). Uses one call from the separate `/check` daily quota per decision. |
| `ABUSEIPDB_MIN_DURATION` | `0` | Skip decisions shorter than N seconds (e.g. `300` ignores 5-minute test bans). |
| `IP_WHITELIST` | _(none)_ | Comma-separated IPs/CIDRs to skip reporting (e.g. `203.0.113.0/24,2001:db8::/32`). |
| `COOLDOWN_DURATION` | `15m` | Per-IP cooldown matching AbuseIPDB's deduplication window. |
| `POLL_INTERVAL` | `10s` | LAPI decision stream polling frequency (minimum: 2s). The bundled docker-compose.yml sets `30s` when unset. |
| `LAPI_TIMEOUT` | `10s` | HTTP timeout for LAPI requests (minimum: 200ms). |
| `LOG_LEVEL` | `info` | `trace`, `debug`, `info`, `warn`, or `error`. |
| `LOG_FORMAT` | `json` | `json` (structured, for SIEM) or `text` (human-readable). |
| `TLS_SKIP_VERIFY` | `false` | Skip TLS verification — only for self-signed LAPI certificates. |
| `WORKER_COUNT` | `4` | Number of goroutines that concurrently send reports to AbuseIPDB (range: 1–64). |
| `WORKER_BUFFER` | `256` | Size of the in-memory job queue between the event loop and workers (range: 1–10000). |
| `JANITOR_INTERVAL` | `5m` | How often the background janitor prunes expired cooldown entries and updates the DB size metric (minimum: 30s). |
| `RETRY_CHECK_INTERVAL` | `30s` | How often to check the retry queue for failed / rate-limited decisions ready to resend (minimum: 10s). |
| `USAGE_METRICS_ENABLED` | `true` | Enable/disable periodic LAPI telemetry push (`POST /v1/usage-metrics`). |
| `USAGE_METRICS_INTERVAL` | `30m` | Interval for LAPI telemetry push (minimum: 10m). |
---
## Observability
### Endpoints
| Endpoint | Description |
|----------|-------------|
| `GET /metrics` | Prometheus metrics in text exposition format |
| `GET /healthz` | Liveness — HTTP 200 while LAPI was polled successfully within `max(5 × POLL_INTERVAL, 2m)` and fewer than 5 consecutive AbuseIPDB calls failed; 503 otherwise (JSON body with the reason) |
| `GET /readyz` | Readiness — as `/healthz`, plus 503 until the first successful LAPI pull |
### 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 |
| `cs_abuseipdb_api_errors_total` | Counter | `type` | API errors by type: `rate_limit`, `auth`, `validation`, `network`, `timeout` |
| `cs_abuseipdb_quota_remaining` | Gauge | — | Remaining daily report 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 |
| `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 |
Scrape example:
```bash
curl http://localhost:9090/metrics | grep cs_abuseipdb
```
---
## Security
### Distroless Runtime
The container image is built `FROM gcr.io/distroless/static-debian12:nonroot`. This means:
- **No shell** — eliminates RCE via shell injection
- **No package manager** — no `apt`, `apk`, or `pip` to install tools post-compromise
- **Minimal CVE surface** — only the Go binary and CA certificates
### Seccomp Profile
The repository ships `security/seccomp-bouncer.json`, a minimal OCI seccomp profile that allows only the syscalls the binary actually needs.
> **The file must exist on your host.** It is read from the host filesystem at container start — it is not embedded in the image. Docker will refuse to start if the path does not exist.
**If you cloned the repo**, `./security/seccomp-bouncer.json` is already present. Otherwise, download it first:
```bash
mkdir -p security
curl -fsSL \
https://raw.githubusercontent.com/developingchet/cs-abuseipdb-bouncer/main/security/seccomp-bouncer.json \
-o security/seccomp-bouncer.json
```
Then apply it in your compose file:
```yaml
security_opt:
- no-new-privileges:true
- "seccomp:./security/seccomp-bouncer.json"
```
If you prefer not to download the file, simply omit that line — `cap_drop: ALL`, `read_only: true`, and the distroless nonroot image still provide strong isolation without it.
> If the container crash-loops with `operation not permitted`, the installed profile may be outdated. See [Troubleshooting](https://github.com/developingchet/cs-abuseipdb-bouncer/blob/main/docs/TROUBLESHOOTING.md#seccomp-profile-blocks-container-startup) for the fix.
### Log Redaction
API keys and Bearer tokens are automatically redacted from all log output before they reach stderr. The regex patterns match runs of 80 or more hex characters (AbuseIPDB key format; the whole run is replaced so no part of a key leaks), `Bearer <token>` values and `X-Api-Key:` header values (CrowdSec LAPI key).
### Supply-Chain Verification
Verify the image signature with Cosign:
```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 available as a GitHub Release asset and embedded as a Cosign attestation on the image.
The same image, with the same tags and digest, is published as `ghcr.io/developingchet/cs-abuseipdb-bouncer`. It is signed and attested there too, so the command above also works with the `ghcr.io` name.
---
## 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` |
**No migration script is needed.** On first start, v2.0 creates a fresh `state.db`. The quota counter resets at UTC midnight anyway, and 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
---
## Links
- **GitHub Repository:** https://github.com/developingchet/cs-abuseipdb-bouncer
- **Setup Guide:** https://github.com/developingchet/cs-abuseipdb-bouncer/blob/main/docs/SETUP.md
- **Configuration Reference:** https://github.com/developingchet/cs-abuseipdb-bouncer/blob/main/docs/CONFIGURATION.md
- **Troubleshooting:** https://github.com/developingchet/cs-abuseipdb-bouncer/blob/main/docs/TROUBLESHOOTING.md
- **Issues / Bug Reports:** https://github.com/developingchet/cs-abuseipdb-bouncer/issues