Sécurisation des entreprises françaises pendant Paris 2024 : Accédez à notre liste d'adresses IP !
protect tcp udp ports from ddos attacks

Protect TCP/UDP Ports Against DDoS Attacks with CrowdSec and Traefik Proxy

This is a guest post by community member Killian Stein.

Hello everyone — how you’re all doing alright!

I’m back on the CrowdSec Blog and today I'm going to show you how to secure the opening of a TCP/UDP port with Traefik Proxy and CrowdSec.

Last time around, I showed you how to enhance security for your Docker Compose with CrowdSec and Traefik Proxy. If you missed it, go check it out here.

In this tutorial, I’ll show you how to protect your services against DDoS attacks, but there's nothing to stop you from going further with your application's authentication logs.

Before getting technical, let's talk about theory.

Understanding the CrowdSec concepts

To put it simply, the Crowdsec Security Engine analyzes the logs in the acquired configuration files using parsers. Once the various parsers have extracted the information and tagged it, we'll have several types of scenarios at our disposal. Scenarios are actually buckets that group elements by tag (e.g., source IP).


Now, I told you there were several parsers because there are several levels of parsing.

Firstly, s00-Raw extracts each raw from the logs and applies a tag according to the type defined in the acquis.yaml configuration. It will perform other operations that you'll see during testing.

Next comes s01, whose role is to parse the log message, applying the GROK pattern to extract the data.

Finally, s02 will provide additional information on the extracted data (e.g., GEOIP).

Workflow source: CrowdSec documentation


Scenarios are a little simpler. In the S01-parse step, add a log_type label, which you’ll simply capture in your scenario and associate with it. There will then be several possible configurations, and here I'm going to give you a quick introduction to the leaky bucket, as that's the one I’m going to use for this tutorial.

Once the corresponding log_type and our source IP have been retrieved from the parser, we can configure a scenario to create a bucket for each source IP. This bucket will be able to hold 5 objects and I’ll indicate that it can remove one object every 10 seconds. So, if you follow the 6th object, an alert will be generated indicating the source IP.

Source: CrowdSec documentation

Now that we've got the big picture, how can you simplify your life?

Here are two useful links:

  • CrowdSec Hub: Make sure what you are trying to do doesn’t already exist, or you can start working on an existing file. I’ll give you an example of this during this tutorial.
  • https://playground.crowdsec.net/#/grok:The playground will help you validate your GROK pattern and check that all the data has been extracted.

Let’s go!

The architecture below represents what I’ll help you build in this tutorial.

Here, I’m not going to ban IPs at the Traefik level but rather use Linux's firewall bouncer to apply the decision. Bouncers — or Remediation Components — consume CrowdSec decisions. There are a bunch of different types of Remediation Components, but the Traefik plugin doesn't support CrowdSec middleware for TCP/UDP frames. Find more information in the Traefik documentation

First, I'll rent a server from OVH (Ubuntu 23.10 - UEFI) and install the Docker Engine.

Next, I'll use Docker Compose for my file and check that I can connect to MongoDB via the port in TCP and TLS. Here, I'm enabling Traefik's debug mode logs, as this is necessary to retrieve TCP/UDP connection logs. (Maybe a feature request to Traefik 🙂)

sudo docker network create proxy

Docker Compose file:

