ITGOIT - Wiki

Traefik-Crowdsec

If you use Cloudflare:
Make sure to set SSL/TLS to FULL

cd /home/myusername/docker
mkdir traefik-crowdsec && cd "$_"
mkdir traefik-data && cd "$_"
touch acme.json && chmod 600 acme.json

nano traefik.yml

global:
  checkNewVersion: true
  sendAnonymousUsage: false
serversTransport:
  insecureSkipVerify: true

entryPoints:
  # Not used in apps, but redirect everything from HTTP to HTTPS
  http:
    address: :80
    forwardedHeaders:
      trustedIPs: &trustedIps
        # Start of Clouflare public IP list for HTTP requests, remove this if you don't use it
        - 173.245.48.0/20
        - 103.21.244.0/22
        - 103.22.200.0/22
        - 103.31.4.0/22
        - 141.101.64.0/18
        - 108.162.192.0/18
        - 190.93.240.0/20
        - 188.114.96.0/20
        - 197.234.240.0/22
        - 198.41.128.0/17
        - 162.158.0.0/15
        - 104.16.0.0/12
        - 172.64.0.0/13
        - 131.0.72.0/22
        - 2400:cb00::/32
        - 2606:4700::/32
        - 2803:f800::/32
        - 2405:b500::/32
        - 2405:8100::/32
        - 2a06:98c0::/29
        - 2c0f:f248::/32
        # End of Cloudlare public IP list
    http:
      redirections:
        entryPoint:
          to: https
          scheme: https

  # HTTPS endpoint, with domain wildcard
  https:
    address: :443
    forwardedHeaders:
     # Reuse list of Cloudflare Trusted IP's above for HTTPS requests
      trustedIPs: *trustedIps
    http:
      tls:
        # Generate a wildcard domain certificate
        certResolver: cloudflare
        domains:
#          - main: "local.DOMAIN.COM" # uncomment to enable certs for internal domain DNS
#            sans:                   # uncomment to enable certs for internal domain DNS
#              - "*.local.DOMAIN.COM" # uncomment to enable certs for internal domain DNS
          - main: "DOMAIN.COM"
            sans:
              - "*.DOMAIN.COM"
      middlewares:
        - securityHeaders@file
        - crowdsec-bouncer@file
        - gzip@file
        - cloudflarewarp@file
        
  metrics:
      address: ":8083"

providers:
  providersThrottleDuration: 2s

  # File provider for connecting things that are outside of docker / defining middleware
  file:
    filename: /etc/traefik/fileConfig.yml
    watch: true

  # Docker provider for connecting all apps that are inside of the docker network
  docker:
    watch: true
    network: proxy # Add Your Docker Network Name Here
    # Default host rule to containername.domain.example
    defaultRule: "Host(`{{ index .Labels \"com.docker.compose.service\"}}.DOMAIN.COM`)"
    exposedByDefault: false
    endpoint: "unix:///var/run/docker.sock"

# Enable traefik ui
api:
  dashboard: false # Set to true to enable dashboard, not recommended for production
  insecure: false # Set to true to enable insecure dashboard, not recommended for production
  debug: false # Set to true to enable debug mode, not recommended for production

metrics:
  prometheus:
    addEntryPointsLabels: true
    addRoutersLabels: true
    addServicesLabels: true
    entryPoint: metrics

# :8083/ping  
ping:
  entryPoint: metrics


log:
  level: INFO # DEBUG, INFO, WARN, ERROR, FATAL, and PANIC
  filePath: "/logs/traefik.log"
  format: json
  
accessLog:
  filePath: "/var/log/crowdsec/traefik_access.log"
  bufferingSize: 50
  format: json
  filters:
    statusCodes:
      - "300-302"
      - "400-599"  # error-pages-mw range
    retryAttempts: true
    minDuration: "10ms"

# Use letsencrypt to generate ssl serficiates
certificatesResolvers:
  cloudflare:
    acme:
      email: [email protected]  # change to your provider account email address.
      storage: /etc/traefik/acme.json
      caServer: https://acme-v02.api.letsencrypt.org/directory # prod (default)
      dnsChallenge:
        provider: cloudflare
        resolvers:
          - "1.1.1.1:53"
          - "1.0.0.1:53"

