Last post ended by deleting an application’s login. The proxy was the only route to that container, the container published no ports, and the second password was protecting something a stranger could not reach in the first place.

OpenMediaVault gets the opposite answer from the same reasoning, and the difference has nothing to do with the software.

No mode for trusting a proxy

OpenMediaVault is a NAS distribution. It runs on Debian, manages the disks, and puts a web interface called the Workbench in front of every part of that: shared folders, SMB and NFS exports, users and groups, scheduled rsync jobs, S.M.A.R.T. monitoring, filesystem creation and deletion.

Mine sits at 192.168.66.7 and holds everything I would be sad to lose. The blast radius from a compromised Workbench session is not a leaked viewing history. It is somebody with a button that formats an array.

Like Tautulli, it has no way to accept an identity from a proxy. There is no trusted header mode, no external authentication setting, nothing that reads X-Authentik-Username and decides you are already logged in. The header arrives on every request and lands in a log at best.

That gap is not a secret. The upstream request for it, issue 1312, asks for exactly this: proxy remote auth headers, or OIDC, or SAML. It was opened in May 2022, collected twelve comments, was last touched in July 2024, and is still open. Four years is a long time for a feature request to sit. Read that as a signal about where it sits on somebody’s list.

So the same fork appears. Two prompts, or remove the application’s login and let the proxy carry it.

Why the last post’s answer is unavailable

Tautulli was safe to strip because nothing could reach it except nginx. That was a property of the deployment. OpenMediaVault does not have it.

The Workbench is served by the appliance itself, on port 80, to the network. From a different machine entirely:

1
curl -s -o /dev/null -w 'status=%{http_code}\n' http://192.168.66.7/
1
status=200

That 200 does not mean the interface is open. I nearly wrote that it did. The Workbench is an Angular application, so the shell loads for anybody and then asks the backend for data. Asking the backend directly is the test that means something:

1
2
3
curl -s -X POST http://192.168.66.7/rpc.php \
  -H 'Content-Type: application/json' \
  -d '{"service":"System","method":"getInformation","params":null,"options":null}'
1
{"response":null,"error":{"code":0,"message":"Session not authenticated.", ...}}

Good. The session check is real and it is enforced at the RPC layer, where the actual capability lives. The login page is not decoration over an open backend.

Which is why it has to stay. The appliance answers on its own address to every host on my LAN. Removing the Workbench login would not move authentication to the proxy, it would delete authentication for anyone who types an IP address. The proxy would keep asking politely at the front while the side door stood open.

Tautulli and OpenMediaVault ask their operator the same question and get different answers, neither of which is about the application. Removing an application’s login is a decision about your network, and the application gets no vote.

One port out of seven

The Caddy site block for this one is two lines, the same as everything else in part three:

1
2
3
omv.homelabdomain.xyz {
	import authentik http://192.168.66.7
}

It works. A request with no authentik session gets bounced to the identity provider:

1
2
curl -s -o /dev/null -w '%{http_code} -> %{redirect_url}\n' \
  --resolve omv.homelabdomain.xyz:443:127.0.0.1 https://omv.homelabdomain.xyz/
1
302 -> https://auth.homelabdomain.xyz/application/o/authorize/?client_id=...

Now look at what the appliance is listening on:

1
2
3
4
5
6
7
22    SSH
80    Workbench
111   NFS portmapper
139   SMB
445   SMB
2049  NFS
5357  WS-Discovery

Forward auth covers one of those, and only when the Workbench is reached through the hostname. The rest is the NAS doing its job. SMB and NFS are how the files get to the machines that want them, and neither speaks HTTP, so neither can pass through an HTTP proxy that makes an authentication subrequest and reads a header off the answer. There is no arrangement of Caddy and authentik that puts a file share behind forward auth.

Part four ran into the identical wall from the other side. Cloudflare Access could only be scoped to /if/admin on the identity provider, because the login flow, the OIDC endpoints and the outpost paths all have to answer unauthenticated requests or the protocols stop working. The policy covers the console and leaves the service alone.

That is the same division here. Authentik sits in front of the administration interface. The service the appliance exists to provide runs on ports it never touches, authenticated by OMV’s own users and Samba’s own credential store, and it would carry on exactly as it does now if the identity provider were switched off.

Naming that changes what the proxy is for. It is not the gate on my storage. It is a gate on the settings panel, which is the part where somebody clicks the button that destroys an array.

Even that is softer than it looks on my LAN, because anybody who can resolve omv.homelabdomain.xyz can also reach 192.168.66.7 directly and skip the redirect. The allowlist in the Caddy snippet permits my LAN and my Tailscale range, which is the same population that can reach the appliance without asking.

I kept it anyway. The proxied path gets TLS from a certificate the browser trusts. The direct path is plain HTTP on port 80, so every Workbench login typed at the IP address crosses the network in the clear, and that alone is the argument for using the hostname. It also gets one naming convention, so every service in the house is reached the same way, plus one place where access is logged and can be revoked.

Unifying the other six ports is a different project with a different mechanism. authentik can run an LDAP outpost and OMV can authenticate its users against LDAP, putting file share credentials in the same directory as everything else. That is a real path and it is not this one.

Making the proxy the only door

What turns the convention into a boundary is a firewall rule on the appliance, and that rule now exists:

