def get_layered_subinterface(self, mode, add=True): """Instantiate a regular subinterface type from this AbstractSubinterface Converts an abstract subinterface to a real subinterface by offering it a mode. Args: mode (str): Mode of the subinterface ('layer3' or 'layer2') add (bool): Add the newly instantiated subinterface to the base interface object Returns: Subinterface: A :class:`pandevice.network.Layer3Subinterface` or :class:`pandevice.network.Layer2Subinterface` instance, depending on the mode argument """ if self.parent is not None: if mode == "layer3": subintclass = Layer3Subinterface elif mode == "layer2": subintclass = Layer2Subinterface else: raise err.PanDeviceError( "Unknown layer passed to subinterface factory: %s" % mode) layered_subinterface = self.parent.find(self.name, subintclass) # Verify tag is correct if layered_subinterface is not None: if layered_subinterface.tag != self.tag: layered_subinterface.tag = self.tag else: if add: layered_subinterface = self.parent.add( subintclass(self.name, tag=self.tag)) else: return return layered_subinterface
def set_zone(self, zone_name, mode=None, refresh=False, update=False, running_config=False): raise err.PanDeviceError( "Unable to set zone on abstract subinterface because layer must be known to set zone" )
def _interface_name(self, interface): if issubclass(interface.__class__, basestring): return interface elif issubclass(interface.__class__, Interface): return interface.name else: raise err.PanDeviceError( "interface argument must be of type str or Interface", pan_device=self)
def set_shared_policy_synced(self, sync_status): if sync_status == "In Sync": self.shared_policy_synced = True elif sync_status == "Out of Sync": self.shared_policy_synced = False elif not sync_status: self.shared_policy_synced = None else: raise err.PanDeviceError("Unknown shared policy status: %s" % str(sync_status))
def refresh_interfaces(self): self.xapi.op('show interface "all"', cmd_xml=True) pconf = PanConfig(self.xapi.element_root) response = pconf.python() hw = {} interfaces = {} # Check if there is a response and result try: response = response['response']['result'] except KeyError as e: raise err.PanDeviceError( "Error reading response while refreshing interfaces", pan_device=self) if response: self._logger.debug("Refresh interfaces result: %s" % response) # Create a hw dict with all the 'hw' info hw_result = response.get('hw', {}) if hw_result is None: return hw_result = hw_result.get('entry', []) for hw_entry in hw_result: hw[hw_entry['name']] = hw_entry if_result = response.get('ifnet', {}) if if_result is None: return if_result = if_result.get('entry', []) for entry in if_result: try: router = entry['fwd'].split(":", 1)[1] except IndexError: router = entry['fwd'] interface = Interface(name=entry['name'], zone=entry['zone'], router=router, subnets=[entry['ip']], state=hw.get(entry['name'], {}).get('state')) interfaces[entry['name']] = interface else: raise err.PanDeviceError("Could not refresh interfaces", pan_device=self) self.interfaces = interfaces
def interface(name, *args, **kwargs): """Interface object factory Creates an interface object of type determined by the name of the interface. Args: name (str): Name of the interface to create (eg. ethernet1/1.5) mode (str): Mode of the interface. Possible values: layer3, layer2, virtual-wire, tap, ha, aggregate-group. Default: None Keyword Args: tag (int): Tag for the interface, aka vlan id Returns: Interface: An instantiated subclass of :class:`pandevice.network.Interface` """ name = str(name) if name.startswith("ethernet") and name.find(".") == -1: return EthernetInterface(name, *args, **kwargs) elif name.startswith("ae") and name.find(".") == -1: return AggregateInterface(name, *args, **kwargs) elif name.startswith("ethernet") or name.startswith("ae"): # Subinterface # Get mode from args args = list(args) if len(args) > 0: mode = args[0] del args[0] else: mode = kwargs.pop("mode", None) # Get tag from kwargs tag = kwargs.get("tag", None) if tag is None: # Determine tag from name tag = name.split(".")[-1] kwargs["tag"] = tag if mode == "layer3": return Layer3Subinterface(name, *args, **kwargs) elif mode == "layer2": return Layer2Subinterface(name, *args, **kwargs) else: return AbstractSubinterface(name, *args, **kwargs) elif name.startswith("vlan"): return VlanInterface(name, *args, **kwargs) elif name.startswith("loopback"): return LoopbackInterface(name, *args, **kwargs) elif name.startswith("tunnel"): return TunnelInterface(name, *args, **kwargs) else: raise err.PanDeviceError( "Can't identify interface type from name: %s" % name)
def show_system_resources(self): self.xapi.op(cmd="show system resources", cmd_xml=True) result = self.xapi.xml_root() regex = re.compile(r"load average: ([\d.]+).* ([\d.]+)%id.*Mem:.*?([\d.]+)k total.*?([\d]+)k free", re.DOTALL) match = regex.search(result) if match: """ return cpu, mem_free, load """ return { 'load': Decimal(match.group(1)), 'cpu': 100 - Decimal(match.group(2)), 'mem_total': int(match.group(3)), 'mem_free': int(match.group(4)), } else: raise err.PanDeviceError("Problem parsing show system resources", pan_device=self)
def _xpath(self): xpath = pandevice.XPATH_INTERFACES + "/%s" % (self.type, ) if self.type == "ethernet": if self.parent: xpath += "/entry[@name='%s']/%s/units/entry[@name='%s']" \ % (self.parent.name, self.parent.mode, self.name) root = ET.Element("entry", {"name": self.name}) settings = root else: match = re.search(r"(ethernet\d/\d{1,3})\.\d{1,4}", self.name) if match: xpath += "/entry[@name='%s']/%s/units/entry[@name='%s']"\ % (match.group(1), self.mode, self.name) root = ET.Element("entry", {"name": self.name}) settings = root else: xpath += "/entry[@name='%s']" % (self.name, ) root = ET.Element("entry", {"name": self.name}) settings = ET.SubElement(root, self.mode) elif self.type == "vlan": xpath += "/units/entry[@name='%s']" % (self.name, ) root = ET.Element("entry", {"name": self.name}) settings = root else: raise err.PanDeviceError("Unknown interface type: %s" % self.type) # For Layer 3 interfaces, apply any subnet configuration if self.mode == "layer3" and self.subnets: node = ET.SubElement(settings, "ip") for subnet in self.subnets: ET.SubElement(node, "entry", {"name": subnet}) # If there is a tag, apply it in the XML if self.tag: node = ET.SubElement(settings, "tag") node.text = self.tag return xpath, root
def system_info(self, all_info=False): """Get system information Returns: system information like version, platform, etc. """ self.xapi.op(cmd="<show><system><info></info></system></show>") pconf = PanConfig(self.xapi.element_result) system_info = pconf.python() self._logger.debug("Systeminfo: %s" % system_info) if not system_info: error_msg = 'Cannot detect device type, unable to get system info' self._logger.error(error_msg) raise err.PanDeviceError(error_msg, pan_device=self) if not all_info: version = system_info['result']['system']['sw-version'] model = system_info['result']['system']['model'] serial = system_info['result']['system']['serial'] return version, model, serial else: return system_info['result']
def __init__(self, *args, **kwargs): if type(self) == PhysicalInterface: raise err.PanDeviceError( "Do not instantiate class. Please use a subclass.") super(PhysicalInterface, self).__init__(*args, **kwargs)
def __init__(self, *args, **kwargs): if type(self) == NTPServer: raise err.PanDeviceError( "Do not instantiate class. Please use a subclass.") super(NTPServer, self).__init__(*args, **kwargs)
def refresh_devices(self, devices=(), only_connected=False, expand_vsys=True, include_device_groups=True, add=False, running_config=False): """Refresh device groups and devices using config and operational commands Uses operational command in addition to configuration to gather as much information as possible about Panorama connected devices. The operational commands used are 'show devices all/connected' and 'show devicegroups'. Information gathered about each device includes: - management IP address (can be different from hostname) - serial - version - high availability peer releationships - panorama connection status - device-group sync status Args: devices (list): Limit refresh to these serial numbers only_connected (bool): Ignore devices that are not 'connected' to Panorama (Default: False) expand_vsys (bool): Instantiate a Firewall object for every Vsys (Default: True) include_device_groups (bool): Instantiate :class:`pandevice.panorama.DeviceGroup` objects with Firewall objects added to them. add (bool): Add the new tree of instantiated DeviceGroup and Firewall objects to the Panorama config tree. Warning: This removes all current DeviceGroup and Firewall objects from the configuration tree, and all their children, so it is typically done before building a configuration tree. (Default: False) running_config (bool): Refresh devices from the running configuration (Default: False) Returns: list: If 'include_device_groups' is True, returns a list containing new DeviceGroup instances which contain new Firewall instances. Any Firewall that is not in a device-group is in the list with the DeviceGroup instances. If 'include_device_groups' is False, returns a list containing new Firewall instances. """ logger.debug(self.hostname + ": refresh_devices called") try: # Test if devices is iterable test_iterable = iter(devices) except TypeError: # This probably means a single device was passed in, not an iterable. # Convert to an iterable with a single item. devices = (devices, ) # Remove None from list of devices devices = [x for x in devices if x is not None] # Get the list of managed devices if only_connected: cmd = "show devices connected" else: cmd = "show devices all" devices_xml = self.op(cmd) devices_xml = devices_xml.find("result/devices") # Filter to only requested devices if devices: filtered_devices_xml = ET.Element("devices") for serial, vsys in [(d.serial, d.vsys) for d in devices]: if serial is None: continue entry = devices_xml.find("entry[@name='%s']" % serial) if entry is None: raise err.PanDeviceError( "Can't find device with serial %s attached to Panorama at %s" % (serial, self.hostname)) multi_vsys = yesno(entry.findtext("multi-vsys")) # Create entry if needed if filtered_devices_xml.find( "entry[@name='%s']" % serial) is None: entry_copy = deepcopy(entry) # If multivsys firewall with vsys defined, erase all vsys in filtered if multi_vsys and vsys != "shared" and vsys is not None: entry_copy.remove(entry_copy.find("vsys")) ET.SubElement(entry_copy, "vsys") filtered_devices_xml.append(entry_copy) # Get specific vsys if vsys != "shared" and vsys is not None: vsys_entry = entry.find("vsys/entry[@name='%s']" % vsys) if vsys_entry is None: raise err.PanDeviceError( "Can't find device with serial %s and" " vsys %s attached to Panorama at %s" % (serial, vsys, self.hostname)) vsys_section = filtered_devices_xml.find( "entry[@name='%s']/vsys" % serial) vsys_section.append(vsys_entry) devices_xml = filtered_devices_xml # Manipulate devices_xml so each vsys is a separate device if expand_vsys: original_devices_xml = deepcopy(devices_xml) devices_xml = ET.Element("devices") for entry in original_devices_xml: serial = entry.findtext("serial") for vsys_entry in entry.findall("vsys/entry"): new_vsys_device = deepcopy(entry) new_vsys_device.set("name", serial) ET.SubElement(new_vsys_device, "vsysid").text = vsys_entry.get("name") ET.SubElement( new_vsys_device, "vsysname").text = vsys_entry.findtext("display-name") devices_xml.append(new_vsys_device) # Create firewall instances firewall_instances = firewall.Firewall.refreshall_from_xml( devices_xml, refresh_children=not expand_vsys) if not include_device_groups: if add: self.removeall(firewall.Firewall) self.extend(firewall_instances) return firewall_instances # Create device-groups # Get the list of device groups from configuration XML api_action = self.xapi.show if running_config else self.xapi.get devicegroup_configxml = api_action( "/config/devices/entry[@name='localhost.localdomain']/device-group" ) devicegroup_configxml = devicegroup_configxml.find( "result/device-group") # Get the list of device groups from operational commands devicegroup_opxml = self.op("show devicegroups") devicegroup_opxml = devicegroup_opxml.find("result/devicegroups") # Combine the config XML and operational command XML to get a complete picture # of the device groups pandevice.xml_combine(devicegroup_opxml, devicegroup_configxml) devicegroup_instances = DeviceGroup.refreshall_from_xml( devicegroup_opxml, refresh_children=False) for dg in devicegroup_instances: dg_serials = [ entry.get("name") for entry in devicegroup_opxml.findall( "entry[@name='%s']/devices/entry" % dg.name) ] # Find firewall with each serial for dg_serial in dg_serials: all_dg_vsys = [ entry.get("name") for entry in devicegroup_opxml.findall( "entry[@name='%s']/devices/entry[@name='%s']/vsys/entry" % (dg.name, dg_serial)) ] # Collect the firewall serial entry to get current status information fw_entry = devicegroup_opxml.find( "entry[@name='%s']/devices/entry[@name='%s']" % (dg.name, dg_serial)) if not all_dg_vsys: # This is a single-context firewall, assume vsys1 all_dg_vsys = ["vsys1"] for dg_vsys in all_dg_vsys: fw = next((x for x in firewall_instances if x.serial == dg_serial and x.vsys == dg_vsys), None) if fw is None: # It's possible for device-groups to reference a serial/vsys that doesn't exist # In this case, create the FW instance if not only_connected: fw = firewall.Firewall(serial=dg_serial, vsys=dg_vsys) dg.add(fw) else: # Move the firewall to the device-group dg.add(fw) firewall_instances.remove(fw) fw.state.connected = yesno( fw_entry.findtext("connected")) fw.state.unsupported_version = yesno( fw_entry.findtext("unsupported-version")) fw.state.set_shared_policy_synced( fw_entry.findtext("shared-policy-status")) if add: for dg in devicegroup_instances: found_dg = self.find(dg.name, DeviceGroup) if found_dg is not None: # Move the firewalls to the existing devicegroup found_dg.removeall(firewall.Firewall) found_dg.extend(dg.children) else: # Devicegroup doesn't exist, add it self.add(dg) # Add firewalls that are not in devicegroups self.removeall(firewall.Firewall) self.extend(firewall_instances) return firewall_instances + devicegroup_instances
def xpath_panorama(self): raise err.PanDeviceError("Attempt to modify Panorama configuration on non-Panorama device")
def syncreboot(self, interval=5.0, timeout=600): """Block until reboot completes and return version of device""" import httplib # Validate interval and convert it to float if interval is not None: try: interval = float(interval) if interval < 0: raise ValueError except ValueError: raise err.PanDeviceError("Invalid interval: %s" % interval) self._logger.debug("Syncing reboot...") # Record start time to gauge timeout start_time = time.time() attempts = 0 is_rebooting = False time.sleep(interval) while True: try: # Try to get the device version (ie. test to see if firewall is up) attempts += 1 version = self.refresh_version() except (pan.xapi.PanXapiError, err.PanDeviceXapiError) as e: # Connection errors (URLError) are ok # Invalid cred errors are ok because FW auth system takes longer to start up # Other errors should be raised if not e.msg.startswith("URLError:") and not e.msg.startswith( "Invalid credentials."): # Error not related to connection issue. Raise it. raise e else: # Connection issue. The firewall is currently rebooting. is_rebooting = True self._logger.debug("Connection attempted: %s" % str(e)) self._logger.debug( "Device is not available yet. Connection attempts: %s" % str(attempts)) except httplib.BadStatusLine as e: # Connection issue. The firewall is currently rebooting. is_rebooting = True self._logger.debug("Connection attempted: %s" % str(e)) self._logger.debug( "Device is not available yet. Connection attempts: %s" % str(attempts)) else: # No exception... connection succeeded and device is up! # This could mean reboot hasn't started yet, so check that we had # a connection error prior to this success. if is_rebooting: self._logger.debug("Device is up! Running version %s" % version) return version else: self._logger.debug( "Device is up, but it probably hasn't started rebooting yet." ) # Check to see if we hit timeout if (self.timeout is not None and self.timeout != 0 and time.time() > start_time + self.timeout): raise err.PanDeviceError( "Timeout waiting for device to reboot") # Sleep and try again self._logger.debug("Sleep %.2f seconds", interval) time.sleep(interval)
def syncjob(self, response, interval=0.5): """Block until job completes and return result response: XML response tag from firewall when job is created :returns True if job completed successfully, False if not """ if interval is not None: try: interval = float(interval) if interval < 0: raise ValueError except ValueError: raise err.PanDeviceError('Invalid interval: %s' % interval) job = response.find('./result/job') if job is None: return False job = job.text self._logger.debug('Syncing job: %s', job) cmd = 'show jobs id "%s"' % job start_time = time.time() while True: try: self.xapi.op(cmd=cmd, cmd_xml=True) except pan.xapi.PanXapiError as msg: raise pan.xapi.PanXapiError('commit %s: %s' % (cmd, msg)) path = './result/job/status' status = self.xapi.element_root.find(path) if status is None: raise pan.xapi.PanXapiError('No status element in ' + "'%s' response" % cmd) if status.text == 'FIN': pconf = PanConfig(self.xapi.element_result) response = pconf.python() job = response['result'] if job is None: return job = job['job'] success = True if job['result'] == "OK" else False messages = job['details']['line'] if issubclass(messages.__class__, basestring): messages = [messages] # Create the results dict result = { 'success': success, 'result': job['result'], 'jobid': job['id'], 'user': job['user'], 'warnings': job['warnings'], 'starttime': job['tenq'], 'endtime': job['tfin'], 'messages': messages, } return result self._logger.debug('Job %s status %s', job, status.text) if (self.timeout is not None and self.timeout != 0 and time.time() > start_time + self.timeout): raise pan.xapi.PanXapiError('Timeout waiting for ' + 'job %s completion' % job) self._logger.debug('Sleep %.2f seconds', interval) time.sleep(interval)
def refresh_devices_from_panorama(self, devices=()): try: # Test if devices is iterable test_iterable = iter(devices) except TypeError: # This probably means a single device was passed in, not an iterable. # Convert to an iterable with a single item. devices = (devices, ) stats_by_ip = {} stats_by_host = {} devicegroup_stats_by_serial = {} template_stats_by_serial = {} # Get the list of managed devices self.xapi.op("show devices all", cmd_xml=True) pconf = PanConfig(self.xapi.element_root) response = pconf.python() try: for device in response['response']['result']['devices']['entry']: stats_by_ip[device['ip-address']] = device stats_by_host[device['ip-address']] = device stats_by_host[device['hostname']] = device # Populate the device objects with some of the data for device in devices: try: device.serial = stats_by_host[device.hostname]['serial'] device.connected_to_panorama = stats_by_host[ device.hostname]['connected'] except KeyError as e: raise err.PanDeviceError( "Can't determine serial for " "device", pan_device=device) # Ignore errors because it means there are no devices except KeyError: return {} # Get the list of device groups self.xapi.op("show devicegroups", cmd_xml=True) dg_element = self.xapi.element_result for dg in dg_element.findall("./devicegroups/entry"): for device in dg.findall("./devices/entry"): pconf = PanConfig(config=device) stats = pconf.python() # Save device stats stats = stats['entry'] # Save device serial serial = stats['serial'] # Save device ip-address ip = stats['ip-address'] # Save device's device-group dg_name = dg.get('name') # Save the device-group to the device's stats stats['devicegroup'] = dg_name devicegroup_stats_by_serial[serial] = stats stats_by_ip[ip]['devicegroup'] = dg_name # Set the device-group for each device for device in devices: if device.serial is not None: stats = devicegroup_stats_by_serial.get(device.serial) if stats is not None: device.devicegroup = stats['devicegroup'] sync_status = stats['shared-policy-status'] device.dg_in_sync = True if sync_status == "In Sync" else False return stats_by_ip