# Plugins (optional)
experimental:
  plugins:
    cloudflarewarp:
      moduleName: "github.com/BetterCorp/cloudflarewarp"
      version: "v1.3.3"
    # CrowdSec AppSec WAF Plugin
    crowdsec-bouncer:
      moduleName: "github.com/maxlerebourg/crowdsec-bouncer-traefik-plugin"
      version: "v1.4.4"

nano fileConfig.yml

  middlewares:
    # Crowdsec bouncer & AppSec WAF
    crowdsec-bouncer:
      plugin:
        crowdsec-bouncer:
          enabled: true
          crowdsecMode: stream
          crowdsecLapiHost: crowdsec:8080
          crowdsecLapiScheme: http
          crowdsecLapiKey: "YOUR_SUPER_SECURE_CROWDSEC_BOUNCER_API_TOKEN" # Replace in configuration phase
          # AppSec WAF Settings
          crowdsecAppsecEnabled: true
          crowdsecAppsecHost: crowdsec:7422
          crowdsecAppsecFailureBlock: true
          crowdsecAppsecUnreachableBlock: true
cd ..
mkdir crowdsec-logs && cd "$_"
nano acquis.yaml
filenames:
  - /var/log/crowdsec/traefik.log
labels:
  type: traefik
---
filenames:
  - /var/log/auth.log
labels:
  type: syslog
cd ..
mkdir crowdsec-config && cd "$_"
touch acquis.yaml && chmod 600 acquis.yaml

nano appsec.yaml

This file configures the Web Application Firewall (WAF). It includes Layer 1 (Virtual Patching/Immediate Blocking) and Layer 2 (OWASP Core Rule Set Out-of-Band detection).

appsec_configs:
  - crowdsecurity/appsec-default # Virtual patching rules (in-band blocking)
  - crowdsecurity/crs            # OWASP CRS rules (out-of-band detection)
labels:
  type: appsec
listen_addr: 0.0.0.0:7422
source: appsec
cd ..

nano docker-compose.yml

services:
  crowdsec:
    image: crowdsecurity/crowdsec:latest
    container_name: traefik_crowdsec
    restart: unless-stopped
    environment:
      GID: "${GID-1000}"
      # Installs Linux, Traefik, WAF Virtual Patching, Generic Rules, and OWASP CRS
      COLLECTIONS: "crowdsecurity/linux crowdsecurity/traefik crowdsecurity/appsec-virtual-patching crowdsecurity/appsec-generic-rules crowdsecurity/appsec-crs"
    volumes:
      - ./crowdsec-config/acquis.yaml:/etc/crowdsec/acquis.yaml
      - ./crowdsec-config/appsec.yaml:/etc/crowdsec/acquis.d/appsec.yaml
      - ./crowdsec-config:/etc/crowdsec/
      - ./crowdsec-db:/var/lib/crowdsec/data/
    ports:
      - 6060:6060
    networks:
      - proxy 
    depends_on:
      - traefik

  traefik:
    image: traefik:latest
    container_name: traefik
    hostname: traefik
    restart: unless-stopped
    security_opt:
      - no-new-privileges:true
    ports:
      - 80:80 # local http
      - 443:443 # local https
    volumes:
      - /etc/localtime:/etc/localtime:ro
      - ./traefik-data:/etc/traefik
      - /var/run/docker.sock:/var/run/docker.sock 
    environment:
      CF_API_EMAIL: [email protected]
      CF_DNS_API_TOKEN: YOUR_SUPER_SECURE_CLOUDFLARE_API_TOKEN
    networks:
      - proxy 

  error-pages:
    image: ghcr.io/tarampampam/error-pages:latest 
    container_name: error-pages
    environment:
      TEMPLATE_NAME: connection  # Choose a template (e.g., l7, cats, ghost)
      SHOW_DETAILS: true  # Optional: Show request details in error pages
      SEND_SAME_HTTP_CODE: true  # Return the same HTTP status code as the error
    restart: unless-stopped
    depends_on:
      - traefik
    networks:
      - proxy

networks:
  proxy:
    driver: bridge
    external: true
docker compose up -d

Configure Traefik

CrowdSec API

  1. Run the following command to get the crowdsec bouncer API key:
docker exec traefik_crowdsec cscli bouncers add bouncer-traefik
  1. Insert the api key in your fileConfig.yml middleware under: crowdsecLapiKey: "YOUR_SUPER_SECURE_CROWDSEC_BOUNCER_API_TOKEN"

Cloudflare API

  1. To get your API token from cloudflare, visit https://dash.cloudflare.com/profile/api-tokens then choose Create Token.

