Iptables rules on lxdbr0 don't seem to filter traffic

I’ve set up LXD and want to be able to block containers from accessing services on my local network. The most obvious option (at least to me) would be do to do something like

# iptables -A INPUT -i lxdbr0 --src 10.0.0.0/8 -j DROP
# iptables -A OUTPUT -o lxdbr0 --dest 10.0.0.0/8 -j DROP

But this doesn’t work. My containers can still ping 10.42.10.128 (which is a different host to my LXD host). Even if I try prepending the rules like so:

# iptables -I INPUT 1 -i lxdbr0 --src 10.0.0.0/8 -j DROP
# iptables -I OUTPUT 1 -o lxdbr0 --dest 10.0.0.0/8 -j DROP

The issue still occurs. And I even tried to set this up with ebtables (I read online that ebtables can better handle the filtering of traffic which originates from bridges) and it still doesn’t work.

# ebtables -A INPUT --proto ipv4 -i lxdbr0 --ip-source 10.0.0.0/8 -j DROP
# ebtables -A OUTPUT --proto ipv4 -o lxdbr0 --ip-destination 10.0.0.0/8 -j DROP

I even tried to mark all the traffic going through lxdbr0:

# iptables -A OUTPUT -o lxdbr0 -j MARK --set-mark 1337
# iptables -A INPUT -i lxdbr0 -j MARK --set-mark 1337
# iptables -A FORWARD -i lxdbr0 -j MARK --set-mark 1337
# iptables -A FORWARD -o lxdbr0 -j MARK --set-mark 1337

# iptables -A OUTPUT -m mark --mark 1337 --dest 10.0.0.0/8 -j DROP
# iptables -A INPUT -m mark --mark 1337 --src 10.0.0.0/8 -j DROP
# iptables -A FORWARD -m mark --mark 1337 --dest 10.0.0.0/8 -j DROP
# iptables -A FORWARD -m mark --mark 1337 --src 10.0.0.0/8 -j DROP

And it still didn’t have an impact. What is the correct way of doing this?


In addition, it might be useful to provide a way to specify raw.iptables for a network so that users can get LXD to auto-insert relevant rules (obviously with substitution of %I or similar for the interface name). The current mix of ipv4.firewall and ipv4.nat options can get a little confusion and it still requires configuring iptables-persistent and making sure there isn’t some weird reloading issue between LXD and systemd.

try to disable default firewall

lxc network set lxdbr0 ipv4.firewall False
lxc network set lxdbr0 ipv6.firewall False

it should turn off default rules that give containers access to the outside world (you have to grant them yourself afterwards)

iptables -I INPUT -i lxdbr0 --src 10.0.0.0/8 -j DROP
iptables -I OUTPUT -o lxdbr0 --dest 10.0.0.0/8 -j DROP

This will only prevent traffic to your host and from the host to the containers, it will not prevent any routed traffic as that’s the FORWARD table.

Adding

iptables -I FORWARD -i lxdbr0 -s 10.0.0.0/8 -j DROP

Though indeed if you don’t want any outside traffic, the best is usually to set ipv4.firewall and ipv4.nat to false and then either have the FORWARD policy to DROP or have a DROP rule in your firewall.

If you don’t have at least ipv4.firewall set to false, LXD will prepend ACCEPT rules which will still let traffic through.

iptables -I FORWARD -i lxdbr0 -d 10.0.0.0/8 -j DROP

works (containers can no longer ping things on my local network but can ping linuxcontainers.org). It isn’t necessary to set ipv4.firewall = false because the DROP rule is matched before the default rules. It is a bit of a shame that users can’t configure iptables rules with a raw.iptables key for LXD-managed network devices. Would there be any interest in something like that?

I’ve been somewhat reluctant to do such a raw.iptables due to the current switch away from traditional iptables to nftables. Not exposing this directly to users means we can do a graceful transition from iptables to nft based on what’s used on the system at the time.

Fair enough. I can set this up with a service that runs after lxd.service is started. I’m not sure what will happen if lxdbr0 gets reset and reconfigured by LXD – it would make sense for the rules to remain in the FILTER table but I don’t know if they would get associated with a new lxdbr0 interface.

By the way (slightly offtopic), have you already looked into how rule injection would work with nftables? From what I understand, it doesn’t have default tables like iptables does, so one e.g. couldn’t call -A INPUT … to target the filter/input chain. I’m probably missing something here, but otherwise I wonder how that would be handled.

They should remain applying to new interfaces with the same name as the original rules as you can specify iptables rules before the interface exists.

Right, that makes sense. In that case blocking traffic would be as simple as a systemd drop-in for LXC to add a rule for lxdbr0.

LXD only removes its own rules (looked up based on rule comment), it will not alter your own rules on startup/restart.

Yes, though LXD does do iptables -I which means that after a restart (or reconfigure of lxdbr0) you do end up with the LXD rules above the ones I insert. But I can work around that. Just one tip for anyone following along with this – don’t block 10.0.0.0/8 because that appears to stop DHCP from working correctly for containers (since the default CIDR for lxdbr0 is 10.133.x.x).