fsm_discovery¶
Metadata¶
Name: fsm_discovery
Description: Able to submit ad-hoc discoveries and query for the results of any task ID that was a discovery.
Author(s): Luke Weighall (@lweighall)
Ansible Version Added/Required: 2.8
Dev Status: No status updates, yet. Contact Authors.
Parameters¶
delta¶
- Description: Only discovers new devices.
- Required: False
- default: False
discover_routes¶
- Description: Discovers routes and follows those in smart scans.
- Required: False
- default: True
exclude_range¶
- Description: Specifies the IP ranges to specify, in comma seperated format.
- Required: False
export_json_to_file_path¶
Description: When populated, an attempt to write JSON dictionary to file is made.
An error will be thrown if this fails.
Required: False
default: None
export_json_to_screen¶
- Description: When enabled this will print the JSON results to screen.
- Required: False
- default: enable
- choices: [‘enable’, ‘disable’]
export_xml_to_file_path¶
Description: When populated, an attempt to write XML to file is made.
An error will be thrown if this fails.
Required: False
default: None
host¶
- Description: The FortiSIEM’s FQDN or IP Address.
- Required: True
ignore_ssl_errors¶
- Description: When Enabled this will instruct the HTTP Libraries to ignore any ssl validation errors.
- Required: False
- default: enable
- choices: [‘enable’, ‘disable’]
include_range¶
- Description: Specifies the IP ranges to specify, in comma seperated format.
- Required: False
monitor_installed_sw¶
- Description: Turns on or off Windows Installed Software monitoring.
- Required: False
- default: True
monitor_win_events¶
- Description: Turns on or off Windows Event log mointor for newly discovered devices.
- Required: False
- default: True
monitor_win_patches¶
- Description: Turns on or off Windows Patching logging.
- Required: False
- default: True
name_resolution_dns_first¶
Description: Specifies to use DNS for name resolution first, and then SNMP/NETBIOS/SSH.
When false, uses SNMP/NETBIOS/SSH first, then DNS
Required: False
default: True
no_ping¶
Description: Tells FortiSIEM not to attempt to ping a device before attempting to discover it.
Useful when ICMP is blocked on target devices.
Required: False
default: False
only_ping¶
- Description: Tells FortiSIEM to only discover devices with ICMP pings.
- Required: False
- default: False
password¶
- Description: The password associated with the username account.
- Required: False
root_ip¶
Description: Specifies the IP of a device to use as the “root” scanning device. Usually a router or switch.
Ignored unless “SmartScan” is set for mode
Required: False
task_id¶
- Description: Tells the module which task ID to query for when type = status.
- Required: False
type¶
- Description: Discovery type to use in FortiSIEM.
- Required: True
- choices: [‘RangeScan’, ‘SmartScan’, ‘L2Scan’, ‘status’]
unmanaged¶
- Description: Sets newly discovered devices to unmanaged.
- Required: False
- default: False
username¶
Description: The username used to authenticate with the FortiManager.
organization/username format. The Organization is important, and will only return data from specified Org.
Required: False
vm_off¶
- Description: Doesn’t discover VMs.
- Required: False
- default: False
vm_templates¶
- Description: Discover VM templates.
- Required: False
- default: False
wait_to_finish¶
Description: When enabled, the module will WAIT until the discovery actually finishes. This may or may not be desired depending on how big the discovery range is.
When disabled, the module will simply submit the discovery and exit. You’ll have to record the task ID that was exported, and re-run the module with type = status.
Required: False
default: enable
choices: [‘enable’, ‘disable’]
winexe_based¶
- Description: Discovers windows boxes with winExe.
- Required: False
- default: False
Functions¶
- main
def main(): argument_spec = dict( host=dict(required=True, type="str"), username=dict(fallback=(env_fallback, ["ANSIBLE_NET_USERNAME"])), password=dict(fallback=(env_fallback, ["ANSIBLE_NET_PASSWORD"]), no_log=True), ignore_ssl_errors=dict(required=False, type="str", choices=["enable", "disable"], default="enable"), export_json_to_screen=dict(required=False, type="str", choices=["enable", "disable"], default="enable"), export_json_to_file_path=dict(required=False, type="str"), export_xml_to_file_path=dict(required=False, type="str"), export_csv_to_file_path=dict(required=False, type="str"), wait_to_finish=dict(required=False, type="bool", default="false"), type=dict(required=True, type="str", choices=["RangeScan", "SmartScan", "L2Scan", "status"]), root_ip=dict(required=False, type="str"), include_range=dict(required=False, type="str"), exclude_range=dict(required=False, type="str"), no_ping=dict(required=False, type="bool", default="false"), only_ping=dict(required=False, type="bool", default="false"), task_id=dict(required=False, type="int"), delta=dict(required=False, type="bool", default="false"), vm_off=dict(required=False, type="bool", default="false"), vm_templates=dict(required=False, type="bool", default="false"), discover_routes=dict(required=False, type="bool", default="true"), winexe_based=dict(required=False, type="bool", default="false"), unmanaged=dict(required=False, type="bool", default="false"), monitor_win_events=dict(required=False, type="bool", default="true"), monitor_win_patches=dict(required=False, type="bool", default="true"), monitor_installed_sw=dict(required=False, type="bool", default="true"), name_resolution_dns_first=dict(required=False, type="bool", default="true"), ) required_if = [ ['type', 'SmartScan', ['root_ip']], ['type', 'RangeScan', ['include_range']], ['type', 'L2Scan', ['include_range']], ['type', 'status', ['task_id']], ] module = AnsibleModule(argument_spec, supports_check_mode=False, required_if=required_if) paramgram = { "host": module.params["host"], "username": module.params["username"], "password": module.params["password"], "export_json_to_screen": module.params["export_json_to_screen"], "export_json_to_file_path": module.params["export_json_to_file_path"], "export_xml_to_file_path": module.params["export_xml_to_file_path"], "export_csv_to_file_path": module.params["export_csv_to_file_path"], "ignore_ssl_errors": module.params["ignore_ssl_errors"], "type": module.params["type"], "wait_to_finish": module.params["wait_to_finish"], "root_ip": module.params["root_ip"], "include_range": module.params["include_range"], "exclude_range": module.params["exclude_range"], "no_ping": module.params["no_ping"], "only_ping": module.params["only_ping"], "task_id": module.params["task_id"], "delta": module.params["delta"], "vm_off": module.params["vm_off"], "vm_templates": module.params["vm_templates"], "discover_routes": module.params["discover_routes"], "winexe_based": module.params["winexe_based"], "unmanaged": module.params["unmanaged"], "monitor_win_events": module.params["monitor_win_events"], "monitor_win_patches": module.params["monitor_win_patches"], "monitor_installed_sw": module.params["monitor_installed_sw"], "name_resolution_dns_first": module.params["name_resolution_dns_first"], "uri": FSMEndpoints.SET_DISCOVERY, "input_xml": None } module.paramgram = paramgram # TRY TO INIT THE CONNECTION SOCKET PATH AND FortiManagerHandler OBJECT AND TOOLS fsm = None results = DEFAULT_EXIT_MSG try: fsm = FortiSIEMHandler(module) except BaseException as err: raise FSMBaseException("Couldn't load FortiSIEM Handler from mod_utils. Error: " + str(err)) # EXECUTE THE MODULE OPERATION # SEND THE DISCOVERY XML PAYLOAD if paramgram["type"] != "status": paramgram["input_xml"] = fsm._xml.create_discover_payload() try: results = fsm.handle_simple_payload_request(paramgram["input_xml"]) except BaseException as err: raise FSMBaseException(err) # REFACTOR THE GENERIC RESPONSE BECAUSE IT WASN'T STRUCTURED BY FORTISIEM IN AN XML RESPONSE # RECORD THE TASK ID try: paramgram["task_id"] = results["json_results"]["fsm_response"] del results["json_results"]["fsm_response"] results["json_results"]["task_id"] = paramgram["task_id"] results["xml_results"] = "<task_id>" + str(paramgram["task_id"]) + "</task_id>" except BaseException as err: raise FSMBaseException(msg="Couldn't extract discovery task ID from response! Error: " + str(err)) # START THE STATUS CHECKING PORTION if paramgram["type"] == "status" or paramgram["wait_to_finish"]: if not paramgram["task_id"]: raise FSMBaseException(msg="fsm_discovery was called to status " "or wait_to_finish but the task ID was empty") if paramgram["task_id"]: paramgram["uri"] = FSMEndpoints.GET_DISCOVERY + str(paramgram["task_id"]) module.paramgram = paramgram try: results = fsm.handle_simple_request() except BaseException as err: raise FSMBaseException(msg="Failed to get status of task ID: " + str(paramgram["task_id"]) + " - Error: " + str(err)) # PROCESS WAIT TO FINISH! if paramgram["wait_to_finish"]: try: task_status_result = results["json_results"]["fsm_response"].split(":") # SLEEP FOR 5 SECOND INTERVALS AND KEEP CHECKING UNTIL PROGRESS IS 100% while task_status_result[1] != "Done": time.sleep(5) try: results = fsm.handle_simple_request() except BaseException as err: raise FSMBaseException(msg="Failed to get status of task ID: " + str(paramgram["task_id"]) + " - Error: " + str(err)) try: if results["json_results"]["taskResults"]: task_status_result = [str(paramgram["task_id"]), "Done"] except BaseException: try: task_status_result = results["json_results"]["fsm_response"].split(":") except BaseException as err: raise FSMBaseException(err) except BaseException: try: if results["json_results"]["taskResults"]: pass except BaseException as err: raise FSMBaseException(msg="Something happened while looping " "for the status. Error: " + str(err)) pass # EXIT USING GOVERN_RESPONSE() fsm.govern_response(module=module, results=results, changed=False, ansible_facts=fsm.construct_ansible_facts(results["json_results"], module.params, paramgram)) return module.exit_json(msg=results)
Module Source Code¶
#!/usr/bin/python
#
# This file is part of Ansible
#
# Ansible is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# Ansible is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with Ansible. If not, see <http://www.gnu.org/licenses/>.
#
from __future__ import absolute_import, division, print_function
__metaclass__ = type
ANSIBLE_METADATA = {
"metadata_version": "1.1",
"status": ["preview"],
"supported_by": "community"
}
DOCUMENTATION = '''
---
module: fsm_discovery
version_added: "2.8"
author: Luke Weighall (@lweighall)
short_description: Submits and Queries for Discovery Tasks.
description:
- Able to submit ad-hoc discoveries and query for the results of any task ID that was a discovery.
options:
host:
description:
- The FortiSIEM's FQDN or IP Address.
required: true
username:
description:
- The username used to authenticate with the FortiManager.
- organization/username format. The Organization is important, and will only return data from specified Org.
required: false
password:
description:
- The password associated with the username account.
required: false
ignore_ssl_errors:
description:
- When Enabled this will instruct the HTTP Libraries to ignore any ssl validation errors.
required: false
default: "enable"
choices: ["enable", "disable"]
export_json_to_screen:
description:
- When enabled this will print the JSON results to screen.
required: false
default: "enable"
choices: ["enable", "disable"]
export_json_to_file_path:
description:
- When populated, an attempt to write JSON dictionary to file is made.
- An error will be thrown if this fails.
required: false
default: None
export_xml_to_file_path:
description:
- When populated, an attempt to write XML to file is made.
- An error will be thrown if this fails.
required: false
default: None
wait_to_finish:
description:
- When enabled, the module will WAIT until the discovery actually finishes.
This may or may not be desired depending on how big the discovery range is.
- When disabled, the module will simply submit the discovery and exit.
You'll have to record the task ID that was exported, and re-run the module with type = status.
required: false
default: "enable"
choices: ["enable", "disable"]
type:
description:
- Discovery type to use in FortiSIEM.
required: true
choices: ["RangeScan", "SmartScan", "L2Scan", "status"]
root_ip:
description:
- Specifies the IP of a device to use as the "root" scanning device. Usually a router or switch.
- Ignored unless "SmartScan" is set for mode
required: false
include_range:
description:
- Specifies the IP ranges to specify, in comma seperated format.
required: false
exclude_range:
description:
- Specifies the IP ranges to specify, in comma seperated format.
required: false
no_ping:
description:
- Tells FortiSIEM not to attempt to ping a device before attempting to discover it.
- Useful when ICMP is blocked on target devices.
required: false
default: false
type: bool
only_ping:
description:
- Tells FortiSIEM to only discover devices with ICMP pings.
required: false
default: false
type: bool
task_id:
description:
- Tells the module which task ID to query for when type = status.
required: false
type: int
delta:
description:
- Only discovers new devices.
required: false
default: false
type: bool
vm_off:
description:
- Doesn't discover VMs.
required: false
default: false
type: bool
vm_templates:
description:
- Discover VM templates.
required: false
default: false
type: bool
discover_routes:
description:
- Discovers routes and follows those in smart scans.
required: false
default: true
type: bool
winexe_based:
description:
- Discovers windows boxes with winExe.
required: false
default: false
type: bool
unmanaged:
description:
- Sets newly discovered devices to unmanaged.
required: false
default: false
type: bool
monitor_win_events:
description:
- Turns on or off Windows Event log mointor for newly discovered devices.
required: false
default: true
type: bool
monitor_win_patches:
description:
- Turns on or off Windows Patching logging.
required: false
default: true
type: bool
monitor_installed_sw:
description:
- Turns on or off Windows Installed Software monitoring.
required: false
default: true
type: bool
name_resolution_dns_first:
description:
- Specifies to use DNS for name resolution first, and then SNMP/NETBIOS/SSH.
- When false, uses SNMP/NETBIOS/SSH first, then DNS
required: false
default: true
type: bool
'''
EXAMPLES = '''
- name: SUBMIT RANGE SCAN FOR SINGLE DEVICE
fsm_discovery:
host: "{{ inventory_hostname }}"
username: "{{ username }}"
password: "{{ password }}"
ignore_ssl_errors: "enable"
export_json_to_screen: "enable"
export_json_to_file_path: "/root/range_scan.json"
export_xml_to_file_path: "/root/range_scan.xml"
type: "RangeScan"
include_range: "10.0.0.254"
- name: SUBMIT RANGE SCAN FOR SINGLE DEVICE AND WAIT FOR FINISH WITH MANY OPTIONS
fsm_discovery:
host: "{{ inventory_hostname }}"
username: "{{ username }}"
password: "{{ password }}"
ignore_ssl_errors: "enable"
export_json_to_screen: "enable"
export_json_to_file_path: "/root/range_scan2.json"
export_xml_to_file_path: "/root/range_scan2.xml"
type: "RangeScan"
include_range: "10.0.0.5-10.0.0.20"
wait_to_finish: True
only_ping: False
vm_off: True
unmanaged: True
delta: True
name_resolution_dns_first: False
winexe_based: True
vm_templates: True
discover_routes: True
monitor_win_events: False
monitor_win_patches: False
monitor_installed_sw: False
- name: SUBMIT RANGE SCAN FOR SINGLE DEVICE AND WAIT FOR FINISH WITH NO PING
fsm_discovery:
host: "{{ inventory_hostname }}"
username: "{{ username }}"
password: "{{ password }}"
ignore_ssl_errors: "enable"
export_json_to_screen: "enable"
export_json_to_file_path: "/root/json_test_out.json"
export_xml_to_file_path: "/root/xml_test_out.xml"
type: "RangeScan"
include_range: "10.0.0.5-10.0.0.50"
wait_to_finish: True
no_ping: True
- name: SUBMIT RANGE SCAN FOR RANGE OF DEVICES
fsm_discovery:
host: "{{ inventory_hostname }}"
username: "{{ username }}"
password: "{{ password }}"
ignore_ssl_errors: "enable"
export_json_to_screen: "enable"
export_json_to_file_path: "/root/json_test_out.json"
export_xml_to_file_path: "/root/xml_test_out.xml"
type: "RangeScan"
include_range: "10.0.0.1-10.0.0.10"
exclude_range: "10.0.0.5-10.0.0.6"
- name: SUBMIT SMART SCAN
fsm_discovery:
host: "{{ inventory_hostname }}"
username: "{{ username }}"
password: "{{ password }}"
ignore_ssl_errors: "enable"
export_json_to_screen: "enable"
export_json_to_file_path: "/root/json_test_out.json"
export_xml_to_file_path: "/root/xml_test_out.xml"
type: "SmartScan"
root_ip: "10.0.0.254"
- name: SUBMIT L2SCAN
fsm_discovery:
host: "{{ inventory_hostname }}"
username: "{{ username }}"
password: "{{ password }}"
ignore_ssl_errors: "enable"
export_json_to_screen: "enable"
export_json_to_file_path: "/root/json_test_out.json"
export_xml_to_file_path: "/root/xml_test_out.xml"
type: "L2Scan"
include_range: "10.0.0.1-10.0.0.254"
'''
RETURN = """
api_result:
description: full API response, includes status code and message
returned: always
type: str
"""
from ansible.module_utils.basic import AnsibleModule, env_fallback
import time
from ansible.module_utils.network.fortisiem.common import FSMEndpoints
from ansible.module_utils.network.fortisiem.common import FSMBaseException
from ansible.module_utils.network.fortisiem.common import DEFAULT_EXIT_MSG
from ansible.module_utils.network.fortisiem.fortisiem import FortiSIEMHandler
def main():
argument_spec = dict(
host=dict(required=True, type="str"),
username=dict(fallback=(env_fallback, ["ANSIBLE_NET_USERNAME"])),
password=dict(fallback=(env_fallback, ["ANSIBLE_NET_PASSWORD"]), no_log=True),
ignore_ssl_errors=dict(required=False, type="str", choices=["enable", "disable"], default="enable"),
export_json_to_screen=dict(required=False, type="str", choices=["enable", "disable"], default="enable"),
export_json_to_file_path=dict(required=False, type="str"),
export_xml_to_file_path=dict(required=False, type="str"),
export_csv_to_file_path=dict(required=False, type="str"),
wait_to_finish=dict(required=False, type="bool", default="false"),
type=dict(required=True, type="str",
choices=["RangeScan", "SmartScan", "L2Scan", "status"]),
root_ip=dict(required=False, type="str"),
include_range=dict(required=False, type="str"),
exclude_range=dict(required=False, type="str"),
no_ping=dict(required=False, type="bool", default="false"),
only_ping=dict(required=False, type="bool", default="false"),
task_id=dict(required=False, type="int"),
delta=dict(required=False, type="bool", default="false"),
vm_off=dict(required=False, type="bool", default="false"),
vm_templates=dict(required=False, type="bool", default="false"),
discover_routes=dict(required=False, type="bool", default="true"),
winexe_based=dict(required=False, type="bool", default="false"),
unmanaged=dict(required=False, type="bool", default="false"),
monitor_win_events=dict(required=False, type="bool", default="true"),
monitor_win_patches=dict(required=False, type="bool", default="true"),
monitor_installed_sw=dict(required=False, type="bool", default="true"),
name_resolution_dns_first=dict(required=False, type="bool", default="true"),
)
required_if = [
['type', 'SmartScan', ['root_ip']],
['type', 'RangeScan', ['include_range']],
['type', 'L2Scan', ['include_range']],
['type', 'status', ['task_id']],
]
module = AnsibleModule(argument_spec, supports_check_mode=False, required_if=required_if)
paramgram = {
"host": module.params["host"],
"username": module.params["username"],
"password": module.params["password"],
"export_json_to_screen": module.params["export_json_to_screen"],
"export_json_to_file_path": module.params["export_json_to_file_path"],
"export_xml_to_file_path": module.params["export_xml_to_file_path"],
"export_csv_to_file_path": module.params["export_csv_to_file_path"],
"ignore_ssl_errors": module.params["ignore_ssl_errors"],
"type": module.params["type"],
"wait_to_finish": module.params["wait_to_finish"],
"root_ip": module.params["root_ip"],
"include_range": module.params["include_range"],
"exclude_range": module.params["exclude_range"],
"no_ping": module.params["no_ping"],
"only_ping": module.params["only_ping"],
"task_id": module.params["task_id"],
"delta": module.params["delta"],
"vm_off": module.params["vm_off"],
"vm_templates": module.params["vm_templates"],
"discover_routes": module.params["discover_routes"],
"winexe_based": module.params["winexe_based"],
"unmanaged": module.params["unmanaged"],
"monitor_win_events": module.params["monitor_win_events"],
"monitor_win_patches": module.params["monitor_win_patches"],
"monitor_installed_sw": module.params["monitor_installed_sw"],
"name_resolution_dns_first": module.params["name_resolution_dns_first"],
"uri": FSMEndpoints.SET_DISCOVERY,
"input_xml": None
}
module.paramgram = paramgram
# TRY TO INIT THE CONNECTION SOCKET PATH AND FortiManagerHandler OBJECT AND TOOLS
fsm = None
results = DEFAULT_EXIT_MSG
try:
fsm = FortiSIEMHandler(module)
except BaseException as err:
raise FSMBaseException("Couldn't load FortiSIEM Handler from mod_utils. Error: " + str(err))
# EXECUTE THE MODULE OPERATION
# SEND THE DISCOVERY XML PAYLOAD
if paramgram["type"] != "status":
paramgram["input_xml"] = fsm._xml.create_discover_payload()
try:
results = fsm.handle_simple_payload_request(paramgram["input_xml"])
except BaseException as err:
raise FSMBaseException(err)
# REFACTOR THE GENERIC RESPONSE BECAUSE IT WASN'T STRUCTURED BY FORTISIEM IN AN XML RESPONSE
# RECORD THE TASK ID
try:
paramgram["task_id"] = results["json_results"]["fsm_response"]
del results["json_results"]["fsm_response"]
results["json_results"]["task_id"] = paramgram["task_id"]
results["xml_results"] = "<task_id>" + str(paramgram["task_id"]) + "</task_id>"
except BaseException as err:
raise FSMBaseException(msg="Couldn't extract discovery task ID from response! Error: " + str(err))
# START THE STATUS CHECKING PORTION
if paramgram["type"] == "status" or paramgram["wait_to_finish"]:
if not paramgram["task_id"]:
raise FSMBaseException(msg="fsm_discovery was called to status "
"or wait_to_finish but the task ID was empty")
if paramgram["task_id"]:
paramgram["uri"] = FSMEndpoints.GET_DISCOVERY + str(paramgram["task_id"])
module.paramgram = paramgram
try:
results = fsm.handle_simple_request()
except BaseException as err:
raise FSMBaseException(msg="Failed to get status of task ID: " +
str(paramgram["task_id"]) + " - Error: " + str(err))
# PROCESS WAIT TO FINISH!
if paramgram["wait_to_finish"]:
try:
task_status_result = results["json_results"]["fsm_response"].split(":")
# SLEEP FOR 5 SECOND INTERVALS AND KEEP CHECKING UNTIL PROGRESS IS 100%
while task_status_result[1] != "Done":
time.sleep(5)
try:
results = fsm.handle_simple_request()
except BaseException as err:
raise FSMBaseException(msg="Failed to get status of task ID: " +
str(paramgram["task_id"]) + " - Error: " + str(err))
try:
if results["json_results"]["taskResults"]:
task_status_result = [str(paramgram["task_id"]), "Done"]
except BaseException:
try:
task_status_result = results["json_results"]["fsm_response"].split(":")
except BaseException as err:
raise FSMBaseException(err)
except BaseException:
try:
if results["json_results"]["taskResults"]:
pass
except BaseException as err:
raise FSMBaseException(msg="Something happened while looping "
"for the status. Error: " + str(err))
pass
# EXIT USING GOVERN_RESPONSE()
fsm.govern_response(module=module, results=results, changed=False,
ansible_facts=fsm.construct_ansible_facts(results["json_results"],
module.params,
paramgram))
return module.exit_json(msg=results)
if __name__ == "__main__":
main()