From ad8788226a3590f9ae0a1721f6702878348c0302 Mon Sep 17 00:00:00 2001 From: Oliver Kaiser Date: Thu, 7 Nov 2019 17:20:56 +0100 Subject: [PATCH 1/9] fixed some incorrect paths and added helper function to get primary device id from ftd hapair --- fireREST/__init__.py | 44 ++++++++++++++++++++++++++++---------------- 1 file changed, 28 insertions(+), 16 deletions(-) diff --git a/fireREST/__init__.py b/fireREST/__init__.py index 05af448..45174dd 100644 --- a/fireREST/__init__.py +++ b/fireREST/__init__.py @@ -195,7 +195,8 @@ def _refresh(self): msg = f'Could not connect to {self.hostname}. Max retries exceeded with url: {request}' self.logger.error(msg) except FireRESTRateLimitException: - self.logger.debug(f'API token refresh to {self.hostname} failed. Rate limit exceeded. Backing of for 10 seconds.') + self.logger.debug( + f'API token refresh to {self.hostname} failed. Rate limit exceeded. Backing of for 10 seconds.') sleep(10) self._login() except FireRESTApiException as exc: @@ -370,12 +371,12 @@ def get_device_id_by_name(self, device_name: str): def get_device_hapair_id_by_name(self, device_hapair_name: str): ''' - heloer function to retrieve device ha-pair id by name + helper function to retrieve device ha-pair id by name :param device_hapair_name: name of the ha-pair :return: id if ha-pair is found, None otherwise ''' - request = 'devicehapairs/ftddevicehapairs' - url = self._url(request) + request = '/devicehapairs/ftddevicehapairs' + url = self._url('config', request) response = self._get(url) for item in response: for ha_pair in item.json()['items']: @@ -383,6 +384,17 @@ def get_device_hapair_id_by_name(self, device_hapair_name: str): return ha_pair['id'] return None + def get_device_id_from_hapair(self, device_hapair_id: str): + ''' + helper function to retrieve device id from ha-pair + :param device_hapar_id: id of ha-pair + :return: id if device is found, None otherwise + ''' + request = f'/devicehapairs/ftddevicehapairs/{device_hapair_id}' + url = self._url('config', request) + response = self._get(url) + return response[0].json()['primary']['id'] + def get_nat_policy_id_by_name(self, nat_policy_name: str): ''' helper function to retrieve nat policy id by name @@ -691,12 +703,12 @@ def delete_ftd_sub_interface(self, device_id: str, interface_id: str): return self._delete(url) def create_ftd_ipv4_route(self, device_id: str, data: Dict): - request = f'/devices/devicerecords/{device_id}/ipv4staticroutes' + request = f'/devices/devicerecords/{device_id}/routing/ipv4staticroutes' url = self._url('config', request) return self._post(url, data) def get_ftd_ipv4_routes(self, device_id: str, expanded=False): - request = f'/devices/devicerecords/{device_id}/ipv4staticroutes' + request = f'/devices/devicerecords/{device_id}/routing/ipv4staticroutes' url = self._url('config', request) params = { 'expanded': expanded @@ -704,27 +716,27 @@ def get_ftd_ipv4_routes(self, device_id: str, expanded=False): return self._get(url, params) def get_ftd_ipv4_route(self, device_id: str, route_id: str): - request = f'/devices/devicerecords/{device_id}/ipv4staticroutes/{route_id}' + request = f'/devices/devicerecords/{device_id}/routing/ipv4staticroutes/{route_id}' url = self._url('config', request) return self._get(url) - def update_ftd_ipv4_route(self, device_id: str, data: Dict): - request = f'/devices/devicerecords/{device_id}/ipv4staticroutes' + def update_ftd_ipv4_route(self, device_id: str, route_id: str, data: Dict): + request = f'/devices/devicerecords/{device_id}/routing/ipv4staticroutes/{route_id}' url = self._url('config', request) return self._put(url, data) def delete_ftd_ipv4_route(self, device_id: str, route_id: str): - request = f'/devices/devicerecords/{device_id}/ipv4staticroutes/{route_id}' + request = f'/devices/devicerecords/{device_id}/routing/ipv4staticroutes/{route_id}' url = self._url('config', request) return self._delete(url) def create_ftd_ipv6_route(self, device_id: str, data: Dict): - request = f'/devices/devicerecords/{device_id}/ipv6staticroutes' + request = f'/devices/devicerecords/{device_id}/routing/ipv6staticroutes' url = self._url('config', request) return self._post(url, data) def get_ftd_ipv6_routes(self, device_id: str, expanded=False): - request = f'/devices/devicerecords/{device_id}/ipv6staticroutes' + request = f'/devices/devicerecords/{device_id}/routing/ipv6staticroutes' url = self._url('config', request) params = { 'expanded': expanded @@ -732,17 +744,17 @@ def get_ftd_ipv6_routes(self, device_id: str, expanded=False): return self._get(url, params) def get_ftd_ipv6_route(self, device_id: str, route_id: str): - request = f'/devices/devicerecords/{device_id}/ipv6staticroutes/{route_id}' + request = f'/devices/devicerecords/{device_id}/routing/ipv6staticroutes/{route_id}' url = self._url('config', request) return self._get(url) - def update_ftd_ipv6_route(self, device_id: str, data: Dict): - request = f'/devices/devicerecords/{device_id}/ipv6staticroutes' + def update_ftd_ipv6_route(self, device_id: str, route_id: str, data: Dict): + request = f'/devices/devicerecords/{device_id}/routing/ipv6staticroutes/{route_id}' url = self._url('config', request) return self._put(url, data) def delete_ftd_ipv6_route(self, device_id: str, route_id: str): - request = f'/devices/devicerecords/{device_id}/ipv6staticroutes/{route_id}' + request = f'/devices/devicerecords/{device_id}/routing/ipv6staticroutes/{route_id}' url = self._url('config', request) return self._delete(url) From 7e399e08c0889af5a598a2308952264449b1d7d0 Mon Sep 17 00:00:00 2001 From: Oliver Kaiser Date: Fri, 8 Nov 2019 13:19:28 +0100 Subject: [PATCH 2/9] fixed default limits --- fireREST/__init__.py | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/fireREST/__init__.py b/fireREST/__init__.py index 45174dd..5e5acd9 100644 --- a/fireREST/__init__.py +++ b/fireREST/__init__.py @@ -235,23 +235,25 @@ def _get_request(self, request: str, params=None, limit=25): GET Operation for FMC REST API. In case of authentication issues session will be refreshed :param request: URL of request that should be performed :param params: dict of parameters for http request + :param limit: set custom limit for paging. If not set, api will default to 25 :return: requests.Response object ''' if params is None: params = dict() + params['limit'] = limit try: response = requests.get(request, headers=self.headers, params=params, verify=self.verify_cert, timeout=self.timeout) if response.status_code == 401: if 'Access token invalid' in str(response.json()): self._refresh() - return self._get_request(request, params) + return self._get_request(request, params, limit) if response.status_code == 429: msg = f'GET operation {request} failed due to FMC rate limiting. Backing off for 10 seconds.' raise FireRESTRateLimitException(msg) except FireRESTRateLimitException: sleep(10) - return self._get_request(request, params) + return self._get_request(request, params, limit) return response def _get(self, request: str, params=None, limit=25): @@ -259,12 +261,13 @@ def _get(self, request: str, params=None, limit=25): GET Operation that supports paging for FMC REST API. In case of authentication issues session will be refreshed :param request: URL of request that should be performed :param params: dict of parameters for http request + :param limit: set custom limit for paging. If not set, api will default to 25 :return: list of requests.Response objects ''' if params is None: params = dict() responses = list() - response = self._get_request(request, params) + response = self._get_request(request, params, limit) responses.append(response) payload = response.json() if 'paging' in payload.keys(): @@ -272,7 +275,7 @@ def _get(self, request: str, params=None, limit=25): limit = int(payload['paging']['limit']) for i in range(1, pages, 1): params['offset'] = str(int(i) * limit) - response_page = self._get_request(request, params) + response_page = self._get_request(request, params, limit) responses.append(response_page) return responses From ed2c9b7fca5c08700f862357e941e3e4186387a5 Mon Sep 17 00:00:00 2001 From: Oliver Kaiser Date: Fri, 8 Nov 2019 13:20:11 +0100 Subject: [PATCH 3/9] set default paging to 100 --- fireREST/__init__.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/fireREST/__init__.py b/fireREST/__init__.py index 5e5acd9..131855b 100644 --- a/fireREST/__init__.py +++ b/fireREST/__init__.py @@ -230,12 +230,12 @@ def _delete(self, request: str, params=None): return response @RequestDebugDecorator('GET') - def _get_request(self, request: str, params=None, limit=25): + def _get_request(self, request: str, params=None, limit=100): ''' GET Operation for FMC REST API. In case of authentication issues session will be refreshed :param request: URL of request that should be performed :param params: dict of parameters for http request - :param limit: set custom limit for paging. If not set, api will default to 25 + :param limit: set custom limit for paging. If not set, api will default to 100 :return: requests.Response object ''' if params is None: @@ -256,12 +256,12 @@ def _get_request(self, request: str, params=None, limit=25): return self._get_request(request, params, limit) return response - def _get(self, request: str, params=None, limit=25): + def _get(self, request: str, params=None, limit=100): ''' GET Operation that supports paging for FMC REST API. In case of authentication issues session will be refreshed :param request: URL of request that should be performed :param params: dict of parameters for http request - :param limit: set custom limit for paging. If not set, api will default to 25 + :param limit: set custom limit for paging. If not set, api will default to 100 :return: list of requests.Response objects ''' if params is None: From 287e5f8f76a4b2002e7a813874b933573c93625e Mon Sep 17 00:00:00 2001 From: Oliver Kaiser Date: Tue, 12 Nov 2019 10:49:04 +0100 Subject: [PATCH 4/9] renamed static route functions --- fireREST/__init__.py | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/fireREST/__init__.py b/fireREST/__init__.py index 131855b..2b25399 100644 --- a/fireREST/__init__.py +++ b/fireREST/__init__.py @@ -705,12 +705,12 @@ def delete_ftd_sub_interface(self, device_id: str, interface_id: str): url = self._url('config', request) return self._delete(url) - def create_ftd_ipv4_route(self, device_id: str, data: Dict): + def create_ftd_ipv4staticroute(self, device_id: str, data: Dict): request = f'/devices/devicerecords/{device_id}/routing/ipv4staticroutes' url = self._url('config', request) return self._post(url, data) - def get_ftd_ipv4_routes(self, device_id: str, expanded=False): + def get_ftd_ipv4staticroutes(self, device_id: str, expanded=False): request = f'/devices/devicerecords/{device_id}/routing/ipv4staticroutes' url = self._url('config', request) params = { @@ -718,27 +718,27 @@ def get_ftd_ipv4_routes(self, device_id: str, expanded=False): } return self._get(url, params) - def get_ftd_ipv4_route(self, device_id: str, route_id: str): + def get_ftd_ipv4staticroute(self, device_id: str, route_id: str): request = f'/devices/devicerecords/{device_id}/routing/ipv4staticroutes/{route_id}' url = self._url('config', request) return self._get(url) - def update_ftd_ipv4_route(self, device_id: str, route_id: str, data: Dict): + def update_ftd_ipv4staticroute(self, device_id: str, route_id: str, data: Dict): request = f'/devices/devicerecords/{device_id}/routing/ipv4staticroutes/{route_id}' url = self._url('config', request) return self._put(url, data) - def delete_ftd_ipv4_route(self, device_id: str, route_id: str): + def delete_ftd_ipv4staticroute(self, device_id: str, route_id: str): request = f'/devices/devicerecords/{device_id}/routing/ipv4staticroutes/{route_id}' url = self._url('config', request) return self._delete(url) - def create_ftd_ipv6_route(self, device_id: str, data: Dict): + def create_ftd_ipv6staticroute(self, device_id: str, data: Dict): request = f'/devices/devicerecords/{device_id}/routing/ipv6staticroutes' url = self._url('config', request) return self._post(url, data) - def get_ftd_ipv6_routes(self, device_id: str, expanded=False): + def get_ftd_ipv6staticroutes(self, device_id: str, expanded=False): request = f'/devices/devicerecords/{device_id}/routing/ipv6staticroutes' url = self._url('config', request) params = { @@ -746,17 +746,17 @@ def get_ftd_ipv6_routes(self, device_id: str, expanded=False): } return self._get(url, params) - def get_ftd_ipv6_route(self, device_id: str, route_id: str): + def get_ftd_ipv6staticroute(self, device_id: str, route_id: str): request = f'/devices/devicerecords/{device_id}/routing/ipv6staticroutes/{route_id}' url = self._url('config', request) return self._get(url) - def update_ftd_ipv6_route(self, device_id: str, route_id: str, data: Dict): + def update_ftd_ipv6staticroute(self, device_id: str, route_id: str, data: Dict): request = f'/devices/devicerecords/{device_id}/routing/ipv6staticroutes/{route_id}' url = self._url('config', request) return self._put(url, data) - def delete_ftd_ipv6_route(self, device_id: str, route_id: str): + def delete_ftd_ipv6staticroute(self, device_id: str, route_id: str): request = f'/devices/devicerecords/{device_id}/routing/ipv6staticroutes/{route_id}' url = self._url('config', request) return self._delete(url) From 46f698bafed4bfbcaf71a7e2eeb447d8f215fc26 Mon Sep 17 00:00:00 2001 From: Oliver Kaiser Date: Tue, 12 Nov 2019 12:00:05 +0100 Subject: [PATCH 5/9] fixed params issue for getbyid operations --- fireREST/__init__.py | 19 ++++++++++++++----- 1 file changed, 14 insertions(+), 5 deletions(-) diff --git a/fireREST/__init__.py b/fireREST/__init__.py index 2b25399..0e91d03 100644 --- a/fireREST/__init__.py +++ b/fireREST/__init__.py @@ -1,14 +1,15 @@ import json -import requests import logging +import requests import urllib3 from .version import __version__ +from requests.auth import HTTPBasicAuth from time import sleep from typing import Dict -from requests.auth import HTTPBasicAuth from urllib3.exceptions import ConnectionError +from uuid import UUID API_AUTH_URL = '/api/fmc_platform/v1/auth/generatetoken' @@ -109,6 +110,13 @@ def _get_logger(logger: object): return dummy_logger return logger + def _is_getbyid_operation(self, request): + try: + val = UUID(request.split('/')[-1]) # noqa: F841 + return True + except ValueError: + return False + def _url(self, namespace='base', path=str()): ''' Generate URLs on the fly for requests to firepower api @@ -235,12 +243,13 @@ def _get_request(self, request: str, params=None, limit=100): GET Operation for FMC REST API. In case of authentication issues session will be refreshed :param request: URL of request that should be performed :param params: dict of parameters for http request - :param limit: set custom limit for paging. If not set, api will default to 100 + :param limit: set custom limit for paging. If not set, api will default to 100 :return: requests.Response object ''' if params is None: params = dict() - params['limit'] = limit + if not self._is_getbyid_operation(request): + params['limit'] = limit try: response = requests.get(request, headers=self.headers, params=params, verify=self.verify_cert, timeout=self.timeout) @@ -261,7 +270,7 @@ def _get(self, request: str, params=None, limit=100): GET Operation that supports paging for FMC REST API. In case of authentication issues session will be refreshed :param request: URL of request that should be performed :param params: dict of parameters for http request - :param limit: set custom limit for paging. If not set, api will default to 100 + :param limit: set custom limit for paging. If not set, api will default to 100 :return: list of requests.Response objects ''' if params is None: From 904fd881bfd9a5b5f9fcd8cfe71b4b4e9510e19e Mon Sep 17 00:00:00 2001 From: Oliver Kaiser Date: Tue, 12 Nov 2019 14:25:51 +0100 Subject: [PATCH 6/9] added monitoredinterface operations fixed some exc handling at login/refresh and added domain_name to client obj --- fireREST/__init__.py | 35 ++++++++++++++++++++++++++--------- 1 file changed, 26 insertions(+), 9 deletions(-) diff --git a/fireREST/__init__.py b/fireREST/__init__.py index 0e91d03..18cdf73 100644 --- a/fireREST/__init__.py +++ b/fireREST/__init__.py @@ -6,9 +6,9 @@ from .version import __version__ from requests.auth import HTTPBasicAuth +from requests.exceptions import ConnectionError from time import sleep from typing import Dict -from urllib3.exceptions import ConnectionError from uuid import UUID @@ -95,6 +95,7 @@ def __init__(self, hostname: str, username: str, password: str, session=dict(), self.domains = session['domains'] self.headers['X-auth-access-token'] = session['X-auth-access-token'] self.headers['X-auth-refresh-token'] = session['X-auth-refresh-token'] + self.domain_name = domain self.domain = self.get_domain_id_by_name(domain) @staticmethod @@ -161,11 +162,8 @@ def _login(self): self.domains = json.loads(response.headers.get('DOMAINS', default=None)) self.refresh_counter = 0 self.logger.debug(f'Successfully authenticated to {self.hostname}') - except ConnectionRefusedError: - self.logger.error(f'Could not login to {self.hostname}. Connection refused') - raise - except ConnectionError: - self.logger.error(f'Could not login to {self.hostname}. Max retries exceeded with url: {request}') + except ConnectionError as exc: + self.logger.error(exc, exc_info=True) raise except FireRESTRateLimitException: self.logger.debug(f'Could not login to {self.hostname}. Rate limit exceeded. Backing of for 10 seconds.') @@ -199,9 +197,9 @@ def _refresh(self): self.headers['X-auth-access-token'] = access_token self.headers['X-auth-refresh-token'] = refresh_token - except ConnectionError: - msg = f'Could not connect to {self.hostname}. Max retries exceeded with url: {request}' - self.logger.error(msg) + except ConnectionError as exc: + self.logger.error(exc, exc_info=True) + raise except FireRESTRateLimitException: self.logger.debug( f'API token refresh to {self.hostname} failed. Rate limit exceeded. Backing of for 10 seconds.') @@ -209,6 +207,7 @@ def _refresh(self): self._login() except FireRESTApiException as exc: self.logger.error(str(exc)) + raise self.logger.debug(f'Successfully refreshed authorization token for {self.hostname}') @@ -612,6 +611,24 @@ def delete_device_hapair(self, device_hapair_id: str): url = self._url('config', request) return self._delete(url) + def get_device_hapair_monitoredinterfaces(self, device_hapair_id: str, expanded=False): + request = f'/devicehapairs/ftddevicehapairs/{device_hapair_id}/monitoredinterfaces' + params = { + 'expanded': expanded + } + url = self._url('config', request) + return self._get(url, params) + + def get_device_hapair_monitoredinterface(self, device_hapair_id: str, monitoredinterface_id: str): + request = f'/devicehapairs/ftddevicehapairs/{device_hapair_id}/monitoredinterfaces/{monitoredinterface_id}' + url = self._url('config', request) + return self._get(url) + + def update_device_hapair_monitoredinterface(self, device_hapair_id: str, monitoredinterface_id: str, data: Dict): + request = f'/devicehapairs/ftddevicehapairs/{device_hapair_id}/monitoredinterfaces/{monitoredinterface_id}' + url = self._url('config', request) + return self._put(url, data) + def get_ftd_physical_interfaces(self, device_id: str, expanded=False): request = f'/devices/devicerecords/{device_id}/physicalinterfaces' url = self._url('config', request) From c376f5b80a0e852c3865ee43801c10f8d07fc634 Mon Sep 17 00:00:00 2001 From: Oliver Kaiser Date: Tue, 12 Nov 2019 15:56:34 +0100 Subject: [PATCH 7/9] added expanded option to get_deployable_devices and renamed deploy function --- fireREST/__init__.py | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/fireREST/__init__.py b/fireREST/__init__.py index 18cdf73..5cbd74b 100644 --- a/fireREST/__init__.py +++ b/fireREST/__init__.py @@ -787,15 +787,18 @@ def delete_ftd_ipv6staticroute(self, device_id: str, route_id: str): url = self._url('config', request) return self._delete(url) - def create_deployment(self, data: Dict): + def deploy(self, data: Dict): request = '/deployment/deploymentrequests' url = self._url('config', request) return self._post(url, data) - def get_deployment(self): + def get_deployable_devices(self, expanded=False): request = '/deployment/deployabledevices' url = self._url('config', request) - return self._get(url) + params = { + 'expanded': expanded + } + return self._get(url, params) def create_policy(self, policy_type: str, data: Dict): request = f'/policy/{policy_type}' From 4959ecfbb16597a2623428a8a8f7cb01bfc1bd78 Mon Sep 17 00:00:00 2001 From: Oliver Kaiser Date: Fri, 15 Nov 2019 10:50:49 +0100 Subject: [PATCH 8/9] added missing param for subintf update function --- fireREST/__init__.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/fireREST/__init__.py b/fireREST/__init__.py index 5cbd74b..aeafa4d 100644 --- a/fireREST/__init__.py +++ b/fireREST/__init__.py @@ -721,8 +721,8 @@ def get_ftd_sub_interface(self, device_id: str, interface_id: str): url = self._url('config', request) return self._get(url) - def update_ftd_sub_interface(self, device_id: str, data: Dict): - request = f'/devices/devicerecords/{device_id}/subinterfaces' + def update_ftd_sub_interface(self, device_id: str, interface_id: str, data: Dict): + request = f'/devices/devicerecords/{device_id}/subinterfaces/{interface_id}' url = self._url('config', request) return self._put(url, data) From db9a285d0492898e9f43c9dfd78243d200c7abc3 Mon Sep 17 00:00:00 2001 From: Oliver Kaiser Date: Fri, 15 Nov 2019 10:59:38 +0100 Subject: [PATCH 9/9] updated version to 0.0.4 --- CHANGELOG.md | 14 ++++++++++++++ fireREST/version.py | 2 +- 2 files changed, 15 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index e69de29..6a72acd 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -0,0 +1,14 @@ +# 0.0.4 + +## Enhancements + +* Add api calls for hapair monitoredinterfaces (read, update) +* Add helper function to get primary device id from hapair +* Add expandable option for get_depoyable_deployable_devices +* Default paging change from 25 to 100 + +## Bugfixes + +* getbyid operations fails due to incorrect limit param +* api calls for ftd ipv4/ipv6 static routing fails due to incorrect URLs +* update ftd sub interface fails due to missing param diff --git a/fireREST/version.py b/fireREST/version.py index ffcc925..156d6f9 100644 --- a/fireREST/version.py +++ b/fireREST/version.py @@ -1 +1 @@ -__version__ = '0.0.3' +__version__ = '0.0.4'