Exemple #1
0
    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 BaseException:
                    pass
                try:
                    if adom_lock_info[1][0]["lock_user"]:
                        locked_list.append(str(adom))
                    if adom_lock_info[1][0]["lock_user"] == self._logged_in_user:
                        locked_by_user_list.append({"adom": str(adom), "user": str(adom_lock_info[1][0]["lock_user"])})
                except BaseException as err:
                    raise FAZBaseException(err)
            self._locked_adom_list = locked_list
            self._locked_adoms_by_user = locked_by_user_list

        except BaseException as err:
            raise FAZBaseException(msg=("An error occurred while trying to get the locked adom list. Error: "
                                        + str(err)))
 def workspace_check(self):
     """
    Checks FortiAnalyzer for the use of Workspace mode.
    """
     url = "/cli/global/system/global"
     data = {"fields": ["workspace-mode", "adom-status"]}
     resp_obj = self.process_request(url, data, FAZMethods.GET)
     try:
         if resp_obj[1]["workspace-mode"] in ["workflow", "normal"]:
             self.uses_workspace = True
         elif resp_obj[1]["workspace-mode"] == "disabled":
             self.uses_workspace = False
     except KeyError:
         self.uses_workspace = False
     except BaseException as err:
         raise FAZBaseException(
             msg="Couldn't determine workspace-mode in the plugin. Error: "
             + str(err))
     try:
         if resp_obj[1]["adom-status"] in [1, "enable"]:
             self.uses_adoms = True
         else:
             self.uses_adoms = False
     except KeyError:
         self.uses_adoms = False
     except BaseException as err:
         raise FAZBaseException(
             msg="Couldn't determine adom-status in the plugin. Error: " +
             str(err))
Exemple #3
0
 def check_mode(self):
     """
     Checks FortiAnalyzer for the use of Workspace mode
     """
     url = "/cli/global/system/global"
     code, resp_obj = self.send_request(FAZMethods.GET,
                                        self._tools.format_request(FAZMethods.GET,
                                                                   url,
                                                                   fields=["workspace-mode", "adom-status"]))
     try:
         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
     except BaseException:
         raise FAZBaseException(msg="Couldn't determine workspace-mode in the plugin")
     try:
         if resp_obj["adom-status"] in [1, "enable"]:
             self.uses_adoms = True
         else:
             self.uses_adoms = False
     except KeyError:
         self.uses_adoms = False
     except BaseException:
         raise FAZBaseException(msg="Couldn't determine adom-status in the plugin")
Exemple #4
0
    def send_request(self, method, params):
        """
        Responsible for actual sending of data to the connection httpapi base plugin. Does some formatting as well.
        :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....)
        :type method: basestring

        :return: Dictionary of status if it logged in or not.
        """

        try:
            if self.sid is None and params[0]["url"] != "sys/login/user":
                try:
                    self.connection._connect()
                except Exception as err:
                    raise FAZBaseException(
                        msg=
                        "An problem happened with the httpapi plugin self-init connection process. "
                        "Error: " + to_text(err))
        except IndexError:
            raise FAZBaseException(
                "An attempt was made at communicating with a FAZ with "
                "no valid session and an incorrectly formatted request.")
        except Exception:
            raise FAZBaseException(
                "An attempt was made at communicating with a FAZ with "
                "no valid session and an unexpected error was discovered.")

        self._update_request_id()
        json_request = {
            "method": method,
            "params": params,
            "session": self.sid,
            "id": self.req_id,
            "verbose": 1
        }
        data = json.dumps(json_request,
                          ensure_ascii=False).replace('\\\\', '\\')
        try:
            # Sending URL and Data in Unicode, per Ansible Specifications for Connection Plugins
            response, response_data = self.connection.send(
                path=to_text(self._url),
                data=to_text(data),
                headers=BASE_HEADERS)
            # Get Unicode Response - Must convert from StringIO to unicode first so we can do a replace function below
            result = json.loads(to_text(response_data.getvalue()))
            self._update_self_from_response(result, self._url, data)
            return self._handle_response(result)
        except Exception as err:
            raise FAZBaseException(err)
