developingchet/cs-abuseipdb-bouncer

By developingchet

Updated 4 months ago

CrowdSec to AbuseIPDB bouncer. Concurrent worker pool, ACID bbolt, Cosign-signed, distroless v2.0

Image
Security
Monitoring & observability
0

5.0K

developingchet/cs-abuseipdb-bouncer repository overview

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

FeatureDetail
Concurrent Worker PoolA 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.
ACID StatePer-IP cooldown and daily quota are stored in a bbolt embedded database (state.db). Quota and cooldown checks execute as single atomic transactions — no TOCTOU races, crash-consistent, survives container restarts.
Prometheus MetricsSix 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, and state.db file size. Ready for Grafana / Alertmanager.
Distroless SecurityThe 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 ProvenanceEvery release is signed with Cosign (keyless OIDC — no stored private key) and accompanied by a CycloneDX SBOM attached as a Cosign attestation and a GitHub Release asset.
Multi-ArchitecturePre-built images for linux/amd64 and linux/arm64 on Docker Hub. Static Go binary — no libc, no CGO.
Zero-Touch ReleasesEvery version tag triggers a GitHub Actions pipeline: tests (with -race) → Trivy CVE scan (blocks on HIGH/CRITICAL) → multi-arch Docker build → Cosign sign + SBOM → Docker Hub push → GitHub Release with binaries and SHA-256 checksums.

Quick Start

# 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
VariableDescription
CROWDSEC_LAPI_URLURL of the CrowdSec Local API (e.g. http://crowdsec:8080)
CROWDSEC_LAPI_KEYBouncer API key from cscli bouncers add
ABUSEIPDB_API_KEYAbuseIPDB v2 API key from abuseipdb.com/account/api
Optional
VariableDefaultDescription
DATA_DIR/dataDirectory for state.db (bbolt database). Mount a named volume here.
METRICS_ENABLEDtrueSet to false to disable the /metrics, /healthz, /readyz HTTP server entirely (no port opened). Takes precedence over METRICS_ADDR.
METRICS_ADDR:9090Address for /metrics, /healthz, /readyz. 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_LIMIT1000Daily report quota (free=1000, webmaster=3000, premium=50000).
ABUSEIPDB_PRECHECKfalsePre-check each IP with /check before reporting (skips whitelisted IPs). Uses one extra API call per decision.
ABUSEIPDB_MIN_DURATION0Skip 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_DURATION15mPer-IP cooldown matching AbuseIPDB's deduplication window.
POLL_INTERVAL30sLAPI decision stream polling frequency.
LOG_LEVELinfotrace, debug, info, warn, or error.
LOG_FORMATjsonjson (structured, for SIEM) or text (human-readable).
TLS_SKIP_VERIFYfalseSkip TLS verification — only for self-signed LAPI certificates.
WORKER_COUNT4Number of goroutines that concurrently send reports to AbuseIPDB (range: 1–64).
WORKER_BUFFER256Size of the in-memory job queue between the event loop and workers (range: 1–10000).
JANITOR_INTERVAL5mHow often the background janitor prunes expired cooldown entries and updates the DB size metric (minimum: 30s).

Observability

Endpoints
EndpointDescription
GET /metricsPrometheus metrics in text exposition format
GET /healthzLiveness probe — returns ok (HTTP 200) when the process is running
GET /readyzReadiness probe — HTTP 200 when connected to LAPI, 503 otherwise
Prometheus Metrics
MetricTypeLabelsDescription
cs_abuseipdb_decisions_processed_totalCounterAll decisions received from the LAPI stream
cs_abuseipdb_reports_sent_totalCounterSuccessful reports sent to AbuseIPDB
cs_abuseipdb_decisions_skipped_totalCounterfilterDecisions dropped by each filter stage
cs_abuseipdb_api_errors_totalCountertypeAPI errors by type: rate_limit, auth, network, timeout
cs_abuseipdb_quota_remainingGaugeRemaining daily report quota (resets at UTC midnight)
cs_abuseipdb_bbolt_db_size_bytesGaugeSize of state.db in bytes, updated by the janitor

Scrape example:

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:

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:

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 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 80-character hex strings (AbuseIPDB / CrowdSec key format) and Bearer <token> values.

Supply-Chain Verification

Verify the image signature with Cosign:

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.


Migration from v1.x

Aspectv1.xv2.0
State env varSTATE_DIRDATA_DIR
Default path/tmp/cs-abuseipdb/data
Storage formatdaily file + cooldown/ directorystate.db (bbolt embedded database)
Volume mountbouncer-state:/tmp/cs-abuseipdbbouncer-state:/data
New in v2.0METRICS_ADDR, CONFIG_FILE

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-abuseipdbDATA_DIR=/data
  2. Change - bouncer-state:/tmp/cs-abuseipdb- bouncer-state:/data
  3. Optionally add METRICS_ADDR=:9090 and expose port 9090

Tag summary

Content type

Image

Digest

sha256:08a2fc0c7

Size

387.6 kB

Last updated

4 months ago

docker pull developingchet/cs-abuseipdb-bouncer:sha256-ebda7712e9388fe57ac88ba0aabbc68fcc557c2125c98d486058e55f1b0626ba.att