Skip to content

Conversation

@LaurenceJJones
Copy link
Member

@LaurenceJJones LaurenceJJones commented Sep 25, 2025

Add AppSec (WAF) Integration Support

This PR adds integration with CrowdSec's AppSec engine, enabling Web Application Firewall (WAF) functionality alongside existing remediation mechanisms (ban, captcha).

Feature Overview

The bouncer now forwards HTTP requests to the CrowdSec AppSec engine for additional validation. AppSec can override decisions made by LAPI, providing more granular WAF-based protection. The integration supports both global and per-host AppSec configuration.

Key Changes

AppSec Component (internal/appsec/root.go)

  • New AppSecClient with optimized HTTP transport (keep-alive, connection pooling)
  • ValidateRequest() forwards requests to AppSec engine with full request context
  • Supports per-host appsec_url and api_key configuration (with global fallback)
  • Processes AppSec responses and maps status codes to remediation actions
  • 5-second timeout with context support to prevent HAProxy connection resets
  • Fail-fast API key validation before processing headers
  • Explicit HTTP version conversion handling edge cases (e.g., "2" → "20")

Note: We should consider changing the underlying SPOE implementation since AppSec increases processing time and we hit timeouts quite easily.

SPOA Handler Integration (pkg/spoa/root.go)

  • Integrated AppSec validation into existing remediation flow
  • Simplified validateWithAppSec() function signature (removed unnecessary parameters)
  • Returns more restrictive remediation (prevents security downgrades)
  • Extracts all HTTP data from message in single pass
  • Only sends requests to AppSec if not already banned/captcha'd (unless always_send: true)
  • Handles HTTP header parsing (normalizes \r\n, skips malformed lines gracefully)
  • Global AppSec fallback when no host matched or host doesn't have AppSec configured

Host Manager (pkg/host/root.go)

  • Removed global AppSec configuration from host manager
  • Hosts now only use explicit AppSec overrides
  • Global AppSec handled entirely at SPOA level

Configuration (pkg/cfg/config.go)

  • Added global appsec_url configuration option
  • AppSec shares the same api_key as LAPI by default (can be overridden per-host)

Infrastructure

  • Vagrantfile: Complete testing environment with CrowdSec, AppSec collections, HAProxy, and Nginx (uses port 4241 for CI compatibility)
  • Docker Compose: Added AppSec collections and acquisition configuration
  • HAProxy configs: Added http-buffer-request option for body capture, increased SPOA timeout to 6s

Security Fixes

  • Fixed remediation downgrade vulnerability: validateWithAppSec() now correctly returns the more restrictive remediation, preventing security bypasses when AppSec returns Allow but current remediation is Ban/Captcha

Bug Fixes & Improvements

  • Fixed User-Agent extraction from HAProxy headers (handles \r\n line endings correctly)
  • Improved header parsing to skip malformed lines gracefully with debug logging
  • Optimized connection reuse via keep-alive and response body draining
  • Simplified header copying using maps.Clone()
  • Refactored code to reduce duplication and improve maintainability
  • Improved error messages to follow Go conventions
  • Cleaned up excessive debug logging

Configuration Example

# Global AppSec URL (optional)
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

Testing

The Vagrantfile provides a complete test environment with:

  • CrowdSec with AppSec collections installed
  • HAProxy with SPOA configured
  • Nginx backend server
  • Automated provisioning for end-to-end testing

Current hub results

-------------------------------------------------------
 Test                               Result  Assertions
