mirror of
https://github.com/maxlerebourg/crowdsec-bouncer-traefik-plugin.git
synced 2026-09-02 20:28:50 +02:00
Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
6c183d9231 | ||
|
|
bd71b58f19 | ||
|
|
b68c692ed1 | ||
|
|
fc3da2fc2d | ||
|
|
1a9bdc578f | ||
|
|
f2aea695fc | ||
|
|
2827fef273 | ||
|
|
07c8fae927 | ||
|
|
13c209be3f | ||
|
|
93340ffa55 | ||
|
|
0c2668d578 | ||
|
|
abae7ee028 | ||
|
|
1fcd4f4e2f | ||
|
|
39fcc38980 | ||
|
|
dd322a966a | ||
|
|
f0bb140596 | ||
|
|
46e581eca2 | ||
|
|
50690d1ac7 | ||
|
|
b079073ff6 |
@@ -30,4 +30,6 @@ Steps to reproduce the behavior:
|
|||||||
3. Scroll down to '....'
|
3. Scroll down to '....'
|
||||||
4. See error
|
4. See error
|
||||||
|
|
||||||
|
<!---
|
||||||
If you like the plugin, please consider starring it, so you can get updates and we get some more visibility ✨
|
If you like the plugin, please consider starring it, so you can get updates and we get some more visibility ✨
|
||||||
|
-->
|
||||||
|
|||||||
@@ -16,4 +16,6 @@ A clear and concise description of what you want to happen.
|
|||||||
**Additional context**
|
**Additional context**
|
||||||
Add any other context or screenshots about the feature request here.
|
Add any other context or screenshots about the feature request here.
|
||||||
|
|
||||||
|
<!---
|
||||||
If you like the plugin, please consider starring it, so you can get updates and we get some more visibility ✨
|
If you like the plugin, please consider starring it, so you can get updates and we get some more visibility ✨
|
||||||
|
-->
|
||||||
|
|||||||
@@ -20,69 +20,76 @@ clean:
|
|||||||
rm -rf ./vendor
|
rm -rf ./vendor
|
||||||
|
|
||||||
run_dev:
|
run_dev:
|
||||||
docker-compose -f docker-compose.dev.yml up -d --remove-orphans
|
docker compose -f docker-compose.dev.yml up -d --remove-orphans
|
||||||
|
|
||||||
run_local:
|
run_local:
|
||||||
docker-compose -f docker-compose.local.yml up -d --remove-orphans
|
docker compose -f docker-compose.local.yml up -d --remove-orphans
|
||||||
|
|
||||||
run_behindproxy:
|
run_behindproxy:
|
||||||
docker-compose -f exemples/behind-proxy/docker-compose.cloudflare.yml up -d --remove-orphans
|
docker compose -f examples/behind-proxy/docker-compose.cloudflare.yml up -d --remove-orphans
|
||||||
|
|
||||||
run_cacheredis:
|
run_cacheredis:
|
||||||
docker-compose -f exemples/redis-cache/docker-compose.redis.yml up -d --remove-orphans
|
docker compose -f examples/redis-cache/docker-compose.redis.yml up -d --remove-orphans
|
||||||
|
|
||||||
run_trustedips:
|
run_trustedips:
|
||||||
docker-compose -f exemples/trusted-ips/docker-compose.trusted.yml up -d --remove-orphans
|
docker compose -f examples/trusted-ips/docker-compose.trusted.yml up -d --remove-orphans
|
||||||
|
|
||||||
run_binaryvm:
|
run_binaryvm:
|
||||||
cd exemples/binary-vm/ && sudo vagrant up
|
cd examples/binary-vm/ && sudo vagrant up
|
||||||
|
|
||||||
run_tlsauth:
|
run_tlsauth:
|
||||||
docker-compose -f exemples/tls-auth/docker-compose.tls-auth.yml down && docker-compose -f exemples/tls-auth/docker-compose.tls-auth.yml up -d && docker-compose -f exemples/tls-auth/docker-compose.tls-auth.yml restart && docker-compose -f exemples/tls-auth/docker-compose.tls-auth.yml logs -f
|
docker compose -f examples/tls-auth/docker-compose.tls-auth.yml down && docker compose -f examples/tls-auth/docker-compose.tls-auth.yml up -d && docker compose -f examples/tls-auth/docker-compose.tls-auth.yml restart && docker compose -f examples/tls-auth/docker-compose.tls-auth.yml logs -f
|
||||||
|
|
||||||
|
run_appsec:
|
||||||
|
docker compose -f examples/appsec-enabled/docker-compose.appsec-enabled.yml up -d
|
||||||
|
|
||||||
run:
|
run:
|
||||||
docker-compose -f docker-compose.yml up -d --remove-orphans
|
docker compose -f docker-compose.yml up -d --remove-orphans
|
||||||
|
|
||||||
restart_dev:
|
restart_dev:
|
||||||
docker-compose -f docker-compose.dev.yml restart
|
docker compose -f docker-compose.dev.yml restart
|
||||||
|
|
||||||
restart_local:
|
restart_local:
|
||||||
docker-compose -f docker-compose.local.yml restart
|
docker compose -f docker-compose.local.yml restart
|
||||||
|
|
||||||
restart:
|
restart:
|
||||||
docker-compose -f docker-compose.yml restart
|
docker compose -f docker-compose.yml restart
|
||||||
|
|
||||||
restart_behindproxy:
|
restart_behindproxy:
|
||||||
docker-compose -f exemples/behind-proxy/docker-compose.cloudflare.yml restart
|
docker compose -f examples/behind-proxy/docker-compose.cloudflare.yml restart
|
||||||
|
|
||||||
restart_cacheredis:
|
restart_cacheredis:
|
||||||
docker-compose -f exemples/redis-cache/docker-compose.redis.yml restart
|
docker compose -f examples/redis-cache/docker-compose.redis.yml restart
|
||||||
|
|
||||||
restart_trustedips:
|
restart_trustedips:
|
||||||
docker-compose -f exemples/trusted-ips/docker-compose.trusted.yml restart
|
docker compose -f examples/trusted-ips/docker-compose.trusted.yml restart
|
||||||
|
|
||||||
restart_tlsauth:
|
restart_tlsauth:
|
||||||
docker-compose -f exemples/tls-auth/docker-compose.tls-auth.yml
|
docker compose -f examples/tls-auth/docker-compose.tls-auth.yml
|
||||||
|
|
||||||
|
restart_appsec:
|
||||||
|
docker compose -f examples/tls-auth/docker-compose.appsec-enabled.yml
|
||||||
|
|
||||||
show_logs:
|
show_logs:
|
||||||
docker-compose -f docker-compose.yml restart
|
docker compose -f docker-compose.yml restart
|
||||||
|
|
||||||
show_local_logs:
|
show_local_logs:
|
||||||
docker-compose -f docker-compose.local.yml logs -f
|
docker compose -f docker-compose.local.yml logs -f
|
||||||
|
|
||||||
show_dev_logs:
|
show_dev_logs:
|
||||||
docker-compose -f docker-compose.dev.yml logs -f
|
docker compose -f docker-compose.dev.yml logs -f
|
||||||
|
|
||||||
clean_all_docker:
|
clean_all_docker:
|
||||||
docker-compose -f exemples/behind-proxy/docker-compose.cloudflare.yml down --remove-orphans
|
docker compose -f examples/behind-proxy/docker-compose.cloudflare.yml down --remove-orphans
|
||||||
docker-compose -f exemples/redis-cache/docker-compose.redis.yml down --remove-orphans
|
docker compose -f examples/redis-cache/docker-compose.redis.yml down --remove-orphans
|
||||||
docker-compose -f exemples/trusted-ips/docker-compose.trusted.yml down --remove-orphans
|
docker compose -f examples/trusted-ips/docker-compose.trusted.yml down --remove-orphans
|
||||||
docker-compose -f exemples/tls-auth/docker-compose.tls-auth.yml down --remove-orphans
|
docker compose -f examples/tls-auth/docker-compose.tls-auth.yml down --remove-orphans
|
||||||
docker-compose -f docker-compose.local.yml down --remove-orphans
|
docker compose -f examples/appsec-enabled/docker-compose.appsec-enabled.yml down --remove-orphans
|
||||||
docker-compose -f docker-compose.yml down --remove-orphans
|
docker compose -f docker-compose.local.yml down --remove-orphans
|
||||||
|
docker compose -f docker-compose.yml down --remove-orphans
|
||||||
|
|
||||||
clean_vagrant:
|
clean_vagrant:
|
||||||
cd exemples/binary-vm/ && sudo vagrant destroy -f
|
cd examples/binary-vm/ && sudo vagrant destroy -f
|
||||||
|
|
||||||
|
|
||||||
show_metrics:
|
show_metrics:
|
||||||
|
|||||||
@@ -6,6 +6,8 @@
|
|||||||
|
|
||||||
# Crowdsec Bouncer Traefik plugin
|
# Crowdsec Bouncer Traefik plugin
|
||||||
|
|
||||||
|
> New! This plugin now supports [AppSec](https://doc.crowdsec.net/docs/next/appsec/intro/) feature including virtual patching and capabilities support for your legacy ModSecurity rules.
|
||||||
|
|
||||||
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.
|
||||||
@@ -17,6 +19,16 @@ The Crowdsec utility will provide the community blocklist which contains highly
|
|||||||
|
|
||||||
When used with Crowdsec it will leverage the local API which will analyze Traefik logs and take decisions on the requests made by users/bots. Malicious actors will be banned based on patterns used against your website.
|
When used with Crowdsec it will leverage the local API which will analyze Traefik logs and take decisions on the requests made by users/bots. Malicious actors will be banned based on patterns used against your website.
|
||||||
|
|
||||||
|
Appsec feature is supported from plugin version 1.2.0 and Crowdsec 1.6.0.
|
||||||
|
|
||||||
|
The AppSec Component offers:
|
||||||
|
|
||||||
|
- Low-effort virtual patching capabilities.
|
||||||
|
- Support for your legacy ModSecurity rules.
|
||||||
|
- Combining classic WAF benefits with advanced CrowdSec features for otherwise difficult advanced behavior detection.
|
||||||
|
More information on appsec in the [Crowdsec Documentation](https://doc.crowdsec.net/docs/next/appsec/intro/).
|
||||||
|
|
||||||
|
|
||||||
There are 4 operating modes (CrowdsecMode) for this plugin:
|
There are 4 operating modes (CrowdsecMode) for this plugin:
|
||||||
|
|
||||||
| Mode | Description |
|
| Mode | Description |
|
||||||
@@ -24,12 +36,13 @@ There are 4 operating modes (CrowdsecMode) for this plugin:
|
|||||||
| none | If the client IP is on ban list, it will get a http code 403 response. Otherwise, request will continue as usual. All request call the Crowdsec LAPI |
|
| none | If the client IP is on ban list, it will get a http code 403 response. Otherwise, request will continue as usual. All request call the Crowdsec LAPI |
|
||||||
| live | If the client IP is on ban list, it will get a http code 403 response. Otherwise, request will continue as usual. The bouncer can leverage use of a local cache in order to reduce the number of requests made to the Crowdsec LAPI. It will keep in cache the status for each IP that makes queries. |
|
| live | If the client IP is on ban list, it will get a http code 403 response. Otherwise, request will continue as usual. The bouncer can leverage use of a local cache in order to reduce the number of requests made to the Crowdsec LAPI. It will keep in cache the status for each IP that makes queries. |
|
||||||
| stream | Stream Streaming mode allows you to keep in the local cache only the Banned IPs, every requests that does not hit the cache is authorized. Every minute, the cache is updated with news from the Crowdsec LAPI. |
|
| stream | Stream Streaming mode allows you to keep in the local cache only the Banned IPs, every requests that does not hit the cache is authorized. Every minute, the cache is updated with news from the Crowdsec LAPI. |
|
||||||
| alone | Standalone mode, similar to the streaming mode but the blacklisted IPs are fetched on the CAPI. Every 2 hours, the cache is updated with news from the Crowdsec CAPI. It does not include any localy banned IP, but can work without a crowdsec service. |
|
| alone | Standalone mode, similar to the streaming mode but the blacklisted IPs are fetched on the CAPI. Every 2 hours, the cache is updated with news from the Crowdsec CAPI. It does not include any locally banned IP, but can work without a crowdsec service. |
|
||||||
|
| appsec | Disable Crowdsec IP checking but apply Crowdsec Appsec checking. This mode is intended to be used when Crowdsec IP checking is applied at the Firewall Level. |
|
||||||
|
|
||||||
The `streaming mode` is recommended for performance, decisions are updated every 60 sec by default and that's the only communication between Traefik and Crowdsec. Every request that happens hits the cache for quick decisions.
|
The `streaming mode` is recommended for performance, decisions are updated every 60 sec by default and that's the only communication between Traefik and Crowdsec. Every request that happens hits the cache for quick decisions.
|
||||||
|
|
||||||
The cache can be local to Traefik using the filesystem, or a separate Redis instance.
|
The cache can be local to Traefik using the filesystem, or a separate Redis instance.
|
||||||
Support for Redis is currently in beta (requires version 7.0.X of Redis).
|
|
||||||
|
|
||||||
## Usage
|
## Usage
|
||||||
|
|
||||||
@@ -44,18 +57,31 @@ make run
|
|||||||
|
|
||||||
**/!\ Cache is shared by all services**
|
**/!\ Cache is shared by all services**
|
||||||
*This means if an IP is banned, all services which are protected by an instance of the plugin will deny requests from that IP*
|
*This means if an IP is banned, all services which are protected by an instance of the plugin will deny requests from that IP*
|
||||||
|
Only one instance of the plugin is *possible*.
|
||||||
|
|
||||||
### Variables
|
### Variables
|
||||||
- Enabled
|
- Enabled
|
||||||
- bool
|
- bool
|
||||||
- default: false
|
- default: false
|
||||||
- enable the plugin
|
- Enable the plugin
|
||||||
- LogLevel
|
- LogLevel
|
||||||
- string
|
- string
|
||||||
- default: `INFO`, expected values are: `INFO`, `DEBUG`
|
- default: `INFO`, expected values are: `INFO`, `DEBUG`
|
||||||
- CrowdsecMode
|
- CrowdsecMode
|
||||||
- string
|
- string
|
||||||
- default: `live`, expected values are: `none`, `live`, `stream`, `alone`
|
- default: `live`, expected values are: `none`, `live`, `stream`, `alone`, `appsec`
|
||||||
|
- CrowdsecAppsecEnabled
|
||||||
|
- bool
|
||||||
|
- default: false
|
||||||
|
- Enable Crowdsec Appsec Server (WAF).
|
||||||
|
- CrowdsecAppsecHost
|
||||||
|
- string
|
||||||
|
- default: "crowdsec:7422"
|
||||||
|
- Crowdsec Appsec Server available on which host and port. The scheme will be handled by the CrowdsecLapiScheme var.
|
||||||
|
- CrowdsecAppsecFailureBlock
|
||||||
|
- bool
|
||||||
|
- default: true
|
||||||
|
- Block request when Crowdsec Appsec Server have a [status 500](https://docs.crowdsec.net/docs/next/appsec/protocol#response-code).
|
||||||
- CrowdsecLapiScheme
|
- CrowdsecLapiScheme
|
||||||
- string
|
- string
|
||||||
- default: `http`, expected values are: `http`, `https`
|
- default: `http`, expected values are: `http`, `https`
|
||||||
@@ -103,6 +129,18 @@ make run
|
|||||||
- string
|
- string
|
||||||
- default: "redis:6379"
|
- default: "redis:6379"
|
||||||
- hostname and port for the Redis service
|
- hostname and port for the Redis service
|
||||||
|
- RedisCachePassword
|
||||||
|
- string
|
||||||
|
- default: ""
|
||||||
|
- Password for the Redis service
|
||||||
|
- RedisCacheDatabase
|
||||||
|
- string
|
||||||
|
- default: ""
|
||||||
|
- Database selection for the Redis service
|
||||||
|
- HTTPTimeoutSeconds
|
||||||
|
- int64
|
||||||
|
- default: 10
|
||||||
|
- Default timeout in seconds for contacting Crowdsec LAPI
|
||||||
- UpdateIntervalSeconds
|
- UpdateIntervalSeconds
|
||||||
- int64
|
- int64
|
||||||
- default: 60
|
- default: 60
|
||||||
@@ -134,6 +172,7 @@ experimental:
|
|||||||
plugins:
|
plugins:
|
||||||
bouncer:
|
bouncer:
|
||||||
moduleName: github.com/maxlerebourg/crowdsec-bouncer-traefik-plugin
|
moduleName: github.com/maxlerebourg/crowdsec-bouncer-traefik-plugin
|
||||||
|
version: vX.Y.Z # To update
|
||||||
```
|
```
|
||||||
|
|
||||||
```yaml
|
```yaml
|
||||||
@@ -163,7 +202,11 @@ http:
|
|||||||
logLevel: DEBUG
|
logLevel: DEBUG
|
||||||
updateIntervalSeconds: 60
|
updateIntervalSeconds: 60
|
||||||
defaultDecisionSeconds: 60
|
defaultDecisionSeconds: 60
|
||||||
|
httpTimeoutSeconds: 10
|
||||||
crowdsecMode: live
|
crowdsecMode: live
|
||||||
|
crowdsecAppsecEnabled: false
|
||||||
|
crowdsecAppsecHost: crowdsec:7422
|
||||||
|
crowdsecAppsecFailureBlock: true
|
||||||
crowdsecLapiKey: privateKey-foo
|
crowdsecLapiKey: privateKey-foo
|
||||||
crowdsecLapiKeyFile: /etc/traefik/cs-privateKey-foo
|
crowdsecLapiKeyFile: /etc/traefik/cs-privateKey-foo
|
||||||
crowdsecLapiHost: crowdsec:8080
|
crowdsecLapiHost: crowdsec:8080
|
||||||
@@ -183,6 +226,8 @@ http:
|
|||||||
forwardedHeadersCustomName: X-Custom-Header
|
forwardedHeadersCustomName: X-Custom-Header
|
||||||
redisCacheEnabled: false
|
redisCacheEnabled: false
|
||||||
redisCacheHost: "redis:6379"
|
redisCacheHost: "redis:6379"
|
||||||
|
redisCachePassword: password
|
||||||
|
redisCacheDatabase: "5"
|
||||||
crowdsecLapiTLSCertificateAuthority: |-
|
crowdsecLapiTLSCertificateAuthority: |-
|
||||||
-----BEGIN CERTIFICATE-----
|
-----BEGIN CERTIFICATE-----
|
||||||
MIIEBzCCAu+gAwIBAgICEAAwDQYJKoZIhvcNAQELBQAwgZQxCzAJBgNVBAYTAlVT
|
MIIEBzCCAu+gAwIBAgICEAAwDQYJKoZIhvcNAQELBQAwgZQxCzAJBgNVBAYTAlVT
|
||||||
@@ -255,10 +300,10 @@ 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 `examples/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 `examples/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
|
||||||
|
|
||||||
@@ -266,7 +311,7 @@ To communicate with the LAPI in HTTPS you need to either accept any certificates
|
|||||||
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](https://github.com/maxlerebourg/crowdsec-bouncer-traefik-plugin/blob/main/exemples/tls-auth/README.md) or the official documentation: [docs.crowdsec.net/docs/local_api/tls_auth/](https://docs.crowdsec.net/docs/local_api/tls_auth/)
|
Please see the [tls-auth example](https://github.com/maxlerebourg/crowdsec-bouncer-traefik-plugin/blob/main/examples/tls-auth/README.md) or the official documentation: [docs.crowdsec.net/docs/local_api/tls_auth/](https://docs.crowdsec.net/docs/local_api/tls_auth/)
|
||||||
|
|
||||||
#### Manually add an IP to the blocklist (for testing purposes)
|
#### Manually add an IP to the blocklist (for testing purposes)
|
||||||
|
|
||||||
@@ -276,21 +321,25 @@ docker exec crowdsec cscli decisions add --ip 10.0.0.10 -d 10m # this will be ef
|
|||||||
docker exec crowdsec cscli decisions remove --ip 10.0.0.10
|
docker exec crowdsec cscli decisions remove --ip 10.0.0.10
|
||||||
```
|
```
|
||||||
|
|
||||||
### Exemples
|
### Examples
|
||||||
|
|
||||||
#### 1. Behind another proxy service (ex: clouflare) [exemples/behind-proxy/README.md](https://github.com/maxlerebourg/crowdsec-bouncer-traefik-plugin/blob/main/exemples/behind-proxy/README.md)
|
#### 1. Behind another proxy service (ex: clouflare) [examples/behind-proxy/README.md](https://github.com/maxlerebourg/crowdsec-bouncer-traefik-plugin/blob/main/examples/behind-proxy/README.md)
|
||||||
|
|
||||||
#### 2. With Redis as an external shared cache [exemples/redis-cache/README.md](https://github.com/maxlerebourg/crowdsec-bouncer-traefik-plugin/blob/main/exemples/redis-cache/README.md)
|
#### 2. With Redis as an external shared cache [examples/redis-cache/README.md](https://github.com/maxlerebourg/crowdsec-bouncer-traefik-plugin/blob/main/examples/redis-cache/README.md)
|
||||||
|
|
||||||
#### 3. Using Trusted IP (ex: LAN OR VPN) that won't get filtered by crowdsec [exemples/trusted-ips/README.md](https://github.com/maxlerebourg/crowdsec-bouncer-traefik-plugin/blob/main/exemples/trusted-ips/README.md)
|
#### 3. Using Trusted IP (ex: LAN OR VPN) that won't get filtered by crowdsec [examples/trusted-ips/README.md](https://github.com/maxlerebourg/crowdsec-bouncer-traefik-plugin/blob/main/examples/trusted-ips/README.md)
|
||||||
|
|
||||||
#### 4. Using Crowdsec and Traefik installed as binary in a single VM [exemples/binary-vm/README.md](https://github.com/maxlerebourg/crowdsec-bouncer-traefik-plugin/blob/main/exemples/binary-vm/README.md)
|
#### 4. Using Crowdsec and Traefik installed as binary in a single VM [examples/binary-vm/README.md](https://github.com/maxlerebourg/crowdsec-bouncer-traefik-plugin/blob/main/examples/binary-vm/README.md)
|
||||||
|
|
||||||
#### 5. Using https communication and tls authentication with Crowdsec [exemples/tls-auth/README.md](https://github.com/maxlerebourg/crowdsec-bouncer-traefik-plugin/blob/main/exemples/tls-auth/README.md)
|
#### 5. Using https communication and tls authentication with Crowdsec [examples/tls-auth/README.md](https://github.com/maxlerebourg/crowdsec-bouncer-traefik-plugin/blob/main/examples/tls-auth/README.md)
|
||||||
|
|
||||||
#### 6. Using Crowdsec and Traefik in Kubernetes [exemples/kubernetes/README.md](https://github.com/maxlerebourg/crowdsec-bouncer-traefik-plugin/blob/main/exemples/kubernetes/README.md)
|
#### 6. Using Crowdsec and Traefik in Kubernetes [examples/kubernetes/README.md](https://github.com/maxlerebourg/crowdsec-bouncer-traefik-plugin/blob/main/examples/kubernetes/README.md)
|
||||||
|
|
||||||
|
#### 7. Using Traefik in standalone mode without Crowdsec [examples/standalone-mode/README.md](https://github.com/maxlerebourg/crowdsec-bouncer-traefik-plugin/blob/main/examples/standalone-mode/README.md)
|
||||||
|
|
||||||
|
|
||||||
|
#### 8. Using Traefik with AppSec feature enabled [examples/appsec-enabled/README.md](https://github.com/maxlerebourg/crowdsec-bouncer-traefik-plugin/blob/main/examples/appsec-enabled/README.md)
|
||||||
|
|
||||||
#### 7. Using Traefik in standalone mode without Crowdsec [exemples/standalone-mode/README.md](https://github.com/maxlerebourg/crowdsec-bouncer-traefik-plugin/blob/main/exemples/standalone-mode/README.md)
|
|
||||||
|
|
||||||
### Local Mode
|
### Local Mode
|
||||||
|
|
||||||
|
|||||||
@@ -1,4 +1,13 @@
|
|||||||
|
---
|
||||||
filenames:
|
filenames:
|
||||||
- /var/log/traefik/access.log
|
- /var/log/traefik/access.log
|
||||||
labels:
|
labels:
|
||||||
type: traefik
|
type: traefik
|
||||||
|
|
||||||
|
---
|
||||||
|
listen_addr: 0.0.0.0:7422
|
||||||
|
appsec_config: crowdsecurity/virtual-patching
|
||||||
|
name: myAppSecComponent
|
||||||
|
source: appsec
|
||||||
|
labels:
|
||||||
|
type: appsec
|
||||||
|
|||||||
+136
-36
@@ -22,13 +22,19 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
crowdsecLapiHeader = "X-Api-Key"
|
crowdsecAppsecIPHeader = "X-Crowdsec-Appsec-Ip"
|
||||||
crowdsecCapiHeader = "Authorization"
|
crowdsecAppsecURIHeader = "X-Crowdsec-Appsec-Uri"
|
||||||
crowdsecLapiRoute = "v1/decisions"
|
crowdsecAppsecHostHeader = "X-Crowdsec-Appsec-Host"
|
||||||
crowdsecLapiStreamRoute = "v1/decisions/stream"
|
crowdsecAppsecVerbHeader = "X-Crowdsec-Appsec-Verb"
|
||||||
crowdsecCapiLogin = "v2/watchers/login"
|
crowdsecAppsecHeader = "X-Crowdsec-Appsec-Api-Key"
|
||||||
crowdsecCapiStreamRoute = "v2/decisions/stream"
|
crowdsecLapiHeader = "X-Api-Key"
|
||||||
cacheTimeoutKey = "updated"
|
crowdsecLapiRoute = "v1/decisions"
|
||||||
|
crowdsecLapiStreamRoute = "v1/decisions/stream"
|
||||||
|
crowdsecCapiHost = "api.crowdsec.net"
|
||||||
|
crowdsecCapiHeader = "Authorization"
|
||||||
|
crowdsecCapiLoginRoute = "v2/watchers/login"
|
||||||
|
crowdsecCapiStreamRoute = "v2/decisions/stream"
|
||||||
|
cacheTimeoutKey = "updated"
|
||||||
)
|
)
|
||||||
|
|
||||||
//nolint:gochecknoglobals
|
//nolint:gochecknoglobals
|
||||||
@@ -50,6 +56,9 @@ type Bouncer struct {
|
|||||||
template *template.Template
|
template *template.Template
|
||||||
|
|
||||||
enabled bool
|
enabled bool
|
||||||
|
appsecEnabled bool
|
||||||
|
appsecHost string
|
||||||
|
appsecFailureBlock bool
|
||||||
crowdsecScheme string
|
crowdsecScheme string
|
||||||
crowdsecHost string
|
crowdsecHost string
|
||||||
crowdsecKey string
|
crowdsecKey string
|
||||||
@@ -86,9 +95,9 @@ func New(ctx context.Context, next http.Handler, config *configuration.Config, n
|
|||||||
if config.CrowdsecMode == configuration.AloneMode {
|
if config.CrowdsecMode == configuration.AloneMode {
|
||||||
config.CrowdsecCapiMachineID, _ = configuration.GetVariable(config, "CrowdsecCapiMachineID")
|
config.CrowdsecCapiMachineID, _ = configuration.GetVariable(config, "CrowdsecCapiMachineID")
|
||||||
config.CrowdsecCapiPassword, _ = configuration.GetVariable(config, "CrowdsecCapiPassword")
|
config.CrowdsecCapiPassword, _ = configuration.GetVariable(config, "CrowdsecCapiPassword")
|
||||||
config.CrowdsecLapiHost = "api.crowdsec.net"
|
config.CrowdsecLapiHost = crowdsecCapiHost
|
||||||
config.CrowdsecLapiScheme = "https"
|
config.CrowdsecLapiScheme = "https"
|
||||||
config.UpdateIntervalSeconds = 7200
|
config.UpdateIntervalSeconds = 7200 // 2 hours
|
||||||
crowdsecStreamRoute = crowdsecCapiStreamRoute
|
crowdsecStreamRoute = crowdsecCapiStreamRoute
|
||||||
crowdsecHeader = crowdsecCapiHeader
|
crowdsecHeader = crowdsecCapiHeader
|
||||||
} else {
|
} else {
|
||||||
@@ -114,6 +123,9 @@ func New(ctx context.Context, next http.Handler, config *configuration.Config, n
|
|||||||
|
|
||||||
enabled: config.Enabled,
|
enabled: config.Enabled,
|
||||||
crowdsecMode: config.CrowdsecMode,
|
crowdsecMode: config.CrowdsecMode,
|
||||||
|
appsecEnabled: config.CrowdsecAppsecEnabled,
|
||||||
|
appsecHost: config.CrowdsecAppsecHost,
|
||||||
|
appsecFailureBlock: config.CrowdsecAppsecFailureBlock,
|
||||||
crowdsecScheme: config.CrowdsecLapiScheme,
|
crowdsecScheme: config.CrowdsecLapiScheme,
|
||||||
crowdsecHost: config.CrowdsecLapiHost,
|
crowdsecHost: config.CrowdsecLapiHost,
|
||||||
crowdsecKey: config.CrowdsecLapiKey,
|
crowdsecKey: config.CrowdsecLapiKey,
|
||||||
@@ -137,25 +149,33 @@ func New(ctx context.Context, next http.Handler, config *configuration.Config, n
|
|||||||
IdleConnTimeout: 30 * time.Second,
|
IdleConnTimeout: 30 * time.Second,
|
||||||
TLSClientConfig: tlsConfig,
|
TLSClientConfig: tlsConfig,
|
||||||
},
|
},
|
||||||
Timeout: 10 * time.Second,
|
Timeout: time.Duration(config.HTTPTimeoutSeconds) * time.Second,
|
||||||
},
|
},
|
||||||
cacheClient: &cache.Client{},
|
cacheClient: &cache.Client{},
|
||||||
}
|
}
|
||||||
bouncer.cacheClient.New(config.RedisCacheEnabled, config.RedisCacheHost)
|
if config.CrowdsecMode == configuration.AppsecMode {
|
||||||
|
return bouncer, nil
|
||||||
|
}
|
||||||
|
config.RedisCachePassword, _ = configuration.GetVariable(config, "RedisCachePassword")
|
||||||
|
bouncer.cacheClient.New(
|
||||||
|
config.RedisCacheEnabled,
|
||||||
|
config.RedisCacheHost,
|
||||||
|
config.RedisCachePassword,
|
||||||
|
config.RedisCacheDatabase,
|
||||||
|
)
|
||||||
|
|
||||||
if (config.CrowdsecMode == configuration.StreamMode || config.CrowdsecMode == configuration.AloneMode) && ticker == nil {
|
if (config.CrowdsecMode == configuration.StreamMode || config.CrowdsecMode == configuration.AloneMode) && ticker == nil {
|
||||||
if config.CrowdsecMode == configuration.AloneMode {
|
if config.CrowdsecMode == configuration.AloneMode {
|
||||||
err = getToken(bouncer)
|
if err := getToken(bouncer); err != nil {
|
||||||
if err != nil {
|
|
||||||
logger.Error(fmt.Sprintf("New:getToken %s", err.Error()))
|
logger.Error(fmt.Sprintf("New:getToken %s", err.Error()))
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
ticker = startTicker(config, func() {
|
handleStreamTicker(bouncer)
|
||||||
handleStreamCache(bouncer)
|
|
||||||
})
|
|
||||||
handleStreamCache(bouncer)
|
|
||||||
isStartup = false
|
isStartup = false
|
||||||
|
ticker = startTicker(config, func() {
|
||||||
|
handleStreamTicker(bouncer)
|
||||||
|
})
|
||||||
}
|
}
|
||||||
logger.Debug(fmt.Sprintf("New initialized mode:%s", config.CrowdsecMode))
|
logger.Debug(fmt.Sprintf("New initialized mode:%s", config.CrowdsecMode))
|
||||||
|
|
||||||
@@ -191,6 +211,11 @@ func (bouncer *Bouncer) ServeHTTP(rw http.ResponseWriter, req *http.Request) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if bouncer.crowdsecMode == configuration.AppsecMode {
|
||||||
|
handleNextServeHTTP(bouncer, remoteIP, rw, req)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
// TODO This should be simplified
|
// TODO This should be simplified
|
||||||
if bouncer.crowdsecMode != configuration.NoneMode {
|
if bouncer.crowdsecMode != configuration.NoneMode {
|
||||||
isBanned, cacheErr := bouncer.cacheClient.GetDecision(remoteIP)
|
isBanned, cacheErr := bouncer.cacheClient.GetDecision(remoteIP)
|
||||||
@@ -207,7 +232,7 @@ func (bouncer *Bouncer) ServeHTTP(rw http.ResponseWriter, req *http.Request) {
|
|||||||
if isBanned {
|
if isBanned {
|
||||||
rw.WriteHeader(http.StatusForbidden)
|
rw.WriteHeader(http.StatusForbidden)
|
||||||
} else {
|
} else {
|
||||||
bouncer.next.ServeHTTP(rw, req)
|
handleNextServeHTTP(bouncer, remoteIP, rw, req)
|
||||||
}
|
}
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@@ -216,9 +241,9 @@ func (bouncer *Bouncer) ServeHTTP(rw http.ResponseWriter, req *http.Request) {
|
|||||||
// Right here if we cannot join the stream we forbid the request to go on.
|
// Right here if we cannot join the stream we forbid the request to go on.
|
||||||
if bouncer.crowdsecMode == configuration.StreamMode || bouncer.crowdsecMode == configuration.AloneMode {
|
if bouncer.crowdsecMode == configuration.StreamMode || bouncer.crowdsecMode == configuration.AloneMode {
|
||||||
if isCrowdsecStreamHealthy {
|
if isCrowdsecStreamHealthy {
|
||||||
bouncer.next.ServeHTTP(rw, req)
|
handleNextServeHTTP(bouncer, remoteIP, rw, req)
|
||||||
} else {
|
} else {
|
||||||
logger.Error(fmt.Sprintf("ServeHTTP isCrowdsecStreamHealthy:false ip:%s", remoteIP))
|
logger.Debug(fmt.Sprintf("ServeHTTP isCrowdsecStreamHealthy:false ip:%s", remoteIP))
|
||||||
rw.WriteHeader(http.StatusForbidden)
|
rw.WriteHeader(http.StatusForbidden)
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
@@ -227,8 +252,7 @@ func (bouncer *Bouncer) ServeHTTP(rw http.ResponseWriter, req *http.Request) {
|
|||||||
logger.Debug(fmt.Sprintf("ServeHTTP:handleNoStreamCache ip:%s isBanned:true %s", remoteIP, err.Error()))
|
logger.Debug(fmt.Sprintf("ServeHTTP:handleNoStreamCache ip:%s isBanned:true %s", remoteIP, err.Error()))
|
||||||
rw.WriteHeader(http.StatusForbidden)
|
rw.WriteHeader(http.StatusForbidden)
|
||||||
} else {
|
} else {
|
||||||
logger.Debug(fmt.Sprintf("ServeHTTP:handleNoStreamCache ip:%s isBanned:false", remoteIP))
|
handleNextServeHTTP(bouncer, remoteIP, rw, req)
|
||||||
bouncer.next.ServeHTTP(rw, req)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -261,6 +285,27 @@ type Login struct {
|
|||||||
Expire string `json:"expire"`
|
Expire string `json:"expire"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func handleNextServeHTTP(bouncer *Bouncer, remoteIP string, rw http.ResponseWriter, req *http.Request) {
|
||||||
|
if bouncer.appsecEnabled {
|
||||||
|
err := appsecQuery(bouncer, remoteIP, req)
|
||||||
|
if err != nil {
|
||||||
|
logger.Debug(fmt.Sprintf("handleNextServeHTTP ip:%s isWaf:true %s", remoteIP, err.Error()))
|
||||||
|
rw.WriteHeader(http.StatusForbidden)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
bouncer.next.ServeHTTP(rw, req)
|
||||||
|
}
|
||||||
|
|
||||||
|
func handleStreamTicker(bouncer *Bouncer) {
|
||||||
|
if err := handleStreamCache(bouncer); err != nil {
|
||||||
|
isCrowdsecStreamHealthy = false
|
||||||
|
logger.Error(err.Error())
|
||||||
|
} else {
|
||||||
|
isCrowdsecStreamHealthy = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func startTicker(config *configuration.Config, work func()) chan bool {
|
func startTicker(config *configuration.Config, work func()) chan bool {
|
||||||
ticker := time.NewTicker(time.Duration(config.UpdateIntervalSeconds) * time.Second)
|
ticker := time.NewTicker(time.Duration(config.UpdateIntervalSeconds) * time.Second)
|
||||||
stop := make(chan bool, 1)
|
stop := make(chan bool, 1)
|
||||||
@@ -328,7 +373,7 @@ func getToken(bouncer *Bouncer) error {
|
|||||||
loginURL := url.URL{
|
loginURL := url.URL{
|
||||||
Scheme: bouncer.crowdsecScheme,
|
Scheme: bouncer.crowdsecScheme,
|
||||||
Host: bouncer.crowdsecHost,
|
Host: bouncer.crowdsecHost,
|
||||||
Path: crowdsecCapiLogin,
|
Path: crowdsecCapiLoginRoute,
|
||||||
}
|
}
|
||||||
body, err := crowdsecQuery(bouncer, loginURL.String(), true)
|
body, err := crowdsecQuery(bouncer, loginURL.String(), true)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -348,7 +393,7 @@ func getToken(bouncer *Bouncer) error {
|
|||||||
return fmt.Errorf("getToken statusCode:%d", login.Code)
|
return fmt.Errorf("getToken statusCode:%d", login.Code)
|
||||||
}
|
}
|
||||||
|
|
||||||
func handleStreamCache(bouncer *Bouncer) {
|
func handleStreamCache(bouncer *Bouncer) error {
|
||||||
// TODO clean properly on exit.
|
// TODO clean properly on exit.
|
||||||
// Instead of blocking the goroutine interval for all the secondary node,
|
// Instead of blocking the goroutine interval for all the secondary node,
|
||||||
// if the master service is shut down, other goroutine can take the lead
|
// if the master service is shut down, other goroutine can take the lead
|
||||||
@@ -356,7 +401,10 @@ func handleStreamCache(bouncer *Bouncer) {
|
|||||||
_, err := bouncer.cacheClient.GetDecision(cacheTimeoutKey)
|
_, err := bouncer.cacheClient.GetDecision(cacheTimeoutKey)
|
||||||
if err == nil {
|
if err == nil {
|
||||||
logger.Debug("handleStreamCache:alreadyUpdated")
|
logger.Debug("handleStreamCache:alreadyUpdated")
|
||||||
return
|
return nil
|
||||||
|
}
|
||||||
|
if err.Error() != cache.CacheMiss {
|
||||||
|
return err
|
||||||
}
|
}
|
||||||
bouncer.cacheClient.SetDecision(cacheTimeoutKey, false, bouncer.updateInterval-1)
|
bouncer.cacheClient.SetDecision(cacheTimeoutKey, false, bouncer.updateInterval-1)
|
||||||
streamRouteURL := url.URL{
|
streamRouteURL := url.URL{
|
||||||
@@ -367,16 +415,12 @@ func handleStreamCache(bouncer *Bouncer) {
|
|||||||
}
|
}
|
||||||
body, err := crowdsecQuery(bouncer, streamRouteURL.String(), false)
|
body, err := crowdsecQuery(bouncer, streamRouteURL.String(), false)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
logger.Error(err.Error())
|
return err
|
||||||
isCrowdsecStreamHealthy = false
|
|
||||||
return
|
|
||||||
}
|
}
|
||||||
var stream Stream
|
var stream Stream
|
||||||
err = json.Unmarshal(body, &stream)
|
err = json.Unmarshal(body, &stream)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
logger.Error(fmt.Sprintf("handleStreamCache:parsingBody %s", err.Error()))
|
return fmt.Errorf("handleStreamCache:parsingBody %w", err)
|
||||||
isCrowdsecStreamHealthy = false
|
|
||||||
return
|
|
||||||
}
|
}
|
||||||
for _, decision := range stream.New {
|
for _, decision := range stream.New {
|
||||||
duration, err := time.ParseDuration(decision.Duration)
|
duration, err := time.ParseDuration(decision.Duration)
|
||||||
@@ -389,6 +433,7 @@ func handleStreamCache(bouncer *Bouncer) {
|
|||||||
}
|
}
|
||||||
logger.Debug("handleStreamCache:updated")
|
logger.Debug("handleStreamCache:updated")
|
||||||
isCrowdsecStreamHealthy = true
|
isCrowdsecStreamHealthy = true
|
||||||
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func crowdsecQuery(bouncer *Bouncer, stringURL string, isPost bool) ([]byte, error) {
|
func crowdsecQuery(bouncer *Bouncer, stringURL string, isPost bool) ([]byte, error) {
|
||||||
@@ -409,6 +454,11 @@ func crowdsecQuery(bouncer *Bouncer, stringURL string, isPost bool) ([]byte, err
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("crowdsecQuery url:%s %w", stringURL, err)
|
return nil, fmt.Errorf("crowdsecQuery url:%s %w", stringURL, err)
|
||||||
}
|
}
|
||||||
|
defer func() {
|
||||||
|
if err = res.Body.Close(); err != nil {
|
||||||
|
logger.Error(fmt.Sprintf("crowdsecQuery:closeBody %s", err.Error()))
|
||||||
|
}
|
||||||
|
}()
|
||||||
if res.StatusCode == http.StatusUnauthorized && bouncer.crowdsecMode == configuration.AloneMode {
|
if res.StatusCode == http.StatusUnauthorized && bouncer.crowdsecMode == configuration.AloneMode {
|
||||||
if errToken := getToken(bouncer); errToken != nil {
|
if errToken := getToken(bouncer); errToken != nil {
|
||||||
return nil, fmt.Errorf("crowdsecQuery:renewToken url:%s %w", stringURL, errToken)
|
return nil, fmt.Errorf("crowdsecQuery:renewToken url:%s %w", stringURL, errToken)
|
||||||
@@ -418,11 +468,6 @@ func crowdsecQuery(bouncer *Bouncer, stringURL string, isPost bool) ([]byte, err
|
|||||||
if res.StatusCode != http.StatusOK {
|
if res.StatusCode != http.StatusOK {
|
||||||
return nil, fmt.Errorf("crowdsecQuery url:%s, statusCode:%d", stringURL, res.StatusCode)
|
return nil, fmt.Errorf("crowdsecQuery url:%s, statusCode:%d", stringURL, res.StatusCode)
|
||||||
}
|
}
|
||||||
defer func() {
|
|
||||||
if err = res.Body.Close(); err != nil {
|
|
||||||
logger.Error(fmt.Sprintf("crowdsecQuery:closeBody %s", err.Error()))
|
|
||||||
}
|
|
||||||
}()
|
|
||||||
body, err := io.ReadAll(res.Body)
|
body, err := io.ReadAll(res.Body)
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -430,3 +475,58 @@ func crowdsecQuery(bouncer *Bouncer, stringURL string, isPost bool) ([]byte, err
|
|||||||
}
|
}
|
||||||
return body, nil
|
return body, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func appsecQuery(bouncer *Bouncer, ip string, httpReq *http.Request) error {
|
||||||
|
routeURL := url.URL{
|
||||||
|
Scheme: bouncer.crowdsecScheme,
|
||||||
|
Host: bouncer.appsecHost,
|
||||||
|
Path: "/",
|
||||||
|
}
|
||||||
|
var req *http.Request
|
||||||
|
if httpReq.Body != nil && httpReq.ContentLength > 0 {
|
||||||
|
bodyBytes, err := io.ReadAll(httpReq.Body)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("appsecQuery:GetBody %w", err)
|
||||||
|
}
|
||||||
|
httpReq.Body = io.NopCloser(bytes.NewBuffer(bodyBytes))
|
||||||
|
req, _ = http.NewRequest(http.MethodPost, routeURL.String(), bytes.NewBuffer(bodyBytes))
|
||||||
|
} else {
|
||||||
|
req, _ = http.NewRequest(http.MethodGet, routeURL.String(), nil)
|
||||||
|
}
|
||||||
|
|
||||||
|
for key, headers := range httpReq.Header {
|
||||||
|
for _, value := range headers {
|
||||||
|
req.Header.Add(key, value)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
req.Header.Set(crowdsecAppsecHeader, bouncer.crowdsecKey)
|
||||||
|
req.Header.Set(crowdsecAppsecIPHeader, ip)
|
||||||
|
req.Header.Set(crowdsecAppsecVerbHeader, httpReq.Method)
|
||||||
|
req.Header.Set(crowdsecAppsecHostHeader, httpReq.Host)
|
||||||
|
req.Header.Set(crowdsecAppsecURIHeader, httpReq.URL.Path)
|
||||||
|
|
||||||
|
res, err := bouncer.httpClient.Do(req)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("appsecQuery %w", err)
|
||||||
|
}
|
||||||
|
defer func() {
|
||||||
|
if err = res.Body.Close(); err != nil {
|
||||||
|
logger.Error(fmt.Sprintf("appsecQuery:closeBody %s", err.Error()))
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
if res.StatusCode == http.StatusInternalServerError {
|
||||||
|
logger.Debug("crowdsecQuery statusCode:500")
|
||||||
|
if bouncer.appsecFailureBlock {
|
||||||
|
return fmt.Errorf("appsecQuery statusCode:%d", res.StatusCode)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
if res.StatusCode != http.StatusOK {
|
||||||
|
return fmt.Errorf("appsecQuery statusCode:%d", res.StatusCode)
|
||||||
|
}
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("appsecQuery:readBody %w", err)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|||||||
+8
-3
@@ -142,14 +142,19 @@ func Test_handleStreamCache(t *testing.T) {
|
|||||||
bouncer *Bouncer
|
bouncer *Bouncer
|
||||||
}
|
}
|
||||||
tests := []struct {
|
tests := []struct {
|
||||||
name string
|
name string
|
||||||
args args
|
args args
|
||||||
|
wantErr bool
|
||||||
}{
|
}{
|
||||||
// TODO: Add test cases.
|
// TODO: Add test cases.
|
||||||
}
|
}
|
||||||
for _, tt := range tests {
|
for _, tt := range tests {
|
||||||
t.Run(tt.name, func(t *testing.T) {
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
handleStreamCache(tt.args.bouncer)
|
err := handleStreamCache(tt.args.bouncer)
|
||||||
|
if (err != nil) != tt.wantErr {
|
||||||
|
t.Errorf("handleStreamCache() error = %v, wantErr %v", err, tt.wantErr)
|
||||||
|
return
|
||||||
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+12
-10
@@ -2,7 +2,7 @@ version: "3.8"
|
|||||||
|
|
||||||
services:
|
services:
|
||||||
traefik:
|
traefik:
|
||||||
image: "traefik:v2.9.6"
|
image: "traefik:v2.10.4"
|
||||||
container_name: "traefik"
|
container_name: "traefik"
|
||||||
restart: unless-stopped
|
restart: unless-stopped
|
||||||
command:
|
command:
|
||||||
@@ -20,7 +20,7 @@ services:
|
|||||||
- logs-local:/var/log/traefik
|
- logs-local:/var/log/traefik
|
||||||
- ./:/plugins-local/src/github.com/maxlerebourg/crowdsec-bouncer-traefik-plugin
|
- ./:/plugins-local/src/github.com/maxlerebourg/crowdsec-bouncer-traefik-plugin
|
||||||
ports:
|
ports:
|
||||||
- 80:80
|
- 8000:80
|
||||||
- 8080:8080
|
- 8080:8080
|
||||||
depends_on:
|
depends_on:
|
||||||
- crowdsec
|
- crowdsec
|
||||||
@@ -31,12 +31,12 @@ services:
|
|||||||
restart: unless-stopped
|
restart: unless-stopped
|
||||||
labels:
|
labels:
|
||||||
- "traefik.enable=true"
|
- "traefik.enable=true"
|
||||||
- "traefik.http.routers.router-foo.rule=Path(`/foo`)"
|
- "traefik.http.routers.router-foo.rule=PathPrefix(`/foo`)"
|
||||||
- "traefik.http.routers.router-foo.entrypoints=web"
|
- "traefik.http.routers.router-foo.entrypoints=web"
|
||||||
- "traefik.http.routers.router-foo.middlewares=crowdsec-foo@docker"
|
- "traefik.http.routers.router-foo.middlewares=crowdsec-foo@docker"
|
||||||
- "traefik.http.services.service-foo.loadbalancer.server.port=80"
|
- "traefik.http.services.service-foo.loadbalancer.server.port=80"
|
||||||
- "traefik.http.middlewares.crowdsec-foo.plugin.bouncer.enabled=true"
|
- "traefik.http.middlewares.crowdsec-foo.plugin.bouncer.enabled=true"
|
||||||
- "traefik.http.middlewares.crowdsec-foo.plugin.bouncer.crowdseclapikey=40796d93c2958f9e58345514e67740e5"
|
- "traefik.http.middlewares.crowdsec-foo.plugin.bouncer.crowdseclapikey=40796d93c2958f9e58345514e67740e5="
|
||||||
|
|
||||||
whoami2:
|
whoami2:
|
||||||
image: traefik/whoami
|
image: traefik/whoami
|
||||||
@@ -44,22 +44,23 @@ services:
|
|||||||
restart: unless-stopped
|
restart: unless-stopped
|
||||||
labels:
|
labels:
|
||||||
- "traefik.enable=true"
|
- "traefik.enable=true"
|
||||||
- "traefik.http.routers.router-bar.rule=Path(`/bar`)"
|
- "traefik.http.routers.router-bar.rule=PathPrefix(`/bar`)"
|
||||||
- "traefik.http.routers.router-bar.entrypoints=web"
|
- "traefik.http.routers.router-bar.entrypoints=web"
|
||||||
- "traefik.http.routers.router-bar.middlewares=crowdsec-bar@docker"
|
- "traefik.http.routers.router-bar.middlewares=crowdsec-bar@docker"
|
||||||
- "traefik.http.services.service-bar.loadbalancer.server.port=80"
|
- "traefik.http.services.service-bar.loadbalancer.server.port=80"
|
||||||
- "traefik.http.middlewares.crowdsec-bar.plugin.bouncer.enabled=true"
|
- "traefik.http.middlewares.crowdsec-bar.plugin.bouncer.enabled=true"
|
||||||
- "traefik.http.middlewares.crowdsec-bar.plugin.bouncer.crowdseclapikey=44c36dac5c4140af9f06f397508e82c7"
|
- "traefik.http.middlewares.crowdsec-bar.plugin.bouncer.loglevel=DEBUG"
|
||||||
|
- "traefik.http.middlewares.crowdsec-bar.plugin.bouncer.crowdsecappsecenabled=true"
|
||||||
|
- "traefik.http.middlewares.crowdsec-bar.plugin.bouncer.crowdseclapikey=40796d93c2958f9e58345514e67740e5="
|
||||||
|
|
||||||
crowdsec:
|
crowdsec:
|
||||||
image: crowdsecurity/crowdsec:v1.4.1
|
image: crowdsecurity/crowdsec:dev
|
||||||
container_name: "crowdsec"
|
container_name: "crowdsec"
|
||||||
restart: unless-stopped
|
restart: unless-stopped
|
||||||
environment:
|
environment:
|
||||||
COLLECTIONS: crowdsecurity/traefik
|
COLLECTIONS: crowdsecurity/traefik crowdsecurity/appsec-virtual-patching
|
||||||
CUSTOM_HOSTNAME: crowdsec
|
CUSTOM_HOSTNAME: crowdsec
|
||||||
BOUNCER_KEY_TRAEFIK_1: 40796d93c2958f9e58345514e67740e5
|
BOUNCER_KEY_TRAEFIK: 40796d93c2958f9e58345514e67740e5=
|
||||||
BOUNCER_KEY_TRAEFIK_2: 44c36dac5c4140af9f06f397508e82c7
|
|
||||||
volumes:
|
volumes:
|
||||||
- ./acquis.yaml:/etc/crowdsec/acquis.yaml:ro
|
- ./acquis.yaml:/etc/crowdsec/acquis.yaml:ro
|
||||||
- logs-local:/var/log/traefik:ro
|
- logs-local:/var/log/traefik:ro
|
||||||
@@ -67,6 +68,7 @@ services:
|
|||||||
- crowdsec-config-local:/etc/crowdsec/
|
- crowdsec-config-local:/etc/crowdsec/
|
||||||
labels:
|
labels:
|
||||||
- "traefik.enable=false"
|
- "traefik.enable=false"
|
||||||
|
|
||||||
volumes:
|
volumes:
|
||||||
logs-local:
|
logs-local:
|
||||||
crowdsec-db-local:
|
crowdsec-db-local:
|
||||||
|
|||||||
+9
-7
@@ -2,7 +2,7 @@ version: "3.8"
|
|||||||
|
|
||||||
services:
|
services:
|
||||||
traefik:
|
traefik:
|
||||||
image: "traefik:v2.9.6"
|
image: "traefik:v2.10.4"
|
||||||
container_name: "traefik"
|
container_name: "traefik"
|
||||||
restart: unless-stopped
|
restart: unless-stopped
|
||||||
command:
|
command:
|
||||||
@@ -14,7 +14,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.8"
|
- "--experimental.plugins.bouncer.version=v1.1.15"
|
||||||
volumes:
|
volumes:
|
||||||
- "/var/run/docker.sock:/var/run/docker.sock:ro"
|
- "/var/run/docker.sock:/var/run/docker.sock:ro"
|
||||||
- "logs:/var/log/traefik"
|
- "logs:/var/log/traefik"
|
||||||
@@ -39,7 +39,8 @@ services:
|
|||||||
# Definition of the middleware
|
# Definition of the middleware
|
||||||
- "traefik.http.middlewares.crowdsec-foo.plugin.bouncer.enabled=true"
|
- "traefik.http.middlewares.crowdsec-foo.plugin.bouncer.enabled=true"
|
||||||
# crowdseclapikey must be unique to the middleware attached to the service
|
# crowdseclapikey must be unique to the middleware attached to the service
|
||||||
- "traefik.http.middlewares.crowdsec-foo.plugin.bouncer.crowdseclapikey=FIXME-LAPI-KEY-1"
|
- "traefik.http.middlewares.crowdsec-foo.plugin.bouncer.crowdseclapikey=FIXME-LAPI-KEY-1="
|
||||||
|
|
||||||
# forwardedheaderstrustedips should be the IP of the proxy that is in front of traefik (if any)
|
# forwardedheaderstrustedips should be the IP of the proxy that is in front of traefik (if any)
|
||||||
- "traefik.http.middlewares.crowdsec-foo.plugin.bouncer.forwardedheaderstrustedips=172.21.0.5"
|
- "traefik.http.middlewares.crowdsec-foo.plugin.bouncer.forwardedheaderstrustedips=172.21.0.5"
|
||||||
|
|
||||||
@@ -58,20 +59,21 @@ services:
|
|||||||
# Definitin of the middleware
|
# Definitin of the middleware
|
||||||
- "traefik.http.middlewares.crowdsec-bar.plugin.bouncer.enabled=true"
|
- "traefik.http.middlewares.crowdsec-bar.plugin.bouncer.enabled=true"
|
||||||
# crowdseclapikey must be unique to the middleware attached to the service
|
# crowdseclapikey must be unique to the middleware attached to the service
|
||||||
- "traefik.http.middlewares.crowdsec-bar.plugin.bouncer.crowdseclapikey=FIXME-LAPI-KEY-2"
|
- "traefik.http.middlewares.crowdsec-bar.plugin.bouncer.crowdseclapikey=FIXME-LAPI-KEY-1="
|
||||||
|
# enable AppSec real time check
|
||||||
|
- "traefik.http.middlewares.crowdsec-bar.plugin.bouncer.crowdsecappsecenabled=true"
|
||||||
# forwardedheaderstrustedips should be the IP of the proxy that is in front of traefik (if any)
|
# forwardedheaderstrustedips should be the IP of the proxy that is in front of traefik (if any)
|
||||||
- "traefik.http.middlewares.crowdsec-bar.plugin.bouncer.forwardedheaderstrustedips=172.21.0.5"
|
- "traefik.http.middlewares.crowdsec-bar.plugin.bouncer.forwardedheaderstrustedips=172.21.0.5"
|
||||||
|
|
||||||
crowdsec:
|
crowdsec:
|
||||||
image: crowdsecurity/crowdsec:v1.4.1
|
image: crowdsecurity/crowdsec:v1.5.3
|
||||||
container_name: "crowdsec"
|
container_name: "crowdsec"
|
||||||
restart: unless-stopped
|
restart: unless-stopped
|
||||||
environment:
|
environment:
|
||||||
COLLECTIONS: crowdsecurity/traefik
|
COLLECTIONS: crowdsecurity/traefik
|
||||||
CUSTOM_HOSTNAME: crowdsec
|
CUSTOM_HOSTNAME: crowdsec
|
||||||
# We need to register one api key per service we will use
|
# We need to register one api key per service we will use
|
||||||
BOUNCER_KEY_TRAEFIK_1: FIXME-LAPI-KEY-1
|
BOUNCER_KEY_TRAEFIK: FIXME-LAPI-KEY-1=
|
||||||
BOUNCER_KEY_TRAEFIK_2: FIXME-LAPI-KEY-2
|
|
||||||
volumes:
|
volumes:
|
||||||
- ./acquis.yaml:/etc/crowdsec/acquis.yaml:ro
|
- ./acquis.yaml:/etc/crowdsec/acquis.yaml:ro
|
||||||
- logs:/var/log/traefik:ro
|
- logs:/var/log/traefik:ro
|
||||||
|
|||||||
@@ -0,0 +1,28 @@
|
|||||||
|
# Example
|
||||||
|
## Enabling AppSec WAF feature from crowdsec
|
||||||
|
|
||||||
|
You mostly need to configure Crowdsec for this to work by enabling virtual patching and configuring some custom rules.
|
||||||
|
In the example we use a whoami container protected by crowdsec with virtual patching enabled.
|
||||||
|
|
||||||
|
The Traefik instance just needs to know where appsec engine is located
|
||||||
|
```yaml
|
||||||
|
labels:
|
||||||
|
|
||||||
|
- "traefik.http.middlewares.crowdsec-bar.plugin.bouncer.crowdsecappsecenabled=true"
|
||||||
|
- "traefik.http.middlewares.crowdsec-bar.plugin.bouncer.crowdsecappsechost=crowdsec:7422"
|
||||||
|
```
|
||||||
|
We can try to query normally the whoami server:
|
||||||
|
```bash
|
||||||
|
curl http://localhost:8000/foo
|
||||||
|
```
|
||||||
|
|
||||||
|
And then we verify that a malicious request will be blocked:
|
||||||
|
```bash
|
||||||
|
curl http://localhost:8000/foo/rpc2
|
||||||
|
```
|
||||||
|
You should get a 403 on http://localhost:8000/foo/rpc2
|
||||||
|
|
||||||
|
To play the demo environment run:
|
||||||
|
```bash
|
||||||
|
make run_appsec
|
||||||
|
```
|
||||||
@@ -0,0 +1,12 @@
|
|||||||
|
filenames:
|
||||||
|
- /var/log/traefik/access.log
|
||||||
|
labels:
|
||||||
|
type: traefik
|
||||||
|
|
||||||
|
---
|
||||||
|
listen_addr: 0.0.0.0:7422
|
||||||
|
appsec_config: crowdsecurity/virtual-patching
|
||||||
|
name: myAppSecComponent
|
||||||
|
source: appsec
|
||||||
|
labels:
|
||||||
|
type: appsec
|
||||||
@@ -0,0 +1,72 @@
|
|||||||
|
version: "3.8"
|
||||||
|
|
||||||
|
services:
|
||||||
|
traefik:
|
||||||
|
image: "traefik:v2.10.7"
|
||||||
|
container_name: "traefik"
|
||||||
|
restart: unless-stopped
|
||||||
|
command:
|
||||||
|
# - "--log.level=DEBUG"
|
||||||
|
- "--accesslog"
|
||||||
|
- "--accesslog.filepath=/var/log/traefik/access.log"
|
||||||
|
- "--api.insecure=true"
|
||||||
|
- "--providers.docker=true"
|
||||||
|
- "--providers.docker.exposedbydefault=false"
|
||||||
|
- "--entrypoints.web.address=:80"
|
||||||
|
|
||||||
|
- "--experimental.plugins.bouncer.modulename=github.com/maxlerebourg/crowdsec-bouncer-traefik-plugin"
|
||||||
|
- "--experimental.plugins.bouncer.version=v1.2.0"
|
||||||
|
# - "--experimental.localplugins.bouncer.modulename=github.com/maxlerebourg/crowdsec-bouncer-traefik-plugin"
|
||||||
|
volumes:
|
||||||
|
- /var/run/docker.sock:/var/run/docker.sock:ro
|
||||||
|
- logs-appsec-enabled:/var/log/traefik
|
||||||
|
- ./../../:/plugins-local/src/github.com/maxlerebourg/crowdsec-bouncer-traefik-plugin
|
||||||
|
ports:
|
||||||
|
- 8000:80
|
||||||
|
- 8080:8080
|
||||||
|
depends_on:
|
||||||
|
- crowdsec
|
||||||
|
|
||||||
|
whoami1:
|
||||||
|
image: traefik/whoami
|
||||||
|
container_name: "simple-service-foo"
|
||||||
|
restart: unless-stopped
|
||||||
|
labels:
|
||||||
|
- "traefik.enable=true"
|
||||||
|
# Definition of the router
|
||||||
|
- "traefik.http.routers.router-foo.rule=PathPrefix(`/foo`)"
|
||||||
|
- "traefik.http.routers.router-foo.entrypoints=web"
|
||||||
|
- "traefik.http.routers.router-foo.middlewares=crowdsec-foo@docker"
|
||||||
|
# Definition of the service
|
||||||
|
- "traefik.http.services.service-foo.loadbalancer.server.port=80"
|
||||||
|
# Definition of the middleware
|
||||||
|
- "traefik.http.middlewares.crowdsec.plugin.bouncer.enabled=true"
|
||||||
|
- "traefik.http.middlewares.crowdsec.plugin.bouncer.crowdseclapikey=40796d93c2958f9e58345514e67740e5"
|
||||||
|
- "traefik.http.middlewares.crowdsec.plugin.bouncer.loglevel=DEBUG"
|
||||||
|
# Enable AppSec
|
||||||
|
- "traefik.http.middlewares.crowdsec.plugin.bouncer.crowdsecappsecenabled=true"
|
||||||
|
# Define AppSec host and port informations
|
||||||
|
- "traefik.http.middlewares.crowdsec.plugin.bouncer.crowdsecappsechost=crowdsec:7422"
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
crowdsec:
|
||||||
|
image: crowdsecurity/crowdsec:v1.6.0
|
||||||
|
container_name: "crowdsec"
|
||||||
|
restart: unless-stopped
|
||||||
|
environment:
|
||||||
|
COLLECTIONS: crowdsecurity/traefik
|
||||||
|
CUSTOM_HOSTNAME: crowdsec
|
||||||
|
BOUNCER_KEY_TRAEFIK_DEV: 40796d93c2958f9e58345514e67740e5
|
||||||
|
volumes:
|
||||||
|
- ./acquis.yaml:/etc/crowdsec/acquis.yaml:ro
|
||||||
|
- logs-appsec-enabled:/var/log/traefik:ro
|
||||||
|
- crowdsec-db-appsec-enabled:/var/lib/crowdsec/data/
|
||||||
|
- crowdsec-config-appsec-enabled:/etc/crowdsec/
|
||||||
|
labels:
|
||||||
|
- "traefik.enable=false"
|
||||||
|
|
||||||
|
volumes:
|
||||||
|
logs-appsec-enabled:
|
||||||
|
crowdsec-db-appsec-enabled:
|
||||||
|
crowdsec-config-appsec-enabled:
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
# Exemple
|
# Example
|
||||||
## Behind another proxy service (ex: clouflare)
|
## Behind another proxy service (ex: clouflare)
|
||||||
|
|
||||||
You need to configure your Traefik to trust Forwarded headers by your front proxy
|
You need to configure your Traefik to trust Forwarded headers by your front proxy
|
||||||
+19
-23
@@ -2,7 +2,7 @@ version: "3.8"
|
|||||||
|
|
||||||
services:
|
services:
|
||||||
cloudflare:
|
cloudflare:
|
||||||
image: "traefik:v2.9.6"
|
image: "traefik:v2.10.4"
|
||||||
container_name: "cloudflare"
|
container_name: "cloudflare"
|
||||||
restart: unless-stopped
|
restart: unless-stopped
|
||||||
command:
|
command:
|
||||||
@@ -12,17 +12,16 @@ services:
|
|||||||
- "--api.insecure=true"
|
- "--api.insecure=true"
|
||||||
- "--entrypoints.web.address=:80"
|
- "--entrypoints.web.address=:80"
|
||||||
- "--providers.file.filename=/cloud.yaml"
|
- "--providers.file.filename=/cloud.yaml"
|
||||||
- "--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
|
||||||
- ./cloudflare-exemple.yaml:/cloud.yaml:ro
|
- ./cloudflare-example.yaml:/cloud.yaml:ro
|
||||||
- logs-cloudflare:/var/log/traefik
|
- logs-cloudflare:/var/log/traefik
|
||||||
ports:
|
ports:
|
||||||
- 80:80
|
- 80:80
|
||||||
- 8080:8080
|
- 8080:8080
|
||||||
|
|
||||||
traefik:
|
traefik:
|
||||||
image: "traefik:v2.9.6"
|
image: "traefik:v2.10.4"
|
||||||
container_name: "traefik"
|
container_name: "traefik"
|
||||||
restart: unless-stopped
|
restart: unless-stopped
|
||||||
command:
|
command:
|
||||||
@@ -36,7 +35,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.7"
|
- "--experimental.plugins.bouncer.version=v1.1.13"
|
||||||
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
|
||||||
@@ -46,7 +45,7 @@ services:
|
|||||||
depends_on:
|
depends_on:
|
||||||
- crowdsec
|
- crowdsec
|
||||||
|
|
||||||
whoami1:
|
whoami-foo:
|
||||||
image: traefik/whoami
|
image: traefik/whoami
|
||||||
container_name: "simple-service-foo"
|
container_name: "simple-service-foo"
|
||||||
restart: unless-stopped
|
restart: unless-stopped
|
||||||
@@ -58,15 +57,14 @@ services:
|
|||||||
- "traefik.http.routers.router-foo.middlewares=crowdsec-foo@docker"
|
- "traefik.http.routers.router-foo.middlewares=crowdsec-foo@docker"
|
||||||
# Definition of the service
|
# Definition of the service
|
||||||
- "traefik.http.services.service-foo.loadbalancer.server.port=80"
|
- "traefik.http.services.service-foo.loadbalancer.server.port=80"
|
||||||
# Definitin of the middleware
|
# Definition of the middleware
|
||||||
- "traefik.http.middlewares.crowdsec-foo.plugin.bouncer.enabled=true"
|
- "traefik.http.middlewares.crowdsec.plugin.bouncer.enabled=true"
|
||||||
# crowdseclapikey must be uniq to the middleware attached to the service
|
- "traefik.http.middlewares.crowdsec.plugin.bouncer.crowdseclapikey=40796d93c2958f9e58345514e67740e5"
|
||||||
- "traefik.http.middlewares.crowdsec-foo.plugin.bouncer.crowdseclapikey=40796d93c2958f9e58345514e67740e5"
|
- "traefik.http.middlewares.crowdsec.plugin.bouncer.crowdsecmode=live"
|
||||||
- "traefik.http.middlewares.crowdsec-foo.plugin.bouncer.crowdsecmode=live"
|
- "traefik.http.middlewares.crowdsec.plugin.bouncer.forwardedheaderstrustedips=172.21.0.5"
|
||||||
- "traefik.http.middlewares.crowdsec-foo.plugin.bouncer.forwardedheaderstrustedips=172.21.0.5"
|
- "traefik.http.middlewares.crowdsec.plugin.bouncer.loglevel=DEBUG"
|
||||||
- "traefik.http.middlewares.crowdsec-foo.plugin.bouncer.loglevel=DEBUG"
|
|
||||||
|
|
||||||
whoami2:
|
whoami-bar:
|
||||||
image: traefik/whoami
|
image: traefik/whoami
|
||||||
container_name: "simple-service-bar"
|
container_name: "simple-service-bar"
|
||||||
restart: unless-stopped
|
restart: unless-stopped
|
||||||
@@ -78,24 +76,22 @@ services:
|
|||||||
- "traefik.http.routers.router-bar.middlewares=crowdsec-bar@docker"
|
- "traefik.http.routers.router-bar.middlewares=crowdsec-bar@docker"
|
||||||
# Definition of the service
|
# Definition of the service
|
||||||
- "traefik.http.services.service-bar.loadbalancer.server.port=80"
|
- "traefik.http.services.service-bar.loadbalancer.server.port=80"
|
||||||
# Definitin of the middleware
|
# Definition of the middleware
|
||||||
- "traefik.http.middlewares.crowdsec-bar.plugin.bouncer.enabled=true"
|
- "traefik.http.middlewares.crowdsec-bar.plugin.bouncer.enabled=true"
|
||||||
# crowdseclapikey must be uniq to the middleware attached to the service
|
- "traefik.http.middlewares.crowdsec.plugin.bouncer.crowdseclapikey=40796d93c2958f9e58345514e67740e5"
|
||||||
- "traefik.http.middlewares.crowdsec-bar.plugin.bouncer.crowdseclapikey=44c36dac5c4140af9f06f397508e82c7"
|
- "traefik.http.middlewares.crowdsec.plugin.bouncer.crowdsecmode=live"
|
||||||
- "traefik.http.middlewares.crowdsec-bar.plugin.bouncer.crowdsecmode=live"
|
- "traefik.http.middlewares.crowdsec.plugin.bouncer.forwardedheaderstrustedips=172.21.0.5"
|
||||||
- "traefik.http.middlewares.crowdsec-bar.plugin.bouncer.forwardedheaderstrustedips=172.21.0.5"
|
- "traefik.http.middlewares.crowdsec.plugin.bouncer.loglevel=DEBUG"
|
||||||
- "traefik.http.middlewares.crowdsec-bar.plugin.bouncer.loglevel=DEBUG"
|
|
||||||
|
|
||||||
|
|
||||||
crowdsec:
|
crowdsec:
|
||||||
image: crowdsecurity/crowdsec:v1.4.3
|
image: crowdsecurity/crowdsec:v1.5.2
|
||||||
container_name: "crowdsec"
|
container_name: "crowdsec"
|
||||||
restart: unless-stopped
|
restart: unless-stopped
|
||||||
environment:
|
environment:
|
||||||
COLLECTIONS: crowdsecurity/traefik
|
COLLECTIONS: crowdsecurity/traefik
|
||||||
CUSTOM_HOSTNAME: crowdsec
|
CUSTOM_HOSTNAME: crowdsec
|
||||||
BOUNCER_KEY_TRAEFIK_DEV_1: 40796d93c2958f9e58345514e67740e5
|
BOUNCER_KEY_TRAEFIK_DEV: 40796d93c2958f9e58345514e67740e5
|
||||||
BOUNCER_KEY_TRAEFIK_DEV_2: 44c36dac5c4140af9f06f397508e82c7
|
|
||||||
volumes:
|
volumes:
|
||||||
- ./acquis.yaml:/etc/crowdsec/acquis.yaml:ro
|
- ./acquis.yaml:/etc/crowdsec/acquis.yaml:ro
|
||||||
- logs-cloudflare:/var/log/traefik:ro
|
- logs-cloudflare:/var/log/traefik:ro
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
# Exemple
|
# Example
|
||||||
|
|
||||||
### Install vagrant
|
### Install vagrant
|
||||||
|
|
||||||
+1
-1
@@ -2,7 +2,7 @@
|
|||||||
|
|
||||||
DEBIAN_FRONTEND=noninteractive sudo apt-get update && sudo apt-get install wget -y
|
DEBIAN_FRONTEND=noninteractive sudo apt-get update && sudo apt-get install wget -y
|
||||||
# DEBIAN_FRONTEND=noninteractive sudo apt-get upgrade -y --assume-yes
|
# 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"
|
wget -O traefik.tar.gz "https://github.com/traefik/traefik/releases/download/v2.10.4/traefik_v2.10.4_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
@@ -1,7 +1,7 @@
|
|||||||
#!/bin/bash
|
#!/bin/bash
|
||||||
|
|
||||||
DEBIAN_FRONTEND=noninteractive 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.9.0/whoami_v1.9.0_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
|
||||||
sudo mv ./whoami /usr/local/bin/
|
sudo mv ./whoami /usr/local/bin/
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
### Kubernetes Exemple
|
### Kubernetes Example
|
||||||
|
|
||||||
#### Official docs
|
#### Official docs
|
||||||
|
|
||||||
@@ -1,5 +1,5 @@
|
|||||||
image:
|
image:
|
||||||
tag: v1.4.4-rc1
|
tag: v1.5.2
|
||||||
|
|
||||||
agent:
|
agent:
|
||||||
acquisition:
|
acquisition:
|
||||||
@@ -22,4 +22,4 @@ lapi:
|
|||||||
value: "k8s linux test"
|
value: "k8s linux test"
|
||||||
# If it's a test, we don't want to share signals with CrowdSec so disable the Online API.
|
# If it's a test, we don't want to share signals with CrowdSec so disable the Online API.
|
||||||
- name: DISABLE_ONLINE_API
|
- name: DISABLE_ONLINE_API
|
||||||
value: true
|
value: "true"
|
||||||
@@ -1,5 +1,5 @@
|
|||||||
image:
|
image:
|
||||||
tag: v2.9.6
|
tag: v2.10.4
|
||||||
|
|
||||||
logs:
|
logs:
|
||||||
general:
|
general:
|
||||||
@@ -16,4 +16,4 @@ experimental:
|
|||||||
|
|
||||||
additionalArguments:
|
additionalArguments:
|
||||||
- "--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.7-beta1"
|
- "--experimental.plugins.bouncer.version=v1.1.13"
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
# Exemple
|
# Example
|
||||||
## With Redis as an external shared cache
|
## With Redis as an external shared cache
|
||||||
|
|
||||||
The plugin must be configured to connect to a redis instance
|
The plugin must be configured to connect to a redis instance
|
||||||
@@ -0,0 +1,114 @@
|
|||||||
|
version: "3.8"
|
||||||
|
|
||||||
|
services:
|
||||||
|
traefik:
|
||||||
|
image: "traefik:v2.10.4"
|
||||||
|
container_name: "traefik"
|
||||||
|
restart: unless-stopped
|
||||||
|
command:
|
||||||
|
# - "--log.level=DEBUG"
|
||||||
|
- "--accesslog"
|
||||||
|
- "--accesslog.filepath=/var/log/traefik/access.log"
|
||||||
|
- "--api.insecure=true"
|
||||||
|
- "--providers.docker=true"
|
||||||
|
- "--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.13"
|
||||||
|
# - "--experimental.localplugins.bouncer.modulename=github.com/maxlerebourg/crowdsec-bouncer-traefik-plugin"
|
||||||
|
volumes:
|
||||||
|
- /var/run/docker.sock:/var/run/docker.sock:ro
|
||||||
|
- logs-redis:/var/log/traefik
|
||||||
|
# - ./../../:/plugins-local/src/github.com/maxlerebourg/crowdsec-bouncer-traefik-plugin
|
||||||
|
ports:
|
||||||
|
- 80:80
|
||||||
|
- 8080:8080
|
||||||
|
depends_on:
|
||||||
|
- crowdsec
|
||||||
|
# - redis-insecure
|
||||||
|
- redis-secure
|
||||||
|
|
||||||
|
# Either use secure, or insecure but do not mix both
|
||||||
|
# whoami-redis-insecure:
|
||||||
|
# image: traefik/whoami
|
||||||
|
# container_name: "simple-service-foo"
|
||||||
|
# restart: unless-stopped
|
||||||
|
# labels:
|
||||||
|
# - "traefik.enable=true"
|
||||||
|
# # Definition of the router
|
||||||
|
# - "traefik.http.routers.router-foo.rule=Path(`/redis-insecure`)"
|
||||||
|
# - "traefik.http.routers.router-foo.entrypoints=web"
|
||||||
|
# - "traefik.http.routers.router-foo.middlewares=crowdsec-foo@docker"
|
||||||
|
# # Definition of the service
|
||||||
|
# - "traefik.http.services.service-foo.loadbalancer.server.port=80"
|
||||||
|
# # Definition of the middleware
|
||||||
|
# - "traefik.http.middlewares.crowdsec.plugin.bouncer.enabled=true"
|
||||||
|
# - "traefik.http.middlewares.crowdsec.plugin.bouncer.crowdseclapikey=40796d93c2958f9e58345514e67740e5"
|
||||||
|
# - "traefik.http.middlewares.crowdsec.plugin.bouncer.rediscacheenabled=true"
|
||||||
|
# # Contact redis-unsecure without a password
|
||||||
|
# - "traefik.http.middlewares.crowdsec.plugin.bouncer.rediscachehost=redis-insecure:6379"
|
||||||
|
# - "traefik.http.middlewares.crowdsec.plugin.bouncer.loglevel=DEBUG"
|
||||||
|
|
||||||
|
whoami-redis-secure:
|
||||||
|
image: traefik/whoami
|
||||||
|
container_name: "simple-service-bar"
|
||||||
|
restart: unless-stopped
|
||||||
|
labels:
|
||||||
|
- "traefik.enable=true"
|
||||||
|
# Definition of the router
|
||||||
|
- "traefik.http.routers.router-bar.rule=Path(`/redis-secure`)"
|
||||||
|
- "traefik.http.routers.router-bar.entrypoints=web"
|
||||||
|
- "traefik.http.routers.router-bar.middlewares=crowdsec-bar@docker"
|
||||||
|
# Definition of the service
|
||||||
|
- "traefik.http.services.service-bar.loadbalancer.server.port=80"
|
||||||
|
# Definition of the middleware
|
||||||
|
- "traefik.http.middlewares.crowdsec.plugin.bouncer.enabled=true"
|
||||||
|
- "traefik.http.middlewares.crowdsec.plugin.bouncer.crowdseclapikey=40796d93c2958f9e58345514e67740e5"
|
||||||
|
- "traefik.http.middlewares.crowdsec.plugin.bouncer.rediscacheenabled=true"
|
||||||
|
- "traefik.http.middlewares.crowdsec.plugin.bouncer.rediscachepassword=FIXME"
|
||||||
|
# Contact redis-secure with password
|
||||||
|
- "traefik.http.middlewares.crowdsec.plugin.bouncer.rediscachehost=redis-secure:6379"
|
||||||
|
- "traefik.http.middlewares.crowdsec.plugin.bouncer.loglevel=DEBUG"
|
||||||
|
|
||||||
|
|
||||||
|
crowdsec:
|
||||||
|
image: crowdsecurity/crowdsec:v1.5.2
|
||||||
|
container_name: "crowdsec"
|
||||||
|
restart: unless-stopped
|
||||||
|
environment:
|
||||||
|
COLLECTIONS: crowdsecurity/traefik
|
||||||
|
CUSTOM_HOSTNAME: crowdsec
|
||||||
|
BOUNCER_KEY_TRAEFIK_DEV: 40796d93c2958f9e58345514e67740e5
|
||||||
|
volumes:
|
||||||
|
- ./acquis.yaml:/etc/crowdsec/acquis.yaml:ro
|
||||||
|
- logs-redis:/var/log/traefik:ro
|
||||||
|
- crowdsec-db-redis:/var/lib/crowdsec/data/
|
||||||
|
- crowdsec-config-redis:/etc/crowdsec/
|
||||||
|
labels:
|
||||||
|
- "traefik.enable=false"
|
||||||
|
|
||||||
|
redis-secure:
|
||||||
|
image: "redis:7.0.12-alpine"
|
||||||
|
container_name: "redis-secure"
|
||||||
|
hostname: redis-secure
|
||||||
|
restart: unless-stopped
|
||||||
|
command: "redis-server --save 60 1 --loglevel debug --requirepass FIXME"
|
||||||
|
volumes:
|
||||||
|
- redis-secure-data:/data
|
||||||
|
|
||||||
|
# redis-insecure:
|
||||||
|
# image: "redis:7.0.12-alpine"
|
||||||
|
# container_name: "redis-insecure"
|
||||||
|
# hostname: redis-unsecure
|
||||||
|
# restart: unless-stopped
|
||||||
|
# command: "redis-server --save 60 1 --loglevel debug"
|
||||||
|
# volumes:
|
||||||
|
# - redis-unsecure-data:/data
|
||||||
|
|
||||||
|
volumes:
|
||||||
|
logs-redis:
|
||||||
|
crowdsec-db-redis:
|
||||||
|
crowdsec-config-redis:
|
||||||
|
redis-unsecure-data:
|
||||||
|
redis-secure-data:
|
||||||
+2
-2
@@ -2,7 +2,7 @@ version: "3.8"
|
|||||||
|
|
||||||
services:
|
services:
|
||||||
traefik:
|
traefik:
|
||||||
image: "traefik:v2.9.6"
|
image: "traefik:v2.10.4"
|
||||||
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.7"
|
- "--experimental.plugins.bouncer.version=v1.1.13"
|
||||||
volumes:
|
volumes:
|
||||||
- /var/run/docker.sock:/var/run/docker.sock:ro
|
- /var/run/docker.sock:/var/run/docker.sock:ro
|
||||||
ports:
|
ports:
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
# Exemple
|
# Example
|
||||||
## Using https communication and tls authentication with Crowdsec
|
## Using https communication and tls authentication with Crowdsec
|
||||||
|
|
||||||
##### Summary
|
##### Summary
|
||||||
+27
-25
@@ -2,7 +2,7 @@ version: "3.8"
|
|||||||
|
|
||||||
services:
|
services:
|
||||||
traefik:
|
traefik:
|
||||||
image: "traefik:v2.9.6"
|
image: "traefik:v2.10.4"
|
||||||
container_name: "traefik"
|
container_name: "traefik"
|
||||||
restart: unless-stopped
|
restart: unless-stopped
|
||||||
command:
|
command:
|
||||||
@@ -15,14 +15,14 @@ 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.7"
|
- "--experimental.plugins.bouncer.version=v1.1.13"
|
||||||
# - "--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
|
- ./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
|
||||||
|
|
||||||
ports:
|
ports:
|
||||||
- 80:80
|
- 80:80
|
||||||
@@ -30,22 +30,24 @@ services:
|
|||||||
depends_on:
|
depends_on:
|
||||||
- crowdsec
|
- crowdsec
|
||||||
|
|
||||||
whoami-foo:
|
# Use HTTPS scheme but with lapikey authentication
|
||||||
image: traefik/whoami
|
# whoami-foo:
|
||||||
container_name: "simple-service-foo"
|
# image: traefik/whoami
|
||||||
restart: unless-stopped
|
# container_name: "simple-service-foo"
|
||||||
labels:
|
# restart: unless-stopped
|
||||||
- "traefik.enable=true"
|
# labels:
|
||||||
- "traefik.http.routers.router-foo.rule=Path(`/foo`)"
|
# - "traefik.enable=true"
|
||||||
- "traefik.http.routers.router-foo.entrypoints=web"
|
# - "traefik.http.routers.router-foo.rule=Path(`/foo`)"
|
||||||
- "traefik.http.routers.router-foo.middlewares=crowdsec-foo@docker"
|
# - "traefik.http.routers.router-foo.entrypoints=web"
|
||||||
- "traefik.http.services.service-foo.loadbalancer.server.port=80"
|
# - "traefik.http.routers.router-foo.middlewares=crowdsec-foo@docker"
|
||||||
- "traefik.http.middlewares.crowdsec-foo.plugin.bouncer.enabled=true"
|
# - "traefik.http.services.service-foo.loadbalancer.server.port=80"
|
||||||
- "traefik.http.middlewares.crowdsec-foo.plugin.bouncer.loglevel=DEBUG"
|
# - "traefik.http.middlewares.crowdsec.plugin.bouncer.enabled=true"
|
||||||
- "traefik.http.middlewares.crowdsec-foo.plugin.bouncer.crowdseclapikey=40796d93c2958f9e58345514e67740e5"
|
# - "traefik.http.middlewares.crowdsec.plugin.bouncer.loglevel=DEBUG"
|
||||||
- "traefik.http.middlewares.crowdsec-foo.plugin.bouncer.crowdseclapischeme=https"
|
# - "traefik.http.middlewares.crowdsec.plugin.bouncer.crowdseclapikey=40796d93c2958f9e58345514e67740e5"
|
||||||
- "traefik.http.middlewares.crowdsec-foo.plugin.bouncer.crowdsecLapiTLSCertificateAuthorityFile=/etc/traefik/crowdsec-certs/inter.pem"
|
# - "traefik.http.middlewares.crowdsec.plugin.bouncer.crowdseclapischeme=https"
|
||||||
|
# - "traefik.http.middlewares.crowdsec.plugin.bouncer.crowdsecLapiTLSCertificateAuthorityFile=/etc/traefik/crowdsec-certs/inter.pem"
|
||||||
|
|
||||||
|
# Use HTTPS scheme with TLS cert authentication
|
||||||
whoami-bar:
|
whoami-bar:
|
||||||
image: traefik/whoami
|
image: traefik/whoami
|
||||||
container_name: "simple-service-bar"
|
container_name: "simple-service-bar"
|
||||||
@@ -56,15 +58,15 @@ services:
|
|||||||
- "traefik.http.routers.router-bar.entrypoints=web"
|
- "traefik.http.routers.router-bar.entrypoints=web"
|
||||||
- "traefik.http.routers.router-bar.middlewares=crowdsec-bar@docker"
|
- "traefik.http.routers.router-bar.middlewares=crowdsec-bar@docker"
|
||||||
- "traefik.http.services.service-bar.loadbalancer.server.port=80"
|
- "traefik.http.services.service-bar.loadbalancer.server.port=80"
|
||||||
- "traefik.http.middlewares.crowdsec-bar.plugin.bouncer.enabled=true"
|
- "traefik.http.middlewares.crowdsec.plugin.bouncer.enabled=true"
|
||||||
- "traefik.http.middlewares.crowdsec-bar.plugin.bouncer.loglevel=DEBUG"
|
- "traefik.http.middlewares.crowdsec.plugin.bouncer.loglevel=DEBUG"
|
||||||
- "traefik.http.middlewares.crowdsec-bar.plugin.bouncer.crowdseclapischeme=https"
|
- "traefik.http.middlewares.crowdsec.plugin.bouncer.crowdseclapischeme=https"
|
||||||
- "traefik.http.middlewares.crowdsec-bar.plugin.bouncer.crowdsecLapiTLSCertificateAuthorityFile=/etc/traefik/crowdsec-certs/inter.pem"
|
- "traefik.http.middlewares.crowdsec.plugin.bouncer.crowdsecLapiTLSCertificateAuthorityFile=/etc/traefik/crowdsec-certs/inter.pem"
|
||||||
- "traefik.http.middlewares.crowdsec-bar.plugin.bouncer.crowdsecLapiTLSCertificateBouncerFile=/etc/traefik/crowdsec-certs/bouncer.pem"
|
- "traefik.http.middlewares.crowdsec.plugin.bouncer.crowdsecLapiTLSCertificateBouncerFile=/etc/traefik/crowdsec-certs/bouncer.pem"
|
||||||
- "traefik.http.middlewares.crowdsec-bar.plugin.bouncer.crowdsecLapiTLSCertificateBouncerKeyFile=/etc/traefik/crowdsec-certs/bouncer-key.pem"
|
- "traefik.http.middlewares.crowdsec.plugin.bouncer.crowdsecLapiTLSCertificateBouncerKeyFile=/etc/traefik/crowdsec-certs/bouncer-key.pem"
|
||||||
|
|
||||||
crowdsec:
|
crowdsec:
|
||||||
image: crowdsecurity/crowdsec:v1.4.3
|
image: crowdsecurity/crowdsec:v1.5.2
|
||||||
container_name: "crowdsec"
|
container_name: "crowdsec"
|
||||||
restart: unless-stopped
|
restart: unless-stopped
|
||||||
environment:
|
environment:
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
# Exemple
|
# Example
|
||||||
## Using Trusted IP (ex: LAN OR VPN) that won't get filtered by crowdsec
|
## 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
|
You need to configure your Traefik to trust Forwarded headers by your front proxy
|
||||||
+13
-16
@@ -2,7 +2,7 @@ version: "3.8"
|
|||||||
|
|
||||||
services:
|
services:
|
||||||
traefik:
|
traefik:
|
||||||
image: "traefik:v2.9.6"
|
image: "traefik:v2.10.4"
|
||||||
container_name: "traefik"
|
container_name: "traefik"
|
||||||
restart: unless-stopped
|
restart: unless-stopped
|
||||||
command:
|
command:
|
||||||
@@ -15,12 +15,12 @@ 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.7"
|
- "--experimental.plugins.bouncer.version=v1.1.13"
|
||||||
# - "--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
|
||||||
- logs-trustedips:/var/log/traefik
|
- logs-trustedips:/var/log/traefik
|
||||||
- ./../../:/plugins-local/src/github.com/maxlerebourg/crowdsec-bouncer-traefik-plugin
|
# - ./../../:/plugins-local/src/github.com/maxlerebourg/crowdsec-bouncer-traefik-plugin
|
||||||
ports:
|
ports:
|
||||||
- 80:80
|
- 80:80
|
||||||
- 8080:8080
|
- 8080:8080
|
||||||
@@ -40,12 +40,11 @@ services:
|
|||||||
# Definition of the service
|
# Definition of the service
|
||||||
- "traefik.http.services.service-foo.loadbalancer.server.port=80"
|
- "traefik.http.services.service-foo.loadbalancer.server.port=80"
|
||||||
# Definition of the middleware
|
# Definition of the middleware
|
||||||
- "traefik.http.middlewares.crowdsec-foo.plugin.bouncer.enabled=true"
|
- "traefik.http.middlewares.crowdsec.plugin.bouncer.enabled=true"
|
||||||
# crowdseclapikey must be uniq to the middleware attached to the service
|
- "traefik.http.middlewares.crowdsec.plugin.bouncer.crowdseclapikey=40796d93c2958f9e58345514e67740e5"
|
||||||
- "traefik.http.middlewares.crowdsec-foo.plugin.bouncer.crowdseclapikey=40796d93c2958f9e58345514e67740e5"
|
|
||||||
# Replace 10.0.10.30/32 by your IP range which is "trusted"
|
# Replace 10.0.10.30/32 by your IP range which is "trusted"
|
||||||
- "traefik.http.middlewares.crowdsec-foo.plugin.bouncer.clienttrustedips=10.0.10.30/32"
|
- "traefik.http.middlewares.crowdsec.plugin.bouncer.clienttrustedips=10.0.10.30/32"
|
||||||
- "traefik.http.middlewares.crowdsec-foo.plugin.bouncer.loglevel=DEBUG"
|
- "traefik.http.middlewares.crowdsec.plugin.bouncer.loglevel=DEBUG"
|
||||||
|
|
||||||
whoami2:
|
whoami2:
|
||||||
image: traefik/whoami
|
image: traefik/whoami
|
||||||
@@ -60,23 +59,21 @@ services:
|
|||||||
# Definition of the service
|
# Definition of the service
|
||||||
- "traefik.http.services.service-bar.loadbalancer.server.port=80"
|
- "traefik.http.services.service-bar.loadbalancer.server.port=80"
|
||||||
# Definition of the middleware
|
# Definition of the middleware
|
||||||
- "traefik.http.middlewares.crowdsec-bar.plugin.bouncer.enabled=true"
|
- "traefik.http.middlewares.crowdsec.plugin.bouncer.enabled=true"
|
||||||
# crowdseclapikey must be uniq to the middleware attached to the service
|
- "traefik.http.middlewares.crowdsec.plugin.bouncer.crowdseclapikey=40796d93c2958f9e58345514e67740e5"
|
||||||
- "traefik.http.middlewares.crowdsec-bar.plugin.bouncer.crowdseclapikey=44c36dac5c4140af9f06f397508e82c7"
|
|
||||||
# Replace 10.0.10.30/32 by your IP range which is "trusted"
|
# Replace 10.0.10.30/32 by your IP range which is "trusted"
|
||||||
- "traefik.http.middlewares.crowdsec-bar.plugin.bouncer.clienttrustedips=10.0.10.30/32"
|
- "traefik.http.middlewares.crowdsec.plugin.bouncer.clienttrustedips=10.0.10.30/32"
|
||||||
- "traefik.http.middlewares.crowdsec-bar.plugin.bouncer.loglevel=DEBUG"
|
- "traefik.http.middlewares.crowdsec.plugin.bouncer.loglevel=DEBUG"
|
||||||
|
|
||||||
|
|
||||||
crowdsec:
|
crowdsec:
|
||||||
image: crowdsecurity/crowdsec:v1.4.3
|
image: crowdsecurity/crowdsec:v1.5.2
|
||||||
container_name: "crowdsec"
|
container_name: "crowdsec"
|
||||||
restart: unless-stopped
|
restart: unless-stopped
|
||||||
environment:
|
environment:
|
||||||
COLLECTIONS: crowdsecurity/traefik
|
COLLECTIONS: crowdsecurity/traefik
|
||||||
CUSTOM_HOSTNAME: crowdsec
|
CUSTOM_HOSTNAME: crowdsec
|
||||||
BOUNCER_KEY_TRAEFIK_DEV_1: 40796d93c2958f9e58345514e67740e5
|
BOUNCER_KEY_TRAEFIK_DEV: 40796d93c2958f9e58345514e67740e5
|
||||||
BOUNCER_KEY_TRAEFIK_DEV_2: 44c36dac5c4140af9f06f397508e82c7
|
|
||||||
volumes:
|
volumes:
|
||||||
- ./acquis.yaml:/etc/crowdsec/acquis.yaml:ro
|
- ./acquis.yaml:/etc/crowdsec/acquis.yaml:ro
|
||||||
- logs-trustedips:/var/log/traefik:ro
|
- logs-trustedips:/var/log/traefik:ro
|
||||||
@@ -1,101 +0,0 @@
|
|||||||
version: "3.8"
|
|
||||||
|
|
||||||
services:
|
|
||||||
traefik:
|
|
||||||
image: "traefik:v2.9.6"
|
|
||||||
container_name: "traefik"
|
|
||||||
restart: unless-stopped
|
|
||||||
command:
|
|
||||||
# - "--log.level=DEBUG"
|
|
||||||
- "--accesslog"
|
|
||||||
- "--accesslog.filepath=/var/log/traefik/access.log"
|
|
||||||
- "--api.insecure=true"
|
|
||||||
- "--providers.docker=true"
|
|
||||||
- "--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.7"
|
|
||||||
# - "--experimental.localplugins.bouncer.modulename=github.com/maxlerebourg/crowdsec-bouncer-traefik-plugin"
|
|
||||||
volumes:
|
|
||||||
- /var/run/docker.sock:/var/run/docker.sock:ro
|
|
||||||
- logs-redis:/var/log/traefik
|
|
||||||
# - ./../../:/plugins-local/src/github.com/maxlerebourg/crowdsec-bouncer-traefik-plugin
|
|
||||||
ports:
|
|
||||||
- 80:80
|
|
||||||
- 8080:8080
|
|
||||||
depends_on:
|
|
||||||
- crowdsec
|
|
||||||
- redis
|
|
||||||
|
|
||||||
whoami-foo:
|
|
||||||
image: traefik/whoami
|
|
||||||
container_name: "simple-service-foo"
|
|
||||||
restart: unless-stopped
|
|
||||||
labels:
|
|
||||||
- "traefik.enable=true"
|
|
||||||
# Definition of the router
|
|
||||||
- "traefik.http.routers.router-foo.rule=Path(`/foo`)"
|
|
||||||
- "traefik.http.routers.router-foo.entrypoints=web"
|
|
||||||
- "traefik.http.routers.router-foo.middlewares=crowdsec-foo@docker"
|
|
||||||
# Definition of the service
|
|
||||||
- "traefik.http.services.service-foo.loadbalancer.server.port=80"
|
|
||||||
# Definition of the middleware
|
|
||||||
- "traefik.http.middlewares.crowdsec-foo.plugin.bouncer.enabled=true"
|
|
||||||
# crowdseclapikey must be uniq to the middleware attached to the service
|
|
||||||
- "traefik.http.middlewares.crowdsec-foo.plugin.bouncer.crowdseclapikey=40796d93c2958f9e58345514e67740e5"
|
|
||||||
- "traefik.http.middlewares.crowdsec-foo.plugin.bouncer.rediscacheenabled=true"
|
|
||||||
- "traefik.http.middlewares.crowdsec-foo.plugin.bouncer.loglevel=DEBUG"
|
|
||||||
|
|
||||||
whoami-bar:
|
|
||||||
image: traefik/whoami
|
|
||||||
container_name: "simple-service-bar"
|
|
||||||
restart: unless-stopped
|
|
||||||
labels:
|
|
||||||
- "traefik.enable=true"
|
|
||||||
# Definition of the router
|
|
||||||
- "traefik.http.routers.router-bar.rule=Path(`/bar`)"
|
|
||||||
- "traefik.http.routers.router-bar.entrypoints=web"
|
|
||||||
- "traefik.http.routers.router-bar.middlewares=crowdsec-bar@docker"
|
|
||||||
# Definition of the service
|
|
||||||
- "traefik.http.services.service-bar.loadbalancer.server.port=80"
|
|
||||||
# Definition of the middleware
|
|
||||||
- "traefik.http.middlewares.crowdsec-bar.plugin.bouncer.enabled=true"
|
|
||||||
# crowdseclapikey must be uniq to the middleware attached to the service
|
|
||||||
- "traefik.http.middlewares.crowdsec-bar.plugin.bouncer.crowdseclapikey=44c36dac5c4140af9f06f397508e82c7"
|
|
||||||
- "traefik.http.middlewares.crowdsec-bar.plugin.bouncer.rediscacheenabled=true"
|
|
||||||
- "traefik.http.middlewares.crowdsec-bar.plugin.bouncer.loglevel=DEBUG"
|
|
||||||
|
|
||||||
|
|
||||||
crowdsec:
|
|
||||||
image: crowdsecurity/crowdsec:v1.4.3
|
|
||||||
container_name: "crowdsec"
|
|
||||||
restart: unless-stopped
|
|
||||||
environment:
|
|
||||||
COLLECTIONS: crowdsecurity/traefik
|
|
||||||
CUSTOM_HOSTNAME: crowdsec
|
|
||||||
BOUNCER_KEY_TRAEFIK_DEV_1: 40796d93c2958f9e58345514e67740e5
|
|
||||||
BOUNCER_KEY_TRAEFIK_DEV_2: 44c36dac5c4140af9f06f397508e82c7
|
|
||||||
volumes:
|
|
||||||
- ./acquis.yaml:/etc/crowdsec/acquis.yaml:ro
|
|
||||||
- logs-redis:/var/log/traefik:ro
|
|
||||||
- crowdsec-db-redis:/var/lib/crowdsec/data/
|
|
||||||
- crowdsec-config-redis:/etc/crowdsec/
|
|
||||||
labels:
|
|
||||||
- "traefik.enable=false"
|
|
||||||
|
|
||||||
redis:
|
|
||||||
image: "redis:7.0.5-alpine"
|
|
||||||
container_name: "redis"
|
|
||||||
restart: unless-stopped
|
|
||||||
command: "redis-server --save 60 1"
|
|
||||||
volumes:
|
|
||||||
- redis-data:/data
|
|
||||||
ports:
|
|
||||||
- 6379:6379
|
|
||||||
|
|
||||||
volumes:
|
|
||||||
logs-redis:
|
|
||||||
crowdsec-db-redis:
|
|
||||||
crowdsec-config-redis:
|
|
||||||
redis-data:
|
|
||||||
@@ -4,5 +4,5 @@ go 1.19
|
|||||||
|
|
||||||
require (
|
require (
|
||||||
github.com/leprosus/golang-ttl-map v1.1.7
|
github.com/leprosus/golang-ttl-map v1.1.7
|
||||||
github.com/maxlerebourg/simpleredis v1.0.3
|
github.com/maxlerebourg/simpleredis v1.0.9
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
github.com/leprosus/golang-ttl-map v1.1.7 h1:cF4AAFDDnJTFSV+/42sKLhmMluvLdRlCGS2UaifH6UM=
|
github.com/leprosus/golang-ttl-map v1.1.7 h1:cF4AAFDDnJTFSV+/42sKLhmMluvLdRlCGS2UaifH6UM=
|
||||||
github.com/leprosus/golang-ttl-map v1.1.7/go.mod h1:4QWHJPeVBbrkhOhXdhCv9IEiyj/YzkO04/iexy4vSe0=
|
github.com/leprosus/golang-ttl-map v1.1.7/go.mod h1:4QWHJPeVBbrkhOhXdhCv9IEiyj/YzkO04/iexy4vSe0=
|
||||||
github.com/maxlerebourg/simpleredis v1.0.3 h1:VhXq9bVytWDqD/TS/GjHKayvQb/VUeEql5F+yUbdOiI=
|
github.com/maxlerebourg/simpleredis v1.0.9 h1:aj1hKaYPeOVE4Ksu3TV/zsreUDDWOpKXBAvoFysiqII=
|
||||||
github.com/maxlerebourg/simpleredis v1.0.3/go.mod h1:/DH8zOK6kDskSqoX/m5CJJdNGfkIQZd/ERBJgytDDSk=
|
github.com/maxlerebourg/simpleredis v1.0.9/go.mod h1:/DH8zOK6kDskSqoX/m5CJJdNGfkIQZd/ERBJgytDDSk=
|
||||||
|
|||||||
Vendored
+2
-2
@@ -82,9 +82,9 @@ type Client struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// New Initialize cache client.
|
// New Initialize cache client.
|
||||||
func (client *Client) New(isRedis bool, host string) {
|
func (client *Client) New(isRedis bool, host, pass, database string) {
|
||||||
if isRedis {
|
if isRedis {
|
||||||
redis.Init(host)
|
redis.Init(host, pass, database)
|
||||||
client.cache = &redisCache{}
|
client.cache = &redisCache{}
|
||||||
} else {
|
} else {
|
||||||
client.cache = &localCache{}
|
client.cache = &localCache{}
|
||||||
|
|||||||
@@ -23,6 +23,7 @@ const (
|
|||||||
StreamMode = "stream"
|
StreamMode = "stream"
|
||||||
LiveMode = "live"
|
LiveMode = "live"
|
||||||
NoneMode = "none"
|
NoneMode = "none"
|
||||||
|
AppsecMode = "appsec"
|
||||||
HTTPS = "https"
|
HTTPS = "https"
|
||||||
HTTP = "http"
|
HTTP = "http"
|
||||||
)
|
)
|
||||||
@@ -32,6 +33,9 @@ type Config struct {
|
|||||||
Enabled bool `json:"enabled,omitempty"`
|
Enabled bool `json:"enabled,omitempty"`
|
||||||
LogLevel string `json:"logLevel,omitempty"`
|
LogLevel string `json:"logLevel,omitempty"`
|
||||||
CrowdsecMode string `json:"crowdsecMode,omitempty"`
|
CrowdsecMode string `json:"crowdsecMode,omitempty"`
|
||||||
|
CrowdsecAppsecEnabled bool `json:"crowdsecAppsecEnabled,omitempty"`
|
||||||
|
CrowdsecAppsecHost string `json:"crowdsecAppsecHost,omitempty"`
|
||||||
|
CrowdsecAppsecFailureBlock bool `json:"crowdsecAppsecFailureBlock,omitempty"`
|
||||||
CrowdsecLapiScheme string `json:"crowdsecLapiScheme,omitempty"`
|
CrowdsecLapiScheme string `json:"crowdsecLapiScheme,omitempty"`
|
||||||
CrowdsecLapiHost string `json:"crowdsecLapiHost,omitempty"`
|
CrowdsecLapiHost string `json:"crowdsecLapiHost,omitempty"`
|
||||||
CrowdsecLapiKey string `json:"crowdsecLapiKey,omitempty"`
|
CrowdsecLapiKey string `json:"crowdsecLapiKey,omitempty"`
|
||||||
@@ -50,11 +54,15 @@ type Config struct {
|
|||||||
CrowdsecCapiScenarios []string `json:"crowdsecCapiScenarios,omitempty"`
|
CrowdsecCapiScenarios []string `json:"crowdsecCapiScenarios,omitempty"`
|
||||||
UpdateIntervalSeconds int64 `json:"updateIntervalSeconds,omitempty"`
|
UpdateIntervalSeconds int64 `json:"updateIntervalSeconds,omitempty"`
|
||||||
DefaultDecisionSeconds int64 `json:"defaultDecisionSeconds,omitempty"`
|
DefaultDecisionSeconds int64 `json:"defaultDecisionSeconds,omitempty"`
|
||||||
ForwardedHeadersCustomName string `json:"forwardedheaderscustomheader,omitempty"`
|
HTTPTimeoutSeconds int64 `json:"httpTimeoutSeconds,omitempty"`
|
||||||
|
ForwardedHeadersCustomName string `json:"forwardedHeadersCustomName,omitempty"`
|
||||||
ForwardedHeadersTrustedIPs []string `json:"forwardedHeadersTrustedIps,omitempty"`
|
ForwardedHeadersTrustedIPs []string `json:"forwardedHeadersTrustedIps,omitempty"`
|
||||||
ClientTrustedIPs []string `json:"clientTrustedIps,omitempty"`
|
ClientTrustedIPs []string `json:"clientTrustedIps,omitempty"`
|
||||||
RedisCacheEnabled bool `json:"redisCacheEnabled,omitempty"`
|
RedisCacheEnabled bool `json:"redisCacheEnabled,omitempty"`
|
||||||
RedisCacheHost string `json:"redisCacheHost,omitempty"`
|
RedisCacheHost string `json:"redisCacheHost,omitempty"`
|
||||||
|
RedisCachePassword string `json:"redisCachePassword,omitempty"`
|
||||||
|
RedisCachePasswordFile string `json:"redisCachePasswordFile,omitempty"`
|
||||||
|
RedisCacheDatabase string `json:"redisCacheDatabase,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
func contains(source []string, target string) bool {
|
func contains(source []string, target string) bool {
|
||||||
@@ -72,17 +80,23 @@ func New() *Config {
|
|||||||
Enabled: false,
|
Enabled: false,
|
||||||
LogLevel: "INFO",
|
LogLevel: "INFO",
|
||||||
CrowdsecMode: LiveMode,
|
CrowdsecMode: LiveMode,
|
||||||
|
CrowdsecAppsecEnabled: false,
|
||||||
|
CrowdsecAppsecHost: "crowdsec:7422",
|
||||||
|
CrowdsecAppsecFailureBlock: true,
|
||||||
CrowdsecLapiScheme: HTTP,
|
CrowdsecLapiScheme: HTTP,
|
||||||
CrowdsecLapiHost: "crowdsec:8080",
|
CrowdsecLapiHost: "crowdsec:8080",
|
||||||
CrowdsecLapiKey: "",
|
CrowdsecLapiKey: "",
|
||||||
CrowdsecLapiTLSInsecureVerify: false,
|
CrowdsecLapiTLSInsecureVerify: false,
|
||||||
UpdateIntervalSeconds: 60,
|
UpdateIntervalSeconds: 60,
|
||||||
DefaultDecisionSeconds: 60,
|
DefaultDecisionSeconds: 60,
|
||||||
|
HTTPTimeoutSeconds: 10,
|
||||||
ForwardedHeadersCustomName: "X-Forwarded-For",
|
ForwardedHeadersCustomName: "X-Forwarded-For",
|
||||||
ForwardedHeadersTrustedIPs: []string{},
|
ForwardedHeadersTrustedIPs: []string{},
|
||||||
ClientTrustedIPs: []string{},
|
ClientTrustedIPs: []string{},
|
||||||
RedisCacheEnabled: false,
|
RedisCacheEnabled: false,
|
||||||
RedisCacheHost: "redis:6379",
|
RedisCacheHost: "redis:6379",
|
||||||
|
RedisCachePassword: "",
|
||||||
|
RedisCacheDatabase: "",
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -115,7 +129,7 @@ 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
|
//nolint:gocyclo,gocognit
|
||||||
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
|
||||||
@@ -128,6 +142,10 @@ func ValidateParams(config *Config) error {
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if _, err := GetVariable(config, "RedisCachePassword"); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
if config.CrowdsecMode == AloneMode {
|
if config.CrowdsecMode == AloneMode {
|
||||||
if _, err := GetVariable(config, "CrowdsecCapiMachineID"); err != nil {
|
if _, err := GetVariable(config, "CrowdsecCapiMachineID"); err != nil {
|
||||||
return err
|
return err
|
||||||
@@ -138,13 +156,12 @@ func ValidateParams(config *Config) error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// This only check that the format of the URL scheme:// is correct and do not make requests
|
if err := validateURL("CrowdsecLapi", config.CrowdsecLapiScheme, config.CrowdsecLapiHost); err != nil {
|
||||||
testURL := url.URL{
|
return err
|
||||||
Scheme: config.CrowdsecLapiScheme,
|
|
||||||
Host: config.CrowdsecLapiHost,
|
|
||||||
}
|
}
|
||||||
if _, err := http.NewRequest(http.MethodGet, testURL.String(), nil); err != nil {
|
|
||||||
return fmt.Errorf("CrowdsecLapiScheme://CrowdsecLapiHost: '%v://%v' must be an URL", config.CrowdsecLapiScheme, config.CrowdsecLapiHost)
|
if err := validateURL("CrowdsecAppsec", config.CrowdsecLapiScheme, config.CrowdsecAppsecHost); err != nil {
|
||||||
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
lapiKey, err := GetVariable(config, "CrowdsecLapiKey")
|
lapiKey, err := GetVariable(config, "CrowdsecLapiKey")
|
||||||
@@ -179,12 +196,21 @@ func ValidateParams(config *Config) error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func validateURL(variable, scheme, host string) error {
|
||||||
|
// This only check that the format of the URL scheme://host is correct and do not make requests
|
||||||
|
testURL := url.URL{Scheme: scheme, Host: host}
|
||||||
|
if _, err := http.NewRequest(http.MethodGet, testURL.String(), nil); err != nil {
|
||||||
|
return fmt.Errorf("CrowdsecLapiScheme://%sHost: '%v://%v' must be an URL", variable, scheme, host)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
// validHeaderFieldByte reports whether b is a valid byte in a header
|
// validHeaderFieldByte reports whether b is a valid byte in a header
|
||||||
// field name. RFC 7230 says:
|
// field name. RFC 7230 says:
|
||||||
// valid ! # $ % & ' * + - . ^ _ ` | ~ DIGIT ALPHA
|
// valid ! # $ % & ' * + - . ^ _ ` | ~ DIGIT ALPHA
|
||||||
// See https://httpwg.github.io/specs/rfc7230.html#rule.token.separators
|
// See https://httpwg.github.io/specs/rfc7230.html#rule.token.separators
|
||||||
func validateParamsAPIKey(lapiKey string) error {
|
func validateParamsAPIKey(lapiKey string) error {
|
||||||
reg := regexp.MustCompile("^[a-zA-Z0-9 !#$%&'*+-.^_`|~]*$")
|
reg := regexp.MustCompile("^[a-zA-Z0-9 !#$%&'*+-.^_`|~=/]*$")
|
||||||
if !reg.Match([]byte(lapiKey)) {
|
if !reg.Match([]byte(lapiKey)) {
|
||||||
return fmt.Errorf("CrowdsecLapiKey doesn't valid this regexp: '/%s/'", reg.String())
|
return fmt.Errorf("CrowdsecLapiKey doesn't valid this regexp: '/%s/'", reg.String())
|
||||||
}
|
}
|
||||||
@@ -232,14 +258,15 @@ func validateParamsRequired(config *Config) error {
|
|||||||
requiredInt := map[string]int64{
|
requiredInt := map[string]int64{
|
||||||
"UpdateIntervalSeconds": config.UpdateIntervalSeconds,
|
"UpdateIntervalSeconds": config.UpdateIntervalSeconds,
|
||||||
"DefaultDecisionSeconds": config.DefaultDecisionSeconds,
|
"DefaultDecisionSeconds": config.DefaultDecisionSeconds,
|
||||||
|
"HTTPTimeoutSeconds": config.HTTPTimeoutSeconds,
|
||||||
}
|
}
|
||||||
for key, val := range requiredInt {
|
for key, val := range requiredInt {
|
||||||
if val < 1 {
|
if val < 1 {
|
||||||
return fmt.Errorf("%v: cannot be less than 1", key)
|
return fmt.Errorf("%v: cannot be less than 1", key)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if !contains([]string{NoneMode, LiveMode, StreamMode, AloneMode}, config.CrowdsecMode) {
|
if !contains([]string{NoneMode, LiveMode, StreamMode, AloneMode, AppsecMode}, config.CrowdsecMode) {
|
||||||
return fmt.Errorf("CrowdsecMode: must be one of 'none', 'live', 'stream' or 'alone'")
|
return fmt.Errorf("CrowdsecMode: must be one of 'none', 'live', 'stream', 'alone' or 'appsec'")
|
||||||
}
|
}
|
||||||
if !contains([]string{HTTP, HTTPS}, config.CrowdsecLapiScheme) {
|
if !contains([]string{HTTP, HTTPS}, config.CrowdsecLapiScheme) {
|
||||||
return fmt.Errorf("CrowdsecLapiScheme: must be one of 'http' or 'https'")
|
return fmt.Errorf("CrowdsecLapiScheme: must be one of 'http' or 'https'")
|
||||||
|
|||||||
+30
-1
@@ -1,2 +1,31 @@
|
|||||||
# simpleredis
|
# simpleredis
|
||||||
Minimal go redis with only get, set and delete operation
|
Minimal go redis with only `get`, `set` and `delete` operation.
|
||||||
|
It supports password authentication with redis.
|
||||||
|
With **NO** external dependencies.
|
||||||
|
|
||||||
|
## Example
|
||||||
|
```go
|
||||||
|
import simpleredis "github.com/maxlerebourg/simpleredis"
|
||||||
|
|
||||||
|
var redis simpleredis.SimpleRedis
|
||||||
|
|
||||||
|
redis.Init("redis:6379", "", "") // redisHost, redisPass, redisDatabase
|
||||||
|
|
||||||
|
err := redis.Set("test", []bytes("whatever"), 60), // Set key "test" with "whatever" for 60 seconds
|
||||||
|
if err != nil {
|
||||||
|
...
|
||||||
|
}
|
||||||
|
val, err := redis.Get("test") // get key test
|
||||||
|
if err != nil {
|
||||||
|
// err could be only redis:unreachable, redis:miss or redis:timeout available in simpleredis.RedisUnreachable
|
||||||
|
...
|
||||||
|
}
|
||||||
|
err = redis.Del("test")
|
||||||
|
if err != nil {
|
||||||
|
...
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## Author
|
||||||
|
Max Lerebourg @ [Primadviz.com](https://primadviz.com)
|
||||||
|
Mathieu Hanotaux
|
||||||
|
|||||||
+60
-24
@@ -17,10 +17,12 @@ const (
|
|||||||
RedisUnreachable = "redis:unreachable"
|
RedisUnreachable = "redis:unreachable"
|
||||||
RedisMiss = "redis:miss"
|
RedisMiss = "redis:miss"
|
||||||
RedisTimeout = "redis:timeout"
|
RedisTimeout = "redis:timeout"
|
||||||
|
RedisNoAuth = "redis:noauth"
|
||||||
|
RedisIssue = "redis:issue?"
|
||||||
)
|
)
|
||||||
|
|
||||||
// A RedisCmd is used to communicate with redis at low level using commands.
|
// A redisCmd is used to communicate with redis at low level using commands.
|
||||||
type RedisCmd struct {
|
type redisCmd struct {
|
||||||
Command string
|
Command string
|
||||||
Name string
|
Name string
|
||||||
Data []byte
|
Data []byte
|
||||||
@@ -30,7 +32,9 @@ type RedisCmd struct {
|
|||||||
|
|
||||||
// A SimpleRedis is used to communicate with redis.
|
// A SimpleRedis is used to communicate with redis.
|
||||||
type SimpleRedis struct {
|
type SimpleRedis struct {
|
||||||
redisHost string
|
host string
|
||||||
|
pass string
|
||||||
|
database string
|
||||||
}
|
}
|
||||||
|
|
||||||
func genRedisArray(params ...[]byte) []byte {
|
func genRedisArray(params ...[]byte) []byte {
|
||||||
@@ -49,12 +53,29 @@ func send(wr *textproto.Writer, method string, data []byte) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func askRedis(hostnamePort string, cmd RedisCmd, channel chan RedisCmd) {
|
func (sr *SimpleRedis) waitRedis(reader *textproto.Reader, channel chan redisCmd) {
|
||||||
|
for {
|
||||||
|
select {
|
||||||
|
case <-time.After(time.Second * 1):
|
||||||
|
channel <- redisCmd{Error: fmt.Errorf(RedisTimeout)}
|
||||||
|
return
|
||||||
|
default:
|
||||||
|
read, _ := reader.ReadLineBytes()
|
||||||
|
if string(read) != "+OK" {
|
||||||
|
channel <- redisCmd{Error: fmt.Errorf(RedisNoAuth)}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// breaks out of for
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (sr *SimpleRedis) askRedis(cmd redisCmd, channel chan redisCmd) redisCmd {
|
||||||
dialer := net.Dialer{Timeout: 2 * time.Second}
|
dialer := net.Dialer{Timeout: 2 * time.Second}
|
||||||
conn, err := dialer.Dial("tcp", hostnamePort)
|
conn, err := dialer.Dial("tcp", sr.host)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
channel <- RedisCmd{Error: fmt.Errorf(RedisUnreachable)}
|
return redisCmd{Error: fmt.Errorf(RedisUnreachable)}
|
||||||
return
|
|
||||||
}
|
}
|
||||||
defer func() {
|
defer func() {
|
||||||
if err := conn.Close(); err != nil {
|
if err := conn.Close(); err != nil {
|
||||||
@@ -65,6 +86,18 @@ func askRedis(hostnamePort string, cmd RedisCmd, channel chan RedisCmd) {
|
|||||||
writer := textproto.NewWriter(bufio.NewWriter(conn))
|
writer := textproto.NewWriter(bufio.NewWriter(conn))
|
||||||
reader := textproto.NewReader(bufio.NewReader(conn))
|
reader := textproto.NewReader(bufio.NewReader(conn))
|
||||||
|
|
||||||
|
if sr.pass != "" {
|
||||||
|
data := genRedisArray([]byte("AUTH"), []byte(sr.pass))
|
||||||
|
send(writer, "auth", data)
|
||||||
|
sr.waitRedis(reader, channel)
|
||||||
|
}
|
||||||
|
|
||||||
|
if sr.database != "" {
|
||||||
|
data := genRedisArray([]byte("SELECT"), []byte(sr.database))
|
||||||
|
send(writer, "select", data)
|
||||||
|
sr.waitRedis(reader, channel)
|
||||||
|
}
|
||||||
|
|
||||||
switch cmd.Command {
|
switch cmd.Command {
|
||||||
case "SET":
|
case "SET":
|
||||||
data := genRedisArray([]byte("SET"), []byte(cmd.Name), cmd.Data, []byte("EX"), []byte(fmt.Sprintf("%d", cmd.Duration)))
|
data := genRedisArray([]byte("SET"), []byte(cmd.Name), cmd.Data, []byte("EX"), []byte(fmt.Sprintf("%d", cmd.Duration)))
|
||||||
@@ -78,36 +111,39 @@ func askRedis(hostnamePort string, cmd RedisCmd, channel chan RedisCmd) {
|
|||||||
for {
|
for {
|
||||||
select {
|
select {
|
||||||
case <-time.After(time.Second * 1):
|
case <-time.After(time.Second * 1):
|
||||||
channel <- RedisCmd{Error: fmt.Errorf(RedisTimeout)}
|
return redisCmd{Error: fmt.Errorf(RedisTimeout)}
|
||||||
return
|
|
||||||
default:
|
default:
|
||||||
read, _ := reader.ReadLineBytes()
|
read, _ := reader.ReadLineBytes()
|
||||||
if string(read) != "$1" {
|
str := string(read)
|
||||||
channel <- RedisCmd{Error: fmt.Errorf(RedisMiss)}
|
if strings.Contains(str, "-NOAUTH") {
|
||||||
return
|
return redisCmd{Error: fmt.Errorf(RedisNoAuth)}
|
||||||
|
} else if str != "$1" {
|
||||||
|
return redisCmd{Error: fmt.Errorf(RedisMiss)}
|
||||||
}
|
}
|
||||||
read, _ = reader.ReadLineBytes()
|
read, _ = reader.ReadLineBytes()
|
||||||
channel <- RedisCmd{Data: read}
|
return redisCmd{Data: read}
|
||||||
return
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
return redisCmd{Error: fmt.Errorf(RedisIssue)}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Init sets the redisHost used to connect to redis.
|
// Init sets the redisHost used to connect to redis.
|
||||||
func (sr *SimpleRedis) Init(redisHost string) {
|
func (sr *SimpleRedis) Init(host, pass, database string) {
|
||||||
sr.redisHost = redisHost
|
sr.host = host
|
||||||
|
sr.pass = pass
|
||||||
|
sr.database = database
|
||||||
}
|
}
|
||||||
|
|
||||||
// Get fetches the value for key name in redis.
|
// Get fetches the value for key name in redis.
|
||||||
func (sr *SimpleRedis) Get(name string) ([]byte, error) {
|
func (sr *SimpleRedis) Get(name string) ([]byte, error) {
|
||||||
redisCmd := RedisCmd{
|
cmd := redisCmd{
|
||||||
Command: "GET",
|
Command: "GET",
|
||||||
Name: name,
|
Name: name,
|
||||||
}
|
}
|
||||||
channel := make(chan RedisCmd)
|
channel := make(chan redisCmd)
|
||||||
go askRedis(sr.redisHost, redisCmd, channel)
|
resp := sr.askRedis(cmd, channel)
|
||||||
resp := <-channel
|
|
||||||
if resp.Error != nil {
|
if resp.Error != nil {
|
||||||
return nil, resp.Error
|
return nil, resp.Error
|
||||||
}
|
}
|
||||||
@@ -116,22 +152,22 @@ func (sr *SimpleRedis) Get(name string) ([]byte, error) {
|
|||||||
|
|
||||||
// Set updates the value for key name in redis with value data for duration.
|
// Set updates the value for key name in redis with value data for duration.
|
||||||
func (sr *SimpleRedis) Set(name string, data []byte, duration int64) error {
|
func (sr *SimpleRedis) Set(name string, data []byte, duration int64) error {
|
||||||
redisCmd := RedisCmd{
|
cmd := redisCmd{
|
||||||
Command: "SET",
|
Command: "SET",
|
||||||
Name: name,
|
Name: name,
|
||||||
Data: data,
|
Data: data,
|
||||||
Duration: duration,
|
Duration: duration,
|
||||||
}
|
}
|
||||||
go askRedis(sr.redisHost, redisCmd, nil)
|
sr.askRedis(cmd, nil)
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// Del removes the key name in redis.
|
// Del removes the key name in redis.
|
||||||
func (sr *SimpleRedis) Del(name string) error {
|
func (sr *SimpleRedis) Del(name string) error {
|
||||||
redisCmd := RedisCmd{
|
cmd := redisCmd{
|
||||||
Command: "DEL",
|
Command: "DEL",
|
||||||
Name: name,
|
Name: name,
|
||||||
}
|
}
|
||||||
go askRedis(sr.redisHost, redisCmd, nil)
|
sr.askRedis(cmd, nil)
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|||||||
Vendored
+1
-1
@@ -1,6 +1,6 @@
|
|||||||
# github.com/leprosus/golang-ttl-map v1.1.7
|
# github.com/leprosus/golang-ttl-map v1.1.7
|
||||||
## explicit; go 1.15
|
## explicit; go 1.15
|
||||||
github.com/leprosus/golang-ttl-map
|
github.com/leprosus/golang-ttl-map
|
||||||
# github.com/maxlerebourg/simpleredis v1.0.3
|
# github.com/maxlerebourg/simpleredis v1.0.9
|
||||||
## explicit; go 1.19
|
## explicit; go 1.19
|
||||||
github.com/maxlerebourg/simpleredis
|
github.com/maxlerebourg/simpleredis
|
||||||
|
|||||||
Reference in New Issue
Block a user