Traefik with Crowdsec

Traefik with Crowdsec

Setting up Traefik with Let's Encrypt certificates is safe and easy, but does it fully protect you against malicious users? The answer is no. You're still vulnerable to a variety of attacks. A common one would be a DDoS attack. This where a malicious user(s) overloads your server with a massive amount of data, sent all at once. How do we protect our services from these kinds of attacks? This is where Crowdsec can help us.

DISCLAIMER: Crowdsec is NOT an all-in-one solution for protecting your publically exposed services. Crowdsec will help mitigate attacks and is an overall great project! There are many practices and standards to help enforce cyber security, so do NOT relie on a single service for projection. Remember to regularly maintain your server and perform security updates.

What is Crowdsec?

Crowdsec is a free, open-sourced project to help analyize the logs of a proxy and determine malicious behavior. Crowdsec was built on the idea of collaberation to have a community-powered security. Crowdsec keeps a public collection of known malicious IPs to help prevent attacks ahead of time.

Acquisition

The acquisition provides a list of logs for Crowdsec to parse and analyze. It requires the source of the logs, as well as the type of logs. To configure an acquisition for traefik, create an acquis.yaml file and put the following text inside:

filenames:
  - /var/log/traefik/*
labels:
  type:	traefik 

Place this file in a secure location inside your server, as we will be mounting this file to your Crowdsec container.

Crowdsec Setup

Now that we have our aquis.yaml file ready, we can start building out our Docker Compose stack.

version: '3.9'
 
networks:
  public-network:
    name: public
    driver: bridge
    
volumes:
  network-logs:
    name: traefik-logs
  certs-volume:
    name: traefik-certs
  crowdsec-db:
    name: crowdsec-database
  crowdsec-config:
    name: crowdsec-config

services:

  traefik:
    image: traefik:v2.4
    container_name: traefik
    restart: unless-stopped
    volumes:
      - /var/run/docker.sock:/var/run/docker.sock:ro
      - certs-volume:/letsencrypt
      - network-logs:/var/log/
    networks:
      - public-network
    ports:
      - 80:80
      - 443:443
    command:
      - --api.insecure=true
      - --certificatesresolvers.letsencrypt.acme.httpchallenge=true
      - --certificatesresolvers.letsencrypt.acme.httpchallenge.entrypoint=web
      - --certificatesresolvers.letsencrypt.acme.email=support@example.com # CHANGE ME
      - --certificatesresolvers.letsencrypt.acme.storage=/letsencrypt/acme.json
      - --entrypoints.web.address=:80
      - --entrypoints.web.http.redirections.entrypoint.to=websecure
      - --entrypoints.web.http.redirections.entrypoint.scheme=https
      - --entrypoints.websecure.address=:443
      - --entrypoints.websecure.http.tls=true
      - --entrypoints.websecure.http.tls.certResolver=letsencrypt
      - --log=true
      - --log.level=DEBUG
      - --log.filepath=/var/log/traefik.log
      - --providers.docker=true
      - --providers.docker.exposedbydefault=false
      - --providers.docker.network=public

  crowdsec:
    image: crowdsecurity/crowdsec
    restart: unless-stopped
    container_name: crowdsec
    expose:
      - 8080
    environment:
      COLLECTIONS: "crowdsecurity/linux crowdsecurity/traefik"
      GID: "${GID-1000}"
    depends_on:
      - traefik
    volumes:
      - /path/to/crowdsec/acquis.yaml:/etc/crowdsec/acquis.yaml # Change Me!
      - network-logs:/var/log/traefik/:ro
      - crowdsec-db:/var/lib/crowdsec/data/
      - crowdsec-config:/etc/crowdsec/
    networks:
      - public-network

In this stack we have two services: Traefik and Crowdsec. Traefik is configured to use Let's Encrypt certs, redirect HTTP request to HTTPS, and perform HTTP challenges.

This is a solid setup, but something to notice is the logs volume. We have our logs defined with the /var/log/traefik.log path. If we look at our acquis.yaml file, we notice the paths match the same location. These paths MUST MATCH, otherwise Crowdsec will not find your logs.

Looking into our Crowdsec configurations, we are setting Crowdsec to depend on traefik, Crowdsec is on the same network as traefik, we expose port 8080, and we mount the aquis.yaml file. For our environment, we have two important variables: COLLECTIONS and GID. COLLECTIONS help define a set of tools our Crowdsec will need to perform checks on our Traefik logs. These collections can come with many different tools. Such as a parser to help parse the logs of your web server. The GID needs to point a group that has permissions to read the logs of Traefik. We also mount the same volume that carries the Traefik logs and give it read-only permissions. Crowdsec now has Traefik logs and can read them.

Now that you have Traefik and Crowdsec running, you probably don't notice anything different. Thats because Crowdsec isn't protecting you. You're probably upset with me for wasting your time, but I can assure you that we are not done yet.

Bouncers

A bouncer is a component to Crowdsec that reads the events Crowdsec broadcasts. Its essentially a middleware to your Traefik and will make the decisions to reject a user or not.

Crowdsec Traefik Bouncer

Now that you have Traefik running and Crowdsec is reading/parsing your logs, lets set up the Traefik Bouncer. Before we do that, we need to generate an API key from your Crowdsec instance.

To do that, we'll need to run a command to your Docker container. You can run this command in the terminal (don't forget sudo if you need it):

docker exec crowdsec cscli bouncers add bouncer-traefik

If you are using portainer to manager your containers, you run a console session for your crowdsec container. Then run this command instead:

cscli bouncers add bouncer-traefik

Keep that generated key somewhere safe so you can use for your Crowdsec bouncer.

Now that you have an API key to your Crowdsec container you can configure the Traefik Bouncer.

  crowdsec-bouncer:
    image: fbonalair/traefik-crowdsec-bouncer
    container_name: crowdsec-bouncer
    environment:
      GIN_MODE: release # default is debug (more logs)
      CROWDSEC_BOUNCER_API_KEY: ChangeME # Change to bouncer api key
      CROWDSEC_AGENT_HOST: crowdsec:8080
    expose:
      - 8080
    networks:
      - public-network

Don't forget to put your API key inside of the CROWDSEC_BOUNCER_API_KEY environment variable.

Next, you'll need to add the crowdsec-bouncer service as middleware to your Traefik container. You're entire docker-compose.yaml should look something like this:

version: '3.9'
 
networks:
  public-network:
    name: public
    driver: bridge
    
volumes:
  network-logs:
    name: traefik-logs
  certs-volume:
    name: traefik-certs
  crowdsec-db:
    name: crowdsec-database
  crowdsec-config:
    name: crowdsec-config

services:

  traefik:
    image: traefik:v2.4
    container_name: traefik
    restart: unless-stopped
    volumes:
      - /var/run/docker.sock:/var/run/docker.sock:ro
      - certs-volume:/letsencrypt
      - network-logs:/var/log/
    networks:
      - public-network
    ports:
      - 80:80
      - 443:443
    labels:
      - traefik.enable=true
      - traefik.http.middlewares.crowdsec-bouncer.forwardauth.address=http://crowdsec-bouncer:8080/api/v1/forwardAuth
      - traefik.http.middlewares.crowdsec-bouncer.forwardauth.trustForwardHeader=true
    command:
      - --api.insecure=true
      - --certificatesresolvers.letsencrypt.acme.httpchallenge=true
      - --certificatesresolvers.letsencrypt.acme.httpchallenge.entrypoint=web
      - --certificatesresolvers.letsencrypt.acme.email=support@example.com # CHANGE ME
      - --certificatesresolvers.letsencrypt.acme.storage=/letsencrypt/acme.json
      - --entrypoints.web.address=:80
      - --entrypoints.web.http.middlewares=crowdsec-bouncer@docker
      - --entrypoints.web.http.redirections.entrypoint.to=websecure
      - --entrypoints.web.http.redirections.entrypoint.scheme=https
      - --entrypoints.websecure.address=:443
      - --entrypoints.websecure.http.middlewares=crowdsec-bouncer@docker
      - --entrypoints.websecure.http.tls=true
      - --entrypoints.websecure.http.tls.certResolver=letsencrypt
      - --log=true
      - --log.level=DEBUG
      - --log.filepath=/var/log/traefik.log
      - --providers.docker=true
      - --providers.docker.exposedbydefault=false
      - --providers.docker.network=public

  crowdsec:
    image: crowdsecurity/crowdsec
    restart: unless-stopped
    container_name: crowdsec
    expose:
      - 8080
    environment:
      COLLECTIONS: "crowdsecurity/linux crowdsecurity/traefik"
      GID: "${GID-1000}"
    depends_on:
      - traefik
    volumes:
      - /path/to/crowdsec/acquis.yaml:/etc/crowdsec/acquis.yaml # Change Me!
      - network-logs:/var/log/traefik/:ro
      - crowdsec-db:/var/lib/crowdsec/data/
      - crowdsec-config:/etc/crowdsec/
    networks:
      - public-network

  crowdsec-bouncer:
    image: fbonalair/traefik-crowdsec-bouncer
    container_name: crowdsec-bouncer
    environment:
      GIN_MODE: release # default is debug (more logs)
      CROWDSEC_BOUNCER_API_KEY: ChangeME # Change to bouncer api key
      CROWDSEC_AGENT_HOST: crowdsec:8080
    expose:
      - 8080
    networks:
      - public-network

Be sure to go through this and make sure you update all settings commented with a "change..." statement. After everything is running you should see some logs inside of your crowdsec-bouncer container.

I hope this helps you get started with securing your services and have a better understand of Crowdsec. Thank you for reading!

Docker compose and acquisition configurations can be found in my GitHub.