Skip to content

Releases: crowdsecurity/cs-haproxy-spoa-bouncer

v0.3.0

19 Jan 11:31
39bfe92

Choose a tag to compare

Breaking Changes

As outlined in the 0.1.0 release, every minor version before 1.0.0 may contain breaking changes.
Please review the sections below carefully before upgrading to avoid service interruptions.

1) SPOE execution now uses spoe-groups (explicit send-spoe-group)

What changed

SPOE execution is now driven by HAProxy spoe-groups and explicit calls to:

  • http-request send-spoe-group crowdsec <group-name>

This replaces relying on SPOE “event hooks” in the SPOE configuration.

Why this changed

This gives operators full control over when SPOE messages are sent, and allows you to set/override the source IP before the request is evaluated by the SPOA.

Who is impacted

All users upgrading to v0.3.0.

Required migration

  1. Update your HAProxy config to explicitly call the relevant SPOE group(s) in your frontend(s).

  2. Standardize on the upstream crowdsec.cfg and stop relying on custom SPOE config logic (for example forcing req.hdr_ip() usage).

Example

frontend test
    mode http
    bind *:9090

    filter spoe engine crowdsec config /etc/haproxy/crowdsec.cfg

    # Ensure HAProxy sees the correct client IP before calling the SPOE group
    http-request set-src hdr_ip(X-Real-IP) if { req.hdr(X-Real-IP) -m found }

    # Explicitly trigger the SPOE group at the right point in the request lifecycle
    http-request send-spoe-group crowdsec crowdsec-http-body if body_within_limit || !{ req.body_size -m found }

Operational note

You now control SPOE evaluation placement explicitly. If you previously relied on event hooks, ensure you add send-spoe-group to every relevant frontend and at the appropriate stage(s).


2) Captcha now requires a persistent signing_key (minimum 32 bytes)

What changed

Captcha moved away from purely server-side in-memory session state and now relies on signed tokens (JWT). As a result, a signing_key is now required.

Why this changed

Previously, restarting the SPOA invalidated in-memory captcha state, forcing users to re-complete captcha regardless of TTLs. Signed tokens allow captcha state to remain verifiable across restarts.

Who is impacted

All users with captcha enabled.

Required migration

Add signing_key to your captcha configuration (minimum 32 bytes):

hosts:
  - host: "*.example.com"
    captcha:
      site_key: "123"
      secret_key: "456"
      provider: "hcaptcha"
      timeout: 10          # HTTP client timeout in seconds (default: 5)
      pending_ttl: "30m"   # TTL for pending captcha tokens (default: 30m)
      passed_ttl: "24h"    # TTL for passed captcha tokens (default: 24h)
      signing_key: "your-32-byte-minimum-secret-key-here"  # REQUIRED in 0.3.0
  - host: "*"
    captcha:
      fallback_remediation: allow

Change redirection logic to use %[url] instead of %[var(txn.crowdsec.redirect)]

http-request redirect code 302 location %[url] if { var(txn.crowdsec.remediation) -m str "allow" } { var(txn.crowdsec.redirect) -m found }

note we found an issue for some users where without defining the following settings, the validation requests were being rejected

defaults
    option http-buffer-request

Multi-SPOA / HA setups

If you run multiple SPOA instances serving the same domains, use the same signing_key across all deployments so tokens validate consistently.

Key generation

openssl rand -hex 32

New Features

AppSec (WAF evaluation via SPOA)

The SPOA bouncer can now forward requests to CrowdSec AppSec for WAF evaluation, enabling HAProxy to provide:

  • IP-based remediation (ban/captcha/allow)
  • Request filtering via AppSec rules (WAF)

Docs:

Enable AppSec forwarding in the SPOA bouncer

After following the quickstart, configure the SPOA bouncer with a global AppSec endpoint and optional per-host overrides:

# Global AppSec URL (optional)
api_key: 12345
appsec_url: http://127.0.0.1:7422

hosts:
  - host: "*"
    appsec:
      always_send: false  # Only validate if not already banned/captcha'd
      # url: http://custom-appsec:7422  # Optional per-host override
      # api_key: custom-key             # Optional per-host override

Behavior note

With always_send: false, AppSec evaluation is skipped when a request already has a higher-priority remediation outcome (for example, ban or captcha). This reduces unnecessary WAF calls and latency.


AppSec limitations and required HAProxy settings

