During my research of the ACL module of OnePK I found some interesting behaviors. I think this feature isn’t fully cooked as it has some definite room for improvement. The ACL module (found in `onep.policy`) doesn’t have a way to interact with existing ACLs and ACEs on an IOS device. The ACLs seem to be intended for on-the-fly ACL configuration but with a couple caveats:
- Applying an ACL to an interface overwrites the existing ACL for that direction. Interfaces must play by the rule: ‘One ACL per direction’.
- Make sure to use `OnepLifetime.ONEP_PERSISTENT` to keep ACLs applied after the OneP session is disconnected.
First I’ll cover ACL management with OneP, and then I’ll use the `onep.vty` module to show how to workaround some of the shortcomings.
Creating and Applying an ACL On-the-Fly
A use case that fits in well with OnePK ACL management is creating dynamic ACLs based on input from a separate application such as a IDS/IPS system. Let’s use this scenario for the first example: Our OnePK app received an alert (possibly a syslog or SNMP trap) that a device on the network is receiving an attack using a SSH exploit and we need to stop the bogus traffic. We know that SSH uses TCP port 22 and also that the end host receiving the attack has an IP address of 10.10.20.15/24. Here’s one way we can create an ACL to block that traffic:
from onep_connect import connect from onep.core.util import OnepConstants from onep.policy import Acl, L3Acl, L3Ace # Connect to a router inline with the attacker's traffic ne = connect('10.211.55.200', 'admin', 'admin') #specify the interface towards the attacker interface = ne.get_interface_by_name('thub2') # Create a IPv4 L3 ACL l3_acl = L3Acl(ne, OnepConstants.OnepAddressFamilyType.ONEP_AF_INET, L3Acl.OnepLifetime.ONEP_PERSISTENT) # Create ACE that matches the attacker's traffic l3_ace_10 = L3Ace(10, False) #False == deny l3_ace_10.protocol = OnepConstants.AclProtocol.TCP l3_ace_10.set_src_prefix_any() l3_ace_10.dst_prefix = '10.10.20.15' l3_ace_10.dst_prefix_len = 32 l3_ace_10.set_dst_port_range(22,22) #SSH Port # Create ACE to allow all other traffic l3_ace_20 = L3Ace(20, True) #True == permit l3_ace_20.protocol = OnepConstants.AclProtocol.ALL l3_ace_20.set_src_prefix_any() l3_ace_20.set_dst_prefix_any() # Add both ACEs to the ACL l3_acl.add_ace(l3_ace_10) l3_acl.add_ace(l3_ace_20) # Apply the ACL to the interface l3_acl.apply_to_interface(interface, Acl.Direction.ONEP_DIRECTION_IN) # End the onep session (ACL remains because we used a persistent lifetime) ne.disconnect()
There isn’t a lot of logging provided on the IOS device to tell us that the ACL was applied, but we can run a `show ip interface gi2` command at the enable prompt to see the following confirmation:
CSR1#sh ip interface gi2 | i access list Outgoing Common access list is not set Outgoing access list is not set Inbound Common access list is not set Inbound access list is onep-acl-18
Boom, there’s our dynamic ACL on the inbound direction of the ‘gi2’ interface. We could apply this interface in both directions if we used `Acl.Direction.ONEP_DIRECTION_BOTH` in the `.apply_to_interface()` method. Also, you guessed it, we can use `Acl.Direction.ONEP_DIRECTION_OUT` for traffic leaving that interface also. This ACL could also be applied to multiple interfaces (more than one Internet facing interface?) like this:
# Apply the ACL to multiple interfaces l3_acl.apply_to_interface(ne.get_interface_by_name('gi2'), Acl.Direction.ONEP_DIRECTION_IN) l3_acl.apply_to_interface(ne.get_interface_by_name('fa0/1'), Acl.Direction.ONEP_DIRECTION_IN)
Depending on our expectations for the life of the ACL, we can have it automatically removed at the end of the onep session by specifying `L3Acl.OnepLifetime.ONEP_TRANSIENT` during the creation of the L3Acl Object.
To see a more in-depth example that includes syslog parsing and a bi-directional ACE, check it out on the GitHub repo here.
Viewing and Modifying Existing ACLs
Support for interacting with existing ACLs isn’t directly built-in to OnePK, but we can use the `onep.vty` module to achieve some pretty close capabilities. Here’s a simple way to view existing ACLs:
from one_connect import connect from onep.vty.util import VtyHelper ne = connect('188.8.131.52', 'admin', 'admin') ne_vty = VtyHelper(ne) ne_vty.show('ip access-list')
Which gives the output:
Extended IP access list DEMO 10 permit ip 10.10.20.0 0.0.0.255 any 20 deny tcp any any eq smtp 30 permit tcp 192.168.10.0 0.0.0.255 any range www 443 Extended IP access list onep-acl-18 10 permit tcp any any range 22 22
Very cool, there’s our dynamically created ACL from the previous example along with an existing ACL on the router. This is returned to us as a string but some fancy parsing could be done in order to confirm compliance to policies. Using that same `VtyHelper` object, we can also modify the DEMO ACL by adding an ACE using the `.config()` method:
from one_connect import connect from onep.vty.util import VtyHelper ne = connect('184.108.40.206', 'admin', 'admin') ne_vty = VtyHelper(ne) ne_vty.vty_exec('conf t') ne_vty.vty_exec('ip acce ext DEMO') ne_vty.vty_exec('15 deny udp any any') ne_vty.vty_exec('no 20') ne_vty.vty_exec('end') ne_vty.show('ip access-list')
And we can see our added sequence #15 ACE and removed #20 sequence ACE in the DEMO ACL:
Extended IP access list DEMO 10 permit ip 10.10.20.0 0.0.0.255 any 15 deny udp any any 20 deny tcp any any eq smtp 30 permit tcp 192.168.10.0 0.0.0.255 any range www 443 Extended IP access list onep-acl-18 10 permit tcp any any range 22 22
With both of these ACL creation technique, don’t forget to save the config on the device with the VtyHelper `.commit()` method.
As you can see, dynamic ACL creation is very powerful especially since you can manipulate existing ACLs with the `VtyHelper` object. I think this is a good supplement in places where the onep SDK doesn’t have full feature coverage. It certainly does open the door to any configuration that can be done via CLI.