Exemple #5
0
    def login(self, username, password):
        """
        This function will log the plugin into FortiAnalyzer, and return the results.
        :param username: Username of FortiAnalyzer Admin
        :param password: Password of FortiAnalyzer Admin

        :return: Dictionary of status if it logged in or not.
        """

        self._logged_in_user = username
        self.send_request(
            FAZMethods.EXEC,
            self._tools.format_request(
                FAZMethods.EXEC,
                "sys/login/user",
                passwd=password,
                user=username,
            ))

        if "FortiAnalyzer object connected to FortiAnalyzer" in self.__str__():
            # If Login worked then inspect the FortiAnalyzer for Workspace Mode, and it's system information.
            self.inspect_faz()
            return
        else:
            raise FAZBaseException(
                msg=
                "Unknown error while logging in...connection was lost during login operation..."
                " Exiting")
Exemple #6
0
    def return_connected_faz(self):
        """
        Returns the data stored under self._connected_faz

        :return: dict
        """
        try:
            if self._connected_faz:
                return self._connected_faz
        except BaseException:
            raise FAZBaseException("Couldn't Retrieve Connected FAZ Stats")
    def process_request(self, url, datagram, method):
        """
        Formats and Runs the API Request via Connection Plugin. Streamlined for use from Modules.

        :param url: Connection URL to access
        :type url: string
        :param datagram: The prepared payload for the API Request in dictionary format
        :type datagram: dict
        :param method: The preferred API Request method (GET, ADD, POST, etc....)
        :type method: basestring

        :return: Dictionary containing results of the API Request via Connection Plugin.
        :rtype: dict
        """
        try:
            adom = self._module.paramgram["adom"]
            if self.uses_workspace and adom not in self._locked_adom_list and method != FAZMethods.GET:
                self.lock_adom(adom=adom)
        except BaseException as err:
            raise FAZBaseException(err)

        data = self._tools.format_request(method, url, **datagram)
        response = self._conn.send_request(method, data)

        try:
            adom = self._module.paramgram["adom"]
            if self.uses_workspace and adom in self._locked_adom_list \
                    and response[0] == 0 and method != FAZMethods.GET:
                self.commit_changes(adom=adom)
        except BaseException as err:
            raise FAZBaseException(err)

        # if HAS_FAZ_DEBUG:
        #     try:
        #         debug_dump(response, datagram, self._module.paramgram, url, method)
        #     except BaseException:
        #         pass

        return response
