diff --git a/.gitignore b/.gitignore index 5f3ce065..94f17612 100644 --- a/.gitignore +++ b/.gitignore @@ -6,6 +6,9 @@ __pycache__/ # C extensions *.so +#vscode file containing some env variables +launch.json + # Distribution / packaging .Python env/ diff --git a/.vscode/settings.json b/.vscode/settings.json index 2c91bcea..2c7b4d59 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -1,31 +1,31 @@ { - "files.exclude": { - "**/__pycache__": true - }, - "editor.codeActionsOnSave": { - "source.organizeImports": true - }, - "python.testing.unittestEnabled": false, - "python.testing.nosetestsEnabled": false, - "python.testing.pytestEnabled": true, - "python.testing.pytestArgs": ["--no-cov"], - "python.linting.enabled": true, - "python.linting.pylintEnabled": false, - "python.linting.banditEnabled": true, - "python.linting.banditArgs": ["-r", "--ini .bandit"], - "python.linting.flake8Enabled": true, - "python.linting.flake8CategorySeverity.E": "Warning", - "python.linting.mypyEnabled": false, - "python.formatting.provider": "black", - "editor.formatOnSave": true, - "restructuredtext.confPath": "${workspaceFolder}/docs", - "cSpell.words": [ - "Vlan", - "etree", - "nosuffix", - "onlink", - "pandevice", - "pan-os-python", - "refreshall" - ] -} + "files.exclude": { + "**/__pycache__": true + }, + "editor.codeActionsOnSave": { + "source.organizeImports": true + }, + "python.testing.unittestEnabled": false, + "python.testing.nosetestsEnabled": false, + "python.testing.pytestEnabled": true, + "python.testing.pytestArgs": ["--no-cov"], + "python.linting.enabled": true, + "python.linting.pylintEnabled": false, + "python.linting.banditEnabled": true, + "python.linting.banditArgs": ["-r", "--ini .bandit"], + "python.linting.flake8Enabled": true, + "python.linting.flake8CategorySeverity.E": "Warning", + "python.linting.mypyEnabled": false, + "python.formatting.provider": "black", + "editor.formatOnSave": true, + "restructuredtext.confPath": "${workspaceFolder}/docs", + "cSpell.words": [ + "Vlan", + "etree", + "nosuffix", + "onlink", + "pandevice", + "pan-os-python", + "refreshall" + ] + } \ No newline at end of file diff --git a/examples/prisma_access_create_remote_network.py b/examples/prisma_access_create_remote_network.py new file mode 100644 index 00000000..a9c3357e --- /dev/null +++ b/examples/prisma_access_create_remote_network.py @@ -0,0 +1,151 @@ +#!/usr/bin/env python + +# Copyright (c) 2022, Palo Alto Networks +# +# Permission to use, copy, modify, and/or distribute this software for any +# purpose with or without fee is hereby granted, provided that the above +# copyright notice and this permission notice appear in all copies. +# +# THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES +# WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF +# MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR +# ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES +# WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN +# ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF +# OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + +# Author: Bastien Migette + +""" +prisma_access_create_remote_network.py +========== + +This script is an example on how to create a prisma access Remote Network, +along with needed IPSEC Tunnel and IKEv2 Gateway. +To use the script, you need to replace the variables below with desired values. + +""" +import logging +import os +import sys + +# This is needed to import module from parent folder +curpath = os.path.dirname(os.path.abspath(__file__)) +sys.path[:0] = [os.path.join(curpath, os.pardir)] + + +from panos.panorama import Panorama +from panos.plugins import ( + CloudServicesPlugin, + RemoteNetwork, + RemoteNetworks, + Bgp, + AggBandwidth, +) +from panos.network import IkeGateway, IpsecTunnel +from panos.panorama import Template + +__author__ = "bmigette" + + +HOSTNAME = os.environ["PAN_HOSTNAME"] +USERNAME = os.environ["PAN_USERNAME"] +PASSWORD = os.environ["PAN_PASSWORD"] + +IPSEC_PEER = "1.2.3.4" +BGP_PEER = "1.2.3.4" +BGP_PEER_AS = 65123 +IPSEC_TUNNEL_NAME = "panos-sdk-tunnel" +IKE_GW = "panos-sdk-ikev2-gw" +IKE_PSK = "Secret123" +IKE_CRYPTO = "Generic-IKE-Crypto-Default" +IPSEC_CRYPTO = "Generic-IPSEC-Crypto-Default" +TEMPLATE = "Remote_Network_Template" + +REMOTE_NETWORK_NAME = "panos-sdk-rn" +# This is the Region that you put in the RN. A compute region can have multiple Regions +REMOTE_NETWORK_REGION = "eu-central-1" +# This is the Compute Region, used to get SPN list. You can use Panorama CLI to get available options +REMOTE_NETWORK_COMPUTEREGION = "europe-central" + + +def get_region_spn(remote_networks, region): + """This function will return first SPN from a given region name. + You should implement some logic here to get the correct SPN. + The script will break if the region has no SPN / BW allocated + + Args: + remote_networks (RemoteNetworks): RemoteNetworks Object + region (str): The region to get SPN from + + Returns: + str: spn name + """ + agg_bw = remote_networks.findall(AggBandwidth) + region_obj = agg_bw[0].find(region) + print(f"SPN for region {region}: {region_obj.spn_name_list[0]}") + return region_obj.spn_name_list[0] + + +def main(): + # Setting logging to debug the PanOS SDK + logging_format = "%(levelname)s:%(name)s:%(message)s" + # logging.basicConfig(format=logging_format, level=logging.DEBUG - 2) #Use this to be even more verbose + logging.basicConfig(format=logging_format, level=logging.DEBUG) + # 1 - let's create the panorama object that we want to modify. + pan = Panorama(HOSTNAME, USERNAME, PASSWORD) + + # 2 - Refreshing Prisma Access config + csp = pan.add(CloudServicesPlugin()) + csp.refresh() + + rn_template = pan.add(Template(name=TEMPLATE)) + rn_template.refresh() + # 3 - Getting the remote_networks object + remote_networks = csp.findall(RemoteNetworks)[0] + + # 4 - Creating IKEv2 GW and IPSEC Tunnels + # 4.1 - IKEv2 GW + gw = IkeGateway( + name=IKE_GW, + version="ikev2", + peer_ip_type="ip", + peer_ip_value=IPSEC_PEER, + peer_id_type="ipaddr", + peer_id_value=IPSEC_PEER, + auth_type="pre-shared-key", + pre_shared_key=IKE_PSK, + ikev2_crypto_profile=IKE_CRYPTO, + enable_liveness_check=True, + ) + rn_template.add(gw).create() + + # 4.2 - IPSEC Tunnel + ipsec_tun = IpsecTunnel( + name=IPSEC_TUNNEL_NAME, + ak_ike_gateway=IKE_GW, + ak_ipsec_crypto_profile=IPSEC_CRYPTO, + mk_remote_address=IPSEC_PEER, + ) + + rn_template.add(ipsec_tun).create() + + # 5 - Creating Remote Network + rn = RemoteNetwork( + name=REMOTE_NETWORK_NAME, + static_routes=["10.11.12.0/24"], + region=REMOTE_NETWORK_REGION, + spn_name=get_region_spn(remote_networks, REMOTE_NETWORK_COMPUTEREGION), + ipsec_tunnel=IPSEC_TUNNEL_NAME, + ) + bgp = Bgp(enable=True, peer_as=BGP_PEER_AS, peer_ip_address=BGP_PEER) + + rn.add(bgp) + remote_networks.add(rn).create() + # 6 - Commit + Push + # pan.commit_all(devicegroup="Remote_Network_Device_Group") #commit + push + pan.commit() # commit only + + +if __name__ == "__main__": + main() diff --git a/examples/prisma_access_list_RN_regions_bw.py b/examples/prisma_access_list_RN_regions_bw.py new file mode 100644 index 00000000..8d7a62a3 --- /dev/null +++ b/examples/prisma_access_list_RN_regions_bw.py @@ -0,0 +1,101 @@ +#!/usr/bin/env python + +# Copyright (c) 2022, Palo Alto Networks +# +# Permission to use, copy, modify, and/or distribute this software for any +# purpose with or without fee is hereby granted, provided that the above +# copyright notice and this permission notice appear in all copies. +# +# THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES +# WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF +# MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR +# ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES +# WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN +# ACTION OF CONTRACT, NEGLIGENCE OR OTpHER TORTIOUS ACTION, ARISING OUT OF +# OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + +# Author: Bastien Migette + +""" +prisma_access_list_RN_regions_bw.py +========== + +This script is an example on how to retrieve list of prisma access +remote networks locations and bandwidth allocation and print it. + +""" +__author__ = "bmigette" + + +import logging +import os +import sys + + +# This is needed to import module from parent folder +curpath = os.path.dirname(os.path.abspath(__file__)) +sys.path[:0] = [os.path.join(curpath, os.pardir)] + + +from panos.plugins import ( + CloudServicesPlugin, + RemoteNetwork, + RemoteNetworks, + AggBandwidth, + Region, +) +from panos.panorama import Panorama +from panos.base import PanDevice + + +curpath = os.path.dirname(os.path.abspath(__file__)) +sys.path[:0] = [os.path.join(curpath, os.pardir)] + + +HOSTNAME = os.environ["PAN_HOSTNAME"] +USERNAME = os.environ["PAN_USERNAME"] +PASSWORD = os.environ["PAN_PASSWORD"] + + +def main(): + # Setting logging to debug the PanOS SDK + logging_format = "%(levelname)s:%(name)s:%(message)s" + # logging.basicConfig(format=logging_format, level=logging.DEBUG - 2) #Use this to be even more verbose + logging.basicConfig(format=logging_format, level=logging.DEBUG) + # First, let's create the panorama object that we want to modify. + pan = Panorama(HOSTNAME, USERNAME, PASSWORD) + csp = pan.add(CloudServicesPlugin()) + + csp.refresh() + + rn = csp.findall(RemoteNetworks) + rnes = rn[0].findall(RemoteNetwork) + agg_bw = rn[0].findall(AggBandwidth) + + regions = agg_bw[0].findall(Region) + ### Print XML Dump of Prisma Config ### + print(csp.element_str()) + print(csp.about()) + + ### Print Remote networks name ### + print(" -- Remote Networks --") + for rne in rnes: + print( + f"{rne.name} - spn: {rne.spn_name}, region: {rne.region}, tunnel {rne.ipsec_tunnel}, subnets: {rne.subnets}" + ) + print( + f"{rne.name} - secondary_wan: {rne.secondary_wan_enabled}, secondary ipsec tunnel: {rne.secondary_ipsec_tunnel}" + ) + + ### Print Regions BW ### + print(f"Agg BW Enabled: {agg_bw[0].enabled}") + print(" -- Regions --") + print(regions) + for region in regions: + print( + f"Region: {region}, allocated_bw: {region.allocated_bw}, spns: {region.spn_name_list}" + ) + + +if __name__ == "__main__": + main() diff --git a/panos/base.py b/panos/base.py index 1cc2784f..3044893e 100644 --- a/panos/base.py +++ b/panos/base.py @@ -1002,6 +1002,8 @@ def _refresh_children(self, running_config=False, xml=None): import panos.panorama elif module_name == "policies": import panos.policies + elif module_name == "plugins": + import panos.plugins child = getattr(getattr(panos, module_name), class_name)() # Versioned objects need a PanDevice to get the version from, so diff --git a/panos/panorama.py b/panos/panorama.py index 38fc7432..88bd1ed9 100644 --- a/panos/panorama.py +++ b/panos/panorama.py @@ -396,6 +396,7 @@ class Panorama(base.PanDevice): "panorama.DeviceGroup", "panorama.Template", "panorama.TemplateStack", + "plugins.CloudServicesPlugin" ) def __init__( diff --git a/panos/plugins.py b/panos/plugins.py new file mode 100644 index 00000000..991378e6 --- /dev/null +++ b/panos/plugins.py @@ -0,0 +1,509 @@ +#!/usr/bin/env python + +# Copyright (c) 2022, Palo Alto Networks +# +# Permission to use, copy, modify, and/or distribute this software for any +# purpose with or without fee is hereby granted, provided that the above +# copyright notice and this permission notice appear in all copies. +# +# THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES +# WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF +# MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR +# ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES +# WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN +# ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF +# OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + + +"""Prisma Access module contains objects that exist in the 'Plugins/Cloud Services' tab in the Panorama GUI""" + + +import panos.errors as err +from panos.base import ENTRY, Root, VersionedPanObject, VersionedParamPath + + +class CloudServicesPlugin(VersionedPanObject): + """Prisma Access configuration base object + + Args: + all_traffic_to_dc(bool): Send All Traffic to DC Option + + """ + + ROOT = Root.DEVICE + SUFFIX = None + NAME = None + CHILDTYPES = ( + "plugins.RemoteNetworks", + "plugins.RoutingPreference", + ) + + def _setup(self): + # xpaths + self._xpaths.add_profile(value="/plugins/cloud_services") + + # params + params = [] + + params.append( + VersionedParamPath( + "all_traffic_to_dc", + vartype="yesno", + path="traffic-steering/All-Traffic-To-DC", + ) + ) + + self._params = tuple(params) + + +class AggBandwidth(VersionedPanObject): + """Prisma Access remote networks Aggregated Bandwidth configuration base object + + Args: + enabled(bool): Whether Aggregated BW mode is enabled or not + """ + + # TODO: Add support for QoS Here ? + ROOT = Root.DEVICE + SUFFIX = None + NAME = None + CHILDTYPES = ("plugins.Region",) + + def _setup(self): + # xpaths + self._xpaths.add_profile(value="/agg-bandwidth") + + # params + params = [] + + params.append(VersionedParamPath("enabled", vartype="yesno", path="enabled")) + + self._params = tuple(params) + + +class Region(VersionedPanObject): + """Prisma Access remote networks Aggregated Bandwidth configuration base object + + Args: + name(str): Region Name + allocated_bw(int): Allocated BW in Mbps + spn_name_list(list/str): Names of the SPN for the region + """ + + ROOT = Root.DEVICE + SUFFIX = ENTRY + CHILDTYPES = () + + def _setup(self): + # xpaths + self._xpaths.add_profile(value="/region") + + # params + params = [] + params.append( + VersionedParamPath("allocated_bw", vartype="int", path="allocated-bw") + ) + params.append( + VersionedParamPath("spn_name_list", path="spn-name-list", vartype="member") + ) + self._params = tuple(params) + + +class RemoteNetworks(VersionedPanObject): + """Prisma Access Remote-Networks configuration base object + + Args: + overlapped_subnets(bool): Whether or not overlapped subnets are enabled + template_stack(str): Remote Networks Template stack + device_group(str): Remote Networks device group + trusted_zones(list/str): Remote Networks trusted zones + udp_query_interval(int): DNS UDP Query interval + udp_query_attempts(int): DNS UDP Query attempts + """ + + ROOT = Root.DEVICE + NAME = None + SUFFIX = None + CHILDTYPES = ( + "plugins.RemoteNetwork", + "plugins.AggBandwidth", + "plugins.InternalDnsMatch", + "plugins.PrimaryPublicDNSServer", + "plugins.SecondaryPublicDNSServer", + ) + # TODO Add support for inbound remote network later + + def _setup(self): + # xpaths + self._xpaths.add_profile(value="/remote-networks") + + # params + params = [] + + params.append( + VersionedParamPath( + "overlapped_subnets", vartype="yesno", path="overlapped-subnets" + ) + ) + params.append(VersionedParamPath("template_stack", path="template-stack")) + params.append(VersionedParamPath("device_group", path="device-group")) + params.append( + VersionedParamPath("trusted_zones", vartype="member", path="trusted-zones") + ) + params.append( + VersionedParamPath( + "udp_query_interval", + vartype="int", + path="udp-queries/retries/interval", + default=2, + ) + ) + params.append( + VersionedParamPath( + "udp_query_attempts", + vartype="int", + path="udp-queries/retries/attempts", + default=5, + ) + ) + self._params = tuple(params) + + +class InternalDnsMatch(VersionedPanObject): + """Prisma Access remote-networks Internal DNS entry configuration base object + + Args: + domain_list(list/str): Internal Domains names + + """ + + ROOT = Root.DEVICE + NAME = None + SUFFIX = ENTRY + CHILDTYPES = ( + "plugins.PrimaryInternalDNSServer", + "plugins.SecondaryInternalDNSServer", + ) + + def _setup(self): + # xpaths + self._xpaths.add_profile(value="/internal-dns-match") + + # params + params = [] + + params.append( + VersionedParamPath("domain_list", vartype="member", path="domain-list") + ) + + self._params = tuple(params) + + +class DNSServerBase(VersionedPanObject): + """Abstract DNS Class, will be inherited for correct XPATH + + Args: + dns_server(str): IP of DNS Server + use-cloud-default(bool): Use cloud default DNS + same_as_internal(bool): Use same DNS server as Internal + """ + + ROOT = Root.DEVICE + NAME = None + + def __init__(self, *args, **kwargs): + if type(self) == DNSServerBase: + raise err.PanDeviceError("Do not instantiate class. Please use a subclass.") + super(DNSServerBase, self).__init__(*args, **kwargs) + + def add_dns_params(self, same_as_internal): + params = [] + + params.append(VersionedParamPath("dns_server", path="dns-server")) + params.append( + VersionedParamPath( + "use-cloud-default", vartype="exist", path="use_cloud_default" + ) + ) + if same_as_internal: + params.append( + VersionedParamPath( + "same_as_internal", vartype="exist", path="same-as-internal" + ) + ) + self._params = tuple(params) + + +class PrimaryInternalDNSServer(DNSServerBase): + """A primary Internal DNS Server for remote networks + + Args: + dns_server(str): IP of DNS Server + use_cloud_default(bool): Use cloud default DNS + """ + + def _setup(self): + # xpaths + self._xpaths.add_profile(value="/primary") + self.add_dns_params(False) + + +class SecondaryInternalDNSServer(DNSServerBase): + """A Secondary Internal DNS Server for remote networks + + Args: + dns_server(str): IP of DNS Server + use_cloud_default(bool): Use cloud default DNS + """ + + def _setup(self): + # xpaths + self._xpaths.add_profile(value="/dns-servers/secondary") + self.add_dns_params(False) + + +class PrimaryPublicDNSServer(DNSServerBase): + """A primary Public DNS Server for remote networks + + Args: + dns_server(str): IP of DNS Server + use_cloud_default(bool): Use cloud default DNS + same_as_internal(bool): Use same DNS server as Internal + """ + + def _setup(self): + # xpaths + self._xpaths.add_profile(value="/dns-servers/primary-public-dns") + self.add_dns_params(True) + + +class SecondaryPublicDNSServer(DNSServerBase): + """A secondary Internal DNS Server for remote networks + + Args: + dns_server(str): IP of DNS Server + use_cloud_default(bool): Use cloud default DNS + same_as_internal(bool): Use same DNS server as Internal + """ + + def _setup(self): + # xpaths + self._xpaths.add_profile(value="/dns-servers/secondary-public-dns") + self.add_dns_params(True) + + +class Bgp(VersionedPanObject): # TODO : shoud it be protcol-bgp ? + """Prisma Access BGP configuration object + + Args: + enable(bool): Whether BGP is enabled or not. + originate_default_route(bool): Originate default route + summarize_mobile_user_routes(bool): Summarize mobile users routes or not + do_not_export_routes(bool): Do not export routes + peer_as(int): Peer AS + peer_ip_address(str): Peer IP Address + local_ip_address(str): Local IP Address + secret(str): BGP Password + """ + + ROOT = Root.DEVICE + SUFFIX = None + CHILDTYPES = () + NAME = None + + def _setup(self): + # xpaths + self._xpaths.add_profile(value="/protocol/bgp") + + # params + params = [] + + params.append(VersionedParamPath("enable", vartype="yesno", path="enable")) + + params.append( + VersionedParamPath( + "originate_default_route", + vartype="yesno", + path="originate-default-route", + ) + ) + params.append( + VersionedParamPath( + "summarize_mobile_user_routes", + vartype="yesno", + path="summarize-mobile-user-routes", + ) + ) + params.append( + VersionedParamPath( + "do_not_export_routes", vartype="yesno", path="do-not-export-routes" + ) + ) + + params.append(VersionedParamPath("peer_as", vartype="int", path="peer-as")) + params.append(VersionedParamPath("peer_ip_address", path="peer-ip-address")) + params.append(VersionedParamPath("local_ip_address", path="local-ip-address")) + params.append(VersionedParamPath("secret", vartype="encrypted", path="secret")) + + self._params = tuple(params) + + +class BgpPeer(VersionedPanObject): + """Prisma Access BGP Peer configuration object + + Args: + same_as_primary(bool) Same AS as primary WAN Peer. + peer_ip_address(str): Peer IP Address + local_ip_address(str): Local IP Address + secret(str): BGP Password + + """ + + ROOT = Root.DEVICE + NAME = None + SUFFIX = None + CHILDTYPES = () + + def _setup(self): + # xpaths + self._xpaths.add_profile(value="/bgp-peer") + + # params + params = [] + + params.append( + VersionedParamPath( + "same_as_primary", + vartype="yesno", + path="same-as-primary", + ) + ) + params.append(VersionedParamPath("peer_ip_address", path="peer-ip-address")) + params.append(VersionedParamPath("local_ip_address", path="local-ip-address")) + params.append(VersionedParamPath("secret", vartype="encrypted", path="secret")) + self._params = tuple(params) + + +class RoutingPreference(VersionedPanObject): + """Prisma Access routing-preference configuration base object + + Args: + default(bool): Default Routing Mode + hot_potato_routing(bool): Hot Potato Routing Mode + + """ + + ROOT = Root.DEVICE + NAME = None + SUFFIX = None + CHILDTYPES = () + + def _setup(self): + # xpaths + self._xpaths.add_profile(value="/routing-preference") + + # params + params = [] + + params.append( + VersionedParamPath("default", vartype="exist", path="default", default=True) + ) + params.append( + VersionedParamPath( + "hot_potato_routing", vartype="exist", path="Hot-Potato-Routing" + ) + ) + self._params = tuple(params) + + +class Link(VersionedPanObject): + """Prisma Access ECMP Links config object + + Args: + name(str): Link Name + ipsec_tunnel(str): IPSEC Tunnel Name + + """ + + # NAME = None #Not needed, default value + ROOT = Root.DEVICE + SUFFIX = ENTRY + CHILDTYPES = ("plugins.Bgp",) + + def _setup(self): + # xpaths + self._xpaths.add_profile(value="/link") + + # params + params = [] + + params.append(VersionedParamPath("ipsec_tunnel", path="ipsec-tunnel")) + + # TODO QOS HERE + self._params = tuple(params) + + +class RemoteNetwork(VersionedPanObject): + """Prisma Access Remote-Networks Onboarding configuration base object + + Args: + name(str): Remote Network Name + static_routes(list/str): Static Routes + region(str): Remote Network Region Name + license_type(str): License Type + ipsec_tunnel(str): IPSEC tunnel Name + secondary_wan_enabled(bool): Secondary WAN Enabled ? + ecmp_load_balancing(bool): Enabled ECMP or not + secondary_ipsec_tunnel(str): Name of secondary IPSEC tunnel + spn_name(str): SPN Name of the remote network + inbound_flow_over_pa_backbone(bool): inbound flow over pa backbone + """ + + ROOT = Root.DEVICE + SUFFIX = ENTRY + CHILDTYPES = ( + "plugins.Bgp", + "plugins.BgpPeer", + "plugins.Link", + ) + + def _setup(self): + # xpaths + self._xpaths.add_profile(value="/onboarding") + + # params + params = [] + + params.append( + VersionedParamPath("static_routes", vartype="member", path="subnets") + ) + params.append(VersionedParamPath("region", path="region")) + params.append(VersionedParamPath("license_type", path="license-type")) + params.append(VersionedParamPath("ipsec_tunnel", path="ipsec-tunnel")) + params.append( + VersionedParamPath( + "secondary_wan_enabled", vartype="yesno", path="secondary-wan-enabled" + ) + ) + params.append( + VersionedParamPath( + "ecmp_load_balancing", + path="ecmp-load-balancing", + values=("enabled-with-symmetric-return", "disabled"), + ) + ) + params.append( + VersionedParamPath("secondary_ipsec_tunnel", path="secondary-ipsec-tunnel") + ) + params.append(VersionedParamPath("spn_name", path="spn-name")) + params.append( + VersionedParamPath( + "inbound_flow_over_pa_backbone", + vartype="yesno", + path="inbound-flow-over-pa-backbone", + ) + ) + + # TODO Add QoS Support + + self._params = tuple(params) diff --git a/tests/test_standards.py b/tests/test_standards.py index f2d5dca1..4b97c7ff 100644 --- a/tests/test_standards.py +++ b/tests/test_standards.py @@ -16,6 +16,7 @@ from panos import panorama from panos import policies from panos import predefined +from panos import plugins # Versioning constants. @@ -36,6 +37,7 @@ network.PhysicalInterface, network.RedistributionProfileBase, network.Subinterface, + plugins.DNSServerBase, ] # Pull in the classes to omit. for pkg in (panos, errors, base): @@ -52,7 +54,9 @@ UNNAMED_OBJECTS = [] VERSIONED_PAN_OBJECTS = [] CLASSIC_PAN_OBJECTS = [] -for pkg in (device, firewall, ha, network, objects, panorama, policies, predefined): +PANORAMA_OBJECTS = ["plugins.CloudServicesPlugin"] + +for pkg in (device, firewall, ha, network, objects, panorama, policies, predefined, plugins): for name, cls in inspect.getmembers(pkg, inspect.isclass): if not cls.__module__.startswith("panos"): continue @@ -257,6 +261,9 @@ def test_firewall_object_childtypes(panobj): pytest.skip("Skipping panorama specific classes for firewall test") cts = childtype_string(panobj) + + if cts in PANORAMA_OBJECTS: + pytest.skip("Skipping Panoroama-only objects") found = cts in firewall.Firewall.CHILDTYPES if not found: