Set up CentOS 6 Linux Server as a Router Using Iptables

Configuring CentOS Linux server as a router.

Software

Software used in this article:

  1. CentOS 6.7
  2. iptables 1.4.7

What is Iptables?

Iptables is a user-space application program that allows a users to configure the tables provided by the Linux kernel firewall (implemented as different Netfilter modules) and the chains and rules it stores. In other words, iptables is a tool used to manage Linux firewall rules.

Iptables should come with all Linux distributions.

Linux Router Configuration

Our CentOS server has 2 network cards configured as below:

  1. eth0: external from eth1’s perspective network, configured with a static IP address 10.10.1.20,
  2. eth1: internal (LAN) network, configured with a static IP address 10.8.8.2.

The 10.10.1.0/24 network is then naturally further NAT’ed via home router to have Internet access. This is not covered here.

What we want to achieve is this:

  1. Servers on the private network 10.8.8.0/24 have to be able to ping external IPs,
  2. Servers on the private network 10.8.8.0/24 have to be able to access public webservers via HTTP/HTTPS,
  3. All other outgoing traffic from the private network 10.8.8.0/24 to the public should to be denied,
  4. Servers on the public network 10.10.1.0/24 have to be able to access webservers on the private network 10.8.8.0/24 via HTTP/HTTPS,
  5. Servers on the public network 10.10.1.0/24 have to be able to connect to the private network via SSH,
  6. All other incoming traffic from the public network 10.10.1.0/24 to the private 10.8.8.0/24 should be logged and denied.

The image below may help to understand the network configuration.

Check Linux home lab environment with VirtualBox for more info.

Enable Forwarding

Enable IP forwarding on the CentOS server:

# sed -i 's/net.ipv4.ip_forward = 0/net.ipv4.ip_forward = 1/' /etc/sysctl.conf
# sysctl -p

Routing table looks like this:

# ip ro 
10.10.1.0/24 dev eth0  proto kernel  scope link  src 10.10.1.20 
10.8.8.0/24 dev eth1  proto kernel  scope link  src 10.8.8.2 
default via 10.10.1.1 dev eth0

Default gateway is on a “public” eth0 interface.

Configure Forward Chain (Filter Table)

We want the machines on the internal network (eth1) to be able to ping public servers. Therefore we need to allow all new ICMP traffic, originated from eth1, to leave via eth0:

# iptables -A FORWARD -i eth1 -o eth0 -p icmp -m state --state NEW -j ACCEPT

We also want the servers on the internal (eth1) network to able to initiate connections to public webservers on both HTTP and HTTPS ports:

# iptables -A FORWARD -s 10.8.8.0/24 -i eth1 -o eth0 -p tcp -m multiport --dports 80,443 -m state --state NEW -j ACCEPT

Note that the source network address definition is not mandatory here.

On the other hand, if we are going to host some private webservers ourselves, we want the ability to reach them from the public (eth0) network:

# iptables -A FORWARD -d 10.8.8.0/24 -i eth0 -o eth1 -p tcp -m multiport --dports 80,443 -m state --state NEW -j ACCEPT

We also want to be able to connect to the private (eth1) network via SSH:

# iptables -A FORWARD -d 10.8.8.0/24 -i eth0 -o eth1 -p tcp -m multiport --dports 22 -m state --state NEW -j ACCEPT

Then, we want to allow all already established or related connections, both ways:

# iptables -A FORWARD -p tcp -m state --state RELATED,ESTABLISHED -j ACCEPT

Lastly, we are to log everything else for troubleshoting purposes before dropping:

# iptables -A FORWARD -p tcp -j LOG --log-prefix "iptables_forward " 
# iptables -A FORWARD -p tcp -j DROP

Here’s the final iptables configuration for the filter table forward chain:

# iptables -S FORWARD
-P FORWARD ACCEPT
-A FORWARD -i eth1 -o eth0 -p icmp -m state --state NEW -j ACCEPT 
-A FORWARD -s 10.8.8.0/24 -i eth1 -o eth0 -p tcp -m multiport --dports 80,443 -m state --state NEW -j ACCEPT 
-A FORWARD -d 10.8.8.0/24 -i eth0 -o eth1 -p tcp -m multiport --dports 80,443 -m state --state NEW -j ACCEPT 
-A FORWARD -d 10.8.8.0/24 -i eth0 -o eth1 -p tcp -m multiport --dports 22 -m state --state NEW -j ACCEPT 
-A FORWARD -p tcp -m state --state RELATED,ESTABLISHED -j ACCEPT 
-A FORWARD -p tcp -j LOG --log-prefix "iptables_forward " 
-A FORWARD -p tcp -j DROP

Configure NAT Table: Prerouting Chain

Prerouting chain helps to translate the destination IP address of the packets to something that matches the routing on the local server. This is used for destination NAT (DNAT).

In the following example, we want to forward all SSH traffic on port 22043 to 10.8.8.43 machine’s local port 22:

# iptables -t nat -A PREROUTING -i eth0 -p tcp --dport 22043 -m comment --comment "DNAT SSH for .43" -j DNAT --to-destination 10.8.8.43:22

