Korzen
Bot Protection

Kubernetes

Bot Protection from an app on Kubernetes: the API key as a Secret, the visitor's IP through the load balancer and ingress-nginx, and egress.

Kubernetes changes three things about the call from your server: where the API key lives, how the visitor's address survives the load balancer and the ingress, and whether the pod may reach korzen.dev at all. The call itself is your framework's: take it from the guide for your stack.

The key

Keep the API key in a Secret, never in the image or a ConfigMap, and hand it to the container as an environment variable:

kubectl create secret generic korzen --from-literal=api-key=ak_your_key
# deployment.yaml, in your app's container
env:
  - name: KORZEN_API_KEY
    valueFrom:
      secretKeyRef:
        name: korzen
        key: api-key

The visitor's IP

By default a cloud load balancer hides the visitor behind its own address, and so does the ingress. Two settings keep it: externalTrafficPolicy: Local on the ingress controller's Service, and ingress-nginx trusting the header the load balancer adds. Your application then reads X-Real-IP, or the last entry of X-Forwarded-For, which ingress-nginx sets; behind Cloudflare, CF-Connecting-IP.

# The ingress controller's Service: keep the visitor's address on the way in.
apiVersion: v1
kind: Service
metadata:
  name: ingress-nginx-controller
  namespace: ingress-nginx
spec:
  type: LoadBalancer
  externalTrafficPolicy: Local
# ingress-nginx's ConfigMap: trust the load balancer in front, pass the address on.
apiVersion: v1
kind: ConfigMap
metadata:
  name: ingress-nginx-controller
  namespace: ingress-nginx
data:
  use-forwarded-headers: "true"     # only if a load balancer you control is in front
  compute-full-forwarded-for: "true"
  # use-proxy-protocol: "true"      # instead, if your cloud's load balancer speaks PROXY protocol

Egress

If the cluster blocks traffic out of a namespace by default, the pod must be allowed to reach port 443:

# Only if your cluster blocks egress by default: let the app reach HTTPS.
# A plain NetworkPolicy matches addresses, not names, and korzen.dev answers from
# Cloudflare's ranges; with Cilium, an FQDN rule can name korzen.dev itself.
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
  name: app-egress-https
spec:
  podSelector:
    matchLabels:
      app: shop
  policyTypes: [Egress]
  egress:
    - ports:
        - port: 443
          protocol: TCP
    - ports:
        - port: 53
          protocol: UDP

Running it

  • One HTTP client per pod, kept open; every replica opens its own connection, which is fine.
  • Never make a readiness or liveness probe depend on Bot Protection: when it is slow, the pods must stay in service and fail open.
  • Your SDK's timeout counts from where the cluster runs: a cluster in the US west needs about 300 ms.

Common mistakes

  • The ingress's own address sent as the visitor's: every decision then shares one IP, and Bot Protection sees one very busy visitor.
  • use-forwarded-headers on with nothing in front that you control: a visitor writes X-Forwarded-For and picks their address.
  • The key in a ConfigMap or baked into the image, where anyone with read access to the namespace or the registry sees it.