Friday, June 10, 2011

Zone Based Firewall Configuration Example

There aren’t a ton of examples available for IOS Zone-Based Firewall configurations, so I thought I’d put up one with which I’ve been working recently.

My test network looks like this:

R4 ------------------- R5 ----------------------- R6

R4 Loopback: 4.4.4.4
R4 to R5 link: 1.1.45.0/24
R5 Loopback: 5.5.5.5
R5 to R6 link: 1.1.56.0/24
R6 Loopback: 6.6.6.6


My goal is to express this policy:
  1. Hosts in group X on the OUTSIDE network can initiate sessions to hosts on the INSIDE network. Return traffic for those sessions should be statefully permitted. In the lab, group X is represented by R4's loopback address.
  2. Any host on the INSIDE network can initiate sessions to hosts in group X on the OUTSIDE network. Return traffic for those sessions should be statefully permitted. In the lab, the inside hosts are represented by R6's loopback address.
  3.  Permitted traffic should be logged. All other traffic should be dropped and logged.
The network on which the production version of this lab test will be deployed currently uses extended access-lists to implement the policy. Access-lists have the advantage of being familiar to anyone with a basic knowledge of IOS, but they have a lot of disadvantages:

  1. Hard to read and troubleshoot as they grow. 
  2. No stateful awareness. Trying to retrofit statefulness onto extended ACLs to allow return traffic requires ugly hacks with source port restrictions, filtering on ACK bits, etc. This path eventually leads to mind-numbing troubleshooting problems, and is less than optimal in its security. 
  3. Extended ACLs don’t have application-layer awareness.

There are several tools in IOS to facilitate stateful traffic inspection, but Zone-Based Firewalls are the newest and most flexible, so it made sense to use them.

Most of the examples I found via Google show the simple case of inside hosts having unfettered access to the outside, and outside hosts having no access to the inside--a classic stateful firewall design. My case is only slightly more complex, but it still took me a couple of tries to make it work.

First, I built access-lists defining the traffic to be allowed. I decided to use the relatively new IOS object-group support to make it easier to add and delete hosts on the respective networks:


object-group network OG_INSIDE_HOSTS
  host 6.6.6.6
object-group network OG_OUTSIDE_ALLOWED
  host 4.4.4.4
ip access-list extended INSIDE_TO_OUTSIDE
  permit ip object-group OG_INSIDE_HOSTS object-group OG_OUTSIDE_ALLOWED
ip access-list extended OUTSIDE_TO_INSIDE
  permit ip object-group OG_OUTSIDE_ALLOWED object-group OG_INSIDE_HOSTS


You might notice that there’s no “deny ip any any log” statement in these ACLs, which is strange given requirement #3 above. It turns out that when using ZBFW, you configure drop logging elsewhere; I’ll cover that below.

I couldn’t find a way to express protocol inspection policy and IPv4 address policy in the same class-map, so I had to use a hierarchical configuration. To express the protocol inspection policy, I built a class-map to define the layer 4 protocols that will be inspected by the firewall:


class-map type inspect match-any CM_INSPECTED_PROTOCOLS
  match protocol icmp
  match protocol tcp
  match protocol udp


This class-map does simple generic TCP/UDP/ICMP inspection, but it could easily be extended or rewritten to use much more complex inspection rules.

Next, I built class-maps for each direction that match the appropriate ACL and the inspection policy configured above:


class-map type inspect match-all CM_OUTSIDE_INSIDE
  match access-group name OUTSIDE_TO_INSIDE
  match class-map CM_INSPECTED_PROTOCOLS
class-map type inspect match-all CM_INSIDE_OUTSIDE
  match access-group name INSIDE_TO_OUTSIDE
  match class-map CM_INSPECTED_PROTOCOLS


To meet the packet permit-log requirement, we need a “parameter-map” that will be applied in the policy-map that references the previous class-maps:


parameter-map type inspect PARAM_AUDIT_LOG
  audit-trail on


