Tuesday, April 28, 2015

OpenVZ Containers, the Kernel & Iptables: The Lowdown

OpenVZ is a virtualization solution used by many VPS providers and users alike. It's sort of like Virtualbox,  allowing one to create many virtual computers and host them on the same hardware node. In comparison to other virtualization systems, such as KVM, it has some drawbacks. For one thing, all the containers (OpenVZ terminology for a guest virtual machine) sharing a hardware node will also share that nodes kernel, consequently you cannot load kernel modules on a guest container unless they are already loaded on the host.

This causes problems for the users. For example, I hate using swap unless I absolutely have to. I cannot simply run 'swapoff -a' on my OpenVZ vps because the administrator has disabled that feature on the host kernel. For a while I had a workaround, I would set the vmswappiness level down to 0, basically causing the system to not use swap until it really needs to. But than one day I got an 'access denied' error message when trying to do this. Another example would be trying to enable tun support. You can't just run 'modprobe tun'. It's either off or it's on. If it's off and you need to set up a VPN, than you have to contact your provider and ask if they will do so. Fortunately, almost all vps providers allow the users tun support.

Than there is iptables. It took a long time for me to understand how to configure iptables on an Openvz vps. Unlike normal linux systems (and KVM based virtual machines), you don't have interfaces like 'eth0' or 'eth1' that allow you to easily write iptables rules based on the interface.  If you have an OpenVZ vps with multiple IP addresses, than you may have noticed that you have interfaces such as 'venet0', 'venet0:0', 'vnet0:1', and so on. If you try to write a firewall rule so that http connections are served on a particular IP, such as:

iptables -A INPUT -i venet0:1 -p tcp --dport 80 -j ACCEPT

It won't work, because iptables does not know what venet0:1 is, even though when you run ifconfig you can clearly see your IP address on an interface with that name. So, instead you have to use workarounds, referancing the destination IP of the incoming rule. You also use 'venet0' for every interface, don't ask me why. I am sure someone that understands OpenVZ knows. For example, let's say you wanted to serve http traffic from 1.2.3.4, and https from 1.2.3.5. This is how you could do that:

iptables -A INPUT -i venet0 -d 1.2.3.4 -p tcp --dport 80 -j ACCEPT
iptables -A INPUT -i venet0 -d 1.2.3.5 -p tcp --dport 443 -j ACCEPT

I like to think doing that provides an equal level of security that you'd get on a physical system where you can reference separate interfaces. The following is an iptables script that I've put together from various sources on the net, made specifically for people running openvpn on openvz server. Overzealous security precaution was taken when writing these rules:

#!/bin/bash
# A Linux Shell Script with common rules for IPTABLES Firewall, customized
# for OpenVZ Openvpn servers running dnscrypt-proxy.
# -------------------------------------------------------------------------
# Credit: Copyright (c) 2004 nixCraft project <http://cyberciti.biz/fb/>
# This script is licensed under GNU GPL version 2.0 or above
# -------------------------------------------------------------------------
# Modified by Chev Y.  #
#--------------------------------------------------------------------------
IPT="/sbin/iptables"
SPAMLIST="blockedip"
SPAMDROPMSG="BLOCKED IP DROP"

echo "Starting IPv4 Wall..."
$IPT -F
$IPT -X
$IPT -t nat -F
$IPT -t nat -X
$IPT -t mangle -F
$IPT -t mangle -X
modprobe ip_conntrack

[ -f /root/scripts/blocked.ips.txt ] && BADIPS=$(egrep -v -E "^#|^$" /root/scripts/blocked.ips.txt)

VPN_IF="tun+"
VPN_PRT="13959"
SSH_PRT="17171"
EXTIF="venet0"
EXTIP="1.2.3.4"
OUTIP="1.2.3.5"
VPN_SN="10.99.11.0/24"
VPNSRVR="10.99.0.1"
$STATIC_IP="some.static.ip.address" # Some ip to allow ssh in case vpn goes down

# Allow local connections
$IPT -A INPUT -i lo -j ACCEPT
$IPT -A OUTPUT -o lo -j ACCEPT
# Block weird stuff
iptables -A INPUT -s $EXTIP -j DROP
iptables -A OUTPUT -d $EXTIP -j DROP
# Stop  floods
iptables -N flood
iptables -A INPUT -p tcp --syn -j flood
iptables -A flood -m limit --limit 1/s --limit-burst 3 -j RETURN
iptables -A flood -j DROP

# DROP all incomming traffic
$IPT -P INPUT DROP
$IPT -P OUTPUT DROP
$IPT -P FORWARD DROP

if [ -f /root/scripts/blocked.ips.txt ];
then
# create a new iptables list
$IPT -N $SPAMLIST

for ipblock in $BADIPS
do
$IPT -A $SPAMLIST -s $ipblock -j LOG --log-prefix "$SPAMDROPMSG"
$IPT -A $SPAMLIST -s $ipblock -j DROP
done

$IPT -I INPUT -j $SPAMLIST
$IPT -I OUTPUT -j $SPAMLIST
$IPT -I FORWARD -j $SPAMLIST
fi

# Block sync
$IPT -A INPUT -p tcp ! --syn -m state --state NEW -m limit --limit 5/m --limit-burst 7 -j LOG --log-level 4 --log-prefix "Drop Sync"
$IPT -A INPUT -p tcp ! --syn -m state --state NEW -j DROP

# Block Fragments
$IPT -A INPUT -f -m limit --limit 5/m --limit-burst 7 -j LOG --log-level 4 --log-prefix "Fragments Packets"
$IPT -A INPUT -f -j DROP