Use the template Edit zone DNS.

Add the following settings

Then click Continue to Summary and then Create Token.

  1. Insert the token in the docker-compose.yml at YOUR_SUPER_SECURE_CLOUDFLARE_API_TOKEN
# Restart the stack to apply all API keys
docker compose down
docker compose up -d

Cloudflare DNS Setup

Visit the Cloudflare DNS records and use the following setup:

A        traefik        YOUR_PUBLIC_IP         Proxied  Auto

CNAME Records (pointing to Traefik domain):

CNAME    myservice      traefik.DOMAIN.COM     Proxied  Auto
CNAME    myservice02    traefik.DOMAIN.COM     Proxied  Auto

Testing WAF & Protection Layers

With the new AppSec WAF and CRS (Core Rule Set) running, your protection works in two layers:

Test 1: Immediate Blocking (Virtual Patching)

Tests the WAF's ability to instantly block known malicious files.

curl -I https://DOMAIN.COM/.env

Expected Result: You should be immediately blocked with an HTTP 403 Forbidden.

Test 2: Out-of-Band Detection (CRS & SQL Injection)

Tests the WAF's ability to analyze generic attacks silently and ban repeat offenders.

curl "https://DOMAIN.COM/?id=1'+OR+'1'='1"

Check if the WAF recorded the attack:

docker exec traefik_crowdsec cscli alerts list

Expected Result: You should see a new alert for your IP showing anomaly score out-of-band: sql_injection.

Test 3: Trigger an IP Ban via CRS

Simulate a persistent attacker to trigger the crowdsec-appsec-outofband scenario:

for i in {1..6}; do curl "https://DOMAIN.COM/?id=1'+OR+'1'='1"; done

Check the alerts again:

docker exec traefik_crowdsec cscli alerts list

Expected Result: You will now see a ban:1 decision. That IP is entirely blocked from accessing any service on your Traefik instance.

Manual IP Management

Ban an IP Manually:

docker exec traefik_crowdsec cscli decisions add --ip 192.168.123.123 --duration 24h --reason "web bruteforce"

List existing Bans:

docker exec traefik_crowdsec cscli decisions list

Whitelist / Remove an IP Ban:

docker exec traefik_crowdsec cscli decisions delete --ip 192.168.123.123

Configure CrowdSec with Gotify

cd /home/myusername/docker/traefik-crowdsec/crowdsec-config
nano profiles.yaml

Uncomment (remove the hashtag) the following section:

#notifications:
#   - http_default
cd notifications
nano http.yaml
type: http          # Don't change
name: http_default # Must match the registered plugin in the profile

# One of "trace", "debug", "info", "warn", "error", "off"
log_level: info

# group_wait:         # Time to wait collecting alerts before relaying a message to this plugin, eg "30s"
# group_threshold:    # Amount of alerts that triggers a message before <group_wait> has expired, eg "10"
# max_retry:          # Number of attempts to relay messages to plugins in case of error
# timeout:            # Time to wait for response from the plugin before considering the attempt a failure, eg "10s"

#-------------------------
# plugin-specific options

# The following template receives a list of models.Alert objects
# The output goes in the http request body
format: |
  {{ range . -}}
  {{ $alert := . -}}
  {
    "extras": {
      "client::display": {
      "contentType": "text/markdown"
    }
  },
  "priority": 3,
  {{range .Decisions -}}
  "title": "{{.Type }} {{ .Value }} for {{.Duration}}",
  "message": "{{.Scenario}}  \n\n[crowdsec cti](https://app.crowdsec.net/cti/{{.Value -}})  \n\n[shodan](https://shodan.io/host/{{.Value -}})"
  {{end -}}
  }
  {{ end -}}

# The plugin will make requests to this url, eg:  https://www.example.com/
url: http://IMPORT_GOTFIY_URL_HERE/message

# Any of the http verbs: "POST", "GET", "PUT"...
method: POST

headers:
  X-Gotify-Key: IMPORT_CROWDSEC_TOKEN_HERE
  Content-Type: application/json
# skip_tls_verification:  # true or false. Default is false
  1. Go to: Apps in Gotify
  2. Click on: Create Application
  3. Name it: CrowdSec
  4. Copy the token then edit http.yaml and replace the following:
docker restart traefik_crowdsec

The following command will send a test notification from CrowdSec to Gotify:

docker exec traefik_crowdsec cscli notifications test http_default

Next steps