Compare commits

...
4 Commits
Author SHA1 Message Date
mathieuHa 041b0f67b7 📝 55 feature clean readme doc (#70)
* 📝 Clean documentation in Readme

* 📝 Update readme

* 📝 Place exemples in seperated README

* ⬆️  Update traefik to 2.9.6 and crowdsec to 1.4.3 in exemples

* 📝 Add exemple at the right place
2022-12-30 23:35:33 +01:00
mathieuHa 5458db93cf 📝 40 Add working exemple on kubernetes (#56)
* 📝 Add initial doc and file structure

* 🍱 Add struct for crowdsec

* 📝 Update doc install cs

* 🚧 Update exemple, add traefik settings

* 📝 Update doc for kubernetes

* 📝 update doc on kubernetes usage

* 📝 Add read logs from traefik

* 📝 Add read logs from traefik

* 📝 Update doc lisibility

* 📝 Update doc with new namespace

* 📝 Update doc with enable plugin in traefik

* 📝 Update doc on kube

* 📝 Finish exemple of Kubernetes

* 📝 Update after review
2022-12-30 22:20:45 +01:00
mathieuHa 29878e837c 🐛 65 confused about configuring tls (#69)
* 🐛 Fix bug reading LAPIKEY, update exemples

* 🚨 Fix lint
2022-12-30 11:24:14 +01:00
mathieuHa aff7c496c2 🐛Remove trailing and leading spaces between each IP or range (#68) 2022-12-29 10:30:19 +01:00
29 changed files with 563 additions and 193 deletions
+13 -167
View File
@@ -8,7 +8,7 @@
This plugin aims to implement a Crowdsec Bouncer in a traefik plugin. 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. > 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. The purpose is to enable treafik to authorize or block requests from IPs based on their reputation and behavior.
@@ -200,7 +200,7 @@ The file variable will be used as preference if both content and file are provid
Format is: Format is:
- Content: VariableName: XXX - Content: VariableName: XXX
- File : VariableNameFILE: /path - File : VariableNameFile: /path
#### Authenticate with LAPI #### Authenticate with LAPI
@@ -241,15 +241,15 @@ docker-compose up -d
#### Use certificates to authenticate with CrowdSec #### 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. 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 #### 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. 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. Set the `crowdsecLapiScheme` to https.
Crowdsec must be listening in HTTPS for this to work. 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/) 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) 1. Behind another proxy service (ex: clouflare)
You need to configure your Traefik to trust Forwarded headers by your front proxy 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)
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. With Redis as an external shared cache 2. With Redis as an external shared cache
The plugin must be configured to connect to a redis instance 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)
```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
```
3. Using Trusted IP (ex: LAN OR VPN) that won't get filtered by crowdsec 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 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)
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
```
4. Using Crowdsec and Traefik installed as binary in a single VM 4. Using Crowdsec and Traefik installed as binary in a single VM
Please see details in `exemples/binary-vm/README.md` 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)
To play the demo environment run:
```bash
make run_binaryvm
```
5. Using https communication and tls authentication with Crowdsec 5. Using https communication and tls authentication with Crowdsec
##### Summary 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)
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. 6. Using Crowdsec and Traefik in Kubernetes
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"
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 ### About
+2
View File
@@ -10,6 +10,7 @@ import (
"io" "io"
"net/http" "net/http"
"net/url" "net/url"
"strings"
"text/template" "text/template"
"time" "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())) logger.Error(fmt.Sprintf("New:crowdsecLapiKey fail to get CrowdsecLapiKey and no client certificate setup %s", err.Error()))
return nil, err return nil, err
} }
apiKey = strings.TrimSuffix(apiKey, "\n")
bouncer := &Bouncer{ bouncer := &Bouncer{
next: next, next: next,
+21
View File
@@ -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: services:
cloudflare: cloudflare:
image: "traefik:v2.9.4" image: "traefik:v2.9.6"
container_name: "cloudflare" container_name: "cloudflare"
restart: unless-stopped restart: unless-stopped
command: command:
@@ -22,7 +22,7 @@ services:
- 8080:8080 - 8080:8080
traefik: traefik:
image: "traefik:v2.9.4" image: "traefik:v2.9.6"
container_name: "traefik" container_name: "traefik"
restart: unless-stopped restart: unless-stopped
command: command:
@@ -36,7 +36,7 @@ services:
- "--entrypoints.web.forwardedheaders.trustedips=172.21.0.5" - "--entrypoints.web.forwardedheaders.trustedips=172.21.0.5"
- "--experimental.plugins.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.3" - "--experimental.plugins.bouncer.version=v1.1.7"
volumes: volumes:
- /var/run/docker.sock:/var/run/docker.sock:ro - /var/run/docker.sock:/var/run/docker.sock:ro
- logs-traefik:/var/log/traefik - logs-traefik:/var/log/traefik
@@ -88,7 +88,7 @@ services:
crowdsec: crowdsec:
image: crowdsecurity/crowdsec:v1.4.1 image: crowdsecurity/crowdsec:v1.4.3
container_name: "crowdsec" container_name: "crowdsec"
restart: unless-stopped restart: unless-stopped
environment: environment:
+2
View File
@@ -1,3 +1,5 @@
# Exemple
### Install vagrant ### Install vagrant
##### On linux ##### On linux
+2
View File
@@ -67,6 +67,8 @@ Vagrant.configure("2") do |config|
# vb.memory = "2048" # vb.memory = "2048"
# end # end
config.vm.provision "file", source: "./files", destination: "/home/vagrant/vagrant_data" 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/install_traefik.sh"
config.vm.provision "shell", path: "scripts/configure_traefik.sh" config.vm.provision "shell", path: "scripts/configure_traefik.sh"
@@ -19,7 +19,7 @@ http:
plugin: plugin:
bouncer: bouncer:
enabled: true enabled: true
crowdseclapikey: whoami-demo crowdseclapikey: "whoami-demo"
updateintervalseconds: 60 updateintervalseconds: 60
crowdsecmode: live crowdsecmode: live
loglevel: "DEBUG" loglevel: "DEBUG"
+1 -1
View File
@@ -31,7 +31,7 @@ experimental:
plugins: plugins:
bouncer: bouncer:
moduleName: github.com/maxlerebourg/crowdsec-bouncer-traefik-plugin moduleName: github.com/maxlerebourg/crowdsec-bouncer-traefik-plugin
version: v1.1.5 version: v1.1.6
################################################################ ################################################################
# Certificate Resolver # Certificate Resolver
+1 -1
View File
@@ -28,4 +28,4 @@ ProtectSystem=full
NoNewPrivileges=true NoNewPrivileges=true
[Install] [Install]
WantedBy=multi-user. WantedBy=multi-user.target
@@ -1,5 +1,5 @@
#!/bin/bash #!/bin/bash
curl -s https://packagecloud.io/install/repositories/crowdsec/crowdsec/script.deb.sh | sudo bash DEBIAN_FRONTEND=noninteractive curl -s https://packagecloud.io/install/repositories/crowdsec/crowdsec/script.deb.sh | sudo bash
sudo apt install crowdsec -y DEBIAN_FRONTEND=noninteractive sudo apt install crowdsec -y
sudo cp /home/vagrant/vagrant_data/crowdsec/acquis.yaml /etc/crowdsec/acquis.yaml sudo cp /home/vagrant/vagrant_data/crowdsec/acquis.yaml /etc/crowdsec/acquis.yaml
@@ -1,7 +1,8 @@
#!/bin/bash #!/bin/bash
sudo apt-get update && apt-get install wget -y && apt-get upgrade -y DEBIAN_FRONTEND=noninteractive sudo apt-get update && sudo apt-get install wget -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 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 tar -zxvf traefik.tar.gz
# inspired from https://gist.github.com/ubergesundheit/7c9d875befc2d7bfd0bf43d8b3862d85 # inspired from https://gist.github.com/ubergesundheit/7c9d875befc2d7bfd0bf43d8b3862d85
sudo mv ./traefik /usr/local/bin/ sudo mv ./traefik /usr/local/bin/
+1 -1
View File
@@ -1,6 +1,6 @@
#!/bin/bash #!/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" 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 tar -zxvf whoami.tar.gz
# inspired from https://gist.github.com/ubergesundheit/7c9d875befc2d7bfd0bf43d8b3862d85 # inspired from https://gist.github.com/ubergesundheit/7c9d875befc2d7bfd0bf43d8b3862d85
+91
View File
@@ -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
```
+14
View File
@@ -0,0 +1,14 @@
apiVersion: v1
kind: Service
metadata:
name: crowdsec
namespace: crowdsec
spec:
ports:
- name: web
port: 8080
targetPort: web
selector:
app: crowdsec
+25
View File
@@ -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
+14
View File
@@ -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
+14
View File
@@ -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
+19
View File
@@ -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
+24
View File
@@ -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
+13
View File
@@ -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: services:
traefik: traefik:
image: "traefik:v2.9.4" image: "traefik:v2.9.6"
container_name: "traefik" container_name: "traefik"
restart: unless-stopped restart: unless-stopped
command: command:
@@ -15,7 +15,7 @@ services:
- "--entrypoints.web.address=:80" - "--entrypoints.web.address=:80"
- "--experimental.plugins.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.4" - "--experimental.plugins.bouncer.version=v1.1.7"
# - "--experimental.localplugins.bouncer.modulename=github.com/maxlerebourg/crowdsec-bouncer-traefik-plugin" # - "--experimental.localplugins.bouncer.modulename=github.com/maxlerebourg/crowdsec-bouncer-traefik-plugin"
volumes: volumes:
- /var/run/docker.sock:/var/run/docker.sock:ro - /var/run/docker.sock:/var/run/docker.sock:ro
@@ -68,7 +68,7 @@ services:
crowdsec: crowdsec:
image: crowdsecurity/crowdsec:v1.4.1 image: crowdsecurity/crowdsec:v1.4.3
container_name: "crowdsec" container_name: "crowdsec"
restart: unless-stopped restart: unless-stopped
environment: environment:
+93
View File
@@ -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: services:
traefik: traefik:
image: "traefik:v2.9.4" image: "traefik:v2.9.6"
container_name: "traefik" container_name: "traefik"
restart: unless-stopped restart: unless-stopped
command: command:
- "--log.level=DEBUG" # - "--log.level=DEBUG"
- "--accesslog" - "--accesslog"
- "--accesslog.filepath=/var/log/traefik/access.log" - "--accesslog.filepath=/var/log/traefik/access.log"
- "--api.insecure=true" - "--api.insecure=true"
@@ -14,11 +14,12 @@ services:
- "--providers.docker.exposedbydefault=false" - "--providers.docker.exposedbydefault=false"
- "--entrypoints.web.address=:80" - "--entrypoints.web.address=:80"
# - "--experimental.plugins.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.5" - "--experimental.plugins.bouncer.version=v1.1.7"
- "--experimental.localplugins.bouncer.modulename=github.com/maxlerebourg/crowdsec-bouncer-traefik-plugin" # - "--experimental.localplugins.bouncer.modulename=github.com/maxlerebourg/crowdsec-bouncer-traefik-plugin"
volumes: volumes:
- /var/run/docker.sock:/var/run/docker.sock:ro - /var/run/docker.sock:/var/run/docker.sock:ro
- ./LAPIKEY:/etc/traefik/LAPIKEY:ro
- logs-tls-auth:/var/log/traefik - logs-tls-auth:/var/log/traefik
- crowdsec-certs-tls-auth:/etc/traefik/crowdsec-certs - crowdsec-certs-tls-auth:/etc/traefik/crowdsec-certs
- ./../../:/plugins-local/src/github.com/maxlerebourg/crowdsec-bouncer-traefik-plugin - ./../../:/plugins-local/src/github.com/maxlerebourg/crowdsec-bouncer-traefik-plugin
+39
View File
@@ -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: services:
traefik: traefik:
image: "traefik:v2.9.4" image: "traefik:v2.9.6"
container_name: "traefik" container_name: "traefik"
restart: unless-stopped restart: unless-stopped
command: command:
@@ -15,7 +15,7 @@ services:
- "--entrypoints.web.address=:80" - "--entrypoints.web.address=:80"
- "--experimental.plugins.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.3" - "--experimental.plugins.bouncer.version=v1.1.7"
# - "--experimental.localplugins.bouncer.modulename=github.com/maxlerebourg/crowdsec-bouncer-traefik-plugin" # - "--experimental.localplugins.bouncer.modulename=github.com/maxlerebourg/crowdsec-bouncer-traefik-plugin"
volumes: volumes:
- /var/run/docker.sock:/var/run/docker.sock:ro - /var/run/docker.sock:/var/run/docker.sock:ro
@@ -69,7 +69,7 @@ services:
crowdsec: crowdsec:
image: crowdsecurity/crowdsec:v1.4.1 image: crowdsecurity/crowdsec:v1.4.3
container_name: "crowdsec" container_name: "crowdsec"
restart: unless-stopped restart: unless-stopped
environment: environment:
+113
View File
@@ -10,6 +10,7 @@ import (
"os" "os"
"path/filepath" "path/filepath"
"reflect" "reflect"
"strings"
ip "github.com/maxlerebourg/crowdsec-bouncer-traefik-plugin/pkg/ip" ip "github.com/maxlerebourg/crowdsec-bouncer-traefik-plugin/pkg/ip"
logger "github.com/maxlerebourg/crowdsec-bouncer-traefik-plugin/pkg/logger" 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. // ValidateParams validate all the param gave by user.
//
//nolint:gocyclo
func ValidateParams(config *Config) error { func ValidateParams(config *Config) error {
if err := validateParamsRequired(config); err != nil { if err := validateParamsRequired(config); err != nil {
return err return err
@@ -142,6 +145,13 @@ func ValidateParams(config *Config) error {
// We need to either have crowdsecLapiKey defined or the BouncerCert and Bouncerkey // We need to either have crowdsecLapiKey defined or the BouncerCert and Bouncerkey
if lapiKey == "" && (certBouncer == "" || certBouncerKey == "") { if lapiKey == "" && (certBouncer == "" || certBouncerKey == "") {
return fmt.Errorf("CrowdsecLapiKey || (CrowdsecLapiTLSCertificateBouncer && CrowdsecLapiTLSCertificateBouncerKey): cannot be both empty") 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 // Case https to contact Crowdsec LAPI and certificate must be provided
@@ -155,6 +165,109 @@ func ValidateParams(config *Config) error {
return nil 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 { func validateParamsTLS(config *Config) error {
certAuth, err := GetVariable(config, "CrowdsecLapiTLSCertificateAuthority") certAuth, err := GetVariable(config, "CrowdsecLapiTLSCertificateAuthority")
if err != nil { if err != nil {
+2
View File
@@ -24,6 +24,8 @@ func NewChecker(trustedIPs []string) (*Checker, error) {
checker := &Checker{} checker := &Checker{}
for _, ipMask := range trustedIPs { for _, ipMask := range trustedIPs {
// remove leading and trailing spaces
ipMask = strings.TrimSpace(ipMask)
if ipAddr := net.ParseIP(ipMask); ipAddr != nil { if ipAddr := net.ParseIP(ipMask); ipAddr != nil {
checker.authorizedIPs = append(checker.authorizedIPs, &ipAddr) checker.authorizedIPs = append(checker.authorizedIPs, &ipAddr)
logger.Debug(fmt.Sprintf("IP %v is trusted", ipAddr)) logger.Debug(fmt.Sprintf("IP %v is trusted", ipAddr))