<?xml version="1.0" encoding="iso-8859-1"?> <!-- The FreeBSD Documentation Project $FreeBSD$ --> <chapter id="firewalls"> <chapterinfo> <authorgroup> <author> <firstname>Joseph J.</firstname> <surname>Barbish</surname> <contrib>Contributed by </contrib> </author> </authorgroup> <authorgroup> <author> <firstname>Brad</firstname> <surname>Davis</surname> <contrib>Converted to SGML and updated by </contrib> </author> </authorgroup> </chapterinfo> <title>Firewalls</title> <indexterm><primary>firewall</primary></indexterm> <indexterm> <primary>security</primary> <secondary>firewalls</secondary> </indexterm> <sect1 id="firewalls-intro"> <title>Introduction</title> <para>Firewalls make it possible to filter the incoming and outgoing traffic that flows through a system. A firewall can use one or more sets of <quote>rules</quote> to inspect network packets as they come in or go out of network connections and either allows the traffic through or blocks it. The rules of a firewall can inspect one or more characteristics of the packets such as the protocol type, source or destination host address, and source or destination port.</para> <para>Firewalls can enhance the security of a host or a network. They can be used to do one or more of the following:</para> <itemizedlist> <listitem> <para>Protect and insulate the applications, services, and machines of an internal network from unwanted traffic from the public Internet.</para> </listitem> <listitem> <para>Limit or disable access from hosts of the internal network to services of the public Internet.</para> </listitem> <listitem> <para>Support network address translation (<acronym>NAT</acronym>), which allows an internal network to use private <acronym>IP</acronym> addresses and share a single connection to the public Internet using either a single <acronym>IP</acronym> address or a shared pool of automatically assigned public addresses.</para> </listitem> </itemizedlist> <para>After reading this chapter, you will know:</para> <itemizedlist> <listitem> <para>How to define packet filtering rules.</para> </listitem> <listitem> <para>The differences between the firewalls built into &os;.</para> </listitem> <listitem> <para>How to use and configure the <application>PF</application> firewall.</para> </listitem> <listitem> <para>How to use and configure the <application>IPFILTER</application> firewall.</para> </listitem> <listitem> <para>How to use and configure the <application>IPFW</application> firewall.</para> </listitem> </itemizedlist> <para>Before reading this chapter, you should:</para> <itemizedlist> <listitem> <para>Understand basic &os; and Internet concepts.</para> </listitem> </itemizedlist> </sect1> <sect1 id="firewalls-concepts"> <title>Firewall Concepts</title> <indexterm> <primary>firewall</primary> <secondary>rulesets</secondary> </indexterm> <para>A firewall ruleset can be either <quote>exclusive</quote> or <quote>inclusive</quote>. An exclusive firewall allows all traffic through except for the traffic matching the ruleset. An inclusive firewall does the reverse as it only allows traffic matching the rules through and blocks everything else.</para> <para>An inclusive firewall offers better control of the outgoing traffic, making it a better choice for systems that offer services to the public Internet. It also controls the type of traffic originating from the public Internet that can gain access to a private network. All traffic that does not match the rules is blocked and logged. Inclusive firewalls are generally safer than exclusive firewalls because they significantly reduce the risk of allowing unwanted traffic.</para> <note> <para>Unless noted otherwise, all configuration and example rulesets in this chapter create inclusive firewall rulesets.</para> </note> <para>Security can be tightened further using a <quote>stateful firewall</quote>. This type of firewall keeps track of open connections and only allows traffic which either matches an existing connection or opens a new, allowed connection. The disadvantage of a stateful firewall is that it can be vulnerable to Denial of Service (<acronym>DoS</acronym>) attacks if a lot of new connections are opened very fast. Most firewalls use a combination of stateful and non-stateful behavior.</para> </sect1> <sect1 id="firewalls-apps"> <title>Firewall Packages</title> <para>&os; has three firewalls built into the base system: <emphasis>IPFILTER</emphasis>, also known as <acronym>IPF</acronym>, <emphasis>IPFIREWALL</emphasis>, also known as <acronym>IPFW</acronym>, and <acronym>PF</acronym>). &os; also provides two traffic shapers for controlling bandwidth usage: &man.altq.4; and &man.dummynet.4;. Dummynet has traditionally been closely tied with <acronym>IPFW</acronym>, and <acronym>ALTQ</acronym> with <acronym>PF</acronym>. Each firewall uses rules to control the access of packets to and from a &os; system, although they go about it in different ways and each has a different rule syntax.</para> <para>&os; provides multiple firewalls in order to meet the different requirements and preferences for a wide variety of users. Each user should evaluate which firewall best meets their needs.</para> <para>Since all firewalls are based on inspecting the values of selected packet control fields, the creator of the firewall ruleset must have an understanding of how <acronym>TCP/IP</acronym> works, what the different values in the packet control fields are, and how these values are used in a normal session conversation. For a good introduction, refer to <ulink url="http://www.ipprimer.com/overview.cfm">Daryl's TCP/IP Primer</ulink>.</para> </sect1> <sect1 id="firewalls-pf"> <sect1info> <authorgroup> <author> <firstname>John</firstname> <surname>Ferrell</surname> <contrib>Revised and updated by </contrib> <!-- 24 March 2008 --> </author> </authorgroup> </sect1info> <title>PF and <acronym>ALTQ</acronym></title> <indexterm> <primary>firewall</primary> <secondary>PF</secondary> </indexterm> <para>Since &os; 5.3, a ported version of OpenBSD's <acronym>PF</acronym> firewall has been included as an integrated part of the base system. <acronym>PF</acronym> is a complete, full-featured firewall that has optional support for <acronym>ALTQ</acronym> (Alternate Queuing), which provides Quality of Service (<acronym>QoS</acronym>).</para> <para>Since the OpenBSD Project maintains the definitive reference for <acronym>PF</acronym> in the<ulink url="http://www.openbsd.org/faq/pf/">PF FAQ</ulink>, this section of the Handbook focuses on <acronym>PF</acronym> as it pertains to &os;, while providing some general usage information.</para> <para>More information about porting <acronym>PF</acronym> to &os; can be found at <ulink url="http://pf4freebsd.love2party.net/"></ulink>.</para> <sect2> <title>Using the PF Loadable Kernel Modules</title> <para>In order to use PF, the PF kernel module must be first loaded. Add the following line to <filename>/etc/rc.conf</filename>:</para> <programlisting>pf_enable="YES"</programlisting> <para>Then, run the startup script to load the module:</para> <screen>&prompt.root; <userinput>service pf start</userinput></screen> <para>The PF module will not load if it cannot find the ruleset configuration file. The default location is <filename>/etc/pf.conf</filename>. If the PF ruleset is located somewhere else, add a line to <filename>/etc/rc.conf</filename> which specifies the full path to the file:</para> <programlisting>pf_rules="<replaceable>/path/to/pf.conf</replaceable>"</programlisting> <para>The sample <filename>pf.conf</filename> can be found in <filename class="directory">/usr/share/examples/pf/</filename>.</para> <para>The <acronym>PF</acronym> module can also be loaded manually from the command line:</para> <screen>&prompt.root; <userinput>kldload pf.ko</userinput></screen> <para>Logging support for PF is provided by <varname>pflog.ko</varname> which can be loaded by adding the following line to <filename>/etc/rc.conf</filename>:</para> <programlisting>pflog_enable="YES"</programlisting> <para>Then, run the startup script to load the module:</para> <screen>&prompt.root; <userinput>service pflog start</userinput></screen> </sect2> <sect2> <title>PF Kernel Options</title> <indexterm> <primary>kernel options</primary> <secondary>device pf</secondary> </indexterm> <indexterm> <primary>kernel options</primary> <secondary>device pflog</secondary> </indexterm> <indexterm> <primary>kernel options</primary> <secondary>device pfsync</secondary> </indexterm> <para>While it is not necessary to compile <acronym>PF</acronym> support into the &os; kernel, some of PF's advanced features are not included in the loadable module, namely &man.pfsync.4;, which is a pseudo-device that exposes certain changes to the state table used by <acronym>PF</acronym>. It can be paired with &man.carp.4; to create failover firewalls using <acronym>PF</acronym>. More information on <acronym>CARP</acronym> can be found in <link linkend="carp">of the Handbook</link>.</para> <para>The following <acronym>PF</acronym> kernel options can be found in <filename>/usr/src/sys/conf/NOTES</filename>:</para> <programlisting>device pf device pflog device pfsync</programlisting> <para><literal>device pf</literal> enables PF support.</para> <para><literal>device pflog</literal> enables the optional &man.pflog.4; pseudo network device which can be used to log traffic to a &man.bpf.4; descriptor. The &man.pflogd.8; daemon can then be used to store the logging information to disk.</para> <para><literal>device pfsync</literal> enables the optional &man.pfsync.4; pseudo-network device that is used to monitor <quote>state changes</quote>.</para> </sect2> <sect2> <title>Available <filename>rc.conf</filename> Options</title> <para>The following &man.rc.conf.5; statements can be used to configure <acronym>PF</acronym> and &man.pflog.4; at boot:</para> <programlisting>pf_enable="YES" # Enable PF (load module if required) pf_rules="/etc/pf.conf" # rules definition file for pf pf_flags="" # additional flags for pfctl startup pflog_enable="YES" # start pflogd(8) pflog_logfile="/var/log/pflog" # where pflogd should store the logfile pflog_flags="" # additional flags for pflogd startup</programlisting> <para>If there is a LAN behind the firewall and packets need to be forwarded for the computers on the LAN, or NAT is required, add the following option:</para> <programlisting>gateway_enable="YES" # Enable as LAN gateway</programlisting> </sect2> <sect2> <title>Creating Filtering Rules</title> <para>By default, <acronym>PF</acronym> reads its configuration rules from <filename>/etc/pf.conf</filename> and modifies, drops, or passes packets according to the rules or definitions specified in this file. The &os; installation includes several sample files located in <filename>/usr/share/examples/pf/</filename>. Refer to the <ulink url="http://www.openbsd.org/faq/pf/">PF FAQ</ulink> for complete coverage of <acronym>PF</acronym> rulesets.</para> <warning> <para>When reading the <ulink url="http://www.openbsd.org/faq/pf/">PF FAQ</ulink>, keep in mind that different versions of &os; contain different versions of PF. Currently, &os; 8.<replaceable>X</replaceable> is using the same version of <acronym>PF</acronym> as OpenBSD 4.1. &os; 9.<replaceable>X</replaceable> and later is using the same version of <acronym>PF</acronym> as OpenBSD 4.5.</para> </warning> <para>The &a.pf; is a good place to ask questions about configuring and running the <acronym>PF</acronym> firewall. Do not forget to check the mailing list archives before asking questions.</para> <para>To control <acronym>PF</acronym>, use &man.pfctl.8;. Below are some useful options to this command. Review &man.pfctl.8; for a description of all available options:</para> <informaltable frame="none" pgwide="1"> <tgroup cols="2"> <thead> <row> <entry>Command</entry> <entry>Purpose</entry> </row> </thead> <tbody> <row> <entry><command>pfctl <option>-e</option></command></entry> <entry>Enable PF.</entry> </row> <row> <entry><command>pfctl <option>-d</option></command></entry> <entry>Disable PF.</entry> </row> <row> <entry><command>pfctl <option>-F</option> all <option>-f</option> /etc/pf.conf</command></entry> <entry>Flush all NAT, filter, state, and table rules and reload <filename>/etc/pf.conf</filename>.</entry> </row> <row> <entry><command>pfctl <option>-s</option> [ rules | nat state ]</command></entry> <entry>Report on the filter rules, NAT rules, or state table.</entry> </row> <row> <entry><command>pfctl <option>-vnf</option> /etc/pf.conf</command></entry> <entry>Check <filename>/etc/pf.conf</filename> for errors, but do not load ruleset.</entry> </row> </tbody> </tgroup> </informaltable> </sect2> <sect2> <title>Enabling <acronym>ALTQ</acronym></title> <para><acronym>ALTQ</acronym> is only available by compiling its support into the &os; kernel. <acronym>ALTQ</acronym> is not supported by all network card drivers. Refer to &man.altq.4; for a list of drivers that are supported by the release of &os;.</para> <para>The following kernel options will enable <acronym>ALTQ</acronym> and add additional functionality:</para> <programlisting>options ALTQ options ALTQ_CBQ # Class Based Queuing (CBQ) options ALTQ_RED # Random Early Detection (RED) options ALTQ_RIO # RED In/Out options ALTQ_HFSC # Hierarchical Packet Scheduler (HFSC) options ALTQ_PRIQ # Priority Queuing (PRIQ) options ALTQ_NOPCC # Required for SMP build</programlisting> <para><literal>options ALTQ</literal> enables the <acronym>ALTQ</acronym> framework.</para> <para><literal>options ALTQ_CBQ</literal> enables <emphasis>Class Based Queuing</emphasis> (<acronym>CBQ</acronym>). <acronym>CBQ</acronym> can be used to divide a connection's bandwidth into different classes or queues to prioritize traffic based on filter rules.</para> <para><literal>options ALTQ_RED</literal> enables <emphasis>Random Early Detection</emphasis> (<acronym>RED</acronym>). <acronym>RED</acronym> is used to avoid network congestion by measuring the length of the queue and comparing it to the minimum and maximum thresholds for the queue. If the queue is over the maximum, all new packets will be dropped. <acronym>RED</acronym> drops packets from different connections randomly.</para> <para><literal>options ALTQ_RIO</literal> enables <emphasis>Random Early Detection In and Out</emphasis>.</para> <para><literal>options ALTQ_HFSC</literal> enables the <emphasis>Hierarchical Fair Service Curve Packet Scheduler</emphasis> <acronym>HFSC</acronym>. For more information, refer to <ulink url="http://www-2.cs.cmu.edu/~hzhang/HFSC/main.html"></ulink>.</para> <para><literal>options ALTQ_PRIQ</literal> enables <emphasis>Priority Queuing</emphasis> (<acronym>PRIQ</acronym>). <acronym>PRIQ</acronym> will always pass traffic that is in a higher queue first.</para> <para><literal>options ALTQ_NOPCC</literal> enables <acronym>SMP</acronym> support for <acronym>ALTQ</acronym>. This option is required on <acronym>SMP</acronym> systems.</para> </sect2> <sect2 id="pf-tutorial"> <sect2info> <authorgroup> <author> <firstname>Peter</firstname> <surname>Hansteen</surname> <othername>N. M.</othername> <contrib>Contributed by </contrib> </author> </authorgroup> </sect2info> <title><acronym>PF</acronym> Rule Sets and Tools</title> <para>This section demonstrates some useful <acronym>PF</acronym> features and <acronym>PF</acronym> related tools in a series of examples. A more thorough tutorial is available at <ulink url="http://home.nuug.no/~peter/pf/">http://home.nuug.no/~peter/pf/</ulink>.</para> <tip> <para><filename role="package">security/sudo</filename> is useful for running commands like <command>pfctl</command> that require elevated privileges. It can be installed from the Ports Collection.</para> </tip> <sect3 id="pftut-simplest"> <title>The Simplest Rule Set Ever</title> <para>The simplest possible setup is for a single machine which will not run any services, and which will talk to one network which may be the Internet. A minimal <filename>/etc/pf.conf</filename> looks like this:</para> <programlisting>block in all pass out all keep state</programlisting> <para>Here we deny any incoming traffic, allow traffic we make ourselves to pass, and retain state information on our connections. Keeping state information allows return traffic for all connections we have initiated to pass back to us. This rule set is used on machines that can be trusted. The rule set can be loaded with</para> <screen>&prompt.root; <userinput>pfctl -e ; pfctl -f /etc/pf.conf</userinput></screen> </sect3> <sect3> <title>Tighter and More Elegant</title> <para>For a slightly more structured and complete setup, we start by denying everything and then allowing only those things we know that we need <footnote><para>Why write the rule set to default deny? The short answer is, it gives better control at the expense of some thinking. The point of packet filtering is to take control, not to run catch-up with what the bad guys do. Marcus Ranum has written a very entertaining and informative article about this, <ulink url="http://www.ranum.com/security/computer_security/editorials/dumb/index.html">The Six Dumbest Ideas in Computer Security</ulink>, and it is well written too.</para></footnote>. This gives us the opportunity to introduce two of the features which make <acronym>PF</acronym> such a wonderful tool: <firstterm>lists</firstterm> and <firstterm>macros</firstterm>.</para> <para>We will make some changes to <filename>/etc/pf.conf</filename>, starting with</para> <programlisting>block all</programlisting> <para>Then we back up a little. Macros need to be defined before use, so at the very top of the file, we add:</para> <programlisting>tcp_services = "{ ssh, smtp, domain, www, pop3, auth, pop3s }" udp_services = "{ domain }"</programlisting> <para>Now we have demonstrated several things at once - what macros look like, that macros may be lists, and that <acronym>PF</acronym> understands rules using port names equally well as it does port numbers. The names are the ones listed in <filename>/etc/services</filename>. This gives us something to put in our rules, which we edit slightly to look like this:</para> <programlisting>block all pass out proto tcp to any port $tcp_services keep state pass proto udp to any port $udp_services keep state</programlisting> <para>At this point some of us will point out that UDP is stateless, but <acronym>PF</acronym> actually manages to maintain state information despite this. Keeping state for a UDP connection means that for example when you ask a name server about a domain name, you will be able to receive its answer.</para> <para>Since we have made changes to our <filename>pf.conf</filename>, we load the new rules:</para> <screen>&prompt.root; <userinput>pfctl -f /etc/pf.conf</userinput></screen> <para>and the new rules are applied. If there are no syntax errors, <command>pfctl</command> will not output any messages during the rule load. The <option>-v</option> flag will produce more verbose <command>pfctl</command> output.</para> <para>If there have been extensive changes to the rule set, the rules can be tested before attempting to load them. The command to do this is</para> <screen>&prompt.root; <userinput>pfctl -nf /etc/pf.conf</userinput></screen> <para><option>-n</option> causes the rules to be interpreted only, but does not load them. This provides an opportunity to correct any errors. Under any circumstances, the last valid rule set loaded will be in force until <acronym>PF</acronym> is disabled or a new rule set is loaded.</para> <tip> <title>Use <command>pfctl -v</command> to Show the Parsed Rule Set</title> <para>Adding the <option>-v</option> to a <command>pfctl</command> ruleset load (even a dry run with <option>-n</option>) will display the fully parsed rules exactly the way they will be loaded. This is extremely useful when debugging rules.</para> </tip> </sect3> <sect3 id="pftut-gateway"> <title>A Simple Gateway with NAT</title> <para>To most users, a single machine setup will be of limited interest, and at this point we move on to more realistic or at least more common setups, concentrating on a machine which is running <acronym>PF</acronym> and also acts as a gateway for at least one other machine.</para> <sect4 id="pftut-gwpitfalls"> <title>Gateways and the Pitfalls of <literal>in</literal>, <literal>out</literal> and <literal>on</literal></title> <para>In the single machine setup, life is relatively simple. Traffic created on it should either pass out to the rest of the world or not, and the administrator decides what to let in from elsewhere.</para> <para>On a gateway, the perspective changes from <quote>me versus the network out there</quote> to <quote>I am the one who decides what to pass to or from all the networks I am connected to</quote>. The machine has at least two network interfaces, each connected to a separate net.</para> <para>It is very reasonable to think that for traffic to pass from the network connected to <devicename>xl1</devicename> to hosts on the network connected to <devicename>xl0</devicename>, a rule like this is needed:</para> <programlisting>pass in on xl1 from xl1:network to xl0:network port $ports keep state</programlisting> <para>This rule keeps track of states as well.</para> <para>However, one of the most common and most complained-about mistakes in firewall configuration is not realizing that the <quote>to</quote> keyword does not in itself guarantee passage all the way there. The rule we just wrote only lets the traffic pass in to the gateway on the internal interface. To let the packets get a bit further, a matching rule is needed which says</para> <programlisting>pass out on xl0 from xl1:network to xl0:network port $ports keep state</programlisting> <para>These rules will work, but they will not necessarily achieve the desired effect.</para> <para>Rules this specific are rarely needed. For the basic gateway configurations we will be dealing with here, a better rule says</para> <programlisting>pass from xl1:network to any port $ports keep state</programlisting> <para>This provides local net access to the Internet and leaves the detective work to the <firstterm>antispoof</firstterm> and <firstterm>scrub</firstterm> code. They are both pretty good these days, and we will get back to them later. For now we just accept the fact that for simple setups, interface-bound rules with in/out rules tend to add more clutter than they are worth to rule sets.</para> <para>For a busy network admin, a readable rule set is a safer rule set.</para> <para>For the remainder of this section, with some exceptions, we will keep the rules as simple as possible for readability.</para> </sect4> <sect4 id="pftut-whatsthelocalnet"> <title>What is the Local Network, Anyway?</title> <para>Above, we introduced the <literal>interface:network</literal> notation. That is a nice piece of shorthand, but the rule set can be made even more readable and maintainable by taking the macro use a tiny bit further.</para> <para>For example, a <literal>$localnet</literal> macro could be defined as the network directly attached to your internal interface (<literal>$xl1:network</literal> in the examples above).</para> <para>Alternatively, the definition of <literal>$localnet</literal> could be changed to an <emphasis>IP address/netmask</emphasis> notation to denote a network, such as <literal>192.168.100.1/24</literal> for a subnet of private addresses.</para> <para>If required, <literal>$localnet</literal> could even be defined as a list of networks. Whatever the specific needs, a sensible <literal>$localnet</literal> definition and a typical pass rule of the type</para> <programlisting>pass from $localnet to any port $ports keep state</programlisting> <para>could end up saving you a few headaches. We will stick to that convention from here on.</para> </sect4> <sect4 id="pftut-gwsimplesetup"> <title>Setting Up</title> <para>We assume that the machine has acquired another network card or at any rate there is a network connection from the local network, via PPP or other means. We will not consider the specific interface configurations.</para> <para>For the discussion and examples below, only the interface names will differ between a PPP setup and an Ethernet one, and we will do our best to get rid of the actual interface names as quickly as possible.</para> <para>First, we need to turn on gatewaying in order to let the machine forward the network traffic it receives on one interface to other networks via a separate interface. Initially we will do this on the command line with &man.sysctl.8;, for traditional <emphasis>IP version four</emphasis>.</para> <screen>&prompt.root; <userinput>sysctl net.inet.ip.forwarding=1</userinput></screen> <para>If we need to forward <emphasis>IP version six</emphasis> traffic, the command is</para> <screen>&prompt.root; <userinput>sysctl net.inet6.ip6.forwarding=1</userinput></screen> <para>In order for this to continue working after the computer has been restarted at some time in the future, enter these settings into <filename>/etc/rc.conf</filename>:</para> <programlisting>gateway_enable="YES" #for ipv4 ipv6_gateway_enable="YES" #for ipv6</programlisting> <para>Use <command>ifconfig -a</command>, or <command>ifconfig <replaceable>interface_name</replaceable></command> to find out if both of the interfaces to be used are up and running.</para> <para>If all traffic initiated by machines on the inside is to be allowed, <filename>/etc/pf.conf</filename> could look roughly like this <footnote> <para>For dialup users, the external interface is the <filename>tun0</filename> pseudo-device. Broadband users such as ADSL subscribers tend to have an Ethernet interface to play with, however for a significant subset of ADSL users, specifically those using PPP over Ethernet (PPPoE), the correct external interface will be the <filename>tun0</filename> pseudo-device, not the physical Ethernet interface.</para> </footnote>:</para> <programlisting>ext_if = "xl0" # macro for external interface - use tun0 for PPPoE int_if = "xl1" # macro for internal interface localnet = $int_if:network # ext_if IP address could be dynamic, hence ($ext_if) nat on $ext_if from $localnet to any -> ($ext_if) block all pass from { lo0, $localnet } to any keep state</programlisting> <para>Note the use of macros to assign logical names to the network interfaces. Here 3Com cards are used, but this is the last time during this tutorial we will find this of any interest whatsoever. In truly simple setups like this one, we may not gain very much by using macros like these, but once the rule sets grow somewhat larger, you will learn to appreciate the readability this provides.</para> <para>Also note the <literal>nat</literal> rule. This is where we handle the network address translation from the non-routable address inside the local net to the sole official address we assume has been assigned.</para> <para>The parentheses surrounding the last part of the nat rule <literal>($ext_if)</literal> are there to compensate for the possibility that the IP address of the external interface may be dynamically assigned. This detail will ensure that network traffic runs without serious interruptions even if the external IP address changes.</para> <para>On the other hand, this rule set probably allows more traffic to pass out of the network than actually desired. One reasonable setup could contain the macro</para> <programlisting>client_out = "{ ftp-data, ftp, ssh, domain, pop3, auth, nntp, http, \ https, cvspserver, 2628, 5999, 8000, 8080 }"</programlisting> <para>and the main pass rule</para> <programlisting>pass inet proto tcp from $localnet to any port $client_out \ flags S/SA keep state</programlisting> <para>This may be a somewhat peculiar selection of ports, but it is based on a real life example. Individual needs probably differ at least in some specifics, but this should cover at least some of the more useful services.</para> <para>In addition, we have a few other pass rules. We will be returning to some of the more interesting ones rather soon. One pass rule which is useful to those of us who want the ability to administer our machines from elsewhere is</para> <programlisting>pass in inet proto tcp to port ssh</programlisting> <para>or for that matter</para> <programlisting>pass in inet proto tcp to $ext_if port ssh</programlisting> <para>whichever is preferred. Lastly we need to make the name service work for our clients:</para> <programlisting>udp_services = "{ domain, ntp }"</programlisting> <para>This is supplemented with a rule which passes the traffic we want through our firewall:</para> <programlisting>pass quick inet proto { tcp, udp } to any port $udp_services keep state</programlisting> <para>Note the <literal>quick</literal> keyword in this rule. We have started writing rule sets which consist of several rules, and it is time to take a look at the relationships between the rules in a rule set. The rules are evaluated from top to bottom, in the sequence they are written in the configuration file. For each packet or connection evaluated by <acronym>PF</acronym>, <emphasis>the last matching rule</emphasis> in the rule set is the one which is applied. The <literal>quick</literal> keyword offers an escape from the ordinary sequence. When a packet matches a quick rule, the packet is treated according to the present rule. The rule processing stops without considering any further rules which might have matched the packet. This is very useful when a few isolated exceptions to the general rules are needed.</para> <para>This rule also takes care of <acronym>NTP</acronym>, which is used for time synchronization. One thing common to both protocols is that they may under certain circumstances communicate alternately over TCP and UDP.</para> </sect4> </sect3> <sect3 id="pftut-ftp"> <title>That Sad Old <acronym>FTP</acronym> Thing</title> <para>The short list of real life <acronym>TCP</acronym> ports above contained, among other things, <acronym>FTP</acronym>. <acronym>FTP</acronym> is a sad old thing and a problem child, emphatically so for anyone trying to combine <acronym>FTP</acronym> and firewalls. <acronym>FTP</acronym> is an old and weird protocol, with a lot to not like. The most common points against it are</para> <itemizedlist> <listitem> <para>Passwords are transferred in the clear</para> </listitem> <listitem> <para>The protocol demands the use of at least two <acronym>TCP</acronym> connections (control and data) on separate ports</para> </listitem> <listitem> <para>When a session is established, data is communicated via ports selected at random</para> </listitem> </itemizedlist> <para>All of these points make for challenges security-wise, even before considering any potential weaknesses in client or server software which may lead to security issues. These things have tended to happen.</para> <para>Under any circumstances, other more modern and more secure options for file transfer exist, such as &man.sftp.1; or &man.scp.1;, which feature both authentication and data transfer via encrypted connections. Competent <acronym>IT</acronym> professionals should have a preference for some other form of file transfer than <acronym>FTP</acronym>.</para> <para>Regardless of our professionalism and preferences, we are all too aware that at times we will need to handle things we would prefer not to. In the case of <acronym>FTP</acronym> through firewalls, the main part of our handling consists of redirecting the traffic to a small program which is written specifically for this purpose.</para> <sect4 id="pftut-ftp-proxy"> <title><acronym>FTP</acronym> Via Redirect: <application>ftp-proxy</application></title> <para>Enabling <acronym>FTP</acronym> transfers through your gateway is amazingly simple, thanks to the <acronym>FTP</acronym> proxy program (called &man.ftp-proxy.8;) included in the base system on &os; and other systems which offer <acronym>PF</acronym>. </para> <para>The <acronym>FTP</acronym> protocol being what it is, the proxy needs to dynamically insert rules in your rule set. &man.ftp-proxy.8; interacts with your configuration via a set of anchors where the proxy inserts and deletes the rules it constructs to handle your <acronym>FTP</acronym> traffic.</para> <para>To enable &man.ftp-proxy.8;, add this line to <filename>/etc/rc.conf</filename>:</para> <programlisting>ftpproxy_flags=""</programlisting> <para>Starting the proxy manually by running <command>/usr/sbin/ftp-proxy</command> allows testing of the <acronym>PF</acronym> configuration changes we are about to make.</para> <para>For a basic configuration, only three elements need to be added to <filename>/etc/pf.conf</filename>. First, the anchors:</para> <programlisting>nat-anchor "ftp-proxy/*" rdr-anchor "ftp-proxy/*"</programlisting> <para>The proxy will insert the rules it generates for the <acronym>FTP</acronym> sessions here. A pass rule is needed to let <acronym>FTP</acronym> traffic in to the proxy.</para> <para>Now for the actual redirection. Redirection rules and <acronym>NAT</acronym> rules fall into the same rule class. These rules may be referenced directly by other rules, and filtering rules may depend on these rules. Logically, <literal>rdr</literal> and <literal>nat</literal> rules need to be defined before the filtering rules.</para> <para>We insert our <literal>rdr</literal> rule immediately after the <literal>nat</literal> rule in <filename>/etc/pf.conf</filename></para> <programlisting>rdr pass on $int_if proto tcp from any to any port ftp -> 127.0.0.1 port 8021</programlisting> <para>In addition, the redirected traffic must be allowed to pass. We achieve this with</para> <programlisting>pass out proto tcp from $proxy to any port ftp</programlisting> <para>where <literal>$proxy</literal> expands to the address the proxy daemon is bound to.</para> <para>Save <filename>pf.conf</filename>, then load the new rules with</para> <screen>&prompt.root; <userinput>pfctl -f /etc/pf.conf</userinput></screen> <para>At this point, users will probably begin noticing that <acronym>FTP</acronym> works before they have been told.</para> <para>This example covers a basic setup where the clients in the local net need to contact <acronym>FTP</acronym> servers elsewhere. The basic configuration here should work well with most combinations of <acronym>FTP</acronym> clients and servers. As shown in the man page, the proxy's behavior can be changed in various ways by adding options to the <literal>ftpproxy_flags=</literal> line. Some clients or servers may have specific quirks that must be compensated for in the configuration, or there may be a need to integrate the proxy in specific ways such as assigning <acronym>FTP</acronym> traffic to a specific queue. For these and other finer points of &man.ftp-proxy.8; configuration, start by studying the man page.</para> <para>For ways to run an <acronym>FTP</acronym> server protected by <acronym>PF</acronym> and &man.ftp-proxy.8;, look into running a separate <command>ftp-proxy</command> in reverse mode (using <option>-R</option>), on a separate port with its own redirecting pass rule.</para> </sect4> </sect3> <sect3 id="pftut-icmp"> <title>Easing Troubleshooting</title> <para>Making network troubleshooting friendly is a potentially large subject. At most times, the debugging or troubleshooting friendliness of a <acronym>TCP/IP</acronym> network depends on treatment of the Internet protocol which was designed specifically with debugging in mind, the <emphasis>Internet Control Message Protocol</emphasis>, or <acronym>ICMP</acronym> as it is usually abbreviated.</para> <para><acronym>ICMP</acronym> is the protocol for sending and receiving <emphasis>control messages</emphasis> between hosts and gateways, mainly to provide feedback to a sender about any unusual or difficult conditions enroute to the target host.</para> <para>There is a lot of <acronym>ICMP</acronym> traffic which usually just happens in the background while users are surfing the web, reading mail or transferring files. Routers use <acronym>ICMP</acronym> to negotiate packet sizes and other transmission parameters in a process often referred to as <emphasis>path <acronym>MTU</acronym> discovery</emphasis>.</para> <para>Some admins refer to <acronym>ICMP</acronym> as either <quote>just evil</quote>, or, if their understanding runs a little deeper, <quote>a necessary evil</quote>. The reason for this attitude is purely historical. The reason can be found a few years back when it was discovered that several operating systems contained code in their networking stack which could make a machine running one of the affected systems crash and fall over, or in some cases just do really strange things, with a sufficiently large <acronym>ICMP</acronym> request.</para> <para>One of the companies which was hit hard was Microsoft, and you can find rather a lot of material on the <quote>ping of death</quote> bug by using your favorite search engine. This all happened in the second half of the 1990s, and all modern operating systems, at least the ones we can read, have thoroughly sanitized their network code since then. At least that is what we are led to believe.</para> <para>One of the early workarounds was to simply block either all <acronym>ICMP</acronym> traffic or at least <acronym>ICMP</acronym> ECHO, which is what ping uses. Now these rule sets have been around for roughly fifteen years, and the people who put them there are still scared.</para> <sect4 id="pftut-dowepass"> <title>Then, Do We Let it All Through?</title> <para>The obvious question then becomes, if <acronym>ICMP</acronym> is such a good and useful thing, should we not let it all through, all the time? The answer is <quote>It depends</quote>.</para> <para>Letting diagnostic traffic pass unconditionally of course makes debugging easier, but also makes it relatively easy for others to extract information about your network. That means that a rule like</para> <programlisting>pass inet proto icmp from any to any</programlisting> <para>might not be optimal if the internal workings of the local network should be cloaked in a bit of mystery. In all fairness it should also be said that some <acronym>ICMP</acronym> traffic might be found quite harmlessly riding piggyback on <literal>keep state</literal> rules.</para> </sect4> <sect4 id="pftut-icmpstopatgw"> <title>The Easy Way Out: the Buck Stops Here</title> <para>The easiest solution could very well be to let all <acronym>ICMP</acronym> traffic from the local net through and stop probes from elsewhere at the gateway:</para> <programlisting>pass inet proto icmp from $localnet to any keep state pass inet proto icmp from any to $ext_if keep state</programlisting> <para>Stopping probes at the gateway might be an attractive option anyway, but let us have a look at a few other options which will show some of <acronym>PF</acronym>'s flexibility.</para> </sect4> <sect4 id="pftut-letpingthru"> <title>Letting <command>ping</command> Through</title> <para>The rule set we have developed so far has one clear disadvantage: common troubleshooting commands such as &man.ping.8; and &man.traceroute.8; will not work. That may not matter too much to end users, and since it was <command>ping</command> which scared people into filtering or blocking <acronym>ICMP</acronym> traffic in the first place, there are apparently some people who feel we are better off without it. If you are in my perceived target audience, you will be rather fond of having those troubleshooting tools avalable. With a couple of small additions to the rule set, they will be. &man.ping.8; uses <acronym>ICMP</acronym>, and in order to keep our rule set tidy, we start by defining another macro:</para> <programlisting>icmp_types = "echoreq"</programlisting> <para>and a rule which uses the definition,</para> <programlisting>pass inet proto icmp all icmp-type $icmp_types keep state</programlisting> <para>More or other types of <acronym>ICMP</acronym> packets may need to go through, and <literal>icmp_types</literal> can be expanded to a list of those packet types that are allowed.</para> </sect4> <sect4 id="pftut-helptraceroute"> <title>Helping &man.traceroute.8;</title> <para>&man.traceroute.8; is another command which is quite useful when users claim that the Internet is not working. By default, Unix <command>traceroute</command> uses UDP connections according to a set formula based on destination. The rule below works with <command>traceroute</command> on all unixes I've had access to, including GNU/Linux:</para> <programlisting># allow out the default range for traceroute(8): # "base+nhops*nqueries-1" (33434+64*3-1) pass out on $ext_if inet proto udp from any to any port 33433 >< 33626 keep state</programlisting> <para>Experience so far indicates that <command>traceroute</command> implementations on other operating systems work roughly the same. Except, of course, on Microsoft Windows. On that platform, <command>TRACERT.EXE</command> uses ICMP ECHO for this purpose. So to let Windows traceroutes through, only the first rule is needed. Unix <command>traceroute</command> can be instructed to use other protocols as well, and will behave remarkably like its Microsoft counterpart if <option>-I</option> is used. Check the &man.traceroute.8; man page (or its source code, for that matter) for all the details.</para> <para>Under any circumstances, this solution was lifted from an openbsd-misc post. I've found that list, and the searchable list archives (accessible among other places from <ulink url="http://marc.theaimsgroup.com/">http://marc.theaimsgroup.com/</ulink>), to be a very valuable resource whenever you need OpenBSD or <acronym>PF</acronym> related information.</para> </sect4> <sect4 id="pftut-pathmtudisc"> <title>Path <acronym>MTU</acronym> Discovery</title> <para>Internet protocols are designed to be device independent, and one consequence of device independence is that the optimal packet size for a given connection cannot always be predicted reliably. The main constraint on packet size is called the <firstterm>Maximum Transmission Unit</firstterm>, or <acronym>MTU</acronym>, which sets the upper limit on the packet size for an interface. &man.ifconfig.8; shows the <acronym>MTU</acronym> for the network interfaces.</para> <para>Modern TCP/IP implementations expect to be able to determine the right packet size for a connection through a process which, simply put, involves sending packets of varying sizes with the <quote>Do not fragment</quote> flag set, expecting an <acronym>ICMP</acronym> return packet indicating <quote>type 3, code 4</quote> when the upper limit has been reached. Now do not dive for the RFCs right away. Type 3 means <quote>destination unreachable</quote>, while code 4 is short for <quote>fragmentation needed, but the do-not-fragment flag is set</quote>. So if connections to networks which may have other <acronym>MTU</acronym>s than the local network seem sub-optimal, and there is no need to be that specific, the list of <acronym>ICMP</acronym> types can be changed slightly to let the <quote>destination unreachable</quote> packets through, too:</para> <programlisting>icmp_types = "{ echoreq, unreach }"</programlisting> <para>As we can see, this means we do not need to change the pass rule itself:</para> <programlisting>pass inet proto icmp all icmp-type $icmp_types keep state</programlisting> <para><acronym>PF</acronym> allows filtering on all variations of <acronym>ICMP</acronym> types and codes. For those who want to delve into what to pass (or not) of <acronym>ICMP</acronym> traffic, the list of possible types and codes are documented in the &man.icmp.4; and &man.icmp6.4; man pages. The background information is available in the <acronym>RFC</acronym>s <footnote><para>The main internet <acronym>RFC</acronym>s describing <acronym>ICMP</acronym> and some related techhiques are RFC792, RFC950, RFC1191, RFC1256, RFC2521, rfc2765, while necessary updates for ICMP for IPv6 are found in RFC1885, RFC2463, RFC2466. These documents are available in a number of places on the net, such as the <ulink url="http://www.ietf.org">ietf.org</ulink> and <ulink url="http://www.faqs.org">faqs.org</ulink> web sites.</para></footnote>.</para> </sect4> </sect3> <sect3 id="pftut-tables"> <title>Tables Make Life Easier</title> <para>By this time it may appear that this gets awfully static and rigid. There will after all be some kinds of data which are relevant to filtering and redirection at a given time, but do not deserve to be put into a configuration file! Quite right, and <acronym>PF</acronym> offers mechanisms for handling these situations as well. Tables are one such feature, mainly useful as lists which can be manipulated without needing to reload the entire rule set, and where fast lookups are desirable. Table names are always enclosed in <literal>< ></literal>, like this:</para> <programlisting>table <clients> { 192.168.2.0/24, !192.168.2.5 }</programlisting> <para>Here, the network <literal>192.168.2.0/24</literal> is part of the table, except the address <literal>192.168.2.5</literal>, which is excluded using the <literal>!</literal> operator (logical NOT). It is also possible to load tables from files where each item is on a separate line, such as the file <filename>/etc/clients</filename>.</para> <programlisting>192.168.2.0/24 !192.168.2.5</programlisting> <para>which in turn is used to initialize the table in <filename>/etc/pf.conf</filename>:</para> <programlisting>table <clients> persist file /etc/clients</programlisting> <para>Then, for example, one of our earlier rules can be changed to read</para> <programlisting>pass inet proto tcp from <clients> to any port $client_out flags S/SA keep state</programlisting> <para>to manage outgoing traffic from client computers. With this in hand, the table's contents can be manipulated live, such as</para> <screen>&prompt.root; <userinput>pfctl -t clients -T add 192.168.1/16</userinput></screen> <para>Note that this changes the in-memory copy of the table only, meaning that the change will not survive a power failure or other reboot unless there are arrangements to store the changes.</para> <para>One might opt to maintain the on-disk copy of the table using a &man.cron.8; job which dumps the table content to disk at regular intervals, using a command such as <command>pfctl -t clients -T show >/etc/clients</command>. Alternatively, <filename>/etc/clients</filename> could be edited, replacing the in-memory table contents with the file data:</para> <screen>&prompt.root; <userinput>pfctl -t clients -T replace -f /etc/clients</userinput></screen> <para>For operations performed frequently, administrators will sooner or later end up writing shell scripts for tasks such as inserting or removing items or replacing table contents. The only real limitations lie in individual needs and creativity.</para> </sect3> <sect3 id="pftut-overload"> <title>Overload Tables</title> <para>Those who run a Secure Shell login service which is accessible from the Internet have probably seen something like this in the authentication logs:</para> <programlisting>Sep 26 03:12:34 skapet sshd[25771]: Failed password for root from 200.72.41.31 port 40992 ssh2 Sep 26 03:12:34 skapet sshd[5279]: Failed password for root from 200.72.41.31 port 40992 ssh2 Sep 26 03:12:35 skapet sshd[5279]: Received disconnect from 200.72.41.31: 11: Bye Bye Sep 26 03:12:44 skapet sshd[29635]: Invalid user admin from 200.72.41.31 Sep 26 03:12:44 skapet sshd[24703]: input_userauth_request: invalid user admin Sep 26 03:12:44 skapet sshd[24703]: Failed password for invalid user admin from 200.72.41.31 port 41484 ssh2</programlisting> <para>And so on. This is what a brute force attack looks like. Essentially somebody, or more likely, a cracked computer somewhere, is trying by brute force to find a combination of user name and password which will let them into your system.</para> <para>The simplest response would be to write a <filename>pf.conf</filename> rule which blocks all access. This leads to another class of problems, including what to do in order to let people with legitimate business on the system access it anyway. Some might consider moving the service to another port, but then again, the ones flooding on port 22 would probably be able to scan their way to port 22222 for a repeat performance.</para> <para>Since OpenBSD 3.7, and soon after in &os; version 6.0, <acronym>PF</acronym> has offered a slightly more elegant solution. Pass rules can be written so they maintain certain limits on what connecting hosts can do. For good measure, violators can be banished to a table of addresses which are denied some or all access. If desired, it's even possible to drop all existing connections from machines which overreach the limits. Here is how it is done:</para> <para>First, set up the table. In the tables section, add</para> <programlisting>table <bruteforce> persist</programlisting> <para>Then somewhere fairly early in the rule set, add a rule to block the bruteforcers:</para> <programlisting>block quick from <bruteforce></programlisting> <para>And finally, the pass rule.</para> <programlisting>pass inet proto tcp from any to $localnet port $tcp_services \ flags S/SA keep state \ (max-src-conn 100, max-src-conn-rate 15/5, \ overload <bruteforce> flush global)</programlisting> <para>The first part here is identical to the main rule we constructed earlier. The part in parentheses is the new stuff which will ease network load even further.</para> <para><literal>max-src-conn</literal> is the number of simultaneous connections allowed from one host. In this example, it is set at 100. Other setups may want a slightly higher or lower value.</para> <para><literal>max-src-conn-rate</literal> is the rate of new connections allowed from any single host, here 15 connections per 5 seconds. Again, the administrator is the one to judge what suits their setup.</para> <para><literal>overload <bruteforce></literal> means that any host which exceeds these limits gets its address added to the table <literal>bruteforce</literal>. Our rule set blocks all traffic from addresses in the bruteforce table.</para> <para>Finally, <literal>flush global</literal> says that when a host reaches the limit, that host's connections will be terminated (flushed). The global part says that for good measure, this applies to connections which match other pass rules too.</para> <para>The effect is dramatic. From here on, bruteforcers more often than not will end up with <computeroutput>"Fatal: timeout before authentication"</computeroutput> messages, getting nowhere.</para> <note> <para>These rules will <emphasis>not</emphasis> block slow bruteforcers, sometimes referred to as <ulink url="http://home.nuug.no/~peter/hailmary2013/">the Hail Mary Cloud</ulink>.</para> </note> <para>Once again, please keep in mind that this example rule is intended mainly as an illustration. It is not unlikely that a particular network's needs are better served by rather different rules or combinations of rules.</para> <para>If, for example, a generous number of connections in general are wanted, but the desire is to be a little more tight fisted when it comes to <application>ssh</application>, supplement the rule above with something like the one below, early on in the rule set:</para> <programlisting>pass quick proto { tcp, udp } from any to any port ssh \ flags S/SA keep state \ (max-src-conn 15, max-src-conn-rate 5/3, \ overload <bruteforce> flush global)</programlisting> <para>It should be possible to find the set of parameters which is just right for individual situations by reading the relevant man pages and the <ulink url="http://www.openbsd.org/faq/pf/">PF User Guide</ulink>, and perhaps a bit of experimentation.</para> <note> <title>It May Not be Necessary to Block All Overloaders</title> <para>It is probably worth noting at this point that the <emphasis>overload</emphasis> mechanism is a general technique which does not have to apply exclusively to the <emphasis>ssh</emphasis> service, and it is not always optimal to block all traffic from offenders entirely.</para> <para>For example, an overload rule could be used to protect a mail service or a web service, and the overload table could be used in a rule to assign offenders to a queue with a minimal bandwidth allocation or, in the web case, to redirect to a specific web page.</para> </note> <sect4 id="pftut-expire"> <title>Expiring Table Entries with <application>pfctl</application></title> <para>At this point, we have tables which will be filled by our <literal>overload</literal> rules, and since we could reasonably expect our gateways to have months of uptime, the tables will grow incrementally, taking up more memory as time goes by.</para> <para>Sometimes an IP address that was blocked last week due to a brute force attack was in fact a dynamically assigned one, which is now assigned to a different ISP customer who has a legitimate reason to try communicating with hosts in the local network.</para> <para>Situations like these were what caused Henning Brauer to add to <application>pfctl</application> the ability to expire table entries not referenced in a specified number of seconds (in OpenBSD 4.1). For example, the command</para> <screen>&prompt.root; <userinput>pfctl -t bruteforce -T expire 86400</userinput></screen> <para>will remove <literal><bruteforce></literal> table entries which have not been referenced for 86400 seconds.</para> </sect4> <sect4 id="pftut-expiretable"> <title>The <application>expiretable</application> Tool</title> <para>Before <application>pfctl</application> acquired the ability to expire table entries, Henrik Gustafsson had written <application>expiretable</application>, which removes table entries which have not been accessed for a specified period of time.</para> <para>One useful example is to use the <application>expiretable</application> program as a way of removing outdated <literal><bruteforce></literal> table entries.</para> <para>For example, let <application>expiretable</application> remove <literal><bruteforce></literal> table entries older than 24 hours by adding an entry containing the following to <filename>/etc/rc.local</filename>:</para> <programlisting>/usr/local/sbin/expiretable -v -d -t 24h bruteforce</programlisting> <para><application>expiretable</application> is in the Ports Collection on &os; as <filename role="package">security/expiretable</filename>.</para> </sect4> </sect3> <sect3 id="pftut-tools"> <title>Other <acronym>PF</acronym> Tools</title> <para>Over time, a number of tools have been developed which interact with <acronym>PF</acronym> in various ways.</para> <sect4 id="pftut-pftop"> <title>The <application>pftop</application> Traffic Viewer</title> <para>Can Erkin Acar's <application>pftop</application> makes it possible to keep an eye on what passes into and out of the network. <application>pftop</application> is available through the ports system as <filename role="package">sysutils/pftop</filename>. The name is a strong hint at what it does - <application>pftop</application> shows a running snapshot of traffic in a format which is strongly inspired by &man.top.1;.</para> </sect4> <sect4 id="pftut-spamd"> <title>The <application>spamd</application> Spam Deferral Daemon</title> <para>Not to be confused with the <application>spamd</application> daemon which comes bundled with <application>spamassassin</application>, the <acronym>PF</acronym> companion <application>spamd</application> was designed to run on a PF gateway to form part of the outer defense against spam. <application>spamd</application> hooks into the <acronym>PF</acronym> configuration via a set of redirections.</para> <para>The main point underlying the <application>spamd</application> design is the fact that spammers send a large number of messages, and the probability that you are the first person receiving a particular message is incredibly small. In addition, spam is mainly sent via a few spammer friendly networks and a large number of hijacked machines. Both the individual messages and the machines will be reported to blacklists fairly quickly, and this is the kind of data <application>spamd</application> can use to our advantage with <firstterm>blacklists</firstterm>.</para> <para>What <application>spamd</application> does to SMTP connections from addresses in the blacklist is to present its banner and immediately switch to a mode where it answers SMTP traffic one byte at the time. This technique, which is intended to waste as much time as possible on the sending end while costing the receiver pretty much nothing, is called <firstterm>tarpitting</firstterm>. The specific implementation with one byte SMTP replies is often referred to as <firstterm>stuttering</firstterm>.</para> <sect5 id="pftut-spamd-allblack"> <title>A Basic Blacklisting <application>spamd</application></title> <para>Here is the basic procedure for setting up <application>spamd</application> with automatically updated blacklists:</para> <procedure> <step> <para>Install the <filename role="package">mail/spamd/</filename> port. In particular, be sure to read the package message and act upon what it says. Specifically, to use <application>spamd</application>'s greylisting features, a file descriptor file system (see <ulink url="http://www.freebsd.org/cgi/man.cgi?query=fdescfs&sektion=5">fdescfs(5)</ulink>) must be mounted at <filename>/dev/fd/</filename>. Do this by adding the following line to <filename>/etc/fstab</filename>:</para> <programlisting> fdescfs /dev/fd fdescfs rw 0 0</programlisting> <para>Make sure the <filename>fdescfs</filename> code is in the kernel, either compiled in or by loading the module with &man.kldload.8;.</para> </step> <step> <para>Next, edit the rule set to include</para> <programlisting>table <spamd> persist table <spamd-white> persist rdr pass on $ext_if inet proto tcp from <spamd> to \ { $ext_if, $localnet } port smtp -> 127.0.0.1 port 8025 rdr pass on $ext_if inet proto tcp from !<spamd-white> to \ { $ext_if, $localnet } port smtp -> 127.0.0.1 port 8025</programlisting> <para>The two tables <spamd> and <spamd-white> are essential. SMTP traffic from the addresses in the first table plus the ones which are not in the other table are redirected to a daemon listening at port 8025.</para> </step> <step> <para>The next step is to set up <application>spamd</application>'s own configuration in <filename>/usr/local/etc/spamd.conf</filename> supplemented by <filename>rc.conf</filename> parameters.</para> <para>The supplied sample file offers quite a bit of explanation, and the man page offers additional information, but we will recap the essentials here.</para> <para>One of the first lines without a <literal>#</literal> comment sign at the start contains the block which defines the <literal>all</literal> list, which specifies the lists actually used:</para> <programlisting>all:\ :traplist:whitelist:</programlisting> <para>Here, all the desired black lists are added, separated by colons (<literal>:</literal>). To use whitelists to subtract addresses from the blacklist, add the name of the whitelist immediately after the name of each blacklist, i.e., <literal>:blacklist:whitelist:</literal>.</para> <para>Next up is a blacklist definition:</para> <programlisting>traplist:\ :black:\ :msg="SPAM. Your address %A has sent spam within the last 24 hours":\ :method=http:\ :file=www.openbsd.org/spamd/traplist.gz</programlisting> <para>Following the name, the first data field specifies the list type, in this case <literal>black</literal>. The <literal>msg</literal> field contains the message to display to blacklisted senders during the SMTP dialogue. The <literal>method</literal> field specifies how spamd-setup fetches the list data, here <literal>http</literal>. The other options are fetching via <literal>ftp</literal>, from a <literal>file</literal> in a mounted file system or via <literal>exec</literal> of an external program. Finally the <literal>file</literal> field specifies the name of the file spamd expects to receive.</para> <para>The definition of a whitelist follows much the same pattern:</para> <programlisting>whitelist:\ :white:\ :method=file:\ :file=/var/mail/whitelist.txt</programlisting> <para>but omits the message parameters since a message is not needed.</para> <tip> <title>Choose Data Sources with Care</title> <para>Using all the blacklists in the sample <filename>spamd.conf</filename> will end up blacklisting large blocks of the Internet, including several Asian nations. Administrators need to edit the file to end up with an optimal configuration. The administrator is the judge of which data sources to use, and using lists other than the ones suggested in the sample file is possible.</para> </tip> <para>Put the lines for spamd and any startup parameters desired in <filename>/etc/rc.conf</filename>, for example:</para> <programlisting>spamd_flags="-v" # for normal use: "" and see spamd-setup(8)</programlisting> <para>When done with editing the setup, reload the rule set, start <application>spamd</application> with the options desired using the <filename>/usr/local/etc/rc.d/obspamd</filename> script, and complete the configuration using <command>spamd-setup</command>. Finally, create a &man.cron.8; job which calls <command>spamd-setup</command> to update the tables at reasonable intervals.</para> </step> </procedure> <para>On a typical gateway in front of a mail server, hosts will start getting trapped within a few seconds to several minutes.</para> </sect5> <sect5 id="pftut-spamd-greylist"> <title>Adding Greylisting to the <application>spamd</application> Setup</title> <para><application>spamd</application> also supports <firstterm>greylisting</firstterm>, which works by rejecting messages from unknown hosts temporarily with <replaceable>45n</replaceable> codes, letting messages from hosts which try again within a reasonable time through. Traffic from well behaved hosts, that is, senders which are set up to behave within the limits set up in the relevant RFCs <footnote><para>The relevant RFCs are mainly RFC1123 and RFC2821.</para></footnote>, will be let through.</para> <para>Greylisting as a technique was presented in a 2003 paper by Evan Harris <footnote><para>The original Harris paper and a number of other useful articles and resources can be found at the <ulink url="http://www.greylisting.org/">greylisting.org</ulink> web site.</para></footnote>, and a number of implementations followed over the next few months. OpenBSD's <application>spamd</application> acquired its ability to greylist in OpenBSD 3.5, which was released in May 2004.</para> <para>The most amazing thing about greylisting, apart from its simplicity, is that it still works. Spammers and malware writers have been very slow to adapt.</para> <para>The basic procedure for adding greylisting to your setup follows below.</para> <procedure> <step> <para>If not done already, make sure the file descriptor file system (see &man.fdescfs.5;) is mounted at <filename>/dev/fd/</filename>. Do this by adding the following line to <filename>/etc/fstab</filename>:</para> <programlisting>fdescfs /dev/fd fdescfs rw 0 0</programlisting> <para>and make sure the &man.fdescfs.5; code is in the kernel, either compiled in or by loading the module with &man.kldload.8;.</para> </step> <step> <para>To run <application>spamd</application> in greylisting mode, <filename>/etc/rc.conf</filename> must be changed slightly by adding</para> <programlisting>spamd_grey="YES" # use spamd greylisting if YES</programlisting> <para>Several greylisting related parameters can be fine-tuned with <command>spamd</command>'s command line parameters and the corresponding <filename>/etc/rc.conf</filename> settings. Check the <application>spamd</application> man page to see what the parameters mean.</para> </step> <step> <para>To complete the greylisting setup, restart <application>spamd</application> using the <filename>/usr/local/etc/rc.d/obspamd</filename> script.</para> </step> </procedure> <para>Behind the scenes, rarely mentioned and barely documented are two of <application>spamd</application>'s helpers, the <application>spamdb</application> database tool and the <application>spamlogd</application> whitelist updater, which both perform essential functions for the greylisting feature. Of the two <application>spamlogd</application> works quietly in the background, while <application>spamdb</application> has been developed to offer some interesting features.</para> <note> <title>Restart <application>spamd</application> to Enable Greylisting</title> <para>After following all steps in the tutorial exactly up to this point, <application>spamlogd</application> has been started automatically already. However, if the initial <application>spamd</application> configuration did not include greylisting, <application>spamlogd</application> may not have been started, and there may be strange symptoms, such as greylists and whitelists not getting updated properly.</para> <para>Under normal circumstances, it should not be necessary to start <application>spamlogd</application> by hand. Restarting <application>spamd</application> after enabling greylisting ensures <application>spamlogd</application> is loaded and available too.</para> </note> <para><application>spamdb</application> is the administrator's main interface to managing the black, grey and white lists via the contents of the <filename>/var/db/spamdb</filename> database.</para> </sect5> </sect4> <sect4 id="pftut-hygiene"> <title>Network Hygiene: Blocking, Scrubbing and so On</title> <para>Our gateway does not feel quite complete without a few more items in the configuration which will make it behave a bit more sanely towards hosts on the wide net and our local network.</para> <sect5 id="pftut-blockpolicy"> <title><literal>block-policy</literal></title> <para><literal>block-policy</literal> is an option which can be set in the <literal>options</literal> part of the ruleset, which precedes the redirection and filtering rules. This option determines which feedback, if any, <acronym>PF</acronym> will give to hosts which try to create connections which are subsequently blocked. The option has two possible values, <literal>drop</literal>, which drops blocked packets with no feedback, and <literal>return</literal>, which returns with status codes such as <computeroutput>Connection refused</computeroutput> or similar.</para> <para>The correct strategy for block policies has been the subject of rather a lot of discussion. We choose to play nicely and instruct our firewall to issue returns:</para> <programlisting>set block-policy return</programlisting> </sect5> <sect5 id="pftut-scrub"> <title><literal>scrub</literal></title> <para>In <acronym>PF</acronym> versions up to OpenBSD 4.5 inclusive, <literal>scrub</literal> is a keyword which enables network packet normalization, causing fragmented packets to be assembled and removing ambiguity. Enabling <literal>scrub</literal> provides a measure of protection against certain kinds of attacks based on incorrect handling of packet fragments. A number of supplementing options are available, but we choose the simplest form which is suitable for most configurations.</para> <programlisting>scrub in all</programlisting> <para>Some services, such as NFS, require some specific fragment handling options. This is extensively documented in the <acronym>PF</acronym> user guide and man pages provide all the information you could need.</para> <para>One fairly common example is this,</para> <programlisting>scrub in all fragment reassemble no-df max-mss 1440</programlisting> <para>meaning, we reassemble fragments, clear the <quote>do not fragment</quote> bit and set the maximum segment size to 1440 bytes. Other variations are possible, and you should be able to cater to various specific needs by consulting the man pages and some experimentation.</para> </sect5> <sect5 id="pftut-antispoof"> <title><literal>antispoof</literal></title> <para><literal>antispoof</literal> is a common special case of filtering and blocking. This mechanism protects against activity from spoofed or forged IP addresses, mainly by blocking packets appearing on interfaces and in directions which are logically not possible.</para> <para>We specify that we want to weed out spoofed traffic coming in from the rest of the world and any spoofed packets which, however unlikely, were to originate in our own network:</para> <programlisting>antispoof for $ext_if antispoof for $int_if</programlisting> </sect5> <sect5 id="pftut-unrouteables"> <title>Handling Non-Routable Addresses from Elsewhere</title> <para>Even with a properly configured gateway to handle network address translation for your own network, you may find yourself in the unenviable position of having to compensate for other people's misconfigurations.</para> <para>One depressingly common class of misconfigurations is the kind which lets traffic with non-routable addresses out to the Internet. Traffic from non-routeable addresses have also played a part in several DOS attack techniques, so it may be worth considering explicitly blocking traffic from non-routeable addresses from entering your network.</para> <para>One possible solution is the one outlined below, which for good measure also blocks any attempt to initiate contact to non-routable addresses through the gateway's external interface:</para> <programlisting>martians = "{ 127.0.0.0/8, 192.168.0.0/16, 172.16.0.0/12, \ 10.0.0.0/8, 169.254.0.0/16, 192.0.2.0/24, \ 0.0.0.0/8, 240.0.0.0/4 }" block drop in quick on $ext_if from $martians to any block drop out quick on $ext_if from any to $martians</programlisting> <para>Here, the <literal>martians</literal> macro denotes the RFC 1918 addresses and a few other ranges which are mandated by various RFCs not to be in circulation on the open Internet. Traffic to and from such addresses is quietly dropped on the gateway's external interface.</para> <para>The specific details of how to implement this kind of protection will vary, among other things according to your specific network configuration. Your network design could for example dictate that you include or exclude other address ranges than these.</para> <para>This completes our simple NATing firewall for a small local network. A more thorough tutorial is available at <ulink url="http://home.nuug.no/~peter/pf/">http://home.nuug.no/~peter/pf/</ulink>, where you will also find slides from related presentations.</para> </sect5> </sect4> </sect3> </sect2> </sect1> <sect1 id="firewalls-ipf"> <title>The IPFILTER (IPF) Firewall</title> <indexterm> <primary>firewall</primary> <secondary>IPFILTER</secondary> </indexterm> <para>IPFILTER is a cross-platform, open source firewall which has been ported to &os;, NetBSD, OpenBSD, &sunos;, HP/UX, and &solaris; operating systems.</para> <para>IPFILTER is based on a kernel-side firewall and <acronym>NAT</acronym> mechanism that can be controlled and monitored by userland interface programs. The firewall rules can be set or deleted using &man.ipf.8;. The <acronym>NAT</acronym> rules can be set or deleted using &man.ipnat.8;. Run-time statistics for the kernel parts of IPFILTER can be printed using &man.ipfstat.8;. To log IPFILTER actions to the system log files, use &man.ipmon.8;.</para> <para>IPF was originally written using a rule processing logic of <quote>the last matching rule wins</quote> and only used stateless rules. Over time, IPF has been enhanced to include a <quote>quick</quote> option and a stateful <quote>keep state</quote> option which modernized the rules processing logic. IPF's official documentation covers only the legacy rule coding parameters and rule file processing logic and the modernized functions are only included as additional options.</para> <para>The instructions contained in this section are based on using rules that contain <quote>quick</quote> and <quote>keep state</quote> as these provide the basic framework for configuring an inclusive firewall ruleset.</para> <para>For a detailed explanation of the legacy rules processing method, refer to <ulink url="http://www.munk.me.uk/ipf/ipf-howto.html"></ulink> and <ulink url="http://coombs.anu.edu.au/~avalon/ip-filter.html"></ulink>.</para> <para>The IPF FAQ is at <ulink url="http://www.phildev.net/ipf/index.html"></ulink>.</para> <para>A searchable archive of the IPFilter mailing list is available at <ulink url="http://marc.theaimsgroup.com/?l=ipfilter"></ulink>.</para> <sect2> <title>Enabling IPF</title> <indexterm> <primary>IPFILTER</primary> <secondary>enabling</secondary> </indexterm> <para>IPF is included in the basic &os; install as a kernel loadable module. The system will dynamically load this module at boot time when <varname>ipfilter_enable="YES"</varname> is added to <filename>rc.conf</filename>. The module enables logging and <literal>default pass all</literal>. To change the default to <literal>block all</literal>, add a <literal>block all</literal> rule at the end of the ruleset.</para> </sect2> <sect2> <title>Kernel Options</title> <indexterm> <primary>kernel options</primary> <secondary>IPFILTER</secondary> </indexterm> <indexterm> <primary>kernel options</primary> <secondary>IPFILTER_LOG</secondary> </indexterm> <indexterm> <primary>kernel options</primary> <secondary>IPFILTER_DEFAULT_BLOCK</secondary> </indexterm> <indexterm> <primary>IPFILTER</primary> <secondary>kernel options</secondary> </indexterm> <para>For users who prefer to statically compile IPF support into a custom kernel, the following IPF option statements, listed in <filename>/usr/src/sys/conf/NOTES</filename>, are available:</para> <programlisting>options IPFILTER options IPFILTER_LOG options IPFILTER_DEFAULT_BLOCK</programlisting> <para><literal>options IPFILTER</literal> enables support for the <quote>IPFILTER</quote> firewall.</para> <para><literal>options IPFILTER_LOG</literal> enables IPF logging using the <devicename>ipl</devicename> packet logging pseudo—device for every rule that has the <literal>log</literal> keyword.</para> <para><literal>options IPFILTER_DEFAULT_BLOCK</literal> changes the default behavior so that any packet not matching a firewall <literal>pass</literal> rule gets blocked.</para> <para>These settings will take effect only after installing a kernel that has been built with the above options set.</para> </sect2> <sect2> <title>Available <filename>rc.conf</filename> Options</title> <para>To activate IPF at boot time, the following statements need to be added to <filename>/etc/rc.conf</filename>:</para> <programlisting>ipfilter_enable="YES" # Start ipf firewall ipfilter_rules="/etc/ipf.rules" # loads rules definition text file ipmon_enable="YES" # Start IP monitor log ipmon_flags="-Ds" # D = start as daemon # s = log to syslog # v = log tcp window, ack, seq # n = map IP & port to names</programlisting> <para>If there is a LAN behind the firewall that uses the reserved private IP address ranges, the following lines have to be added to enable <acronym>NAT</acronym> functionality:</para> <programlisting>gateway_enable="YES" # Enable as LAN gateway ipnat_enable="YES" # Start ipnat function ipnat_rules="/etc/ipnat.rules" # rules definition file for ipnat</programlisting> </sect2> <sect2> <title>IPF</title> <indexterm><primary><command>ipf</command></primary></indexterm> <para>To load the ruleset file, use &man.ipf.8;. Custom rules are normally placed in a file, and the following command can be used to replace the currently running firewall rules:</para> <screen>&prompt.root; <userinput>ipf -Fa -f /etc/ipf.rules</userinput></screen> <para><option>-Fa</option> flushes all the internal rules tables.</para> <para><option>-f</option> specifies the file containing the rules to load.</para> <para>This provides the ability to make changes to a custom rules file, run the above IPF command, and thus update the running firewall with a fresh copy of the rules without having to reboot the system. This method is convenient for testing new rules as the procedure can be executed as many times as needed.</para> <para>Refer to &man.ipf.8; for details on the other flags available with this command.</para> <para>&man.ipf.8; expects the rules file to be a standard text file. It will not accept a rules file written as a script with symbolic substitution.</para> <para>There is a way to build IPF rules that utilize the power of script symbolic substitution. For more information, see <xref linkend="firewalls-ipf-rules-script"/>.</para> </sect2> <sect2> <title>IPFSTAT</title> <indexterm><primary><command>ipfstat</command></primary></indexterm> <indexterm> <primary>IPFILTER</primary> <secondary>statistics</secondary> </indexterm> <para>The default behavior of &man.ipfstat.8; is to retrieve and display the totals of the accumulated statistics gathered by applying the rules against packets going in and out of the firewall since it was last started, or since the last time the accumulators were reset to zero using <command>ipf -Z</command>.</para> <para>Refer to &man.ipfstat.8; for details.</para> <para>The default &man.ipfstat.8; output will look something like this:</para> <screen>input packets: blocked 99286 passed 1255609 nomatch 14686 counted 0 output packets: blocked 4200 passed 1284345 nomatch 14687 counted 0 input packets logged: blocked 99286 passed 0 output packets logged: blocked 0 passed 0 packets logged: input 0 output 0 log failures: input 3898 output 0 fragment state(in): kept 0 lost 0 fragment state(out): kept 0 lost 0 packet state(in): kept 169364 lost 0 packet state(out): kept 431395 lost 0 ICMP replies: 0 <acronym>TCP</acronym> RSTs sent: 0 Result cache hits(in): 1215208 (out): 1098963 IN Pullups succeeded: 2 failed: 0 OUT Pullups succeeded: 0 failed: 0 Fastroute successes: 0 failures: 0 <acronym>TCP</acronym> cksum fails(in): 0 (out): 0 Packet log flags set: (0)</screen> <para>When supplied with either <option>-i</option> for inbound or <option>-o</option> for outbound, the command will retrieve and display the appropriate list of filter rules currently installed and in use by the kernel.</para> <para><command>ipfstat -in</command> displays the inbound internal rules table with rule numbers.</para> <para><command>ipfstat -on</command> displays the outbound internal rules table with rule numbers.</para> <para>The output will look something like this:</para> <screen>@1 pass out on xl0 from any to any @2 block out on dc0 from any to any @3 pass out quick on dc0 proto tcp/udp from any to any keep state</screen> <para><command>ipfstat -ih</command> displays the inbound internal rules table, prefixing each rule with a count of how many times the rule was matched.</para> <para><command>ipfstat -oh</command> displays the outbound internal rules table, prefixing each rule with a count of how many times the rule was matched.</para> <para>The output will look something like this:</para> <screen>2451423 pass out on xl0 from any to any 354727 block out on dc0 from any to any 430918 pass out quick on dc0 proto tcp/udp from any to any keep state</screen> <para>One of the most important options of <command>ipfstat</command> is <option>-t</option> which displays the state table in a way similar to how &man.top.1; shows the &os; running process table. When a firewall is under attack, this function provides the ability to identify and see the attacking packets. The optional sub-flags give the ability to select the destination or source IP, port, or protocol to be monitored in real time. Refer to &man.ipfstat.8; for details.</para> </sect2> <sect2> <title>IPMON</title> <indexterm><primary><command>ipmon</command></primary></indexterm> <indexterm> <primary>IPFILTER</primary> <secondary>logging</secondary> </indexterm> <para>In order for <command>ipmon</command> to work properly, the kernel option <literal>IPFILTER_LOG</literal> must be turned on. This command has two different modes. Native mode is the default mode when the command is used without <option>-D</option>.</para> <para>Daemon mode provides a continuous system log file so that logging of past events may be reviewed. &os; has a built in facility to automatically rotate system logs. This is why outputting the log information to &man.syslogd.8; is better than the default of outputting to a regular file. The default <filename>rc.conf</filename> <literal>ipmon_flags</literal> statement uses <option>-Ds</option>:</para> <programlisting>ipmon_flags="-Ds" # D = start as daemon # s = log to syslog # v = log tcp window, ack, seq # n = map IP & port to names</programlisting> <para>Logging provides the ability to review, after the fact, information such as which packets were dropped, what addresses they came from and where they were going. These can all provide a significant edge in tracking down attackers.</para> <para>Even with the logging facility enabled, IPF will not generate any rule logging by default. The firewall administrator decides which rules in the ruleset should be logged and adds the log keyword to those rules. Normally, only deny rules are logged.</para> <para>It is customary to include a <quote>default deny everything</quote> rule with the log keyword included as the last rule in the ruleset. This makes it possible to see all the packets that did not match any of the rules in the ruleset.</para> </sect2> <sect2> <title>IPMON Logging</title> <para>&man.syslogd.8; uses its own method for segregation of log data. It uses groupings called <quote>facility</quote> and <quote>level</quote>. By default, IPMON in <option>-Ds</option> mode uses <literal>local0</literal> as the <quote>facility</quote> name. The following levels can be used to further segregate the logged data:</para> <screen>LOG_INFO - packets logged using the "log" keyword as the action rather than pass or block. LOG_NOTICE - packets logged which are also passed LOG_WARNING - packets logged which are also blocked LOG_ERR - packets which have been logged and which can be considered short</screen> <!-- XXX: "can be considered short" == "with incomplete header" --> <para>In order to setup IPFILTER to log all data to <filename>/var/log/ipfilter.log</filename>, first create the empty file:</para> <screen>&prompt.root; <userinput>touch /var/log/ipfilter.log</userinput></screen> <para>&man.syslogd.8; is controlled by definition statements in <filename>/etc/syslog.conf</filename>. This file offers considerable flexibility in how <application>syslog</application> will deal with system messages issued by software applications like IPF.</para> <para>To write all logged messages to the specified file, add the following statement to <filename>/etc/syslog.conf</filename>:</para> <programlisting>local0.* /var/log/ipfilter.log</programlisting> <para>To activate the changes and instruct &man.syslogd.8; to read the modified <filename>/etc/syslog.conf</filename>, run <command>service syslogd reload</command>.</para> <para>Do not forget to change <filename>/etc/newsyslog.conf</filename> to rotate the new log file.</para> </sect2> <sect2> <title>The Format of Logged Messages</title> <para>Messages generated by <command>ipmon</command> consist of data fields separated by white space. Fields common to all messages are:</para> <orderedlist> <listitem> <para>The date of packet receipt.</para> </listitem> <listitem> <para>The time of packet receipt. This is in the form HH:MM:SS.F, for hours, minutes, seconds, and fractions of a second.</para> </listitem> <listitem> <para>The name of the interface that processed the packet.</para> </listitem> <listitem> <para>The group and rule number of the rule in the format <literal>@0:17</literal>.</para> </listitem> </orderedlist> <para>These can be viewed with <command>ipfstat -in</command>.</para> <orderedlist> <listitem> <para>The action: <literal>p</literal> for passed, <literal>b</literal> for blocked, <literal>S</literal> for a short packet, <literal>n</literal> did not match any rules, and <literal>L</literal> for a log rule. The order of precedence in showing flags is: <literal>S</literal>, <literal>p</literal>, <literal>b</literal>, <literal>n</literal>, <literal>L</literal>. A capital <literal>P</literal> or <literal>B</literal> means that the packet has been logged due to a global logging setting, not a particular rule.</para> </listitem> <listitem> <para>The addresses written as three fields: the source address and port separated by a comma, the -> symbol, and the destination address and port. For example: <literal>209.53.17.22,80 -> 198.73.220.17,1722</literal>.</para> </listitem> <listitem> <para><literal>PR</literal> followed by the protocol name or number: for example, <literal>PR tcp</literal>.</para> </listitem> <listitem> <para><literal>len</literal> followed by the header length and total length of the packet: for example, <literal>len 20 40</literal>.</para> </listitem> </orderedlist> <para>If the packet is a <acronym>TCP</acronym> packet, there will be an additional field starting with a hyphen followed by letters corresponding to any flags that were set. Refer to &man.ipf.5; for a list of letters and their flags.</para> <para>If the packet is an ICMP packet, there will be two fields at the end: the first always being <quote>ICMP</quote> and the next being the ICMP message and sub-message type, separated by a slash. For example: ICMP 3/3 for a port unreachable message.</para> </sect2> <sect2 id="firewalls-ipf-rules-script"> <title>Building the Rule Script with Symbolic Substitution</title> <para>Some experienced IPF users create a file containing the rules and code them in a manner compatible with running them as a script with symbolic substitution. The major benefit of doing this is that only the value associated with the symbolic name needs to be changed, and when the script is run all the rules containing the symbolic name will have the value substituted in the rules. Being a script, symbolic substitution can be used to code frequently used values and substitute them in multiple rules. This can be seen in the following example.</para> <para>The script syntax used here is compatible with the &man.sh.1;, &man.csh.1;, and &man.tcsh.1; shells.</para> <para>Symbolic substitution fields are prefixed with a <literal>$</literal>.</para> <para>Symbolic fields do not have the $ prefix.</para> <para>The value to populate the symbolic field must be enclosed between double quotes (<literal>"</literal>).</para> <para>Start the rule file with something like this:</para> <programlisting>############# Start of IPF rules script ######################## oif="dc0" # name of the outbound interface odns="192.0.2.11" # ISP's DNS server IP address myip="192.0.2.7" # my static IP address from ISP ks="keep state" fks="flags S keep state" # You can choose between building /etc/ipf.rules file # from this script or running this script "as is". # # Uncomment only one line and comment out another. # # 1) This can be used for building /etc/ipf.rules: #cat > /etc/ipf.rules << EOF # # 2) This can be used to run script "as is": /sbin/ipf -Fa -f - << EOF # Allow out access to my ISP's Domain name server. pass out quick on $oif proto tcp from any to $odns port = 53 $fks pass out quick on $oif proto udp from any to $odns port = 53 $ks # Allow out non-secure standard www function pass out quick on $oif proto tcp from $myip to any port = 80 $fks # Allow out secure www function https over TLS SSL pass out quick on $oif proto tcp from $myip to any port = 443 $fks EOF ################## End of IPF rules script ########################</programlisting> <para>The rules are not important in this example as it instead focuses on how the symbolic substitution fields are populated. If this example was in a file named <filename>/etc/ipf.rules.script</filename>, these rules could be reloaded by running:</para> <screen>&prompt.root; <userinput>sh /etc/ipf.rules.script</userinput></screen> <para>There is one problem with using a rules file with embedded symbolics: IPF does not understand symbolic substitution, and cannot read such scripts directly.</para> <para>This script can be used in one of two ways:</para> <itemizedlist> <listitem> <para>Uncomment the line that begins with <literal>cat</literal>, and comment out the line that begins with <literal>/sbin/ipf</literal>. Place <literal>ipfilter_enable="YES"</literal> into <filename>/etc/rc.conf</filename>, and run the script once after each modification to create or update <filename>/etc/ipf.rules</filename>.</para> </listitem> <listitem> <para>Disable IPFILTER in the system startup scripts by adding <literal>ipfilter_enable="NO"</literal>to <filename>/etc/rc.conf</filename>.</para> <para>Then, add a script like the following to <filename class="directory">/usr/local/etc/rc.d/</filename>. The script should have an obvious name like <filename>ipf.loadrules.sh</filename>, where the <filename>.sh</filename> extension is mandatory.</para> <programlisting>#!/bin/sh sh /etc/ipf.rules.script</programlisting> <para>The permissions on this script file must be read, write, execute for owner <username>root</username>:</para> <screen>&prompt.root; <userinput>chmod 700 /usr/local/etc/rc.d/ipf.loadrules.sh</userinput></screen> </listitem> </itemizedlist> <para>Now, when the system boots, the IPF rules will be loaded.</para> </sect2> <sect2> <title>IPF Rulesets</title> <para>A ruleset contains a group of IPF rules which pass or block packets based on the values contained in the packet. The bi-directional exchange of packets between hosts comprises a session conversation. The firewall ruleset processes both the packets arriving from the public Internet, as well as the packets produced by the system as a response to them. Each <acronym>TCP/IP</acronym> service is predefined by its protocol and listening port. Packets destined for a specific service originate from the source address using an unprivileged port and target the specific service port on the destination address. All the above parameters can be used as selection criteria to create rules which will pass or block services.</para> <indexterm> <primary>IPFILTER</primary> <secondary>rule processing order</secondary> </indexterm> <warning> <para>When working with the firewall rules, be <emphasis>very careful</emphasis>. Some configurations <emphasis>can lock the administrator out</emphasis> of the server. To be on the safe side, consider performing the initial firewall configuration from the local console rather than doing it remotely over <application>ssh</application>.</para> </warning> </sect2> <sect2> <title>Rule Syntax</title> <indexterm> <primary>IPFILTER</primary> <secondary>rule syntax</secondary> </indexterm> <para>The rule syntax presented here has been simplified to only address the modern stateful rule context and <quote>first matching rule wins</quote> logic. For the complete legacy rule syntax, refer to &man.ipf.8;.</para> <para>A <literal>#</literal> character is used to mark the start of a comment and may appear at the end of a rule line or on its own line. Blank lines are ignored.</para> <para>Rules contain keywords which must be written in a specific order from left to right on the line. Keywords are identified in bold type. Some keywords have sub-options which may be keywords themselves and also include more sub-options. Each of the headings in the below syntax has a bold section header which expands on the content.</para> <!-- This section is probably wrong. See the OpenBSD flag --> <!-- What is the "OpenBSD flag"? Reference please --> <para><replaceable>ACTION IN-OUT OPTIONS SELECTION STATEFUL PROTO SRC_ADDR,DST_ADDR OBJECT PORT_NUM TCP_FLAG STATEFUL</replaceable></para> <para><replaceable>ACTION</replaceable> = block | pass</para> <para><replaceable>IN-OUT</replaceable> = in | out</para> <para><replaceable>OPTIONS</replaceable> = log | quick | on interface-name</para> <para><replaceable>SELECTION</replaceable> = proto value | source/destination IP | port = number | flags flag-value</para> <para><replaceable>PROTO</replaceable> = tcp/udp | udp | tcp | icmp</para> <para><replaceable>SRC_ADD,DST_ADDR</replaceable> = all | from object to object</para> <para><replaceable>OBJECT</replaceable> = IP address | any</para> <para><replaceable>PORT_NUM</replaceable> = port number</para> <para><replaceable>TCP_FLAG</replaceable> = S</para> <para><replaceable>STATEFUL</replaceable> = keep state</para> <sect3> <title>ACTION</title> <para>The action keyword indicates what to do with the packet if it matches the rest of the filter rule. Each rule <emphasis>must</emphasis> have an action. The following actions are recognized:</para> <para><literal>block</literal> indicates that the packet should be dropped if the selection parameters match the packet.</para> <para><literal>pass</literal> indicates that the packet should exit the firewall if the selection parameters match the packet.</para> </sect3> <sect3> <title>IN-OUT</title> <para>A mandatory requirement is that each filter rule explicitly state which side of the I/O it is to be used on. The next keyword must be either <literal>in</literal> or <literal>out</literal> and one or the other has to be included or the rule will not pass syntax checks.</para> <para><literal>in</literal> means this rule is being applied against an inbound packet which has just been received on the interface facing the public Internet.</para> <para><literal>out</literal> means this rule is being applied against an outbound packet destined for the interface facing the public Internet.</para> </sect3> <sect3> <title>OPTIONS</title> <note> <para>These options must be used in the order shown here.</para> </note> <para><literal>log</literal> indicates that the packet header will be written to the &man.ipl.4; packet log pseudo-device if the selection parameters match the packet.</para> <para><literal>quick</literal> indicates that if the selection parameters match the packet, this rule will be the last rule checked, and no further processing of any following rules will occur for this packet.</para> <para><literal>on</literal> indicates the interface name to be incorporated into the selection parameters. Interface names are as displayed by &man.ifconfig.8;. Using this option, the rule will only match if the packet is going through that interface in the specified direction.</para> <para>When a packet is logged, the headers of the packet are written to the &man.ipl.4; packet logging pseudo-device. Immediately following the <literal>log</literal> keyword, the following qualifiers may be used in this order:</para> <para><literal>body</literal> indicates that the first 128 bytes of the packet contents will be logged after the headers.</para> <para><literal>first</literal>. If the <literal>log</literal> keyword is being used in conjunction with a <literal>keep state</literal> option, this option is recommended so that only the triggering packet is logged and not every packet which matches the stateful connection.</para> </sect3> <sect3> <title>SELECTION</title> <para>The keywords described in this section are used to describe attributes of the packet to be checked when determining whether or not rules match. There is a keyword subject, and it has sub-option keywords, one of which has to be selected. The following general-purpose attributes are provided for matching, and must be used in this order:</para> </sect3> <sect3> <title>PROTO</title> <para><literal>proto</literal> is the subject keyword which must include one of its corresponding keyword sub-option values. The sub-option indicates a specific protocol to be matched against.</para> <para><literal>tcp/udp | udp | tcp | icmp</literal> or any protocol names found in <filename>/etc/protocols</filename> are recognized and may be used. The special protocol keyword <literal>tcp/udp</literal> may be used to match either a <acronym>TCP</acronym> or a <acronym>UDP</acronym> packet, and has been added as a convenience to save duplication of otherwise identical rules.</para> </sect3> <sect3> <title>SRC_ADDR/DST_ADDR</title> <para>The <literal>all</literal> keyword is equivalent to <quote>from any to any</quote> with no other match parameters.</para> <para><literal>from | to src to dst</literal>: the <literal>from</literal> and <literal>to</literal> keywords are used to match against IP addresses. Rules must specify <emphasis>both</emphasis> the source and destination parameters. <literal>any</literal> is a special keyword that matches any IP address. Examples include: <literal>from any to any</literal>, <literal>from 0.0.0.0/0 to any</literal>, <literal>from any to 0.0.0.0/0</literal>, <literal>from 0.0.0.0 to any</literal>, and <literal>from any to 0.0.0.0</literal>.</para> <para>There is no way to match ranges of IP addresses which do not express themselves easily using the dotted numeric form / mask-length notation. The <filename role="package">net-mgmt/ipcalc</filename> port may be used to ease the calculation. Additional information is available at the utility's web page: <ulink url="http://jodies.de/ipcalc"></ulink>.</para> </sect3> <sect3> <title>PORT</title> <para>If a port match is included, for either or both of source and destination, it is only applied to <acronym>TCP</acronym> and <acronym>UDP</acronym> packets. When composing port comparisons, either the service name from <filename>/etc/services</filename> or an integer port number may be used. When the port appears as part of the <literal>from</literal> object, it matches the source port number. When it appears as part of the <literal>to</literal> object, it matches the destination port number. An example usage is <literal>from any to any port = 80</literal></para> <para>Single port comparisons may be done in a number of ways, using a number of different comparison operators. Instead of the <literal>=</literal> shown in the example above, the following operators may be used: <literal>!=</literal>, <literal><</literal>, <literal>></literal>, <literal><=</literal>, <literal>>=</literal>, <literal>eq</literal>, <literal>ne</literal>, <literal>lt</literal>, <literal>gt</literal>, <literal>le</literal>, and <literal>ge</literal>.</para> <para>To specify port ranges, place the two port numbers between <literal><></literal> or <literal>><</literal></para> </sect3> <sect3> <title><acronym>TCP</acronym>_FLAG</title> <para>Flags are only effective for <acronym>TCP</acronym> filtering. The letters represent one of the possible flags that can be matched against the <acronym>TCP</acronym> packet header.</para> <para>The modernized rules processing logic uses the <literal>flags S</literal> parameter to identify the TCP session start request.</para> </sect3> <sect3> <title>STATEFUL</title> <para><literal>keep state</literal> indicates that on a pass rule, any packets that match the rules selection parameters should activate the stateful filtering facility.</para> </sect3> </sect2> <sect2> <title>Stateful Filtering</title> <indexterm> <primary>IPFILTER</primary> <secondary>stateful filtering</secondary> </indexterm> <!-- XXX: duplicated --> <para>Stateful filtering treats traffic as a bi-directional exchange of packets comprising a session. When activated, <literal>keep-state</literal> dynamically generates internal rules for each anticipated packet being exchanged during the session. It has sufficient matching capabilities to determine if a packet is valid for a session. Any packets that do not properly fit the session template are automatically rejected.</para> <para>IPF stateful filtering will also allow <acronym>ICMP</acronym> packets related to an existing <acronym>TCP</acronym> or <acronym>UDP</acronym> session. So, if an <acronym>ICMP</acronym> type 3 code 4 packet is a response in a session started by a keep state rule, it will automatically be allowed. Any packet that IPF can be certain is part of an active session, even if it is a different protocol, will be allowed.</para> <para>Packets destined to go out through the interface connected to the public Internet are first checked against the dynamic state table. If the packet matches the next expected packet comprising an active session conversation, it exits the firewall and the state of the session conversation flow is updated in the dynamic state table. Packets that do not belong to an already active session, are checked against the outbound ruleset.</para> <para>Packets coming in from the interface connected to the public Internet are first checked against the dynamic state table. If the packet matches the next expected packet comprising an active session, it exits the firewall and the state of the session conversation flow is updated in the dynamic state table. Packets that do not belong to an already active session, are checked against the inbound ruleset.</para> <para>When the session completes, it is removed from the dynamic state table.</para> <para>Stateful filtering allows one to focus on blocking/passing new sessions. If the new session is passed, all its subsequent packets are allowed automatically and any impostor packets are automatically rejected. If a new session is blocked, none of its subsequent packets are allowed. Stateful filtering provides advanced matching abilities capable of defending against the flood of different attack methods employed by attackers.</para> </sect2> <sect2> <!-- XXX: This section needs a rewrite --> <title>Inclusive Ruleset Example</title> <para>The following ruleset is an example of an inclusive type of firewall which only allows services matching <literal>pass</literal> rules and blocks all others by default. Network firewalls intended to protect other machines should have at least two interfaces, and are generally configured to trust the <acronym>LAN</acronym> and to not trust the public Internet. Alternatively, a host based firewall might be configured to protect only the system it is running on, and is appropriate for servers on an untrusted network or a desktop system not protected by firewall on the network.</para> <para>&os; uses interface <devicename>lo0</devicename> and IP address <hostid role="ipaddr">127.0.0.1</hostid> for internal communication within the operating system. The firewall rules must contain rules to allow free movement of these internally used packets.</para> <para>The interface which faces the public Internet is the one specified in the rules that authorize and control access of the outbound and inbound connections.</para> <para>In cases where one or more NICs are cabled to private network segments, those interfaces may require rules to allow packets originating from those LAN interfaces transit to each other or to the Internet.</para> <para>The rules should be organized into three major sections: the trusted interfaces, then the public interface outbound, and lastly, the public untrusted interface inbound.</para> <para>The rules in each of the public interface sections should have the most frequently matched rules placed before less commonly matched rules, with the last rule in the section blocking and logging all packets on that interface and direction.</para> <para>The outbound section in the following ruleset only contains <literal>pass</literal> rules which uniquely identify the services that are authorized for public Internet access. All the rules use <literal>quick</literal>, <literal>on</literal>, <literal>proto</literal>, <literal>port</literal>, and <literal>keep state</literal>. The <literal>proto tcp</literal> rules include <literal>flag</literal> to identify the session start request as the triggering packet to activate the stateful facility.</para> <para>The inbound section blocks undesirable packets first, for two different reasons. The first is that malicious packets may be partial matches for legitimate traffic. These packets have to be discarded rather than allowed, based on their partial matches against the <literal>allow</literal> rules. The second reason is that known and uninteresting rejects may be blocked silently, rather than being logged by the last rule in the section.</para> <para>The ruleset should ensure that there is no response returned for any undesirable traffic. Invalid packets should be silently dropped so that the attacker has no knowledge if the packets reached the system. Rules that include a <literal>log first</literal> option, will only log the event the first time they are triggered. This option is included in the sample <literal>nmap OS fingerprint</literal> rule. The <filename role="package">security/nmap</filename> utility is commonly used by attackers who attempt to identify the operating system of the server.</para> <para>Any time there are logged messages on a rule with the <literal>log first</literal> option, <command>ipfstat -hio</command> should be executed to evaluate how many times the rule has been matched. A large number of matches usually indicates that the system is being flooded or is under attack.</para> <para>To lookup unknown port numbers, refer to <filename>/etc/services</filename>. Alternatively, visit <ulink url="http://en.wikipedia.org/wiki/List_of_TCP_and_UDP_port_numbers"></ulink> and do a port number lookup to find the purpose of a particular port number.</para> <para>Check out this link for port numbers used by Trojans <ulink url="http://www.sans.org/security-resources/idfaq/oddports.php"></ulink>.</para> <para>The following ruleset creates an <literal>inclusive</literal> firewall ruleset which can be easily customized by commenting out <literal>pass</literal> rules for services that should not be authorized.</para> <para>To avoid logging unwanted messages, add a <literal>block</literal> rule in the inbound section.</para> <para>Change the <devicename>dc0</devicename> interface name in every rule to the interface name that connects the system to the public Internet.</para> <para>The following statements were added to <filename>/etc/ipf.rules</filename>:</para> <programlisting>################################################################# # No restrictions on Inside LAN Interface for private network # Not needed unless you have LAN ################################################################# #pass out quick on xl0 all #pass in quick on xl0 all ################################################################# # No restrictions on Loopback Interface ################################################################# pass in quick on lo0 all pass out quick on lo0 all ################################################################# # Interface facing Public Internet (Outbound Section) # Match session start requests originating from behind the # firewall on the private network # or from this gateway server destined for the public Internet. ################################################################# # Allow out access to my ISP's Domain name server. # xxx must be the IP address of your ISP's DNS. # Dup these lines if your ISP has more than one DNS server # Get the IP addresses from /etc/resolv.conf file pass out quick on dc0 proto tcp from any to xxx port = 53 flags S keep state pass out quick on dc0 proto udp from any to xxx port = 53 keep state # Allow out access to my ISP's DHCP server for cable or DSL networks. # This rule is not needed for 'user ppp' type connection to the # public Internet, so you can delete this whole group. # Use the following rule and check log for IP address. # Then put IP address in commented out rule & delete first rule pass out log quick on dc0 proto udp from any to any port = 67 keep state #pass out quick on dc0 proto udp from any to z.z.z.z port = 67 keep state # Allow out non-secure standard www function pass out quick on dc0 proto tcp from any to any port = 80 flags S keep state # Allow out secure www function https over TLS SSL pass out quick on dc0 proto tcp from any to any port = 443 flags S keep state # Allow out send & get email function pass out quick on dc0 proto tcp from any to any port = 110 flags S keep state pass out quick on dc0 proto tcp from any to any port = 25 flags S keep state # Allow out Time pass out quick on dc0 proto tcp from any to any port = 37 flags S keep state # Allow out nntp news pass out quick on dc0 proto tcp from any to any port = 119 flags S keep state # Allow out gateway & LAN users' non-secure FTP ( both passive & active modes) # This function uses the IP<acronym>NAT</acronym> built in FTP proxy function coded in # the nat rules file to make this single rule function correctly. # If you want to use the pkg_add command to install application packages # on your gateway system you need this rule. pass out quick on dc0 proto tcp from any to any port = 21 flags S keep state # Allow out ssh/sftp/scp (telnet/rlogin/FTP replacements) # This function is using SSH (secure shell) pass out quick on dc0 proto tcp from any to any port = 22 flags S keep state # Allow out insecure Telnet pass out quick on dc0 proto tcp from any to any port = 23 flags S keep state # Allow out FreeBSD CVSup pass out quick on dc0 proto tcp from any to any port = 5999 flags S keep state # Allow out ping to public Internet pass out quick on dc0 proto icmp from any to any icmp-type 8 keep state # Allow out whois from LAN to public Internet pass out quick on dc0 proto tcp from any to any port = 43 flags S keep state # Block and log only the first occurrence of everything # else that's trying to get out. # This rule implements the default block block out log first quick on dc0 all ################################################################# # Interface facing Public Internet (Inbound Section) # Match packets originating from the public Internet # destined for this gateway server or the private network. ################################################################# # Block all inbound traffic from non-routable or reserved address spaces block in quick on dc0 from 192.168.0.0/16 to any #RFC 1918 private IP block in quick on dc0 from 172.16.0.0/12 to any #RFC 1918 private IP block in quick on dc0 from 10.0.0.0/8 to any #RFC 1918 private IP block in quick on dc0 from 127.0.0.0/8 to any #loopback block in quick on dc0 from 0.0.0.0/8 to any #loopback block in quick on dc0 from 169.254.0.0/16 to any #DHCP auto-config block in quick on dc0 from 192.0.2.0/24 to any #reserved for docs block in quick on dc0 from 204.152.64.0/23 to any #Sun cluster interconnect block in quick on dc0 from 224.0.0.0/3 to any #Class D & E multicast ##### Block a bunch of different nasty things. ############ # That I do not want to see in the log # Block frags block in quick on dc0 all with frags # Block short tcp packets block in quick on dc0 proto tcp all with short # block source routed packets block in quick on dc0 all with opt lsrr block in quick on dc0 all with opt ssrr # Block nmap OS fingerprint attempts # Log first occurrence of these so I can get their IP address block in log first quick on dc0 proto tcp from any to any flags FUP # Block anything with special options block in quick on dc0 all with ipopts # Block public pings block in quick on dc0 proto icmp all icmp-type 8 # Block ident block in quick on dc0 proto tcp from any to any port = 113 # Block all Netbios service. 137=name, 138=datagram, 139=session # Netbios is MS/Windows sharing services. # Block MS/Windows hosts2 name server requests 81 block in log first quick on dc0 proto tcp/udp from any to any port = 137 block in log first quick on dc0 proto tcp/udp from any to any port = 138 block in log first quick on dc0 proto tcp/udp from any to any port = 139 block in log first quick on dc0 proto tcp/udp from any to any port = 81 # Allow traffic in from ISP's DHCP server. This rule must contain # the IP address of your ISP's DHCP server as it is the only # authorized source to send this packet type. Only necessary for # cable or DSL configurations. This rule is not needed for # 'user ppp' type connection to the public Internet. # This is the same IP address you captured and # used in the outbound section. pass in quick on dc0 proto udp from z.z.z.z to any port = 68 keep state # Allow in standard www function because I have apache server pass in quick on dc0 proto tcp from any to any port = 80 flags S keep state # Allow in non-secure Telnet session from public Internet # labeled non-secure because ID/PW passed over public Internet as clear text. # Delete this sample group if you do not have telnet server enabled. #pass in quick on dc0 proto tcp from any to any port = 23 flags S keep state # Allow in secure FTP, Telnet, and SCP from public Internet # This function is using SSH (secure shell) pass in quick on dc0 proto tcp from any to any port = 22 flags S keep state # Block and log only first occurrence of all remaining traffic # coming into the firewall. The logging of only the first # occurrence avoids filling up disk with Denial of Service logs. # This rule implements the default block. block in log first quick on dc0 all ################### End of rules file #####################################</programlisting> </sect2> <sect2> <title><acronym>NAT</acronym></title> <indexterm><primary>NAT</primary></indexterm> <indexterm> <primary>IP masquerading</primary> <see>NAT</see> </indexterm> <indexterm> <primary>network address translation</primary> <see>NAT</see> </indexterm> <para><acronym>NAT</acronym> stands for <emphasis>Network Address Translation</emphasis>. In &linux;, NAT is called <quote>IP Masquerading</quote>. The IPF <acronym>NAT</acronym> function enables the private LAN behind the firewall to share a single ISP-assigned IP address, even if that address is dynamically assigned. NAT allows each computer in the LAN to have Internet access, without having to pay the ISP for multiple Internet accounts or IP addresses.</para> <para><acronym>NAT</acronym> will automatically translate the private LAN IP address for each system on the LAN to the single public IP address as packets exit the firewall bound for the public Internet. It also performs the reverse translation for returning packets.</para> <para>According to RFC 1918, the following IP address ranges are reserved for private networks which will never be routed directly to the public Internet, and therefore are available for use with NAT:</para> <itemizedlist> <listitem> <para><literal>10.0.0.0/8</literal>.</para> </listitem> <listitem> <para><literal>172.16.0.0/12</literal>.</para> </listitem> <listitem> <para><literal>192.168.0.0/16</literal>.</para> </listitem> </itemizedlist> </sect2> <sect2> <title>IP<acronym>NAT</acronym></title> <indexterm> <primary>NAT</primary> <secondary>and IPFILTER</secondary> </indexterm> <indexterm><primary><command>ipnat</command></primary></indexterm> <para><acronym>NAT</acronym> rules are loaded using <command>ipnat</command>. Typically, the <acronym>NAT</acronym> rules are stored in <filename>/etc/ipnat.rules</filename>. See &man.ipnat.8; for details.</para> <para>When the file containing the <acronym>NAT</acronym> rules is edited after <acronym>NAT</acronym> has been started, run <command>ipnat</command> with <option>-CF</option> to delete the internal in use <acronym>NAT</acronym> rules and flush the contents of the translation table of all active entries.</para> <para>To reload the <acronym>NAT</acronym> rules, issue a command like this:</para> <screen>&prompt.root; <userinput>ipnat -CF -f /etc/ipnat.rules</userinput></screen> <para>To display some <acronym>NAT</acronym> statistics, use this command:</para> <screen>&prompt.root; <userinput>ipnat -s</userinput></screen> <para>To list the <acronym>NAT</acronym> table's current mappings, use this command:</para> <screen>&prompt.root; <userinput>ipnat -l</userinput></screen> <para>To turn verbose mode on and display information relating to rule processing and active rules/table entries:</para> <screen>&prompt.root; <userinput>ipnat -v</userinput></screen> </sect2> <sect2> <title>IP<acronym>NAT</acronym> Rules</title> <para><acronym>NAT</acronym> rules are flexible and can accomplish many different things to fit the needs of commercial and home users.</para> <para>The rule syntax presented here has been simplified to what is most commonly used in a non-commercial environment. For a complete rule syntax description, refer to &man.ipnat.5;.</para> <para>The syntax for a <acronym>NAT</acronym> rule looks like this:</para> <programlisting>map <replaceable>IF</replaceable> <replaceable>LAN_IP_RANGE</replaceable> -> <replaceable>PUBLIC_ADDRESS</replaceable></programlisting> <para>The keyword <literal>map</literal> starts the rule.</para> <para>Replace <replaceable>IF</replaceable> with the external interface.</para> <para>The <replaceable>LAN_IP_RANGE</replaceable> is used by the internal clients use for IP Addressing. Usually, this is something like <hostid role="ipaddr">192.168.1.0/24</hostid>.</para> <para>The <replaceable>PUBLIC_ADDRESS</replaceable> can either be the static external IP address or the special keyword <literal>0/32</literal> which uses the IP address assigned to <replaceable>IF</replaceable>.</para> </sect2> <sect2> <title>How <acronym>NAT</acronym> Works</title> <para>In IPF, when a packet arrives at the firewall from the LAN with a public destination, it passes through the outbound filter rules. <acronym>NAT</acronym> gets its turn at the packet and applies its rules top down, where the first matching rule wins. <acronym>NAT</acronym> tests each of its rules against the packet's interface name and source IP address. When a packet's interface name matches a <acronym>NAT</acronym> rule, the packet's source IP address in the private LAN is checked to see if it falls within the IP address range specified to the left of the arrow symbol on the <acronym>NAT</acronym> rule. On a match, the packet has its source IP address rewritten with the public IP address obtained by the <literal>0/32</literal> keyword. <acronym>NAT</acronym> posts an entry in its internal <acronym>NAT</acronym> table so when the packet returns from the public Internet it can be mapped back to its original private IP address and then passed to the filter rules for processing.</para> </sect2> <sect2> <title>Enabling IP<acronym>NAT</acronym></title> <para>To enable IP<acronym>NAT</acronym>, add these statements to <filename>/etc/rc.conf</filename>.</para> <para>To enable the machine to route traffic between interfaces:</para> <programlisting>gateway_enable="YES"</programlisting> <para>To start IP<acronym>NAT</acronym> automatically each time:</para> <programlisting>ipnat_enable="YES"</programlisting> <para>To specify where to load the IP<acronym>NAT</acronym> rules from:</para> <programlisting>ipnat_rules="/etc/ipnat.rules"</programlisting> </sect2> <sect2> <title><acronym>NAT</acronym> for a Large LAN</title> <para>For networks that have large numbers of systems on the LAN or networks with more than a single LAN, the process of funneling all those private IP addresses into a single public IP address becomes a resource problem that may cause problems with the same port numbers being used many times across many connections, causing collisions. There are two ways to relieve this resource problem.</para> <sect3> <title>Assigning Ports to Use</title> <para>A normal NAT rule would look like:</para> <programlisting>map dc0 192.168.1.0/24 -> 0/32</programlisting> <para>In the above rule, the packet's source port is unchanged as the packet passes through IP<acronym>NAT</acronym>. By adding the <literal>portmap</literal> keyword, IP<acronym>NAT</acronym> can be directed to only use source ports in the specified range. For example, the following rule will tell IP<acronym>NAT</acronym> to modify the source port to be within the range shown:</para> <programlisting>map dc0 192.168.1.0/24 -> 0/32 portmap tcp/udp 20000:60000</programlisting> <para>Additionally, the <literal>auto</literal> keyword tells IP<acronym>NAT</acronym> to determine which ports are available for use:</para> <programlisting>map dc0 192.168.1.0/24 -> 0/32 portmap tcp/udp auto</programlisting> </sect3> <sect3> <title>Using a Pool of Public Addresses</title> <para>In very large LANs there comes a point where there are just too many LAN addresses to fit into a single public address. If a block of public IP addresses is available, these addresses can be used as a <quote>pool</quote>, and IP<acronym>NAT</acronym> may pick one of the public IP addresses as packet addresses are mapped on their way out.</para> <para>For example, instead of mapping all packets through a single public IP address:</para> <programlisting>map dc0 192.168.1.0/24 -> 204.134.75.1</programlisting> <para>A range of public IP addresses can be specified either with a netmask:</para> <programlisting>map dc0 192.168.1.0/24 -> 204.134.75.0/255.255.255.0</programlisting> <para>or using CIDR notation:</para> <programlisting>map dc0 192.168.1.0/24 -> 204.134.75.0/24</programlisting> </sect3> </sect2> <sect2> <title>Port Redirection</title> <para>A common practice is to have a web server, email server, database server, and DNS server each segregated to a different system on the LAN. In this case, the traffic from these servers still has to undergo <acronym>NAT</acronym>, but there has to be some way to direct the inbound traffic to the correct server. For example, a web server operating on LAN address <hostid role="ipaddr">10.0.10.25</hostid> and using a single public IP address of <hostid role="ipaddr">20.20.20.5</hostid>, would use this rule:</para> <programlisting>rdr dc0 20.20.20.5/32 port 80 -> 10.0.10.25 port 80</programlisting> <para>or:</para> <programlisting>rdr dc0 0.0.0.0/0 port 80 -> 10.0.10.25 port 80</programlisting> <para>For a LAN DNS server on a private address of <hostid role="ipaddr">10.0.10.33</hostid> that needs to receive public DNS requests:</para> <programlisting>rdr dc0 20.20.20.5/32 port 53 -> 10.0.10.33 port 53 udp</programlisting> </sect2> <sect2> <title>FTP and <acronym>NAT</acronym></title> <para>FTP has two modes: active mode and passive mode. The difference is in how the data channel is acquired. Passive mode is more secure as the data channel is acquired by the ordinal ftp session requester. For a good explanation of FTP and the different modes, see <ulink url="http://www.slacksite.com/other/ftp.html"></ulink>.</para> <sect3> <title>IP<acronym>NAT</acronym> Rules</title> <para>IP<acronym>NAT</acronym> has a built in FTP proxy option which can be specified on the <acronym>NAT</acronym> map rule. It can monitor all outbound packet traffic for FTP active or passive start session requests and dynamically create temporary filter rules containing the port number being used by the data channel. This eliminates the security risk FTP normally exposes the firewall to as it no longer needs to open large ranges of high order ports for FTP connections.</para> <para>This rule will handle all the traffic for the internal LAN:</para> <programlisting>map dc0 10.0.10.0/29 -> 0/32 proxy port 21 ftp/tcp</programlisting> <para>This rule handles the FTP traffic from the gateway:</para> <programlisting>map dc0 0.0.0.0/0 -> 0/32 proxy port 21 ftp/tcp</programlisting> <para>This rule handles all non-FTP traffic from the internal LAN:</para> <programlisting>map dc0 10.0.10.0/29 -> 0/32</programlisting> <para>The FTP <literal>map</literal> rules go before the <acronym>NAT</acronym> rule so that when a packet matches an FTP rule, the FTP proxy creates temporary filter rules to let the FTP session packets pass and undergo <acronym>NAT</acronym>. All LAN packets that are not FTP will not match the FTP rules but will undergo <acronym>NAT</acronym> if they match the third rule.</para> </sect3> <sect3> <title>IP<acronym>NAT</acronym> FTP Filter Rules</title> <para>Only one filter rule is needed for FTP if the <acronym>NAT</acronym> FTP proxy is used.</para> <para>Without the FTP proxy, the following three rules will be needed:</para> <programlisting># Allow out LAN PC client FTP to public Internet # Active and passive modes pass out quick on rl0 proto tcp from any to any port = 21 flags S keep state # Allow out passive mode data channel high order port numbers pass out quick on rl0 proto tcp from any to any port > 1024 flags S keep state # Active mode let data channel in from FTP server pass in quick on rl0 proto tcp from any to any port = 20 flags S keep state</programlisting> </sect3> </sect2> </sect1> <sect1 id="firewalls-ipfw"> <title>IPFW</title> <indexterm> <primary>firewall</primary> <secondary>IPFW</secondary> </indexterm> <para><acronym>IPFW</acronym> is a stateful firewall written for &os; which also provides a traffic shaper, packet scheduler, and in-kernel NAT.</para> <para>&os; provides a sample ruleset in <filename>/etc/rc.firewall</filename>. The sample ruleset define several firewall types for common scenarios to assist novice users in generating an appropriate ruleset. &man.ipfw.8; provides a powerful syntax which advanced users can use to craft customized rulesets that meet the security requirements of a given environment.</para> <para>IPFW is composed of several components: the kernel firewall filter rule processor and its integrated packet accounting facility, the logging facility, the <literal>divert</literal> rule which triggers <acronym>NAT</acronym>, the dummynet traffic shaper facilities, the <literal>fwd rule</literal> forward facility, the bridge facility, and the ipstealth facility. IPFW supports both IPv4 and IPv6.</para> <sect2 id="firewalls-ipfw-enable"> <title>Enabling IPFW</title> <indexterm> <primary>IPFW</primary> <secondary>enabling</secondary> </indexterm> <para>IPFW is included in the basic &os; install as a run time loadable module. The system will dynamically load the kernel module when <filename>rc.conf</filename> contains the statement <literal>firewall_enable="YES"</literal>. After rebooting the system, the following white highlighted message is displayed on the screen as part of the boot process:</para> <screen>ipfw2 initialized, divert disabled, rule-based forwarding disabled, default to deny, logging disabled</screen> <para>The loadable module includes logging ability. To enable logging and set the verbose logging limit, add these statements to <filename>/etc/sysctl.conf</filename> before rebooting:</para> <programlisting>net.inet.ip.fw.verbose=1 net.inet.ip.fw.verbose_limit=5</programlisting> </sect2> <sect2 id="firewalls-ipfw-kernel"> <title>Kernel Options</title> <indexterm> <primary>kernel options</primary> <secondary>IPFIREWALL</secondary> </indexterm> <indexterm> <primary>kernel options</primary> <secondary>IPFIREWALL_VERBOSE</secondary> </indexterm> <indexterm> <primary>kernel options</primary> <secondary>IPFIREWALL_VERBOSE_LIMIT</secondary> </indexterm> <indexterm> <primary>IPFW</primary> <secondary>kernel options</secondary> </indexterm> <para>For those users who wish to statically compile kernel IPFW support, the following options are available for the custom kernel configuration file:</para> <programlisting>options IPFIREWALL</programlisting> <para>This option enables IPFW as part of the kernel.</para> <programlisting>options IPFIREWALL_VERBOSE</programlisting> <para>This option enables logging of packets that pass through IPFW and have the <literal>log</literal> keyword specified in the ruleset.</para> <programlisting>options IPFIREWALL_VERBOSE_LIMIT=5</programlisting> <para>This option limits the number of packets logged through &man.syslogd.8;, on a per-entry basis. This option may be used in hostile environments, when firewall activity logging is desired. This will close a possible denial of service attack via syslog flooding.</para> <indexterm> <primary>kernel options</primary> <secondary>IPFIREWALL_DEFAULT_TO_ACCEPT</secondary> </indexterm> <programlisting>options IPFIREWALL_DEFAULT_TO_ACCEPT</programlisting> <para>This option allows everything to pass through the firewall by default, which is a good idea when the firewall is being set up for the first time.</para> <indexterm> <primary>kernel options</primary> <secondary>IPDIVERT</secondary> </indexterm> <programlisting>options IPDIVERT</programlisting> <para>This option enables the use of <acronym>NAT</acronym> functionality.</para> <note> <para>The firewall will block all incoming and outgoing packets if either the <literal>IPFIREWALL_DEFAULT_TO_ACCEPT</literal> kernel option or a rule to explicitly allow these connections is missing.</para> </note> </sect2> <sect2 id="firewalls-ipfw-rc"> <title><filename>/etc/rc.conf</filename> Options</title> <para>Enables the firewall:</para> <programlisting>firewall_enable="YES"</programlisting> <para>To select one of the default firewall types provided by &os;, select one by reading <filename>/etc/rc.firewall</filename> and specify it in the following:</para> <programlisting>firewall_type="open"</programlisting> <para>Available values for this setting are:</para> <itemizedlist> <listitem> <para><literal>open</literal>: passes all traffic.</para> </listitem> <listitem> <para><literal>client</literal>: protects only this machine.</para> </listitem> <listitem> <para><literal>simple</literal>: protects the whole network.</para> </listitem> <listitem> <para><literal>closed</literal>: entirely disables IP traffic except for the loopback interface.</para> </listitem> <listitem> <para><literal>UNKNOWN</literal>: disables the loading of firewall rules.</para> </listitem> <listitem> <para><filename><replaceable>filename</replaceable></filename>: absolute path of the file containing the firewall rules.</para> </listitem> </itemizedlist> <para>Two methods are available for loading custom <application>ipfw</application> rules. One is to set the <literal>firewall_type</literal> variable to the absolute path of the file which contains the firewall rules.</para> <para>The other method is to set the <literal>firewall_script</literal> variable to the absolute path of an executable script that includes <command>ipfw</command> commands. A ruleset script that blocks all incoming and outgoing traffic would look like this:</para> <programlisting>#!/bin/sh ipfw -q flush ipfw add deny in ipfw add deny out</programlisting> <note> <para>If <literal>firewall_type</literal> is set to either <literal>client</literal> or <literal>simple</literal>, modify the default rules found in <filename>/etc/rc.firewall</filename> to fit the configuration of the system. The examples used in this section assume that the <literal>firewall_script</literal> is set to <filename>/etc/ipfw.rules</filename>.</para> </note> <para>Enable logging:</para> <programlisting>firewall_logging="YES"</programlisting> <warning> <para><varname>firewall_logging</varname> sets the <varname>net.inet.ip.fw.verbose</varname> sysctl variable to the value of <literal>1</literal>. There is no <filename>rc.conf</filename> variable to set log limitations, but the desired value can be set using <command>sysctl</command> or by adding the following variable and desired value to <filename>/etc/sysctl.conf</filename>:</para> <programlisting>net.inet.ip.fw.verbose_limit=5</programlisting> </warning> <para>If the machine is acting as a gateway providing <acronym>NAT</acronym> using &man.natd.8;, refer to <xref linkend="network-natd"/> for information regarding the required <filename>/etc/rc.conf</filename> options.</para> </sect2> <sect2 id="firewalls-ipfw-cmd"> <title>The IPFW Command</title> <indexterm><primary><command>ipfw</command></primary></indexterm> <para><command>ipfw</command> can be used to make manual, single rule additions or deletions to the active firewall while it is running. The problem with using this method is that all the changes are lost when the system reboots. It is recommended to instead write all the rules in a file and to use that file to load the rules at boot time and to replace the currently running firewall rules whenever that file changes.</para> <para><command>ipfw</command> is a useful way to display the running firewall rules to the console screen. The IPFW accounting facility dynamically creates a counter for each rule that counts each packet that matches the rule. During the process of testing a rule, listing the rule with its counter is one way to determine if the rule is functioning as expected.</para> <para>To list all the running rules in sequence:</para> <screen>&prompt.root; <userinput>ipfw list</userinput></screen> <para>To list all the running rules with a time stamp of when the last time the rule was matched:</para> <screen>&prompt.root; <userinput>ipfw -t list</userinput></screen> <para>The next example lists accounting information and the packet count for matched rules along with the rules themselves. The first column is the rule number, followed by the number of matched packets and bytes, followed by the rule itself.</para> <screen>&prompt.root; <userinput>ipfw -a list</userinput></screen> <para>To list dynamic rules in addition to static rules:</para> <screen>&prompt.root; <userinput>ipfw -d list</userinput></screen> <para>To also show the expired dynamic rules:</para> <screen>&prompt.root; <userinput>ipfw -d -e list</userinput></screen> <para>To zero the counters:</para> <screen>&prompt.root; <userinput>ipfw zero</userinput></screen> <para>To zero the counters for just the rule with number <replaceable>NUM</replaceable>:</para> <screen>&prompt.root; <userinput>ipfw zero <replaceable>NUM</replaceable></userinput></screen> </sect2> <sect2 id="firewalls-ipfw-rules"> <title>IPFW Rulesets</title> <indexterm> <primary>IPFW</primary> <secondary>rule processing order</secondary> </indexterm> <para>When a packet enters the <acronym>IPFW</acronym> firewall, it is compared against the first rule in the ruleset and progresses one rule at a time, moving from top to bottom of the set in ascending rule number sequence order. When the packet matches the selection parameters of a rule, the rule's action field value is executed and the search of the ruleset terminates for that packet. This is referred to as <quote>first match wins</quote>. If the packet does not match any of the rules, it gets caught by the mandatory IPFW default rule, number 65535, which denies all packets and silently discards them. However, if the packet matches a rule that contains the <literal>count</literal>, <literal>skipto</literal>, or <literal>tee</literal> keywords, the search continues. Refer to &man.ipfw.8; for details on how these keywords affect rule processing.</para> <para>The examples in this section create an inclusive type firewall ruleset containing the stateful <literal>keep state</literal>, <literal>limit</literal>, <literal>in</literal>, <literal>out</literal> and <literal>via</literal> options. For a complete rule syntax description, refer to &man.ipfw.8;.</para> <warning> <para>Be careful when working with firewall rules, as it is easy to lock out even the administrator.</para> </warning> <sect3 id="firewalls-ipfw-rules-syntax"> <title>Rule Syntax</title> <indexterm> <primary>IPFW</primary> <secondary>rule syntax</secondary> </indexterm> <para>This section describes the keywords which comprise an <acronym>IPFW</acronym> rule. Keywords must be written in the following order. <literal>#</literal> is used to mark the start of a comment and may appear at the end of a rule line or on its own line. Blank lines are ignored.</para> <para><replaceable>CMD RULE_NUMBER ACTION LOGGING SELECTION STATEFUL</replaceable></para> <sect4> <title>CMD</title> <para>Each new rule has to be prefixed with <parameter>add</parameter> to add the rule to the internal table.</para> </sect4> <sect4> <title>RULE_NUMBER</title> <para>Each rule is associated with a rule_number in the range of <literal>1</literal> to <literal>65535</literal>.</para> </sect4> <sect4> <title>ACTION</title> <para>A rule can be associated with one of the following actions. The specified action will be executed when the packet matches the selection criterion of the rule.</para> <para><parameter>allow | accept | pass | permit</parameter></para> <para>These keywords are equivalent as they allow packets that match the rule to exit the firewall rule processing. The search terminates at this rule.</para> <para><parameter>check-state</parameter></para> <para>Checks the packet against the dynamic rules table. If a match is found, execute the action associated with the rule which generated this dynamic rule, otherwise move to the next rule. A <literal>check-state</literal> rule does not have selection criterion. If no <literal>check-state</literal> rule is present in the ruleset, the dynamic rules table is checked at the first <literal>keep-state</literal> or <literal>limit</literal> rule.</para> <para><parameter>deny | drop</parameter></para> <para>Both words mean the same thing, which is to discard packets that match this rule. The search terminates.</para> </sect4> <sect4> <title>Logging</title> <para>When a packet matches a rule with the <literal>log</literal> keyword, a message will be logged to &man.syslogd.8; with a facility name of <literal>SECURITY</literal>. Logging only occurs if the number of packets logged for that particular rule does not exceed the <literal>logamount</literal> parameter. If no <literal>logamount</literal> is specified, the limit is taken from the <command>sysctl</command> value of <varname>net.inet.ip.fw.verbose_limit</varname>. In both cases, a value of zero removes the logging limit. Once the limit is reached, logging can be re-enabled by clearing the logging counter or the packet counter for that rule, using <command>ipfw reset log</command>.</para> <note> <para>Logging is done after all other packet matching conditions have been met, and before performing the final action on the packet. The administrator decides which rules to enable logging on.</para> </note> </sect4> <sect4> <title>Selection</title> <para>The keywords described in this section are used to describe attributes of the packet to be checked when determining whether rules match the packet or not. The following general-purpose attributes are provided for matching, and must be used in this order:</para> <para><parameter>udp | tcp | icmp</parameter></para> <para>Any other protocol names found in <filename>/etc/protocols</filename> can be used. The value specified is the protocol to be matched against. This is a mandatory keyword.</para> <para><parameter>from src to dst</parameter></para> <para>The <literal>from</literal> and <literal>to</literal> keywords are used to match against IP addresses. Rules must specify <emphasis>both</emphasis> source and destination parameters. <literal>any</literal> is a special keyword that matches any IP address. <literal>me</literal> is a special keyword that matches any IP address configured on an interface in the &os; system to represent the PC the firewall is running on. Example usage includes <literal>from me to any</literal>, <literal>from any to me</literal>, <literal>from 0.0.0.0/0 to any</literal>, <literal>from any to 0.0.0.0/0</literal>, <literal>from 0.0.0.0 to any</literal>. <literal>from any to 0.0.0.0</literal>, and <literal>from me to 0.0.0.0</literal>. IP addresses are specified in dotted IP address format followed by the mask in CIDR notation, or as a single host in dotted IP address format. This keyword is a mandatory requirement. The <filename role="package">net-mgmt/ipcalc</filename> port may be used to assist the mask calculation.</para> <para><parameter>port number</parameter></para> <para>For protocols which support port numbers, such as <acronym>TCP</acronym> and <acronym>UDP</acronym>, it is mandatory to include the port number of the service that will be matched. Service names from <filename>/etc/services</filename> may be used instead of numeric port values.</para> <para><parameter>in | out</parameter></para> <para>Matches incoming or outgoing packets. It is mandatory that one or the other is included as part of the rule matching criterion.</para> <para><parameter>via IF</parameter></para> <para>Matches packets going through the interface specified by device name. The <literal>via</literal> keyword causes the interface to always be checked as part of the match process.</para> <para><parameter>setup</parameter></para> <para>This mandatory keyword identifies the session start request for <acronym>TCP</acronym> packets.</para> <para><parameter>keep-state</parameter></para> <para>This is a mandatory keyword. Upon a match, the firewall will create a dynamic rule, whose default behavior is to match bidirectional traffic between source and destination IP/port using the same protocol.</para> <para><parameter>limit {src-addr | src-port | dst-addr | dst-port}</parameter></para> <para>The firewall will only allow <replaceable>N</replaceable> connections with the same set of parameters as specified in the rule. One or more of source and destination addresses and ports can be specified. <literal>limit</literal> and <literal>keep-state</literal> can not be used on the same rule as they provide the same stateful function.</para> </sect4> </sect3> <sect3> <title>Stateful Rule Option</title> <indexterm> <primary>IPFW</primary> <secondary>stateful filtering</secondary> </indexterm> <para>The <literal>check-state</literal> option is used to identify where in the IPFW ruleset the packet is to be tested against the dynamic rules facility. On a match, the packet exits the firewall to continue on its way and a new rule is dynamically created for the next anticipated packet being exchanged during this session. On a no match, the packet advances to the next rule in the ruleset for testing.</para> <para>The dynamic rules facility is vulnerable to resource depletion from a SYN-flood attack which would open a huge number of dynamic rules. To counter this type of attack with <acronym>IPFW</acronym>, use <literal>limit</literal>. This keyword limits the number of simultaneous sessions by checking that rule's source or destinations fields and using the packet's IP address in a search of the open dynamic rules, counting the number of times this rule and IP address combination occurred. If this count is greater than the value specified by <literal>limit</literal>, the packet is discarded.</para> </sect3> <sect3> <title>Logging Firewall Messages</title> <indexterm> <primary>IPFW</primary> <secondary>logging</secondary> </indexterm> <para>Even with the logging facility enabled, IPFW will not generate any rule logging on its own. The firewall administrator decides which rules in the ruleset will be logged, and adds the <literal>log</literal> keyword to those rules. Normally only deny rules are logged. It is customary to duplicate the <quote>ipfw default deny everything</quote> rule with the <literal>log</literal> keyword included as the last rule in the ruleset. This way, it is possible to see all the packets that did not match any of the rules in the ruleset.</para> <para>Logging is a two edged sword. If one is not careful, an over abundance of log data or a DoS attack can fill the disk with log files. Log messages are not only written to <application>syslogd</application>, but also are displayed on the root console screen and soon become annoying.</para> <para>The <literal>IPFIREWALL_VERBOSE_LIMIT=5</literal> kernel option limits the number of consecutive messages sent to &man.syslogd.8;, concerning the packet matching of a given rule. When this option is enabled in the kernel, the number of consecutive messages concerning a particular rule is capped at the number specified. There is nothing to be gained from 200 identical log messages. With this option set to five, five consecutive messages concerning a particular rule would be logged to <application>syslogd</application> and the remainder identical consecutive messages would be counted and posted to <application>syslogd</application> with a phrase like the following:</para> <programlisting>last message repeated 45 times</programlisting> <para>All logged packets messages are written by default to <filename>/var/log/security</filename>, which is defined in <filename>/etc/syslog.conf</filename>.</para> </sect3> <sect3 id="firewalls-ipfw-rules-script"> <title>Building a Rule Script</title> <para>Most experienced IPFW users create a file containing the rules and code them in a manner compatible with running them as a script. The major benefit of doing this is the firewall rules can be refreshed in mass without the need of rebooting the system to activate them. This method is convenient in testing new rules as the procedure can be executed as many times as needed. Being a script, symbolic substitution can be used for frequently used values to be substituted into multiple rules.</para> <para>This example script is compatible with the syntax used by the &man.sh.1;, &man.csh.1;, and &man.tcsh.1; shells. Symbolic substitution fields are prefixed with a dollar sign ($). Symbolic fields do not have the $ prefix. The value to populate the symbolic field must be enclosed in double quotes ("").</para> <para>Start the rules file like this:</para> <programlisting>############### start of example ipfw rules script ############# # ipfw -q -f flush # Delete all rules # Set defaults oif="tun0" # out interface odns="192.0.2.11" # ISP's DNS server IP address cmd="ipfw -q add " # build rule prefix ks="keep-state" # just too lazy to key this each time $cmd 00500 check-state $cmd 00502 deny all from any to any frag $cmd 00501 deny tcp from any to any established $cmd 00600 allow tcp from any to any 80 out via $oif setup $ks $cmd 00610 allow tcp from any to $odns 53 out via $oif setup $ks $cmd 00611 allow udp from any to $odns 53 out via $oif $ks ################### End of example ipfw rules script ############</programlisting> <para>The rules are not important as the focus of this example is how the symbolic substitution fields are populated.</para> <para>If the above example was in <filename>/etc/ipfw.rules</filename>, the rules could be reloaded by the following command:</para> <screen>&prompt.root; <userinput>sh /etc/ipfw.rules</userinput></screen> <para><filename>/etc/ipfw.rules</filename> can be located anywhere and the file can have any name.</para> <para>The same thing could be accomplished by running these commands by hand:</para> <screen>&prompt.root; <userinput>ipfw -q -f flush</userinput> &prompt.root; <userinput>ipfw -q add check-state</userinput> &prompt.root; <userinput>ipfw -q add deny all from any to any frag</userinput> &prompt.root; <userinput>ipfw -q add deny tcp from any to any established</userinput> &prompt.root; <userinput>ipfw -q add allow tcp from any to any 80 out via tun0 setup keep-state</userinput> &prompt.root; <userinput>ipfw -q add allow tcp from any to 192.0.2.11 53 out via tun0 setup keep-state</userinput> &prompt.root; <userinput>ipfw -q add 00611 allow udp from any to 192.0.2.11 53 out via tun0 keep-state</userinput></screen> </sect3> <sect3> <title>An Example Stateful Ruleset</title> <para>The following sample ruleset is a complete inclusive type ruleset. Comment out any <literal>pass</literal> rules for services that are not required. To avoid logging undesired messages, add a <literal>deny</literal> rule in the inbound section. Change the <devicename>dc0</devicename> in every rule to the device name of the interface that connects the system to the Internet.</para> <para>There is a noticeable pattern in the usage of these rules.</para> <itemizedlist> <listitem> <para>All statements that are a request to start a session to the Internet use <literal>keep-state</literal>.</para> </listitem> <listitem> <para>All the authorized services that originate from the Internet use <literal>limit</literal> to prevent flooding.</para> </listitem> <listitem> <para>All rules use <literal>in</literal> or <literal>out</literal> to clarify direction.</para> </listitem> <listitem> <para>All rules use <literal>via</literal> <replaceable>interface-name</replaceable> to specify the interface the packet is traveling over.</para> </listitem> </itemizedlist> <para>The following rules go into <filename>/etc/ipfw.rules</filename>:</para> <programlisting>################ Start of IPFW rules file ############################### # Flush out the list before we begin. ipfw -q -f flush # Set rules command prefix cmd="ipfw -q add" pif="dc0" # public interface name of NIC # facing the public Internet ################################################################# # No restrictions on Inside LAN Interface for private network # Not needed unless you have LAN. # Change xl0 to your LAN NIC interface name ################################################################# #$cmd 00005 allow all from any to any via xl0 ################################################################# # No restrictions on Loopback Interface ################################################################# $cmd 00010 allow all from any to any via lo0 ################################################################# # Allow the packet through if it has previous been added to the # the "dynamic" rules table by a allow keep-state statement. ################################################################# $cmd 00015 check-state ################################################################# # Interface facing Public Internet (Outbound Section) # Interrogate session start requests originating from behind the # firewall on the private network or from this gateway server # destined for the public Internet. ################################################################# # Allow out access to my ISP's Domain name server. # x.x.x.x must be the IP address of your ISP.s DNS # Dup these lines if your ISP has more than one DNS server # Get the IP addresses from /etc/resolv.conf file $cmd 00110 allow tcp from any to x.x.x.x 53 out via $pif setup keep-state $cmd 00111 allow udp from any to x.x.x.x 53 out via $pif keep-state # Allow out access to my ISP's DHCP server for cable/DSL configurations. # This rule is not needed for .user ppp. connection to the public Internet. # so you can delete this whole group. # Use the following rule and check log for IP address. # Then put IP address in commented out rule & delete first rule $cmd 00120 allow log udp from any to any 67 out via $pif keep-state #$cmd 00120 allow udp from any to x.x.x.x 67 out via $pif keep-state # Allow out non-secure standard www function $cmd 00200 allow tcp from any to any 80 out via $pif setup keep-state # Allow out secure www function https over TLS SSL $cmd 00220 allow tcp from any to any 443 out via $pif setup keep-state # Allow out send & get email function $cmd 00230 allow tcp from any to any 25 out via $pif setup keep-state $cmd 00231 allow tcp from any to any 110 out via $pif setup keep-state # Allow out FBSD (make install & CVSUP) functions # Basically give user root "GOD" privileges. $cmd 00240 allow tcp from me to any out via $pif setup keep-state uid root # Allow out ping $cmd 00250 allow icmp from any to any out via $pif keep-state # Allow out Time $cmd 00260 allow tcp from any to any 37 out via $pif setup keep-state # Allow out nntp news (i.e., news groups) $cmd 00270 allow tcp from any to any 119 out via $pif setup keep-state # Allow out secure FTP, Telnet, and SCP # This function is using SSH (secure shell) $cmd 00280 allow tcp from any to any 22 out via $pif setup keep-state # Allow out whois $cmd 00290 allow tcp from any to any 43 out via $pif setup keep-state # deny and log everything else that.s trying to get out. # This rule enforces the block all by default logic. $cmd 00299 deny log all from any to any out via $pif ################################################################# # Interface facing Public Internet (Inbound Section) # Check packets originating from the public Internet # destined for this gateway server or the private network. ################################################################# # Deny all inbound traffic from non-routable reserved address spaces $cmd 00300 deny all from 192.168.0.0/16 to any in via $pif #RFC 1918 private IP $cmd 00301 deny all from 172.16.0.0/12 to any in via $pif #RFC 1918 private IP $cmd 00302 deny all from 10.0.0.0/8 to any in via $pif #RFC 1918 private IP $cmd 00303 deny all from 127.0.0.0/8 to any in via $pif #loopback $cmd 00304 deny all from 0.0.0.0/8 to any in via $pif #loopback $cmd 00305 deny all from 169.254.0.0/16 to any in via $pif #DHCP auto-config $cmd 00306 deny all from 192.0.2.0/24 to any in via $pif #reserved for docs $cmd 00307 deny all from 204.152.64.0/23 to any in via $pif #Sun cluster interconnect $cmd 00308 deny all from 224.0.0.0/3 to any in via $pif #Class D & E multicast # Deny public pings $cmd 00310 deny icmp from any to any in via $pif # Deny ident $cmd 00315 deny tcp from any to any 113 in via $pif # Deny all Netbios service. 137=name, 138=datagram, 139=session # Netbios is MS/Windows sharing services. # Block MS/Windows hosts2 name server requests 81 $cmd 00320 deny tcp from any to any 137 in via $pif $cmd 00321 deny tcp from any to any 138 in via $pif $cmd 00322 deny tcp from any to any 139 in via $pif $cmd 00323 deny tcp from any to any 81 in via $pif # Deny any late arriving packets $cmd 00330 deny all from any to any frag in via $pif # Deny ACK packets that did not match the dynamic rule table $cmd 00332 deny tcp from any to any established in via $pif # Allow traffic in from ISP's DHCP server. This rule must contain # the IP address of your ISP.s DHCP server as it.s the only # authorized source to send this packet type. # Only necessary for cable or DSL configurations. # This rule is not needed for .user ppp. type connection to # the public Internet. This is the same IP address you captured # and used in the outbound section. #$cmd 00360 allow udp from any to x.x.x.x 67 in via $pif keep-state # Allow in standard www function because I have apache server $cmd 00400 allow tcp from any to me 80 in via $pif setup limit src-addr 2 # Allow in secure FTP, Telnet, and SCP from public Internet $cmd 00410 allow tcp from any to me 22 in via $pif setup limit src-addr 2 # Allow in non-secure Telnet session from public Internet # labeled non-secure because ID & PW are passed over public # Internet as clear text. # Delete this sample group if you do not have telnet server enabled. $cmd 00420 allow tcp from any to me 23 in via $pif setup limit src-addr 2 # Reject & Log all incoming connections from the outside $cmd 00499 deny log all from any to any in via $pif # Everything else is denied by default # deny and log all packets that fell through to see what they are $cmd 00999 deny log all from any to any ################ End of IPFW rules file ###############################</programlisting> </sect3> <sect3> <title>An Example <acronym>NAT</acronym> and Stateful Ruleset</title> <indexterm> <primary>NAT</primary> <secondary>and IPFW</secondary> </indexterm> <para>There are some additional configuration statements that need to be enabled to activate the <acronym>NAT</acronym> function of IPFW. For a customized kernel, the kernel configuration file needs <literal>option IPDIVERT</literal> added to the other <literal>IPFIREWALL</literal> options.</para> <para>In addition to the normal IPFW options in <filename>/etc/rc.conf</filename>, the following are needed:</para> <programlisting>natd_enable="YES" # Enable <acronym>NAT</acronym>D function natd_interface="rl0" # interface name of public Internet NIC natd_flags="-dynamic -m" # -m = preserve port numbers if possible</programlisting> <para>Utilizing stateful rules with a <literal>divert natd</literal> rule complicates the ruleset logic. The positioning of the <literal>check-state</literal>, and <literal>divert natd</literal> rules in the ruleset is critical and a new action type is used, called <literal>skipto</literal>. When using <literal>skipto</literal>, it is mandatory that each rule is numbered, so that the <literal>skipto</literal> rule knows which rule to jump to.</para> <para>The following is an uncommented example of a ruleset which explains the sequence of the packet flow.</para> <para>The processing flow starts with the first rule from the top of the ruleset and progresses one rule at a time until the end is reached or the packet matches and the packet is released out of the firewall. Take note of the location of rule numbers 100 101, 450, 500, and 510. These rules control the translation of the outbound and inbound packets so that their entries in the dynamic keep-state table always register the private LAN IP address. All the allow and deny rules specify the direction of the packet and the interface. All start outbound session requests will <literal>skipto rule 500</literal> to undergo NAT.</para> <para>Consider a web browser which initializes a new HTTP session over port 80. When the first outbound packet enters the firewall, it does not match rule 100 because it is headed out rather than in. It passes rule 101 because this is the first packet, and it has not been posted to the dynamic keep-state table yet. The packet finally matches rule 125 as it is outbound through the NIC facing the Internet and has a source IP address as a private LAN IP address. On matching this rule, two actions take place. <literal>keep-state</literal> adds this rule to the dynamic keep-state rules table and the specified action is executed and posted as part of the info in the dynamic table. In this case, the action is <literal>skipto rule 500</literal>. Rule 500 <acronym>NAT</acronym>s the packet IP address and sends it out to the Internet. This packet makes its way to the destination web server, where a response packet is generated and sent back. This new packet enters the top of the ruleset. It matches rule 100 and has it destination IP address mapped back to the corresponding LAN IP address. It then is processed by the <literal>check-state</literal> rule, is found in the table as an existing session, and is released to the LAN. It goes to the LAN system that sent it and a new packet is sent requesting another segment of the data from the remote server. This time it matches the <literal>check-state</literal> rule, its outbound entry is found, and the associated action, <literal>skipto 500</literal>, is executed. The packet jumps to rule 500, gets <acronym>NAT</acronym>ed, and is released to the Internet.</para> <para>On the inbound side, everything coming in that is part of an existing session is automatically handled by the <literal>check-state</literal> rule and the properly placed <literal>divert natd</literal> rules. The ruleset only has to deny bad packets and allow only authorized services. Consider a web server running on the firewall where web requests from the Internet should have access to the local web site. An inbound start request packet will match rule 100 and its IP address will be mapped to the LAN IP address of the firewall. The packet is then matched against all the nasty things that need to be checked and finally matches rule 425 where two actions occur. The packet rule is posted to the dynamic keep-state table but this time, any new session requests originating from that source IP address are limited to 2. This defends against DoS attacks against the service running on the specified port number. The action is <literal>allow</literal>, so the packet is released to the LAN. The packet generated as a response is recognized by the <literal>check-state</literal> as belonging to an existing session. It is then sent to rule 500 for <acronym>NAT</acronym>ing and released to the outbound interface.</para> <para>Example Ruleset #1:</para> <programlisting>#!/bin/sh cmd="ipfw -q add" skip="skipto 500" pif=rl0 ks="keep-state" good_tcpo="22,25,37,43,53,80,443,110,119" ipfw -q -f flush $cmd 002 allow all from any to any via xl0 # exclude LAN traffic $cmd 003 allow all from any to any via lo0 # exclude loopback traffic $cmd 100 divert natd ip from any to any in via $pif $cmd 101 check-state # Authorized outbound packets $cmd 120 $skip udp from any to xx.168.240.2 53 out via $pif $ks $cmd 121 $skip udp from any to xx.168.240.5 53 out via $pif $ks $cmd 125 $skip tcp from any to any $good_tcpo out via $pif setup $ks $cmd 130 $skip icmp from any to any out via $pif $ks $cmd 135 $skip udp from any to any 123 out via $pif $ks # Deny all inbound traffic from non-routable reserved address spaces $cmd 300 deny all from 192.168.0.0/16 to any in via $pif #RFC 1918 private IP $cmd 301 deny all from 172.16.0.0/12 to any in via $pif #RFC 1918 private IP $cmd 302 deny all from 10.0.0.0/8 to any in via $pif #RFC 1918 private IP $cmd 303 deny all from 127.0.0.0/8 to any in via $pif #loopback $cmd 304 deny all from 0.0.0.0/8 to any in via $pif #loopback $cmd 305 deny all from 169.254.0.0/16 to any in via $pif #DHCP auto-config $cmd 306 deny all from 192.0.2.0/24 to any in via $pif #reserved for docs $cmd 307 deny all from 204.152.64.0/23 to any in via $pif #Sun cluster $cmd 308 deny all from 224.0.0.0/3 to any in via $pif #Class D & E multicast # Authorized inbound packets $cmd 400 allow udp from xx.70.207.54 to any 68 in $ks $cmd 420 allow tcp from any to me 80 in via $pif setup limit src-addr 1 $cmd 450 deny log ip from any to any # This is skipto location for outbound stateful rules $cmd 500 divert natd ip from any to any out via $pif $cmd 510 allow ip from any to any ######################## end of rules ##################</programlisting> <para>The next example is functionally equivalent, but uses descriptive comments to help the inexperienced IPFW rule writer to better understand what the rules are doing.</para> <para>Example Ruleset #2:</para> <programlisting>#!/bin/sh ################ Start of IPFW rules file ############################### # Flush out the list before we begin. ipfw -q -f flush # Set rules command prefix cmd="ipfw -q add" skip="skipto 800" pif="rl0" # public interface name of NIC # facing the public Internet ################################################################# # No restrictions on Inside LAN Interface for private network # Change xl0 to your LAN NIC interface name ################################################################# $cmd 005 allow all from any to any via xl0 ################################################################# # No restrictions on Loopback Interface ################################################################# $cmd 010 allow all from any to any via lo0 ################################################################# # check if packet is inbound and nat address if it is ################################################################# $cmd 014 divert natd ip from any to any in via $pif ################################################################# # Allow the packet through if it has previous been added to the # the "dynamic" rules table by a allow keep-state statement. ################################################################# $cmd 015 check-state ################################################################# # Interface facing Public Internet (Outbound Section) # Check session start requests originating from behind the # firewall on the private network or from this gateway server # destined for the public Internet. ################################################################# # Allow out access to my ISP's Domain name server. # x.x.x.x must be the IP address of your ISP's DNS # Dup these lines if your ISP has more than one DNS server # Get the IP addresses from /etc/resolv.conf file $cmd 020 $skip tcp from any to x.x.x.x 53 out via $pif setup keep-state # Allow out access to my ISP's DHCP server for cable/DSL configurations. $cmd 030 $skip udp from any to x.x.x.x 67 out via $pif keep-state # Allow out non-secure standard www function $cmd 040 $skip tcp from any to any 80 out via $pif setup keep-state # Allow out secure www function https over TLS SSL $cmd 050 $skip tcp from any to any 443 out via $pif setup keep-state # Allow out send & get email function $cmd 060 $skip tcp from any to any 25 out via $pif setup keep-state $cmd 061 $skip tcp from any to any 110 out via $pif setup keep-state # Allow out FreeBSD (make install & CVSUP) functions # Basically give user root "GOD" privileges. $cmd 070 $skip tcp from me to any out via $pif setup keep-state uid root # Allow out ping $cmd 080 $skip icmp from any to any out via $pif keep-state # Allow out Time $cmd 090 $skip tcp from any to any 37 out via $pif setup keep-state # Allow out nntp news (i.e., news groups) $cmd 100 $skip tcp from any to any 119 out via $pif setup keep-state # Allow out secure FTP, Telnet, and SCP # This function is using SSH (secure shell) $cmd 110 $skip tcp from any to any 22 out via $pif setup keep-state # Allow out whois $cmd 120 $skip tcp from any to any 43 out via $pif setup keep-state # Allow ntp time server $cmd 130 $skip udp from any to any 123 out via $pif keep-state ################################################################# # Interface facing Public Internet (Inbound Section) # Check packets originating from the public Internet # destined for this gateway server or the private network. ################################################################# # Deny all inbound traffic from non-routable reserved address spaces $cmd 300 deny all from 192.168.0.0/16 to any in via $pif #RFC 1918 private IP $cmd 301 deny all from 172.16.0.0/12 to any in via $pif #RFC 1918 private IP $cmd 302 deny all from 10.0.0.0/8 to any in via $pif #RFC 1918 private IP $cmd 303 deny all from 127.0.0.0/8 to any in via $pif #loopback $cmd 304 deny all from 0.0.0.0/8 to any in via $pif #loopback $cmd 305 deny all from 169.254.0.0/16 to any in via $pif #DHCP auto-config $cmd 306 deny all from 192.0.2.0/24 to any in via $pif #reserved for docs $cmd 307 deny all from 204.152.64.0/23 to any in via $pif #Sun cluster $cmd 308 deny all from 224.0.0.0/3 to any in via $pif #Class D & E multicast # Deny ident $cmd 315 deny tcp from any to any 113 in via $pif # Deny all Netbios service. 137=name, 138=datagram, 139=session # Netbios is MS/Windows sharing services. # Block MS/Windows hosts2 name server requests 81 $cmd 320 deny tcp from any to any 137 in via $pif $cmd 321 deny tcp from any to any 138 in via $pif $cmd 322 deny tcp from any to any 139 in via $pif $cmd 323 deny tcp from any to any 81 in via $pif # Deny any late arriving packets $cmd 330 deny all from any to any frag in via $pif # Deny ACK packets that did not match the dynamic rule table $cmd 332 deny tcp from any to any established in via $pif # Allow traffic in from ISP's DHCP server. This rule must contain # the IP address of your ISP's DHCP server as it is the only # authorized source to send this packet type. # Only necessary for cable or DSL configurations. # This rule is not needed for 'user ppp' type connection to # the public Internet. This is the same IP address you captured # and used in the outbound section. $cmd 360 allow udp from x.x.x.x to any 68 in via $pif keep-state # Allow in standard www function because I have Apache server $cmd 370 allow tcp from any to me 80 in via $pif setup limit src-addr 2 # Allow in secure FTP, Telnet, and SCP from public Internet $cmd 380 allow tcp from any to me 22 in via $pif setup limit src-addr 2 # Allow in non-secure Telnet session from public Internet # labeled non-secure because ID & PW are passed over public # Internet as clear text. # Delete this sample group if you do not have telnet server enabled. $cmd 390 allow tcp from any to me 23 in via $pif setup limit src-addr 2 # Reject & Log all unauthorized incoming connections from the public Internet $cmd 400 deny log all from any to any in via $pif # Reject & Log all unauthorized out going connections to the public Internet $cmd 450 deny log all from any to any out via $pif # This is skipto location for outbound stateful rules $cmd 800 divert natd ip from any to any out via $pif $cmd 801 allow ip from any to any # Everything else is denied by default # deny and log all packets that fell through to see what they are $cmd 999 deny log all from any to any ################ End of IPFW rules file ###############################</programlisting> </sect3> </sect2> </sect1> </chapter>