Next, the class-maps and parameter-map are referenced in a pair of policy-maps:


policy-map type inspect PM_ZBFW_OUTSIDE_INSIDE
  class type inspect CM_OUTSIDE_INSIDE
    inspect PARAM_AUDIT_LOG
  class class-default
    drop log
policy-map type inspect PM_ZBFW_INSIDE_OUTSIDE
  class type inspect CM_INSIDE_OUTSIDE
    inspect PARAM_AUDIT_LOG
  class class-default
    drop log


Note the “drop log” statement in the final section. Similar to the implicit deny rule in ACLs, ZBFW policies include a class-default with a drop statement. If you want packet drops to be logged, however, you need to explicitly add the “log” parameter to the drop command.

At this point, the policies are configured. Now they need to be linked to interfaces and traffic direction. This is where the “zones” come in:


zone security INSIDE
  description to R6
zone security OUTSIDE
  description to R4
interface FastEthernet0/0
  zone-member security OUTSIDE
interface FastEthernet0/1
  zone-member security INSIDE


Finally, I created zone pairs that associate zones, traffic direction, and traffic policy:


zone-pair security ZP_OUTSIDE_INSIDE source OUTSIDE destination INSIDE
  service-policy type inspect PM_ZBFW_OUTSIDE_INSIDE
zone-pair security ZP_INSIDE_OUTSIDE source INSIDE destination OUTSIDE
  service-policy type inspect PM_ZBFW_INSIDE_OUTSIDE


At this point, the zone-based firewall should be working and ready to test. Based on the policy defined above, traffic from R4’s loopback address should be able to reach R6’s loopback address, but traffic from other interfaces on R4 should be dropped:


R4#ping 6.6.6.6 source 4.4.4.4

Type escape sequence to abort.
Sending 5, 100-byte ICMP Echos to 6.6.6.6, timeout is 2 seconds:
Packet sent with a source address of 4.4.4.4
!!!!!
Success rate is 100 percent (5/5), round-trip min/avg/max = 1/2/4 ms
R4#
R4#ping 6.6.6.6 source 1.1.45.4

Type escape sequence to abort.
Sending 5, 100-byte ICMP Echos to 6.6.6.6, timeout is 2 seconds:
Packet sent with a source address of 1.1.45.4
.....
Success rate is 0 percent (0/5)


So far, this looks good. The log on R5 should verify the results above:


*Jun 10 14:29:02.604: %FW-6-SESS_AUDIT_TRAIL_START: (target:class)-(ZP_OUTSIDE_INSIDE:CM_OUTSIDE_INSIDE):Start icmp session: initiator (4.4.4.4:0) -- responder (6.6.6.6:0)
*Jun 10 14:29:13.100: %FW-6-SESS_AUDIT_TRAIL: (target:class)-(ZP_OUTSIDE_INSIDE:CM_OUTSIDE_INSIDE):Stop icmp session: in
itiator (4.4.4.4:8) sent 360 bytes -- responder (6.6.6.6:0) sent 360 bytes

*Jun 10 14:29:16.420: %FW-6-DROP_PKT: Dropping icmp session 1.1.45.4:0 6.6.6.6:0 on zone-pair ZP_OUTSIDE_INSIDE class class-default due to  DROP action found in policy-map with ip ident 0
*Jun 10 14:29:19.812: %FW-6-LOG_SUMMARY: 2 packets were dropped from 1.1.45.4:8 => 6.6.6.6:0 (target:class)-(ZP_OUTSIDE_INSIDE:class-default)


The first two lines show the permitted session; the second pair of lines show the dropped session. I haven’t had the chance yet to examine log entries for traffic types other than TCP/UDP/ICMP, but at first glance it looks like the log entries are formatted in a way that’s friendly to machine parsing.

My next step should probably be to get the Cisco Press eBook on ZBFW by the formidable Ivan Pepelnjak, which I’m a little embarassed about not having read.