From 4842c76724baf7c1588d147af1e45e96200c165c Mon Sep 17 00:00:00 2001 From: Kevin Steves Date: Thu, 21 Jul 2016 09:55:04 -0700 Subject: [PATCH] Support for type=report API request. Joint effort with Andrew Stanton. --- bin/panxapi.py | 29 +++++++++++++-- doc/pan.xapi.rst | 36 ++++++++++++++++++- doc/panxapi.rst | 31 ++++++++++++++-- lib/pan/xapi.py | 94 ++++++++++++++++++++++++++++++++++++++++++++++++ 4 files changed, 183 insertions(+), 7 deletions(-) diff --git a/bin/panxapi.py b/bin/panxapi.py index 84d9e28..25f33ba 100755 --- a/bin/panxapi.py +++ b/bin/panxapi.py @@ -249,6 +249,20 @@ def main(): print_status(xapi, action) print_response(xapi, options) + if options['report'] is not None: + action = 'report' + if options['ad_hoc'] is not None: + extra_qs_used = True + vsys = options['vsys'][0] if len(options['vsys']) else None + xapi.report(reporttype=options['report'], + reportname=options['name'], + vsys=vsys, + interval=options['interval'], + timeout=options['job_timeout'], + extra_qs=options['ad_hoc']) + print_status(xapi, action) + print_response(xapi, options) + if options['op'] is not None: action = 'op' kwargs = { @@ -364,6 +378,8 @@ def parse_opts(): 'op': None, 'export': None, 'log': None, + 'report': None, + 'name': None, 'src': None, 'dst': None, 'move': None, @@ -416,6 +432,7 @@ def parse_opts(): 'group=', 'merge', 'nlogs=', 'skip=', 'filter=', 'interval=', 'timeout=', 'stime=', 'pcapid=', 'text', + 'report=', 'name=', ] try: @@ -474,6 +491,10 @@ def parse_opts(): options['export'] = arg elif opt == '--log': options['log'] = arg + elif opt == '--report': + options['report'] = arg + elif opt == '--name': + options['name'] = arg elif opt == '--src': options['src'] = arg elif opt == '--dst': @@ -843,6 +864,8 @@ def usage(): -o cmd execute operational command --export category export files --log log-type retrieve log files + --report report-type retrieve reports (dynamic|predefined|custom) + --name report-name report name --src src clone source node xpath export source file/path/directory --dst dst move/clone destination node name @@ -853,7 +876,7 @@ def usage(): --clone clone object at xpath, src xpath --override element override template object at xpath --vsys vsys VSYS for dynamic update/partial commit/ - operational command + operational command/report -l api_username[:api_password] -h hostname -P port URL port number @@ -864,8 +887,8 @@ def usage(): --nlogs num retrieve num logs --skip num skip num logs --filter filter log selection filter - --interval seconds log/commit job query interval - --timeout seconds log/commit job query timeout + --interval seconds log/commit/report job query interval + --timeout seconds log/commit/report job query timeout --stime time search time for threat-pcap --pcapid id threat-pcap ID -K api_key diff --git a/doc/pan.xapi.rst b/doc/pan.xapi.rst index 8fc48f2..9b1b5ff 100644 --- a/doc/pan.xapi.rst +++ b/doc/pan.xapi.rst @@ -69,6 +69,7 @@ DESCRIPTION - export file: ``type=export`` - dynamic object update: ``type=user-id`` - log retrieval: ``type=log`` + - report retrieval: ``type=report`` pan.xapi Constants ------------------ @@ -340,7 +341,7 @@ op(cmd=None, vsys=None, cmd_xml=False) request with the **cmd** argument and optional **vsys** argument. **cmd** is an XML document which represents the command to be executed. Commands and command options are XML elements, and command arguments - are XML data. **vsys** can be to to target the command to a specific + are XML data. **vsys** can be used to target the command to a specific Virtual System. When **cmd_xml** is *True* a CLI-style **cmd** argument is converted to @@ -467,6 +468,39 @@ log(self, log_type=None, nlogs=None, skip=None, filter=None, interval=None, time The default is to try forever (**timeout** is set to *None* or 0). +report(self, reporttype=None, reportname=None, vsys=None, interval=None, timeout=None) +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + + The report() method performs the ``type=report`` retrieve report API + request with the **reporttype** and **reportname** arguments. + **vsys** can be used to target the report to a specific Virtual + System. + + **reporttype** specifies the type of report to retrieve and can be: + + - dynamic + - predefined + - custom + + In some report requests, the XML API schedules a job to generate the + report data; the report() method will then periodically perform an + API request to determine if the job ID returned in the initial + request is complete and receive the report data. Additional arguments + to control the polling include: + + - **interval** + + A floating point number specifying the query interval in seconds + between each non-finished job status response. + + The default is 0.5 seconds. + + - **timeout** + + The maximum number of seconds to wait for the job to finish. + + The default is to try forever (**timeout** is set to *None* or 0). + extra_qs=None ~~~~~~~~~~~~~ diff --git a/doc/panxapi.rst b/doc/panxapi.rst index 784fc44..5cb985f 100644 --- a/doc/panxapi.rst +++ b/doc/panxapi.rst @@ -52,6 +52,8 @@ SYNOPSIS -o cmd execute operational command --export category export files --log log-type retrieve log files + --report report-type retrieve reports (dynamic|predefined|custom) + --name report-name report name --src src clone source node xpath export source file/path/directory --dst dst move/clone destination node name @@ -62,7 +64,7 @@ SYNOPSIS --clone clone object at xpath, src xpath --override element override template object at xpath --vsys vsys VSYS for dynamic update/partial commit/ - operational command + operational command/report -l api_username[:api_password] -h hostname -P port URL port number @@ -73,8 +75,8 @@ SYNOPSIS --nlogs num retrieve num logs --skip num skip num logs --filter filter log selection filter - --interval seconds log/commit job query interval - --timeout seconds log/commit job query timeout + --interval seconds log/commit/report job query interval + --timeout seconds log/commit/report job query timeout --stime time search time for threat-pcap --pcapid id threat-pcap ID -K api_key @@ -96,6 +98,7 @@ SYNOPSIS --version display version --help display usage + DESCRIPTION =========== @@ -270,6 +273,28 @@ DESCRIPTION Also see the **--nlogs**, **--skip** and **--filter** options. + ``--report`` *report-type* + Perform the ``type=report`` retrieve report API request with the + **report-type** argument. + + **report-type** (``reporttype=`` argument) specifies the type of + report to retrieve and can be: + + - dynamic + - predefined + - custom + + ``--name`` *report-name* + Specify the report name (``reportname=`` argument). This can also + be **custom-dynamic-report** to specify a custom dynamic report. + + The **--ad-hoc** option is used to specify additional report + arguments, for example: + + - cmd + - topn + - period + ``--src`` *src* Specify the source file, path or directory for **--export** and the source XPath for **--clone**. diff --git a/lib/pan/xapi.py b/lib/pan/xapi.py index 49ceeb8..255d43f 100644 --- a/lib/pan/xapi.py +++ b/lib/pan/xapi.py @@ -1051,6 +1051,100 @@ def log(self, log_type=None, nlogs=None, skip=None, filter=None, self._log(DEBUG2, 'sleep %.2f seconds', interval) time.sleep(interval) + def report(self, reporttype=None, reportname=None, vsys=None, + interval=None, timeout=None, extra_qs=None): + self.__set_api_key() + self.__clear_response() + + if interval is None: + interval = _job_query_interval + + try: + interval = float(interval) + if interval < 0: + raise ValueError + except ValueError: + raise PanXapiError('Invalid interval: %s' % interval) + + if timeout is not None: + try: + timeout = int(timeout) + if timeout < 0: + raise ValueError + except ValueError: + raise PanXapiError('Invalid timeout: %s' % timeout) + + query = {} + query['type'] = 'report' + query['key'] = self.api_key + if reporttype is not None: + query['reporttype'] = reporttype + if reportname is not None: + query['reportname'] = reportname + if vsys is not None: + query['vsys'] = vsys + if extra_qs is not None: + query = self.__merge_extra_qs(query, extra_qs) + + response = self.__api_request(query) + if not response: + raise PanXapiError(self.status_detail) + + if not self.__set_response(response): + raise PanXapiError(self.status_detail) + + # XXX 7.1 returns last job status in response; check for + # report first. + # response can be: + # custom: + # predefined: + # dynamic: + # custom dynamic <7.1: + # custom dynamic 7.1: + + if self.element_root.tag == 'report' or \ + self.element_root.find('./report') is not None: + return + + job = self.element_root.find('./result/job') + if job is None: + raise PanXapiError('no job or report element in ' + + 'type=report response') + + query = {} + query['type'] = 'report' + query['action'] = 'get' + query['key'] = self.api_key + query['job-id'] = job.text + self._log(DEBUG2, 'report job: %s', job.text) + + start_time = time.time() + + while True: + response = self.__api_request(query) + if not response: + raise PanXapiError(self.status_detail) + + if not self.__set_response(response): + raise PanXapiError(self.status_detail) + + status = self.element_root.find('./result/job/status') + if status is None: + raise PanXapiError('no status element in ' + + 'type=report&action=get response') + if status.text == 'FIN': + return + + self._log(DEBUG2, 'job %s status %s', job.text, status.text) + + if (timeout is not None and timeout != 0 and + time.time() > start_time + timeout): + raise PanXapiError('timeout waiting for ' + + 'job %s completion' % job.text) + + self._log(DEBUG2, 'sleep %.2f seconds', interval) + time.sleep(interval) + if __name__ == '__main__': # python -m pan.xapi [tag] [xpath] import pan.xapi