version: '3'


    image: crowdsecurity/crowdsec
    container_name: crowdsec
      PGID: "1000"
      COLLECTIONS: "crowdsecurity/traefik crowdsecurity/http-cve crowdsecurity/sshd"
      - "8080"
      - /var/log/crowdsec:/var/log/crowdsec:ro
      - /opt/crowdsec-db:/var/lib/crowdsec/data
      - /var/log/auth.log:/var/log/auth.log:ro
      - /var/log/syslog.log:/logs/syslog.log:ro
      - /opt/crowdsec:/etc/crowdsec
    restart: unless-stopped
      - proxy

    restart: unless-stopped
    image: traefik:latest
      - --api.insecure=true
      - --providers.docker=true ## Logs for debugging
      - --log.filePath=/var/log/crowdsec/traefik-tcpudp.log
      - --log.level=DEBUG # (Default: error) DEBUG, INFO, WARN, ERROR, FATAL, PANIC
      ## Logs for Crowdsec
      - --accessLog=true
      - --accessLog.filePath=/var/log/crowdsec/traefik.log
      - --accessLog.bufferingSize=100 # Configuring a buffer of 100 lines
      - --accessLog.filters.statusCodes=204-299,400-499,500-59
      - --entrypoints.web.address=:80
      - --entrypoints.websecure.address=:443
      - --certificatesresolvers.prodresolver.acme.email=example@email.com
      - --certificatesresolvers.prodresolver.acme.caserver=https://acme-v02.api.letsencrypt.org/directory
      - --certificatesresolvers.prodresolver.acme.keytype=RSA4096
      - --certificatesresolvers.prodresolver.acme.tlschallenge=true
      - --certificatesresolvers.prodresolver.acme.storage=/letsencrypt/acme.json
      - --entrypoints.tcp-mongo.address=:27017
      - --entrypoints.udp-mongo.address=:21116/udp
      - proxy
      - backend
      - target: 80
        published: 80
        protocol: tcp
        mode: host
      - target: 443
        published: 443
        protocol: tcp
        mode: host
      - target: 8080
        published: 8080
        protocol: tcp
        mode: host
      - target: 27017
        published: 27017
        protocol: tcp
        mode: host
      - target: 21116
        published: 21116
        protocol: udp
        mode: host
      - /var/log/crowdsec/:/var/log/crowdsec/
      - "./letsencrypt:/letsencrypt"
      - /var/run/docker.sock:/var/run/docker.sock:ro
      - ./tls.yaml:/etc/traefik/tls.yaml

    container_name: mongo
    image: mongo
      - ./data:/data
      - backend
      - 27017/tcp
      - 21116/udp
    restart: unless-stopped
      - traefik.enable=true
      - traefik.tcp.routers.mongo-tcp.entrypoints=tcp-mongo
      - traefik.tcp.routers.mongo-tcp.rule=HostSNI(`mongo.civ-it.fr`)
      - traefik.tcp.routers.mongo-tcp.tls=true
      - traefik.tcp.routers.mongo-tcp.tls.certresolver=prodresolver
      - traefik.tcp.services.mongo-tcp.loadbalancer.server.port=27017
      - traefik.udp.routers.mongo-udp.entrypoints=udp-mongo
      - traefik.udp.services.mongo-udp.loadbalancer.server.port=21116

    name: proxy
    external: true
    name: backend

This is what you’ll see in MongoDB Compass:

The certificate verification is:

openssl s_client -connect mongo.civ-it.fr:27017

Let’s check the UDP port.

sudo nmap -sU -v -p 21116

And now let’s check Traefik logs.

sudo cat /var/log/crowdsec/traefik-tcpudp.log


Creating the parser

You now have the logs you need to run your tests, so let's move on to creating the parser. I grab a few lines and put them in a test.log file, which I’ll use for my tests. Here's my test.log file:

First of all, I'd like to confirm that my GROK pattern extracts the data correctly. To create the pattern, you can use this link to find example patterns or use AI. Find more info on this process here.

Now let’s run to the playground!

As you can see, the data got normalized.  As I mentioned, I'm going to the CrowdSec hub to start with a working configuration.

I'll start from LaPresidente's file on the adguard parser. Once the file has been cloned and adapted for my use case, this is what it looks like:

## Passe à l'étape suivante une fois les données extraites
## EN: Jump to the next stage once the data has been extracted
onsuccess: next_stage
## Facilite le débogage
## EN: Enable debugging
debug: true
## Nom de la tâche
## EN: Task name
name: Aidalinfo/tcpudp-flood-traefik
## Description de la tâche
## EN: Task description
description: "Parse TCP/UDP traefik logs"
## filtre du log à traiter
## EN: Log filter to process
filter: "evt.Parsed.program == 'tcpudp-traefik'"
## Liste des nœuds
## EN: List of nodes
  - grok:
  ## Grok pattern for extract IP SOURCE and other informations on this message structure
      pattern: 'time="%{TIMESTAMP_ISO8601:time}" level=%{LOGLEVEL:level} msg="Handling TCP connection from %{IP:source_ip}:%{NUMBER:source_port} to %{IP:destination_ip}:%{NUMBER:destination_port}"'
      ## Apply pattern on for all message in logs
      apply_on: message
      ## Add meta value, this type is used by scenario
        - meta: log_type
          value: traefik_tcpudp
## UDP GROK pattern for extract IP SOURCE and other informations on this message structure
  - grok:
      pattern: 'time="%{TIMESTAMP_ISO8601:time}" level=%{LOGLEVEL:level} msg="Handling UDP stream from %{IP:source_ip}:%{NUMBER:source_port} to %{IP:destination_ip}:%{NUMBER:destination_port}"'
     ## Apply pattern on for all message in logs
      apply_on: message
      ## Add meta value, this type is used by scenario
        - meta: log_type
          value: traefik_tcpudp
