diff --git a/ansible_collections/arista/avd/molecule/eos_designs_unit_tests/intended/configs/cv-pathfinder-edge.cfg b/ansible_collections/arista/avd/molecule/eos_designs_unit_tests/intended/configs/cv-pathfinder-edge.cfg index 6ae2633ebed..8423d00be99 100644 --- a/ansible_collections/arista/avd/molecule/eos_designs_unit_tests/intended/configs/cv-pathfinder-edge.cfg +++ b/ansible_collections/arista/avd/molecule/eos_designs_unit_tests/intended/configs/cv-pathfinder-edge.cfg @@ -15,11 +15,11 @@ flow tracking hardware service routing protocols model multi-agent ! ! -ip nat profile IE-DIRECT-NAT - ip nat source dynamic access-list ALLOW-ALL overload +ip nat profile NAT-IE-DIRECT + ip nat source dynamic access-list ACL-NAT-IE-DIRECT overload ! -ip nat profile IE-ZSCALER-NAT - ip nat source dynamic access-list ALLOW-ALL pool PORT-ONLY-POOL +ip nat profile NAT-IE-ZSCALER + ip nat source dynamic access-list ACL-NAT-IE-ZSCALER pool PORT-ONLY-POOL ! hostname cv-pathfinder-edge ! @@ -313,14 +313,14 @@ interface Ethernet2 no shutdown no switchport ip address 172.15.5.5/31 - ip nat service-profile IE-DIRECT-NAT + ip nat service-profile NAT-IE-DIRECT ! interface Ethernet2/1 description Colt_10555 no shutdown no switchport ip address 172.15.5.6/31 - ip nat service-profile IE-DIRECT-NAT + ip nat service-profile NAT-IE-DIRECT ! interface Ethernet3 description Comcast-5G_AF830 @@ -368,7 +368,7 @@ interface Tunnel100 description Internet Exit ZSCALER-EXIT-POLICY-1 PRI mtu 1394 ip address unnumbered Loopback0 - ip nat service-profile IE-ZSCALER-NAT + ip nat service-profile NAT-IE-ZSCALER tunnel mode ipsec tunnel source interface Ethernet3 tunnel destination 10.37.121.1 @@ -378,7 +378,7 @@ interface Tunnel101 description Internet Exit ZSCALER-EXIT-POLICY-1 SEC mtu 1394 ip address unnumbered Loopback0 - ip nat service-profile IE-ZSCALER-NAT + ip nat service-profile NAT-IE-ZSCALER tunnel mode ipsec tunnel source interface Ethernet3 tunnel destination 10.39.77.1 @@ -388,7 +388,7 @@ interface Tunnel102 description Internet Exit ZSCALER-EXIT-POLICY-1 TER mtu 1394 ip address unnumbered Loopback0 - ip nat service-profile IE-ZSCALER-NAT + ip nat service-profile NAT-IE-ZSCALER tunnel mode ipsec tunnel source interface Ethernet3 tunnel destination 10.50.9.1 @@ -489,7 +489,12 @@ monitor connectivity ip 10.50.9.1 url http://gateway.zscalerbeta.net/vpntest ! -ip access-list ALLOW-ALL +ip access-list ACL-NAT-IE-DIRECT + 10 deny ip host 172.15.5.5 any + 20 deny ip host 172.15.5.6 any + 30 permit ip any any +! +ip access-list ACL-NAT-IE-ZSCALER 10 permit ip any any ! ip routing diff --git a/ansible_collections/arista/avd/molecule/eos_designs_unit_tests/intended/configs/cv-pathfinder-edge1.cfg b/ansible_collections/arista/avd/molecule/eos_designs_unit_tests/intended/configs/cv-pathfinder-edge1.cfg index b18519283f6..55a04bb6600 100644 --- a/ansible_collections/arista/avd/molecule/eos_designs_unit_tests/intended/configs/cv-pathfinder-edge1.cfg +++ b/ansible_collections/arista/avd/molecule/eos_designs_unit_tests/intended/configs/cv-pathfinder-edge1.cfg @@ -15,11 +15,11 @@ flow tracking hardware service routing protocols model multi-agent ! ! -ip nat profile IE-DIRECT-NAT - ip nat source dynamic access-list ALLOW-ALL overload +ip nat profile NAT-IE-DIRECT + ip nat source dynamic access-list ACL-NAT-IE-DIRECT overload ! -ip nat profile IE-ZSCALER-NAT - ip nat source dynamic access-list ALLOW-ALL pool PORT-ONLY-POOL +ip nat profile NAT-IE-ZSCALER + ip nat source dynamic access-list ACL-NAT-IE-ZSCALER pool PORT-ONLY-POOL ! hostname cv-pathfinder-edge1 ! @@ -342,7 +342,7 @@ interface Ethernet3 no switchport ip address dhcp dhcp client accept default-route - ip nat service-profile IE-DIRECT-NAT + ip nat service-profile NAT-IE-DIRECT ! interface Ethernet52 description P2P_LINK_TO_SITE-HA-DISABLED-LEAF_Ethernet2 @@ -384,7 +384,7 @@ interface Tunnel100 description Internet Exit ZSCALER-EXIT-POLICY-1 PRI mtu 1394 ip address unnumbered Loopback0 - ip nat service-profile IE-ZSCALER-NAT + ip nat service-profile NAT-IE-ZSCALER tunnel mode ipsec tunnel source interface Ethernet3 tunnel destination 10.37.121.1 @@ -394,7 +394,7 @@ interface Tunnel101 description Internet Exit ZSCALER-EXIT-POLICY-1 SEC mtu 1394 ip address unnumbered Loopback0 - ip nat service-profile IE-ZSCALER-NAT + ip nat service-profile NAT-IE-ZSCALER tunnel mode ipsec tunnel source interface Ethernet3 tunnel destination 10.39.77.1 @@ -404,7 +404,7 @@ interface Tunnel102 description Internet Exit ZSCALER-EXIT-POLICY-1 TER mtu 1394 ip address unnumbered Loopback0 - ip nat service-profile IE-ZSCALER-NAT + ip nat service-profile NAT-IE-ZSCALER tunnel mode ipsec tunnel source interface Ethernet3 tunnel destination 10.50.9.1 @@ -414,7 +414,7 @@ interface Tunnel110 description Internet Exit ZSCALER-EXIT-POLICY-2 PRI mtu 1394 ip address unnumbered Loopback0 - ip nat service-profile IE-ZSCALER-NAT + ip nat service-profile NAT-IE-ZSCALER tunnel mode ipsec tunnel source interface Ethernet3 tunnel destination 10.37.121.1 @@ -424,7 +424,7 @@ interface Tunnel111 description Internet Exit ZSCALER-EXIT-POLICY-2 SEC mtu 1394 ip address unnumbered Loopback0 - ip nat service-profile IE-ZSCALER-NAT + ip nat service-profile NAT-IE-ZSCALER tunnel mode ipsec tunnel source interface Ethernet3 tunnel destination 10.39.77.1 @@ -434,7 +434,7 @@ interface Tunnel112 description Internet Exit ZSCALER-EXIT-POLICY-2 TER mtu 1394 ip address unnumbered Loopback0 - ip nat service-profile IE-ZSCALER-NAT + ip nat service-profile NAT-IE-ZSCALER tunnel mode ipsec tunnel source interface Ethernet3 tunnel destination 10.50.9.1 @@ -554,8 +554,13 @@ ip access-list TEST-IPV4-ACL-WITH-IP-FIELDS-IN_Ethernet1_49.3 15 deny ip any host 172.24.49.3 permit ip host 172.24.49.2 host 172.24.49.3 ! -ip access-list ALLOW-ALL - 10 permit ip any any +ip access-list ACL-NAT-IE-DIRECT + 10 deny ip any 5.0.0.0/24 + 20 permit ip any any +! +ip access-list ACL-NAT-IE-ZSCALER + 10 permit ip any 10.0.0.0/24 + 20 deny ip any any ! ip routing ip routing vrf ATTRACTED-VRF-FROM-UPLINK diff --git a/ansible_collections/arista/avd/molecule/eos_designs_unit_tests/intended/configs/cv-pathfinder-transit1A.cfg b/ansible_collections/arista/avd/molecule/eos_designs_unit_tests/intended/configs/cv-pathfinder-transit1A.cfg index 342f7d2bf0f..7e2c7c9b64e 100644 --- a/ansible_collections/arista/avd/molecule/eos_designs_unit_tests/intended/configs/cv-pathfinder-transit1A.cfg +++ b/ansible_collections/arista/avd/molecule/eos_designs_unit_tests/intended/configs/cv-pathfinder-transit1A.cfg @@ -17,8 +17,8 @@ service routing protocols model multi-agent ip as-path access-list ASPATH-WAN permit 65000 any ! ! -ip nat profile IE-DIRECT-NAT - ip nat source dynamic access-list ALLOW-ALL overload +ip nat profile NAT-IE-DIRECT + ip nat source dynamic access-list ACL-NAT-IE-DIRECT overload ! hostname cv-pathfinder-transit1A ! @@ -310,7 +310,7 @@ interface Ethernet2.42 no shutdown encapsulation dot1q vlan 666 ip address 172.16.6.6/31 - ip nat service-profile IE-DIRECT-NAT + ip nat service-profile NAT-IE-DIRECT ! interface Ethernet52 description P2P_LINK_TO_SITE-HA-ENABLED-LEAF1_Ethernet1 @@ -413,8 +413,9 @@ monitor connectivity local-interfaces SET-Ethernet2.42 ip 123.12.3.4 ! -ip access-list ALLOW-ALL - 10 permit ip any any +ip access-list ACL-NAT-IE-DIRECT + 10 deny ip host 172.16.6.6 any + 20 permit ip any any ! ip routing ip routing vrf ATTRACTED-VRF-FROM-UPLINK diff --git a/ansible_collections/arista/avd/molecule/eos_designs_unit_tests/intended/structured_configs/cv-pathfinder-edge.yml b/ansible_collections/arista/avd/molecule/eos_designs_unit_tests/intended/structured_configs/cv-pathfinder-edge.yml index 4ff52f8bde6..3d263255fff 100644 --- a/ansible_collections/arista/avd/molecule/eos_designs_unit_tests/intended/structured_configs/cv-pathfinder-edge.yml +++ b/ansible_collections/arista/avd/molecule/eos_designs_unit_tests/intended/structured_configs/cv-pathfinder-edge.yml @@ -229,7 +229,7 @@ ethernet_interfaces: type: routed description: Colt_10555 ip_nat: - service_profile: IE-DIRECT-NAT + service_profile: NAT-IE-DIRECT - name: Ethernet2/1 peer_type: l3_interface ip_address: 172.15.5.6/31 @@ -237,7 +237,7 @@ ethernet_interfaces: type: routed description: Colt_10555 ip_nat: - service_profile: IE-DIRECT-NAT + service_profile: NAT-IE-DIRECT - name: Ethernet3 peer_type: l3_interface ip_address: 172.20.20.20/31 @@ -653,7 +653,24 @@ application_traffic_recognition: prefix_values: - 192.168.144.1/32 ip_access_lists: -- name: ALLOW-ALL +- name: ACL-NAT-IE-DIRECT + entries: + - sequence: 10 + action: deny + protocol: ip + source: 172.15.5.5 + destination: any + - sequence: 20 + action: deny + protocol: ip + source: 172.15.5.6 + destination: any + - sequence: 30 + action: permit + protocol: ip + source: any + destination: any +- name: ACL-NAT-IE-ZSCALER entries: - sequence: 10 action: permit @@ -662,15 +679,15 @@ ip_access_lists: destination: any ip_nat: profiles: - - name: IE-DIRECT-NAT + - name: NAT-IE-DIRECT source: dynamic: - - access_list: ALLOW-ALL + - access_list: ACL-NAT-IE-DIRECT nat_type: overload - - name: IE-ZSCALER-NAT + - name: NAT-IE-ZSCALER source: dynamic: - - access_list: ALLOW-ALL + - access_list: ACL-NAT-IE-ZSCALER pool_name: PORT-ONLY-POOL nat_type: pool pools: @@ -760,7 +777,7 @@ tunnel_interfaces: source_interface: Ethernet3 destination: 10.37.121.1 ipsec_profile: IE-ZSCALER-EXIT-POLICY-1-PROFILE - nat_profile: IE-ZSCALER-NAT + nat_profile: NAT-IE-ZSCALER - name: Tunnel101 description: Internet Exit ZSCALER-EXIT-POLICY-1 SEC mtu: 1394 @@ -769,7 +786,7 @@ tunnel_interfaces: source_interface: Ethernet3 destination: 10.39.77.1 ipsec_profile: IE-ZSCALER-EXIT-POLICY-1-PROFILE - nat_profile: IE-ZSCALER-NAT + nat_profile: NAT-IE-ZSCALER - name: Tunnel102 description: Internet Exit ZSCALER-EXIT-POLICY-1 TER mtu: 1394 @@ -778,7 +795,7 @@ tunnel_interfaces: source_interface: Ethernet3 destination: 10.50.9.1 ipsec_profile: IE-ZSCALER-EXIT-POLICY-1-PROFILE - nat_profile: IE-ZSCALER-NAT + nat_profile: NAT-IE-ZSCALER monitor_connectivity: interface_sets: - name: SET-Ethernet2 diff --git a/ansible_collections/arista/avd/molecule/eos_designs_unit_tests/intended/structured_configs/cv-pathfinder-edge1.yml b/ansible_collections/arista/avd/molecule/eos_designs_unit_tests/intended/structured_configs/cv-pathfinder-edge1.yml index 78ef4f34217..bcedbdc448d 100644 --- a/ansible_collections/arista/avd/molecule/eos_designs_unit_tests/intended/structured_configs/cv-pathfinder-edge1.yml +++ b/ansible_collections/arista/avd/molecule/eos_designs_unit_tests/intended/structured_configs/cv-pathfinder-edge1.yml @@ -235,7 +235,7 @@ ethernet_interfaces: description: ATT_404 dhcp_client_accept_default_route: true ip_nat: - service_profile: IE-DIRECT-NAT + service_profile: NAT-IE-DIRECT - name: Ethernet1/49 type: routed peer_type: l3_interface @@ -303,12 +303,29 @@ ip_access_lists: protocol: ip source: 172.24.49.2 destination: 172.24.49.3 -- name: ALLOW-ALL +- name: ACL-NAT-IE-DIRECT + entries: + - sequence: 10 + action: deny + protocol: ip + source: any + destination: 5.0.0.0/24 + - sequence: 20 + action: permit + protocol: ip + source: any + destination: any +- name: ACL-NAT-IE-ZSCALER entries: - sequence: 10 action: permit protocol: ip source: any + destination: 10.0.0.0/24 + - sequence: 20 + action: deny + protocol: ip + source: any destination: any ip_extcommunity_lists: - name: ECL-EVPN-SOO @@ -632,15 +649,15 @@ application_traffic_recognition: - 192.168.144.3/32 ip_nat: profiles: - - name: IE-DIRECT-NAT + - name: NAT-IE-DIRECT source: dynamic: - - access_list: ALLOW-ALL + - access_list: ACL-NAT-IE-DIRECT nat_type: overload - - name: IE-ZSCALER-NAT + - name: NAT-IE-ZSCALER source: dynamic: - - access_list: ALLOW-ALL + - access_list: ACL-NAT-IE-ZSCALER pool_name: PORT-ONLY-POOL nat_type: pool pools: @@ -760,7 +777,7 @@ tunnel_interfaces: source_interface: Ethernet3 destination: 10.37.121.1 ipsec_profile: IE-ZSCALER-EXIT-POLICY-1-PROFILE - nat_profile: IE-ZSCALER-NAT + nat_profile: NAT-IE-ZSCALER - name: Tunnel101 description: Internet Exit ZSCALER-EXIT-POLICY-1 SEC mtu: 1394 @@ -769,7 +786,7 @@ tunnel_interfaces: source_interface: Ethernet3 destination: 10.39.77.1 ipsec_profile: IE-ZSCALER-EXIT-POLICY-1-PROFILE - nat_profile: IE-ZSCALER-NAT + nat_profile: NAT-IE-ZSCALER - name: Tunnel102 description: Internet Exit ZSCALER-EXIT-POLICY-1 TER mtu: 1394 @@ -778,7 +795,7 @@ tunnel_interfaces: source_interface: Ethernet3 destination: 10.50.9.1 ipsec_profile: IE-ZSCALER-EXIT-POLICY-1-PROFILE - nat_profile: IE-ZSCALER-NAT + nat_profile: NAT-IE-ZSCALER - name: Tunnel110 description: Internet Exit ZSCALER-EXIT-POLICY-2 PRI mtu: 1394 @@ -787,7 +804,7 @@ tunnel_interfaces: source_interface: Ethernet3 destination: 10.37.121.1 ipsec_profile: IE-ZSCALER-EXIT-POLICY-2-PROFILE - nat_profile: IE-ZSCALER-NAT + nat_profile: NAT-IE-ZSCALER - name: Tunnel111 description: Internet Exit ZSCALER-EXIT-POLICY-2 SEC mtu: 1394 @@ -796,7 +813,7 @@ tunnel_interfaces: source_interface: Ethernet3 destination: 10.39.77.1 ipsec_profile: IE-ZSCALER-EXIT-POLICY-2-PROFILE - nat_profile: IE-ZSCALER-NAT + nat_profile: NAT-IE-ZSCALER - name: Tunnel112 description: Internet Exit ZSCALER-EXIT-POLICY-2 TER mtu: 1394 @@ -805,7 +822,7 @@ tunnel_interfaces: source_interface: Ethernet3 destination: 10.50.9.1 ipsec_profile: IE-ZSCALER-EXIT-POLICY-2-PROFILE - nat_profile: IE-ZSCALER-NAT + nat_profile: NAT-IE-ZSCALER monitor_connectivity: interface_sets: - name: SET-Ethernet3 diff --git a/ansible_collections/arista/avd/molecule/eos_designs_unit_tests/intended/structured_configs/cv-pathfinder-transit1A.yml b/ansible_collections/arista/avd/molecule/eos_designs_unit_tests/intended/structured_configs/cv-pathfinder-transit1A.yml index 3feef8a6374..183b5319f01 100644 --- a/ansible_collections/arista/avd/molecule/eos_designs_unit_tests/intended/structured_configs/cv-pathfinder-transit1A.yml +++ b/ansible_collections/arista/avd/molecule/eos_designs_unit_tests/intended/structured_configs/cv-pathfinder-transit1A.yml @@ -267,7 +267,7 @@ ethernet_interfaces: description: Colt_10666 encapsulation_dot1q_vlan: 666 ip_nat: - service_profile: IE-DIRECT-NAT + service_profile: NAT-IE-DIRECT - name: Ethernet1 type: routed peer_type: l3_interface @@ -727,19 +727,24 @@ application_traffic_recognition: prefix_values: - 192.168.144.1/32 ip_access_lists: -- name: ALLOW-ALL +- name: ACL-NAT-IE-DIRECT entries: - sequence: 10 + action: deny + protocol: ip + source: 172.16.6.6 + destination: any + - sequence: 20 action: permit protocol: ip source: any destination: any ip_nat: profiles: - - name: IE-DIRECT-NAT + - name: NAT-IE-DIRECT source: dynamic: - - access_list: ALLOW-ALL + - access_list: ACL-NAT-IE-DIRECT nat_type: overload router_service_insertion: enabled: true diff --git a/ansible_collections/arista/avd/molecule/eos_designs_unit_tests/inventory/group_vars/CV_PATHFINDER_TESTS.yml b/ansible_collections/arista/avd/molecule/eos_designs_unit_tests/inventory/group_vars/CV_PATHFINDER_TESTS.yml index 0b863e3d933..4b3e6d3727c 100644 --- a/ansible_collections/arista/avd/molecule/eos_designs_unit_tests/inventory/group_vars/CV_PATHFINDER_TESTS.yml +++ b/ansible_collections/arista/avd/molecule/eos_designs_unit_tests/inventory/group_vars/CV_PATHFINDER_TESTS.yml @@ -159,6 +159,7 @@ wan_router: dhcp_accept_default_route: true ip_address: dhcp peer_ip: 172.31.0.1 + dhcp_ip: 10.0.0.145 cv_pathfinder_internet_exit: policies: - name: ZSCALER-EXIT-POLICY-1 diff --git a/ansible_collections/arista/avd/molecule/eos_designs_unit_tests/inventory/host_vars/cv-pathfinder-edge1.yml b/ansible_collections/arista/avd/molecule/eos_designs_unit_tests/inventory/host_vars/cv-pathfinder-edge1.yml index e0927ccc6a2..c1632b7fceb 100644 --- a/ansible_collections/arista/avd/molecule/eos_designs_unit_tests/inventory/host_vars/cv-pathfinder-edge1.yml +++ b/ansible_collections/arista/avd/molecule/eos_designs_unit_tests/inventory/host_vars/cv-pathfinder-edge1.yml @@ -11,3 +11,41 @@ wan_route_servers: - hostname: cv-pathfinder-pathfinder1 - hostname: cv-pathfinder-pathfinder2 + +# Testing overriding internet exit acls using custom rules with substitution +ipv4_acls: + - name: TEST-IPV4-ACL-WITH-IP-FIELDS-IN + entries: + - sequence: 15 + action: deny + protocol: ip + source: any + destination: interface_ip + - action: permit + protocol: ip + source: peer_ip + destination: interface_ip + - name: ACL-NAT-IE-ZSCALER + entries: + - sequence: 10 + action: permit + protocol: ip + source: any + destination: 10.0.0.0/24 + - sequence: 20 + action: deny + protocol: ip + source: any + destination: any + - name: ACL-NAT-IE-DIRECT + entries: + - sequence: 10 + action: deny + protocol: ip + source: any + destination: 5.0.0.0/24 + - sequence: 20 + action: permit + protocol: ip + source: any + destination: any diff --git a/python-avd/pyavd/_eos_designs/shared_utils/misc.py b/python-avd/pyavd/_eos_designs/shared_utils/misc.py index 9e18889fe7b..de9382097af 100644 --- a/python-avd/pyavd/_eos_designs/shared_utils/misc.py +++ b/python-avd/pyavd/_eos_designs/shared_utils/misc.py @@ -8,7 +8,7 @@ from typing import TYPE_CHECKING from ..._errors import AristaAvdError, AristaAvdMissingVariableError -from ..._utils import default, get, get_item +from ..._utils import default, get from ...j2filters import convert_dicts, natural_sort, range_expand if TYPE_CHECKING: @@ -365,15 +365,15 @@ def new_network_services_bgp_vrf_config(self: SharedUtils) -> bool: return get(self.hostvars, "new_network_services_bgp_vrf_config", default=default_value) @cached_property - def ipv4_acls(self: SharedUtils) -> list: - return get(self.hostvars, "ipv4_acls", default=[]) + def ipv4_acls(self: SharedUtils) -> dict: + return {acl["name"]: acl for acl in get(self.hostvars, "ipv4_acls", default=[])} def get_ipv4_acl(self: SharedUtils, name: str, interface_name: str, *, interface_ip: str | None = None, peer_ip: str | None = None): """ Get one IPv4 ACL from "ipv4_acls" where fields have been substituted. If any substitution is done, the ACL name will get "_" appended. """ - org_ipv4_acl = get_item(self.ipv4_acls, "name", name, required=True, var_name=f"ipv4_acls[name={name}]") + org_ipv4_acl = get(self.ipv4_acls, name, required=True, org_key=f"ipv4_acls[name={name}]") # deepcopy to avoid inplace updates below from modifying the original. ipv4_acl = deepcopy(org_ipv4_acl) ip_replacements = { diff --git a/python-avd/pyavd/_eos_designs/structured_config/network_services/ip_access_lists.py b/python-avd/pyavd/_eos_designs/structured_config/network_services/ip_access_lists.py index 233ab4fa759..cd98b209ed8 100644 --- a/python-avd/pyavd/_eos_designs/structured_config/network_services/ip_access_lists.py +++ b/python-avd/pyavd/_eos_designs/structured_config/network_services/ip_access_lists.py @@ -4,9 +4,10 @@ from __future__ import annotations from functools import cached_property -from typing import TYPE_CHECKING +from typing import TYPE_CHECKING, Literal -from ...._utils import append_if_not_duplicate +from ...._errors import AristaAvdError +from ...._utils import append_if_not_duplicate, get from ....j2filters import natural_sort from .utils import UtilsMixin @@ -20,6 +21,85 @@ class IpAccesslistsMixin(UtilsMixin): Class should only be used as Mixin to a AvdStructuredConfig class """ + @cached_property + def _acl_internet_exit_zscaler(self: AvdStructuredConfigNetworkServices) -> dict: + return { + "name": self.get_internet_exit_nat_acl_name("zscaler"), + "entries": [ + { + "sequence": 10, + "action": "permit", + "protocol": "ip", + "source": "any", + "destination": "any", + } + ], + } + + @cached_property + def _acl_internet_exit_direct(self: AvdStructuredConfigNetworkServices) -> dict | None: + interface_ips = set() + for ie_policy in self._filtered_internet_exit_policies: + if ie_policy["type"] == "direct": + for connection in ie_policy["connections"]: + interface_ips.add(connection["source_interface_ip_address"]) + + if interface_ips: + interface_ips = sorted(interface_ips) + entries = [] + i = 0 + for i, interface_ip in enumerate(interface_ips): + entries.append( + { + "sequence": 10 + i * 10, + "action": "deny", + "protocol": "ip", + "source": interface_ip.split("/", maxsplit=1)[0], + "destination": "any", + } + ) + entries.append( + { + "sequence": 20 + i * 10, + "action": "permit", + "protocol": "ip", + "source": "any", + "destination": "any", + } + ) + + return { + "name": self.get_internet_exit_nat_acl_name("direct"), + "entries": entries, + } + + def _acl_internet_exit_user_defined(self: AvdStructuredConfigNetworkServices, internet_exit_policy_type: Literal["zscaler", "direct"]) -> dict | None: + acl_name = self.get_internet_exit_nat_acl_name(internet_exit_policy_type) + acl = get(self.shared_utils.ipv4_acls, acl_name) + if not acl: + return None + + # pass substitution fields as anything to check if acl requires substitution or not + acl = self.shared_utils.get_ipv4_acl(acl_name, "random", interface_ip="random", peer_ip="random") + if acl["name"] == acl_name: + # ACL doesn't need replacement + return [acl] + + # TODO: We still have one nat for all interfaces, need to also add logic to make nat per interface + # if acl needs substitution + raise AristaAvdError(f"ipv4_acls[name={acl_name}] field substitution is not supported for internet exit access lists") + + def _acl_internet_exit(self: AvdStructuredConfigNetworkServices, internet_exit_policy_type: Literal["zscaler", "direct"]) -> dict | None: + acls = self._acl_internet_exit_user_defined(internet_exit_policy_type) + if acls: + return acls + + if internet_exit_policy_type == "zscaler": + return [self._acl_internet_exit_zscaler] + if internet_exit_policy_type == "direct" and (acl := self._acl_internet_exit_direct): + return [acl] + return None + @cached_property def ip_access_lists(self: AvdStructuredConfigNetworkServices) -> list | None: """ @@ -31,20 +111,9 @@ def ip_access_lists(self: AvdStructuredConfigNetworkServices) -> list | None: for acl in interface_acls.values(): append_if_not_duplicate(ip_access_lists, "name", acl, context="IPv4 Access lists for SVI", context_keys=["name"]) - if self._filtered_internet_exit_policies: - ip_access_lists.append( - { - "name": "ALLOW-ALL", - "entries": [ - { - "sequence": 10, - "action": "permit", - "protocol": "ip", - "source": "any", - "destination": "any", - } - ], - } - ) + for ie_policy_type in self._filtered_internet_exit_policy_types: + acls = self._acl_internet_exit(ie_policy_type) + if acls: + ip_access_lists.extend(acls) return natural_sort(ip_access_lists, "name") or None diff --git a/python-avd/pyavd/_eos_designs/structured_config/network_services/ip_nat.py b/python-avd/pyavd/_eos_designs/structured_config/network_services/ip_nat.py index 6ded30ba256..f648390d681 100644 --- a/python-avd/pyavd/_eos_designs/structured_config/network_services/ip_nat.py +++ b/python-avd/pyavd/_eos_designs/structured_config/network_services/ip_nat.py @@ -29,9 +29,7 @@ def ip_nat(self: AvdStructuredConfigNetworkServices) -> dict | None: ip_nat = defaultdict(list) - policy_types = sorted(set(internet_exit_policy["type"] for internet_exit_policy in self._filtered_internet_exit_policies)) - - for policy_type in policy_types: + for policy_type in self._filtered_internet_exit_policy_types: pool, profile = self.get_internet_exit_nat_pool_and_profile(policy_type) if pool: ip_nat["pools"].append(pool) diff --git a/python-avd/pyavd/_eos_designs/structured_config/network_services/utils.py b/python-avd/pyavd/_eos_designs/structured_config/network_services/utils.py index 3b6e1a3f02e..ea513e3922d 100644 --- a/python-avd/pyavd/_eos_designs/structured_config/network_services/utils.py +++ b/python-avd/pyavd/_eos_designs/structured_config/network_services/utils.py @@ -588,11 +588,56 @@ def _local_path_groups_connected_to_pathfinder(self: AvdStructuredConfigNetworkS if any(wan_interface["connected_to_pathfinder"] for wan_interface in path_group["interfaces"]) ] + @cached_property + def _svi_acls(self: AvdStructuredConfigNetworkServices) -> dict[str, dict[str, dict]] | None: + """ + Returns a dict of + : { + "ipv4_acl_in": , + "ipv4_acl_out": , + } + Only contains interfaces with ACLs and only the ACLs that are set, + so use `get(self._svi_acls, f"{interface_name}.ipv4_acl_in")` to get the value. + """ + if not self.shared_utils.network_services_l3: + return None + + svi_acls = {} + for tenant in self.shared_utils.filtered_tenants: + for vrf in tenant["vrfs"]: + for svi in vrf["svis"]: + ipv4_acl_in = get(svi, "ipv4_acl_in") + ipv4_acl_out = get(svi, "ipv4_acl_out") + if ipv4_acl_in is None and ipv4_acl_out is None: + continue + + interface_name = f"Vlan{svi['id']}" + interface_ip: str | None = svi.get("ip_address_virtual") + if interface_ip is not None and "/" in interface_ip: + interface_ip = interface_ip.split("/", maxsplit=1)[0] + + if ipv4_acl_in is not None: + svi_acls.setdefault(interface_name, {})["ipv4_acl_in"] = self.shared_utils.get_ipv4_acl( + name=ipv4_acl_in, + interface_name=interface_name, + interface_ip=interface_ip, + ) + if ipv4_acl_out is not None: + svi_acls.setdefault(interface_name, {})["ipv4_acl_out"] = self.shared_utils.get_ipv4_acl( + name=ipv4_acl_out, + interface_name=interface_name, + interface_ip=interface_ip, + ) + + return svi_acls + def get_internet_exit_nat_profile_name(self: AvdStructuredConfigNetworkServices, internet_exit_policy_type: Literal["zscaler", "direct"]) -> str: if internet_exit_policy_type == "zscaler": - return "IE-ZSCALER-NAT" + return "NAT-IE-ZSCALER" + return "NAT-IE-DIRECT" - return "IE-DIRECT-NAT" + def get_internet_exit_nat_acl_name(self: AvdStructuredConfigNetworkServices, internet_exit_policy_type: Literal["zscaler", "direct"]) -> str: + return f"ACL-{self.get_internet_exit_nat_profile_name(internet_exit_policy_type)}" def get_internet_exit_nat_pool_and_profile( self: AvdStructuredConfigNetworkServices, internet_exit_policy_type: Literal["zscaler", "direct"] @@ -614,7 +659,7 @@ def get_internet_exit_nat_pool_and_profile( "source": { "dynamic": [ { - "access_list": "ALLOW-ALL", + "access_list": self.get_internet_exit_nat_acl_name(internet_exit_policy_type), "pool_name": "PORT-ONLY-POOL", "nat_type": "pool", } @@ -622,14 +667,14 @@ def get_internet_exit_nat_pool_and_profile( }, } return pool, profile - if internet_exit_policy_type == "direct": + profile_name = self.get_internet_exit_nat_profile_name(internet_exit_policy_type) profile = { - "name": self.get_internet_exit_nat_profile_name(internet_exit_policy_type), + "name": profile_name, "source": { "dynamic": [ { - "access_list": "ALLOW-ALL", + "access_list": self.get_internet_exit_nat_acl_name(internet_exit_policy_type), "nat_type": "overload", } ] @@ -637,50 +682,9 @@ def get_internet_exit_nat_pool_and_profile( } return None, profile - return None, None - @cached_property - def _svi_acls(self: AvdStructuredConfigNetworkServices) -> dict[str, dict[str, dict]] | None: - """ - Returns a dict of - : { - "ipv4_acl_in": , - "ipv4_acl_out": , - } - Only contains interfaces with ACLs and only the ACLs that are set, - so use `get(self._svi_acls, f"{interface_name}.ipv4_acl_in")` to get the value. - """ - if not self.shared_utils.network_services_l3: - return None - - svi_acls = {} - for tenant in self.shared_utils.filtered_tenants: - for vrf in tenant["vrfs"]: - for svi in vrf["svis"]: - ipv4_acl_in = get(svi, "ipv4_acl_in") - ipv4_acl_out = get(svi, "ipv4_acl_out") - if ipv4_acl_in is None and ipv4_acl_out is None: - continue - - interface_name = f"Vlan{svi['id']}" - interface_ip: str | None = svi.get("ip_address_virtual") - if interface_ip is not None and "/" in interface_ip: - interface_ip = interface_ip.split("/", maxsplit=1)[0] - - if ipv4_acl_in is not None: - svi_acls.setdefault(interface_name, {})["ipv4_acl_in"] = self.shared_utils.get_ipv4_acl( - name=ipv4_acl_in, - interface_name=interface_name, - interface_ip=interface_ip, - ) - if ipv4_acl_out is not None: - svi_acls.setdefault(interface_name, {})["ipv4_acl_out"] = self.shared_utils.get_ipv4_acl( - name=ipv4_acl_out, - interface_name=interface_name, - interface_ip=interface_ip, - ) - - return svi_acls + def _filtered_internet_exit_policy_types(self: AvdStructuredConfigNetworkServices) -> list: + return sorted(set(internet_exit_policy["type"] for internet_exit_policy in self._filtered_internet_exit_policies)) @cached_property def _filtered_internet_exit_policies(self: AvdStructuredConfigNetworkServices) -> list: @@ -788,11 +792,21 @@ def get_direct_internet_exit_connections(self: AvdStructuredConfigNetworkService f"{wan_interface['name']} peer_ip needs to be set. When using wan interface " "for direct type internet exit, peer_ip is used for nexthop, and connectivity monitoring." ) + + # wan interface ip will be used for acl, hence raise error if ip is not available + if (ip_address := wan_interface.get("ip_address")) == "dhcp": + if not (ip_address := wan_interface.get("dhcp_ip")): + raise AristaAvdMissingVariableError( + f"{wan_interface['name']} 'dhcp_ip' needs to be set. When using WAN interface for 'direct' type Internet exit, " + "'dhcp_ip' is used in the NAT ACL." + ) + sanitized_interface_name = self.shared_utils.sanitize_interface_name(wan_interface["name"]) connections.append( { "type": "ethernet", "name": f"IE-{sanitized_interface_name}", + "source_interface_ip_address": ip_address, "monitor_name": f"IE-{sanitized_interface_name}", "monitor_host": wan_interface["peer_ip"], "next_hop": wan_interface["peer_ip"],