Files
Homelabv4/infrastructure/crowdsec/manifests/firewall-bouncer.yaml
T
Scooby HuskyandClaude Sonnet 5 e2c8ef0fde Commit crowdsec-firewall-bouncer headless-service fix (was uncommitted)
This fix for the EPERM ClusterIP-from-hostNetwork issue (Cilium socketLB
hostNamespaceOnly + hostNetwork bouncer + ClusterIP VIP) was made and
verified earlier but never committed - ArgoCD's selfHeal kept reverting
the live ConfigMap back to the broken crowdsec-service URL against git's
stale version, causing a fresh bouncer pod to crash-loop with the original
EPERM error again.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-08-17 20:51:47 -05:00

193 lines
5.3 KiB
YAML

# CrowdSec Firewall Bouncer - nftables enforcement on each node
# Blocks malicious IPs before they reach Cilium eBPF processing
# Requires: hostNetwork + privileged (nftables management)
# Image: community Docker build of cs-firewall-bouncer (no official image exists)
#
# api_url points at a headless Service (below), not the normal ClusterIP
# crowdsec-service. Cilium's socketLB is configured hostNamespaceOnly (see
# infrastructure/cilium/values.yaml, needed for Istio ambient compatibility),
# which means socket-level Service translation/policy enforcement applies to
# host-namespace traffic - and this bouncer runs hostNetwork: true, so it IS
# host-namespace traffic. Connecting to a ClusterIP VIP from there gets an
# immediate EPERM on connect() (confirmed live 2026-08-17, been broken since
# this DaemonSet was first deployed 2026-03-03 - not a regression from any
# recent change). A headless Service resolves straight to pod IPs via DNS -
# no VIP, nothing for socket-LB to intercept - and connects cleanly.
---
apiVersion: v1
kind: Service
metadata:
name: crowdsec-lapi-headless
namespace: crowdsec
spec:
clusterIP: None
selector:
k8s-app: crowdsec
type: lapi
version: v1
ports:
- name: lapi
port: 8080
protocol: TCP
targetPort: 8080
---
apiVersion: v1
kind: ConfigMap
metadata:
name: crowdsec-firewall-bouncer-config
namespace: crowdsec
data:
crowdsec-firewall-bouncer.yaml: |
mode: nftables
update_frequency: 10s
log_mode: stdout
log_level: info
api_url: http://crowdsec-lapi-headless.crowdsec.svc:8080/
api_key: ${API_KEY}
insecure_skip_verify: false
disable_ipv6: false
deny_action: DROP
deny_log: true
deny_log_prefix: "crowdsec: "
supported_decisions_types:
- ban
blacklists_ipv4: crowdsec-blacklists
blacklists_ipv6: crowdsec6-blacklists
nftables:
ipv4:
enabled: true
set-only: false
table: crowdsec
chain: crowdsec-chain
priority: -10
ipv6:
enabled: true
set-only: false
table: crowdsec6
chain: crowdsec6-chain
priority: -10
nftables_hooks:
- input
- forward
prometheus:
enabled: true
listen_addr: 0.0.0.0
listen_port: 60601
---
apiVersion: apps/v1
kind: DaemonSet
metadata:
name: crowdsec-firewall-bouncer
namespace: crowdsec
labels:
app: crowdsec-firewall-bouncer
spec:
selector:
matchLabels:
app: crowdsec-firewall-bouncer
template:
metadata:
labels:
app: crowdsec-firewall-bouncer
spec:
hostNetwork: true
dnsPolicy: ClusterFirstWithHostNet
tolerations:
- operator: Exists
initContainers:
# Render env vars in config template
- name: render-config
image: docker.io/library/busybox:1.36
securityContext:
runAsUser: 10000
runAsGroup: 10000
runAsNonRoot: true
allowPrivilegeEscalation: false
readOnlyRootFilesystem: true
capabilities:
drop: ["ALL"]
seccompProfile:
type: RuntimeDefault
command: ["/bin/sh", "-c"]
args:
- |
sed "s|\${API_KEY}|${API_KEY}|g" /config-template/crowdsec-firewall-bouncer.yaml > /config/crowdsec-firewall-bouncer.yaml
env:
- name: API_KEY
valueFrom:
secretKeyRef:
name: crowdsec-bouncer-key
key: api-key
volumeMounts:
- name: config-template
mountPath: /config-template
- name: config-rendered
mountPath: /config
containers:
- name: bouncer
image: ghcr.io/shgew/cs-firewall-bouncer-docker:v0.0.34-patch1
command:
- /usr/local/bin/crowdsec-firewall-bouncer
- -c
- /config/crowdsec-firewall-bouncer.yaml
resources:
requests:
cpu: 10m
memory: 32Mi
limits:
memory: 64Mi
securityContext:
privileged: true
capabilities:
add:
- NET_ADMIN
- NET_RAW
volumeMounts:
- name: config-rendered
mountPath: /config
readOnly: true
- name: nftables-lock
mountPath: /run
volumes:
- name: config-template
configMap:
name: crowdsec-firewall-bouncer-config
- name: config-rendered
emptyDir: {}
- name: nftables-lock
hostPath:
path: /run
type: Directory
---
apiVersion: v1
kind: Service
metadata:
name: crowdsec-firewall-bouncer-metrics
namespace: crowdsec
labels:
app: crowdsec-firewall-bouncer
spec:
selector:
app: crowdsec-firewall-bouncer
ports:
- name: metrics
port: 60601
targetPort: 60601
protocol: TCP
---
apiVersion: monitoring.coreos.com/v1
kind: ServiceMonitor
metadata:
name: crowdsec-firewall-bouncer
namespace: crowdsec
labels:
app: crowdsec-firewall-bouncer
release: kube-prometheus-stack
spec:
selector:
matchLabels:
app: crowdsec-firewall-bouncer
endpoints:
- port: metrics
interval: 30s