filtergen

GNU/Linux's netfilter/iptables framework is a serious piece of kit. Freely available for download, the software can turn any Pentium-class PC or general-purpose server into a flexible, reliable and secure network firewall or IP shaman. The number of iptables(8) match and target extensions available for use pave the way for bewildering possibilities; possibilities all major router and firewall vendors would normally charge you an arm, a leg and your first born child for.

This functionality does not come without a price, however. iptables firewall rulesets are almost guaranteed to grow notoriously complex in large-scale deployments. Smaller, more common, deployments — as would be expected in the case of host-based network filtering for Linux servers — while more manageable in terms of scale, are still not easy to read, and thus, maintain.

The following two (equivalent) stateful firewall rules pass SSH management traffic into a host:

-A INPUT -i eth0 -p tcp -m state --state NEW,ESTABLISHED -m tcp --dport 22 -j ACCEPT 

input eth0 proto tcp dport ssh accept; 

The first rule is vanilla iptables(8)-speak you should already be accustomed to. The second rule is specific to the filtergen high-level grammar. This simple example highlights the unnecessary verbosity inherent in most small-scale iptables firewall deployments.

Here's a second example, this time one demonstrating the use of Cartesian products in filtergen to achieve an even more condensed, noise-free firewall ruleset:

-A INPUT -s 1.2.3.4 -i eth0 -p tcp -m state --state NEW,ESTABLISHED -m tcp --dport 22 -j ACCEPT
-A INPUT -s 1.2.3.4 -i eth0 -p tcp -m state --state NEW,ESTABLISHED -m tcp --dport 443 -j ACCEPT
-A INPUT -s 4.3.2.1 -i eth0 -p tcp -m state --state NEW,ESTABLISHED -m tcp --dport 22 -j ACCEPT
-A INPUT -s 4.3.2.1 -i eth0 -p tcp -m state --state NEW,ESTABLISHED -m tcp --dport 443 -j ACCEPT

input eth0 source { 1.2.3.4 4.3.2.1 } proto tcp dport { ssh https } accept; 

(The astute reader will note that we have missed the opportunity to use the multiport iptables match extension, above. This module was intentionally avoided to better visualise filtergen at work. Under normal circumstances, this particular set of iptables rules can certainly be made a little less wordy.)

filtergen is nothing more than a collection of programs and scripts to facilitate simplified, high-level firewall management for Linux servers. filtergen does not extend iptables in the same fashion as, say, a new match or target extension would; rather, it sits on top of iptables like a C compiler sits on top of an assembler. You cannot do anything with filtergen that you cannot already do without it, although it will make your life as a sysadmin just that little bit easier. Simplified, high-level rules reduce the chance of human error and speed up common, trivial changes to the configuration.

Given input written in filtergen grammar, the tool will perform some obligatory sanity and syntax checking before generating a complete iptables firewall ruleset ready to be hooked into the running kernel. A wrapper script used to bring recent ruleset changes into effect (this process will be referred to from this point onwards as a firewall reload or simply a reload) will additionally take a few precautionary steps to prevent you from filtering yourself out of your own server.

All Anchor dedicated and virtual private servers provisioned with one of Red Hat Enterprise Linux or CentOS will come bundled with the filtergen suite and a set of scrutinised rules written in filtergen grammar. Anchor Secure and Anchor Complete customers need never worry about managing firewalls themselves; this level of support is included with your service. Anchor Monitor (unmanaged) customers, should you choose to continue to use filtergen for firewall management, will benefit most from this article.

Assumed knowledge

This article assumes you are already familiar with at least the TCP/IP protocol stack and Linux systems administration. Additionally, you will have previously used stateful iptables-based filtering to implement a firewall solution for a single host or network of hosts.

filtergen fundamentals

filtergen stores its configuration in /etc/filtergen. Our standard filtergen configuration for Linux servers is split over multiple files, with rules.filter standing in as the conceptual root; other files are pulled in from here. Each file should only implement a small, specific part of the complete configuration. For this reason, these files are commonly referred to as filtergen fragments or just fragments.

filtergen syntax bears some resemblance to C program code, with its abundance of curly braces and obligatory semi-colons terminating individual rules. (Fear not, no pointer arithmetic is required.) The key to writing clean filtergen rulesets is in identifying redundant (duplicate) rules and, wherever possible, re-implementing them using the shortened curly bracket notation, as illustrated above. Remember that your condensed rules must eventually expand to:

<DIRECTION> <INTERFACE> <MATCH 0> [ [ <MATCH n> ] .. ] <TARGET>; 

Our standard root fragment (stored at /etc/filtergen/rules.filter) kicks off the process with one block for each interface and direction of travel (input and output):

# Don't filter loopback traffic
{ input lo; output lo } accept;

# Public interface
input eth0 {
  [...]
};
ouput eth0 {
  [...]
};

Virtual network devices used for VLANs are filtered in a similar manner. Cartesian products, as first introduced above, can be used to apply a ruleset to more than one network interface with a minimal amount of effort. For example:

{ input eth1; input eth0.123 } {
# eth0.123 is a virtual network device mapped to VLAN ID 123.
  [...]
};

Use an asterisk to quickly apply policy to all interfaces:

input * proto icmp accept; 

Default policy

Linux's iptables roughly defines a chain policy as the action to take when a packet falls off the end of your configured ruleset. In other words, the chain policy is the default action to take wherever a more specific course of action has not been defined. This functionality can be duplicated using standard filtergen syntax by adding a filter action at the end of the interface block:

input eth0 {
  [...]
  <ACTION>;
};

