Paranoid Penguin - Running Network Services under User-Mode Linux, Part III
Whether you chroot your User-Mode guests, and whether you use SELinux, depends on how deep you want your layers of security to go and how much time and effort you're able to expend. However, I strongly recommend that on any Internet-facing, bridged User-Mode Linux system, you use iptables on your UML host to restrict your guest systems' network behavior.
On the one hand, if your UML system already resides outside a firewall in a DMZ network (as should any Internet server), you're already protecting your internal network from the possibility of a network server compromise. However, there's really no good reason not to take the opportunity also to use UML-host iptables rules to reduce the ability of an attacker to use one compromised UML guest to attack other UML guests, the UML host itself or other systems in your DMZ network.
There are two categories of rules I strongly recommend you consider. First, anti-IP-spoofing rules can help ensure that every packet sent by each guest bears the source IP address you actually assigned to that guest, and not a forged (spoofed) source IP. These are low-maintenance rules that you'll have to think about only at setup time, unless for some reason you change a guest system's IP address.
Suppose you have a UML system whose IP address is 10.1.1.10 and whose tun/tap interface is (from the host's perspective) uml-conn0. The anti-spoofing rules you install on the UML host might therefore look like that shown in Listing 1.
Listing 1. Anti-IP-Spoofing Rules
iptables -A FORWARD -m physdev --physdev-in uml-conn0 ↪-s ! 10.1.1.10 -j LOG --log-prefix "Spoof from uml-conn0" iptables -A FORWARD -m physdev --physdev-in uml-conn0 ↪-s ! 10.1.1.10 -j DROP
The first rule logs the spoofed packets; the second one actually drops them. As you may know, the LOG target doesn't cause packets to cease being evaluated against subsequent iptables rules, but the DROP target does, so the LOG rule must come before the DROP rule.
Due to space constraints, I can't launch into a primer on how to write iptables rules or how they're managed on your Linux distribution of choice. But, I can talk about the bridge-specific magic in Listing 1: the physdev iptables module and the --physdev-in parameter.
Usually, we use iptables' -i and -o flags to denote which network interface packets are received and sent from, respectively. However, when writing iptables rules on a system doing bridged networking, we need to be a bit more precise, especially when we're also using tun/tap interfaces, as eth0 then takes on a different role than in normal Layer 3 (routed) networking.
Therefore, where we might normally use -i uml-conn0 in a rule, on a bridging host, we should instead use -m physdev --physdev-in uml-conn0. Similarly, instead of -o uml-conn0, we'd use -m physdev --physdev-out uml-conn0. As with other module invocations, you need only one instance of -m physdev if a given iptables rule uses both the --physdev-in and --physdev-out rules.
After setting up a pair of anti-IP-spoofing rules, you also should create a set of “service-specific” rules that actually govern how your guest system may interact with the rest of the world, including other guest systems and the host itself.
Remember that in our example scenario the guest system is a DNS server. Therefore, I'm going to enforce this logical firewall policy:
The UML guest may accept DNS queries (both TCP and UDP).
The UML guest may recurse DNS queries against upstream (external) servers.
The UML guest may send its log messages to a log server (called logserver).
The UML host may initiate SSH sessions on the UML guest.
Listing 2 shows iptables commands that could enforce this policy.
Listing 2. Service Rules for the UML Guest
iptablee -A FORWARD -m state --state ↪RELATED,ESTABLISHED -j ACCEPT iptables -A FORWARD -m physdev --physdev-out uml-conn0 ↪-p udp --dport 53 -m state --state NEW -j ACCEPT iptables -A FORWARD -m physdev --physdev-out uml-conn0 ↪-p tcp --dport 53 -m state --state NEW -j ACCEPT iptables -A FORWARD -m physdev --physdev-in uml-conn0 ↪-p udp --dport 53 -d ! 10.1.1.0/24 -m state --state NEW -j ACCEPT iptables -A FORWARD -m physdev --physdev-in uml-conn0 ↪-p tcp --dport 53 -d ! 10.1.1.0/24 -m state --state NEW -j ACCEPT iptables -A FORWARD -m physdev --physdev-in uml-conn0 ↪-p udp --dport 514 -d logserver -m state --state NEW -j ACCEPT iptables -A FORWARD -j LOG --log-prefix ↪"Forward Dropped by default" iptables -A FORWARD -j DROP iptables -A OUTPUT -d 10.1.1.10 -p tcp --dport 22 -m ↪state --state NEW -j ACCEPT
Listing 2 has two parts: a complete set of FORWARD rules and a single OUTPUT rule. Because, logically speaking, UML guest systems are “external” to the UML host's kernel, interactions between UML guests and each other, and also interactions between UML guests and the rest of the world, are handled via FORWARD rules. Interactions between UML guests and the underlying host system, however, are handled by INPUT and OUTPUT rules (just like any other interactions between external systems and the host system).
Because all of my logical rules except #4 are enforced by iptables FORWARD rules, Listing 2 shows my UML host's complete FORWARD table, including an initial rule allowing packets associated with already-approved sessions, and a final pair of “default log & drop” rules. Note my use of the physdev module; I like to use interface-specific rather than IP-specific rules wherever possible, as that tends to make it harder for attackers to play games with IP headers.
The last rule in Listing 2 should, in actual practice, appear somewhere in the middle of a similar block of OUTPUT rules (beginning with an allow-established rule and ending with a default log/drop rule pair), but I wanted to illustrate that where the source or destination of a rule involves the UML host system, you can write an ordinary OUTPUT or INPUT rule (respectively) rather than a FORWARD rule.
Because your UML host is acting as an Ethernet bridge, you can write still-more-granular and low-level firewall rules—even filtering by MAC addresses, the ARP protocol and so forth. But for that level of filtering, you'll need to install the ebtables command. iptables rules of the type I've just described should, however, suffice for most bastion-host situations.