Exemple #8
0
 def inspect_faz(self):
     # CHECK FOR WORKSPACE MODE TO SEE IF WE HAVE TO ENABLE ADOM LOCKS
     status = self.get_system_status()
     if status[0] == -11:
         # THE CONNECTION GOT LOST SOMEHOW, REMOVE THE SID AND REPORT BAD LOGIN
         self.logout()
         raise FAZBaseException(msg="Error -11 -- the Session ID was likely malformed somehow. Contact authors."
                                    " 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_faz = status[1]
             self._host = self._connected_faz["Hostname"]
         except BaseException:
             pass
     return
Exemple #9
0
def main():
    argument_spec = dict(
        adom=dict(required=False, type="str", default="root"),
        mode=dict(choices=["add", "delete", "promote"],
                  type="str",
                  default="add"),
        device_ip=dict(required=False, type="str"),
        device_username=dict(required=False, type="str"),
        device_password=dict(required=False, type="str", no_log=True),
        device_unique_name=dict(required=False, type="str"),
        device_serial=dict(required=False, type="str"),
        os_type=dict(required=False,
                     type="str",
                     choices=[
                         "unknown", "fos", "fsw", "foc", "fml", "faz", "fwb",
                         "fch", "fct", "log", "fmg", "fsa", "fdd", "fac"
                     ]),
        mgmt_mode=dict(required=False,
                       type="str",
                       choices=["unreg", "fmg", "faz", "fmgfaz"]),
        os_minor_vers=dict(required=False, type="str"),
        os_ver=dict(required=False,
                    type="str",
                    choices=[
                        "unknown", "0.0", "1.0", "2.0", "3.0", "4.0", "5.0",
                        "6.0"
                    ]),
        platform_str=dict(required=False, type="str"),
        faz_quota=dict(required=False, type="str"))

    required_if = [['mode', 'delete', ['device_unique_name']],
                   [
                       'mode', 'add',
                       [
                           'device_serial', 'device_username',
                           'device_password', 'device_unique_name',
                           'device_ip', 'mgmt_mode', 'platform_str'
                       ]
                   ]]

    module = AnsibleModule(
        argument_spec,
        supports_check_mode=True,
        required_if=required_if,
    )

    # START SESSION LOGIC
    paramgram = {
        "adom": module.params["adom"],
        "mode": module.params["mode"],
        "ip": module.params["device_ip"],
        "device_username": module.params["device_username"],
        "device_password": module.params["device_password"],
        "device_unique_name": module.params["device_unique_name"],
        "sn": module.params["device_serial"],
        "os_type": module.params["os_type"],
        "mgmt_mode": module.params["mgmt_mode"],
        "os_minor_vers": module.params["os_minor_vers"],
        "os_ver": module.params["os_ver"],
        "platform_str": module.params["platform_str"],
        "faz.quota": module.params["faz_quota"],
        "device_action": None
    }
    # INSERT THE PARAMGRAM INTO THE MODULE SO WHEN WE PASS IT TO MOD_UTILS.FortiManagerHandler IT HAS THAT INFO

    if paramgram["mode"] == "add":
        paramgram["device_action"] = "add_model"
    elif paramgram["mode"] == "promote":
        paramgram["device_action"] = "promote_unreg"
    module.paramgram = paramgram

    # TRY TO INIT THE CONNECTION SOCKET PATH AND FortiManagerHandler OBJECT AND TOOLS
    faz = None
    if module._socket_path:
        connection = Connection(module._socket_path)
        faz = FortiAnalyzerHandler(connection, module)
        faz.tools = FAZCommon()
    else:
        module.fail_json(**FAIL_SOCKET_MSG)

    # BEGIN MODULE-SPECIFIC LOGIC -- THINGS NEED TO HAPPEN DEPENDING ON THE ENDPOINT AND OPERATION
    results = DEFAULT_RESULT_OBJ

    try:
        if paramgram["mode"] == "add":
            results = faz_add_device(faz, paramgram)
    except BaseException as err:
        raise FAZBaseException(
            msg="An error occurred trying to add the device. Error: " +
            str(err))

    try:
        if paramgram["mode"] == "promote":
            if paramgram["ip"] is not None:
                results = faz_approve_unregistered_device_by_ip(faz, paramgram)
            elif paramgram["device_unique_name"] is not None:
                results = faz_approve_unregistered_device_by_name(
                    faz, paramgram)
    except BaseException as err:
        raise FAZBaseException(
            msg="An error occurred trying to promote the device. Error: " +
            str(err))

    try:
        if paramgram["mode"] == "delete":
            results = faz_delete_device(faz, paramgram)
    except BaseException as err:
        raise FAZBaseException(
            msg="An error occurred trying to delete the device. Error: " +
            str(err))

    # PROCESS RESULTS
    try:
        faz.govern_response(module=module,
                            results=results,
                            ansible_facts=faz.construct_ansible_facts(
                                results, module.params, paramgram))
    except BaseException as err:
        raise FAZBaseException(
            msg="An error occurred with govern_response(). Error: " + str(err))

    # This should only be hit if faz.govern_response is missed or failed somehow. In fact. It should never be hit.
    # But it's here JIC.
    return module.exit_json(**results[1])
    def return_response(self,
                        module,
                        results,
                        msg="NULL",
                        good_codes=(0, ),
                        stop_on_fail=True,
                        stop_on_success=False,
                        skipped=False,
                        changed=False,
                        unreachable=False,
                        failed=False,
                        success=False,
                        changed_if_success=True,
                        ansible_facts=()):
        """
        This function controls the logout and error reporting after an method or function runs. The exit_json for
        ansible comes from logic within this function. If this function returns just the msg, it means to continue
        execution on the playbook. It is called from the ansible module, or from the self.govern_response function.

        :param module: The Ansible Module CLASS object, used to run fail/exit json
        :type module: object
        :param msg: An overridable custom message from the module that called this.
        :type msg: string
        :param results: A dictionary object containing an API call results
        :type results: dict
        :param good_codes: A list of exit codes considered successful from FortiAnalyzer
        :type good_codes: list
        :param stop_on_fail: If true, stops playbook run when return code is NOT IN good codes (default: true)
        :type stop_on_fail: boolean
        :param stop_on_success: If true, stops playbook run when return code is IN good codes (default: false)
        :type stop_on_success: boolean
        :param changed: If True, tells Ansible that object was changed (default: false)
        :type skipped: boolean
        :param skipped: If True, tells Ansible that object was skipped (default: false)
        :type skipped: boolean
        :param unreachable: If True, tells Ansible that object was unreachable (default: false)
        :type unreachable: boolean
        :param failed: If True, tells Ansible that execution was a failure. Overrides good_codes. (default: false)
        :type unreachable: boolean
        :param success: If True, tells Ansible that execution was a success. Overrides good_codes. (default: false)
        :type unreachable: boolean
        :param changed_if_success: If True, defaults to changed if successful if you specify or not"
        :type changed_if_success: boolean
        :param ansible_facts: A prepared dictionary of ansible facts from the execution.
        :type ansible_facts: dict

        :return: A string object that contains an error message
        :rtype: str
        """

        # VALIDATION ERROR
        if (len(results) == 0) or (failed and success) or (changed
                                                           and unreachable):
            module.exit_json(
                msg=
                "Handle_response was called with no results, or conflicting failed/success or "
                "changed/unreachable parameters. Fix the exit code on module. "
                "Generic Failure",
                failed=True)

        # IDENTIFY SUCCESS/FAIL IF NOT DEFINED
        if not failed and not success:
            if len(results) > 0:
                if results[0] not in good_codes:
                    failed = True
                elif results[0] in good_codes:
                    success = True

        if len(results) > 0:
            # IF NO MESSAGE WAS SUPPLIED, GET IT FROM THE RESULTS, IF THAT DOESN'T WORK, THEN WRITE AN ERROR MESSAGE
            if msg == "NULL":
                try:
                    msg = results[1]['status']['message']
                except BaseException:
                    msg = "No status message returned at results[1][status][message], " \
                          "and none supplied to msg parameter for handle_response."

            if failed:
                # BECAUSE SKIPPED/FAILED WILL OFTEN OCCUR ON CODES THAT DON'T GET INCLUDED, THEY ARE CONSIDERED FAILURES
                # HOWEVER, THEY ARE MUTUALLY EXCLUSIVE, SO IF IT IS MARKED SKIPPED OR UNREACHABLE BY THE MODULE LOGIC
                # THEN REMOVE THE FAILED FLAG SO IT DOESN'T OVERRIDE THE DESIRED STATUS OF SKIPPED OR UNREACHABLE.
                if failed and skipped:
                    failed = False
                if failed and unreachable:
                    failed = False
                if stop_on_fail:
                    if self._uses_workspace:
                        try:
                            self.run_unlock()
                        except BaseException as err:
                            raise FAZBaseException(
                                msg=("Couldn't unlock ADOM! Error: " +
                                     str(err)))
                    module.exit_json(msg=msg,
                                     failed=failed,
                                     changed=changed,
                                     unreachable=unreachable,
                                     skipped=skipped,
                                     results=results[1],
                                     ansible_facts=ansible_facts,
                                     rc=results[0],
                                     invocation={
                                         "module_args":
                                         ansible_facts["ansible_params"]
                                     })
            elif success:
                if changed_if_success:
                    changed = True
                    success = False
                if stop_on_success:
                    if self._uses_workspace:
                        try:
                            self.run_unlock()
                        except BaseException as err:
                            raise FAZBaseException(
                                msg=("Couldn't unlock ADOM! Error: " +
                                     str(err)))
                    module.exit_json(msg=msg,
                                     success=success,
                                     changed=changed,
                                     unreachable=unreachable,
                                     skipped=skipped,
                                     results=results[1],
                                     ansible_facts=ansible_facts,
                                     rc=results[0],
                                     invocation={
                                         "module_args":
                                         ansible_facts["ansible_params"]
                                     })
        return msg
    def govern_response(self,
                        module,
                        results,
                        msg=None,
                        good_codes=None,
                        stop_on_fail=None,
                        stop_on_success=None,
                        skipped=None,
                        changed=None,
                        unreachable=None,
                        failed=None,
                        success=None,
                        changed_if_success=None,
                        ansible_facts=None):
        """
        This function will attempt to apply default values to canned responses from FortiAnalyzer we know of.
        This saves time, and turns the response in the module into a "one-liner", while still giving us...
        the flexibility to directly use return_response in modules if we have too. This function saves repeated code.

        :param module: The Ansible Module CLASS object, used to run fail/exit json
        :type module: object
        :param msg: An overridable custom message from the module that called this.
        :type msg: string
        :param results: A dictionary object containing an API call results
        :type results: dict
        :param good_codes: A list of exit codes considered successful from FortiAnalyzer
        :type good_codes: list
        :param stop_on_fail: If true, stops playbook run when return code is NOT IN good codes (default: true)
        :type stop_on_fail: boolean
        :param stop_on_success: If true, stops playbook run when return code is IN good codes (default: false)
        :type stop_on_success: boolean
        :param changed: If True, tells Ansible that object was changed (default: false)
        :type skipped: boolean
        :param skipped: If True, tells Ansible that object was skipped (default: false)
        :type skipped: boolean
        :param unreachable: If True, tells Ansible that object was unreachable (default: false)
        :type unreachable: boolean
        :param failed: If True, tells Ansible that execution was a failure. Overrides good_codes. (default: false)
        :type unreachable: boolean
        :param success: If True, tells Ansible that execution was a success. Overrides good_codes. (default: false)
        :type unreachable: boolean
        :param changed_if_success: If True, defaults to changed if successful if you specify or not"
        :type changed_if_success: boolean
        :param ansible_facts: A prepared dictionary of ansible facts from the execution.
        :type ansible_facts: dict
        """
        if module is None and results is None:
            raise FAZBaseException(
                "govern_response() was called without a module and/or results tuple! Fix!"
            )
        # Get the Return code from results
        try:
            rc = results[0]
        except BaseException:
            raise FAZBaseException(
                "govern_response() was called without the return code at results[0]"
            )

        # init a few items
        rc_data = None

        # Get the default values for the said return code.
        try:
            rc_codes = FAZ_RC.get('faz_return_codes')
            rc_data = rc_codes.get(rc)
        except BaseException:
            pass

        if not rc_data:
            rc_data = {}
        # ONLY add to overrides if not none -- This is very important that the keys aren't added at this stage
        # if they are empty. And there aren't that many, so let's just do a few if then statements.
        if good_codes is not None:
            rc_data["good_codes"] = good_codes
        if stop_on_fail is not None:
            rc_data["stop_on_fail"] = stop_on_fail
        if stop_on_success is not None:
            rc_data["stop_on_success"] = stop_on_success
        if skipped is not None:
            rc_data["skipped"] = skipped
        if changed is not None:
            rc_data["changed"] = changed
        if unreachable is not None:
            rc_data["unreachable"] = unreachable
        if failed is not None:
            rc_data["failed"] = failed
        if success is not None:
            rc_data["success"] = success
        if changed_if_success is not None:
            rc_data["changed_if_success"] = changed_if_success
        if results is not None:
            rc_data["results"] = results
        if msg is not None:
            rc_data["msg"] = msg
        if ansible_facts is None:
            rc_data["ansible_facts"] = {}
        else:
            rc_data["ansible_facts"] = ansible_facts

        return self.return_response(
            module=module,
            results=results,
            msg=rc_data.get("msg", "NULL"),
            good_codes=rc_data.get("good_codes", (0, )),
            stop_on_fail=rc_data.get("stop_on_fail", True),
            stop_on_success=rc_data.get("stop_on_success", False),
            skipped=rc_data.get("skipped", False),
            changed=rc_data.get("changed", False),
            changed_if_success=rc_data.get("changed_if_success", False),
            unreachable=rc_data.get("unreachable", False),
            failed=rc_data.get("failed", False),
            success=rc_data.get("success", False),
            ansible_facts=rc_data.get("ansible_facts", dict()))