traefik 可以帮你:
- 自动续证书
- 反代(自动处理 ws、tls 证书、反代地址、压缩 etc.)
- 使用插件代替 waf
- 负载均衡(可以使用多个后端、k8s)
- 支持 mTLS 认证(如果你使用的是 Cloudflare 的 CDN)
前言
本文是对traefik的一个简单介绍,主要介绍如何使用 traefik 来代替 waf(使用 CrowdSec)、nginx、acme.sh、mtls-auth 等。
traefik会是你服务器的入口,所有的请求都会先经过 traefik,然后再转发到后端服务。
后端服务也不用配置绑定的端口和ip了(例如docker.port: 127.0.0.1:8080:8080,然后nginx反代,每个服务都要记住端口很麻烦),traefik 会自动处理。
Note (info)
主要贴出配置,注意多看注释
Warning (IMPORTANT)
CrowdSec 的工作原理之后会说
Note (info)
简单说一下 mtls 的好处
- 如果其他人通过你的域名扫描全网 ip,没有 mtls 很容易就扫到你的服务器 ip 了,然后被定点攻击
- 如果你使用了 mtls 认证,其他人就算扫到你的 ip 也无法访问你的服务,除非他有 cf 的客户端证书,这个只有 cf 有私钥,无法伪造
traefik 配置
此项配置了之后可以
- 自动帮你续证书
- 如果你用的是 cf 的 cdn,还可以自动 mtls 认证。
- 使用 CrowdSec 作为 WAF(之后再配置 CrowdSec)
- 扫 443 和 80 端口的请求直接丢弃
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.logWarning (ERROR)
ban.html 我就不贴了,需要的自己 ai 写一个
Warning (IMPORTANT)
- 如果要自动帮你续证书,记得
acme.json的权限要设置为600,否则 traefik 无法写入证书。 - 如果你使用的是 cf 的 cdn,记得在添加你的 cf 证书。到这里下载证书。
- 记得事先创建
traefik-net网络,或者在docker-compose.yml中注释掉网络部分。
services: traefik: image: traefik:latest # 建议使用明确的版本号,而不是 latest。现在用的是 v3 container_name: traefik restart: unless-stopped
# --- 静态配置:通过 command 而不是文件 --- command: - '--entrypoints.websecure.http.middlewares=crowdsec-bouncer@file,global-compressor@file' # --- CrowdSec 插件配置 --- - '--experimental.plugins.crowdsec-bouncer-traefik-plugin.modulename=github.com/maxlerebourg/crowdsec-bouncer-traefik-plugin' - '--experimental.plugins.crowdsec-bouncer-traefik-plugin.version=v1.4.4' # --- API 和仪表盘 --- # - "--api.dashboard=true" # 启用仪表盘 # # 警告:下面的 insecure=true 仅用于开发环境,方便通过 8080 端口访问。生产环境请务必通过安全路由暴露。 # - "--api.insecure=true"
# --- 入口点定义 --- - '--entrypoints.web.address=:80' # 定义 HTTP 入口点,监听80端口 - '--entrypoints.websecure.address=:443' # 定义 HTTPS 入口点,监听443端口
# --- Cloudflare IPs --- - '--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'
# --- Docker Provider 配置 --- - '--providers.docker=true' # 启用 Docker 服务发现 - '--providers.docker.exposedbydefault=false' # 默认不暴露任何容器,更安全 - '--providers.docker.network=traefik-net' # 指定Traefik监听哪个网络
#配置log - '--log.level=DEBUG' # 设置日志级别为DEBUG - '--log.filePath=/var/log/traefik.log' # 设置日志文件路径 # - "--log.format=json" # 设置日志格式为JSON,便于后续处理
# --- 文件 Provider 配置 --- - '--providers.file=true' # 启用文件提供者 - '--providers.file.directory=/etc/traefik/dynamic_conf' # 指定动态配置文件目录 - '--providers.file.watch=true' # 启用动态配置文件监控
- '--accesslog=true' # 启用访问日志 - '--accesslog.format=json' # 建议使用json格式,便于后续处理
# --- Let's Encrypt (SSL证书) 配置 --- # 暂时注释掉,在您配置好域名后可以启用 - '--certificatesresolvers.myresolver.acme.httpchallenge=true' - '--certificatesresolvers.myresolver.acme.httpchallenge.entrypoint=web' - '--certificatesresolvers.myresolver.acme.email=xxx' - '--certificatesresolvers.myresolver.acme.storage=/etc/traefik/acme.json' # 注意:请确保acme.json文件权限为600,Traefik才能写入证书
# --- 端口映射 --- ports: - '80:80' - '443:443' # - "8080:8080" # 暴露仪表盘的8080端口(仅开发)
# --- 挂载卷 --- volumes: # 挂载Docker socket,让Traefik可以监听到容器的创建和销毁 - '/var/run/docker.sock:/var/run/docker.sock:ro' # 挂载acme.json文件,用于持久化SSL证书 - './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: truetls: options: # 给这个 TLS 选项起一个名字 cloudflare-mtls: minVersion: VersionTLS12 clientAuth: caFiles: - /etc/traefik/cf-cert/cloudflare-ca.pem clientAuthType: "RequireAndVerifyClientCert"http: middlewares: # 定义一个全局可用的压缩中间件,我们给它起个名字叫 'global-compressor' global-compressor: compress: # 在这里可以设置更详细的参数,进行精细化控制
# 排除不需要压缩的内容类型(例如图片,因为它们已经被压缩过了) excludedContentTypes: - 'image/png' - 'image/jpeg' - 'image/gif' - 'application/pdf'
# 设置压缩算法,默认是gzip # 也可以使用 'br' (Brotli) 或者 'deflate' # 但注意,Brotli在某些浏览器上支持不如gzip # algorithm: "gzip" # 默认是gzip,通常不需要显式设置 # 设置压缩级别,范围是1到9,数字越大压缩率越高,但CPU消耗也越大
# 设置一个最小响应体大小,小于这个大小的响应不进行压缩 (避免浪费CPU) minResponseBodyBytes: 1024 # 即只压缩大于1KB的响应http: middlewares: crowdsec-bouncer: plugin: crowdsec-bouncer-traefik-plugin: CrowdsecLapiKey: '' #填自己的 Enabled: 'true' crowdsecMode: 'stream' # logLevel: "DEBUG" banHTMLFilePath: '/etc/traefik/dynamic_conf/ban.html' #自己生成 forwardedHeadersCustomName: 'CF-Connecting-IP' forwardedHeadersTrustedIPs: # Cloudflare IPs - '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' # Local IPs - '10.0.10.23/32' - '10.0.20.0/24' # Local IPs clientTrustedIPs: - '192.168.0.0/16' - '10.0.0.0/8' - '172.16.0.0/12'http: routers: catchall-http: rule: 'HostRegexp(`{host:.+}`)' entryPoints: - web service: 'blackhole-http-svc' # 指向HTTP黑洞服务 priority: 1
services: blackhole-http-svc: loadBalancer: servers: - url: 'http://10.255.255.1:80'
# TCP部分:处理所有未匹配的HTTPS请求 (例如 https://<IP>)tcp: routers: catchall-tls: rule: 'HostSNI(`*`)' # 匹配任何TLS连接 entryPoints: - websecure service: 'blackhole-tcp-svc' # 指向TCP黑洞服务 priority: 1 tls: passthrough: false
services: blackhole-tcp-svc: loadBalancer: servers: # TCP服务使用 'address' 字段 - 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/traefikcollection 中的解析器和场景分析日志,检测到攻击行为(如暴力破解、扫描等)。 - 一旦检测到威胁,CrowdSec 会生成一个“决策”,将攻击者的 IP 标记为恶意,并推送到 local API。
- local API马上推送到所有Bouncer(如果还配置了其他Bouncer),其中Traefik 中的 CrowdSec Bouncer 插件会获取最新的决策列表。
- 当被标记为恶意的 IP 再次尝试访问时,Bouncer 会在 Traefik 层面直接拒绝该请求,从而保护所有后端服务。
services: crowdsec: image: crowdsecurity/crowdsec:latest container_name: crowdsec restart: unless-stopped environment: - GID=999 - COLLECTIONS=crowdsecurity/traefik #安装了 crowdsecurity/traefik 集合,CrowdSec 知道 Traefik 日志长什么样。当它发现某个容器(也就是 Traefik 容器)的日志格式与 Traefik 解析器匹配时,就会自动开始采集和分析这些日志。 volumes: - /var/run/docker.sock:/var/run/docker.sock:ro #允许 CrowdSec 监控其他容器的日志 - ./data:/var/lib/crowdsec/data - ./config:/etc/crowdsec networks: - traefik-net
networks: traefik-net: external: true反代配置
主要通过 traefik 的标签(labels)来配置反代。
services: umami: image: ghcr.io/umami-software/umami:postgresql-latest # ports: # - "3000:3000" 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`) # 替换为你的域名。会自动帮你申请证书,记得把ip指向vps ip或者cname到cf的cdn - traefik.http.services.umami.loadbalancer.server.port=3000 # umami 默认端口。traefik会主动处理,不需要要你自己设置端口。如果有另一个服务在网络traefik-net上监听3000端口,traefik会自动处理冲突。服务b想连接此服务,直接使用服务名和端口,例如 `http://umami: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 # 使用之前定义的 mtls 选项。traefik似乎不支持全局设置
networks: traefik-net: name: traefik-net external: true