Cloudflare offers great solutions for the protection of a webpage. Although most interesting applications require a paid premium account, the free tier also provides some interesting possibilities. For a while now this blog is “protected” by Cloudflare which acts as a reverse proxy. A nice feature using this approach is the obfuscation of the original IP of the actual webserver. However, as Cloudflare serves an intermediary role, the original IP of an user is replaced by the IP of the Cloudflare servers. Logfiles and consequential analytics lose their significance. As Cloudflare uses a specific HTTP header to send the original users IP address, there are possibilities for a workaround. This blog post explains how the restoring of the original IP coming from Cloudflare can be achieved with a WordPress deployment running in Kubernetes with Traefik as an Ingress controller.
Preparing Traefiks external traffic policy
If you’re already seeing only a single IP address in WordPress, your Traefik might need to have it’s external traffic policy changed. Just edit the Traefik service in Kubernetes and set this property to Local
:
kubectl edit svc traefik -n kube-system
... externalTrafficPolicy: Local ...
Just to be clear: This behaviour will have nothing to do with Cloudflare. Even without Cloudflare you will be seeing a single IP, as this IP is from your Cluster as the original IP is not passed as needed.
Permitting trusted IPs to forward headers
Cloudflare actually has a dedicated page on the issue of restoring the original visitors IP and tutorials for various webservers. However they do not feature Traefik on this page. Only modifying the WordPress webserver (Apache2) will not be enough, as Cloudflare sets it’s specific HTTP header CF-Connecting-IP
which will not be passed down to WordPress from Traefik.
So the first step is to permit trusted IPs (Cloudflares) to include forwarded headers in Traefik. Luckily Cloudflare provides a list of all IPs needed. To permit these IPs to forward HTTP headers into the cluster, the entryPoints in Traefik are modified. The most simple way is to edit your Traefik deployment and set the property entrypoints.web.forwardedHeaders.trustedIPs
yourself, for example:
entrypoints.web.forwardedHeaders.trustedIPs=173.245.48.0/20,103.21.244.0/22,103.22.200.0/22,103.31.4.0/22,141.101.64.0/18,108.162.192.0/18,190.93.240.0/20,188.114.96.0/20,197.234.240.0/22,198.41.128.0/17,162.158.0.0/15,172.64.0.0/13,131.0.72.0/22,104.16.0.0/13,104.24.0.0/14
However I’ve used the default Helm way of installing Traefik. With the Helm deployment a resource type HelmChartConfig
can be created, which Helm scans and updates the Traefik deployment. Now this resource can either include a sub-yaml of the configuration or an additional argument, as the one mentioned above. Choose one of the following resources and apply it to your cluster. Be aware of the Camel Case differences in entryPoints
.
apiVersion: helm.cattle.io/v1 kind: HelmChartConfig metadata: name: traefik namespace: kube-system spec: valuesContent: |- entryPoints: web: forwardedHeaders: trustedIPs: - "173.245.48.0/20" - "103.21.244.0/22" - "103.22.200.0/22" - "103.31.4.0/22" - "141.101.64.0/18" - "108.162.192.0/18" - "190.93.240.0/20" - "188.114.96.0/20" - "197.234.240.0/22" - "198.41.128.0/17" - "162.158.0.0/15" - "172.64.0.0/13" - "131.0.72.0/22" - "104.16.0.0/13" - "104.24.0.0/14"
apiVersion: helm.cattle.io/v1 kind: HelmChartConfig metadata: name: traefik namespace: kube-system spec: valuesContent: |- additionalArguments: - "--entrypoints.web.forwardedHeaders.trustedIPs=173.245.48.0/20,103.21.244.0/22,103.22.200.0/22,103.31.4.0/22,141.101.64.0/18,108.162.192.0/18,190.93.240.0/20,188.114.96.0/20,197.234.240.0/22,198.41.128.0/17,162.158.0.0/15,172.64.0.0/13,131.0.72.0/22,104.16.0.0/13,104.24.0.0/14"
After applying this resource the Traefik pod should restart and forward HTTP headers from Cloudflare into the cluster. WordPress and Apache2 are not yet aware of these headers, so the next step is to restore the IP from the HTTP header.
Handling the Cloudflare header in WordPress / Apache2
As I’m using a WordPress image built on Apache2, the Cloudflare documentation regarding mod_remoteip
is quite helpful. It mentions modifying 3 files in Apache2. However the logfile format in apache2.conf
was actually already correct for me. To modify the other two files I chose to export the original files from the image, modify them according to the Cloudflare documentation and create a Kubernetes ConfigMap which is then mounted inside the container, replacing the original file.
I just had to insert a single line into the file 000-default.conf (RemoteIPHeader CF-Connecting-IP
). However the remoteip.conf was entirely replaced by this:
RemoteIPHeader CF-Connecting-IP RemoteIPTrustedProxy 10.42.0.0/20
For reference the entire ConfigMap can look like this:
apiVersion: v1 kind: ConfigMap metadata: name: remoteip-conf namespace: wordpress data: remoteip.conf: | RemoteIPHeader CF-Connecting-IP RemoteIPTrustedProxy 10.42.0.0/20
Now this is quite different from the Cloudflare documention. Why? Because I started out with inserting all the Cloudflare IPs in here and then I noticed my entire Apache2 log displayed only 10.42.0.2 as a visitors IP. This IP actually stems from the Flannel / CNI inside Kubernetes / K3s. So at this level I just had to permit Flannel / CNI to resolve the HTTP header. You can check your own subnet using ifconfig
on your Kubernetes host system.
These two files are then mounted inside the container to the appropriate locations:
... volumeMounts: - name: 000-default mountPath: /etc/apache2/sites-available/000-default.conf subPath: 000-default.conf - name: remoteip-conf mountPath: /etc/apache2/conf-available/remoteip.conf subPath: remoteip.conf volumes: - name: remoteip-conf configMap: name: remoteip-conf defaultMode: 420 - name: 000-default configMap: name: 000-default defaultMode: 420 ...
After these modifications WordPress registers the original visitors IP and the logfile states the original IP. The restoring of the original IP from cloudflare by WordPress / Traefik was successful.