Sign inSign up

developingchet/cs-abuseipdb-bouncer

By developingchet

•Updated 1 day ago

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

Image
Security
Monitoring & observability
0

10K+

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. Failed reports (network errors, 5xx, 429) are persisted and retried, honouring AbuseIPDB's Retry-After header.
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 MetricsEleven 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 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. The image HEALTHCHECK needs this server — disable the healthcheck too if you turn it off.
METRICS_ADDR:9090Address 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_LIMIT1000Daily report quota (free=1000, webmaster=3000, premium=50000).
ABUSEIPDB_PRECHECKfalsePre-check each IP with /check before reporting (skips whitelisted IPs). Uses one call from the separate /check daily quota 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_INTERVAL2sLAPI decision stream polling frequency (minimum: 2s).
LAPI_TIMEOUT10sHTTP timeout for LAPI requests (minimum: 200ms).
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).
RETRY_CHECK_INTERVAL30sHow often to check the retry queue for failed / rate-limited decisions ready to resend (minimum: 10s).
USAGE_METRICS_ENABLEDtrueEnable/disable periodic LAPI telemetry push (POST /v1/usage-metrics).
USAGE_METRICS_INTERVAL30mInterval for LAPI telemetry push (minimum: 10m).

⁠Observability

⁠Endpoints
EndpointDescription
GET /metricsPrometheus metrics in text exposition format
GET /healthzLiveness — 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 /readyzReadiness — as /healthz, plus 503 until the first successful LAPI pull
⁠Prometheus Metrics
MetricTypeLabelsDescription
cs_abuseipdb_decisions_processed_totalCounter—All decisions received from the LAPI stream
cs_abuseipdb_reports_sent_totalCounter—Successful reports sent to AbuseIPDB
cs_abuseipdb_decisions_skipped_totalCounterfilterDecisions dropped, by filter or outcome
cs_abuseipdb_api_errors_totalCountertypeAPI errors by type: rate_limit, auth, validation, network, timeout
cs_abuseipdb_quota_remainingGauge—Remaining daily report quota (resets at UTC midnight)
cs_abuseipdb_bbolt_db_size_bytesGauge—Size of state.db in bytes, updated by the janitor
cs_abuseipdb_retry_queue_sizeGauge—Decisions waiting in the persistent retry queue
cs_abuseipdb_retry_queue_enqueued_totalCounter—Decisions queued for retry
cs_abuseipdb_retry_attempts_totalCounter—Retry attempts submitted from the queue
cs_abuseipdb_last_lapi_pull_timestamp_secondsGauge—Unix time of the last successful LAPI poll
cs_abuseipdb_last_report_success_timestamp_secondsGauge—Unix time of the last successful AbuseIPDB report

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 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:

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.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

Tag summary

Content type

Image

Digest

sha256:237984da5…

Size

5.8 MB

Last updated

1 day ago

docker pull developingchet/cs-abuseipdb-bouncer