Now, if we try to initialise an SSH connection from the outside (eth0) network to 10.10.1.20:22043, we should be able to access the 10.8.8.43 machine on port 22.

More DNAT rules, similar to the above, can be added, for example, to reach an HTTP webserver on port 8080 which is hosted on 10.8.8.44:80:

# iptables -t nat -A PREROUTING -i eth0 -p tcp --dport 8080 -j DNAT --to-destination 10.8.8.44:80

You should get the idea.

Configure NAT Table: Postrouting Chain

Postrouting chain helps to translate the source IP address of the packets to something that might match the routing on the destination server. This is used for source NAT (SNAT).

Now, to allow private (eth1) network nodes with private IP addresses to communicate with external public networks, we have to configure the firewall for IP masquerading, which masks requests from LAN nodes with the IP address of the firewall’s external device (in this case, eth0):

# iptables -t nat -A POSTROUTING -o eth0 -p icmp -j MASQUERADE 
# iptables -t nat -A POSTROUTING -o eth0 -p tcp -m multiport --dports 80,443 -j MASQUERADE

Here’s the final iptables configurtion for the NAT table (all chains):

# iptables -t nat -S
-P PREROUTING ACCEPT
-P POSTROUTING ACCEPT
-P OUTPUT ACCEPT
-A PREROUTING -i eth0 -p tcp -m tcp --dport 22043 -m comment --comment "DNAT SSH for .43" -j DNAT --to-destination 10.8.8.43:22 
-A POSTROUTING -o eth0 -p icmp -j MASQUERADE 
-A POSTROUTING -o eth0 -p tcp -m multiport --dports 80,443 -j MASQUERADE

Don’t forget to save iptables rules:

# service iptables save

Puppet Classes to Manage Firewall Rules

The following Puppet classes can be used as examples to manage firewall rules. Check this blog post for how to configure a Puppet server on CentOS.

class fw_router_forward_puppet {
  firewall { '001R ICMP':
    chain    => 'FORWARD',
    state    => ['NEW'],
    iniface  => 'eth1',
    outiface => 'eth0',
    proto    => 'icmp',
    action   => 'accept'
  }
  firewall { '002R Outgoing HTTP/S':
    chain    => 'FORWARD',
    state    => ['NEW'],
    iniface  => 'eth1',
    outiface => 'eth0',
    proto    => 'tcp',
    source   => '10.8.8.0/24',
    dport    => [ 80, 443 ],
    action   => 'accept'
  }
  firewall { '003R Incoming HTTP/S':
    chain    => 'FORWARD',
    state    => ['NEW'],
    iniface  => 'eth0',
    outiface => 'eth1',
    proto    => 'tcp',
    destination => '10.8.8.0/24',
    dport    => [ 80, 443 ],
    action   => 'accept'
  }
  firewall { '004R Incoming SSH':
    chain    => 'FORWARD',
    state    => ['NEW'],
    iniface  => 'eth0',
    outiface => 'eth1',
    proto    => 'tcp',
    destination => '10.8.8.0/24',
    dport    => [ 22 ],
    action   => 'accept'
  }
  firewall { '997R ':
    chain    => 'FORWARD',
    state    => ['RELATED', 'ESTABLISHED'],
    action   => 'accept',
  }
  firewall { '998R LOG sessions':
    chain  => 'FORWARD',
    jump    => 'LOG',
    log_level => '4',
    log_prefix => 'iptables_forward ',
  }
  firewall { '999R Drop all':
    chain    => 'FORWARD',
    action   => 'drop',
  }
}
class fw_router_prerouting_puppet {
 firewall { '005R Route ICMP':
    chain    => 'POSTROUTING',
    table    => 'nat',
    outiface => 'eth0',
    proto    => 'icmp',
    jump     => 'MASQUERADE'
  }
  firewall { '006R Route HTTP/S':
    chain    => 'POSTROUTING',
    table    => 'nat',
    outiface => 'eth0',
    proto    => 'tcp',
    dport    => [ 80, 443 ],
    jump     => 'MASQUERADE'
  }
  firewall { '007R NAT HTTP for VIP .35':
    chain    => 'PREROUTING',
    table    => 'nat',
    iniface  => 'eth0',
    proto    => 'tcp',
    dport    => [ 8035 ],
    todest   => '10.8.8.35:80',
    jump     => 'DNAT'
  }
  firewall { '008R NAT HTTPS for VIP .35':
    chain    => 'PREROUTING',
    table    => 'nat',
    iniface  => 'eth0',
    proto    => 'tcp',
    dport    => [ 8835 ],
    todest   => '10.8.8.35:443',
    jump     => 'DNAT'
  }
  firewall { '011R NAT SSH for .43':
    chain    => 'PREROUTING',
    table    => 'nat',
    iniface  => 'eth0',
    proto    => 'tcp',
    dport    => [ 22043 ],
    todest   => '10.8.8.43:22',
    jump     => 'DNAT'
  }
}

One thought on “Set up CentOS 6 Linux Server as a Router Using Iptables

Leave a Reply

Your email address will not be published. Required fields are marked *