前言
本文是对traefik的一个简单介绍,主要介绍如何使用 traefik 来代替 waf(使用 CrowdSec)、nginx、acme.sh、mtls-auth 等。
traefik会是你服务器的入口,所有的请求都会先经过 traefik,然后再转发到后端服务。
后端服务也不用配置绑定的端口和ip了(例如docker.port: 127.0.0.1:8080:8080
,然后nginx反代,每个服务都要记住端口很麻烦),traefik 会自动处理。
IMPORTANT
CrowdSec 的工作原理之后会说
info
简单说一下 mtls 的好处
- 如果其他人通过你的域名扫描全网 ip,没有 mtls 很容易就扫到你的服务器 ip 了,然后被定点攻击
- 如果你使用了 mtls 认证,其他人就算扫到你的 ip 也无法访问你的服务,除非他有 cf 的客户端证书,这个只有 cf 有私钥,无法伪造
traefik 配置
此项配置了之后可以
- 自动帮你续证书
- 如果你用的是 cf 的 cdn,还可以自动 mtls 认证。
- 使用 CrowdSec 作为 WAF(之后再配置 CrowdSec)
- 扫 443 和 80 端口的请求直接丢弃
配置结构1 2 3 4 5 6 7 8 9 10 11 12 13 14
| traefik/ ├── acme.json ├── docker-compose.yml ├── cf-cert/ │ └── cloudflare-ca.pem ├── dynamic_conf/ │ ├── ban.html │ ├── cloudflare-mtls.yml │ ├── compressor.yml │ ├── crowdsec-middleware.yml │ └── drop-ip-access.yml └── logs/ └── traefik.log
|
ERROR
ban.html
我就不贴了,需要的自己 ai 写一个
IMPORTANT
- 如果要自动帮你续证书,记得
acme.json
的权限要设置为600
,否则 traefik 无法写入证书。 - 如果你使用的是 cf 的 cdn,记得在添加你的 cf 证书。到这里下载证书。
- 记得事先创建
traefik-net
网络,或者在docker-compose.yml
中注释掉网络部分。
docker-compose.yml1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80
| services: traefik: image: traefik:latest container_name: traefik restart: unless-stopped
command: - "--entrypoints.websecure.http.middlewares=crowdsec-bouncer@file,global-compressor@file" - "--experimental.plugins.crowdsec-bouncer-traefik-plugin.modulename=github.com/maxlerebourg/crowdsec-bouncer-traefik-plugin" - "--experimental.plugins.crowdsec-bouncer-traefik-plugin.version=v1.4.4"
- "--entrypoints.web.address=:80" - "--entrypoints.websecure.address=:443"
- "--entrypoints.web.forwardedheaders.trustedips=173.245.48.0/20,103.21.244.0/22,103.22.200.0/22,103.31.4.0/22,141.101.64.0/18,108.162.192.0/18,190.93.240.0/20,188.114.96.0/20,197.234.240.0/22,198.41.128.0/17,162.158.0.0/15,104.16.0.0/13,104.24.0.0/14,172.64.0.0/13,131.0.72.0/22"
- "--providers.docker=true" - "--providers.docker.exposedbydefault=false" - "--providers.docker.network=traefik-net"
- "--log.level=DEBUG" - "--log.filePath=/var/log/traefik.log"
- "--providers.file=true" - "--providers.file.directory=/etc/traefik/dynamic_conf" - "--providers.file.watch=true"
- "--accesslog=true" - "--accesslog.format=json"
- "--certificatesresolvers.myresolver.acme.httpchallenge=true" - "--certificatesresolvers.myresolver.acme.httpchallenge.entrypoint=web" - "--certificatesresolvers.myresolver.acme.email=xxx" - "--certificatesresolvers.myresolver.acme.storage=/etc/traefik/acme.json"
ports: - "80:80" - "443:443"
volumes: - "/var/run/docker.sock:/var/run/docker.sock:ro" - "./acme.json:/etc/traefik/acme.json" - "./dynamic_conf:/etc/traefik/dynamic_conf:ro" - "./logs:/var/log:rw" - "./cf-cert:/etc/traefik/cf-cert:ro"
networks: - traefik-net
networks: traefik-net: name: traefik-net external: true
|
cloudflare-mtls.yml1 2 3 4 5 6 7 8
| tls: options: cloudflare-mtls: minVersion: VersionTLS12 clientAuth: caFiles: - /etc/traefik/cf-cert/cloudflare-ca.pem clientAuthType: "RequireAndVerifyClientCert"
|
compressor.yml1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23
| http: middlewares: global-compressor: compress: excludedContentTypes: - "image/png" - "image/jpeg" - "image/gif" - "application/pdf"
minResponseBodyBytes: 1024
|
crowdsec-middleware.yml1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38
| http: middlewares: crowdsec-bouncer: plugin: crowdsec-bouncer-traefik-plugin: CrowdsecLapiKey: "" Enabled: "true" crowdsecMode: "stream" banHTMLFilePath: "/etc/traefik/dynamic_conf/ban.html" forwardedHeadersCustomName: "CF-Connecting-IP" forwardedHeadersTrustedIPs: - "173.245.48.0/20" - "103.21.244.0/22" - "103.22.200.0/22" - "103.31.4.0/22" - "141.101.64.0/18" - "108.162.192.0/18" - "190.93.240.0/20" - "188.114.96.0/20" - "197.234.240.0/22" - "198.41.128.0/17" - "162.158.0.0/15" - "104.16.0.0/13" - "104.24.0.0/14" - "172.64.0.0/13" - "131.0.72.0/22" - "10.0.10.23/32" - "10.0.20.0/24" clientTrustedIPs: - "192.168.0.0/16" - "10.0.0.0/8" - "172.16.0.0/12"
|
drop-ip-access.yml1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33
| http: routers: catchall-http: rule: "HostRegexp(`{host:.+}`)" entryPoints: - web service: "blackhole-http-svc" priority: 1
services: blackhole-http-svc: loadBalancer: servers: - url: "http://10.255.255.1:80"
tcp: routers: catchall-tls: rule: "HostSNI(`*`)" entryPoints: - websecure service: "blackhole-tcp-svc" priority: 1 tls: passthrough: false
services: blackhole-tcp-svc: loadBalancer: servers: - address: "192.0.2.1:1"
|
crowdsec 配置
这里配置 CrowdSec,通过 docker 搭建。
整个安全方案由两部分组成:
- CrowdSec Agent (
crowdsec
服务): 负责分析日志、检测威胁,并管理决策(例如,哪个 IP 应该被禁止)。 - Traefik Bouncer (作为 Traefik 插件): 在 Traefik 中运行,负责执行 CrowdSec Agent 的决策,直接在流量入口处阻止恶意 IP。
工作流程
- Traefik 接收外部请求,并将访问日志(JSON 格式)输出。
- CrowdSec Agent (
crowdsec
服务) 通过挂载的 Docker Socket 读取 Traefik 的日志。 - Agent 使用
crowdsecurity/traefik
collection 中的解析器和场景分析日志,检测到攻击行为(如暴力破解、扫描等)。 - 一旦检测到威胁,CrowdSec 会生成一个“决策”,将攻击者的 IP 标记为恶意,并推送到 local API。
- local API马上推送到所有Bouncer(如果还配置了其他Bouncer),其中Traefik 中的 CrowdSec Bouncer 插件会获取最新的决策列表。
- 当被标记为恶意的 IP 再次尝试访问时,Bouncer 会在 Traefik 层面直接拒绝该请求,从而保护所有后端服务。
crowdsec-bouncer-traefik-plugin.yml1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
| services: crowdsec: image: crowdsecurity/crowdsec:latest container_name: crowdsec restart: unless-stopped environment: - GID=999 - COLLECTIONS=crowdsecurity/traefik volumes: - /var/run/docker.sock:/var/run/docker.sock:ro - ./data:/var/lib/crowdsec/data - ./config:/etc/crowdsec networks: - traefik-net
networks: traefik-net: external: true
|
反代配置
主要通过 traefik 的标签(labels)来配置反代。
docker-compose.yml1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30
| services: umami: image: ghcr.io/umami-software/umami:postgresql-latest environment: DATABASE_URL: DATABASE_TYPE: postgresql init: true restart: unless-stopped healthcheck: test: ["CMD-SHELL", "curl http://localhost:3000/api/heartbeat"] interval: 5s timeout: 5s retries: 5 networks: - traefik-net labels: - traefik.enable=true - traefik.http.routers.umami.rule=Host(`stats.xingpingcn.top`) - traefik.http.services.umami.loadbalancer.server.port=3000 - traefik.http.routers.umami.entrypoints=websecure - traefik.http.routers.umami.tls=true - traefik.http.routers.umami.tls.certresolver=myresolver - traefik.http.routers.umami.tls.options=cloudflare-mtls@file
networks: traefik-net: name: traefik-net external: true
|