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).
Parsers
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).
Scenarios
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.
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'
services:
crowdsec:
image: crowdsecurity/crowdsec
container_name: crowdsec
environment:
PGID: "1000"
COLLECTIONS: "crowdsecurity/traefik crowdsecurity/http-cve crowdsecurity/sshd"
expose:
- "8080"
volumes:
- /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
networks:
- proxy
traefik:
restart: unless-stopped
image: traefik:latest
command:
- --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
networks:
- proxy
- backend
ports:
- 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
volumes:
- /var/log/crowdsec/:/var/log/crowdsec/
- "./letsencrypt:/letsencrypt"
- /var/run/docker.sock:/var/run/docker.sock:ro
- ./tls.yaml:/etc/traefik/tls.yaml
mongo:
container_name: mongo
image: mongo
environment:
MONGO_INITDB_ROOT_USERNAME: root
MONGO_INITDB_ROOT_PASSWORD: example
volumes:
- ./data:/data
networks:
- backend
expose:
- 27017/tcp
- 21116/udp
restart: unless-stopped
labels:
- 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
networks:
proxy:
name: proxy
external: true
backend:
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 1.2.3.4 -p 21116
And now let’s check Traefik logs.
sudo cat /var/log/crowdsec/traefik-tcpudp.log
Perfect!
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
nodes:
## TCP GROK
- 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
statics:
## 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
statics:
## Add meta value, this type is used by scenario
- meta: log_type
value: traefik_tcpudp
statics:
## 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
labels:
remediation: true
classification:
- 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
---
filenames:
- /var/log/crowdsec/traefik-tcpudp.log
labels:
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://install.crowdsec.net/ | 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 1.2.3.4
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!