{"id":797,"date":"2023-07-18T14:23:43","date_gmt":"2023-07-18T11:23:43","guid":{"rendered":"https:\/\/andrewwippler.com\/?p=797"},"modified":"2023-07-18T14:23:43","modified_gmt":"2023-07-18T11:23:43","slug":"from-nginx-to-traefik-and-solving-x-forwarded-for","status":"publish","type":"post","link":"https:\/\/andrewwippler.com\/2023\/07\/18\/from-nginx-to-traefik-and-solving-x-forwarded-for\/","title":{"rendered":"From nginx to traefik (and solving X-Forwarded-For)"},"content":{"rendered":"\n

I recently switched my home setup from nginx<\/em> to traefik<\/em>. I had to get used to the new configuration styles and weird doc style of traefik<\/em>, but suffice to say, I am happy at the end results.<\/p>\n\n\n\n

Traefik<\/em> ships with SNI, allowing me to snoop the connection and prevent unauthorized access to my file server. (One must have the correct hostname<\/em> to see my shared photos.) My first action was to replace my edge nginx<\/em> reverse proxy with traefik<\/em>. If this was a success, then I would move onto replacing nginx<\/em> in my k8s cluster. I was surprized that after zero OS tuning changes, traefik<\/em> was able to serve pages faster for my k8s cluster. I initially thought that traefik<\/em> would perform marginally slower than nginx<\/em> due to additional HostSNI<\/em> checks, but that was totally disproved by the first page load.\n<\/p>\n\n\n\n

After switching to traefik<\/em> inside my k8s cluster, performance improved even more. However, a new problem emerged: I was getting the IPs of my internal traffic logged instead of the real origin IP. While I would not normally care, malicious bots were attempting to brute force my WordPress installations, and I need to block their IPs. <\/p>\n\n\n\n

I was able to get the X-Forwarded-For<\/em> header populated with the true ip by enabling the Proxy Protocol on my edge traefik<\/em>, setting externalTrafficPolicy: Local on my traefik<\/em> k8s service, and finally telling traefik<\/em> to accept the proxyProtocol<\/em> information from the edge server.<\/p>\n\n\n\n

# Edge Router providers.yaml\n...\ntcp:\n  routers:\n    to-web:\n      service: web\n      rule: HostSNIRegexp(`www.andrewwippler.com`, `andrewwippler.com`, ...)\n      entryPoints:\n        - web\n    to-websecure:\n      service: websecure\n      rule: HostSNIRegexp(`www.andrewwippler.com`, `andrewwippler.com`, ...)\n      entryPoints:\n        - websecure\n      tls: \n        passthrough: true\nservices:\n    web:\n      loadBalancer:\n        proxyProtocol:\n          version: 2\n        servers:\n        - ...\n    websecure:\n      loadBalancer:\n        proxyProtocol:\n          version: 2\n        servers:\n        - ...\n...<\/code><\/pre>\n\n\n\n
# traefik k8s deployment and service\n# missing service account definition and CRDs\napiVersion: apps\/v1\nkind: Deployment\nmetadata:\n  name: traefik\nspec:\n  replicas: 3 #<-- one for each of my nodes, for failover\n  strategy: #<-- forces k8s to update pods on configuration change\n    rollingUpdate:\n      maxSurge: 1\n      maxUnavailable: 1\n    type: RollingUpdate\n  selector:\n    matchLabels:\n      app: traefik\n  template:\n    metadata:\n      labels:\n        app: traefik\n    spec:\n      affinity: #<-- one replica per node\n        podAntiAffinity:\n          requiredDuringSchedulingIgnoredDuringExecution:\n          - labelSelector:\n              matchExpressions:\n              - key: app\n                operator: In\n                values:\n                - traefik\n            topologyKey: \"kubernetes.io\/hostname\"\n      serviceAccountName: traefik-ingress-controller\n      containers:\n        - name: traefik\n          image: traefik:v2.10.3\n          args:\n            - --entrypoints.web.address=:80\n            - --entrypoints.websecure.address=:443\n            - --entrypoints.websecure.http.tls\n            - --entrypoints.websecure.http3\n            - --experimental.http3=true\n            - --providers.kubernetesingress\n            - --providers.kubernetescrd\n            - --log.level=DEBUG\n            - --entrypoints.web.http.redirections.entrypoint.scheme=https\n            - --entrypoints.web.http.redirections.entrypoint.to=websecure\n            # - --accesslog\n            - --entryPoints.web.proxyProtocol.trustedIPs=127.0.0.1\/32,10.0.0.0\/8,172.16.0.0\/12,192.168.0.0\/16 #<-- local CIDRs\n            - --entryPoints.websecure.proxyProtocol.trustedIPs=127.0.0.1\/32,10.0.0.0\/8,172.16.0.0\/12,192.168.0.0\/16 #<-- local CIDRs\n          ports:\n            - name: web\n              containerPort: 80\n            - name: websecure\n              containerPort: 443\n---\napiVersion: v1\nkind: Service\nmetadata:\n  name: traefik\nspec:\n  type: NodePort\n  selector:\n    app: traefik\n  ports:\n    - protocol: TCP\n      port: 80\n      name: web\n      targetPort: 80\n    - protocol: TCP\n      port: 443\n      name: websecure\n      targetPort: 443\n  externalTrafficPolicy: Local # <--- changed<\/code><\/pre>\n","protected":false},"excerpt":{"rendered":"

I recently switched my home setup from nginx to traefik. I had to get used to the new configuration styles and weird doc style of traefik, but suffice to say, I am happy at the end results. Traefik ships with SNI, allowing me to snoop the connection and prevent unauthorized access to my file server. […]<\/p>\n","protected":false},"author":1,"featured_media":0,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"advanced_seo_description":"","jetpack_seo_html_title":"","jetpack_seo_noindex":false,"footnotes":"","jetpack_publicize_message":"","jetpack_publicize_feature_enabled":true,"jetpack_social_post_already_shared":true,"jetpack_social_options":{"image_generator_settings":{"template":"highway","enabled":false}}},"categories":[6],"tags":[51,83,29,179],"jetpack_publicize_connections":[],"jetpack_featured_media_url":"","jetpack-related-posts":[],"jetpack_sharing_enabled":true,"jetpack_likes_enabled":true,"_links":{"self":[{"href":"https:\/\/andrewwippler.com\/wp-json\/wp\/v2\/posts\/797"}],"collection":[{"href":"https:\/\/andrewwippler.com\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/andrewwippler.com\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/andrewwippler.com\/wp-json\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/andrewwippler.com\/wp-json\/wp\/v2\/comments?post=797"}],"version-history":[{"count":6,"href":"https:\/\/andrewwippler.com\/wp-json\/wp\/v2\/posts\/797\/revisions"}],"predecessor-version":[{"id":803,"href":"https:\/\/andrewwippler.com\/wp-json\/wp\/v2\/posts\/797\/revisions\/803"}],"wp:attachment":[{"href":"https:\/\/andrewwippler.com\/wp-json\/wp\/v2\/media?parent=797"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/andrewwippler.com\/wp-json\/wp\/v2\/categories?post=797"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/andrewwippler.com\/wp-json\/wp\/v2\/tags?post=797"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}