Monday, November 11, 2013

JNCIA Lesson 4 -- Routing Policies and Firewall Filters

There's a particularly corny scene in the movie "Firewall" where Harrison Ford's character takes over for a junior network admin when the bank he works for is being DoS'd -- or maybe it was just a brute-force password guessing attack, I can't remember now -- by an outside attacker. Peering intently at the screen, Ford thinks for a minute, then pecks out a sequence of commands on the screen, finishing with a smug, "That should hold them up for a while," to which the junior admin replies, "It would hold me up!" What makes the scene corny is that they catch just a second of the monitor on-camera, and I could see that Ford (or more likely, their technical consultant) had typed a simple Cisco access control list to block traffic from a specific source IP address. Any competent network admin can tell you that a serious attacker could easily repeat the attack from another address, and another, and another, ad infinitum, until the increasingly complex access control list alone causes as much trouble as the initial attack.

However, ACL's -- or Juniper's even more flexible and powerful firewall filters -- have a valid role in network security, and on JunOS, they provide for an even more mundane -- yet essential -- role in configuring dynamic routing. Let me start with a simple example: suppose you have a router at your main office location, with two branch offices connected through a private network provided by your service provider (Metro-Ethernet service, for example). You have multiple VLANs on each router which must be able to communicate with each VLAN on the other routers. The main office router has a single Internet connection to your upstream service provider. You want each of the branch office routers to be able to connect to the Internet through your main office router. When finished, the network should look something like this:



In the last lesson, we configured a simple network using OSPF to route between three LAN networks. This time, however, we have added a fourth route to our ISP. Generally, a small or even medium sized business doesn't share routes with their ISP. The ISP doesn't care about routes to your internal network (that's why RFC-1918 was created in the first place), nor does a typical small office router have enough memory or processing power to store the entire Internet's routing table (especially not if using RIP or OSPF, but that's a whole other topic we won't get into now). In fact, if you only have a single connection to your ISP, this is a perfect use for a static route. Router A, which is the only router connected to the upstream service provider, will have a static, default route. If only there were a way to share that route with routers B and C...

On a Cisco router, this would be a piece of cake: you would simply create the default route, then use the "default-information originate" command to tell OSPF to distribute the default route in your OSPF area. On a Juniper, it is (IMHO) more complicated than it needs to be, but it's not really THAT bad. First, just like a Cisco, we'll create the default route:

root@main3200# top

[edit]
root@main3200# set routing-options static route 0.0.0.0/0 next-hop 100.64.170.65

[edit]
root@main3200#


At this point, you should be able to ping onto the Internet (or to a test network beyond the "ISP" router) from Router A. However, from Router B or Router C, there is still no default route defined:

root@branch2-3200# run show route 172.16.0.1

[edit protocols ospf]
root@branch2-3200#


Next, we have to define a routing policy to redistribute this route into OSPF...:

root@main3200# set policy-options policy-statement ospf-default term 1 from route-filter 0.0.0.0/0 exact accept

[edit]
root@main3200#


...and then export that filter into OSPF:

root@main3200# set protocols ospf export ospf-default

[edit]
root@main3200# commit
commit complete

[edit]
root@main3200#


If you try again from Router C...:

root@branch2-3200# run show route 172.16.0.1

inet.0: 10 destinations, 10 routes (10 active, 0 holddown, 0 hidden)
+ = Active Route, - = Last Active, * = Both

0.0.0.0/0          *[OSPF/150] 00:01:16, metric 0, tag 0
                    > to 10.11.12.1 via ge-0/0/21.0

[edit protocols ospf]
root@branch2-3200#


Success!!! However, if you're like I was when I first figured this out, you are probably going, "That's the stupidest, most convoluted thing..."

Yeah, I get it, and I don't entirely disagree. Cisco and pretty much everyone else who makes the network gear that I've played with in the past assume that you *want* to share routes (that's what a router is for, after all, right?), and so their default policy is exactly that: when you enable a dynamic routing protocol, it's going to start advertising all the routes it can find, or at the very least, there will be switches ("default-information originate," "redistribute connected," "redistribute static," etc.) that turn on very broad swathes of networks with a single command. However, in such cases, it can be very easy to share more information than you intended, and in fact, can be rather difficult to filter out some of the networks that you don't want to share. JunOS, on the other hand, takes the exact opposite approach, and only shares routes that you explicitly tell the router that you want to share. From a security standpoint, I'd have to say the Juniper approach might -- just maybe -- be the better choice. And honestly, their filtering language is much more flexible and powerful than Cisco's ACL's, IMHO. So, enough with the sales pitch; let's dissect those commands and try to figure out exactly what they mean.

The first command we used was "set routing-options static route 0.0.0.0/0 next-hop 100.64.170.65" which is essentially telling the router, "if you don't know what route to take to reach a destination address, just send it to our default gateway at 100.64.170.65."

The last command was "set protocols ospf export ospf-default" which was telling the router that we want to export the routes defined in a route filter that we named "ospf-default" into our OSPF routing protocol. Again, pretty simple and straightforward.

The magic all happens in the second command, "set policy-options policy-statement ospf-default term 1 from route-filter 0.0.0.0/0 exact accept." There's a lot happening in this command, so let's dissect it a little further. In the first half, "set policy-options policy-statement ospf-default..." we are telling the router that we are going to create a filter, and that we want to name the filter "ospf-default." Next, we define the filter. A filter in JunOS can be a sequence of match-action statements, which is roughly analogous to "if-then" statements in a programming language. You can chain multiple statements together in JunOS to create complex, compound statements, just like you would in a programming language. We want to match basically any destination address, so the pseudo-code for what we want to accomplish looks more or less like this:

if destination-address matches 0.0.0.0/0 then
  forward the packet through the static route that matches the destination 0.0.0.0/0


Okay, that seems easy enough. What if we want to route anything matching 172.16.10.0/24 to one router, and route anything else to a second router? Let's try it. First, we'll configure a physical interface through which we will forward traffic:

root@main3200# set interfaces ge-0/0/19 unit 0 family ethernet-switching

[edit]
root@main3200# set interfaces ge-0/0/19 unit 0 family inet address 100.64.5.2/26

[edit]
root@main3200# show interfaces ge-0/0/19
unit 0 {
    family inet {
        address 100.64.5.2/26;
    }
}

[edit]
root@main3200# commit
commit complete

[edit]
root@main3200#


I have connected a router to ge-0/0/19 on Router A to simulate another remote network (172.16.10.0/24) and connected a remote host on the outside interface of this router, using the IP address 172.16.10.20. On the outside interface of our "Internet" router, connected to ge-0/0/20, I have connected a host using the IP address 172.16.20.237. Connected to branch1-3200, I have a host using the IP address 192.168.2.32, and connected to branch1-3200, I have a host using the IP address 192.168.3.32. Right now, the two 192.168.x.32 hosts can ping each other, and the 172.16.20.237 host:

root@branch2-host1:~# ip addr | egrep -A3 eth0 | egrep "(eth0|inet) "
    inet 192.168.3.32/24 brd 192.168.3.255 scope global eth0
    inet 172.16.0.58/30 brd 172.16.0.59 scope global eth0.21
root@branch2-host1:~# ping -c1 192.168.2.32
PING 192.168.2.32 (192.168.2.32) 56(84) bytes of data.
64 bytes from 192.168.2.32: icmp_req=1 ttl=62 time=5.16 ms

--- 192.168.2.32 ping statistics ---
1 packets transmitted, 1 received, 0% packet loss, time 0ms
rtt min/avg/max/mdev = 5.163/5.163/5.163/0.000 ms
root@branch2-host1:~# ping -c1 172.16.20.237
PING 172.16.20.237 (172.16.20.237) 56(84) bytes of data.
64 bytes from 172.16.20.237: icmp_req=1 ttl=61 time=1.34 ms

--- 172.16.20.237 ping statistics ---
1 packets transmitted, 1 received, 0% packet loss, time 0ms
rtt min/avg/max/mdev = 1.342/1.342/1.342/0.000 ms
root@branch2-host1:~#


...and...:

me@branch1-host1:~$ ip addr | egrep eth0 | egrep "(eth0|inet) "
    inet 192.168.2.32/24 brd 192.168.2.255 scope global eth0
me@branch1-host1:~$ ping -c1 192.168.3.32
PING 192.168.3.32 (192.168.3.32) 56(84) bytes of data.
64 bytes from 192.168.3.32: icmp_req=1 ttl=62 time=2.93 ms

--- 192.168.3.32 ping statistics ---
1 packets transmitted, 1 received, 0% packet loss, time 0ms
rtt min/avg/max/mdev = 2.936/2.936/2.936/0.000 ms
me@branch1-host1:~$ ping -c1 172.16.20.237
PING 172.16.20.237 (172.16.20.237) 56(84) bytes of data.
64 bytes from 172.16.20.237: icmp_req=1 ttl=61 time=1.51 ms

--- 172.16.20.237 ping statistics ---
1 packets transmitted, 1 received, 0% packet loss, time 0ms
rtt min/avg/max/mdev = 1.510/1.510/1.510/0.000 ms
me@branch1-host1:~$


However, as of yet, there is no path to the host at 172.16.10.20:

root@branch2-host1:~# ping -c1 172.16.10.20
PING 172.16.10.20 (172.16.10.20) 56(84) bytes of data.
From 100.64.170.79 icmp_seq=1 Time to live exceeded

--- 172.16.10.20 ping statistics ---
1 packets transmitted, 0 received, +1 errors, 100% packet loss, time 0ms

root@branch2-host1:~#


...and...:

me@branch1-host1:~$ ping -c1 172.16.10.20
PING 172.16.10.20 (172.16.10.20) 56(84) bytes of data.
From 100.64.170.79 icmp_seq=1 Time to live exceeded

--- 172.16.10.20 ping statistics ---
1 packets transmitted, 0 received, +1 errors, 100% packet loss, time 0ms

me@branch1-host1:~$


This is expected, since we currently have a default route, redistributed through OSPF, from the main office router to both branch office routers. We now need to create a second route on the main office router to match 172.16.10.0/24, and redistribute it through OSPF to the branch office routers:

root@main3200# set routing-options static route 172.16.10.0/24 next-hop 100.64.5.1

[edit]
root@main3200# set policy-options policy-statement ospf-default term 2 from route-filter 172.16.10.0/24 exact accept

[edit]
root@main3200# set protocols ospf export ospf-default

[edit]
root@main3200#


We also need to tell the two branch routers how to reach the next-hop address of 100.64.5.1:

root@main3200# set protocols ospf area 0.0.0.0 interface ge-0/0/19.0 passive

[edit]
root@main3200# commit
commit complete

[edit]
root@main3200#


...and now we have both a default route through the "ISP" through ge-0/0/20 and to the host on 172.16.10.20 through ge-0/0/19, shared with all three office locations.

No comments:

Post a Comment