ouroboros

module
v0.1.0 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Apr 29, 2026 License: BSD-3-Clause

README

ouroboros

Go reimplementation of compumike/hairpin-proxy — fixes the hairpin-NAT problem for Kubernetes Ingress controllers configured with PROXY-protocol.

Why

When an external load balancer in front of an ingress-controller (typically ingress-nginx with use-proxy-protocol: true) prepends the PROXY-protocol header, internal traffic from in-cluster pods bypasses the LB and reaches the ingress-controller without the header. The connection is then rejected. Common offenders: cert-manager HTTP-01 challenges, internal https:// calls to your own public hostnames, healthchecks.

ouroboros fixes this with two cooperating components:

  1. A controller that watches Ingress (and optionally Gateway-API Gateway + HTTPRoute) and rewrites the kube-system/coredns ConfigMap so internal lookups for those hostnames resolve to a small in-cluster proxy.
  2. A TCP proxy that listens on 8080/8443, prepends the PROXY-protocol v1 header, and forwards to the real ingress-controller.

Both components ship as one binary, dispatched by subcommand.

Architecture

                        ┌────────────────────┐
                        │ Ingress / Gateway  │
                        │   (k8s API)        │
                        └────────┬───────────┘
                                 │ informers
                                 ▼
   ┌───────────────────────────────────────────┐
   │ ouroboros controller                      │
   │ - extracts hostnames                      │
   │ - reconciles CoreDNS ConfigMap (or hosts) │
   └───────────────────┬───────────────────────┘
                       │
                       ▼
       ┌───────────────────────────┐
       │ kube-system/coredns       │
       │  rewrite name foo.example │
       │    ouroboros-proxy....    │
       └───────────┬───────────────┘
                   │ DNS lookup from a pod
                   ▼
   ┌───────────────────────────────────────────┐
   │ ouroboros proxy   (Service ClusterIP)     │
   │ - accepts TCP                             │
   │ - prepends PROXY-protocol v1 header       │
   └───────────────────┬───────────────────────┘
                       │
                       ▼
              ingress-nginx-controller

Install

helm install ouroboros oci://ghcr.io/lexfrei/charts/ouroboros \
  --version 0.1.0 \
  --namespace ouroboros --create-namespace

Override the upstream backend if you don't run ingress-nginx:

helm install ouroboros oci://ghcr.io/lexfrei/charts/ouroboros \
  --namespace ouroboros --create-namespace \
  --set proxy.target.host=my-ingress.my-ns.svc.cluster.local

Enable Gateway-API support:

--set controller.gatewayApi.enabled=true

Modes

Mode Reconciler Use when
coredns mutates kube-system/coredns ConfigMap default — works for any pod that uses CoreDNS for DNS
etc-hosts writes /etc/hosts on each node (DaemonSet) for kubelet, container runtime, or anything bypassing CoreDNS

Switch via --set etcHosts.enabled=true.

Coverage caveat (both modes). Hostname extraction is asymmetric by design:

  • Ingress: only spec.tls[].hosts is read; plain HTTP-only Ingresses are ignored. The hairpin-NAT problem manifests for TLS-terminated PROXY-protocol traffic.
  • Gateway-API: Gateway.spec.listeners[].hostname and HTTPRoute.spec.hostnames are read regardless of protocol. Listeners are commonly paired (HTTP + HTTPS for redirect-to-TLS), and operators expect both to be hairpinned.

etc-hosts caveat. Each DaemonSet pod runs a full controller — Ingress/Gateway informers per node. On large clusters that is N replicated kube-apiserver watches producing identical results. Prefer coredns mode unless your nodes genuinely bypass cluster DNS.

CoreDNS reload caveat. coredns mode assumes CoreDNS' reload plugin is enabled (the default in kubeadm). If your Corefile lacks it, ouroboros logs a warning and the rewrite block is written but not picked up until CoreDNS pods are restarted manually. Verify with:

kubectl --namespace kube-system get configmap coredns --output jsonpath='{.data.Corefile}' | grep -w reload

Verification

After install:

kubectl --namespace kube-system get configmap coredns --output jsonpath='{.data.Corefile}' | grep -A20 BEGIN.ouroboros

You should see a block like:

# === BEGIN ouroboros (do not edit by hand) ===
rewrite name foo.example.com ouroboros-proxy.ouroboros.svc.cluster.local.
# === END ouroboros ===

From any pod:

getent hosts foo.example.com
# returns the ouroboros-proxy ClusterIP, not the LoadBalancer IP

A curl https://foo.example.com/ from a pod will then see X-Forwarded-For populated correctly by the ingress-controller because the PROXY-protocol header reached it.

Configuration

Both subcommands accept flags and env vars (flags override env, env overrides defaults). Run with --help for the full list — the table below is the source-of-truth alphabetical reference.

ouroboros controller
Flag Env var Default Notes
--mode OUROBOROS_CONTROLLER_MODE coredns coredns or etc-hosts.
--kubeconfig OUROBOROS_CONTROLLER_KUBECONFIG (empty = in-cluster) Path to a kubeconfig file.
--gateway-api OUROBOROS_CONTROLLER_GATEWAY_API false Watch Gateway/HTTPRoute in addition to Ingress.
--resync OUROBOROS_CONTROLLER_RESYNC 10m Informer resync period.
--coredns-namespace OUROBOROS_CONTROLLER_COREDNS_NAMESPACE kube-system CoreDNS ConfigMap namespace.
--coredns-configmap OUROBOROS_CONTROLLER_COREDNS_CONFIGMAP coredns CoreDNS ConfigMap name.
--coredns-key OUROBOROS_CONTROLLER_COREDNS_KEY Corefile Data key holding the Corefile.
--proxy-fqdn OUROBOROS_CONTROLLER_PROXY_FQDN ouroboros-proxy.ouroboros.svc.cluster.local. Required for coredns mode. Must end with a trailing dot.
--etc-hosts OUROBOROS_CONTROLLER_ETC_HOSTS /host/etc/hosts Path to host-mounted hosts file (etc-hosts mode).
--proxy-ip OUROBOROS_CONTROLLER_PROXY_IP (empty) Required for etc-hosts mode.
ouroboros proxy
Flag Env var Default
--listen-http OUROBOROS_PROXY_LISTEN_HTTP :8080
--listen-https OUROBOROS_PROXY_LISTEN_HTTPS :8443
--listen-health OUROBOROS_PROXY_LISTEN_HEALTH :8081
--target-host OUROBOROS_PROXY_TARGET_HOST ingress-nginx-controller.ingress-nginx.svc.cluster.local
--target-http-port OUROBOROS_PROXY_TARGET_HTTP_PORT 80
--target-https-port OUROBOROS_PROXY_TARGET_HTTPS_PORT 443
--dial-timeout OUROBOROS_PROXY_DIAL_TIMEOUT 5s
--ready-timeout OUROBOROS_PROXY_READY_TIMEOUT 2s
--shutdown-grace OUROBOROS_PROXY_SHUTDOWN_GRACE 30s

Build

go build ./cmd/ouroboros
docker build --file Containerfile --tag ouroboros:dev .

Develop

go test -race -count=1 ./...
golangci-lint run ./...
helm lint   charts/ouroboros
helm unittest charts/ouroboros

License

BSD 3-Clause — see LICENSE.

Directories

Path Synopsis
cmd
ouroboros command
Package main is the entry point for the ouroboros binary, dispatching to the controller or proxy subcommand.
Package main is the entry point for the ouroboros binary, dispatching to the controller or proxy subcommand.
internal
config
Package config parses CLI flags and environment variables into typed configuration structs for the ouroboros controller and proxy.
Package config parses CLI flags and environment variables into typed configuration structs for the ouroboros controller and proxy.
controller
Package controller orchestrates Ingress and Gateway-API informers, extracting hostnames into a sorted, deduplicated set that downstream reconcilers (CoreDNS, /etc/hosts) write into the cluster.
Package controller orchestrates Ingress and Gateway-API informers, extracting hostnames into a sorted, deduplicated set that downstream reconcilers (CoreDNS, /etc/hosts) write into the cluster.
coredns
Package coredns mutates the CoreDNS Corefile so the in-cluster proxy receives DNS-rewritten traffic for ingress hostnames.
Package coredns mutates the CoreDNS Corefile so the in-cluster proxy receives DNS-rewritten traffic for ingress hostnames.
hosts
Package hosts mutates a hosts(5) file so the in-cluster proxy receives traffic for ingress hostnames on nodes that bypass CoreDNS (kubelet, the container runtime, etc.).
Package hosts mutates a hosts(5) file so the in-cluster proxy receives traffic for ingress hostnames on nodes that bypass CoreDNS (kubelet, the container runtime, etc.).
k8s
Package k8s constructs Kubernetes and Gateway-API clientsets from either an in-cluster ServiceAccount or a kubeconfig file.
Package k8s constructs Kubernetes and Gateway-API clientsets from either an in-cluster ServiceAccount or a kubeconfig file.
proxy
Package proxy implements the in-cluster TCP proxy that injects PROXY-protocol v1 headers in front of forwarded connections.
Package proxy implements the in-cluster TCP proxy that injects PROXY-protocol v1 headers in front of forwarded connections.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL