From 202a97e96f7c201c26dde74f541391a163e59d6e Mon Sep 17 00:00:00 2001 From: Matt Mullen Date: Fri, 11 Oct 2024 14:55:34 -0400 Subject: [PATCH 1/3] sfos_snmp_agent --- plugins/modules/sfos_snmp_agent.py | 282 ++++++++++++++++++ test.yml | 26 +- .../targets/sfos_snmp_agent/tasks/main.yml | 116 +++++++ 3 files changed, 419 insertions(+), 5 deletions(-) create mode 100644 plugins/modules/sfos_snmp_agent.py create mode 100644 tests/integration/targets/sfos_snmp_agent/tasks/main.yml diff --git a/plugins/modules/sfos_snmp_agent.py b/plugins/modules/sfos_snmp_agent.py new file mode 100644 index 0000000..8c68c11 --- /dev/null +++ b/plugins/modules/sfos_snmp_agent.py @@ -0,0 +1,282 @@ +#!/usr/bin/python + +# Copyright 2024 Sophos Ltd. All rights reserved. +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) + +from __future__ import absolute_import, division, print_function + +__metaclass__ = type + +DOCUMENTATION = r""" +--- +module: sfos_snmp_agent + +short_description: Manage SNMP Agent (System > Administration > SNMP) + +version_added: "1.1.0" + +description: Manage SNMP Agent (System > Administration > SNMP) on Sophos Firewall + +extends_documentation_fragment: + - sophos.sophos_firewall.fragments.base + +options: + enabled: + description: Enable (true) or disable (false) threat feeds + type: bool + required: false + name: + description: Identifying name of firewall + type: str + required: false + description: + description: Description assigned to SNMP agent + type: str + required: false + location: + description: SNMP location + type: str + required: false + contact_person: + description: SNMP contact + type: str + required: false + state: + description: + - Use C(query) to retrieve or C(updated) to modify + choices: [updated, query] + type: str + required: true + +author: + - Matt Mullen (@mamullen13316) +""" + +EXAMPLES = r""" +- name: Update SNMP agent configuration + sophos.sophos_firewall.sfos_snmp_agent: + username: "{{ username }}" + password: "{{ password }}" + hostname: "{{ inventory_hostname }}" + port: 4444 + verify: false + enabled: true + name: testfirewall + description: Firewall used for automation testing + location: AWS + contact_person: Network Operations + state: updated + delegate_to: localhost + +- name: Query advanced threat protection settings + sophos.sophos_firewall.sfos_snmp_agent: + username: "{{ username }}" + password: "{{ password }}" + hostname: "{{ inventory_hostname }}" + port: 4444 + verify: false + state: query + delegate_to: localhost +""" + +RETURN = r""" +api_response: + description: Serialized object containing the API response. + type: dict + returned: always + +""" +import io +import contextlib + +output_buffer = io.StringIO() + +try: + from sophosfirewall_python.firewallapi import ( + SophosFirewall, + SophosFirewallZeroRecords, + SophosFirewallAuthFailure, + SophosFirewallAPIError, + ) + from requests.exceptions import RequestException + + PREREQ_MET = {"result": True} +except ImportError as errMsg: + PREREQ_MET = {"result": False, "missing_module": errMsg.name} + + +from ansible.module_utils.basic import AnsibleModule +from ansible.module_utils.basic import missing_required_lib + + +def get_snmp_agent(fw_obj, module, result): + """Get current SNMP agent settings from Sophos Firewall + + Args: + fw_obj (SophosFirewall): SophosFirewall object + module (AnsibleModule): AnsibleModule object + result (dict): Result output to be sent to the console + + Returns: + dict: Results of lookup + """ + try: + resp = fw_obj.get_tag("SNMPAgentConfiguration") + except SophosFirewallZeroRecords as error: + return {"exists": False, "api_response": str(error)} + except SophosFirewallAuthFailure as error: + module.fail_json(msg="Authentication error: {0}".format(error), **result) + except SophosFirewallAPIError as error: + module.fail_json(msg="API Error: {0}".format(error), **result) + except RequestException as error: + module.fail_json(msg="Error communicating to API: {0}".format(error), **result) + + return {"exists": True, "api_response": resp} + + +def update_snmp_agent(fw_obj, module, result): + """Update SNMP agent configuration on Sophos Firewall + + Args: + fw_obj (SophosFirewall): SophosFirewall object + module (AnsibleModule): AnsibleModule object + result (dict): Result output to be sent to the console + + Returns: + dict: API response + """ + update_params = {} + if module.params.get("enabled") is True: + update_params["Configuration"] = "Enable" + elif module.params.get("enabled") is False: + update_params["Configuration"] = "Disable" + + if module.params.get("description"): + update_params["Description"] = module.params.get("description") + + if module.params.get("name"): + update_params["Name"] = module.params.get("name") + + if module.params.get("location"): + update_params["Location"] = module.params.get("location") + + if module.params.get("contact_person"): + update_params["ContactPerson"] = module.params.get("contact_person") + + try: + with contextlib.redirect_stdout(output_buffer): + resp = fw_obj.update(xml_tag="SNMPAgentConfiguration", update_params=update_params, debug=True) + except SophosFirewallAuthFailure as error: + module.fail_json(msg="Authentication error: {0}".format(error), **result) + except SophosFirewallAPIError as error: + module.fail_json( + msg="API Error: {0},{1}".format(error, output_buffer.getvalue()), **result + ) + except RequestException as error: + module.fail_json(msg="Error communicating to API: {0}".format(error), **result) + return resp + + +def eval_changed(module, exist_settings): + """Evaluate the provided arguments against existing settings. + + Args: + module (AnsibleModule): AnsibleModule object + exist_settings (dict): Response from the call to get_admin_settings() + + Returns: + bool: Return true if any settings are different, otherwise return false + """ + exist_settings = exist_settings["api_response"]["Response"]["SNMPAgentConfiguration"] + + if module.params.get("enabled"): + status = "Enable" + else: + status = "Disable" + + if not status == exist_settings["Configuration"] or ( + module.params.get("location") and not module.params.get("location") == exist_settings["Location"] or + module.params.get("name") and not module.params.get("name") == exist_settings["Name"] or + module.params.get("description") and not module.params.get("description") == exist_settings["Description"] or + module.params.get("contact_person") and not module.params.get("contact_person") == exist_settings["ContactPerson"] + ): + return True + + return False + + +def main(): + """Code executed at run time.""" + argument_spec = { + "username": {"required": True}, + "password": {"required": True, "no_log": True}, + "hostname": {"required": True}, + "port": {"type": "int", "default": 4444}, + "verify": {"type": "bool", "default": True}, + "enabled": {"type": "bool", "required": False}, + "name": {"type": "str", "required": False}, + "description": {"type": "str", "required": False}, + "location": {"type": "str", "required": False}, + "contact_person": {"type": "str", "required": False}, + "state": {"type": "str", "required": True, "choices": ["updated", "query"]}, + } + + required_if = [ + ( + "enabled", + True, + [ + "name", + "location", + "contact_person" + ], + False, + ), + ] + + module = AnsibleModule( + argument_spec=argument_spec, required_if=required_if, supports_check_mode=True + ) + + if not PREREQ_MET["result"]: + module.fail_json(msg=missing_required_lib(PREREQ_MET["missing_module"])) + + fw = SophosFirewall( + username=module.params.get("username"), + password=module.params.get("password"), + hostname=module.params.get("hostname"), + port=module.params.get("port"), + verify=module.params.get("verify"), + ) + + result = {"changed": False, "check_mode": False} + + state = module.params.get("state") + + exist_settings = get_snmp_agent(fw, module, result) + result["api_response"] = exist_settings["api_response"] + + if state == "query": + module.exit_json(**result) + + if module.check_mode: + result["check_mode"] = True + module.exit_json(**result) + + elif state == "updated": + if eval_changed(module, exist_settings): + api_response = update_snmp_agent(fw, module, result) + + if api_response: + result["api_response"] = api_response + if ( + api_response["Response"]["SNMPAgentConfiguration"]["Status"]["#text"] + == "Configuration applied successfully." + ): + result["changed"] = True + + module.exit_json(**result) + + +if __name__ == "__main__": + main() diff --git a/test.yml b/test.yml index cd408f5..a8d1961 100644 --- a/test.yml +++ b/test.yml @@ -456,15 +456,31 @@ # state: updated # delegate_to: localhost - - name: UPDATE ADVANCED THREAT PROTECTION - sophos.sophos_firewall.sfos_atp: + # - name: UPDATE ADVANCED THREAT PROTECTION + # sophos.sophos_firewall.sfos_atp: + # username: "{{ username }}" + # password: "{{ password }}" + # hostname: "{{ inventory_hostname }}" + # port: 4444 + # verify: false + # enabled: True + # log_policy: Log Only + # inspect_content: untrusted + # state: updated + # delegate_to: localhost + + - name: UPDATE SNMP AGENT CONFIGURATION + sophos.sophos_firewall.sfos_snmp_agent: username: "{{ username }}" password: "{{ password }}" hostname: "{{ inventory_hostname }}" port: 4444 verify: false - enabled: True - log_policy: Log Only - inspect_content: untrusted + # enabled: false + # name: automationtest + # description: Testing automation + # location: AWS Ireland + # contact_person: Sophos IT + agent_port: 171 state: updated delegate_to: localhost \ No newline at end of file diff --git a/tests/integration/targets/sfos_snmp_agent/tasks/main.yml b/tests/integration/targets/sfos_snmp_agent/tasks/main.yml new file mode 100644 index 0000000..4242e2b --- /dev/null +++ b/tests/integration/targets/sfos_snmp_agent/tasks/main.yml @@ -0,0 +1,116 @@ +# Copyright 2023 Sophos Ltd. All rights reserved. +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) + + +- name: CHECK VARS + ansible.builtin.fail: + msg: | + Please ensure these variables are set in tests/integration/integration_config.yml: + sfos_username, sfos_password, sfos_hostname, sfos_port, sfos_verify + when: sfos_username is not defined or + sfos_password is not defined or + sfos_hostname is not defined or + sfos_port is not defined or + sfos_verify is not defined + +- name: SET VARS + set_fact: + sfos_connection_params: &sfos_connection_params + username: "{{ sfos_username }}" + password: "{{ sfos_password }}" + hostname: "{{ sfos_hostname }}" + port: "{{ sfos_port }}" + verify: "{{ sfos_verify }}" + no_log: true + +- name: SET INITIAL SNMP SETTINGS + sophos.sophos_firewall.sfos_snmp_agent: + <<: *sfos_connection_params + enabled: false + name: igt-initial + description: Initial settings for integration testing + location: igt-initial + contact_person: igt-initial + state: updated + delegate_to: localhost + +- name: ENABLE SNMP AGENT + sophos.sophos_firewall.sfos_snmp_agent: + <<: *sfos_connection_params + enabled: true + name: IGT-TEST-NAME + description: IGT-TEST-DESC + location: IGT-TEST-LOCATION + contact_person: IGT-TEST-CONTACT + state: updated + register: set_snmp + +- name: ASSERTION CHECK FOR ENABLE SNMP AGENT + assert: + that: + - set_snmp is changed + - set_snmp['api_response']['Response']['SNMPAgentConfiguration']['Status']['@code'] == '200' + - set_snmp['api_response']['Response']['SNMPAgentConfiguration']['Status']['#text'] == 'Configuration applied successfully.' + +- name: QUERY SNMP AGENT + sophos.sophos_firewall.sfos_snmp_agent: + <<: *sfos_connection_params + state: query + register: query_snmp + +- name: ASSERTION CHECK FOR QUERY SNMP AGENT + assert: + that: + - query_snmp is not changed + - query_snmp['api_response']['Response']['SNMPAgentConfiguration']['Configuration'] == 'Enable' + - query_snmp['api_response']['Response']['SNMPAgentConfiguration']['Name'] == 'IGT-TEST-NAME' + - query_snmp['api_response']['Response']['SNMPAgentConfiguration']['Location'] == 'IGT-TEST-LOCATION' + - query_snmp['api_response']['Response']['SNMPAgentConfiguration']['ContactPerson'] == 'IGT-TEST-CONTACT' + - query_snmp['api_response']['Response']['SNMPAgentConfiguration']['Description'] == 'IGT-TEST-DESC' + - query_snmp['api_response']['Response']['SNMPAgentConfiguration']['Name'] == 'IGT-TEST-NAME' + - query_snmp['api_response']['Response']['SNMPAgentConfiguration']['AgentPort'] == '161' + - query_snmp['api_response']['Response']['SNMPAgentConfiguration']['ManagerPort'] == '162' + + +- name: SET SNMP AGENT NO CHANGE + sophos.sophos_firewall.sfos_snmp_agent: + <<: *sfos_connection_params + enabled: true + name: IGT-TEST-NAME + description: IGT-TEST-DESC + location: IGT-TEST-LOCATION + contact_person: IGT-TEST-CONTACT + state: updated + register: set_snmp_nochg + +- name: ASSERTION CHECK FOR SNMP AGENT NO CHANGE + assert: + that: + - set_snmp_nochg is not changed + +- name: DISABLE SNMP AGENT + sophos.sophos_firewall.sfos_snmp_agent: + <<: *sfos_connection_params + enabled: false + state: updated + register: set_snmp + +- name: ASSERTION CHECK FOR DISABLE SNMP AGENT + assert: + that: + - set_snmp is changed + - set_snmp['api_response']['Response']['SNMPAgentConfiguration']['Status']['@code'] == '200' + - set_snmp['api_response']['Response']['SNMPAgentConfiguration']['Status']['#text'] == 'Configuration applied successfully.' + +- name: QUERY SNMP AGENT DISABLED + sophos.sophos_firewall.sfos_snmp_agent: + <<: *sfos_connection_params + state: query + register: query_snmp + +- name: ASSERTION CHECK FOR QUERY SNMP AGENT DISABLED + assert: + that: + - query_snmp is not changed + - query_snmp['api_response']['Response']['SNMPAgentConfiguration']['Configuration'] == 'Disable' + From b9d63f380e6733e0e30879a68e91b893e9c8399a Mon Sep 17 00:00:00 2001 From: Matt Mullen Date: Tue, 15 Oct 2024 16:50:22 -0400 Subject: [PATCH 2/3] feat: sfos_snmp_user and sfos_snmp_agent --- plugins/modules/sfos_snmp_user.py | 532 ++++++++++++++++++ test.yml | 38 +- .../targets/sfos_snmp_user/tasks/main.yml | 184 ++++++ 3 files changed, 745 insertions(+), 9 deletions(-) create mode 100644 plugins/modules/sfos_snmp_user.py create mode 100644 tests/integration/targets/sfos_snmp_user/tasks/main.yml diff --git a/plugins/modules/sfos_snmp_user.py b/plugins/modules/sfos_snmp_user.py new file mode 100644 index 0000000..ac1a85e --- /dev/null +++ b/plugins/modules/sfos_snmp_user.py @@ -0,0 +1,532 @@ +#!/usr/bin/python + +# Copyright 2024 Sophos Ltd. All rights reserved. +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) + +from __future__ import absolute_import, division, print_function + +__metaclass__ = type + +DOCUMENTATION = r""" +--- +module: sfos_snmp_user + +short_description: Manage SNMPv3 User (System > Administration > SNMP) + +version_added: "1.1.0" + +description: Manage SNMP User (System > Administration > SNMP) on Sophos Firewall + +extends_documentation_fragment: + - sophos.sophos_firewall.fragments.base + +options: + name: + description: SNMPv3 Username + type: str + required: false + accept_queries: + description: Enable or Disable querying + type: str + choices: ["Enable", "Disable"] + required: false + send_traps: + description: Enable or Disable sending of SNMP traps + type: str + choices: ["Enable", "Disable"] + required: false + authorized_hosts: + description: List of authorized hosts + type: list + elements: str + required: false + encryption_algorithm: + description: Encryption algorithm + type: str + choices: ["AES", "DES", "None"] + required: false + encryption_password: + description: Encryption password + type: str + required: false + authentication_algorithm: + description: Authentication algorithm + type: str + choices: ["MD5", "SHA256", "SHA512"] + required: false + authentication_password: + description: Authentication password + type: str + required: false + state: + description: + - Use C(query) to retrieve or C(updated) to modify + choices: [updated, query] + type: str + required: true + +author: + - Matt Mullen (@mamullen13316) +""" + +EXAMPLES = r""" +- name: Add SNMPv3 User + sophos.sophos_firewall.sfos_snmp_user: + username: "{{ username }}" + password: "{{ password }}" + hostname: "{{ inventory_hostname }}" + port: 4444 + verify: false + enabled: true + name: snmpv3user + send_queries: Enable + send_traps: Disable + authorized_hosts: + - 10.100.1.104 + - 10.100.1.105 + encryption_algorithm: AES + encryption_password: "{{ encryption_password }}" + authentication_algorithm: MD5 + authentication_password: "{{ authentication_password }}" + state: present + delegate_to: localhost + +- name: Query SNMPv3 User + sophos.sophos_firewall.sfos_snmp_user: + username: "{{ username }}" + password: "{{ password }}" + hostname: "{{ inventory_hostname }}" + port: 4444 + verify: false + name: snmpv3user + state: query + delegate_to: localhost + +- name: Update SNMPv3 User + sophos.sophos_firewall.sfos_snmp_user: + username: "{{ username }}" + password: "{{ password }}" + hostname: "{{ inventory_hostname }}" + port: 4444 + verify: false + enabled: true + name: snmpv3user + send_queries: Disable + encryption_algorithm: AES + encryption_password: "{{ encryption_password }}" + authentication_password: "{{ authentication_password }}" + state: present + delegate_to: localhost + +- name: Remove SNMPv3 User + sophos.sophos_firewall.sfos_snmp_user: + username: "{{ username }}" + password: "{{ password }}" + hostname: "{{ inventory_hostname }}" + port: 4444 + verify: false + enabled: true + name: snmpv3user + state: absent + delegate_to: localhost +""" + +RETURN = r""" +api_response: + description: Serialized object containing the API response. + type: dict + returned: always + +""" +import io +import contextlib + +output_buffer = io.StringIO() + +try: + from sophosfirewall_python.firewallapi import ( + SophosFirewall, + SophosFirewallZeroRecords, + SophosFirewallAuthFailure, + SophosFirewallAPIError, + ) + from requests.exceptions import RequestException + + PREREQ_MET = {"result": True} +except ImportError as errMsg: + PREREQ_MET = {"result": False, "missing_module": errMsg.name} + + +from ansible.module_utils.basic import AnsibleModule +from ansible.module_utils.basic import missing_required_lib + + +def get_snmp_user(fw_obj, module, result): + """Get SNMP user from Sophos Firewall + + Args: + fw_obj (SophosFirewall): SophosFirewall object + module (AnsibleModule): AnsibleModule object + result (dict): Result output to be sent to the console + + Returns: + dict: Results of lookup + """ + try: + resp = fw_obj.get_tag_with_filter("SNMPv3User", key="Username", value=module.params.get("name")) + except SophosFirewallZeroRecords as error: + return {"exists": False, "api_response": str(error)} + except SophosFirewallAuthFailure as error: + module.fail_json(msg="Authentication error: {0}".format(error), **result) + except SophosFirewallAPIError as error: + module.fail_json(msg="API Error: {0}".format(error), **result) + except RequestException as error: + module.fail_json(msg="Error communicating to API: {0}".format(error), **result) + + return {"exists": True, "api_response": resp} + +def create_snmp_user(fw_obj, module, result): + """Create an SNMPv3 User on Sophos Firewall + + Args: + fw_obj (SophosFirewall): SophosFirewall object + module (AnsibleModule): AnsibleModule object + result (dict): Result output to be sent to the console + + Returns: + dict: API response + """ + payload = """ + + {{ name }} + {{ accept_queries }} + {{ send_traps }} + {% for host in authorized_hosts %} + {{ host }} + {% endfor %} + {% if encryption_algorithm == 'AES' %} + 1 + {% elif encryption_algorithm == 'DES' %} + 2 + {% elif encryption_algorithm == 'None' %} + 3 + {% endif %} + {{ encryption_password }} + {% if authentication_algorithm == 'MD5' %} + 1 + {% elif encryption_algorithm == 'SHA256' %} + 2 + {% elif encryption_algorithm == 'SHA512' %} + 3 + {% endif %} + {{ authentication_password }} + + """ + template_vars = { + "name": module.params.get("name"), + "accept_queries": module.params.get("accept_queries"), + "send_traps": module.params.get("send_traps"), + "authorized_hosts": module.params.get("authorized_hosts"), + "encryption_algorithm": module.params.get("encryption_algorithm"), + "encryption_password": module.params.get("encryption_password"), + "authentication_algorithm": module.params.get("authentication_algorithm"), + "authentication_password": module.params.get("authentication_password") + } + + try: + with contextlib.redirect_stdout(output_buffer): + resp = fw_obj.submit_xml( + template_data=payload, + template_vars=template_vars, + debug=True + ) + except SophosFirewallAuthFailure as error: + module.fail_json(msg="Authentication error: {0}".format(error), **result) + except SophosFirewallAPIError as error: + module.fail_json( + msg="API Error: {0},{1}".format(error, output_buffer.getvalue()), **result + ) + except RequestException as error: + module.fail_json(msg="Error communicating to API: {0}".format(error), **result) + + # module.fail_json(msg=f"{resp['Response']}, {output_buffer.getvalue()}") + return resp + + +def update_snmp_user(fw_obj, exist_settings, module, result): + """Update SNMPv3 user configuration on Sophos Firewall + + Args: + fw_obj (SophosFirewall): SophosFirewall object + exist_settings (dict): API response containing existing SNMPv3 user + module (AnsibleModule): AnsibleModule object + result (dict): Result output to be sent to the console + + Returns: + dict: API response + """ + update_params = {} + + update_params["Username"] = module.params.get("name") + + if module.params.get("accept_queries"): + update_params["AcceptQueries"] = module.params.get("accept_queries") + + if module.params.get("send_traps"): + update_params["SendTraps"] = module.params.get("send_traps") + + if module.params.get("authorized_hosts"): + if isinstance(exist_settings["Response"]["SNMPv3User"]["AuthorizedHosts"], str): + update_params["AuthorizedHosts"] = [exist_settings["Response"]["SNMPv3User"]["AuthorizedHosts"]] + for host in module.params.get("authorized_hosts"): + update_params["AuthorizedHosts"].append(host) + if isinstance(exist_settings["Response"]["SNMPv3User"]["AuthorizedHosts"], list): + host_list = exist_settings["Response"]["SNMPv3User"]["AuthorizedHosts"] + for host in module.params.get("authorized_hosts"): + host_list.append(host) + update_params["AuthorizedHosts"] = host_list + + if module.params.get("encryption_algorithm"): + encr_algo = module.params.get("encryption_algorithm") + if encr_algo == "AES": + update_params["EncryptionAlgorithm"] = "1" + if encr_algo == "DES": + update_params["EncryptionAlgorithm"] = "2" + if encr_algo == "None": + update_params["EncryptionAlgorithm"] = "3" + + if module.params.get("encryption_password"): + update_params["EncryptionPassword"] = module.params.get("encryption_password") + + if module.params.get("authentication_algorithm"): + auth_algo = module.params.get("authentication_algorithm") + if auth_algo == "MD5": + update_params["AuthenticationAlgorithm"] = "1" + if auth_algo == "SHA256": + update_params["AuthenticationAlgorithm"] = "2" + if auth_algo == "SHA512": + update_params["AuthenticationAlgorithm"] = "3" + + if module.params.get("authentication_password"): + update_params["AuthenticationPassword"] = module.params.get("authentication_password") + try: + with contextlib.redirect_stdout(output_buffer): + resp = fw_obj.update(xml_tag="SNMPv3User", + update_params=update_params, + name=module.params.get("name"), + lookup_key="Username", + debug=True) + except SophosFirewallAuthFailure as error: + module.fail_json(msg="Authentication error: {0}".format(error), **result) + except SophosFirewallAPIError as error: + module.fail_json( + msg="API Error: {0},{1}".format(error, output_buffer.getvalue()), **result + ) + except RequestException as error: + module.fail_json(msg="Error communicating to API: {0}".format(error), **result) + return resp + + +def eval_changed(module, exist_settings): + """Evaluate the provided arguments against existing settings. + + Args: + module (AnsibleModule): AnsibleModule object + exist_settings (dict): Response from the call to get_admin_settings() + + Returns: + bool: Return true if any settings are different, otherwise return false + """ + exist_settings = exist_settings["api_response"]["Response"]["SNMPv3User"] + + if (module.params.get("accept_queries") and not module.params.get("accept_queries") == exist_settings["AcceptQueries"] or + module.params.get("send_traps") and not module.params.get("send_traps") == exist_settings["SendTraps"] + ): + return True + + if module.params.get("encryption_algorithm") == "AES": + if not exist_settings["EncryptionAlgorithm"] == "1": + return True + + if module.params.get("encryption_algorithm") == "DES": + if not exist_settings["EncryptionAlgorithm"] == "2": + return True + + if module.params.get("encryption_algorithm") == "None": + if not exist_settings["EncryptionAlgorithm"] == "3": + return True + + if module.params.get("encryption_password"): + return True + + if module.params.get("authentication_algorithm") == "MD5": + if not exist_settings["AuthenticationAlgorithm"] == "1": + return True + + if module.params.get("authentication_algorithm") == "SHA256": + if not exist_settings["AuthenticationAlgorithm"] == "2": + return True + + if module.params.get("authentication_algorithm") == "SHA512": + if not exist_settings["AuthenticationAlgorithm"] == "3": + return True + + if module.params.get("authentication_password"): + return True + + return False + +def remove_snmp_user(fw_obj, module, result): + """Remove an SNMPv3 User from Sophos Firewall. + + Args: + fw_obj (SophosFirewall): SophosFirewall object + module (AnsibleModule): AnsibleModule object + result (dict): Result output to be sent to the console + + Returns: + dict: API response + """ + try: + resp = fw_obj.remove(xml_tag="SNMPv3User", name=module.params.get("name")) + except SophosFirewallAuthFailure as error: + module.fail_json(msg="Authentication error: {0}".format(error), **result) + except SophosFirewallAPIError as error: + module.fail_json(msg="API Error: {0}".format(error), **result) + except RequestException as error: + module.fail_json(msg="Error communicating to API: {0}".format(error), **result) + else: + return resp + +def main(): + """Code executed at run time.""" + argument_spec = { + "username": {"required": True}, + "password": {"required": True, "no_log": True}, + "hostname": {"required": True}, + "port": {"type": "int", "default": 4444}, + "verify": {"type": "bool", "default": True}, + "name": {"type": "str", "required": True}, + "accept_queries": {"type": "str", "choices": ["Enable", "Disable"], "required": False}, + "send_traps": {"type": "str", "choices": ["Enable", "Disable"], "required": False}, + "authorized_hosts": {"type": "list", "elements": "str", "required": False}, + "encryption_algorithm": {"type": "str", "choices": ["AES", "DES", "None"], "required": False}, + "encryption_password": {"type": "str", "required": False, "no_log": True}, + "authentication_algorithm": {"type": "str", "choices": ["MD5", "SHA256", "SHA512"], "required": False}, + "authentication_password": {"type": "str", "required": False, "no_log": True}, + "state": {"type": "str", "required": True, "choices": ["present", "updated", "query", "absent"]}, + } + + required_if = [ + ( + "state", + "present", + [ + "name", + "accept_queries", + "send_traps", + "authorized_hosts", + "encryption_algorithm", + "encryption_password", + "authentication_algorithm", + "authentication_password" + ], + False, + ), + ( + "state", + "updated", + [ + "name" + ], + False, + ), + ( + "state", + "query", + [ + "name" + ], + False, + ), + ( + "send_traps", + "Enable", + [ + "authorized_hosts" + ], + False, + ) + ] + + module = AnsibleModule( + argument_spec=argument_spec, required_if=required_if, supports_check_mode=True + ) + + if not PREREQ_MET["result"]: + module.fail_json(msg=missing_required_lib(PREREQ_MET["missing_module"])) + + fw = SophosFirewall( + username=module.params.get("username"), + password=module.params.get("password"), + hostname=module.params.get("hostname"), + port=module.params.get("port"), + verify=module.params.get("verify"), + ) + + result = {"changed": False, "check_mode": False} + + state = module.params.get("state") + + exist_settings = get_snmp_user(fw, module, result) + result["api_response"] = exist_settings["api_response"] + + if state == "query": + module.exit_json(**result) + + if module.check_mode: + result["check_mode"] = True + module.exit_json(**result) + + if state == "present" and not exist_settings["exists"]: + api_response = create_snmp_user(fw, module, result) + if ( + "Operation Successful" in api_response["Response"]["SNMPv3User"]["Status"]["#text"] + ): + result["changed"] = True + result["api_response"] = api_response + + elif state == "present" and exist_settings["exists"]: + result["changed"] = False + + elif state == "absent" and exist_settings["exists"]: + api_response = remove_snmp_user(fw, module, result) + if ( + "Configuration applied successfully" in api_response["Response"]["SNMPv3User"]["Status"]["#text"] + ): + result["changed"] = True + result["api_response"] = api_response + + elif state == "absent" and not exist_settings["exists"]: + result["changed"] = False + + elif state == "updated" and exist_settings["exists"]: + if eval_changed(module, exist_settings): + api_response = update_snmp_user(fw, exist_settings["api_response"], module, result) + + if api_response: + result["api_response"] = api_response + if ( + "Operation Successful" in api_response["Response"]["SNMPv3User"]["Status"]["#text"] + ): + result["changed"] = True + + elif state == "updated" and not exist_settings["exists"]: + result["changed"] = False + module.fail_json(exist_settings["api_response"], **result) + + module.exit_json(**result) + + +if __name__ == "__main__": + main() \ No newline at end of file diff --git a/test.yml b/test.yml index a8d1961..825bd56 100644 --- a/test.yml +++ b/test.yml @@ -469,18 +469,38 @@ # state: updated # delegate_to: localhost - - name: UPDATE SNMP AGENT CONFIGURATION - sophos.sophos_firewall.sfos_snmp_agent: + # - name: UPDATE SNMP AGENT CONFIGURATION + # sophos.sophos_firewall.sfos_snmp_agent: + # username: "{{ username }}" + # password: "{{ password }}" + # hostname: "{{ inventory_hostname }}" + # port: 4444 + # verify: false + # # enabled: false + # # name: automationtest + # # description: Testing automation + # # location: AWS Ireland + # # contact_person: Sophos IT + # agent_port: 171 + # state: updated + # delegate_to: localhost + + - name: Manage SNMPv3 User + sophos.sophos_firewall.sfos_snmp_user: username: "{{ username }}" password: "{{ password }}" hostname: "{{ inventory_hostname }}" port: 4444 verify: false - # enabled: false - # name: automationtest - # description: Testing automation - # location: AWS Ireland - # contact_person: Sophos IT - agent_port: 171 - state: updated + name: snmpv3user1 + accept_queries: Disable + send_traps: Enable + authorized_hosts: + - 10.100.1.104 + - 10.100.1.105 + encryption_algorithm: AES + encryption_password: sup3rs3cr3tp@ssw0rd + authentication_algorithm: MD5 + authentication_password: sup3rs3cr3tp@ssw0rd + state: present delegate_to: localhost \ No newline at end of file diff --git a/tests/integration/targets/sfos_snmp_user/tasks/main.yml b/tests/integration/targets/sfos_snmp_user/tasks/main.yml new file mode 100644 index 0000000..cce2aa7 --- /dev/null +++ b/tests/integration/targets/sfos_snmp_user/tasks/main.yml @@ -0,0 +1,184 @@ +# Copyright 2023 Sophos Ltd. All rights reserved. +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) + + +- name: CHECK VARS + ansible.builtin.fail: + msg: | + Please ensure these variables are set in tests/integration/integration_config.yml: + sfos_username, sfos_password, sfos_hostname, sfos_port, sfos_verify + when: sfos_username is not defined or + sfos_password is not defined or + sfos_hostname is not defined or + sfos_port is not defined or + sfos_verify is not defined + +- name: SET VARS + set_fact: + sfos_connection_params: &sfos_connection_params + username: "{{ sfos_username }}" + password: "{{ sfos_password }}" + hostname: "{{ sfos_hostname }}" + port: "{{ sfos_port }}" + verify: "{{ sfos_verify }}" + no_log: true + +- name: CREATE SNMPV3 USER + sophos.sophos_firewall.sfos_snmp_user: + <<: *sfos_connection_params + name: IGT-TESTUSER + accept_queries: Disable + send_traps: Enable + authorized_hosts: + - 10.1.1.1 + - 10.1.1.2 + encryption_algorithm: AES + encryption_password: sup3rs3cr3tp@ssw0rd + authentication_algorithm: MD5 + authentication_password: sup3rs3cr3tp@ssw0rd + state: present + register: snmp_user_add + +- name: ASSERTION CHECK FOR CREATE SNMPV3 USER + assert: + that: + - snmp_user_add is changed + - snmp_user_add['api_response']['Response']['SNMPv3User']['Status']['@code'] == '216' + - "'Operation Successful' in snmp_user_add['api_response']['Response']['SNMPv3User']['Status']['#text']" + +- name: QUERY SNMP USER + sophos.sophos_firewall.sfos_snmp_user: + <<: *sfos_connection_params + state: query + name: IGT-TESTUSER + register: query_snmp + +- name: ASSERTION CHECK FOR QUERY SNMP USER + assert: + that: + - query_snmp is not changed + - query_snmp['api_response']['Response']['SNMPv3User']['Username'] == 'IGT-TESTUSER' + - query_snmp['api_response']['Response']['SNMPv3User']['AcceptQueries'] == 'Disable' + - query_snmp['api_response']['Response']['SNMPv3User']['SendTraps'] == 'Enable' + - query_snmp['api_response']['Response']['SNMPv3User']['AuthorizedHosts'] == ['10.1.1.1', '10.1.1.2'] + - query_snmp['api_response']['Response']['SNMPv3User']['EncryptionAlgorithm'] == '1' + - query_snmp['api_response']['Response']['SNMPv3User']['AuthenticationAlgorithm'] == '1' + +- name: UPDATE SNMPv3 USER + sophos.sophos_firewall.sfos_snmp_user: + <<: *sfos_connection_params + name: IGT-TESTUSER + accept_queries: Enable + send_traps: Enable + authorized_hosts: + - 10.1.1.1 + - 10.1.1.2 + - 10.1.1.3 + encryption_algorithm: DES + authentication_algorithm: SHA512 + state: updated + register: update_snmp_user + +- name: ASSERTION CHECK FOR UPDATE SNMPV3 USER + assert: + that: + - update_snmp_user is changed + - update_snmp_user['api_response']['Response']['SNMPv3User']['Status']['@code'] == '216' + - "'Operation Successful' in update_snmp_user['api_response']['Response']['SNMPv3User']['Status']['#text']" + +- name: QUERY UPDATED SNMP USER + sophos.sophos_firewall.sfos_snmp_user: + <<: *sfos_connection_params + state: query + name: IGT-TESTUSER + register: query_snmp + +- name: ASSERTION CHECK FOR QUERY SNMP USER + assert: + that: + - query_snmp is not changed + - query_snmp['api_response']['Response']['SNMPv3User']['Username'] == 'IGT-TESTUSER' + - query_snmp['api_response']['Response']['SNMPv3User']['AcceptQueries'] == 'Enable' + - query_snmp['api_response']['Response']['SNMPv3User']['SendTraps'] == 'Enable' + - query_snmp['api_response']['Response']['SNMPv3User']['AuthorizedHosts'] == ['10.1.1.1', '10.1.1.2', '10.1.1.3'] + - query_snmp['api_response']['Response']['SNMPv3User']['EncryptionAlgorithm'] == '2' + - query_snmp['api_response']['Response']['SNMPv3User']['AuthenticationAlgorithm'] == '3' + +- name: UPDATE SNMPv3 USER NOCHG + sophos.sophos_firewall.sfos_snmp_user: + <<: *sfos_connection_params + name: IGT-TESTUSER + accept_queries: Enable + send_traps: Enable + authorized_hosts: + - 10.1.1.1 + - 10.1.1.2 + - 10.1.1.3 + encryption_algorithm: DES + authentication_algorithm: SHA512 + state: updated + register: update_snmp_user + +- name: ASSERTION CHECK FOR UPDATE SNMPV3 USER NOCHG + assert: + that: + - update_snmp_user is not changed + +- name: UPDATE SNMPv3 ENCRYPTION PASSWORD + sophos.sophos_firewall.sfos_snmp_user: + <<: *sfos_connection_params + name: IGT-TESTUSER + encryption_password: T3st1ngP@ssw0rd! + state: updated + register: update_snmp_user + +- name: ASSERTION CHECK FOR UPDATE SNMPV3 USER ENCRYPTION PASSWORD + assert: + that: + - update_snmp_user is changed + +- name: UPDATE SNMPv3 AUTHENTICATION PASSWORD + sophos.sophos_firewall.sfos_snmp_user: + <<: *sfos_connection_params + name: IGT-TESTUSER + authentication_password: T3st1ngP@ssw0rd! + state: updated + register: update_snmp_user + +- name: ASSERTION CHECK FOR UPDATE SNMPV3 USER ENCRYPTION PASSWORD + assert: + that: + - update_snmp_user is changed + +- name: REMOVE SNMPv3 USER + sophos.sophos_firewall.sfos_snmp_user: + <<: *sfos_connection_params + name: IGT-TESTUSER + state: absent + register: remove_snmp_user + +- name: ASSERTION CHECK FOR REMOVE SNMPv3 USER + assert: + that: + - remove_snmp_user is changed + - remove_snmp_user['api_response']['Response']['SNMPv3User']['Status']['@code'] == "200" + - remove_snmp_user['api_response']['Response']['SNMPv3User']['Status']['#text'] == "Configuration applied successfully." + + +- name: REMOVE NONEXISTING SNMPv3 USER + sophos.sophos_firewall.sfos_snmp_user: + <<: *sfos_connection_params + name: IGT-TESTUSER + state: absent + register: remove_snmp_user + +- name: ASSERTION CHECK FOR REMOVE NONEXISTING IGT_TESTHOST + assert: + that: + - remove_snmp_user is not changed + - remove_snmp_user['api_response'] == "No. of records Zero." + + + + + From 6e5f3e70f65caad4960846eff28e91a77f8bdd57 Mon Sep 17 00:00:00 2001 From: Matt Mullen Date: Tue, 15 Oct 2024 16:56:24 -0400 Subject: [PATCH 3/3] doc: update changelog --- CHANGELOG.md | 23 ++++++++++++++++++++--- changelogs/changelog.yaml | 14 ++++++++++++++ 2 files changed, 34 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index eeb1b2d..167dd20 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,20 +2,37 @@ **Topics** -- v1\.0\.0 +- v1\.1\.0 - Release Summary - New Modules +- v1\.0\.0 + - Release Summary + - New Modules + + +## v1\.1\.0 + + +### Release Summary + +This release contains new modules for working with the SNMP agent and SNMPv3 users on Sophos Firewall + + +### New Modules + +* sophos\.sophos\_firewall\.sfos\_snmp\_agent \- Manage SNMP Agent \(System \> Administration \> SNMP\)\. +* sophos\.sophos\_firewall\.sfos\_snmp\_user \- Manage SNMPv3 User \(System \> Administration \> SNMP\)\. ## v1\.0\.0 - + ### Release Summary This is the first proper release of the sophos\.sophos\_firewall collection\. The changelog describes all changes made to the modules and plugins included in this collection\. - + ### New Modules * sophos\.sophos\_firewall\.sfos\_admin\_settings \- Manage Admin and user settings \(System \> Administration\)\. diff --git a/changelogs/changelog.yaml b/changelogs/changelog.yaml index 143f7d1..242f3e2 100644 --- a/changelogs/changelog.yaml +++ b/changelogs/changelog.yaml @@ -70,3 +70,17 @@ releases: name: sfos_zone namespace: '' release_date: '2024-10-08' + 1.1.0: + changes: + release_summary: This release contains new modules for working with the SNMP + agent and SNMPv3 users on Sophos Firewall + fragments: + - 1.1.0.yml + modules: + - description: Manage SNMP Agent (System > Administration > SNMP). + name: sfos_snmp_agent + namespace: '' + - description: Manage SNMPv3 User (System > Administration > SNMP). + name: sfos_snmp_user + namespace: '' + release_date: '2024-10-15'