1
2
3
4
-A INPUT -s 127.0.0.1/32     -p tcp --dport 80 -j ACCEPT
-A INPUT -s 192.168.66.6/32  -p tcp --dport 80 -j ACCEPT
-A INPUT -s 192.168.66.11/32 -p tcp --dport 80 -j ACCEPT
-A INPUT                     -p tcp --dport 80 -j DROP

Be precise about the size of that. The scope is tcp/80. SSH, SMB and NFS are untouched on purpose, so this gates the web administration interface and leaves the data alone. “OpenMediaVault is firewalled” would overclaim. The Workbench is now reachable only through the proxy, and that is the sentence that turns the previous section from a caveat into a result.

It is verified from both directions. From the Caddy host, port 80 answers in about a millisecond, and the hostname still redirects an unauthenticated browser to the identity provider. From my desktop on the same subnet, which is not on the allow list:

1
2
3
port 80  : code=000, time_total=8.007s, curl exit 28
port 445 : TcpTestSucceeded=True
port 2049: TcpTestSucceeded=True

Exit 28 is a timeout. A refusal would be exit 7, and a REJECT rule would produce one by sending back an RST. Packets are being discarded in silence. DROP makes the appliance look absent where a refusal would make it look closed. The same host still reaches SMB and NFS, demonstrating the scope statement above.

Building it produced a couple of details that belong in this post.

OMV owns its iptables rules, so hand-written ones get overwritten on the next deploy. Going through the configuration database instead took three attempts: omv-confdbadm create does not take JSON, and Iptables.setRule is an update path that fails against a UUID that does not exist yet. The working call is Iptables.setRules, plural, with a bare JSON array where every rule carries a generated uuidv4, followed by omv-salt deploy run iptables.

And the appliance runs no Tailscale, which I checked rather than assumed, because it would have quietly mattered. tailscaled is inactive, there is no tailscale0 interface, and there are no ts-* chains. The INPUT chain is those four rules with nothing ahead of them. Had Tailscale been installed, it would have inserted a ts-input jump at the top of INPUT, and tailnet traffic could have been accepted before my DROP was ever consulted. The rule would have read correctly and been bypassable.

That leaves two allowlists doing two different jobs, and confusing them is easy:

LayerGoverns
Caddy allowlist on .6Who may use the proxy: LAN, Tailscale range, Docker
OMV firewall on .7Who may reach the appliance directly: loopback, .6, .11

No Tailscale address appears in the OMV rule. A tailnet client reaching omv.homelabdomain.xyz arrives at Caddy, and the appliance sees the connection coming from .6, so remote access still works through the front door without a direct tailnet path to port 80.

Who else talks to a service

The rule I drafted for this post permitted only .6, the Caddy host. Shipping it that way would have broken something.

192.168.66.11 is in that list because my dashboard’s OMV widget calls the appliance directly, with its own credentials, to pull S.M.A.R.T. data. A rule allowing only the proxy would have left the tile empty with no error anywhere to explain it, and I would have found out days later while looking for something else.

State that as the general lesson, because it has now caught me twice: a reverse proxy is never the only thing talking to a service. Dashboards poll. Backup jobs connect. Monitoring scrapes. Before you narrow a service to one source address, go and find every client it already has, because the ones that break are the quiet ones that were never going to complain.

There is a consequence I should expect rather than discover. http://192.168.66.7 was a bookmark, and that path is now dropped, so the bookmark stops working and the hostname is the way in. That is the point of the change, and unlearning the habit is still on me.

The missing feature, revisited

Before the firewall rule, the answer to issue 1312 would have been unusable here even if it had landed. Trusting a header is safe only when the header cannot be forged, and it cannot be forged only when nothing can reach the application except the proxy that sets it. Any host on the LAN could open a connection to port 80 and send X-Remote-User: admin.

That condition is now met, and I did not expect to be writing that sentence. Three sources can reach port 80, and one of them is the proxy that would be setting the header. The feature would work, and the same rule would also make deleting the Workbench login safe, exactly as it was safe on Tautulli.

The login stays anyway, and the reason is the one carve-out. 192.168.66.11 reaches port 80 directly, on purpose, for a dashboard widget. An unauthenticated Workbench would be unauthenticated to everything running on that host, and that host runs a lot of containers. The second prompt is what stands between a compromised container over there and a button that formats an array over here.

The missing feature stopped being the blocker, and the shape of my own network took over. That has been true in every post in this series.

Where this leaves the series

Sorting six posts of applications by what they will do with an identity gives three groups.

A few read a header and log the user in, the arrangement forward auth exists to produce. Others speak OIDC properly and would prefer a redirect, a better arrangement and where this stack goes next. The rest have their own login and no opinion about yours.

For that third group the only real lever is topology. Tautulli could give up its login because nothing but the proxy could reach it. OpenMediaVault cannot, because it answers on its own address, and every route around that runs into the same wall.

Which makes the useful question, when you put an identity provider in front of something, less about whether the application supports SSO and more about what can reach it without going through you. The first has an answer in the documentation. The second has an answer in your firewall and your port list, and that is the one deciding what your identity layer protects.

On this appliance the answer came to one port out of seven.

Get it

The Caddy configuration is in my TechbyJeff repo at Docker/authentik/caddy/Caddyfile, with the domain changed and the network addresses in the allowlist set to mine. OpenMediaVault itself is configured through its own interface and has no file here to publish.

Sources


Part six of seven on the identity stack in my homelab: authentik, certificates, forward auth, Cloudflare, Tautulli, OpenMediaVault, Proxmox.