-------------------------------------------------------
 generic-wordpress-uploads-php      ✅      0
 vpatch-CVE-2019-7276               ✅      0
 vpatch-CVE-2025-31324              ✅      0
 CVE-2023-0900                      ✅      0
 CVE-2023-22515                     ✅      0
 CVE-2023-28121                     ✅      0
 vpatch-CVE-2007-0885               ✅      0
 vpatch-CVE-2024-51977              ✅      0
 vpatch-CVE-2024-0204               ✅      0
 vpatch-CVE-2025-29927              ✅      0
 vpatch-CVE-2021-32478              ✅      0
 vpatch-CVE-2023-0297               ✅      0
 vpatch-CVE-2024-34102              ✅      0
 vpatch-CVE-2025-27222              ✅      0
 vpatch-CVE-2025-27223              ✅      0
 vpatch-CVE-2025-55748              ✅      0
 CVE-2019-12989                     ✅      0
 CVE-2023-6360                      ✅      0
 CVE-2023-6553                      ✅      0
 CVE-2023-6567                      ✅      0
 CVE-2023-7028                      ✅      0
 vpatch-CVE-2023-23063              ✅      0
 vpatch-CVE-2024-29973              ✅      0
 CVE-2022-22954                     ✅      0
 vpatch-CVE-2020-5902               ✅      0
 vpatch-laravel-debug-mode          ✅      0
 CVE-2021-3129                      ✅      0
 CVE-2023-23489                     ✅      0
 CVE-2023-34362                     ✅      0
 generic-freemarker-ssti-args       ✅      0
 vpatch-CVE-2018-13379              ✅      0
 vpatch-CVE-2019-1003030            ✅      0
 vpatch-CVE-2024-27564              ✅      0
 vpatch-CVE-2024-29824              ✅      0
 CVE-2022-44877                     ✅      0
 CVE-2023-0600                      ✅      0
 connectwise-auth-bypass            ✅      0
 vpatch-CVE-2021-26072              ✅      0
 vpatch-CVE-2024-0012               ✅      0
 vpatch-env-access                  ✅      0
 CVE-2023-38205                     ✅      0
 CVE-2023-46805                     ✅      0
 CVE-2024-1061                      ✅      0
 appsec-generic-test                ✅      0
 vpatch-CVE-2025-36604              ✅      0
 CVE-2022-22965                     ✅      0
 CVE-2023-1389                      ✅      0
 CVE-2023-24489                     ✅      0
 vpatch-CVE-2020-10987              ✅      0
 vpatch-CVE-2021-26086              ✅      0
 vpatch-CVE-2024-57727              ✅      0
 vpatch-CVE-2025-57819              ✅      0
 CVE-2022-46169                     ✅      0
 vpatch-CVE-2024-3272               ✅      0
 vpatch-CVE-2024-41713              ✅      0
 vpatch-CVE-2025-3248               ✅      0
 CVE-2021-22941                     ✅      0
 vpatch-CVE-2020-13640              ✅      0
 vpatch-CVE-2025-49132              ✅      0
 CVE-2023-35082                     ✅      0
 CVE-2024-29849                     ✅      0
 vpatch-CVE-2023-23488              ✅      0
 vpatch-CVE-2023-47218              ✅      0
 vpatch-CVE-2024-51567              ✅      0
 CVE-2024-1071                      ✅      0
 CVE-2025-64446                     ✅      0
 vpatch-CVE-2002-1131               ✅      0
 CVE-2022-27926                     ✅      0
 CVE-2023-20198                     ✅      0
 vpatch-CVE-2019-5418               ✅      0
 vpatch-CVE-2024-28987              ✅      0
 vpatch-CVE-2024-51378              ✅      0
 CVE-2020-11738                     ✅      0
 CVE-2023-23752                     ✅      0
 generic-wordpress-uploads-listing  ✅      0
 vpatch-CVE-2024-27956              ✅      0
 vpatch-CVE-2025-3605               ✅      0
 CVE-2018-10562                     ✅      0
 CVE-2024-3273                      ✅      0
 vpatch-CVE-2020-9054               ✅      0
 vpatch-CVE-2024-27348              ✅      0
 vpatch-CVE-2024-38816              ✅      0
 CVE-2023-50164                     ✅      0
 vpatch-CVE-2022-1388               ✅      0
 vpatch-CVE-2022-41082              ✅      0
 vpatch-CVE-2023-3169               ✅      0
 generic-no-user-agent              ✅      0
 symfony_profiler                   ✅      0
 vpatch-CVE-2021-43798              ✅      0
 vpatch-CVE-2024-27198              ✅      0
 CVE-2023-3519                      ✅      0
 vpatch-CVE-2020-8656               ✅      0
 vpatch-CVE-2021-26294              ✅      0
 vpatch-CVE-2024-32870              ✅      0
 vpatch-CVE-2024-52301              ✅      0
 CVE-2023-49070                     ✅      0
 cve-2023-42793                     ✅      0
 vpatch-CVE-2022-25488              ✅      0
 vpatch-CVE-2022-26134              ✅      0
 vpatch-CVE-2024-9465               ✅      0
 vpatch-CVE-2025-29306              ✅      0
 vpatch-CVE-2025-47812              ✅      0
 CVE-2022-35914                     ✅      0
 CVE-2023-4634                      ✅      0
 vpatch-CVE-2022-25322              ✅      0
 vpatch-CVE-2022-31499              ✅      0
 vpatch-CVE-2025-28367              ✅      0
 vpatch-CVE-2025-49113              ✅      0
 vpatch-CVE-2025-52488              ✅      0
 vpatch-CVE-2025-54249              ✅      0
 vpatch-CVE-2025-61882              ✅      0
 vpatch-git-config                  ✅      0
 CVE-2017-9841                      ✅      0
 generic-freemarker-ssti-body       ✅      0
 vpatch-CVE-2018-11511              ✅      0
 vpatch-CVE-2018-20062              ✅      0
 vpatch-CVE-2024-23897              ✅      0
 vpatch-CVE-2024-28255              ✅      0
 vpatch-CVE-2024-8963               ✅      0
 vpatch-CVE-2025-24893              ✅      0
 vpatch-CVE-2019-18952              ✅      0
 vpatch-CVE-2020-25078              ✅      0
 vpatch-CVE-2022-38627              ✅      0
 vpatch-CVE-2023-6000               ✅      0
 vpatch-CVE-2024-29028              ✅      0
 vpatch-CVE-2024-6205               ✅      0
 vpatch-CVE-2025-25257              ✅      0
 vpatch-CVE-2025-31161              ✅      0
 vpatch-CVE-2021-44529              ✅      0
 vpatch-CVE-2024-4577               ✅      0
 CVE-2023-33617                     ✅      0
 CVE-2023-6623                      ✅      0
 vpatch-CVE-2019-18935              ✅      0
 vpatch-CVE-2022-24086              ✅      0
 vpatch-CVE-2024-38856              ✅      0
 vpatch-CVE-2024-46506              ✅      0
 vpatch-CVE-2024-27954              ✅      0
 vpatch-CVE-2024-7593               ✅      0
 CVE-2020-17496                     ✅      0
 CVE-2023-22527                     ✅      0
 CVE-2024-22024                     ✅      0
 CVE-2025-2611                      ✅      0
 vpatch-CVE-2024-27292              ✅      0
 CVE-2023-35078                     ✅      0
 vpatch-CVE-2018-1207               ✅      0
 vpatch-CVE-2024-8190               ✅      0
 vpatch-CVE-2024-32113              ✅      0
 vpatch-CVE-2024-9474               ✅      0
 CVE-2023-2009                      ✅      0
 CVE-2024-1212                      ✅      0