Because HAProxy is optimized for high-throughput proxying, request body inspection through SPOE/SPOP has hard constraints.

Body access is limited by tune.bufsize

You have limited access to request bodies via tune.bufsize. The value can only go up to 65536 (64KB), due to an underlying library limitation:

global
    log stdout format raw local0
    tune.bufsize 65536  # 64KB - increased for WAF body inspection

Request buffering must be enabled

To allow body inspection/forwarding, enable request buffering in defaults or the relevant frontend:

defaults
    option http-buffer-request

Choosing the correct SPOE group

Two SPOE groups are provided to make performance vs inspection explicit:

  • crowdsec-http-body

    • Sends request body (when available/within limits)
    • Required if you use captcha (captcha validation requires body-capable handling)
  • crowdsec-http-no-body

    • Avoids body forwarding for lower overhead
    • Suitable for “phase 1” WAF checks and IP remediation when you do not need body inspection

Recommended layered approach

If you want full-depth inspection:

  • Use HAProxy SPOA for IP remediation and phase 1 WAF signals (often crowdsec-http-no-body)
  • Use a downstream component (for example, the Nginx remediation component) for full body inspection and deeper WAF enforcement

This keeps HAProxy fast while enabling deeper inspection where it is most effective.


What’s Changed

v0.3.0-rc1

15 Jan 15:50
39bfe92

Choose a tag to compare

v0.3.0-rc1 Pre-release
Pre-release

Breaking Changes

As outlined in the 0.1.0 release, every minor version before 1.0.0 may contain breaking changes.
Please review the sections below carefully before upgrading to avoid service interruptions.

1) SPOE execution now uses spoe-groups (explicit send-spoe-group)

What changed

SPOE execution is now driven by HAProxy spoe-groups and explicit calls to:

  • http-request send-spoe-group crowdsec <group-name>

This replaces relying on SPOE “event hooks” in the SPOE configuration.

Why this changed

This gives operators full control over when SPOE messages are sent, and allows you to set/override the source IP before the request is evaluated by the SPOA.

Who is impacted

All users upgrading to v0.3.0.

Required migration

  1. Update your HAProxy config to explicitly call the relevant SPOE group(s) in your frontend(s).

  2. Standardize on the upstream crowdsec.cfg and stop relying on custom SPOE config logic (for example forcing req.hdr_ip() usage).

Example

frontend test
    mode http
    bind *:9090

    filter spoe engine crowdsec config /etc/haproxy/crowdsec.cfg

    # Ensure HAProxy sees the correct client IP before calling the SPOE group
    http-request set-src hdr_ip(X-Real-IP) if { req.hdr(X-Real-IP) -m found }

    # Explicitly trigger the SPOE group at the right point in the request lifecycle
    http-request send-spoe-group crowdsec crowdsec-http-body if body_within_limit || !{ req.body_size -m found }

Operational note

You now control SPOE evaluation placement explicitly. If you previously relied on event hooks, ensure you add send-spoe-group to every relevant frontend and at the appropriate stage(s).


2) Captcha now requires a persistent signing_key (minimum 32 bytes)

What changed

Captcha moved away from purely server-side in-memory session state and now relies on signed tokens (JWT). As a result, a signing_key is now required.

Why this changed

Previously, restarting the SPOA invalidated in-memory captcha state, forcing users to re-complete captcha regardless of TTLs. Signed tokens allow captcha state to remain verifiable across restarts.

Who is impacted

All users with captcha enabled.

Required migration

Add signing_key to your captcha configuration (minimum 32 bytes):

hosts:
  - host: "*.example.com"
    captcha:
      site_key: "123"
      secret_key: "456"
      provider: "hcaptcha"
      timeout: 10          # HTTP client timeout in seconds (default: 5)
      pending_ttl: "30m"   # TTL for pending captcha tokens (default: 30m)
      passed_ttl: "24h"    # TTL for passed captcha tokens (default: 24h)
      signing_key: "your-32-byte-minimum-secret-key-here"  # REQUIRED in 0.3.0
  - host: "*"
    captcha:
      fallback_remediation: allow

Change redirection logic to use %[url] instead of %[var(txn.crowdsec.redirect)]

http-request redirect code 302 location %[url] if { var(txn.crowdsec.remediation) -m str "allow" } { var(txn.crowdsec.redirect) -m found }

note we found an issue for some users where without defining the following settings, the validation requests were being rejected

defaults
    option http-buffer-request

