I love Traefik. So much so that if I can run one of my services through it, I do. Why? Mainly to gain the ability of easy certificate management (via Let’s Encrypt in my case) and not having to remember and forward obscure ports. But not every service I run needs to be exposed publicly..
For instance, I use code-server to write the majority of my code - including writing & managing these blog posts. And while code-server is protected by a password, it is using a basic auth mechanism that really shouldn’t be exposed to the internet. This is where Traefik’s IPWhiteList middleware comes in handy: I can quickly limit access to my code-server instance to IP’s or networks of my choosing.
IPWhiteList Overview
This diagram from the Traefik docs explains it well:
When a client makes a request, Traefik’s IPWhiteList middleware inspects the HTTP headers to determine if the request should be allowed to proceed (or not). This inspection can be based on a client’s remote address or X-Forwarded-For header information depending on how the middleware is configured. Official details in the docs, here.
How to Put it Together
We can use the trusty crccheck hello-world container to demonstrate (Note: I’m using Podman but should be the same for Docker). Here is a sample compose file we can work with:
version: 3.7
services:
hello:
image: docker.io/crccheck/hello-world
hostname: hello.cthudson.com
container_name: hello
labels:
- "traefik.enable=true"
- "traefik.http.routers.hello.rule=Host(`hello.cthudson.com`)"
- "traefik.http.middlewares.hello-https-redirect.redirectscheme.scheme=https"
- "traefik.http.routers.hello.middlewares=hello-https-redirect"
- "traefik.http.routers.hello-secure.entrypoints=websecure"
- "traefik.http.routers.hello-secure.rule=Host(`hello.cthudson.com`)"
- "traefik.http.routers.hello-secure.tls=true"
- "traefik.http.routers.hello-secure.tls.certresolver=lets-encrypt"
- "traefik.http.services.hello.loadbalancer.server.port=8000"
- "traefik.http.middlewares.hello-ipwhitelist.ipwhitelist.sourcerange=127.0.0.1/32, 192.168.2.0/24, 10.8.0.0/24"
- "traefik.http.routers.hello-secure.middlewares=hello-ipwhitelist"
In the above compose file, we are running the crccheck hello-world image, using labels to enable Traefik and providing a few configuration options to serve the image how we would like to (as hello.cthudson.com
, encrypted via Let’s Encrypt, forcing https, with the service within the container being on port 8000). For the purposes of this post, we will look at lines 19 & 20.
In line 19 we are providing our source ranges that will be granted access. In this example, we allow localhost (127.0.0.1/32), the lan subnet (192.168.2.0/24), and another subnet representing a VPN subnet (10.8.0.0/24). If your inbound remote address does not satisfy one of these rules, you should be provided a 403 Forbidden
error.
In line 20, we are attaching the “hello-ipwhitelist” IPWhiteList middleware to the “hello-secure” router. NOTE: Per the Traefik docs, whitelisting happens before the actual proxying to the backend takes place.
Here is a visual representation from the Traefik dashboard once the container was brought up with podman-compose
:
Testing Time
Quite simple, really:
From 192.168.2.0/24 network:
$ ip a | grep 192 && curl https://hello.cthudson.com
inet 192.168.2.162/24 brd 192.168.2.255 scope global noprefixroute bond0
<pre>
Hello World
## .
## ## ## ==
## ## ## ## ## ===
/""""""""""""""""\___/ ===
~~~ {~~ ~~~~ ~~~ ~~~~ ~~ ~ / ===- ~~~
\______ o _,/
\ \ _,'
`'--.._\..--''
</pre>
From outside allowed networks (tethering with my mobile in this case):
$ curl icanhazip.com && curl https://hello.cthudson.com
172.58.217.48
Forbidden
Success!