-------------------------------------------------------

@LaurenceJJones LaurenceJJones linked an issue Sep 30, 2025 that may be closed by this pull request
@LaurenceJJones LaurenceJJones added this to the 0.3.0 milestone Oct 10, 2025
- Add AppSec validation to SPOA request handler
- Integrate AppSec component with global URL/API key config
- Add AppSec collections and acquisition setup for docker-compose
- Update HAProxy configs with http-buffer-request option
- Add Vagrantfile for testing environment
- Update bouncer config to support AppSec per-host configuration
- Fix readHeaders to properly handle CRLF line endings from HAProxy req.hdrs
- Extract User-Agent from parsed headers instead of separate SPOE message arg
- Only parse HTTP data when necessary (not for Allow/Ban unless AppSec always_send)
- Move debug logging in createAppSecRequest to show actual headers being sent
- Remove excessive logging from parseHTTPData function
- Clean up verbose debug logs for cookie clearing and redirects
- Check io.Copy return value (errcheck)
- Remove unused logger parameter from parseHTTPData (unparam)
- Remove duplicate User-Agent logging from debug log
- Access req fields directly in debug log instead of reading from headers
Track metrics in SPOA handler where we already have the IP as netip.Addr
type, avoiding unnecessary string-to-IP parsing. This is more efficient
and cleaner than parsing the IP from string in the AppSec component.
@LaurenceJJones LaurenceJJones changed the title WIP: AppSec integration implementation feat: AppSec integration Nov 22, 2025
Copilot finished reviewing on behalf of LaurenceJJones November 23, 2025 07:16
Copy link
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR adds comprehensive AppSec (Web Application Firewall) integration to the CrowdSec SPOA bouncer, enabling additional layer of protection beyond IP-based remediation. The implementation follows a fail-open pattern, optimizes for performance with connection pooling, and supports both global and per-host AppSec configuration.

