Access Control Lists
Access Control Lists (ACLs) are used to prevent access to specific network resources. There are 4 types of ACLs:
• Standard Numbered ACLs
• Standard Named ACLs
• Extended Numbered ACLs
• Extended Named ACLs
Standard ACLs can only filter access by source IPs. Let’s see some examples:
Standard numbered ACL with the old syntax are configured in global config mode:
R1(config)# access-list 1 deny 172.16.0.0 0.0.0.128 /* drop connections from these subnets */
R1(config)# access-list 1 permit 172.16.0.0 0.0.255.255 /* permit connections from this superset of networks */
R1(config)# access-list 1 permit 192.168.0.5 /* when no wildcard is used /32 is assumed so it is a single host */
R1(config)# access-list 1 remark // internal net ACL // /* we can add a remark per ACL for clarity sake */
R1(config)# int GigabitEthernet 0/0 /* ACLs must be applied on a given interface */
R1(config-if)# ip access-group 1 in /* choose whether it applies to IN/OUT traffic */
Each “access-list” statement above is an ACE (Access Control Entry).
We can use the old syntax above but we are better off using the new syntax because it allows us to edit existing ACLs:
R1(config)# ip access-list standard 1
R1(config-std-nacl)# 10 deny 172.16.0.0 0.0.0.128 /* drop connections from this subnets */
R1(config-std-nacl)# 20 permit 172.16.0.0 0.0.0.128 /* let in those from this superset of networks */
R1(config-std-nacl)# remark // internal net ACL // /* we can add a remark per ACL for clarity sake */
R1(config-std-nacl)# int GigabitEthernet 0/0 /* ACLs must be applied on a given interface */
R1(config-if)# ip access-group 1 in /* choose whether it applies to IN/OUT traffic */
For named standard ACLs the syntax is the same:
R1(config)# ip access-list standard vlan350
R1(config-std-nacl)# 10 deny 172.16.0.0 0.0.0.128 /* drop connections from this subnets */
R1(config-std-nacl)# 20 permit 172.16.0.0 0.0.0.128 /* let in those from this superset of networks */
R1(config-std-nacl)# remark // internal net ACL // /* we can add a remark per ACL for clarity sake */
R1(config-std-nacl)# int GigabitEthernet 0/0 /* ACLs must be applied on a given interface */
R1(config-if)# ip access-group vlan350 in /* choose whether it applies to IN/OUT traffic */
The integer before the permit/deny indicates the order in which we want the ACEs executed.
A few important remarks about standard ACLs:
• For standard numbered ACLs we can use two ranges: 1-99 and 1300–1999. So the maximum number of standard numbered ACLs we can have in a device is 800.
• The maximum number of ACEs in an ACL varies by device model but it should not be a constraint in normal circumstances.
• We can only use two ACLs per interface: one for incoming and one for outgoing traffic.
• All ACLs have a last implicit statement of “deny any” unless we explicitly end it with “permit any“.
• Standard ACLs (numbered & named) should be applied to the interface closest to the destination (the IP/network we are protecting).
As said before, if we use the old syntax with standard numbered ACLs we cannot modify them. Executing this for instance…
R1(config)# no access-list 1 deny 172.16.0.0 0.0.0.128
R1(config)# do show running-config | include access-list
R1(config)#
… will not delete this specific ACE. It will delete the whole ACL and we will have to re-enter it! If we use the new syntax we can add, update and delete specific ACEs:
R1(config)# ip access-list standard BLOCK_PC1
R1(config-std-nacl)# 10 deny host 192.168.1.5
R1(config-std-nacl)# 20 deny host 192.168.1.10
R1(config-std-nacl)# 30 permit 192.168.1.0 0.0.0.255
R1(config-std-nacl)# do show access-list BLOCK_PC1
Standard IP access list BLOCK_PC1
10 deny host 192.168.1.5
20 deny host 192.168.1.10
30 permit 192.168.1.0 0.0.0.255
40 deny any
R1(config-std-nacl)# no 20
R1(config-std-nacl)# do show access-list BLOCK_PC1
Standard IP access list BLOCK_PC1
10 deny host 192.168.1.5
30 permit 192.168.1.0 0.0.0.255
40 deny any
R1(config-std-nacl)# 20 deny host 192.168.1.11
R1(config-std-nacl)# do show access-list BLOCK_PC1
Standard IP access list BLOCK_PC1
10 deny host 192.168.1.5
20 deny host 192.168.1.11
30 permit 192.168.1.0 0.0.0.255
40 deny any
R1(config-std-nacl)# 20 deny host 192.168.1.15
R1(config-std-nacl)# do show access-list BLOCK_PC1
Standard IP access list BLOCK_PC1
10 deny host 192.168.1.5
20 deny host 192.168.1.15
30 permit 192.168.1.0 0.0.0.255
40 deny any
R1(config-std-nacl)# 25 deny host 192.168.1.18
R1(config-std-nacl)# do show access-list BLOCK_PC1
Standard IP access list BLOCK_PC1
10 deny host 192.168.1.5
20 deny host 192.168.1.15
25 deny host 192.168.1.18
30 permit 192.168.1.0 0.0.0.255
40 deny any
There is no good reason to use the old syntax!
If we want to add a new ACE between two existing ACEs but they are consecutively numbered (e.g. 20 & 21), we can use the resequencing command:
R1(config)# do show access-list BLOCK_PC2
Standard IP access list BLOCK_PC2
1 deny host 192.168.2.5
2 deny host 192.168.2.15
3 deny host 192.168.2.18
4 permit 192.168.2.0 0.0.0.255
5 deny any
R1(config)# ip access-list resequence BLOCK_PC2 10 10
R1(config)# do show access-list BLOCK_PC2
Standard IP access list BLOCK_PC2
10 deny host 192.168.2.5
20 deny host 192.168.2.15
30 deny host 192.168.2.18
40 permit 192.168.2.0 0.0.0.255
50 deny any
The first integer after the ACL-ID (number or name) is the starting number and the 2nd one is the increment. Now we can proceed and add ACEs in-between.
Extended numbered ACLs allow us to filter by source & destination IPs, protocol and destination port. We can use the ranges 100-199 and 2000-2699.
R1(config)# access-list 110 deny tcp 172.22.5.0 0.0.252.255 172.22.0.0 0.0.240.255 eq 22
R1(config)# access-list 110 permit tcp 172.22.0.0 0.0.255.255 172.22.0.0 0.0.240.255 eq 22
R1(config)# int GigabitEthernet 0/0
R1(config-if)# ip access-group 110 in
As seen above, the ACL number determines whether it is standard or extended in the old syntax. It is recommended to use the new syntax for the reasons explained before:
R1(config)# ip access-list extended 110
R1(config-ext-nacl)# 10 deny tcp 172.22.5.0 0.0.252.255 172.22.0.0 0.0.240.255 eq 22
R1(config-ext-nacl)# 20 permit tcp 172.22.0.0 0.0.255.255 172.22.0.0 0.0.240.255 eq 22
R1(config-ext-nacl)# 30 deny ip any any log /* log any packet that makes it here */
R1(config)# int GigabitEthernet 0/0 /* ACLs must be applied on a given interface */
R1(config-if)# ip access-group 110 in
Extended named ACL are syntactically the same:
R1(config)# ip access-list extended vlan400
R1(config-ext-nacl)# 10 deny tcp 172.22.5.0 0.0.252.255 172.22.0.0 0.0.240.255 eq 22
R1(config-ext-nacl)# 20 permit tcp 172.22.0.0 0.0.255.255 172.22.0.0 0.0.240.255 eq 22
R1(config-ext-nacl)# 30 deny ip any any log /* log any packet that makes it here */
R1(config)# int GigabitEthernet 0/0 /* ACLs must be applied on a given interface */
R1(config-if)# ip access-group vlan400 in
Extended ACLs allow us to filter by protocol or use “ip” for any.
R1(config)# ip access-list extended BLOCK_VLAN20
R1(config-ext-nacl)# permit ?
<0-255> An IP protocol number
ahp Authentication Header Protocol
eigrp Cisco’s EIGRP routing protocol
esp Encapsulation Security Payload
gre Cisco’s GRE tunneling
icmp Internet Control Message Protocol
igmp Internet Gateway Message Protocol
ip Any Internet Protocol
ipinip IP in IP tunneling
nos KA9Q NOS compatible IP over IP tunneling
object-group Service object group
ospf OSPF routing protocol
pcp Payload Compression Protocol
pim Protocol Independent Multicast
sctp Stream Control Transmission Protocol
tcp Transmission Control Protocol
udp User Datagram Protocol
For instance, this ACE will prevent 172.16.1.0/24 from pinging 172.16.2.0/24:
R1(config-ext-nacl)# deny icmp 172.16.1.0 0.0.0.255 172.16.2.0/24 0.0.0.255
Additionally, we can use the tcp/udp protocol for more fine-grained filtering. For example…
R1(config-ext-nacl)# deny tcp 172.16.0.0 0.0.255.255 172.16.32.0 0.0.0.255 eq 443
… would prevent the source network from accessing HTTPS in the target network. The “eq” is the most commonly used operator but we can also use:
• gt = greater than
• lt = lower than
• neq = not equal to
• range x y = from x to y
The port can be specified in either source/destination or both:
R1(config-ext-nacl)# deny tcp 172.16.0.0 0.0.255.255 gt 1023 172.16.32.0 0.0.0.255 eq 443
A final remark for extended ACLs: they should be added to the interface closest to the source. Why? Because if the packets are going to be dropped, we might as well drop them asap!
If we intend to tighten access to the router itself, it is more practical to apply the ACL to the vty lines instead of the different interfaces. See the example below:
R1(config)# ip access-list standard router_access
R1(config-std-nacl)# permit 192.168.1.0 0.0.0.255
R1(config-std-nacl)# deny any
R1(config-std-nacl)# line vty 0 15
R1(config-line# access-class router_access in
What we did is grant access the router’s Telnet/SSH lines only to 192.168.1.0/24. Pay attention to the access-class which applies only to vty lines!
To view current ACLs:
R1# show ip access-lists /* shows only IP ACLs */
Standard IP access list 1
10 deny 172.16.0.0 0.0.0.128
20 permit 172.16.0.0 0.0.255.255
30 permit 192.168.0.5
40 deny any
R1# show access-lists /* show ACLs for all protocols */
R1# show access-list 1 /* show ACL 1 */
R1# show access-list vlan350 /* show ACL vlan350 */
R1# show running-config | section access-list /* show all ACLs with remarks that are otherwise hidden */
R1# show interface gi 0/0 /* show interface and its ACLs */
ACLs are processed in the order shown in “show ip access-lists” or “show access-lists” from top to bottom. As soon as an ACE is fully matched (partial matches are not matches!), its permit or deny will be executed and no further ACEs will be processed.
Cisco iOS might re-order the ACEs for the sake of efficiency from more specific to less specific and it will show it in the output of “show”.