Hotfix for FortiManager Network Connection Plugin (#62534)

* Hotfix for connection manager persistent connection initialization.

* PEP8 Fixes

* First Round of fixes requested by Qualthos.

* Removed _connection check per Mr. Case.

* Somehow the wrong version got sent... should be all good.

Co-authored-by: Luke Weighall <lweighall@users.noreply.github.com>
Co-authored-by: Nathaniel Case <this.is@nathanielca.se>
This commit is contained in:
Fortinet Core CSE NA 2020-02-12 11:30:59 -08:00 committed by GitHub
parent 156dc68e40
commit 33f136292b
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23

View file

@ -17,6 +17,7 @@
#
from __future__ import (absolute_import, division, print_function)
__metaclass__ = type
DOCUMENTATION = """
@ -26,11 +27,10 @@ author:
- Andrew Welsh (@Ghilli3)
- Jim Huber (@p4r4n0y1ng)
httpapi : fortimanager
short_description: HttpApi Plugin for Fortinet FortiManager Appliance or VM
short_description: HttpApi Plugin for Fortinet FortiManager Appliance or VM.
description:
- This HttpApi plugin provides methods to connect to Fortinet FortiManager Appliance or VM via JSON RPC API
- This HttpApi plugin provides methods to connect to Fortinet FortiManager Appliance or VM via JSON RPC API.
version_added: "2.8"
"""
import json
@ -58,12 +58,15 @@ class HttpApi(HttpApiBase):
self._last_url = None
self._last_response_raw = None
self._locked_adom_list = list()
self._locked_adoms_by_user = list()
self._uses_workspace = False
self._uses_adoms = False
self._adom_list = list()
self._logged_in_user = None
def set_become(self, become_context):
"""
ELEVATION IS NOT REQUIRED ON FORTINET DEVICES - SKIPPED
ELEVATION IS NOT REQUIRED ON FORTINET DEVICES - SKIPPED.
:param become_context: Unused input.
:return: None
"""
@ -71,7 +74,7 @@ class HttpApi(HttpApiBase):
def update_auth(self, response, response_data):
"""
TOKENS ARE NOT USED SO NO NEED TO UPDATE AUTH
TOKENS ARE NOT USED SO NO NEED TO UPDATE AUTH.
:param response: Unused input.
:param response_data Unused_input.
:return: None
@ -79,15 +82,17 @@ class HttpApi(HttpApiBase):
return None
def login(self, username, password):
"""
This function will log the plugin into FortiManager, and return the results.
:param username: Username of FortiManager Admin
:param password: Password of FortiManager Admin
:return: Dictionary of status, if it logged in or not.
:return: Dictionary of status if it logged in or not.
"""
self._logged_in_user = username
self.send_request(FMGRMethods.EXEC, self._tools.format_request(FMGRMethods.EXEC, "sys/login/user",
passwd=password, user=username,))
passwd=password, user=username, ))
if "FortiManager object connected to FortiManager" in self.__str__():
# If Login worked, then inspect the FortiManager for Workspace Mode, and it's system information.
@ -99,9 +104,6 @@ class HttpApi(HttpApiBase):
def inspect_fmgr(self):
# CHECK FOR WORKSPACE MODE TO SEE IF WE HAVE TO ENABLE ADOM LOCKS
self.check_mode()
# CHECK FOR SYSTEM STATUS -- SHOULD RETURN 0
status = self.get_system_status()
if status[0] == -11:
# THE CONNECTION GOT LOST SOMEHOW, REMOVE THE SID AND REPORT BAD LOGIN
@ -110,6 +112,11 @@ class HttpApi(HttpApiBase):
" Exiting")
elif status[0] == 0:
try:
self.check_mode()
if self._uses_adoms:
self.get_adom_list()
if self._uses_workspace:
self.get_locked_adom_list()
self._connected_fmgr = status[1]
self._host = self._connected_fmgr["Hostname"]
except BaseException:
@ -121,7 +128,9 @@ class HttpApi(HttpApiBase):
This function will logout of the FortiManager.
"""
if self.sid is not None:
# IF WE WERE USING WORKSPACES, THEN CLEAN UP OUR LOCKS IF THEY STILL EXIST
if self.uses_workspace:
self.get_lock_info()
self.run_unlock()
ret_code, response = self.send_request(FMGRMethods.EXEC,
self._tools.format_request(FMGRMethods.EXEC, "sys/logout"))
@ -130,7 +139,7 @@ class HttpApi(HttpApiBase):
def send_request(self, method, params):
"""
Responsible for actual sending of data to the connection httpapi base plugin. Does some formatting as well.
Responsible for actual sending of data to the connection httpapi base plugin. Does some formatting too.
:param params: A formatted dictionary that was returned by self.common_datagram_params()
before being called here.
:param method: The preferred API Request method (GET, ADD, POST, etc....)
@ -138,16 +147,20 @@ class HttpApi(HttpApiBase):
:return: Dictionary of status, if it logged in or not.
"""
try:
if self.sid is None and params[0]["url"] != "sys/login/user":
raise FMGBaseException("An attempt was made to login with the SID None and URL != login url.")
try:
self.connection._connect()
except Exception as err:
raise FMGBaseException(
msg="An problem happened with the httpapi plugin self-init connection process. "
"Error: " + to_text(err))
except IndexError:
raise FMGBaseException("An attempt was made at communicating with a FMG with "
"no valid session and an incorrectly formatted request.")
except Exception:
except Exception as err:
raise FMGBaseException("An attempt was made at communicating with a FMG with "
"no valid session and an unexpected error was discovered.")
"no valid session and an unexpected error was discovered. \n Error: " + to_text(err))
self._update_request_id()
json_request = {
@ -205,7 +218,7 @@ class HttpApi(HttpApiBase):
try:
if self._connected_fmgr:
return self._connected_fmgr
except BaseException:
except Exception:
raise FMGBaseException("Couldn't Retrieve Connected FMGR Stats")
def get_system_status(self):
@ -245,7 +258,7 @@ class HttpApi(HttpApiBase):
def __str__(self):
if self.sid is not None and self.connection._url is not None:
return "FortiManager object connected to FortiManager: " + str(self.connection._url)
return "FortiManager object connected to FortiManager: " + to_text(self.connection._url)
return "FortiManager object with no valid connection to a FortiManager appliance."
##################################
@ -281,26 +294,32 @@ class HttpApi(HttpApiBase):
Checks FortiManager for the use of Workspace mode
"""
url = "/cli/global/system/global"
code, resp_obj = self.send_request(FMGRMethods.GET, self._tools.format_request(FMGRMethods.GET, url,
fields=["workspace-mode",
"adom-status"]))
code, resp_obj = self.send_request(FMGRMethods.GET,
self._tools.format_request(FMGRMethods.GET,
url,
fields=["workspace-mode", "adom-status"]))
try:
if resp_obj["workspace-mode"] != 0:
if resp_obj["workspace-mode"] == "workflow":
self.uses_workspace = True
elif resp_obj["workspace-mode"] == "disabled":
self.uses_workspace = False
except KeyError:
self.uses_workspace = False
raise FMGBaseException(msg="Couldn't determine workspace-mode in the plugin")
try:
if resp_obj["adom-status"] == 1:
if resp_obj["adom-status"] in [1, "enable"]:
self.uses_adoms = True
else:
self.uses_adoms = False
except KeyError:
self.uses_adoms = False
raise FMGBaseException(msg="Couldn't determine adom-status in the plugin")
def run_unlock(self):
"""
Checks for ADOM status, if locked, it will unlock
"""
for adom_locked in self._locked_adom_list:
self.unlock_adom(adom_locked)
for adom_locked in self._locked_adoms_by_user:
adom = adom_locked["adom"]
self.unlock_adom(adom)
def lock_adom(self, adom=None, *args, **kwargs):
"""
@ -350,6 +369,84 @@ class HttpApi(HttpApiBase):
url = "/dvmdb/adom/root/workspace/commit"
return self.send_request(FMGRMethods.EXEC, self._tools.format_request(FMGRMethods.EXEC, url))
def get_lock_info(self, adom=None):
"""
Gets ADOM lock info so it can be displayed with the error messages. Or if determined to be locked by ansible
for some reason, then unlock it.
"""
if not adom or adom == "root":
url = "/dvmdb/adom/root/workspace/lockinfo"
else:
if adom.lower() == "global":
url = "/dvmdb/global/workspace/lockinfo/"
else:
url = "/dvmdb/adom/{adom}/workspace/lockinfo/".format(adom=adom)
datagram = {}
data = self._tools.format_request(FMGRMethods.GET, url, **datagram)
resp_obj = self.send_request(FMGRMethods.GET, data)
code = resp_obj[0]
if code != 0:
self._module.fail_json(msg=("An error occurred trying to get the ADOM Lock Info. "
"Error: " + to_text(resp_obj)))
elif code == 0:
try:
if resp_obj[1]["status"]["message"] == "OK":
self._lock_info = None
except Exception:
self._lock_info = resp_obj[1]
return resp_obj
def get_adom_list(self):
"""
Gets the list of ADOMs for the FortiManager
"""
if self.uses_adoms:
url = "/dvmdb/adom"
datagram = {}
data = self._tools.format_request(FMGRMethods.GET, url, **datagram)
resp_obj = self.send_request(FMGRMethods.GET, data)
code = resp_obj[0]
if code != 0:
self._module.fail_json(msg=("An error occurred trying to get the ADOM Info. "
"Error: " + to_text(resp_obj)))
elif code == 0:
num_of_adoms = len(resp_obj[1])
append_list = ['root', ]
for adom in resp_obj[1]:
if adom["tab_status"] != "":
append_list.append(to_text(adom["name"]))
self._adom_list = append_list
return resp_obj
def get_locked_adom_list(self):
"""
Gets the list of locked adoms
"""
try:
locked_list = list()
locked_by_user_list = list()
for adom in self._adom_list:
adom_lock_info = self.get_lock_info(adom=adom)
try:
if adom_lock_info[1]["status"]["message"] == "OK":
continue
except IndexError as err:
pass
try:
if adom_lock_info[1][0]["lock_user"]:
locked_list.append(to_text(adom))
if adom_lock_info[1][0]["lock_user"] == self._logged_in_user:
locked_by_user_list.append({"adom": to_text(adom),
"user": to_text(adom_lock_info[1][0]["lock_user"])})
except Exception as err:
raise FMGBaseException(err)
self._locked_adom_list = locked_list
self._locked_adoms_by_user = locked_by_user_list
except Exception as err:
raise FMGBaseException(msg=("An error occurred while trying to get the locked adom list. Error: "
+ to_text(err)))
################################
# END DATABASE LOCK CONTEXT CODE
################################