FreeBSD Firewall
Introduction
This section deals with building a firewall. What is a firewall? A firewall
(in this context) is a set of rules that allows or denies certain types of
network traffic (packets) from entering or leaving your FreeBSD machine.
A D V E R T I S E M E N T
Firewall Concepts
There are several different types of firewalls you can
run. Hardware firewalls, sometimes called Firewall appliances, implement
firewalling using hardware. Most of these 'hardware' firewalls are proprietary
and usually run an embedded OS like FreeBSD or Linux. Hardware firewalls usually
cost money and provide a nice graphical configuration tool to deploy your
firewall. Some example hardware firewalls you may have heard of are 'Cisco
Systems PIX', 'SonicWall', Linkysys, and many others.
Software firewalls, on the other hand, are usually a set
of tools that run on a specific OS. They interface with the OS's kernel packet
processing system. Many different types of software firewalls exist, some
commercial and some free. Some free software firewall packages include IPFilter,
IPChains, IPTables, and IPFW. Several commercial software firewalls exist the
most popular being CheckPoint's Firewall-1. We will be focusing on FreeBSD's
ipfw firewall package. One thing about free software based firewalls is that
they are usually difficult to setup and manage. FreeBSD provides a nice
interface to create, delete and manage your firewall. However, it takes a
certain amount of knowledge about IP packets and routing to understand
completely. I recommend you first understand these basics before you start
reading. I will be expecting you understand some of these basics.
FreeBSD Firewalling notes
As I mentioned earlier, we will be exploring the FreeBSD
ipfw software firewall. This 'tool' is included in the base system of FreeBSD so
you will not need to download any 3rd party software. You should be familiar
with interfaces on FreeBSD. If you are not please read the
Interfaces Section. Interfaces are key to firewalling on any OS as they are
the conduits for packets coming and going on your machine. Also, you may have to
build a kernel on your FreeBSD machine to turn on firewalling in the kernel.
This is done with the 'options IPFIREWALL' line in the kernel config file. See
the Kernel
section for more details on building a custom kernel. Another quick note:
everything in this "how-to" can be retrieved from the ipfw(8) manpage.
Quick note
If you want to be real efficient in management of your firewall(s), you should
understand one very important thing that most people miss: Packets flow
symmetrically to and from your machine. A common mistake made by begginers is
over looking this fact. You must always look at packets like with this in mind,
i.e. you need to firewall both ways: in AND out. Keep this in mind when you are
reading this article.
Basic Firewall
We will start with a simple setup. One machine connected
directly to the internet. We will progress to a more advanced setup in the next
section:
Building a
Gateway/Router. But for now, let's focus on the basics: one machine and one
interface.
First, lets conceptually look at how packets flow to/from your machine
to/from the internet (or network):
Notice how packets flow both ways: in and out of your machine. This will be
crucial in upcoming paragraphs.
Next, lets enable firewalling on boot up. In order to do this we edit
/etc/rc.conf and add:
firewall_enable="YES"
firewall_type="OPEN"
This does 2 things. First off the 'firewall_enable="YES" tells BSD to enable
the firewall on bootup. The firewall_type="OPEN" tells BSD to use a stock OPEN
firewall configuration. There are several different stock preconfiged setups you
can choose from to go between the "" marks in te firewall_type line within
/etc/rc.conf:
open - will allow anyone in
client - will try to protect just this machine
simple - will try to protect a whole network
closed - totally disables IP services except via lo0 interface
UNKNOWN - disables the loading of firewall rules.
filename - will load the rules in the given filename (full path required)
These /etc/rc.conf options get processed by /etc/rc.firewall upon startup.
For now lets focus on 2 different stock configurations we can play with: OPEN
and filename. An OPEN firewall just allows all traffic to and from your machine.
By default, if you forget to add this firewall_type="OPEN" to /etc/rc.conf all
traffic will STOP! That is by IPFIREWALL has a default rule to deny traffic from
any to any. Don't do this remotely or you'll have to get to the console and
reset it to get back in!! You can change this behavior by adding an 'options
DEFAULT_TO_ACCEPT' to the kernel config file and rebuilding your kernel. The
OPEN statement tells /etc/rc.firewall to add a pass rule to your firewall on
bootup. The 'filename' is exactly that, a filename that contains a set of custom
built firewall rules.
Firewall Rules
Firewall rules are just a list of sequentially searched
'rules' that EVERY packet gets compared to when it leaves/enters an interface.
Notice I didn't say machine, I said interface. What I mean by that is every
packet enters an interface and leaves an interface. Each time it passes across
an interface, the kernel compares these packets to these rules to determine
whether it should be allowed or blocked. The matching is done by comparing the
packet header information to the rule criteria to determine if it matches. If a
match is encountered then the system looks at the 'action' for the rule to
determine if it should be passed or blocked. IP header information that the
firewall can match against are: source IP, destination IP, source PORT,
destination PORT, protocol type, and protocol specific flags. It is important to
remember that the firewall can not inspect any further into a packet than the
header. Therefore, it can not determine payload contents, i.e. it's not a
content firewall/proxy.
Let's now look at the syntax of these rules. For example
let's say we have a machine with interface setup:
# ifconfig -a
xl0: flags=8843 mtu 1500
options=3
inet 205.238.129.221 netmask 0xfffffffc broadcast 205.238.129.223
inet6 fe80::250:daff:fe77:cc77%xl0 prefixlen 64 scopeid 0x1
ether 00:50:da:77:cc:77
media: Ethernet autoselect (100baseTX )
status: active
A small ruleset may look something like:
# ipfw l
00100 allow ip from any to any via lo0
00200 deny ip from any to 127.0.0.0/8
00300 deny ip from 127.0.0.0/8 to any
65000 allow ip from any to any
65535 deny ip from any to any
Fairly intimidating. So lets step back for a moment at just
adding one of these rules first. Then we'll worry about the ruleset as a whole.
Let's say we want to add a rule to allow all traffic to and from this machine.
We might add something like this:
# ipfw add 50 allow ip from any to any via xl0
WOW! What the hell is this? Let's examine it piece by piece.
First the 'ipfw' command is the command line interface tool to add and remove
firewall rules from the system. Everything else is an argument to ipfw. I used
the 'l' argument to ipfw above to list the current ruleset. This is a failry
common way to inspect running rulesets.
Next the 'add' argument to ipfw tells the system that this
entry is to be added to the ruleset.
The number 50 is used as kind of a line number. I said
earlier that these rules are sequentially searched for matching packets. This
number 50 just means it is rule number 50. Rule numbers have a range from
1-65534. Rule number 65535 is always the default deny rule (Unless changed as
mentioned above). Rule numbers don't have to be concurrent added, i.e. you can
add rule 100 then add rule 200. The important thing to rememeber about rule
numbers is that they are processed in ascending order (1 to 65535).
The next argument is 'allow'. This is the action to be taken
if a packet matches on this rule. Different types of actions can be taken if a
packet matches this rule. We will only be covering 'allow' and 'deny' in this
section. The 'allow' action means that the kernel will let the packet go. The
'deny' action means the kernel will throw the packet away.
When I said "if a packet matches" I meant exactly that. See,
the kernel takes the packet and compares it to the rest of the line: 'ip from
any to any via xl0'. This is the match criteria. If the kernel decides it
matches the criteria then it performs 'action' (allow or deny) for the rule. If
the kernel doesn't match on this rule, it proceeds to compare the packet to the
next rule in the firewall ruleset. Eventually, if the firewall doesn't find a
match for any rule number, it will get to the default 65536 rule, which will
deny the packet (throw it away). If a match does occur, the action is taken and
no more rules are compared to the current packet, i.e. rule processing stops at
that rule number.
The match criteria means exactly what it sounds like. You can
think of the match criteria kind of like an if-else programming statement.
Therefore, you can analyze 'ip from any to any via xl0' like so:
// Analyzing: 'ip'
// Meaning: Is protocol type in packet header equal to ip?
if (protocol == ip) {
// Yes, protocol is IP
// Analyzing: 'from any'
// Meaning: Does source IP in packet header (from) equal any?
if (from == any) {
//Yes, source IP equals any
//Analyzing: 'to any'
//Meaning: Does destination IP in packet header (to) equal any?
if (to == any) {
//Yes, desintation IP equals any
//Analyzing: 'via xl0'
//Meaning: Did the packet leave or enter through the interface xl0?
if (via eq xl0) {
//Yes, packet left or entered through the xl0 interface
//We have a match!!! now perform action
// In this case action is 'allow' so that means
// let it pass through the interface
} else {
// Packet didn't enter or leave through the xl0 interface,
// So skip this rule and move to next rule number.
}
} else {
// No, destination IP doesn't equal 'any'
// So skip this rule and move to next rule number.
}
} else {
//No, source IP doesn't equal 'any'
// So skip this rule and move to next rule number.
}
} else {
//No, protocol doesn't equal ip
// So skip this and rule move to next rule number.
}
Yes, matching criteria can get quite complicated with many
different combinations to match on. Although confusing, this complexity gives us
extreme amounts of power and flexibility in building rules.
Now that you have added a rule lets look at it in the
firewall within the kernel using the ipfw command line utility with the 'l'
argument (meaning list):
# ipfw l
00050 allow ip from any to any via xl0
I mentioned earlier that you should be aware that packets
traverse your interfaces in both directions. So our single add rule: 'ipfw add
50 allow ip from any to any via xl0' could be added by 2 different ipfw add
statements. Let's take a look:
# ipfw add 50 allow ip from any to any in via xl0
# ipfw add 60 allow ip from any to any out via xl0
# ipfw l
00050 allow ip from any to any in recv xl0
00060 allow ip from any to any out xmit xl0
Notice how I changed the 'via' to 'in via' and 'out via'. The
'in via xl0' means 'Is this packet coming in through the xl0 interface'. The
'out via xl0' means 'Is this packet leaving out the xl0 interface?'. Also take
note that the rule numbers are 50 and 60. That means EVERY packet coming or
leaving first gets compared to rule number 50, then rule number 60.
You can even get more specific if you want. Since I know what
my IP address is (205.238.129.221), I can add that to the ruleset to get even
more specific:
# ipfw add 50 allow ip from any to 205.238.129.221 in via xl0
# ipfw add 60 allow ip from 205.238.129.221 to any out via xl0
# ipfw l
00050 allow ip from any to 205.238.129.221 in recv xl0
00060 allow ip from 205.238.129.221 to any out xmit xl0
SO how do you delete a rule once it's added. That's simple.
You delete it by rule number like so:
# ipfw delete 50
You just deleted rule number 50!
Rules Processing
Now that you are an expert at rule syntax (hehe) let's
look at the firewall ruleset as a whole as we did earlier. Using a sample
ruleset:
# ipfw l
00100 allow ip from any to any via lo0
00200 deny ip from any to 127.0.0.0/8
00300 deny ip from 127.0.0.0/8 to any
65000 allow ip from any to any
65535 deny ip from any to any
The idea here is to reinforce the rule processing of how
packets are inspected and leave/enter the machine. This is a key concept of
understanding firewalls in general. Lets take you through a quick example with
this ruleset above. Suppose my machine (205.238.129.221) receives a telnet
packet from the internet, i.e. Someone (1.2.3.4) is trying to telnet to my
machine from the internet. So a sample packet would have attributes that look
similar to:
Source_IP Source_PORT Destination_IPDestination_PORT Protocol
1.2.3.43333205.238.129.22123 TCP
Steps involved in processing this packet:
1) Kernel receives packet on interface xl0
2) Kernel compares packet to this rule (100):
=====> 00100 allow ip from any to any via lo0
00200 deny ip from any to 127.0.0.0/8
00300 deny ip from 127.0.0.0/8 to any
65000 allow ip from any to any
65535 deny ip from any to any
3) Kernel doesn't match, so proceeds to compare to next
rule (200):
00100 allow ip from any to any via lo0
======> 00200 deny ip from any to 127.0.0.0/8
00300 deny ip from 127.0.0.0/8 to any
65000 allow ip from any to any
65535 deny ip from any to any
4) Kernel doesn't match, so proceeds to compare to next
rule (300):
00100 allow ip from any to any via lo0
00200 deny ip from any to 127.0.0.0/8
======> 00300 deny ip from 127.0.0.0/8 to any
65000 allow ip from any to any
65535 deny ip from any to any
5) Kernel doesn't match, so proceeds to compare to next
rule (65000):
00100 allow ip from any to any via lo0
00200 deny ip from any to 127.0.0.0/8
00300 deny ip from 127.0.0.0/8 to any
======> 65000 allow ip from any to any
65535 deny ip from any to any
6) Kernel FOUND A MATCH! Action is taken which is 'allow'
so
7) Packet gets handed to the telnet daemon (listening on port 23 tcp) for
processing
8) The telnet daemon now responds back to the sender.
This is what I mentioned earlier about packets
being symmetrical. Most people forget that the response packet gets sent back to
the sender. Its attributes look similar to this:
Source_IPSource_PORT Destination_IPDestination_PORT Protocol
205.238.129.221231.2.3.4 3333 TCP
So now we repeat the process for the outgoing packet: 1)
Kernel compares packet to this rule (100):
=====> 00100 allow ip from any to any via lo0
00200 deny ip from any to 127.0.0.0/8
00300 deny ip from 127.0.0.0/8 to any
65000 allow ip from any to any
65535 deny ip from any to any
2) Kernel doesn't match, so proceeds to compare to next
rule (200):
00100 allow ip from any to any via lo0
======> 00200 deny ip from any to 127.0.0.0/8
00300 deny ip from 127.0.0.0/8 to any
65000 allow ip from any to any
65535 deny ip from any to any
3) Kernel doesn't match, so proceeds to compare to next
rule (300):
00100 allow ip from any to any via lo0
00200 deny ip from any to 127.0.0.0/8
======> 00300 deny ip from 127.0.0.0/8 to any
65000 allow ip from any to any
65535 deny ip from any to any
5) Kernel doesn't match, so proceeds to compare to next
rule (65000):
00100 allow ip from any to any via lo0
00200 deny ip from any to 127.0.0.0/8
00300 deny ip from 127.0.0.0/8 to any
======> 65000 allow ip from any to any
65535 deny ip from any to any
6) Kernel MATCHES! So packet gets transmitted out interface
xl0
Notice in the above steps that if we wouldn't have
matched on rule number 65000, then we would have hit the default rule of 65536
which would 'deny' or drop the packet!
There are several different ways to write rules as
mentioned above. We could have matched on that telnet example above by using
several different rules. The first 7 steps which was when the kernel was
processing a packet that was inbound would have matched by any of these rules
(which i'll show you how to add):
# ipfw add 55 allow ip from any to any via xl0
OR
# ipfw add 55 allow ip from any to 205.238.129.221 via xl0
OR
# ipfw add 55 allow ip from 1.2.3.4 to any via xl0
OR
# ipfw add 55 allow ip from any to any in via xl0
OR
# ipfw add 55 allow ip from any to 205.238.129.221 in via xl0
OR
# ipfw add 55 allow ip from 1.2.3.4 to any in via xl0
OR
# ipfw add 55 allow ip from 1.2.3.4 to 205.238.129.221 in via xl0
OR
# ipfw add 55 allow ip from 1.2.3.4 to 205.238.129.221 in via xl0
OR
# ipfw add 55 allow tcp from any to 205.238.129.221 23
OR
# ipfw add 55 allow tcp from any to any 23 via xl0
OR
# ipfw add 55 allow tcp from any to any 23 in via xl0
OR
# ipfw add 55 allow tcp from any to 205.238.129.221 23 in via xl0
OR
# ipfw add 55 allow tcp from 1.2.3.4 to 205.238.129.221 23 in via xl0
OR
# ipfw add 55 allow tcp from 1.2.3.4 3333 to 205.238.129.221 23 in via xl0
As you can see, there are a lot of different
combinations which would've matched JUST THE INBOUND PACKET! Same goes for the
outbound (response) packet, except for the the from and to order would be
swapped and in replaced with out:
# ipfw add 65 allow ip from any to any via xl0
OR
# ipfw add 65 allow ip from 205.238.129.221 to any via xl0
OR
# ipfw add 65 allow ip from any to 1.2.3.4 via xl0
OR
# ipfw add 65 allow ip from any to any out via xl0
OR
# ipfw add 65 allow ip from 205.238.129.221 to any out via xl0
OR
# ipfw add 65 allow ip from any to 1.2.3.4 out via xl0
OR
# ipfw add 65 allow ip from 205.238.129.221 to 1.2.3.4 out via xl0
OR
# ipfw add 65 allow ip from 205.238.129.221 to 1.2.3.4 out via xl0
OR
# ipfw add 65 allow tcp from 205.238.129.221 23 to any
OR
# ipfw add 65 allow tcp from any 23 to any via xl0
OR
# ipfw add 65 allow tcp from any 23 to any out via xl0
OR
# ipfw add 65 allow tcp from 205.238.129.221 23 to any out via xl0
OR
# ipfw add 65 allow tcp from 205.238.129.221 23 to 1.2.3.4 out via xl0
OR
# ipfw add 65 allow tcp from 205.238.129.221 23 to 1.2.3.4 3333 out via xl0
There are quite a few combinations I didn't get to
either. Not to menton several things which I'm not covering in this section,
which would add even more combinations. So, that poses a good question: Which
rule should I use? The answer is not an easy one. Different people like
different techniques. I can advise one general rule of thumb. The more specific
a rule is the more secure you will be (or at least feel). That is, the rule:
# ipfw add 65 allow tcp from 205.238.129.221 23 to 1.2.3.4 out via xl0
Is better (more tightly secured) than a more general rule such as:
# ipfw add 65 allow ip from any to any via xl0
This isn't always the case but when you get to refining
your ruleset being more specific about rules can help you better monitor your
machine/network. In a later section,
Fun
with Firewalling, I will be covering fine tuning your rulesets and such
refinements will be covered.
Firewall Options
Now that you can build firewalls via the command line,
let's add them so they come up when the system boots. As mentioned above,
firewall rules are loaded by a special startup script called /etc/rc.firewall.
Many people add their rules to this file manually. I believe this is a terrible
idea. This is a system file and shold not be modified directly. It causes
problems with mergemaster and makes upgrading other things difficult. The
approach I recommend is to create your own file and put your rules in it to be
loaded at boot time.
So create a file, e.g. /etc/firewall.local, and add your
rules to it line by line:
# Allow bob.rogness.net to talk to me and me to talk to bob.rogness.net
add 100 allow ip from bob.rogness.net to any in via xl0
add 105 allow ip from any to bob.rogness.net out via xl0
# Allow this machine to make DNS queries
add 5000 allow udp from me to any 53 out via xl0
add 5005 allow udp from any 50 to me in via xl0
# Allow anyone to ssh to me and allow me to respond
add 10000 allow tcp from any to 205.238.129.221 22 via xl0
add 15673 allow tcp from 205.238.129.221 22 to any via xl0
# Allow and log everything else to look for
add 20000 allow log ip from any to any
Save the file. You can now add it to the /etc/rc.conf file
and when you reboot it will automagically load your firewall:
firewall_type="/etc/firewall.local"
You can also invoke it from the command like manually if
you are too impatient for a reboot by:
# ipfw /etc/firewall.local
The above firewall ruleset also showed a few more details I
left out earlier. One being the use of hostnames (bob.rogness.net) as source or
destination address (rule 100). When added like this ipfw will look up the name
'bob.rogness.net' and replace 'bob.rogness.net' by the IP it resolves to. Beware
that this is not dynamic, if bob.rogness.net changes IPs, ipfw will not
automagically change it for you!
Another trick I used was the keywork 'me' (rule 5000). This
referes to any IP referenced on your machine. This is a great trick for use in
DHCP or when your machines interface IPs change frequently. This 'me' keywork is
dynamic, i.e. your IP can change and the rule will still match.
The last trick I used was the keyword 'log' (rule 20000).
This tells the firewall to log the packet (in abbreviated form) to syslog. You
must enable this in the kernel before usage with 'options IPFIREWALL_VERBOSE'
and rebuild your kernel. The reason I showed you this is because it is crutial
to troubleshooting. If you are having problems because packets are being denied
or allowed or not being matched, etc. Turn on the 'log' option on your
questionable rule(s). This will dump the packet header matches to syslog for
your inspection. It is very convient without having to use a packet sniffer.
Just remember the log keywork will save your butt!
Well, that's it! Firewalling can be very complex. The only
way to get good at such an unbearable act is practice. Checkout the next
firewalling section
Fun
with Firewalling to go over some more advanced things you can do with ipfw.
Any questions, just email me. Oh and, you the ipfw man page. It is a very
indepth and helpful tool when you hit a roadblock.
|