I’d like to only allow connections to LuCi over VPN or a dedicated management port. I can recover using the management port or OpenSSH, which I consider safe to expose without a VPN. At the same time, DHCP and NDP still need to be accessible without a VPN.
Is this something for which I should use custom nftables rules?
Again. A dedicated vlan. And firewall rules for a zone where only this vlan is a member.
And then you restrict the webserver which serves Luci to listen on the router IP of that vlan and not on all interfaces.
WireGuard is a layer 3 interface, so it can’t be added to a VLAN. Linux uses a weak host model, so traffic to an IP address on an interface would be accepted from any other interface. I can, of course, use custom nftables rules to stop that, but uHTTPd starts even if applying these rules fail.
I agree with the VLAN + firewall config suggestions, but I don't recommend binding the webserver (and dropbear ssh server) as this doesn't actually secure the router and it can actually cause more headaches. If you want an explanation for this, I can elaborate.
That said, yes, if you make the firewall drop/reject input from the other zones/networks, DHCP and DNS fail. The solution is to add firewall rules specifically allowing those two services. It's the same as the firewall setting for a guest wifi network...
I think I understand the reason, but an explanation might well be helpful for others. I definitely want to know if reverse path filtering would solve the security problem.
firewall configuration on both networks accepts input.
In the above scenario, the web/ssh servers are reachable at both 192.168.1.1 and 192.168.2.1 and this works from both networks as a function of the firewall.
Now, bind the web/ssh servers to 192.168.1.1. This is the listen-on address.
At this point, the web/ssh servers are only listening on 192.168.1.1. But, the nuance here is that this address is still reachable from both networks. A host on 192.168.2.0/24 can still reach 192.168.1.1.
What is happening is that the router sees the request coming from the 192.168.2.0/24 network and says "I'll route your request.... oh, yeah, that's me - I have an address on 192.168.1.1 so there's actually no routing required, I can accept that traffic directly." This happens because the input rule in the firewall affecting 192.168.2.0/24 is still set to accept -- so the router sees traffic bound for itself on a network from which input is allowed and thus it accepts it.
Obviously, the request from 192.168.2.0/24 --> 192.168.1.1 is less 'intuitive' than 192.168.2.1 (which is no longer a listen-on address), but it's still completely valid and the firewall allows the traffic. This would be, at best, security by obscurity.
So, the correct way to prevent access from the .2.0/24 network is to reject/drop input. And this method blocks input even when the web/ssh servers are listening on that address.
The headache can come in if you have the listen-on address bound to 192.168.1.1 in this case. If you then happen to delete the 192.168.1.0/24 subnet and maybe add 10.0.41.0/24 as your administrative network, you'll run into an issue. Because your web/ssh servers are bound to the listen address that no longer exists on the router, you will not be able to connect and you'll have locked yourself out. This risk is non-existent if you have the listen-on address set to all interfaces (0.0.0.0/0).