# Block bad stuff
$IPT -A INPUT -p tcp --tcp-flags ALL FIN,URG,PSH -j DROP
$IPT -A INPUT -p tcp --tcp-flags ALL ALL -j DROP
$IPT -A INPUT -p tcp --tcp-flags ALL NONE -m limit --limit 5/m --limit-burst 7 -j LOG --log-level 4 --log-prefix "NULL Packets"
$IPT -A INPUT -p tcp --tcp-flags ALL NONE -j DROP # NULL packets
$IPT -A INPUT -p tcp --tcp-flags SYN,RST SYN,RST -j DROP
$IPT -A INPUT -p tcp --tcp-flags SYN,FIN SYN,FIN -m limit --limit 5/m --limit-burst 7 -j LOG --log-level 4 --log-prefix "XMAS \ Packets"
$IPT -A INPUT -p tcp --tcp-flags SYN,FIN SYN,FIN -j DROP #XMAS
$IPT -A INPUT -p tcp --tcp-flags FIN,ACK FIN -m limit --limit 5/m --limit-burst 7 -j LOG --log-level 4 --log-prefix "Fin Packets Scan"
$IPT -A INPUT -p tcp --tcp-flags FIN,ACK FIN -j DROP # FIN packet scans
$IPT -A INPUT -p tcp --tcp-flags ALL SYN,RST,ACK,FIN,URG -j DROP

# Outgoing interface
$IPT -A INPUT -i venet0 -d $OUTIP -j DROP

# Allow full outgoing connection but no incomming stuff
$IPT -A INPUT -m state --state ESTABLISHED,RELATED -j ACCEPT
$IPT -A OUTPUT -m state --state NEW,ESTABLISHED,RELATED -j ACCEPT

# allow incomming ICMP ping pong stuff
$IPT -A INPUT -i venet0 -d  $EXTIP -p icmp --icmp-type 8 -m state --state NEW,ESTABLISHED,RELATED -j ACCEPT
$IPT -A OUTPUT -p icmp --icmp-type 0 -m state --state ESTABLISHED,RELATED -j ACCEPT
#### VPN ####
$IPT -A INPUT -i venet0 -d $EXTIP --source-port 1024:65535 -p udp -m udp --dport $VPN_PRT -j ACCEPT
$IPT -A INPUT -i venet0 -d $EXTIP --source-port 1024:65535 -p tcp -m tcp --dport $VPN_PRT -j ACCEPT
#### SSH ####
$IPT -A INPUT -i venet0 -d $EXTIP --source-port 1024:65535 -s $S
TATIC_IP -p tcp -m tcp --dport $SSH_PRT -j ACCEPT
$IPT -A INPUT -i tun+ -s $VPN_SN --source-port 1024:65535 -d $VPNSRVR -p tcp -m tcp --dport $SSH_PRT -j ACCEPT
# Allow port 53 tcp/udp (DNS Server)
$IPT -A INPUT -i tun+ -s $VPN_SN -d $VPNSRVR -p udp --dport 53 -m state --state NEW,ESTABLISHED,RELATED -j ACCEPT
$IPT -A OUTPUT -p udp -s $VPNSRVR -d $VPN_SN --sport 53 -m state --state NEW,ESTABLISHED,RELATED -j ACCEPT
$IPT -A INPUT -i tun+ -s $VPN_SN -d $VPNSRVR -p tcp --destination-port 53 -m state --state NEW,ESTABLISHED,RELATED -j \ ACCEPT
$IPT -A OUTPUT -p tcp -s $VPNSRVR -d $VPN_SN --sport 53 -m state --state NEW,ESTABLISHED,RELATED -j ACCEPT
#### DNSCRYPT/VPN ####
$IPT -A OUTPUT -p udp -m owner --uid-owner dnscrypt -m udp --sport 1024:65535 --dport 443 -j ACCEPT
$IPT -A OUTPUT -p tcp -m owner --uid-owner dnscrypt -m tcp --sport 1024:65535 --dport 443 -j ACCEPT
$IPT -A OUTPUT -m owner --uid-owner dnscrypt -j DROP
#### VPN TUNNEL ####
$IPT -A FORWARD -m state --state RELATED,ESTABLISHED -j ACCEPT
$IPT -A FORWARD -s 10.77.69.0/24 -j ACCEPT 
$IPT -A FORWARD -j REJECT$IPT -t nat -A POSTROUTING  -s 10.77.69.0/24 -o venet0 -j SNAT --to-source $OUTIP
#### Drop Source Spoofing #### 
$IPT -A INPUT -s 127.0.0.0/8 -j DROP
$IPT -A INPUT -s 10.0.0.0/8 --j DROP $
$IPT -A INPUT -s 172.16.0.0/12 -j DROP 
$IPT -A INPUT -s 192.168.0.0/16 -j DROP 
$IPT -A INPUT -s 224.0.0.0/3 -j DROP
# Kill Windows Bullshit
$IPT -A INPUT -p tcp -i venet0 -d $EXTIP --dport 137:139 -j REJECT
$IPT -A INPUT -p udp -i venet0 -d $EXTIP --dport 137:139 -j REJECT
$IPT -A INPUT -p udp -i venet0 -d $EXTIP --dport 445 -j REJECT
# log everything else and drop
$IPT -A INPUT -j LOG
$IPT -A FORWARD -j LOG
$IPT -A INPUT -j DROP
exit 0


The font is stupid small so that it would format correctly on this blog. As you can see, this is tailored specifically to servers running on OpenVZ, running OpenVPN, and using dnscrypt-proxy to serve the vpn clients. Keep in mind that often times your vps is also behind your providers firewall as well. This in no way means that you should not configure a firewall, no matter what you read on the internet. Strong firewall rules, tight file permissions, and good user permission management are at the core of Linux security. Of course, don't forget to choose your provider wisely. All of the firewall rules in hell won't help you if the people with physical access to your vps have malicious intents.

No comments:

Post a Comment