Key Changes

  • AppSec Client Implementation: New HTTP client with optimized connection pooling (keep-alive, 5s timeout) that validates requests against CrowdSec's AppSec engine via custom X-Crowdsec-Appsec-* headers
  • SPOA Handler Integration: AppSec validation integrated into remediation flow with conditional execution (skips if already banned/captcha'd unless always_send: true), reuses parsed HTTP data from captcha handling
  • Infrastructure Setup: Complete test environments added via Vagrantfile and Docker Compose with AppSec collections, acquisition configs, and HAProxy body buffering enabled

Reviewed changes

Copilot reviewed 12 out of 12 changed files in this pull request and generated 9 comments.

Show a summary per file
File Description
internal/appsec/root.go New AppSec client with request validation, response processing, and HTTP transport configuration with connection pooling
pkg/spoa/root.go AppSec integration into HTTP request handling flow with data reuse, improved header parsing with HTTP request line detection, and metrics tracking
pkg/host/root.go Global AppSec configuration management with per-host override support
pkg/cfg/config.go Global appsec_url and api_key configuration fields
cmd/root.go Global AppSec configuration initialization in host manager
docker-compose.yaml, docker-compose.proxy-test.yaml AppSec collections installation and acquisition configuration mounting
docker/crowdsec/acquisitions/appsec.yaml AppSec component acquisition config listening on port 7422
docker/crowdsec/README.md Documentation for AppSec collections and manual installation
config/haproxy.cfg, config/haproxy-upstreamproxy.cfg Added http-buffer-request option and removed non-existent SPOA server
Vagrantfile Complete test environment provisioning with CrowdSec, AppSec, HAProxy, and Nginx

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

- Fix metrics call to use WithLabelValues instead of prometheus.Labels
- Improve HTTP request line detection with method validation
- Clarify remediation condition (>= Captcha instead of > Unknown)
- Add body length check before sending to AppSec
- Add debug logging for malformed headers
- Fix port mismatch in Vagrantfile (4241 -> 7422)
- Remove HTTP client timeout, rely on context timeout
Remove HTTP request line detection logic since HAProxy only sends headers, not the initial HTTP request line
…obal AppSec from host manager

- Simplified validateWithAppSec to return remediation instead of modifying pointer
- Removed req parameter - ban injection handled at call sites
- Removed hoststring parameter - extracted from message in extractAppSecData
- Removed httpData parameter - always parsed from message
- Created logger with host context for better logging
- Removed global AppSec config from host manager
- Hosts now only use explicit AppSec overrides
- Global AppSec handled entirely at SPOA level
- Fixed validateWithAppSec to return the more restrictive remediation
- Prevents security downgrade when AppSec returns Allow but current remediation is Ban/Captcha
- Now correctly compares and returns max(currentRemediation, appSecRemediation)
- Matches function documentation behavior
Copy link
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 12 out of 12 changed files in this pull request and generated 15 comments.


💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

- Move API key validation before cloning headers (fail-fast optimization)
- Fix error message capitalization to follow Go conventions
- Improve HTTP version conversion with explicit switch for edge cases
- Simplify response body handling - just discard without size checks
@LaurenceJJones LaurenceJJones requested a review from sabban December 3, 2025 11:05
@LaurenceJJones LaurenceJJones changed the title feat: AppSec integration feat(appsec): implementation and tests Dec 3, 2025
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Will this replace the HAProxy bouncer?

2 participants