Midmarket and small business branch installations often present some unique problems and access for remote support can be a prevalent one. This is especially true when one wants to avoid opening unnecessary access from the Internet. Permitting access only from a jump-off point with a fixed IP address was a good approach; until the day came where it became unreachable during a customer crisis. Painful moments like these are the mother of new approaches.
So, how do we enable remote access from almost anywhere without increasing the attack footprint of a customer's router?
A few years ago, I was reading an old blog post by my friend Aaron Conaway where he talked about port knocking on Linux. This is a process where a predefined set of ports, accessed in a particular order, trigger access to a set of otherwise-closed services.
I became intrigued and started searching for people who had done this on IOS. I found a few posts, but all involved two processes that I prefer to avoid:
Logging all packets on an interface for tracking the access sequence. As soon as we add a log keyword for an interface Access Control List (ACL), CEF is disabled and the router's performance suffers.
Using a script to change the router's configuration to enable services. I'm not against using a script to change router configuration, but only if it's necessary. In this particular case, it isn't.
Using Control Plane Policing (CoPP) and lock-and-key dynamic ACLs, it isn't a difficult process to make this happen elegantly. If you're interested in giving it a try, here's how we get started.
All services that we want to permit are denied by default. Predefined knock triggers (UDP packets in this example) are permitted to reach the control plane. At this point they are dropped and logged by the CoPP configuration.
Once the knock sequence is verified, a dynamic ACL entry is created that permits traffic to the applicable services from the source address of the knock sequence.
We'll start by establishing a sequence of five pseudo-random ports for the knock sequence itself. These can be TCP or UDP ports, but I'll stick with UDP ports for this example.
We need to be sure that these ports are permitted by the inbound interface and/or firewall security mechanisms. They will be dropped once they reach the control plane. The process won't work if they are blocked before they reach this point because we are relying on CoPP logging for the trigger.
Next, we'll define the services that we want permitted after a successful knock. We'll use some common ones: SSH on port 22/tcp, and the AnyConnect access VPN in TLS/DTLS mode using ports 80/tcp, 443/tcp, and 443/udp.
Once these are defined, we'll define a CoPP policy to deny attempts to our knock sequence.
ip access-list extended ACL_CoPP_Knock permit object-group OG_Knock any any ! class-map match-any CM_CoPP_Knock match access-group name ACL_CoPP_Knock ! policy-map PM_CoPP class CM_CoPP_Knock drop ! control-plane host service-policy input PM_CoPP
Then, we add CoPP logging of denied packets to generate a set of syslog events when the elements of the sequence have been triggered.
class-map type logging match-any CM_CoPP_Log match packets dropped ! policy-map type logging PM_CoPP_Log class CM_CoPP_Log log ! control-plane host service-policy type logging input PM_CoPP_Log
Here's where we resurrect the old lock and key dynamic ACLs to apply a dynamic means to permit our remote access.
This has been tested using inbound ACLs on interfaces, using the Zone-based Policy Firewall (ZBF/ZPF) and using CoPP itself. Anything that can define traffic based on an extended ACL should be able to use this mechanism, but I haven't tested beyond these.
ip access-list extended ACL_Internet_In dynamic DACL_Internet_In permit object-group OG_Knock_Services any any deny object-group OG_Knock_Services any any permit ip any any
The "permit ip any any" statement is for proof of concept only. Definitely replace this with whatever permit/deny statements are necessary for your configuration.
Now to tie it all together, we enable sequence numbers in the router’s syslog entries and use an Embedded Event Manager (EEM) applet to detect the sequence, verify it, and add the appropriate entry to the dynamic ACL. No configuration changes to the router are made in the process. The sequence numbers are important to this application as the EEM applet uses them to verify that the ports were triggered in the correct order.
There are two key variables that we need to set for the script. They are the name of the extended ACL that the script will operate on, and the idle timeout for the dynamic ACL entry. If there is no traffic during the timeout period, the knock will need to be repeated to regain access.
service sequence-numbers ! event manager environment _knock_Services ACL_Internet_In event manager environment _knock_Timeout 1 ! event manager applet EEM_Knock event tag _knock_Tag_E1 syslog pattern ".*%CP-6-UDP.*46543.*$" event tag _knock_Tag_E2 syslog pattern ".*%CP-6-UDP.*28654.*$" event tag _knock_Tag_E3 syslog pattern ".*%CP-6-UDP.*40204.*$" event tag _knock_Tag_E4 syslog pattern ".*%CP-6-UDP.*57566.*$" event tag _knock_Tag_E5 syslog pattern ".*%CP-6-UDP.*34515.*$" trigger occurs 5 period 1 correlate event _knock_Tag_E1 and event _knock_Tag_E2 and event _knock_Tag_E3 and event _knock_Tag_E4 and event _knock_Tag_E5 action 0000 info type event reqinfo tag _knock_Tag_E1 action 0010 regexp “^([0-9]+):.*DROP.* ([0-9.]+)" "$_syslog_msg" _knock_RegEx_Result _knock_Sequence_E1 _knock_Source_E1 action 0020 info type event reqinfo tag _knock_Tag_E2 action 0030 regexp “^([0-9]+):.*DROP.* ([0-9.]+)" "$_syslog_msg" _knock_RegEx_Result _knock_Sequence_E2 _knock_Source_E2 action 0040 info type event reqinfo tag _knock_Tag_E3 action 0050 regexp “^([0-9]+):.*DROP.* ([0-9.]+)" "$_syslog_msg" _knock_RegEx_Result _knock_Sequence_E3 _knock_Source_E3 action 0060 info type event reqinfo tag _knock_Tag_E4 action 0070 regexp “^([0-9]+):.*DROP.* ([0-9.]+)" "$_syslog_msg" _knock_RegEx_Result _knock_Sequence_E4 _knock_Source_E4 action 0080 info type event reqinfo tag _knock_Tag_E5 action 0090 regexp “^([0-9]+):.*DROP.* ([0-9.]+)" "$_syslog_msg" _knock_RegEx_Result _knock_Sequence_E5 _knock_Source_E5 action 0100 if $_knock_Sequence_E1 lt $_knock_Sequence_E2 action 0110 if $_knock_Sequence_E2 lt $_knock_Sequence_E3 action 0120 if $_knock_Sequence_E3 lt $_knock_Sequence_E4 action 0130 if $_knock_Sequence_E4 lt $_knock_Sequence_E5 action 0140 if $_knock_Source_E1 eq $_knock_Source_E2 action 0150 if $_knock_Source_E2 eq $_knock_Source_E3 action 0160 if $_knock_Source_E3 eq $_knock_Source_E4 action 0170 if $_knock_Source_E4 eq $_knock_Source_E5 action 0180 cli command "enable" action 0190 cli command "access-template $_knock_Services D$_knock_Services host $_knock_Source_E1 any timeout $_knock_Timeout" action 0200 end action 0210 end action 0220 end action 0230 end action 0240 end action 0250 end action 0260 end action 0270 end action 0280 exit
Ideally, I’d like to be able to have this pull the event sequence from the OG_Knock object group. I suspect that this might be possible using a TCL script rather than an applet, but expedience is going to win out over elegance for the moment. The applet converter at http://www.marcuscom.com/convert_applet/will create a good beginning for the adventurous.
Thanks to Joe Clarke for his input on extracting variables from multiple trigger events and for the link to the applet-to-script converter.
Triggering the knock sequence itself requires an extra piece of software. This are available for almost all platforms.
This provides a poor man's two-factor authentication (2FA), but I wouldn't really bill it as this. This is mostly because the trigger ports are semi-fixed. This is more about reducing the attack footprint of the router while still enabling remote access for support. I use client-side SSH keys for SSH access, and cloud-based 2FA solutions for AnyConnect access in addition to this.