Part 5 Implementing CrowdSec Captcha Protection in Pangolin

Part 5: Implementing CrowdSec Captcha Protection in Pangolin

1. Captcha Configuration Setup

1.1 Cloudflare Turnstile Setup

  1. Go to Cloudflare Dashboard > Turnstile
  2. Create a new site widget
  3. Configure the settings:
    • Widget Mode: Managed
    • Domain: pangolin.testing.hhf.technology
    • Widget Type: Non-Interactive

Save the following credentials:

  • Site Key: <your_turnstile_site_key>
  • Secret Key: <your_turnstile_secret_key>

1.2 Update CrowdSec Profiles

Update /config/crowdsec/profiles.yaml:

name: captcha_remediation
filters:
  - Alert.Remediation == true && Alert.GetScope() == "Ip" && Alert.GetScenario() contains "http"
decisions:
 - type: captcha
   duration: 4h
on_success: break

---
name: default_ip_remediation
filters:
 - Alert.Remediation == true && Alert.GetScope() == "Ip"
decisions:
 - type: ban
   duration: 4h
on_success: break

2. Traefik Middleware Configuration

2.1 Update Dynamic Config

Modify /config/traefik/dynamic_config.yml:

http:
  middlewares:
    crowdsec:
      plugin:
        crowdsec:
          enabled: true
          logLevel: INFO
          updateIntervalSeconds: 15
          defaultDecisionSeconds: 15
          crowdsecMode: live
          captchaProvider: turnstile
          captchaSiteKey: "<your_turnstile_site_key>"
          captchaSecretKey: "<your_turnstile_secret_key>"
          captchaGracePeriodSeconds: 1800
          captchaHTMLFilePath: "/etc/traefik/conf/captcha.html"

2.2 Create Captcha HTML Template

Create /config/traefik/conf/captcha.html:

<!DOCTYPE html>
<html>
<head>
    <title>Security Check</title>
    <meta charset="utf-8">
    <meta name="viewport" content="width=device-width, initial-scale=1">
    <script src="https://challenges.cloudflare.com/turnstile/v0/api.js" async defer></script>
    <style>
        body {
            font-family: -apple-system, system-ui, BlinkMacSystemFont;
            margin: 0;
            padding: 20px;
            display: flex;
            justify-content: center;
            align-items: center;
            min-height: 100vh;
            background-color: #f5f5f5;
        }
        .container {
            background-color: white;
            padding: 2rem;
            border-radius: 8px;
            box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
            max-width: 500px;
            width: 100%;
        }
        h1 {
            color: #333;
            margin-bottom: 1rem;
        }
        p {
            color: #666;
            line-height: 1.6;
        }
        .captcha-container {
            display: flex;
            justify-content: center;
            margin: 2rem 0;
        }
        .error {
            color: #dc3545;
            margin-top: 1rem;
        }
    </style>
</head>
<body>
    <div class="container">
        <h1>Security Check Required</h1>
        <p>Our system detected unusual traffic from your device. Please complete the security check below to continue.</p>
        
        <div class="captcha-container">
            <div class="cf-turnstile" 
                 data-sitekey="{{.SiteKey}}" 
                 data-callback="captchaCallback">
            </div>
        </div>
        
        <div id="error-message" class="error" style="display: none;">
            Verification failed. Please try again.
        </div>
    </div>

    <script>
        function captchaCallback(token) {
            window.location.href = window.location.href + 
                (window.location.search ? '&' : '?') + 
                'cf-turnstile-response=' + token;
        }
    </script>
</body>
</html>

3. Testing Captcha Integration

3.1 Manual Testing

# Add a test captcha decision
docker exec crowdsec cscli decisions add --ip 192.168.1.1 --type captcha -d 1h

# Verify decision was added
docker exec -it crowdsec cscli decisions list

# Test accessing the site from the IP
curl -v -H "X-Forwarded-For: 192.168.1.1" https://pangolin.testing.hhf.technology

3.2 Monitor Captcha Activity

# Monitor CrowdSec logs for captcha events
docker exec -it crowdsec tail -f /var/log/crowdsec.log | grep captcha

# Check Traefik logs for captcha middleware
docker compose logs traefik -f | grep captcha

# View metrics
curl http://localhost:6060/metrics | grep captcha

4. Captcha Customization

4.1 Custom Scenarios

Create /config/crowdsec/scenarios/custom-captcha.yaml:

type: leaky  # Changed from 'trigger'( was my error)—enables capacity + leakspeed for aggregation
name: custom-captcha-trigger
description: "Detect multiple unique 404s from a single IP (http scanner)"
filter: evt.Meta.log_type == 'traefik_access' && evt.Meta.http_status in ['404']  # Simplified; add '403','400' if needed like the example
groupby: evt.Meta.source_ip  # Fixed casing; partitions buckets per IP
distinct: evt.Meta.http_path  # NEW: From docs example—counts unique paths only (ignores repeats)
capacity: 10  # Overflow after 10 unique 404s
leakspeed: "10s"  # Fixed casing; leak 1 event every 10s (sliding window)
blackhole: 1m  # Silences this IP's bucket for 1m post-overflow (anti-spam)
labels:
  service: traefik  # Matches your setup
  type: captcha  # Custom for your bouncer
  remediation: true  # Signals: Ban/Remediate this IP (duration set in profiles)

