mirror of
https://github.com/maxlerebourg/crowdsec-bouncer-traefik-plugin.git
synced 2026-09-02 20:28:50 +02:00
Compare commits
4
Commits
v1.1.5-beta-1
...
v1.1.7
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
041b0f67b7 | ||
|
|
5458db93cf | ||
|
|
29878e837c | ||
|
|
aff7c496c2 |
@@ -8,7 +8,7 @@
|
||||
|
||||
This plugin aims to implement a Crowdsec Bouncer in a traefik plugin.
|
||||
|
||||
> [CrowdSec](https://www.crowdsec.net/) is an open-source and collaborative IPS (Intrusion Prevention System) and a security suite.
|
||||
> [CrowdSec](https://www.crowdsec.net/) is an open-source and collaborative IPS (Intrusion Prevention System) and a security suite.
|
||||
> We leverage local behavior analysis and crowd power to build the largest CTI network in the world.
|
||||
|
||||
The purpose is to enable treafik to authorize or block requests from IPs based on their reputation and behavior.
|
||||
@@ -195,20 +195,20 @@ http:
|
||||
|
||||
#### Fill variable with value of file
|
||||
|
||||
`CrowdsecLapiTlsCertificateBouncerKey`, `CrowdsecLapiTlsCertificateBouncer`, `CrowdsecLapiTlsCertificateAuthority` and `CrowdsecLapiKey` can be provided with the content as raw or through a file path that Traefik can read.
|
||||
`CrowdsecLapiTlsCertificateBouncerKey`, `CrowdsecLapiTlsCertificateBouncer`, `CrowdsecLapiTlsCertificateAuthority` and `CrowdsecLapiKey` can be provided with the content as raw or through a file path that Traefik can read.
|
||||
The file variable will be used as preference if both content and file are provided for the same variable.
|
||||
|
||||
Format is:
|
||||
- Content: VariableName: XXX
|
||||
- File : VariableNameFILE: /path
|
||||
- File : VariableNameFile: /path
|
||||
|
||||
#### Authenticate with LAPI
|
||||
|
||||
You can authenticate to the LAPI either with LAPIKEY or by using client certificates.
|
||||
You can authenticate to the LAPI either with LAPIKEY or by using client certificates.
|
||||
Please see below for more details on each option.
|
||||
|
||||
#### Generate LAPI KEY
|
||||
You can generate a crowdsec API key for the LAPI.
|
||||
You can generate a crowdsec API key for the LAPI.
|
||||
You can follow the documentation here: https://docs.crowdsec.net/docs/user_guides/lapi_mgmt/
|
||||
|
||||
```bash
|
||||
@@ -241,15 +241,15 @@ docker-compose up -d
|
||||
|
||||
#### Use certificates to authenticate with CrowdSec
|
||||
|
||||
You can follow the example in exemples/tls-auth to view how to authenticate with client certificates with the LAPI.
|
||||
You can follow the example in `exemples/tls-auth` to view how to authenticate with client certificates with the LAPI.
|
||||
In that case communications with the LAPI must go through HTTPS.
|
||||
|
||||
A script is available to generate certificates in exemples/tls-auth/gencerts.sh and must be in the same directory as the inputs for the PKI creation.
|
||||
A script is available to generate certificates in `exemples/tls-auth/gencerts.sh` and must be in the same directory as the inputs for the PKI creation.
|
||||
|
||||
#### Use HTTPS to communicate with the LAPI
|
||||
|
||||
To communicate with the LAPI in HTTPS you need to either accept any certificates by setting the crowdsecLapiTLSInsecureVerify to true or add the CA used by the server certificate of Crowdsec using crowdsecLapiTLSCertificateAuthority or crowdsecLapiTLSCertificateAuthorityFile.
|
||||
Set the crowdsecLapiScheme to https.
|
||||
To communicate with the LAPI in HTTPS you need to either accept any certificates by setting the `crowdsecLapiTLSInsecureVerify` to true or add the CA used by the server certificate of Crowdsec using `crowdsecLapiTLSCertificateAuthority` or `crowdsecLapiTLSCertificateAuthorityFile`.
|
||||
Set the `crowdsecLapiScheme` to https.
|
||||
|
||||
Crowdsec must be listening in HTTPS for this to work.
|
||||
Please see the tls-auth exemple or the official documentation: [https://docs.crowdsec.net/docs/local_api/tls_auth/](https://docs.crowdsec.net/docs/local_api/tls_auth/)
|
||||
@@ -300,181 +300,27 @@ make run_local
|
||||
|
||||
1. Behind another proxy service (ex: clouflare)
|
||||
|
||||
You need to configure your Traefik to trust Forwarded headers by your front proxy
|
||||
In the example we use another instance of traefik with the container named cloudflare to simulate a front proxy
|
||||
|
||||
The "internal" Traefik instance is configured to trust the cloudflare forward headers
|
||||
This helps Traefik choose the right IP of the client: see https://doc.traefik.io/traefik/routing/entrypoints/#forwarded-headers
|
||||
```yaml
|
||||
- "--entrypoints.web.forwardedheaders.trustedips=172.21.0.5"
|
||||
```
|
||||
|
||||
We configure the middleware to trust as well the IP:
|
||||
```yaml
|
||||
- "traefik.http.middlewares.crowdsec1.plugin.bouncer.forwardedheaderstrustedips=172.21.0.5"
|
||||
```
|
||||
|
||||
To play the demo environment run:
|
||||
```bash
|
||||
make run_behindproxy
|
||||
```
|
||||
A complete exemple is available in [exemples/behind-proxy/README.md](https://github.com/maxlerebourg/crowdsec-bouncer-traefik-plugin/blob/main/exemples/behind-proxy/README.md)
|
||||
|
||||
2. With Redis as an external shared cache
|
||||
|
||||
The plugin must be configured to connect to a redis instance
|
||||
```yaml
|
||||
redisCacheHost: "redis:6379"
|
||||
```
|
||||
Here **redis** is the hostname of a container located in the same network as Traefik and **6379** is the default port of redis
|
||||
|
||||
To play the demo environment run:
|
||||
```bash
|
||||
make run_cacheredis
|
||||
```
|
||||
A complete exemple is available in [exemples/redis-cache/README.md](https://github.com/maxlerebourg/crowdsec-bouncer-traefik-plugin/blob/main/exemples/redis-cache/README.md)
|
||||
|
||||
3. Using Trusted IP (ex: LAN OR VPN) that won't get filtered by crowdsec
|
||||
|
||||
You need to configure your Traefik to trust Forwarded headers by your front proxy
|
||||
In the example we use a whoami container protected by crowdsec, and we ban our IP before allowing using TrustedIPs
|
||||
|
||||
If you are using another proxy in front, you need to add its IP in the trusted IP for the forwarded headers.
|
||||
This helps Traefik choose the right IP of the client: see https://doc.traefik.io/traefik/routing/entrypoints/#forwarded-headers
|
||||
The "internal" Traefik instance is configured to trust the forward headers
|
||||
```yaml
|
||||
- "--entrypoints.web.forwardedheaders.trustedips=172.21.0.5"
|
||||
```
|
||||
|
||||
We configure the middleware to trust as well as the IP of the intermediate proxy if needed:
|
||||
```yaml
|
||||
- "traefik.http.middlewares.crowdsec.plugin.bouncer.forwardedheaderstrustedips=172.21.0.5"
|
||||
```
|
||||
|
||||
Add your IP to the ban list
|
||||
```bash
|
||||
docker exec crowdsec cscli decisions add --ip 10.0.10.30 -d 10m
|
||||
```
|
||||
You should get a 403 on http://localhost/foo
|
||||
|
||||
> Replace *10.0.10.30* by your IP
|
||||
|
||||
Add the IPs that will not be filtered by the plugin
|
||||
```yaml
|
||||
- "traefik.http.middlewares.crowdsec.plugin.bouncer.clientTrustedips=10.0.10.30/32"
|
||||
```
|
||||
|
||||
> Replace *10.0.10.30/32* by your IP or IP range, so it's not getting checked against ban cache of crowdsec
|
||||
|
||||
You should get a 200 on http://localhost/foo even if you are on the ban cache
|
||||
|
||||
To play the demo environment run:
|
||||
```bash
|
||||
make run_trustedips
|
||||
```
|
||||
A complete exemple is available in [exemples/trusted-ips/README.md](https://github.com/maxlerebourg/crowdsec-bouncer-traefik-plugin/blob/main/exemples/trusted-ips/README.md)
|
||||
|
||||
4. Using Crowdsec and Traefik installed as binary in a single VM
|
||||
|
||||
Please see details in `exemples/binary-vm/README.md`
|
||||
|
||||
To play the demo environment run:
|
||||
```bash
|
||||
make run_binaryvm
|
||||
```
|
||||
A complete exemple is available in [exemples/binary-vm/README.md](https://github.com/maxlerebourg/crowdsec-bouncer-traefik-plugin/blob/main/exemples/binary-vm/README.md)
|
||||
|
||||
5. Using https communication and tls authentication with Crowdsec
|
||||
|
||||
##### Summary
|
||||
This example demonstrates the use of https between the Traefik plugin and the Crowdsec LAPI.
|
||||
A complete exemple is available in [exemples/tls-auth/README.md](https://github.com/maxlerebourg/crowdsec-bouncer-traefik-plugin/blob/main/exemples/tls-auth/README.md)
|
||||
|
||||
It is possible to communicate with the LAPI in https and still authenticate with API key.
|
||||
You can add the client TLS certificate generated to authenticate without any Token in the plugin.
|
||||
|
||||
However, note that it is not possible to authenticate with TLS client certificate without https setup for the LAPI.
|
||||
|
||||
The example is detailed below and will be placed in the `examples/tls-auth/README.md` file.
|
||||
|
||||
##### Details
|
||||
|
||||
Simple HTTPS communication: It is possible to talk to Crowdsec LAPI which is configured with a self-signed certificate
|
||||
In that case the setting **crowdsecLapiTLSInsecureVerify** must be set to true.
|
||||
|
||||
It is recommended to validate the certificate presented by Crowdsec LAPI using the Certificate Authority which created it.
|
||||
|
||||
You can provide the Certificate Authority using:
|
||||
* A file path readable by Traefik
|
||||
```yaml
|
||||
http:
|
||||
middlewares:
|
||||
crowdsec:
|
||||
plugin:
|
||||
bouncer:
|
||||
crowdsecLapiTlsCertificateAuthorityFile: /etc/traefik/certs/crowdsecCA.pem
|
||||
```
|
||||
* The PEM encoded certificate as a text variable
|
||||
|
||||
In the static file configuration of Traefik
|
||||
```yaml
|
||||
http:
|
||||
middlewares:
|
||||
crowdsec:
|
||||
plugin:
|
||||
bouncer:
|
||||
crowdsecLapiTlsCertificateAuthority: |-
|
||||
-----BEGIN CERTIFICATE-----
|
||||
MIIEBzCCAu+gAwIBAgICEAAwDQYJKoZIhvcNAQELBQAwgZQxCzAJBgNVBAYTAlVT
|
||||
MRAwDgYDVQQHDAdTZWF0dGxlMRMwEQYDVQQIDApXYXNoaW5ndG9uMSIwIAYDVQQK
|
||||
...
|
||||
C6qNieSwcvWL7C03ri0DefTQMY54r5wP33QU5hJ71JoaZI3YTeT0Nf+NRL4hM++w
|
||||
Q0veeNzBQXg1f/JxfeA39IDIX1kiCf71tGlT
|
||||
-----END CERTIFICATE-----
|
||||
```
|
||||
In a dynamic configuration of a provider (ex docker) as a Label
|
||||
```yaml
|
||||
services:
|
||||
whoami-foo:
|
||||
image: traefik/whoami
|
||||
labels:
|
||||
- |
|
||||
traefik.http.middlewares.crowdsec-foo.plugin.bouncer.crowdsecLapiTlsCertificateAuthority=
|
||||
-----BEGIN CERTIFICATE-----
|
||||
MIIEBzCCAu+gAwIBAgICEAAwDQYJKoZIhvcNAQELBQAwgZQxCzAJBgNVBAYTAlVT
|
||||
MRAwDgYDVQQHDAdTZWF0dGxlMRMwEQYDVQQIDApXYXNoaW5ndG9uMSIwIAYDVQQK
|
||||
...
|
||||
C6qNieSwcvWL7C03ri0DefTQMY54r5wP33QU5hJ71JoaZI3YTeT0Nf+NRL4hM++w
|
||||
Q0veeNzBQXg1f/JxfeA39IDIX1kiCf71tGlT
|
||||
-----END CERTIFICATE-----
|
||||
```
|
||||
|
||||
The example tls-auth presents 2 services, foo and bar which comes with the bouncer.
|
||||
At startup, certificates are created in a shared docker volume by a sidecar container which exits after.
|
||||
|
||||
Traefik will use client and CA certificates.
|
||||
The Bouncer will use server and CA certificates.
|
||||
|
||||
The service `whoami-foo` will authenticate with an **API key** over HTTPS after verifying the server certificate with CA.
|
||||
The service `whoami-bar` will authenticate with a **client certificate** signed by the CA.
|
||||
|
||||
Access to a route that communicate via https and authenticate with API-key:
|
||||
```
|
||||
curl http://localhost:80/foo
|
||||
```
|
||||
Access to a route that communicate via https and authenticate with a client certificate:
|
||||
```
|
||||
curl http://localhost:80/bar
|
||||
```
|
||||
Access to the traefik dashboard
|
||||
```
|
||||
curl http://localhost:8080/dashboard/#/
|
||||
```
|
||||
|
||||
To play the demo environnement run:
|
||||
```bash
|
||||
make run_tlsauth
|
||||
```
|
||||
|
||||
Note:
|
||||
> This example is still in Beta and use a new version of Crowdsec (v1.4.3) at time of writing
|
||||
A functionnality has been disabled in Crowdsec in order to make the example work DISABLE_AGENT: "true"
|
||||
6. Using Crowdsec and Traefik in Kubernetes
|
||||
|
||||
A complete exemple is available in [exemples/kubernetes/README.md](https://github.com/maxlerebourg/crowdsec-bouncer-traefik-plugin/blob/main/exemples/kubernetes/README.md)
|
||||
|
||||
### About
|
||||
|
||||
|
||||
@@ -10,6 +10,7 @@ import (
|
||||
"io"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"strings"
|
||||
"text/template"
|
||||
"time"
|
||||
|
||||
@@ -79,6 +80,7 @@ func New(ctx context.Context, next http.Handler, config *configuration.Config, n
|
||||
logger.Error(fmt.Sprintf("New:crowdsecLapiKey fail to get CrowdsecLapiKey and no client certificate setup %s", err.Error()))
|
||||
return nil, err
|
||||
}
|
||||
apiKey = strings.TrimSuffix(apiKey, "\n")
|
||||
|
||||
bouncer := &Bouncer{
|
||||
next: next,
|
||||
|
||||
@@ -0,0 +1,21 @@
|
||||
# Exemple
|
||||
## Behind another proxy service (ex: clouflare)
|
||||
|
||||
You need to configure your Traefik to trust Forwarded headers by your front proxy
|
||||
In the example we use another instance of traefik with the container named cloudflare to simulate a front proxy
|
||||
|
||||
The "internal" Traefik instance is configured to trust the cloudflare forward headers
|
||||
This helps Traefik choose the right IP of the client: see https://doc.traefik.io/traefik/routing/entrypoints/#forwarded-headers
|
||||
```yaml
|
||||
- "--entrypoints.web.forwardedheaders.trustedips=172.21.0.5"
|
||||
```
|
||||
|
||||
We configure the middleware to trust as well the IP:
|
||||
```yaml
|
||||
- "traefik.http.middlewares.crowdsec1.plugin.bouncer.forwardedheaderstrustedips=172.21.0.5"
|
||||
```
|
||||
|
||||
To play the demo environment run:
|
||||
```bash
|
||||
make run_behindproxy
|
||||
```
|
||||
@@ -2,7 +2,7 @@ version: "3.8"
|
||||
|
||||
services:
|
||||
cloudflare:
|
||||
image: "traefik:v2.9.4"
|
||||
image: "traefik:v2.9.6"
|
||||
container_name: "cloudflare"
|
||||
restart: unless-stopped
|
||||
command:
|
||||
@@ -22,7 +22,7 @@ services:
|
||||
- 8080:8080
|
||||
|
||||
traefik:
|
||||
image: "traefik:v2.9.4"
|
||||
image: "traefik:v2.9.6"
|
||||
container_name: "traefik"
|
||||
restart: unless-stopped
|
||||
command:
|
||||
@@ -36,7 +36,7 @@ services:
|
||||
- "--entrypoints.web.forwardedheaders.trustedips=172.21.0.5"
|
||||
|
||||
- "--experimental.plugins.bouncer.modulename=github.com/maxlerebourg/crowdsec-bouncer-traefik-plugin"
|
||||
- "--experimental.plugins.bouncer.version=v1.1.3"
|
||||
- "--experimental.plugins.bouncer.version=v1.1.7"
|
||||
volumes:
|
||||
- /var/run/docker.sock:/var/run/docker.sock:ro
|
||||
- logs-traefik:/var/log/traefik
|
||||
@@ -88,7 +88,7 @@ services:
|
||||
|
||||
|
||||
crowdsec:
|
||||
image: crowdsecurity/crowdsec:v1.4.1
|
||||
image: crowdsecurity/crowdsec:v1.4.3
|
||||
container_name: "crowdsec"
|
||||
restart: unless-stopped
|
||||
environment:
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
# Exemple
|
||||
|
||||
### Install vagrant
|
||||
|
||||
##### On linux
|
||||
|
||||
Vendored
+2
@@ -67,6 +67,8 @@ Vagrant.configure("2") do |config|
|
||||
# vb.memory = "2048"
|
||||
# end
|
||||
config.vm.provision "file", source: "./files", destination: "/home/vagrant/vagrant_data"
|
||||
config.vm.provision "file", source: "./scripts", destination: "/home/vagrant/scripts"
|
||||
# config.vm.provision "file", source: "../../", destination: "/home/vagrant/plugin"
|
||||
|
||||
config.vm.provision "shell", path: "scripts/install_traefik.sh"
|
||||
config.vm.provision "shell", path: "scripts/configure_traefik.sh"
|
||||
|
||||
@@ -19,7 +19,7 @@ http:
|
||||
plugin:
|
||||
bouncer:
|
||||
enabled: true
|
||||
crowdseclapikey: whoami-demo
|
||||
crowdseclapikey: "whoami-demo"
|
||||
updateintervalseconds: 60
|
||||
crowdsecmode: live
|
||||
loglevel: "DEBUG"
|
||||
|
||||
@@ -31,7 +31,7 @@ experimental:
|
||||
plugins:
|
||||
bouncer:
|
||||
moduleName: github.com/maxlerebourg/crowdsec-bouncer-traefik-plugin
|
||||
version: v1.1.5
|
||||
version: v1.1.6
|
||||
|
||||
################################################################
|
||||
# Certificate Resolver
|
||||
|
||||
@@ -28,4 +28,4 @@ ProtectSystem=full
|
||||
NoNewPrivileges=true
|
||||
|
||||
[Install]
|
||||
WantedBy=multi-user.
|
||||
WantedBy=multi-user.target
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
#!/bin/bash
|
||||
|
||||
curl -s https://packagecloud.io/install/repositories/crowdsec/crowdsec/script.deb.sh | sudo bash
|
||||
sudo apt install crowdsec -y
|
||||
DEBIAN_FRONTEND=noninteractive curl -s https://packagecloud.io/install/repositories/crowdsec/crowdsec/script.deb.sh | sudo bash
|
||||
DEBIAN_FRONTEND=noninteractive sudo apt install crowdsec -y
|
||||
sudo cp /home/vagrant/vagrant_data/crowdsec/acquis.yaml /etc/crowdsec/acquis.yaml
|
||||
|
||||
@@ -1,7 +1,8 @@
|
||||
#!/bin/bash
|
||||
|
||||
sudo apt-get update && apt-get install wget -y && apt-get upgrade -y
|
||||
wget -O traefik.tar.gz "https://github.com/traefik/traefik/releases/download/v2.9.5/traefik_v2.9.5_linux_amd64.tar.gz"
|
||||
DEBIAN_FRONTEND=noninteractive sudo apt-get update && sudo apt-get install wget -y
|
||||
# DEBIAN_FRONTEND=noninteractive sudo apt-get upgrade -y --assume-yes
|
||||
wget -O traefik.tar.gz "https://github.com/traefik/traefik/releases/download/v2.9.6/traefik_v2.9.6_linux_amd64.tar.gz"
|
||||
tar -zxvf traefik.tar.gz
|
||||
# inspired from https://gist.github.com/ubergesundheit/7c9d875befc2d7bfd0bf43d8b3862d85
|
||||
sudo mv ./traefik /usr/local/bin/
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
#!/bin/bash
|
||||
|
||||
sudo apt-get update && apt-get install wget -y
|
||||
DEBIAN_FRONTEND=noninteractive sudo apt-get update && apt-get install wget -y
|
||||
wget -O whoami.tar.gz "https://github.com/traefik/whoami/releases/download/v1.8.7/whoami_v1.8.7_linux_amd64.tar.gz"
|
||||
tar -zxvf whoami.tar.gz
|
||||
# inspired from https://gist.github.com/ubergesundheit/7c9d875befc2d7bfd0bf43d8b3862d85
|
||||
|
||||
@@ -0,0 +1,91 @@
|
||||
### Kubernetes Exemple
|
||||
|
||||
#### Official docs
|
||||
|
||||
##### Install Kubernetes on Docker Desktop
|
||||
|
||||
Install Docker Desktop
|
||||
|
||||
[https://www.docker.com/products/docker-desktop/](https://www.docker.com/products/docker-desktop/)
|
||||
|
||||
In settings, click on `Kubernetes` menu, and click `Enable Kubernetes`, then `Apply and Restart`.
|
||||
In case of any issue, you can reset the cluster from this menu and the button `Reset Kubernetes Cluster`.
|
||||
|
||||
##### Install Traefik
|
||||
|
||||
[getting-started/install-traefik/#use-the-helm-chart](https://doc.traefik.io/traefik/getting-started/install-traefik/#use-the-helm-chart)
|
||||
|
||||
```bash
|
||||
helm repo add traefik https://traefik.github.io/charts
|
||||
helm repo update
|
||||
kubectl create ns traefik
|
||||
helm upgrade --install --namespace=traefik \
|
||||
--values=./traefik/values.yml \
|
||||
traefik traefik/traefik
|
||||
```
|
||||
|
||||
#### View the Traefik dashboard
|
||||
|
||||
> Port forward the dashboard:
|
||||
|
||||
```bash
|
||||
kubectl --namespace=traefik port-forward $(kubectl get pods --namespace=traefik --selector "app.kubernetes.io/name=traefik" --output=name) 9000:9000
|
||||
```
|
||||
|
||||
Access the dashboard with: [localhost:9000/dashboard/#/](http://localhost:9000/dashboard/#/)
|
||||
|
||||
#### Install the plugin
|
||||
|
||||
```bash
|
||||
kubectl apply -f traefik/plugin.yml
|
||||
```
|
||||
|
||||
#### Install Whoami
|
||||
```bash
|
||||
kubectl apply -f whoami/whoami.yml
|
||||
kubectl apply -f whoami/whoami-services.yml
|
||||
kubectl apply -f whoami/whoami-ingress.yml
|
||||
```
|
||||
|
||||
#### Access Whoami
|
||||
|
||||
> Port forward web port of Traefik
|
||||
|
||||
```bash
|
||||
kubectl --namespace=traefik port-forward $(kubectl get pods --namespace=traefik --selector "app.kubernetes.io/name=traefik" --output=name) 8000:8000
|
||||
```
|
||||
|
||||
Access the whoami with: [localhost:8000/](http://localhost:8000/)
|
||||
|
||||
#### Install Crowdsec
|
||||
|
||||
[helm/crowdsec/crowdsec](https://artifacthub.io/packages/helm/crowdsec/crowdsec)
|
||||
|
||||
```bash
|
||||
helm repo add crowdsec https://crowdsecurity.github.io/helm-charts
|
||||
helm repo update
|
||||
kubectl create ns crowdsec
|
||||
helm upgrade --install --namespace=crowdsec \
|
||||
--values=./crowdsec/values.yml \
|
||||
crowdsec crowdsec/crowdsec
|
||||
```
|
||||
|
||||
#### Read Traefik Logs
|
||||
|
||||
```bash
|
||||
kubectl get pod --namespace traefik
|
||||
kubectl logs $(kubectl get pods --namespace=traefik --selector "app.kubernetes.io/name=traefik" --output=name) --namespace traefik -f
|
||||
```
|
||||
|
||||
#### Use CSCLI in Crowdsec container
|
||||
|
||||
```bash
|
||||
kubectl -n crowdsec exec -it $(kubectl get pods -n crowdsec --selector "k8s-app=crowdsec,type=lapi" --output=name) bash
|
||||
```
|
||||
|
||||
|
||||
#### Shell in Traefik container
|
||||
|
||||
```bash
|
||||
kubectl -n traefik exec -it $(kubectl get pods -n traefik --selector "app.kubernetes.io/name=traefik" --output=name) sh
|
||||
```
|
||||
@@ -0,0 +1,14 @@
|
||||
apiVersion: v1
|
||||
kind: Service
|
||||
metadata:
|
||||
name: crowdsec
|
||||
namespace: crowdsec
|
||||
|
||||
spec:
|
||||
ports:
|
||||
- name: web
|
||||
port: 8080
|
||||
targetPort: web
|
||||
|
||||
selector:
|
||||
app: crowdsec
|
||||
@@ -0,0 +1,25 @@
|
||||
image:
|
||||
tag: v1.4.4-rc1
|
||||
|
||||
agent:
|
||||
acquisition:
|
||||
- namespace: traefik
|
||||
podName: traefik-*
|
||||
program: traefik
|
||||
env:
|
||||
- name: COLLECTIONS
|
||||
value: "crowdsecurity/traefik"
|
||||
|
||||
lapi:
|
||||
dashboard:
|
||||
enabled: false
|
||||
env:
|
||||
- name: ENROLL_KEY
|
||||
value: 40796d93c2958f9e58345514e67740e5
|
||||
- name: ENROLL_INSTANCE_NAME
|
||||
value: k8s_cluster
|
||||
- name: ENROLL_TAGS
|
||||
value: "k8s linux test"
|
||||
# If it's a test, we don't want to share signals with CrowdSec so disable the Online API.
|
||||
- name: DISABLE_ONLINE_API
|
||||
value: true
|
||||
@@ -0,0 +1,14 @@
|
||||
# dashboard.yaml
|
||||
apiVersion: traefik.containo.us/v1alpha1
|
||||
kind: IngressRoute
|
||||
metadata:
|
||||
name: dashboard
|
||||
spec:
|
||||
entryPoints:
|
||||
- web
|
||||
routes:
|
||||
- match: Host(`traefik.localhost`) && (PathPrefix(`/dashboard`) || PathPrefix(`/api`))
|
||||
kind: Rule
|
||||
services:
|
||||
- name: api@internal
|
||||
kind: TraefikService
|
||||
@@ -0,0 +1,14 @@
|
||||
apiVersion: traefik.containo.us/v1alpha1
|
||||
kind: Middleware
|
||||
metadata:
|
||||
name: bouncer
|
||||
namespace: traefik
|
||||
spec:
|
||||
plugin:
|
||||
bouncer:
|
||||
CrowdsecLapiKey: 40796d93c2958f9e58345514e67740e5
|
||||
Enabled: "true"
|
||||
logLevel: DEBUG
|
||||
crowdsecMode: live
|
||||
crowdsecLapiHost: crowdsec-service.crowdsec.svc.cluster.local:8080
|
||||
crowdsecLapiScheme: http
|
||||
@@ -0,0 +1,19 @@
|
||||
image:
|
||||
tag: v2.9.6
|
||||
|
||||
logs:
|
||||
general:
|
||||
level: DEBUG
|
||||
access:
|
||||
enabled: true
|
||||
fields:
|
||||
headers:
|
||||
defaultmode: keep
|
||||
|
||||
experimental:
|
||||
plugins:
|
||||
enabled: true
|
||||
|
||||
additionalArguments:
|
||||
- "--experimental.plugins.bouncer.modulename=github.com/maxlerebourg/crowdsec-bouncer-traefik-plugin"
|
||||
- "--experimental.plugins.bouncer.version=v1.1.7-beta1"
|
||||
@@ -0,0 +1,20 @@
|
||||
apiVersion: networking.k8s.io/v1
|
||||
kind: Ingress
|
||||
metadata:
|
||||
name: whoami
|
||||
namespace: traefik
|
||||
annotations:
|
||||
traefik.ingress.kubernetes.io/router.middlewares: traefik-bouncer@kubernetescrd
|
||||
traefik.ingress.kubernetes.io/router.entrypoints: web
|
||||
|
||||
spec:
|
||||
rules:
|
||||
- http:
|
||||
paths:
|
||||
- path: /
|
||||
pathType: Prefix
|
||||
backend:
|
||||
service:
|
||||
name: whoami
|
||||
port:
|
||||
name: web
|
||||
@@ -0,0 +1,14 @@
|
||||
apiVersion: v1
|
||||
kind: Service
|
||||
metadata:
|
||||
name: whoami
|
||||
namespace: traefik
|
||||
|
||||
spec:
|
||||
ports:
|
||||
- name: web
|
||||
port: 80
|
||||
targetPort: web
|
||||
|
||||
selector:
|
||||
app: whoami
|
||||
@@ -0,0 +1,24 @@
|
||||
kind: Deployment
|
||||
apiVersion: apps/v1
|
||||
metadata:
|
||||
name: whoami
|
||||
namespace: traefik
|
||||
labels:
|
||||
app: whoami
|
||||
|
||||
spec:
|
||||
replicas: 1
|
||||
selector:
|
||||
matchLabels:
|
||||
app: whoami
|
||||
template:
|
||||
metadata:
|
||||
labels:
|
||||
app: whoami
|
||||
spec:
|
||||
containers:
|
||||
- name: whoami
|
||||
image: traefik/whoami
|
||||
ports:
|
||||
- name: web
|
||||
containerPort: 80
|
||||
@@ -0,0 +1,13 @@
|
||||
# Exemple
|
||||
## With Redis as an external shared cache
|
||||
|
||||
The plugin must be configured to connect to a redis instance
|
||||
```yaml
|
||||
redisCacheHost: "redis:6379"
|
||||
```
|
||||
Here **redis** is the hostname of a container located in the same network as Traefik and **6379** is the default port of redis
|
||||
|
||||
To play the demo environment run:
|
||||
```bash
|
||||
make run_cacheredis
|
||||
```
|
||||
@@ -2,7 +2,7 @@ version: "3.8"
|
||||
|
||||
services:
|
||||
traefik:
|
||||
image: "traefik:v2.9.4"
|
||||
image: "traefik:v2.9.6"
|
||||
container_name: "traefik"
|
||||
restart: unless-stopped
|
||||
command:
|
||||
@@ -15,7 +15,7 @@ services:
|
||||
- "--entrypoints.web.address=:80"
|
||||
|
||||
- "--experimental.plugins.bouncer.modulename=github.com/maxlerebourg/crowdsec-bouncer-traefik-plugin"
|
||||
- "--experimental.plugins.bouncer.version=v1.1.4"
|
||||
- "--experimental.plugins.bouncer.version=v1.1.7"
|
||||
# - "--experimental.localplugins.bouncer.modulename=github.com/maxlerebourg/crowdsec-bouncer-traefik-plugin"
|
||||
volumes:
|
||||
- /var/run/docker.sock:/var/run/docker.sock:ro
|
||||
@@ -68,7 +68,7 @@ services:
|
||||
|
||||
|
||||
crowdsec:
|
||||
image: crowdsecurity/crowdsec:v1.4.1
|
||||
image: crowdsecurity/crowdsec:v1.4.3
|
||||
container_name: "crowdsec"
|
||||
restart: unless-stopped
|
||||
environment:
|
||||
|
||||
@@ -0,0 +1,93 @@
|
||||
# Exemple
|
||||
## Using https communication and tls authentication with Crowdsec
|
||||
|
||||
##### Summary
|
||||
This example demonstrates the use of https between the Traefik plugin and the Crowdsec LAPI.
|
||||
|
||||
It is possible to communicate with the LAPI in https and still authenticate with API key.
|
||||
You can add the client TLS certificate generated to authenticate without any Token in the plugin.
|
||||
|
||||
However, note that it is not possible to authenticate with TLS client certificate without https setup for the LAPI.
|
||||
|
||||
##### Details
|
||||
|
||||
Simple HTTPS communication: It is possible to talk to Crowdsec LAPI which is configured with a self-signed certificate
|
||||
In that case the setting **crowdsecLapiTLSInsecureVerify** must be set to true.
|
||||
|
||||
It is recommended to validate the certificate presented by Crowdsec LAPI using the Certificate Authority which created it.
|
||||
|
||||
You can provide the Certificate Authority using:
|
||||
* A file path readable by Traefik
|
||||
```yaml
|
||||
http:
|
||||
middlewares:
|
||||
crowdsec:
|
||||
plugin:
|
||||
bouncer:
|
||||
crowdsecLapiTlsCertificateAuthorityFile: /etc/traefik/certs/crowdsecCA.pem
|
||||
```
|
||||
* The PEM encoded certificate as a text variable
|
||||
|
||||
In the static file configuration of Traefik
|
||||
```yaml
|
||||
http:
|
||||
middlewares:
|
||||
crowdsec:
|
||||
plugin:
|
||||
bouncer:
|
||||
crowdsecLapiTlsCertificateAuthority: |-
|
||||
-----BEGIN CERTIFICATE-----
|
||||
MIIEBzCCAu+gAwIBAgICEAAwDQYJKoZIhvcNAQELBQAwgZQxCzAJBgNVBAYTAlVT
|
||||
MRAwDgYDVQQHDAdTZWF0dGxlMRMwEQYDVQQIDApXYXNoaW5ndG9uMSIwIAYDVQQK
|
||||
...
|
||||
C6qNieSwcvWL7C03ri0DefTQMY54r5wP33QU5hJ71JoaZI3YTeT0Nf+NRL4hM++w
|
||||
Q0veeNzBQXg1f/JxfeA39IDIX1kiCf71tGlT
|
||||
-----END CERTIFICATE-----
|
||||
```
|
||||
In a dynamic configuration of a provider (ex docker) as a Label
|
||||
```yaml
|
||||
services:
|
||||
whoami-foo:
|
||||
image: traefik/whoami
|
||||
labels:
|
||||
- |
|
||||
traefik.http.middlewares.crowdsec-foo.plugin.bouncer.crowdsecLapiTlsCertificateAuthority=
|
||||
-----BEGIN CERTIFICATE-----
|
||||
MIIEBzCCAu+gAwIBAgICEAAwDQYJKoZIhvcNAQELBQAwgZQxCzAJBgNVBAYTAlVT
|
||||
MRAwDgYDVQQHDAdTZWF0dGxlMRMwEQYDVQQIDApXYXNoaW5ndG9uMSIwIAYDVQQK
|
||||
...
|
||||
C6qNieSwcvWL7C03ri0DefTQMY54r5wP33QU5hJ71JoaZI3YTeT0Nf+NRL4hM++w
|
||||
Q0veeNzBQXg1f/JxfeA39IDIX1kiCf71tGlT
|
||||
-----END CERTIFICATE-----
|
||||
```
|
||||
|
||||
The example tls-auth presents 2 services, foo and bar which comes with the bouncer.
|
||||
At startup, certificates are created in a shared docker volume by a sidecar container which exits after.
|
||||
|
||||
Traefik will use client and CA certificates.
|
||||
The Bouncer will use server and CA certificates.
|
||||
|
||||
The service `whoami-foo` will authenticate with an **API key** over HTTPS after verifying the server certificate with CA.
|
||||
The service `whoami-bar` will authenticate with a **client certificate** signed by the CA.
|
||||
|
||||
Access to a route that communicate via https and authenticate with API-key:
|
||||
```
|
||||
curl http://localhost:80/foo
|
||||
```
|
||||
Access to a route that communicate via https and authenticate with a client certificate:
|
||||
```
|
||||
curl http://localhost:80/bar
|
||||
```
|
||||
Access to the traefik dashboard
|
||||
```
|
||||
curl http://localhost:8080/dashboard/#/
|
||||
```
|
||||
|
||||
To play the demo environnement run:
|
||||
```bash
|
||||
make run_tlsauth
|
||||
```
|
||||
|
||||
Note:
|
||||
> This example is still in Beta and use a new version of Crowdsec (v1.4.3) at time of writing
|
||||
A functionnality has been disabled in Crowdsec in order to make the example work DISABLE_AGENT: "true"
|
||||
@@ -2,11 +2,11 @@ version: "3.8"
|
||||
|
||||
services:
|
||||
traefik:
|
||||
image: "traefik:v2.9.4"
|
||||
image: "traefik:v2.9.6"
|
||||
container_name: "traefik"
|
||||
restart: unless-stopped
|
||||
command:
|
||||
- "--log.level=DEBUG"
|
||||
# - "--log.level=DEBUG"
|
||||
- "--accesslog"
|
||||
- "--accesslog.filepath=/var/log/traefik/access.log"
|
||||
- "--api.insecure=true"
|
||||
@@ -14,11 +14,12 @@ services:
|
||||
- "--providers.docker.exposedbydefault=false"
|
||||
- "--entrypoints.web.address=:80"
|
||||
|
||||
# - "--experimental.plugins.bouncer.modulename=github.com/maxlerebourg/crowdsec-bouncer-traefik-plugin"
|
||||
# - "--experimental.plugins.bouncer.version=v1.1.5"
|
||||
- "--experimental.localplugins.bouncer.modulename=github.com/maxlerebourg/crowdsec-bouncer-traefik-plugin"
|
||||
- "--experimental.plugins.bouncer.modulename=github.com/maxlerebourg/crowdsec-bouncer-traefik-plugin"
|
||||
- "--experimental.plugins.bouncer.version=v1.1.7"
|
||||
# - "--experimental.localplugins.bouncer.modulename=github.com/maxlerebourg/crowdsec-bouncer-traefik-plugin"
|
||||
volumes:
|
||||
- /var/run/docker.sock:/var/run/docker.sock:ro
|
||||
- ./LAPIKEY:/etc/traefik/LAPIKEY:ro
|
||||
- logs-tls-auth:/var/log/traefik
|
||||
- crowdsec-certs-tls-auth:/etc/traefik/crowdsec-certs
|
||||
- ./../../:/plugins-local/src/github.com/maxlerebourg/crowdsec-bouncer-traefik-plugin
|
||||
|
||||
@@ -0,0 +1,39 @@
|
||||
# Exemple
|
||||
## Using Trusted IP (ex: LAN OR VPN) that won't get filtered by crowdsec
|
||||
|
||||
You need to configure your Traefik to trust Forwarded headers by your front proxy
|
||||
In the example we use a whoami container protected by crowdsec, and we ban our IP before allowing using TrustedIPs
|
||||
|
||||
If you are using another proxy in front, you need to add its IP in the trusted IP for the forwarded headers.
|
||||
This helps Traefik choose the right IP of the client: see https://doc.traefik.io/traefik/routing/entrypoints/#forwarded-headers
|
||||
The "internal" Traefik instance is configured to trust the forward headers
|
||||
```yaml
|
||||
- "--entrypoints.web.forwardedheaders.trustedips=172.21.0.5"
|
||||
```
|
||||
|
||||
We configure the middleware to trust as well as the IP of the intermediate proxy if needed:
|
||||
```yaml
|
||||
- "traefik.http.middlewares.crowdsec.plugin.bouncer.forwardedheaderstrustedips=172.21.0.5"
|
||||
```
|
||||
|
||||
Add your IP to the ban list
|
||||
```bash
|
||||
docker exec crowdsec cscli decisions add --ip 10.0.10.30 -d 10m
|
||||
```
|
||||
You should get a 403 on http://localhost/foo
|
||||
|
||||
> Replace *10.0.10.30* by your IP
|
||||
|
||||
Add the IPs that will not be filtered by the plugin
|
||||
```yaml
|
||||
- "traefik.http.middlewares.crowdsec.plugin.bouncer.clientTrustedips=10.0.10.30/32"
|
||||
```
|
||||
|
||||
> Replace *10.0.10.30/32* by your IP or IP range, so it's not getting checked against ban cache of crowdsec
|
||||
|
||||
You should get a 200 on http://localhost/foo even if you are on the ban cache
|
||||
|
||||
To play the demo environment run:
|
||||
```bash
|
||||
make run_trustedips
|
||||
```
|
||||
@@ -2,7 +2,7 @@ version: "3.8"
|
||||
|
||||
services:
|
||||
traefik:
|
||||
image: "traefik:v2.9.4"
|
||||
image: "traefik:v2.9.6"
|
||||
container_name: "traefik"
|
||||
restart: unless-stopped
|
||||
command:
|
||||
@@ -15,7 +15,7 @@ services:
|
||||
- "--entrypoints.web.address=:80"
|
||||
|
||||
- "--experimental.plugins.bouncer.modulename=github.com/maxlerebourg/crowdsec-bouncer-traefik-plugin"
|
||||
- "--experimental.plugins.bouncer.version=v1.1.3"
|
||||
- "--experimental.plugins.bouncer.version=v1.1.7"
|
||||
# - "--experimental.localplugins.bouncer.modulename=github.com/maxlerebourg/crowdsec-bouncer-traefik-plugin"
|
||||
volumes:
|
||||
- /var/run/docker.sock:/var/run/docker.sock:ro
|
||||
@@ -69,7 +69,7 @@ services:
|
||||
|
||||
|
||||
crowdsec:
|
||||
image: crowdsecurity/crowdsec:v1.4.1
|
||||
image: crowdsecurity/crowdsec:v1.4.3
|
||||
container_name: "crowdsec"
|
||||
restart: unless-stopped
|
||||
environment:
|
||||
|
||||
@@ -10,6 +10,7 @@ import (
|
||||
"os"
|
||||
"path/filepath"
|
||||
"reflect"
|
||||
"strings"
|
||||
|
||||
ip "github.com/maxlerebourg/crowdsec-bouncer-traefik-plugin/pkg/ip"
|
||||
logger "github.com/maxlerebourg/crowdsec-bouncer-traefik-plugin/pkg/logger"
|
||||
@@ -106,6 +107,8 @@ func GetVariable(config *Config, key string) (string, error) {
|
||||
}
|
||||
|
||||
// ValidateParams validate all the param gave by user.
|
||||
//
|
||||
//nolint:gocyclo
|
||||
func ValidateParams(config *Config) error {
|
||||
if err := validateParamsRequired(config); err != nil {
|
||||
return err
|
||||
@@ -142,6 +145,13 @@ func ValidateParams(config *Config) error {
|
||||
// We need to either have crowdsecLapiKey defined or the BouncerCert and Bouncerkey
|
||||
if lapiKey == "" && (certBouncer == "" || certBouncerKey == "") {
|
||||
return fmt.Errorf("CrowdsecLapiKey || (CrowdsecLapiTLSCertificateBouncer && CrowdsecLapiTLSCertificateBouncerKey): cannot be both empty")
|
||||
} else if lapiKey != "" && (certBouncer == "" || certBouncerKey == "") {
|
||||
// check LAPIKey
|
||||
lapiKey = strings.TrimSuffix(lapiKey, "\n")
|
||||
err = validateParamsAPIKey(lapiKey)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
// Case https to contact Crowdsec LAPI and certificate must be provided
|
||||
@@ -155,6 +165,109 @@ func ValidateParams(config *Config) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func validateParamsAPIKey(lapiKey string) error {
|
||||
for i := 0; i < len(lapiKey); i++ {
|
||||
c := lapiKey[i]
|
||||
if !validHeaderFieldByte(c) {
|
||||
return fmt.Errorf("CrowdsecLapiKey contains the following forbidden caracter %q", c)
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// validHeaderFieldByte reports whether b is a valid byte in a header
|
||||
// field name. RFC 7230 says:
|
||||
//
|
||||
// header-field = field-name ":" OWS field-value OWS
|
||||
// field-name = token
|
||||
// tchar = "!" / "#" / "$" / "%" / "&" / "'" / "*" / "+" / "-" / "." /
|
||||
// "^" / "_" / "`" / "|" / "~" / DIGIT / ALPHA
|
||||
// token = 1*tchar
|
||||
func validHeaderFieldByte(b byte) bool {
|
||||
// isTokenTable is a copy of net/http/lex.go's isTokenTable.
|
||||
// See https://httpwg.github.io/specs/rfc7230.html#rule.token.separators
|
||||
var isTokenTable = [127]bool{
|
||||
'!': true,
|
||||
'#': true,
|
||||
'$': true,
|
||||
'%': true,
|
||||
'&': true,
|
||||
'\'': true,
|
||||
'*': true,
|
||||
'+': true,
|
||||
'-': true,
|
||||
'.': true,
|
||||
'0': true,
|
||||
'1': true,
|
||||
'2': true,
|
||||
'3': true,
|
||||
'4': true,
|
||||
'5': true,
|
||||
'6': true,
|
||||
'7': true,
|
||||
'8': true,
|
||||
'9': true,
|
||||
'A': true,
|
||||
'B': true,
|
||||
'C': true,
|
||||
'D': true,
|
||||
'E': true,
|
||||
'F': true,
|
||||
'G': true,
|
||||
'H': true,
|
||||
'I': true,
|
||||
'J': true,
|
||||
'K': true,
|
||||
'L': true,
|
||||
'M': true,
|
||||
'N': true,
|
||||
'O': true,
|
||||
'P': true,
|
||||
'Q': true,
|
||||
'R': true,
|
||||
'S': true,
|
||||
'T': true,
|
||||
'U': true,
|
||||
'W': true,
|
||||
'V': true,
|
||||
'X': true,
|
||||
'Y': true,
|
||||
'Z': true,
|
||||
'^': true,
|
||||
'_': true,
|
||||
'`': true,
|
||||
'a': true,
|
||||
'b': true,
|
||||
'c': true,
|
||||
'd': true,
|
||||
'e': true,
|
||||
'f': true,
|
||||
'g': true,
|
||||
'h': true,
|
||||
'i': true,
|
||||
'j': true,
|
||||
'k': true,
|
||||
'l': true,
|
||||
'm': true,
|
||||
'n': true,
|
||||
'o': true,
|
||||
'p': true,
|
||||
'q': true,
|
||||
'r': true,
|
||||
's': true,
|
||||
't': true,
|
||||
'u': true,
|
||||
'v': true,
|
||||
'w': true,
|
||||
'x': true,
|
||||
'y': true,
|
||||
'z': true,
|
||||
'|': true,
|
||||
'~': true,
|
||||
}
|
||||
return int(b) < len(isTokenTable) && isTokenTable[b]
|
||||
}
|
||||
|
||||
func validateParamsTLS(config *Config) error {
|
||||
certAuth, err := GetVariable(config, "CrowdsecLapiTLSCertificateAuthority")
|
||||
if err != nil {
|
||||
|
||||
@@ -24,6 +24,8 @@ func NewChecker(trustedIPs []string) (*Checker, error) {
|
||||
checker := &Checker{}
|
||||
|
||||
for _, ipMask := range trustedIPs {
|
||||
// remove leading and trailing spaces
|
||||
ipMask = strings.TrimSpace(ipMask)
|
||||
if ipAddr := net.ParseIP(ipMask); ipAddr != nil {
|
||||
checker.authorizedIPs = append(checker.authorizedIPs, &ipAddr)
|
||||
logger.Debug(fmt.Sprintf("IP %v is trusted", ipAddr))
|
||||
|
||||
Reference in New Issue
Block a user