Multi-SPOA / HA setups

If you run multiple SPOA instances serving the same domains, use the same signing_key across all deployments so tokens validate consistently.

Key generation

openssl rand -hex 32

New Features

AppSec (WAF evaluation via SPOA)

The SPOA bouncer can now forward requests to CrowdSec AppSec for WAF evaluation, enabling HAProxy to provide:

  • IP-based remediation (ban/captcha/allow)
  • Request filtering via AppSec rules (WAF)

Docs:

Enable AppSec forwarding in the SPOA bouncer

After following the quickstart, configure the SPOA bouncer with a global AppSec endpoint and optional per-host overrides:

# Global AppSec URL (optional)
api_key: 12345
appsec_url: http://127.0.0.1:7422

hosts:
  - host: "*"
    appsec:
      always_send: false  # Only validate if not already banned/captcha'd
      # url: http://custom-appsec:7422  # Optional per-host override
      # api_key: custom-key             # Optional per-host override

Behavior note

With always_send: false, AppSec evaluation is skipped when a request already has a higher-priority remediation outcome (for example, ban or captcha). This reduces unnecessary WAF calls and latency.


AppSec limitations and required HAProxy settings

Because HAProxy is optimized for high-throughput proxying, request body inspection through SPOE/SPOP has hard constraints.

Body access is limited by tune.bufsize

You have limited access to request bodies via tune.bufsize. The value can only go up to 65536 (64KB), due to an underlying library limitation:

global
    log stdout format raw local0
    tune.bufsize 65536  # 64KB - increased for WAF body inspection

Request buffering must be enabled

To allow body inspection/forwarding, enable request buffering in defaults or the relevant frontend:

defaults
    option http-buffer-request

Choosing the correct SPOE group

Two SPOE groups are provided to make performance vs inspection explicit:

  • crowdsec-http-body

    • Sends request body (when available/within limits)
    • Required if you use captcha (captcha validation requires body-capable handling)
  • crowdsec-http-no-body

    • Avoids body forwarding for lower overhead
    • Suitable for “phase 1” WAF checks and IP remediation when you do not need body inspection

Recommended layered approach

If you want full-depth inspection:

  • Use HAProxy SPOA for IP remediation and phase 1 WAF signals (often crowdsec-http-no-body)
  • Use a downstream component (for example, the Nginx remediation component) for full body inspection and deeper WAF enforcement

This keeps HAProxy fast while enabling deeper inspection where it is most effective.


What’s Changed

v0.2.1

03 Dec 15:44
489bc83

Choose a tag to compare

What's Changed

No breaking changes as minor patch version

Warning

We plan to release this around a CrowdSec release make sure to update either CrowdSec OR this remediation firstly, do not update both at the same time as the LAPI will be unavailable when the remediation comes up and will fail to connect unless retry_initial_connect configuration is set.