## Pass Time and Source IP to other stages and scenarios.
    - meta: source_ip
      expression: "evt.Parsed.source_ip"
    - target: evt.StrTime
      expression: evt.Parsed.time

Now let's test the parser.

sudo nano /opt/crowdsec/parsers/s01-parse/parser-tcpupd-flood.yaml
sudo docker exec -it crowdsec cscli explain --file /var/log/crowdsec/test.log --type tcpudp-traefik -v

I'll let you analyze the output of the command for yourself. The -v argument will allow you to see any fields added or removed, as well as the data.

This enormously helps in understanding the path of our log.

Here are the results of the TCP line:

And the UDP line:

Creating the scenario

Let's move on to the scenario. I'll be using LaPresidente’s scenario as a starting point. 

Here's the adapted file:

type: leaky
name: traefik-udptcp-flood
description: "Detect TCP/UDP flood"
filter: "evt.Meta.log_type == 'traefik_tcpudp'"
groupby: "evt.Meta.source_ip"
capacity: 1000
leakspeed: "10s"
blackhole: 5m
  remediation: true
    - attack.T1498

I'll create a file in the scenarios folder and copy/paste the above code.

sudo nano /opt/crowdsec/scenarios/traefikflood.yaml

If you run the test again, you’ll see that it now passes into the scenario.

cscli explain --file /var/log/crowdsec/test.log --type tcpudp-traefik -v

Now that we've checked that the parsers and scenario are working, we'll add the Traefik logs to the CrowdSec acquired file.

sudo nano /opt/crowdsec/acquis.yaml

- /var/log/crowdsec/traefik-tcpudp.log
  type: tcpudp-traefik

sudo docker restart crowdsec

I’m going to generate a bouncer API key on the CrowdSec container, keeping it carefully to one side 😉

sudo docker exec crowdsec cscli bouncer add firewall-host

Later, I'll retrieve the IP of the CrowdSec container with the following command:

sudo docker inspect -f '{{range.NetworkSettings.Networks}}{{.IPAddress}}{{end}}' crowdsec

Installing the CrowdSec Firewall bouncer

Time to install the CrowdSec Firewall bouncer to ban the IP that's going to flood. You have a few options, the easiest one is to add CrowdSec source and apt install package. 

Note: Later on, to update the bouncer, only apt update is needed.

curl -s https://packagecloud.io/install/repositories/crowdsec/crowdsec/script.deb.sh | sudo bash

Install the firewall bouncer with apt install:

sudo apt install crowdsec-firewall-bouncer-nftables

You can find more details on this this process in the CrowdSec documentation here and here

For this tutorial, I'm going to the repository of CrowdSec firewall bouncer to download and install the package manually.

curl -L https://github.com/crowdsecurity/cs-firewall-bouncer/releases/download/v0.0.28/crowdsec-firewall-bouncer-linux-amd64.tgz -o crowdsec-firewall-bouncer.tgz

Then simply unzip and run ./install.sh and follow the guide.

tar xzvf crowdsec-firewall-bouncer.tgz
cd crowdsec-firewall-bouncer-v*/
sudo ./install.sh

The service is failing, that's normal! In the configuration file, enter CrowdSec's IP and API key.

sudo nano /etc/crowdsec/bouncers/crowdsec-firewall-bouncer.yaml

Modify the API_URL and API_KEY lines.

sudo systemctl restart crowdsec-firewall-bouncer.service
sudo systemctl status crowdsec-firewall-bouncer.service

Let's put it to the test with a small flood on the server with HPING3:

sudo hping3 --udp -p 21116 --flood

Ok let's check CrowdSec alerts.

Check with cscli metrics:

If you check the bouncer firewall logs, here’s what you’ll see:

And if you try to scan the port from the banned machine, you won't get a result if the port is open or not because the host is unreachable.

Perfect, isn't it? 

Let’s sum up

Now you know which path to take to create your parsers and scenarios. I also hope that this guide has helped you understand the general workflows and will help you protect your production or homelab projects!

If you need this parser/scenario, it is available directly from the CrowdSec Hub and on GitHub

And if you don’t want to leave your cscli console, you can directly access it with  cscli parsers install aidalinfo/tcpudp-flood-traefik and cscli scenarios install aidalinfo/tcpudp-flood-traefik.

Thanks for reading and see you soon!

About Killian Stein

As a young IT enthusiast and teaching enthusiast, Killian tries to demystify modern technologies.
He is a DevSecOps Engineer at Aidalinfo, where he is learning and consolidating his experience of the cloud and open source tools. If you liked Killian’s article and are curious to follow his next projects, don't hesitate to connect with him on LinkedIn. New projects are coming soon! 

No items found.