where <ACTION> may be one of drop (do not notify the origin host), reject (notify origin) and accept. These actions correspond to the DROP, REJECT and ACCEPT actions of iptables(8), respectively.

Comments

Comments in filtergen follow standard UNIX shell conventions for the most part:

# This is a comment.
This is not a comment.

Use the hash (or pound) symbol at the start of a line. Anything that follows is ignored by the filtergen generator.

Use comments liberally. This applies doubly if your firewall configuration is not under revision control.

Includes

The include directive can be used to include a single file or all files in a directory at the point the directive is used. Includes can be nested.

Example

input eth0 {
  [...]
  include input.d
  [...]
}

In this example, input.d is a directory rooted at /etc/filtergen that contains zero or more filtergen fragments. You should not depend on the order in which these fragments are processed and included. Include each file by hand if ordering is significant.

.acl fragments

.acl files are used to conveniently reference common sets of hosts or networks. (The .acl file extension is simply a convention in use at our organisation and is not enforced by filtergen.) This functionality is in no way different to the use of include directive, discussed above, however it is often beneficial to have lists of hosts defined in their own fragment. Separated fragments can be less error-prone with frequent edits and are easier to manipulate with automated scripts.

Hosts may be specified in any of the following formats:

  • Single IP address, e.g.: 1.2.3.4

  • Network of hosts (CIDR notation), e.g.: 1.2.3.4/22

Example

input eth0 {
    source { include my-hosts.acl } accept;
    [...]
    drop;
}

then in my-hosts.acl:

123.4.5.6/20
1.2.3.4 

For comparison, here is an equivalent fragment that does not make use of a separated .acl:

input eth0 {
    source { 123.4.5.6/20 1.2.3.4 } accept;
    [...]
    drop;
}

Match statements

Writing match statements is likely where you will spending most of your time with filtergen. Only a small subset of matches from iptables(8) are available for use in filtergen:

  • source <ADDRESS RANGE>

  • dest <ADDRESS RANGE>

    • Where <ADDRESS RANGE> may be any network address supplied in a supported format.

  • proto <PROTOCOL>

    • Where <PROTOCOL> may be one of tcp, udp, icmp or any other network protocol recognised by and supported on the system.

  • sport <PORT RANGE>

  • dport <PORT RANGE>

    • Where <PORT RANGE> may be specified as a single port or an inclusive range with X:Y notation.

  • icmptype <ICMP TYPE>

Matches may be negated by prefixing them with an exclamation mark (!).

The matches above also support a number of options:

  • log

    • Implements the LOG target from iptables(8). Tagging some of your filtergen rules with this option will have the kernel log all matching packets to disk. A single-word log prefix can be supplied with log text "<PREFIX>", i.e.:

      input eth0 source 1.2.3.4 log text "myhost" accept;
      This option is most useful when debugging new or recently modified firewall rulesets. The log prefix usually becomes necessary when logging packets from more than one point in the ruleset, as it is otherwise difficult to differentiate between the flows in the logs.
  • local

    • Restrict positive matches to packets that are ultimately destined for the specified network interface. (This option also restricts positive matches to packets that had originated on the specified network interface.) For host-based firewalling scenarios, this option should be given on most, if not all, of your rules. See the forward option, below, for the natural inverse of this option.

  • forward

    • Restrict positive matches to packets that neither originated on the specified network interface nor are destined for it. This option matches forwarded traffic; traffic this host is forwarding on the behalf of another (as when acting as a router). See the local option, above, for the natural inverse of this option.

  • oneway

    • For each high-level rule you give it, filtergen will normally output a pair of rules in native iptables format. The first rule coresponds to what you had specified originally; the second is created to account for return traffic. The oneway option blocks filtergen from generating the second, returning traffic, rule.

Actions

All rules must finally terminate with an action. filtergen supports the following three:

  • accept

    • Passes the packet. This is what you want to be doing with traffic you expect and are equipped to handle.
  • drop

    • Drops the packet without notifying the origin host.
  • reject

    • Rejects the packet and notifies the origin host with icmp-port-unreachable. This action is most commonly used to avoid timeouts with some network services like ident.

An example that demonstrates all three:

input eth0 {
  proto tcp {
    dport { smtp pop-3 } accept;
    dport ssh source 1.2.3.4/24 accept;
    # We don't answer this, but don't want to cause timeouts by blocking it
    dport auth reject;
    log drop;
  };
  # We don't run any UDP (or other non-TCP) services
  log drop;
};

Reloading the firewall

Follow this procedure with every change:

  1. Log into the system as root.

  2. Make your edits to the filtergen fragments in /etc/filtergen.

  3. Check for any syntax errors you may have accidentally introduced:

    # fgadm check >/dev/null 

    fgadm check will output native iptables(8) rules to standard output and a list of errors to standard error. Redirecting standard output will let you focus on the errors. If everything is in order, filtergen will output nothing more than:

    generated ### rules 

    to standard error, where ### is the number of native rules generated. Return to step (2) to correct any errors as necessary.

  4. Reload the firewall with:

    # fgadm_wrapper safereload 

    fgadm_wrapper will load the new firewall configuration and prompt you to hit the y key on your keyboard to confirm you still have management access to the system. This is a fail-safe measure to help prevent administrators from locking themselves out of their systems. In the event fgadm_wrapper does not receive feedback from the administrator in time (15 seconds by default), an atd job will kick into gear and automatically restore the previous, known-good, firewall configuration.

Limitations

filtergen cannot currently handle any rule that references a host name that resolves to multiple A-records. As a work-around, substitute the host name for one of the IP addresses.


External links

  • Playbook: a modern, web-based firewall management system for distributed systems