🚀 New Features

  • BART-Based Trie for Ranges – Replaced O(n) CIDR lookups with a hardware-accelerated path-compressed trie for faster decision matching when dealing with IP ranges. The trie now correctly implements longest prefix match (LPM) so more specific ranges override general ones. In the current hybrid design, BART is only used for range-based remediations, while single IP decisions stay on a simpler, fast path. (#66)
Explanation for BART

Previously, we stored decision ranges as a slice and checked them one by one:

// sudo code example of old approach: linear scan over a slice of CIDR ranges.
for _, cidr := range rangeSet {
    if cidr.Contains(ip) {
        return remediationFor(cidr)
    }
}

This was simple, but had two important drawbacks:

  1. Order-dependent (“first match wins”)
    The first matching CIDR in the slice was used, even if a more specific range existed later. That makes decision order matter and can give surprising results.

  2. O(n) lookup time
    In the worst case, every lookup walks the entire slice. As the number of ranges n grows, misses and broad scans become slower.

With BART we now use a path-compressed trie for range storage and lookups. Instead of scanning all ranges, lookups follow the bits of the IP address through the trie:

  • Lookup complexity is O(k) where k is the number of bits in the address (at most 32 for IPv4, 128 for IPv6).
  • The number of steps depends on the address length, not on how many prefixes you have.
  • The trie naturally performs longest-prefix match, so more specific ranges always win over broader ones, regardless of insertion order. EG: 192.168.0.0/16 vs 192.168.1.0/24 the latter wins for 192.168.1.160.

In practice: even with hundreds or thousands of ranges, lookups stay fast and deterministic.


  • pprof Debug Endpoint – Added an optional HTTP endpoint exposing Go pprof handlers, making it easier to capture CPU/heap profiles from live instances during load tests and production debugging. (#127)

⚡ Performance & Architecture Improvements

  • Global Session Management – Consolidated per-host session managers into a single global manager, reducing goroutine overhead and preparing the ground for future AppSec integration. (#113)

  • Simplified Signal Handling – Improved shutdown reliability using Go’s native signal.NotifyContext, ensuring graceful termination on SIGTERM/SIGINT. (#103)

  • HAProxy SPOE Library Upgrade – Upgraded to v1.0.7 with automatic workgroup handling, simplifying code and improving reliability. (#119)

  • Host Runtime Cleanup – Removed unused runtime operations in the host management logic, simplifying the code and shaving off minor overhead. (#124)

  • Hybrid IP Storage & Parallel Batch Processing – Reworked the dataset backend to split storage between single IPs and ranges. BART is now dedicated to CIDR ranges for true LPM, while single IP decisions are handled via a straightforward map-based fast path. Batch updates are processed in parallel to reduce lock contention and speed up large decision syncs. (#128)

  • Prometheus Metrics Allocation – Switched to WithLabelValues for metric labelling to avoid per-call map allocations in hot paths, reducing overhead in high-traffic environments. (#130)

🔧 Bug Fixes

  • Geo Database Initialization – Fixed database loading issues and improved error handling for missing or invalid GeoIP databases. (#121)

  • Dataset Stability – Fixed a potential nil map panic in dataset handling and removed redundant Clone calls, improving safety and reducing unnecessary allocations in hot paths. (#125)

  • Decision Stream GC Improvements – Broke long-lived string references to DecisionsStreamResponse payloads so large updates can be garbage-collected promptly, reducing memory pressure during intensive syncs. (#126)

📦 Dependencies & Maintenance

  • GeoIP2 Library Upgrade – Updated to v2.0.0 with modern netip.Addr support, improving performance and type safety. (#118)

  • Development Tools – Added .vagrant/ to .gitignore for cleaner development workflows. (#117)

Full Changelog: v0.2.0...v0.2.1

v0.2.1-rc3

02 Dec 16:20
489bc83

Choose a tag to compare

v0.2.1-rc3 Pre-release
Pre-release

What's Changed

🚀 New Features

  • BART-Based Trie for Ranges – Replaced O(n) CIDR lookups with a hardware-accelerated path-compressed trie for faster decision matching when dealing with IP ranges. The trie now correctly implements longest prefix match (LPM) so more specific ranges override general ones. In the current hybrid design, BART is only used for range-based remediations, while single IP decisions stay on a simpler, fast path. (#66)

  • pprof Debug Endpoint – Added an optional HTTP endpoint exposing Go pprof handlers, making it easier to capture CPU/heap profiles from live instances during load tests and production debugging. (#127)

⚡ Performance & Architecture Improvements

  • Global Session Management – Consolidated per-host session managers into a single global manager, reducing goroutine overhead and preparing the ground for future AppSec integration. (#113)

  • Simplified Signal Handling – Improved shutdown reliability using Go’s native signal.NotifyContext, ensuring graceful termination on SIGTERM/SIGINT. (#103)

  • HAProxy SPOE Library Upgrade – Upgraded to v1.0.7 with automatic workgroup handling, simplifying code and improving reliability. (#119)

  • Host Runtime Cleanup – Removed unused runtime operations in the host management logic, simplifying the code and shaving off minor overhead. (#124)

  • Hybrid IP Storage & Parallel Batch Processing – Reworked the dataset backend to split storage between single IPs and ranges. BART is now dedicated to CIDR ranges for true LPM, while single IP decisions are handled via a straightforward map-based fast path. Batch updates are processed in parallel to reduce lock contention and speed up large decision syncs. (#128)

  • Prometheus Metrics Allocation – Switched to WithLabelValues for metric labelling to avoid per-call map allocations in hot paths, reducing overhead in high-traffic environments. (#130)

🔧 Bug Fixes

  • Geo Database Initialization – Fixed database loading issues and improved error handling for missing or invalid GeoIP databases. (#121)

  • Dataset Stability – Fixed a potential nil map panic in dataset handling and removed redundant Clone calls, improving safety and reducing unnecessary allocations in hot paths. (#125)

  • Decision Stream GC Improvements – Broke long-lived string references to DecisionsStreamResponse payloads so large updates can be garbage-collected promptly, reducing memory pressure during intensive syncs. (#126)

📦 Dependencies & Maintenance

  • GeoIP2 Library Upgrade – Updated to v2.0.0 with modern netip.Addr support, improving performance and type safety. (#118)

  • Development Tools – Added .vagrant/ to .gitignore for cleaner development workflows. (#117)

Full Changelog: v0.2.0...v0.2.1-rc3

v0.2.1-rc2

26 Nov 16:12
ec203a7

Choose a tag to compare

v0.2.1-rc2 Pre-release
Pre-release

What's Changed

🚀 New Features

  • BART-Based Trie Implementation – Replaced O(n) CIDR lookups with a hardware-accelerated path-compressed trie for faster decision matching, especially with large numbers of decisions. Now correctly implements longest prefix match (LPM) so more specific rules override general ones. (#66)

⚡ Performance & Architecture Improvements

  • Global Session Management – Consolidated per-host session managers into a single global manager, reducing goroutine overhead and preparing for future AppSec integration. (#113)

  • Simplified Signal Handling – Improved shutdown reliability using Go's native signal.NotifyContext, ensuring graceful termination on SIGTERM/SIGINT. (#103)

  • HAProxy SPOE Library Upgrade – Upgraded to v1.0.7 with automatic workgroup handling, simplifying code and improving reliability. (#119)

  • Host Runtime Cleanup – Removed unused runtime operations in the host management logic, simplifying the code and shaving off minor overhead. (#124)

🔧 Bug Fixes

  • Geo Database Initialization – Fixed database loading issues and improved error handling for missing or invalid GeoIP databases. (#121)

  • Dataset Stability – Fixed a potential nil map panic in dataset handling and removed redundant Clone calls, improving safety and reducing unnecessary allocations in hot paths. (#125)

📦 Dependencies & Maintenance

  • GeoIP2 Library Upgrade – Updated to v2.0.0 with modern netip.Addr support, improving performance and type safety. (#118)

  • Development Tools – Added .vagrant/ to .gitignore for cleaner development workflows. (#117)

Full Changelog: v0.2.0...v0.2.1-rc2

v0.2.1-rc1

25 Nov 16:17
4e2cef1

Choose a tag to compare

v0.2.1-rc1 Pre-release
Pre-release

What's Changed

🚀 New Features

  • BART-Based Trie Implementation - Replaced O(n) CIDR lookups with a hardware-accelerated path-compressed trie for faster decision matching, especially with large numbers of decisions. Now correctly implements longest prefix match (LPM) so more specific rules override general ones. (#66)

⚡ Performance & Architecture Improvements

  • Global Session Management - Consolidated per-host session managers into a single global manager, reducing goroutine overhead and preparing for future AppSec integration. (#113)

  • Simplified Signal Handling - Improved shutdown reliability using Go's native signal.NotifyContext, ensuring graceful termination on SIGTERM/SIGINT. (#103)

  • HAProxy SPOE Library Upgrade - Upgraded to v1.0.7 with automatic workgroup handling, simplifying code and improving reliability. (#119)

🔧 Bug Fixes

  • Geo Database Initialization - Fixed database loading issues and improved error handling for missing or invalid GeoIP databases. (#121)

📦 Dependencies & Maintenance

  • GeoIP2 Library Upgrade - Updated to v2.0.0 with modern netip.Addr support, improving performance and type safety. (#118)

  • Development Tools - Added .vagrant/ to .gitignore for cleaner development workflows. (#117)

Full Changelog: v0.2.0...v0.2.1-rc1

v0.2.0

18 Nov 16:17
10eec3b

Choose a tag to compare

Breaking Changes

As outlined in the 0.1.0 release, every minor version before 1.0.0 may contain breaking changes.
Please review the sections below carefully before upgrading to avoid service interruptions.


TL;DR (Key Changes)

  • Parent/worker model removed – the SPOA now runs as a single process.
  • workers, worker_user, worker_group options removed – replaced by listen_tcp / listen_unix.
  • admin_socket removed – the setting is now ignored and can be deleted.
  • Service now runs fully as crowdsec-spoa user – config/log file permissions must allow this.
  • Docker image now runs as crowdsec-spoa.
  • Default log directory moved to /var/log/crowdsec-spoa/ (config change required).

Inner Architecture

In short:
We removed the parent/worker architecture and now run a simpler, single-process model.

Background

Previously:

  • Multiple workers handled incoming SPOE messages from HAProxy.

  • Each worker communicated with a parent process over a Unix socket to fetch:

    • State
    • Decisions
    • Host configuration (e.g. ban / captcha)

This design existed to support multiple SPOA listeners from a single process.

In practice, we found that users almost never configured more than one SPOA listener.
The added complexity:

  • Slowed down feature development,
  • Increased code debt,
  • Made onboarding new contributors harder.

As a result, we have simplified the architecture to a single process that directly handles SPOE messages.

Configuration Changes

We have removed the following YAML options:

  • workers
  • worker_user
  • worker_group

These are replaced with:

listen_tcp: 127.0.0.1:9090
listen_unix: /path/to/unix.sock

Recommendation:
Add these keys to your configuration before upgrading to avoid startup failures.


Admin Socket

Because we no longer support multiple SPOA listeners, the admin socket no longer provides value.

  • The following option has been removed and is now ignored:
admin_socket: /path/to/admin.sock

You can safely remove this key from your configuration.

We know users still need a way to reload the SPOA without dropping in-flight messages from HAProxy.
In a future release, we plan to add a proper systemctl reload hook to handle this cleanly.


Process Owner (systemd Unit)

With the removal of the parent/worker model and the admin socket, the systemd unit now runs entirely as the crowdsec-spoa user.

This improves separation between service accounts and root, but it also means:

  • All files needed by the SPOA must be readable by the crowdsec-spoa group.

For example:

chown root:crowdsec-spoa /etc/crowdsec/bouncers/crowdsec-spoa-bouncer.yaml
chmod 640 /etc/crowdsec/bouncers/crowdsec-spoa-bouncer.yaml

If you have created .local variants of these configuration files, please run equivalent commands for those files as well.

Note:
The DEB/RPM packages are patched to adjust these permissions on upgrade.
If you encounter issues, manually validate permissions/ownership and open an issue on this repository if problems persist.


Dockerfile

The Docker image has also been updated to run as the crowdsec-spoa user.

If you are building the container locally and mounting configuration files, ensure that:

  • The mounted configuration is readable by crowdsec-spoa.

Log Files

Since the systemd unit now runs as crowdsec-spoa, it can no longer write to the root-owned default /var/log directly.

The unit is configured to create a dedicated directory:

/var/log/crowdsec-spoa

Please update your YAML configuration accordingly:

log_dir: /var/log/crowdsec-spoa/

Note:
The DEB/RPM packages may not clean up old log files in previous locations.
After the upgrade, you may want to manually remove any obsolete log files or directories.

v0.2.0-rc5

18 Nov 15:38
10eec3b

Choose a tag to compare

v0.2.0-rc5 Pre-release
Pre-release

Breaking Changes

As outlined in the 0.1.0 release, every minor version before 1.0.0 may contain breaking changes.
Please review the sections below carefully before upgrading to avoid service interruptions.


TL;DR (Key Changes)

  • Parent/worker model removed – the SPOA now runs as a single process.
  • workers, worker_user, worker_group options removed – replaced by listen_tcp / listen_unix.
  • admin_socket removed – the setting is now ignored and can be deleted.
  • Service now runs fully as crowdsec-spoa user – config/log file permissions must allow this.
  • Docker image now runs as crowdsec-spoa.
  • Default log directory moved to /var/log/crowdsec-spoa/ (config change required).

Inner Architecture

In short:
We removed the parent/worker architecture and now run a simpler, single-process model.

Background

Previously:

  • Multiple workers handled incoming SPOE messages from HAProxy.

  • Each worker communicated with a parent process over a Unix socket to fetch:

    • State
    • Decisions
    • Host configuration (e.g. ban / captcha)

This design existed to support multiple SPOA listeners from a single process.

In practice, we found that users almost never configured more than one SPOA listener.
The added complexity:

  • Slowed down feature development,
  • Increased code debt,
  • Made onboarding new contributors harder.

As a result, we have simplified the architecture to a single process that directly handles SPOE messages.

Configuration Changes

We have removed the following YAML options:

  • workers
  • worker_user
  • worker_group

These are replaced with:

listen_tcp: 127.0.0.1:9090
listen_unix: /path/to/unix.sock

Recommendation:
Add these keys to your configuration before upgrading to avoid startup failures.


Admin Socket

Because we no longer support multiple SPOA listeners, the admin socket no longer provides value.

  • The following option has been removed and is now ignored:
admin_socket: /path/to/admin.sock

You can safely remove this key from your configuration.

We know users still need a way to reload the SPOA without dropping in-flight messages from HAProxy.
In a future release, we plan to add a proper systemctl reload hook to handle this cleanly.


Process Owner (systemd Unit)

With the removal of the parent/worker model and the admin socket, the systemd unit now runs entirely as the crowdsec-spoa user.

This improves separation between service accounts and root, but it also means:

  • All files needed by the SPOA must be readable by the crowdsec-spoa group.

For example:

chown root:crowdsec-spoa /etc/crowdsec/bouncers/crowdsec-spoa-bouncer.yaml
chmod 640 /etc/crowdsec/bouncers/crowdsec-spoa-bouncer.yaml

If you have created .local variants of these configuration files, please run equivalent commands for those files as well.

Note:
The DEB/RPM packages are patched to adjust these permissions on upgrade.
If you encounter issues, manually validate permissions/ownership and open an issue on this repository if problems persist.


Dockerfile

The Docker image has also been updated to run as the crowdsec-spoa user.

If you are building the container locally and mounting configuration files, ensure that:

  • The mounted configuration is readable by crowdsec-spoa.

Log Files

Since the systemd unit now runs as crowdsec-spoa, it can no longer write to the root-owned default /var/log directly.

The unit is configured to create a dedicated directory:

/var/log/crowdsec-spoa

Please update your YAML configuration accordingly:

log_dir: /var/log/crowdsec-spoa/

Note:
The DEB/RPM packages may not clean up old log files in previous locations.
After the upgrade, you may want to manually remove any obsolete log files or directories.

v0.2.0-rc4

17 Nov 16:03
d38678c

Choose a tag to compare

v0.2.0-rc4 Pre-release
Pre-release

Breaking Changes

As outlined in the 0.1.0 release, every minor version before 1.0.0 may contain breaking changes.
Please review the sections below carefully before upgrading to avoid service interruptions.


TL;DR (Key Changes)

  • Parent/worker model removed – the SPOA now runs as a single process.
  • workers, worker_user, worker_group options removed – replaced by listen_tcp / listen_unix.
  • admin_socket removed – the setting is now ignored and can be deleted.
  • Service now runs fully as crowdsec-spoa user – config/log file permissions must allow this.
  • Docker image now runs as crowdsec-spoa.
  • Default log directory moved to /var/log/crowdsec-spoa/ (config change required).

Inner Architecture

In short:
We removed the parent/worker architecture and now run a simpler, single-process model.

Background

Previously:

  • Multiple workers handled incoming SPOE messages from HAProxy.

  • Each worker communicated with a parent process over a Unix socket to fetch:

    • State
    • Decisions
    • Host configuration (e.g. ban / captcha)

This design existed to support multiple SPOA listeners from a single process.

In practice, we found that users almost never configured more than one SPOA listener.
The added complexity:

  • Slowed down feature development,
  • Increased code debt,
  • Made onboarding new contributors harder.

As a result, we have simplified the architecture to a single process that directly handles SPOE messages.

Configuration Changes

We have removed the following YAML options:

  • workers
  • worker_user
  • worker_group

These are replaced with:

listen_tcp: 127.0.0.1:9090
listen_unix: /path/to/unix.sock

Recommendation:
Add these keys to your configuration before upgrading to avoid startup failures.


Admin Socket

Because we no longer support multiple SPOA listeners, the admin socket no longer provides value.

  • The following option has been removed and is now ignored:
admin_socket: /path/to/admin.sock

You can safely remove this key from your configuration.

We know users still need a way to reload the SPOA without dropping in-flight messages from HAProxy.
In a future release, we plan to add a proper systemctl reload hook to handle this cleanly.


Process Owner (systemd Unit)

With the removal of the parent/worker model and the admin socket, the systemd unit now runs entirely as the crowdsec-spoa user.

This improves separation between service accounts and root, but it also means:

  • All files needed by the SPOA must be readable by the crowdsec-spoa group.

For example:

chown root:crowdsec-spoa /etc/crowdsec/bouncers/crowdsec-spoa-bouncer.yaml
chmod 640 /etc/crowdsec/bouncers/crowdsec-spoa-bouncer.yaml

If you have created .local variants of these configuration files, please run equivalent commands for those files as well.

Note:
The DEB/RPM packages are patched to adjust these permissions on upgrade.
If you encounter issues, manually validate permissions/ownership and open an issue on this repository if problems persist.


Dockerfile

The Docker image has also been updated to run as the crowdsec-spoa user.

If you are building the container locally and mounting configuration files, ensure that:

  • The mounted configuration is readable by crowdsec-spoa.

Log Files

Since the systemd unit now runs as crowdsec-spoa, it can no longer write to the root-owned default /var/log directly.

The unit is configured to create a dedicated directory:

/var/log/crowdsec-spoa

Please update your YAML configuration accordingly:

log_dir: /var/log/crowdsec-spoa/

Note:
The DEB/RPM packages may not clean up old log files in previous locations.
After the upgrade, you may want to manually remove any obsolete log files or directories.

v0.2.0-rc3

17 Nov 15:03
aa4d984

Choose a tag to compare

v0.2.0-rc3 Pre-release
Pre-release

Breaking Changes

As outlined in the 0.1.0 release, every minor version before 1.0.0 may contain breaking changes.
Please review the sections below carefully before upgrading to avoid service interruptions.


TL;DR (Key Changes)

  • Parent/worker model removed – the SPOA now runs as a single process.
  • workers, worker_user, worker_group options removed – replaced by listen_tcp / listen_unix.
  • admin_socket removed – the setting is now ignored and can be deleted.
  • Service now runs fully as crowdsec-spoa user – config/log file permissions must allow this.
  • Docker image now runs as crowdsec-spoa.
  • Default log directory moved to /var/log/crowdsec-spoa/ (config change required).

Inner Architecture

In short:
We removed the parent/worker architecture and now run a simpler, single-process model.

Background

Previously:

  • Multiple workers handled incoming SPOE messages from HAProxy.

  • Each worker communicated with a parent process over a Unix socket to fetch:

    • State
    • Decisions
    • Host configuration (e.g. ban / captcha)

This design existed to support multiple SPOA listeners from a single process.

In practice, we found that users almost never configured more than one SPOA listener.
The added complexity:

  • Slowed down feature development,
  • Increased code debt,
  • Made onboarding new contributors harder.

As a result, we have simplified the architecture to a single process that directly handles SPOE messages.

Configuration Changes

We have removed the following YAML options:

  • workers
  • worker_user
  • worker_group

These are replaced with:

listen_tcp: 127.0.0.1:9090
listen_unix: /path/to/unix.sock

Recommendation:
Add these keys to your configuration before upgrading to avoid startup failures.


Admin Socket

Because we no longer support multiple SPOA listeners, the admin socket no longer provides value.

  • The following option has been removed and is now ignored:
admin_socket: /path/to/admin.sock

You can safely remove this key from your configuration.

We know users still need a way to reload the SPOA without dropping in-flight messages from HAProxy.
In a future release, we plan to add a proper systemctl reload hook to handle this cleanly.


Process Owner (systemd Unit)

With the removal of the parent/worker model and the admin socket, the systemd unit now runs entirely as the crowdsec-spoa user.

This improves separation between service accounts and root, but it also means:

  • All files needed by the SPOA must be readable by the crowdsec-spoa group.

For example:

chown root:crowdsec-spoa /etc/crowdsec/bouncers/crowdsec-spoa-bouncer.yaml
chmod 640 /etc/crowdsec/bouncers/crowdsec-spoa-bouncer.yaml

If you have created .local variants of these configuration files, please run equivalent commands for those files as well.

Note:
The DEB/RPM packages are patched to adjust these permissions on upgrade.
If you encounter issues, manually validate permissions/ownership and open an issue on this repository if problems persist.


Dockerfile

The Docker image has also been updated to run as the crowdsec-spoa user.

If you are building the container locally and mounting configuration files, ensure that:

  • The mounted configuration is readable by crowdsec-spoa.

Log Files

Since the systemd unit now runs as crowdsec-spoa, it can no longer write to the root-owned default /var/log directly.

The unit is configured to create a dedicated directory:

/var/log/crowdsec-spoa

Please update your YAML configuration accordingly:

log_dir: /var/log/crowdsec-spoa/

Note:
The DEB/RPM packages may not clean up old log files in previous locations.
After the upgrade, you may want to manually remove any obsolete log files or directories.