4.2 Captcha Settings Optimization

Update the middleware configuration:

crowdsec:
  plugin:
    crowdsec:
      captchaGracePeriodSeconds: 3600
      captchaDisplayMode: "managed"
      captchaOnFailure: true
      captchaOnUnreachable: false

5. Advanced Configuration

5.1 IP Whitelisting for Captcha

Add trusted IPs to bypass captcha:

http:
  middlewares:
    crowdsec:
      plugin:
        crowdsec:
          clientTrustedIPs:
            - "10.0.0.0/8"
            - "172.16.0.0/12"
            - "192.168.0.0/16"
            - "100.89.137.0/20"

5.2 Custom Error Pages

Create custom error pages for different scenarios:

<!-- /config/traefik/conf/error.html -->
<!DOCTYPE html>
<html>
<head>
    <title>Access Denied</title>
    <style>
        /* Your custom styles */
    </style>
</head>
<body>
    <div class="container">
        <h1>Access Denied</h1>
        <p>{{.Message}}</p>
    </div>
</body>
</html>

6. Monitoring and Alerts

6.1 Captcha Metrics

Add custom metrics in Prometheus configuration:

metrics:
  - name: crowdsec_captcha_total
    help: Total number of captcha challenges served
    type: counter
    
  - name: crowdsec_captcha_success
    help: Number of successful captcha completions
    type: counter
    
  - name: crowdsec_captcha_failure
    help: Number of failed captcha attempts
    type: counter

6.2 Alert Configuration

Create /config/crowdsec/notifications/discord.yaml:

type: http
name: discord
log_level: info
format: |
  {
    "embeds": [
      {
        "title": "Pangolin Security Alert",
        "description": "{{range . -}}{{$alert := . -}}{{range .Decisions -}}IP {{.Value}} will get {{.Type}} for {{.Duration}} due to {{.Scenario}}{{end -}}{{end -}}",
        "color": 15158332
      }
    ]
  }
url: "https://discord.com/api/webhooks/"
method: POST
headers:
  Content-Type: application/json

7. Troubleshooting

7.1 Common Issues

  1. Captcha not appearing:
# Check if captcha decision exists
docker exec -it crowdsec cscli decisions list --type captcha

# Verify Traefik logs
docker compose logs traefik | grep captcha
  1. Turnstile integration issues:
# Verify Turnstile configuration
grep -r "captcha" /config/traefik/dynamic_config.yml

# Check HTML template
cat /config/traefik/conf/captcha.html

7.2 Debug Mode

Enable detailed logging:

# Enable debug logging in CrowdSec
docker exec -it crowdsec cscli config show --debug

# Enable debug in Traefik
sed -i 's/logLevel: INFO/logLevel: DEBUG/' /config/traefik/dynamic_config.yml

Next Steps

  1. Implementing CrowdSec AppSec WAF (Part 6)
  2. Configure custom scenarios and rules
  3. Set up monitoring dashboards
  4. Implement backup procedures

Part 6 Configuring CrowdSec Trusted IPs for Internal Pangolin Traffic - General - HHF Technology Forums

My site is refreshing every 2 seconds automatically and does not process the captcha, has something changed?

you will need to share your config files and use pastebin for it.

what files exactly? thanks

your dynamic_config, docker-compose, separate traefik logs and separate crowdsec logs and config/crowdsec/profiles.yaml

I didn’t get who calls the custom error page.? Where is it referenced? Thanks!

http:
  middlewares:
    my-error-pages:
      errors:
        status:
          - "400-599" # range of status codes to handle
        service: error-pages-service # name of the service serving error pages
        query: "/{status}.html" 


http:
  routers:
    my-app-router:
      # ... other router configuration
      middlewares:
        - my-error-pages@file # Reference the middleware by its name and provider

or
patbec/traefik-error-pages: Custom error pages for the Traefik reverse proxy.

1 Like

Understood!

By the way, trigger is not going to work in the custom scenario in crowdsec with capacity set to 10.

  • trigger scenarios fire immediately when a single log event matches the filter conditions.

  • They are stateless: every event is checked one-by-one, no accumulation or counting.

  • capacity is used to specify how many events are allowed in a certain time frame before an action is taken

I was getting this error all the time

trigger bucket must have 0 capacity" crowdsec | level=fatal msg="crowdsec init: while loading scenarios: scenario loading failed: loading of custom-captcha-trigger failed: invalid bucket from /etc/crowdsec/scenarios/custom-captcha.yaml: trigger bucket must have 0 capacity"

Need to use leaky

type: leaky
name: custom-captcha-trigger
description: "Trigger captcha for suspicious behavior"
filter: evt.Meta.log_type == 'traefik_access' && evt.Meta.http_status == '404'
groupby: evt.Meta.source_ip
capacity: 10
leakspeed: "10s"
blackhole: 1m
labels:
  service: traefik
  type: captcha
  remediation: true

1 Like

you are correct. it should be leaky not triggered. i have update the doc.