背景 在Traefik中,Fail2Ban 是一个非常强大的插件,可以轻松实现IP黑名单,IP白名单,IP封禁等操作。我们可以根据不同的需求,选择封禁的时长。
比如:我想针对1分钟内请求100次以上的IP封禁1小时
。对于比DDOS小很多的某些攻击,可以有效防止。(如果处于DDOS,大部分都是在服务商那就已经将云服务拉入小黑屋了, 此时服务流量基本进不来了,所以这里只是针对小攻击的处理方法,或者某些暴力爬虫等)
对于Fail2Ban的原理,你可以参考Github ,基本原理是时间窗口,go语言写这类工具再对接Kubernetes有天生的优势,所以这里就不再赘述了。
环境 traefik: v2.9.10 kubernetes: v1.24.6+k3s1 Fail2Ban: v0.7.1
安装 在K3S中,我们已经内置了Traefik,所以如果想安装插件,我们需要对内置的Traefik进行改造,添加插件。
由于K3S会周期性的执行 /var/lib/rancher/k3s/server/manifests
目录下的所有yaml,而如果我们修改了默认的traefik.yaml
,K3S会自动恢复默认文件 ,所以我们这里需要新建一个yaml文件,用HelmChartConfig
的方式修改traefik安装的配置即可。
创建配置文件 nano /var/lib/rancher/k3s/server/manifests/traefik-config.yaml
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 apiVersion: helm.cattle.io/v1 kind: HelmChartConfig metadata: name: traefik namespace: kube-system spec: valuesContent: |- additionalArguments: - "--experimental.plugins.fail2ban.modulename=github.com/tomMoulard/fail2ban" - "--experimental.plugins.fail2ban.version=v0.7.1" - "--providers.kubernetescrd=true" - "--providers.kubernetesingress=true" experimental: plugins: enabled: true providers: kubernetesCRD: allowCrossNamespace: true image: name: "rancher/mirrored-library-traefik" tag: "2.9.10"
additionalArguments用于添加插件的启动参数,在启动Traefik时,他会自动下载,需要注意的是,他是自主访问github下载的,如果网络不通畅,下载插件会失败,但Traefik会正常启动,导致所有使用了fail2ban的Middleware全部失效,所以启动后还要确保有没有fail2ban的成功日志信息
experimental用于启动插件(新版本好像不配置也可以)
allowCrossNamespace允许跨命名空间调用(比如我在A命名空间创建了有关fail2ban的Middleware,但我想在B命名空间同样使用这个Middleware,所以需要允许跨命名空间调用)
因为自带的Traefik版本比较低,所以指定下新版本的镜像,亲测在2.10版本中无法使用,所以只能升级到2.9版本的,在自带的2.6版本同样无法使用,会报plugin: unknown plugin type: sablier
的错误,重启也无效
additionalArguments中插件的CLI配置,可以参考每个插件右上角的Install pLugin
,里面写了具体的配置,插件地址是:https://plugins.traefik.io/plugins/628c9ebcffc0cd18356a979f/fail2-ban
在配置好之后,我们等待一会,K3S会自动执行该配置文件。
接下来使用 kubectl get jobs -n kube-system
查看下Helm job是否触发
1 2 3 4 root@m3:/var/lib/rancher/k3s/server/manifests# kubectl get jobs -n kube-system NAME COMPLETIONS DURATION AGE helm-install-traefik 1/1 7s 13h helm-install-traefik-crd 1/1 8s 92d
可以看到,已经执行完了,接下来看下Traefik是否正常重启,使用kubectl describe pods -n kube-system traefik-xxxxx
,启动参数是否含有上面配置的。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 Containers: traefik: Container ID: docker://6f058c4329b788f51bbea5f6e8debf15217f8067647 Image: rancher/mirrored-library-traefik:2.9.10 Image ID: docker-pullable://rancher/mirrored-library-traefik@sha256:aaec134463b277ca7aa4f888fe724a Ports: 9100/TCP, 9000/TCP, 8000/TCP, 8443/TCP Host Ports: 0/TCP, 0/TCP, 0/TCP, 0/TCP Args: ... --experimental.plugins.fail2ban.modulename=github.com/tomMoulard/fail2ban --experimental.plugins.fail2ban.version=v0.7.1 --providers.kubernetescrd=true --providers.kubernetesingress=true ...
查看Traefik的日志,看看是否有fail2ban的日志信息
1 2024/04/23 14:05:33 Plugin: FailToBan is up and running
接下来我们只需要创建一个Middleware,然后就可以使用Fail2Ban了。
使用 创建Middleware,规则为1分钟内请求100次以上的IP封禁1小时
1 2 3 4 5 6 7 8 9 10 11 12 13 apiVersion: traefik.containo.us/v1alpha1 kind: Middleware metadata: name: api-protect namespace: default spec: plugin: fail2ban: rules: bantime: 1h enabled: 'true' findtime: 1m maxretry: '100'
这里参数的配置请参考插件README即可,创建好Middle后,我们需要将流量拦截到这个Middleware上。
在你想要配置的Ingress中,添加如下信息:
1 2 3 4 5 apiVersion: networking.k8s.io/v1 kind: Ingress metadata: annotations: traefik.ingress.kubernetes.io/router.middlewares: default-api-protect@kubernetescrd
绑定规则:命名空间-中间件名称@kubernetescrd
如果有多个则用英文逗号分隔(traefik.ingress.kubernetes.io/router.middlewares: default-api-protect1@kubernetescrd,default-api-protect2@kubernetescrd)
比如,我想先从http转https,再拦截,就需要两个middleware。
在Traefik的Dashboard中,能看到对应的Middleware和绑定的pod即可,如下图所示:
测试 我们可以把条件改苛刻些测试下,比如5秒内有两次请求则ban1分钟
,请求如果返回403或者5xx则证明ban成功了
1 2 3 4 5 6 7 8 spec: plugin: fail2ban: rules: bantime: 1m enabled: 'true' findtime: 5s maxretry: '2'
更多玩法 白名单 traefik的白名单坑很多,原因在于外网ip取决于X-Real-Ip
,而为了透传此参数,下的功夫可不少。
首先,我们需要添加如下配置 nano /var/lib/rancher/k3s/server/manifests/traefik-config.yaml
:
1 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 apiVersion: helm.cattle.io/v1 kind: HelmChartConfig metadata: name: traefik namespace: kube-system spec: valuesContent: |- ... ports: # http内部信任列表 web: forwardedHeaders: trustedIPs: - 10.0.0.1/8 proxyProtocol: trustedIPs: - 10.0.0.1/8 # https内部信任列表 websecure: forwardedHeaders: trustedIPs: - 10.0.0.1/8 proxyProtocol: trustedIPs: - 10.0.0.1/8 # 使用 Local 策略时,客户端的 IP 地址将被保留,也就是所谓的真实ip service: spec: externalTrafficPolicy: Local # 此处重要,一定要选择一个非master的节点,否则无法获取到客户端真实ip affinity: nodeAffinity: preferredDuringSchedulingIgnoredDuringExecution: - weight: 1 preference: matchExpressions: - key: kubernetes.io/hostname operator: In values: - n6 requiredDuringSchedulingIgnoredDuringExecution: nodeSelectorTerms: - matchExpressions: - key: kubernetes.io/hostname operator: In values: - n5 - n6 # 这个配置在我这里无效,但也贴出来吧。同理,不要在master上schedule tolerations: - key: "CriticalAddonsOnly" operator: "Exists" - key: "node-role.kubernetes.io/master" operator: "Exists" effect: "NoSchedule" additionalArguments: - "--experimental.plugins.fail2ban.modulename=github.com/tomMoulard/fail2ban" - "--experimental.plugins.fail2ban.version=v0.8.3" # 此处重要,允许insecure - "--entryPoints.web.proxyProtocol.insecure" - "--entryPoints.web.forwardedHeaders.insecure" - "--entryPoints.websecure.proxyProtocol.insecure" - "--entryPoints.websecure.forwardedHeaders.insecure" experimental: plugins: enabled: true ...
创建middleware:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 apiVersion: traefik.containo.us/v1alpha1 kind: Middleware metadata: name: test-allow-ip namespace: defalut spec: plugin: fail2ban: allowlist: ip: - '::1' - 127.0 .0 .1 - xx.xx.xx.xx logLevel: DEBUG
接下来我们尝试创建一个应用来看看是否生效
1 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 cat << EOF > whoami.yml apiVersion: v1 kind: Service metadata: name: whoami spec: ports: - protocol: TCP name: web port: 80 selector: app: whoami --- kind: Deployment apiVersion: apps/v1 metadata: name: whoami labels: app: whoami spec: replicas: 1 selector: matchLabels: app: whoami template: metadata: labels: app: whoami spec: containers: - name: whoami image: traefik/whoami ports: - name: web containerPort: 80 EOF
创建应用:
1 kubectl apply -f whoami.yml
接下来我们创建一个ingress,用于访问这个Deployment:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 cat << EOF > ingress-whoami.yml apiVersion: networking.k8s.io/v1 kind: Ingress metadata: name: test.yourdomain.com namespace: default annotations: traefik.ingress.kubernetes.io/router.middlewares: default-test-allow-ip@kubernetescrd spec: rules: - host: test.yourdomain.com http: paths: - backend: service: name: whoami port: number: 80 path: / pathType: Prefix EOF
1 kubectl apply -f ingress-whoami.yml
访问这个域名:http://test.yourdomain.com
,可以看到类似信息:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 Hostname: whoami-54b87bcdc-dpjhl IP: 127.0 .0 .1 IP: 10.42 .0 .251 RemoteAddr: 10.42 .7 .26 :38144 GET / HTTP/1.1 Host: test.yourdomain.com User-Agent: Mozilla/5.0 (Windows NT 10.0 ; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/128.0.0.0 Safari/537.36 Edg/128.0.0.0 Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7 Accept-Encoding: gzip, deflate Accept-Language: zh-CN,zh;q=0.9,en;q=0.8,en-GB;q=0.7,en-US;q=0.6 Cache-Control: max-age=0 Upgrade-Insecure-Requests: 1 X-Forwarded-For: 43. xx.xx.xx X-Forwarded-Host: test.yourdomain.com X-Forwarded-Port: 80 X-Forwarded-Proto: http X-Forwarded-Server: traefik-xxx-xxx X-Real-Ip: 43. xx.xx.xx
其中:X-Real-Ip: 43.xx.xx.xx
就是你的真实ip,证明已经生效,且通过该白名单ip可正常访问,其余ip均返回403错误。
经测试,如果traefik部署在master节点,X-Real-Ip
一定为内部ip10.xx.xx.xx
,导致无法通过白名单校验,切记不要在master节点部署traefik。
结语 Fail2Ban基本上满足了我对于某些恶意请求的拦截,在安装的时候经常发现不起作用,后来发现是Traefik版本的问题,2.0~2.6安装插件那会,好像还需要去官网注册token,现在已经摒弃了,取而代之的是Traefik的Hub。
白名单踩了2个月的坑,最后发现是主节点不能部署,也算是一个隐藏的坑点吧,具体为什么不能部署,后面理解了再贴吧。
在后续的文章中,我会玩一玩Traefik插件中星星最多的Sablier插件,它能根据流量自动启动pod,没人访问后自动关闭pod。我的测试环境刚好只在我开发的时候才用得到,不用的时候还占用内存(没错,说的就是你这个JVM占好多内存),看我后面的文章吧~
参考资料: