From d7e1e6429cc2bbc3127dd985d1d7fd93fafc2870 Mon Sep 17 00:00:00 2001 From: ftntcorecse <43451990+ftntcorecse@users.noreply.github.com> Date: Tue, 20 Nov 2018 22:16:13 -0700 Subject: [PATCH] FortiManager Web Application Firewall Security Profile (#46967) * Initial Commit * Initial Commit * Initial Commit * Fixing syntax errors * After running sanity tests, fixed pylint and pep8 errors * After running sanity tests, fixed pylint and pep8 errors * Fixing more syntax issues * Fixing more syntax issues * Adding username to doc block * PR candidate * PR candidate * fixed pep8 and docs issues * fixed 2.6 function issues * fixed 2.6 function issues part duex * Initial commit for security profile group module * Adding better playbook example to module * Adding another commit to test shippable tests * Fixing shippable errors, pep8 in unit test file and doc block problem in main module * Fixing documentation module error * Fixing pep8 line too long in unit test * Fixing utility function with nested dictionaries * Fixing utility function with pep8 issue * Adding change to allow for multiple list of dictionaries to be submitted via a single playbook * Initial commit for FMG Security Profile Web Application Firewall * adding extra line at bottom for pep8 conditions * Adding descriptions to documentation * Fixing more pep8 issues * New commit for new PR * Removing todo in documentation * Changing module name in documentation to match actual module name * Fixing yaml syntax for long choices list * Fixing yaml syntax for long choices list * Fixing review changes * Adding @ in author names per @Gundalow's request per Ansible's guidelines --- .../network/fortimanager/fmgr_secprof_waf.py | 1630 +++++++++++++++++ .../fixtures/test_fmgr_secprof_waf.json | 360 ++++ .../fortimanager/test_fmgr_secprof_waf.py | 131 ++ 3 files changed, 2121 insertions(+) create mode 100644 lib/ansible/modules/network/fortimanager/fmgr_secprof_waf.py create mode 100644 test/units/modules/network/fortimanager/fixtures/test_fmgr_secprof_waf.json create mode 100644 test/units/modules/network/fortimanager/test_fmgr_secprof_waf.py diff --git a/lib/ansible/modules/network/fortimanager/fmgr_secprof_waf.py b/lib/ansible/modules/network/fortimanager/fmgr_secprof_waf.py new file mode 100644 index 00000000000..b42a045f1df --- /dev/null +++ b/lib/ansible/modules/network/fortimanager/fmgr_secprof_waf.py @@ -0,0 +1,1630 @@ +#!/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 . +# + +from __future__ import absolute_import, division, print_function + +__metaclass__ = type + +ANSIBLE_METADATA = {'status': ['preview'], + 'supported_by': 'community', + 'metadata_version': '1.1'} + +DOCUMENTATION = ''' +--- +module: fmgr_secprof_waf +version_added: "2.8" +author: + - Luke Weighall (@lweighall) + - Andrew Welsh (@Ghilli3) + - Jim Huber (@p4r4n0y1ng) +short_description: FortiManager web application firewall security profile +description: + - Manage web application firewall security profiles for FGTs via FMG + +options: + adom: + description: + - The ADOM the configuration should belong to. + required: false + default: root + + host: + description: + - The FortiManager's Address. + required: true + + username: + description: + - The username associated with the account. + required: true + + password: + description: + - The password associated with the username account. + required: true + + mode: + description: + - Sets one of three modes for managing the object. + - Allows use of soft-adds instead of overwriting existing values + choices: ['add', 'set', 'delete', 'update'] + required: false + default: add + + name: + description: + - WAF Profile name. + required: false + + external: + description: + - Disable/Enable external HTTP Inspection. + - choice | disable | Disable external inspection. + - choice | enable | Enable external inspection. + required: false + choices: ["disable", "enable"] + + extended_log: + description: + - Enable/disable extended logging. + - choice | disable | Disable setting. + - choice | enable | Enable setting. + required: false + choices: ["disable", "enable"] + + comment: + description: + - Comment. + required: false + + address_list: + description: + - EXPERTS ONLY! KNOWLEDGE OF FMGR JSON API IS REQUIRED! + - List of multiple child objects to be added. Expects a list of dictionaries. + - Dictionaries must use FortiManager API parameters, not the ansible ones listed below. + - If submitted, all other prefixed sub-parameters ARE IGNORED. + - This object is MUTUALLY EXCLUSIVE with its options. + - We expect that you know what you are doing with these list parameters, and are leveraging the JSON API Guide. + - WHEN IN DOUBT, USE THE SUB OPTIONS BELOW INSTEAD TO CREATE OBJECTS WITH MULTIPLE TASKS + required: false + + address_list_blocked_address: + description: + - Blocked address. + required: false + + address_list_blocked_log: + description: + - Enable/disable logging on blocked addresses. + - choice | disable | Disable setting. + - choice | enable | Enable setting. + required: false + choices: ["disable", "enable"] + + address_list_severity: + description: + - Severity. + - choice | low | Low severity. + - choice | medium | Medium severity. + - choice | high | High severity. + required: false + choices: ["low", "medium", "high"] + + address_list_status: + description: + - Status. + - choice | disable | Disable setting. + - choice | enable | Enable setting. + required: false + choices: ["disable", "enable"] + + address_list_trusted_address: + description: + - Trusted address. + required: false + + constraint: + description: + - EXPERTS ONLY! KNOWLEDGE OF FMGR JSON API IS REQUIRED! + - List of multiple child objects to be added. Expects a list of dictionaries. + - Dictionaries must use FortiManager API parameters, not the ansible ones listed below. + - If submitted, all other prefixed sub-parameters ARE IGNORED. + - This object is MUTUALLY EXCLUSIVE with its options. + - We expect that you know what you are doing with these list parameters, and are leveraging the JSON API Guide. + - WHEN IN DOUBT, USE THE SUB OPTIONS BELOW INSTEAD TO CREATE OBJECTS WITH MULTIPLE TASKS + required: false + + constraint_content_length_action: + description: + - Action. + - choice | allow | Allow. + - choice | block | Block. + required: false + choices: ["allow", "block"] + + constraint_content_length_length: + description: + - Length of HTTP content in bytes (0 to 2147483647). + required: false + + constraint_content_length_log: + description: + - Enable/disable logging. + - choice | disable | Disable setting. + - choice | enable | Enable setting. + required: false + choices: ["disable", "enable"] + + constraint_content_length_severity: + description: + - Severity. + - choice | low | Low severity. + - choice | medium | Medium severity. + - choice | high | High severity. + required: false + choices: ["low", "medium", "high"] + + constraint_content_length_status: + description: + - Enable/disable the constraint. + - choice | disable | Disable setting. + - choice | enable | Enable setting. + required: false + choices: ["disable", "enable"] + + constraint_exception_address: + description: + - Host address. + required: false + + constraint_exception_content_length: + description: + - HTTP content length in request. + - choice | disable | Disable setting. + - choice | enable | Enable setting. + required: false + choices: ["disable", "enable"] + + constraint_exception_header_length: + description: + - HTTP header length in request. + - choice | disable | Disable setting. + - choice | enable | Enable setting. + required: false + choices: ["disable", "enable"] + + constraint_exception_hostname: + description: + - Enable/disable hostname check. + - choice | disable | Disable setting. + - choice | enable | Enable setting. + required: false + choices: ["disable", "enable"] + + constraint_exception_line_length: + description: + - HTTP line length in request. + - choice | disable | Disable setting. + - choice | enable | Enable setting. + required: false + choices: ["disable", "enable"] + + constraint_exception_malformed: + description: + - Enable/disable malformed HTTP request check. + - choice | disable | Disable setting. + - choice | enable | Enable setting. + required: false + choices: ["disable", "enable"] + + constraint_exception_max_cookie: + description: + - Maximum number of cookies in HTTP request. + - choice | disable | Disable setting. + - choice | enable | Enable setting. + required: false + choices: ["disable", "enable"] + + constraint_exception_max_header_line: + description: + - Maximum number of HTTP header line. + - choice | disable | Disable setting. + - choice | enable | Enable setting. + required: false + choices: ["disable", "enable"] + + constraint_exception_max_range_segment: + description: + - Maximum number of range segments in HTTP range line. + - choice | disable | Disable setting. + - choice | enable | Enable setting. + required: false + choices: ["disable", "enable"] + + constraint_exception_max_url_param: + description: + - Maximum number of parameters in URL. + - choice | disable | Disable setting. + - choice | enable | Enable setting. + required: false + choices: ["disable", "enable"] + + constraint_exception_method: + description: + - Enable/disable HTTP method check. + - choice | disable | Disable setting. + - choice | enable | Enable setting. + required: false + choices: ["disable", "enable"] + + constraint_exception_param_length: + description: + - Maximum length of parameter in URL, HTTP POST request or HTTP body. + - choice | disable | Disable setting. + - choice | enable | Enable setting. + required: false + choices: ["disable", "enable"] + + constraint_exception_pattern: + description: + - URL pattern. + required: false + + constraint_exception_regex: + description: + - Enable/disable regular expression based pattern match. + - choice | disable | Disable setting. + - choice | enable | Enable setting. + required: false + choices: ["disable", "enable"] + + constraint_exception_url_param_length: + description: + - Maximum length of parameter in URL. + - choice | disable | Disable setting. + - choice | enable | Enable setting. + required: false + choices: ["disable", "enable"] + + constraint_exception_version: + description: + - Enable/disable HTTP version check. + - choice | disable | Disable setting. + - choice | enable | Enable setting. + required: false + choices: ["disable", "enable"] + + constraint_header_length_action: + description: + - Action. + - choice | allow | Allow. + - choice | block | Block. + required: false + choices: ["allow", "block"] + + constraint_header_length_length: + description: + - Length of HTTP header in bytes (0 to 2147483647). + required: false + + constraint_header_length_log: + description: + - Enable/disable logging. + - choice | disable | Disable setting. + - choice | enable | Enable setting. + required: false + choices: ["disable", "enable"] + + constraint_header_length_severity: + description: + - Severity. + - choice | low | Low severity. + - choice | medium | Medium severity. + - choice | high | High severity. + required: false + choices: ["low", "medium", "high"] + + constraint_header_length_status: + description: + - Enable/disable the constraint. + - choice | disable | Disable setting. + - choice | enable | Enable setting. + required: false + choices: ["disable", "enable"] + + constraint_hostname_action: + description: + - Action for a hostname constraint. + - choice | allow | Allow. + - choice | block | Block. + required: false + choices: ["allow", "block"] + + constraint_hostname_log: + description: + - Enable/disable logging. + - choice | disable | Disable setting. + - choice | enable | Enable setting. + required: false + choices: ["disable", "enable"] + + constraint_hostname_severity: + description: + - Severity. + - choice | low | Low severity. + - choice | medium | Medium severity. + - choice | high | High severity. + required: false + choices: ["low", "medium", "high"] + + constraint_hostname_status: + description: + - Enable/disable the constraint. + - choice | disable | Disable setting. + - choice | enable | Enable setting. + required: false + choices: ["disable", "enable"] + + constraint_line_length_action: + description: + - Action. + - choice | allow | Allow. + - choice | block | Block. + required: false + choices: ["allow", "block"] + + constraint_line_length_length: + description: + - Length of HTTP line in bytes (0 to 2147483647). + required: false + + constraint_line_length_log: + description: + - Enable/disable logging. + - choice | disable | Disable setting. + - choice | enable | Enable setting. + required: false + choices: ["disable", "enable"] + + constraint_line_length_severity: + description: + - Severity. + - choice | low | Low severity. + - choice | medium | Medium severity. + - choice | high | High severity. + required: false + choices: ["low", "medium", "high"] + + constraint_line_length_status: + description: + - Enable/disable the constraint. + - choice | disable | Disable setting. + - choice | enable | Enable setting. + required: false + choices: ["disable", "enable"] + + constraint_malformed_action: + description: + - Action. + - choice | allow | Allow. + - choice | block | Block. + required: false + choices: ["allow", "block"] + + constraint_malformed_log: + description: + - Enable/disable logging. + - choice | disable | Disable setting. + - choice | enable | Enable setting. + required: false + choices: ["disable", "enable"] + + constraint_malformed_severity: + description: + - Severity. + - choice | low | Low severity. + - choice | medium | Medium severity. + - choice | high | High severity. + required: false + choices: ["low", "medium", "high"] + + constraint_malformed_status: + description: + - Enable/disable the constraint. + - choice | disable | Disable setting. + - choice | enable | Enable setting. + required: false + choices: ["disable", "enable"] + + constraint_max_cookie_action: + description: + - Action. + - choice | allow | Allow. + - choice | block | Block. + required: false + choices: ["allow", "block"] + + constraint_max_cookie_log: + description: + - Enable/disable logging. + - choice | disable | Disable setting. + - choice | enable | Enable setting. + required: false + choices: ["disable", "enable"] + + constraint_max_cookie_max_cookie: + description: + - Maximum number of cookies in HTTP request (0 to 2147483647). + required: false + + constraint_max_cookie_severity: + description: + - Severity. + - choice | low | Low severity. + - choice | medium | Medium severity. + - choice | high | High severity. + required: false + choices: ["low", "medium", "high"] + + constraint_max_cookie_status: + description: + - Enable/disable the constraint. + - choice | disable | Disable setting. + - choice | enable | Enable setting. + required: false + choices: ["disable", "enable"] + + constraint_max_header_line_action: + description: + - Action. + - choice | allow | Allow. + - choice | block | Block. + required: false + choices: ["allow", "block"] + + constraint_max_header_line_log: + description: + - Enable/disable logging. + - choice | disable | Disable setting. + - choice | enable | Enable setting. + required: false + choices: ["disable", "enable"] + + constraint_max_header_line_max_header_line: + description: + - Maximum number HTTP header lines (0 to 2147483647). + required: false + + constraint_max_header_line_severity: + description: + - Severity. + - choice | low | Low severity. + - choice | medium | Medium severity. + - choice | high | High severity. + required: false + choices: ["low", "medium", "high"] + + constraint_max_header_line_status: + description: + - Enable/disable the constraint. + - choice | disable | Disable setting. + - choice | enable | Enable setting. + required: false + choices: ["disable", "enable"] + + constraint_max_range_segment_action: + description: + - Action. + - choice | allow | Allow. + - choice | block | Block. + required: false + choices: ["allow", "block"] + + constraint_max_range_segment_log: + description: + - Enable/disable logging. + - choice | disable | Disable setting. + - choice | enable | Enable setting. + required: false + choices: ["disable", "enable"] + + constraint_max_range_segment_max_range_segment: + description: + - Maximum number of range segments in HTTP range line (0 to 2147483647). + required: false + + constraint_max_range_segment_severity: + description: + - Severity. + - choice | low | Low severity. + - choice | medium | Medium severity. + - choice | high | High severity. + required: false + choices: ["low", "medium", "high"] + + constraint_max_range_segment_status: + description: + - Enable/disable the constraint. + - choice | disable | Disable setting. + - choice | enable | Enable setting. + required: false + choices: ["disable", "enable"] + + constraint_max_url_param_action: + description: + - Action. + - choice | allow | Allow. + - choice | block | Block. + required: false + choices: ["allow", "block"] + + constraint_max_url_param_log: + description: + - Enable/disable logging. + - choice | disable | Disable setting. + - choice | enable | Enable setting. + required: false + choices: ["disable", "enable"] + + constraint_max_url_param_max_url_param: + description: + - Maximum number of parameters in URL (0 to 2147483647). + required: false + + constraint_max_url_param_severity: + description: + - Severity. + - choice | low | Low severity. + - choice | medium | Medium severity. + - choice | high | High severity. + required: false + choices: ["low", "medium", "high"] + + constraint_max_url_param_status: + description: + - Enable/disable the constraint. + - choice | disable | Disable setting. + - choice | enable | Enable setting. + required: false + choices: ["disable", "enable"] + + constraint_method_action: + description: + - Action. + - choice | allow | Allow. + - choice | block | Block. + required: false + choices: ["allow", "block"] + + constraint_method_log: + description: + - Enable/disable logging. + - choice | disable | Disable setting. + - choice | enable | Enable setting. + required: false + choices: ["disable", "enable"] + + constraint_method_severity: + description: + - Severity. + - choice | low | Low severity. + - choice | medium | Medium severity. + - choice | high | High severity. + required: false + choices: ["low", "medium", "high"] + + constraint_method_status: + description: + - Enable/disable the constraint. + - choice | disable | Disable setting. + - choice | enable | Enable setting. + required: false + choices: ["disable", "enable"] + + constraint_param_length_action: + description: + - Action. + - choice | allow | Allow. + - choice | block | Block. + required: false + choices: ["allow", "block"] + + constraint_param_length_length: + description: + - Maximum length of parameter in URL, HTTP POST request or HTTP body in bytes (0 to 2147483647). + required: false + + constraint_param_length_log: + description: + - Enable/disable logging. + - choice | disable | Disable setting. + - choice | enable | Enable setting. + required: false + choices: ["disable", "enable"] + + constraint_param_length_severity: + description: + - Severity. + - choice | low | Low severity. + - choice | medium | Medium severity. + - choice | high | High severity. + required: false + choices: ["low", "medium", "high"] + + constraint_param_length_status: + description: + - Enable/disable the constraint. + - choice | disable | Disable setting. + - choice | enable | Enable setting. + required: false + choices: ["disable", "enable"] + + constraint_url_param_length_action: + description: + - Action. + - choice | allow | Allow. + - choice | block | Block. + required: false + choices: ["allow", "block"] + + constraint_url_param_length_length: + description: + - Maximum length of URL parameter in bytes (0 to 2147483647). + required: false + + constraint_url_param_length_log: + description: + - Enable/disable logging. + - choice | disable | Disable setting. + - choice | enable | Enable setting. + required: false + choices: ["disable", "enable"] + + constraint_url_param_length_severity: + description: + - Severity. + - choice | low | Low severity. + - choice | medium | Medium severity. + - choice | high | High severity. + required: false + choices: ["low", "medium", "high"] + + constraint_url_param_length_status: + description: + - Enable/disable the constraint. + - choice | disable | Disable setting. + - choice | enable | Enable setting. + required: false + choices: ["disable", "enable"] + + constraint_version_action: + description: + - Action. + - choice | allow | Allow. + - choice | block | Block. + required: false + choices: ["allow", "block"] + + constraint_version_log: + description: + - Enable/disable logging. + - choice | disable | Disable setting. + - choice | enable | Enable setting. + required: false + choices: ["disable", "enable"] + + constraint_version_severity: + description: + - Severity. + - choice | low | Low severity. + - choice | medium | Medium severity. + - choice | high | High severity. + required: false + choices: ["low", "medium", "high"] + + constraint_version_status: + description: + - Enable/disable the constraint. + - choice | disable | Disable setting. + - choice | enable | Enable setting. + required: false + choices: ["disable", "enable"] + + method: + description: + - EXPERTS ONLY! KNOWLEDGE OF FMGR JSON API IS REQUIRED! + - List of multiple child objects to be added. Expects a list of dictionaries. + - Dictionaries must use FortiManager API parameters, not the ansible ones listed below. + - If submitted, all other prefixed sub-parameters ARE IGNORED. + - This object is MUTUALLY EXCLUSIVE with its options. + - We expect that you know what you are doing with these list parameters, and are leveraging the JSON API Guide. + - WHEN IN DOUBT, USE THE SUB OPTIONS BELOW INSTEAD TO CREATE OBJECTS WITH MULTIPLE TASKS + required: false + + method_default_allowed_methods: + description: + - Methods. + - FLAG Based Options. Specify multiple in list form. + - flag | delete | HTTP DELETE method. + - flag | get | HTTP GET method. + - flag | head | HTTP HEAD method. + - flag | options | HTTP OPTIONS method. + - flag | post | HTTP POST method. + - flag | put | HTTP PUT method. + - flag | trace | HTTP TRACE method. + - flag | others | Other HTTP methods. + - flag | connect | HTTP CONNECT method. + required: false + choices: ["delete", "get", "head", "options", "post", "put", "trace", "others", "connect"] + + method_log: + description: + - Enable/disable logging. + - choice | disable | Disable setting. + - choice | enable | Enable setting. + required: false + choices: ["disable", "enable"] + + method_severity: + description: + - Severity. + - choice | low | low severity + - choice | medium | medium severity + - choice | high | High severity + required: false + choices: ["low", "medium", "high"] + + method_status: + description: + - Status. + - choice | disable | Disable setting. + - choice | enable | Enable setting. + required: false + choices: ["disable", "enable"] + + method_method_policy_address: + description: + - Host address. + required: false + + method_method_policy_allowed_methods: + description: + - Allowed Methods. + - FLAG Based Options. Specify multiple in list form. + - flag | delete | HTTP DELETE method. + - flag | get | HTTP GET method. + - flag | head | HTTP HEAD method. + - flag | options | HTTP OPTIONS method. + - flag | post | HTTP POST method. + - flag | put | HTTP PUT method. + - flag | trace | HTTP TRACE method. + - flag | others | Other HTTP methods. + - flag | connect | HTTP CONNECT method. + required: false + choices: ["delete", "get", "head", "options", "post", "put", "trace", "others", "connect"] + + method_method_policy_pattern: + description: + - URL pattern. + required: false + + method_method_policy_regex: + description: + - Enable/disable regular expression based pattern match. + - choice | disable | Disable setting. + - choice | enable | Enable setting. + required: false + choices: ["disable", "enable"] + + signature: + description: + - EXPERTS ONLY! KNOWLEDGE OF FMGR JSON API IS REQUIRED! + - List of multiple child objects to be added. Expects a list of dictionaries. + - Dictionaries must use FortiManager API parameters, not the ansible ones listed below. + - If submitted, all other prefixed sub-parameters ARE IGNORED. + - This object is MUTUALLY EXCLUSIVE with its options. + - We expect that you know what you are doing with these list parameters, and are leveraging the JSON API Guide. + - WHEN IN DOUBT, USE THE SUB OPTIONS BELOW INSTEAD TO CREATE OBJECTS WITH MULTIPLE TASKS + required: false + + signature_credit_card_detection_threshold: + description: + - The minimum number of Credit cards to detect violation. + required: false + + signature_disabled_signature: + description: + - Disabled signatures + required: false + + signature_disabled_sub_class: + description: + - Disabled signature subclasses. + required: false + + signature_custom_signature_action: + description: + - Action. + - choice | allow | Allow. + - choice | block | Block. + - choice | erase | Erase credit card numbers. + required: false + choices: ["allow", "block", "erase"] + + signature_custom_signature_case_sensitivity: + description: + - Case sensitivity in pattern. + - choice | disable | Case insensitive in pattern. + - choice | enable | Case sensitive in pattern. + required: false + choices: ["disable", "enable"] + + signature_custom_signature_direction: + description: + - Traffic direction. + - choice | request | Match HTTP request. + - choice | response | Match HTTP response. + required: false + choices: ["request", "response"] + + signature_custom_signature_log: + description: + - Enable/disable logging. + - choice | disable | Disable setting. + - choice | enable | Enable setting. + required: false + choices: ["disable", "enable"] + + signature_custom_signature_name: + description: + - Signature name. + required: false + + signature_custom_signature_pattern: + description: + - Match pattern. + required: false + + signature_custom_signature_severity: + description: + - Severity. + - choice | low | Low severity. + - choice | medium | Medium severity. + - choice | high | High severity. + required: false + choices: ["low", "medium", "high"] + + signature_custom_signature_status: + description: + - Status. + - choice | disable | Disable setting. + - choice | enable | Enable setting. + required: false + choices: ["disable", "enable"] + + signature_custom_signature_target: + description: + - Match HTTP target. + - FLAG Based Options. Specify multiple in list form. + - flag | arg | HTTP arguments. + - flag | arg-name | Names of HTTP arguments. + - flag | req-body | HTTP request body. + - flag | req-cookie | HTTP request cookies. + - flag | req-cookie-name | HTTP request cookie names. + - flag | req-filename | HTTP request file name. + - flag | req-header | HTTP request headers. + - flag | req-header-name | HTTP request header names. + - flag | req-raw-uri | Raw URI of HTTP request. + - flag | req-uri | URI of HTTP request. + - flag | resp-body | HTTP response body. + - flag | resp-hdr | HTTP response headers. + - flag | resp-status | HTTP response status. + required: false + choices: ["arg","arg-name","req-body","req-cookie","req-cookie-name","req-filename","req-header","req-header-name", + "req-raw-uri","req-uri","resp-body","resp-hdr","resp-status"] + + signature_main_class_action: + description: + - Action. + - choice | allow | Allow. + - choice | block | Block. + - choice | erase | Erase credit card numbers. + required: false + choices: ["allow", "block", "erase"] + + signature_main_class_log: + description: + - Enable/disable logging. + - choice | disable | Disable setting. + - choice | enable | Enable setting. + required: false + choices: ["disable", "enable"] + + signature_main_class_severity: + description: + - Severity. + - choice | low | Low severity. + - choice | medium | Medium severity. + - choice | high | High severity. + required: false + choices: ["low", "medium", "high"] + + signature_main_class_status: + description: + - Status. + - choice | disable | Disable setting. + - choice | enable | Enable setting. + required: false + choices: ["disable", "enable"] + + url_access: + description: + - EXPERTS ONLY! KNOWLEDGE OF FMGR JSON API IS REQUIRED! + - List of multiple child objects to be added. Expects a list of dictionaries. + - Dictionaries must use FortiManager API parameters, not the ansible ones listed below. + - If submitted, all other prefixed sub-parameters ARE IGNORED. + - This object is MUTUALLY EXCLUSIVE with its options. + - We expect that you know what you are doing with these list parameters, and are leveraging the JSON API Guide. + - WHEN IN DOUBT, USE THE SUB OPTIONS BELOW INSTEAD TO CREATE OBJECTS WITH MULTIPLE TASKS + required: false + + url_access_action: + description: + - Action. + - choice | bypass | Allow the HTTP request, also bypass further WAF scanning. + - choice | permit | Allow the HTTP request, and continue further WAF scanning. + - choice | block | Block HTTP request. + required: false + choices: ["bypass", "permit", "block"] + + url_access_address: + description: + - Host address. + required: false + + url_access_log: + description: + - Enable/disable logging. + - choice | disable | Disable setting. + - choice | enable | Enable setting. + required: false + choices: ["disable", "enable"] + + url_access_severity: + description: + - Severity. + - choice | low | Low severity. + - choice | medium | Medium severity. + - choice | high | High severity. + required: false + choices: ["low", "medium", "high"] + + url_access_access_pattern_negate: + description: + - Enable/disable match negation. + - choice | disable | Disable setting. + - choice | enable | Enable setting. + required: false + choices: ["disable", "enable"] + + url_access_access_pattern_pattern: + description: + - URL pattern. + required: false + + url_access_access_pattern_regex: + description: + - Enable/disable regular expression based pattern match. + - choice | disable | Disable setting. + - choice | enable | Enable setting. + required: false + choices: ["disable", "enable"] + + url_access_access_pattern_srcaddr: + description: + - Source address. + required: false + +''' + +EXAMPLES = ''' + - name: DELETE Profile + fmgr_secprof_waf: + host: "{{inventory_hostname}}" + username: "{{ username }}" + password: "{{ password }}" + name: "Ansible_WAF_Profile" + comment: "Created by Ansible Module TEST" + mode: "delete" + + - name: CREATE Profile + fmgr_secprof_waf: + host: "{{inventory_hostname}}" + username: "{{ username }}" + password: "{{ password }}" + name: "Ansible_WAF_Profile" + comment: "Created by Ansible Module TEST" + mode: "set" +''' + +RETURN = """ +api_result: + description: full API response, includes status code and message + returned: always + type: string +""" + +from ansible.module_utils.basic import AnsibleModule, env_fallback +from ansible.module_utils.network.fortimanager.fortimanager import AnsibleFortiManager + +# check for pyFMG lib +try: + from pyFMG.fortimgr import FortiManager + + HAS_PYFMGR = True +except ImportError: + HAS_PYFMGR = False + + +############### +# START METHODS +############### + + +def fmgr_waf_profile_addsetdelete(fmg, paramgram): + + mode = paramgram["mode"] + adom = paramgram["adom"] + # INIT A BASIC OBJECTS + response = (-100000, {"msg": "Illegal or malformed paramgram discovered. System Exception"}) + url = "" + datagram = {} + + # EVAL THE MODE PARAMETER FOR SET OR ADD + if mode in ['set', 'add', 'update']: + url = '/pm/config/adom/{adom}/obj/waf/profile'.format(adom=adom) + datagram = fmgr_del_none(fmgr_prepare_dict(paramgram)) + + # EVAL THE MODE PARAMETER FOR DELETE + elif mode == "delete": + # SET THE CORRECT URL FOR DELETE + url = '/pm/config/adom/{adom}/obj/waf/profile/{name}'.format(adom=adom, name=paramgram["name"]) + datagram = {} + + # IF MODE = SET -- USE THE 'SET' API CALL MODE + if mode == "set": + response = fmg.set(url, datagram) + # IF MODE = UPDATE -- USER THE 'UPDATE' API CALL MODE + elif mode == "update": + response = fmg.update(url, datagram) + # IF MODE = ADD -- USE THE 'ADD' API CALL MODE + elif mode == "add": + response = fmg.add(url, datagram) + # IF MODE = DELETE -- USE THE DELETE URL AND API CALL MODE + elif mode == "delete": + response = fmg.delete(url, datagram) + + return response + + +# ADDITIONAL COMMON FUNCTIONS +# FUNCTION/METHOD FOR LOGGING OUT AND ANALYZING ERROR CODES +def fmgr_logout(fmg, module, msg="NULL", results=(), good_codes=(0,), logout_on_fail=True, logout_on_success=False): + """ + THIS METHOD CONTROLS THE LOGOUT AND ERROR REPORTING AFTER AN METHOD OR FUNCTION RUNS + """ + # pydevd.settrace('10.0.0.122', port=54654, stdoutToServer=True, stderrToServer=True) + # VALIDATION ERROR (NO RESULTS, JUST AN EXIT) + if msg != "NULL" and len(results) == 0: + try: + fmg.logout() + except BaseException: + pass + module.fail_json(msg=msg) + + # SUBMISSION ERROR + if len(results) > 0: + if msg == "NULL": + try: + msg = results[1]['status']['message'] + except BaseException: + msg = "No status message returned from pyFMG. Possible that this was a GET with a tuple result." + + if results[0] not in good_codes: + if logout_on_fail: + fmg.logout() + module.fail_json(msg=msg, **results[1]) + else: + return msg + else: + if logout_on_success: + fmg.logout() + module.exit_json(msg="API Called worked, but logout handler has been asked to logout on success", + **results[1]) + else: + return msg + + +# FUNCTION/METHOD FOR CONVERTING CIDR TO A NETMASK +# DID NOT USE IP ADDRESS MODULE TO KEEP INCLUDES TO A MINIMUM +def fmgr_cidr_to_netmask(cidr): + cidr = int(cidr) + mask = (0xffffffff >> (32 - cidr)) << (32 - cidr) + return (str((0xff000000 & mask) >> 24) + '.' + + str((0x00ff0000 & mask) >> 16) + '.' + + str((0x0000ff00 & mask) >> 8) + '.' + + str((0x000000ff & mask))) + + +# utility function: removing keys wih value of None, nothing in playbook for that key +def fmgr_del_none(obj): + if isinstance(obj, dict): + return type(obj)((fmgr_del_none(k), fmgr_del_none(v)) + for k, v in obj.items() if k is not None and (v is not None and not fmgr_is_empty_dict(v))) + else: + return obj + + +# utility function: remove keys that are need for the logic but the FMG API won't accept them +def fmgr_prepare_dict(obj): + list_of_elems = ["mode", "adom", "host", "username", "password"] + if isinstance(obj, dict): + obj = dict((key, fmgr_prepare_dict(value)) for (key, value) in obj.items() if key not in list_of_elems) + return obj + + +def fmgr_is_empty_dict(obj): + return_val = False + if isinstance(obj, dict): + if len(obj) > 0: + for k, v in obj.items(): + if isinstance(v, dict): + if len(v) == 0: + return_val = True + elif len(v) > 0: + for k1, v1 in v.items(): + if v1 is None: + return_val = True + elif v1 is not None: + return_val = False + return return_val + elif v is None: + return_val = True + elif v is not None: + return_val = False + return return_val + elif len(obj) == 0: + return_val = True + + return return_val + + +def fmgr_split_comma_strings_into_lists(obj): + if isinstance(obj, dict): + if len(obj) > 0: + for k, v in obj.items(): + if isinstance(v, str): + new_list = list() + if "," in v: + new_items = v.split(",") + for item in new_items: + new_list.append(item.strip()) + obj[k] = new_list + + return obj + + +############# +# END METHODS +############# + + +def main(): + argument_spec = dict( + adom=dict(type="str", default="root"), + host=dict(required=True, type="str"), + password=dict(fallback=(env_fallback, ["ANSIBLE_NET_PASSWORD"]), no_log=True, required=True), + username=dict(fallback=(env_fallback, ["ANSIBLE_NET_USERNAME"]), no_log=True, required=True), + mode=dict(choices=["add", "set", "delete", "update"], type="str", default="add"), + + name=dict(required=False, type="str"), + external=dict(required=False, type="str", choices=["disable", "enable"]), + extended_log=dict(required=False, type="str", choices=["disable", "enable"]), + comment=dict(required=False, type="str"), + address_list=dict(required=False, type="list"), + address_list_blocked_address=dict(required=False, type="str"), + address_list_blocked_log=dict(required=False, type="str", choices=["disable", "enable"]), + address_list_severity=dict(required=False, type="str", choices=["low", "medium", "high"]), + address_list_status=dict(required=False, type="str", choices=["disable", "enable"]), + address_list_trusted_address=dict(required=False, type="str"), + constraint=dict(required=False, type="list"), + + constraint_content_length_action=dict(required=False, type="str", choices=["allow", "block"]), + constraint_content_length_length=dict(required=False, type="int"), + constraint_content_length_log=dict(required=False, type="str", choices=["disable", "enable"]), + constraint_content_length_severity=dict(required=False, type="str", choices=["low", "medium", "high"]), + constraint_content_length_status=dict(required=False, type="str", choices=["disable", "enable"]), + + constraint_exception_address=dict(required=False, type="str"), + constraint_exception_content_length=dict(required=False, type="str", choices=["disable", "enable"]), + constraint_exception_header_length=dict(required=False, type="str", choices=["disable", "enable"]), + constraint_exception_hostname=dict(required=False, type="str", choices=["disable", "enable"]), + constraint_exception_line_length=dict(required=False, type="str", choices=["disable", "enable"]), + constraint_exception_malformed=dict(required=False, type="str", choices=["disable", "enable"]), + constraint_exception_max_cookie=dict(required=False, type="str", choices=["disable", "enable"]), + constraint_exception_max_header_line=dict(required=False, type="str", choices=["disable", "enable"]), + constraint_exception_max_range_segment=dict(required=False, type="str", choices=["disable", "enable"]), + constraint_exception_max_url_param=dict(required=False, type="str", choices=["disable", "enable"]), + constraint_exception_method=dict(required=False, type="str", choices=["disable", "enable"]), + constraint_exception_param_length=dict(required=False, type="str", choices=["disable", "enable"]), + constraint_exception_pattern=dict(required=False, type="str"), + constraint_exception_regex=dict(required=False, type="str", choices=["disable", "enable"]), + constraint_exception_url_param_length=dict(required=False, type="str", choices=["disable", "enable"]), + constraint_exception_version=dict(required=False, type="str", choices=["disable", "enable"]), + + constraint_header_length_action=dict(required=False, type="str", choices=["allow", "block"]), + constraint_header_length_length=dict(required=False, type="int"), + constraint_header_length_log=dict(required=False, type="str", choices=["disable", "enable"]), + constraint_header_length_severity=dict(required=False, type="str", choices=["low", "medium", "high"]), + constraint_header_length_status=dict(required=False, type="str", choices=["disable", "enable"]), + + constraint_hostname_action=dict(required=False, type="str", choices=["allow", "block"]), + constraint_hostname_log=dict(required=False, type="str", choices=["disable", "enable"]), + constraint_hostname_severity=dict(required=False, type="str", choices=["low", "medium", "high"]), + constraint_hostname_status=dict(required=False, type="str", choices=["disable", "enable"]), + + constraint_line_length_action=dict(required=False, type="str", choices=["allow", "block"]), + constraint_line_length_length=dict(required=False, type="int"), + constraint_line_length_log=dict(required=False, type="str", choices=["disable", "enable"]), + constraint_line_length_severity=dict(required=False, type="str", choices=["low", "medium", "high"]), + constraint_line_length_status=dict(required=False, type="str", choices=["disable", "enable"]), + + constraint_malformed_action=dict(required=False, type="str", choices=["allow", "block"]), + constraint_malformed_log=dict(required=False, type="str", choices=["disable", "enable"]), + constraint_malformed_severity=dict(required=False, type="str", choices=["low", "medium", "high"]), + constraint_malformed_status=dict(required=False, type="str", choices=["disable", "enable"]), + + constraint_max_cookie_action=dict(required=False, type="str", choices=["allow", "block"]), + constraint_max_cookie_log=dict(required=False, type="str", choices=["disable", "enable"]), + constraint_max_cookie_max_cookie=dict(required=False, type="int"), + constraint_max_cookie_severity=dict(required=False, type="str", choices=["low", "medium", "high"]), + constraint_max_cookie_status=dict(required=False, type="str", choices=["disable", "enable"]), + + constraint_max_header_line_action=dict(required=False, type="str", choices=["allow", "block"]), + constraint_max_header_line_log=dict(required=False, type="str", choices=["disable", "enable"]), + constraint_max_header_line_max_header_line=dict(required=False, type="int"), + constraint_max_header_line_severity=dict(required=False, type="str", choices=["low", "medium", "high"]), + constraint_max_header_line_status=dict(required=False, type="str", choices=["disable", "enable"]), + + constraint_max_range_segment_action=dict(required=False, type="str", choices=["allow", "block"]), + constraint_max_range_segment_log=dict(required=False, type="str", choices=["disable", "enable"]), + constraint_max_range_segment_max_range_segment=dict(required=False, type="int"), + constraint_max_range_segment_severity=dict(required=False, type="str", choices=["low", "medium", "high"]), + constraint_max_range_segment_status=dict(required=False, type="str", choices=["disable", "enable"]), + + constraint_max_url_param_action=dict(required=False, type="str", choices=["allow", "block"]), + constraint_max_url_param_log=dict(required=False, type="str", choices=["disable", "enable"]), + constraint_max_url_param_max_url_param=dict(required=False, type="int"), + constraint_max_url_param_severity=dict(required=False, type="str", choices=["low", "medium", "high"]), + constraint_max_url_param_status=dict(required=False, type="str", choices=["disable", "enable"]), + + constraint_method_action=dict(required=False, type="str", choices=["allow", "block"]), + constraint_method_log=dict(required=False, type="str", choices=["disable", "enable"]), + constraint_method_severity=dict(required=False, type="str", choices=["low", "medium", "high"]), + constraint_method_status=dict(required=False, type="str", choices=["disable", "enable"]), + + constraint_param_length_action=dict(required=False, type="str", choices=["allow", "block"]), + constraint_param_length_length=dict(required=False, type="int"), + constraint_param_length_log=dict(required=False, type="str", choices=["disable", "enable"]), + constraint_param_length_severity=dict(required=False, type="str", choices=["low", "medium", "high"]), + constraint_param_length_status=dict(required=False, type="str", choices=["disable", "enable"]), + + constraint_url_param_length_action=dict(required=False, type="str", choices=["allow", "block"]), + constraint_url_param_length_length=dict(required=False, type="int"), + constraint_url_param_length_log=dict(required=False, type="str", choices=["disable", "enable"]), + constraint_url_param_length_severity=dict(required=False, type="str", choices=["low", "medium", "high"]), + constraint_url_param_length_status=dict(required=False, type="str", choices=["disable", "enable"]), + + constraint_version_action=dict(required=False, type="str", choices=["allow", "block"]), + constraint_version_log=dict(required=False, type="str", choices=["disable", "enable"]), + constraint_version_severity=dict(required=False, type="str", choices=["low", "medium", "high"]), + constraint_version_status=dict(required=False, type="str", choices=["disable", "enable"]), + method=dict(required=False, type="list"), + method_default_allowed_methods=dict(required=False, type="str", choices=["delete", + "get", + "head", + "options", + "post", + "put", + "trace", + "others", + "connect"]), + method_log=dict(required=False, type="str", choices=["disable", "enable"]), + method_severity=dict(required=False, type="str", choices=["low", "medium", "high"]), + method_status=dict(required=False, type="str", choices=["disable", "enable"]), + + method_method_policy_address=dict(required=False, type="str"), + method_method_policy_allowed_methods=dict(required=False, type="str", choices=["delete", + "get", + "head", + "options", + "post", + "put", + "trace", + "others", + "connect"]), + method_method_policy_pattern=dict(required=False, type="str"), + method_method_policy_regex=dict(required=False, type="str", choices=["disable", "enable"]), + signature=dict(required=False, type="list"), + signature_credit_card_detection_threshold=dict(required=False, type="int"), + signature_disabled_signature=dict(required=False, type="str"), + signature_disabled_sub_class=dict(required=False, type="str"), + + signature_custom_signature_action=dict(required=False, type="str", choices=["allow", "block", "erase"]), + signature_custom_signature_case_sensitivity=dict(required=False, type="str", choices=["disable", "enable"]), + signature_custom_signature_direction=dict(required=False, type="str", choices=["request", "response"]), + signature_custom_signature_log=dict(required=False, type="str", choices=["disable", "enable"]), + signature_custom_signature_name=dict(required=False, type="str"), + signature_custom_signature_pattern=dict(required=False, type="str"), + signature_custom_signature_severity=dict(required=False, type="str", choices=["low", "medium", "high"]), + signature_custom_signature_status=dict(required=False, type="str", choices=["disable", "enable"]), + signature_custom_signature_target=dict(required=False, type="str", choices=["arg", + "arg-name", + "req-body", + "req-cookie", + "req-cookie-name", + "req-filename", + "req-header", + "req-header-name", + "req-raw-uri", + "req-uri", + "resp-body", + "resp-hdr", + "resp-status"]), + + signature_main_class_action=dict(required=False, type="str", choices=["allow", "block", "erase"]), + signature_main_class_log=dict(required=False, type="str", choices=["disable", "enable"]), + signature_main_class_severity=dict(required=False, type="str", choices=["low", "medium", "high"]), + signature_main_class_status=dict(required=False, type="str", choices=["disable", "enable"]), + url_access=dict(required=False, type="list"), + url_access_action=dict(required=False, type="str", choices=["bypass", "permit", "block"]), + url_access_address=dict(required=False, type="str"), + url_access_log=dict(required=False, type="str", choices=["disable", "enable"]), + url_access_severity=dict(required=False, type="str", choices=["low", "medium", "high"]), + + url_access_access_pattern_negate=dict(required=False, type="str", choices=["disable", "enable"]), + url_access_access_pattern_pattern=dict(required=False, type="str"), + url_access_access_pattern_regex=dict(required=False, type="str", choices=["disable", "enable"]), + url_access_access_pattern_srcaddr=dict(required=False, type="str"), + + ) + + module = AnsibleModule(argument_spec, supports_check_mode=False) + + # MODULE PARAMGRAM + paramgram = { + "mode": module.params["mode"], + "adom": module.params["adom"], + "name": module.params["name"], + "external": module.params["external"], + "extended-log": module.params["extended_log"], + "comment": module.params["comment"], + "address-list": { + "blocked-address": module.params["address_list_blocked_address"], + "blocked-log": module.params["address_list_blocked_log"], + "severity": module.params["address_list_severity"], + "status": module.params["address_list_status"], + "trusted-address": module.params["address_list_trusted_address"], + }, + "constraint": { + "content-length": { + "action": module.params["constraint_content_length_action"], + "length": module.params["constraint_content_length_length"], + "log": module.params["constraint_content_length_log"], + "severity": module.params["constraint_content_length_severity"], + "status": module.params["constraint_content_length_status"], + }, + "exception": { + "address": module.params["constraint_exception_address"], + "content-length": module.params["constraint_exception_content_length"], + "header-length": module.params["constraint_exception_header_length"], + "hostname": module.params["constraint_exception_hostname"], + "line-length": module.params["constraint_exception_line_length"], + "malformed": module.params["constraint_exception_malformed"], + "max-cookie": module.params["constraint_exception_max_cookie"], + "max-header-line": module.params["constraint_exception_max_header_line"], + "max-range-segment": module.params["constraint_exception_max_range_segment"], + "max-url-param": module.params["constraint_exception_max_url_param"], + "method": module.params["constraint_exception_method"], + "param-length": module.params["constraint_exception_param_length"], + "pattern": module.params["constraint_exception_pattern"], + "regex": module.params["constraint_exception_regex"], + "url-param-length": module.params["constraint_exception_url_param_length"], + "version": module.params["constraint_exception_version"], + }, + "header-length": { + "action": module.params["constraint_header_length_action"], + "length": module.params["constraint_header_length_length"], + "log": module.params["constraint_header_length_log"], + "severity": module.params["constraint_header_length_severity"], + "status": module.params["constraint_header_length_status"], + }, + "hostname": { + "action": module.params["constraint_hostname_action"], + "log": module.params["constraint_hostname_log"], + "severity": module.params["constraint_hostname_severity"], + "status": module.params["constraint_hostname_status"], + }, + "line-length": { + "action": module.params["constraint_line_length_action"], + "length": module.params["constraint_line_length_length"], + "log": module.params["constraint_line_length_log"], + "severity": module.params["constraint_line_length_severity"], + "status": module.params["constraint_line_length_status"], + }, + "malformed": { + "action": module.params["constraint_malformed_action"], + "log": module.params["constraint_malformed_log"], + "severity": module.params["constraint_malformed_severity"], + "status": module.params["constraint_malformed_status"], + }, + "max-cookie": { + "action": module.params["constraint_max_cookie_action"], + "log": module.params["constraint_max_cookie_log"], + "max-cookie": module.params["constraint_max_cookie_max_cookie"], + "severity": module.params["constraint_max_cookie_severity"], + "status": module.params["constraint_max_cookie_status"], + }, + "max-header-line": { + "action": module.params["constraint_max_header_line_action"], + "log": module.params["constraint_max_header_line_log"], + "max-header-line": module.params["constraint_max_header_line_max_header_line"], + "severity": module.params["constraint_max_header_line_severity"], + "status": module.params["constraint_max_header_line_status"], + }, + "max-range-segment": { + "action": module.params["constraint_max_range_segment_action"], + "log": module.params["constraint_max_range_segment_log"], + "max-range-segment": module.params["constraint_max_range_segment_max_range_segment"], + "severity": module.params["constraint_max_range_segment_severity"], + "status": module.params["constraint_max_range_segment_status"], + }, + "max-url-param": { + "action": module.params["constraint_max_url_param_action"], + "log": module.params["constraint_max_url_param_log"], + "max-url-param": module.params["constraint_max_url_param_max_url_param"], + "severity": module.params["constraint_max_url_param_severity"], + "status": module.params["constraint_max_url_param_status"], + }, + "method": { + "action": module.params["constraint_method_action"], + "log": module.params["constraint_method_log"], + "severity": module.params["constraint_method_severity"], + "status": module.params["constraint_method_status"], + }, + "param-length": { + "action": module.params["constraint_param_length_action"], + "length": module.params["constraint_param_length_length"], + "log": module.params["constraint_param_length_log"], + "severity": module.params["constraint_param_length_severity"], + "status": module.params["constraint_param_length_status"], + }, + "url-param-length": { + "action": module.params["constraint_url_param_length_action"], + "length": module.params["constraint_url_param_length_length"], + "log": module.params["constraint_url_param_length_log"], + "severity": module.params["constraint_url_param_length_severity"], + "status": module.params["constraint_url_param_length_status"], + }, + "version": { + "action": module.params["constraint_version_action"], + "log": module.params["constraint_version_log"], + "severity": module.params["constraint_version_severity"], + "status": module.params["constraint_version_status"], + }, + }, + "method": { + "default-allowed-methods": module.params["method_default_allowed_methods"], + "log": module.params["method_log"], + "severity": module.params["method_severity"], + "status": module.params["method_status"], + "method-policy": { + "address": module.params["method_method_policy_address"], + "allowed-methods": module.params["method_method_policy_allowed_methods"], + "pattern": module.params["method_method_policy_pattern"], + "regex": module.params["method_method_policy_regex"], + }, + }, + "signature": { + "credit-card-detection-threshold": module.params["signature_credit_card_detection_threshold"], + "disabled-signature": module.params["signature_disabled_signature"], + "disabled-sub-class": module.params["signature_disabled_sub_class"], + "custom-signature": { + "action": module.params["signature_custom_signature_action"], + "case-sensitivity": module.params["signature_custom_signature_case_sensitivity"], + "direction": module.params["signature_custom_signature_direction"], + "log": module.params["signature_custom_signature_log"], + "name": module.params["signature_custom_signature_name"], + "pattern": module.params["signature_custom_signature_pattern"], + "severity": module.params["signature_custom_signature_severity"], + "status": module.params["signature_custom_signature_status"], + "target": module.params["signature_custom_signature_target"], + }, + "main-class": { + "action": module.params["signature_main_class_action"], + "log": module.params["signature_main_class_log"], + "severity": module.params["signature_main_class_severity"], + "status": module.params["signature_main_class_status"], + }, + }, + "url-access": { + "action": module.params["url_access_action"], + "address": module.params["url_access_address"], + "log": module.params["url_access_log"], + "severity": module.params["url_access_severity"], + "access-pattern": { + "negate": module.params["url_access_access_pattern_negate"], + "pattern": module.params["url_access_access_pattern_pattern"], + "regex": module.params["url_access_access_pattern_regex"], + "srcaddr": module.params["url_access_access_pattern_srcaddr"], + } + } + } + + list_overrides = ['address-list', 'constraint', 'method', 'signature', 'url-access'] + for list_variable in list_overrides: + override_data = list() + try: + override_data = module.params[list_variable] + except BaseException: + pass + try: + if override_data: + del paramgram[list_variable] + paramgram[list_variable] = override_data + except BaseException: + pass + + # CHECK IF THE HOST/USERNAME/PW EXISTS, AND IF IT DOES, LOGIN. + host = module.params["host"] + password = module.params["password"] + username = module.params["username"] + if host is None or username is None or password is None: + module.fail_json(msg="Host and username and password are required") + + # CHECK IF LOGIN FAILED + fmg = AnsibleFortiManager(module, module.params["host"], module.params["username"], module.params["password"]) + + response = fmg.login() + if response[1]['status']['code'] != 0: + module.fail_json(msg="Connection to FortiManager Failed") + + results = fmgr_waf_profile_addsetdelete(fmg, paramgram) + if results[0] != 0: + fmgr_logout(fmg, module, results=results, good_codes=[0]) + + fmg.logout() + + if results is not None: + return module.exit_json(**results[1]) + else: + return module.exit_json(msg="No results were returned from the API call.") + + +if __name__ == "__main__": + main() diff --git a/test/units/modules/network/fortimanager/fixtures/test_fmgr_secprof_waf.json b/test/units/modules/network/fortimanager/fixtures/test_fmgr_secprof_waf.json new file mode 100644 index 00000000000..1fa90d75a52 --- /dev/null +++ b/test/units/modules/network/fortimanager/fixtures/test_fmgr_secprof_waf.json @@ -0,0 +1,360 @@ +{ + "fmgr_waf_profile_addsetdelete": [ + { + "paramgram_used": { + "comment": "Created by Ansible Module TEST", + "name": "Ansible_WAF_Profile", + "adom": "root", + "address-list": { + "blocked-address": null, + "status": null, + "severity": null, + "blocked-log": null, + "trusted-address": null + }, + "constraint": { + "header-length": { + "action": null, + "status": null, + "length": null, + "log": null, + "severity": null + }, + "content-length": { + "action": null, + "status": null, + "length": null, + "log": null, + "severity": null + }, + "max-cookie": { + "action": null, + "status": null, + "max-cookie": null, + "log": null, + "severity": null + }, + "url-param-length": { + "action": null, + "status": null, + "length": null, + "log": null, + "severity": null + }, + "hostname": { + "action": null, + "status": null, + "log": null, + "severity": null + }, + "line-length": { + "action": null, + "status": null, + "length": null, + "log": null, + "severity": null + }, + "exception": { + "regex": null, + "header-length": null, + "content-length": null, + "max-cookie": null, + "pattern": null, + "hostname": null, + "line-length": null, + "max-range-segment": null, + "url-param-length": null, + "version": null, + "param-length": null, + "malformed": null, + "address": null, + "max-url-param": null, + "max-header-line": null, + "method": null + }, + "max-range-segment": { + "action": null, + "status": null, + "max-range-segment": null, + "severity": null, + "log": null + }, + "version": { + "action": null, + "status": null, + "log": null, + "severity": null + }, + "param-length": { + "action": null, + "status": null, + "length": null, + "log": null, + "severity": null + }, + "malformed": { + "action": null, + "status": null, + "log": null, + "severity": null + }, + "max-url-param": { + "action": null, + "status": null, + "max-url-param": null, + "log": null, + "severity": null + }, + "max-header-line": { + "action": null, + "status": null, + "max-header-line": null, + "log": null, + "severity": null + }, + "method": { + "action": null, + "status": null, + "log": null, + "severity": null + } + }, + "extended-log": null, + "url-access": { + "action": null, + "address": null, + "severity": null, + "access-pattern": { + "negate": null, + "pattern": null, + "srcaddr": null, + "regex": null + }, + "log": null + }, + "external": null, + "signature": { + "custom-signature": { + "status": null, + "direction": null, + "target": null, + "severity": null, + "case-sensitivity": null, + "name": null, + "pattern": null, + "action": null, + "log": null + }, + "credit-card-detection-threshold": null, + "main-class": { + "action": null, + "status": null, + "log": null, + "severity": null + }, + "disabled-signature": null, + "disabled-sub-class": null + }, + "method": { + "status": null, + "severity": null, + "default-allowed-methods": null, + "log": null, + "method-policy": { + "regex": null, + "pattern": null, + "allowed-methods": null, + "address": null + } + }, + "mode": "delete" + }, + "raw_response": { + "status": { + "message": "OK", + "code": 0 + }, + "url": "/pm/config/adom/root/obj/waf/profile/Ansible_WAF_Profile" + }, + "post_method": "delete" + }, + { + "raw_response": { + "status": { + "message": "OK", + "code": 0 + }, + "url": "/pm/config/adom/root/obj/waf/profile" + }, + "paramgram_used": { + "comment": "Created by Ansible Module TEST", + "adom": "root", + "address-list": { + "blocked-address": null, + "status": null, + "severity": null, + "blocked-log": null, + "trusted-address": null + }, + "extended-log": null, + "url-access": { + "action": null, + "severity": null, + "log": null, + "access-pattern": { + "negate": null, + "pattern": null, + "srcaddr": null, + "regex": null + }, + "address": null + }, + "external": null, + "name": "Ansible_WAF_Profile", + "constraint": { + "content-length": { + "action": null, + "status": null, + "length": null, + "log": null, + "severity": null + }, + "max-cookie": { + "action": null, + "status": null, + "max-cookie": null, + "log": null, + "severity": null + }, + "line-length": { + "action": null, + "status": null, + "length": null, + "log": null, + "severity": null + }, + "max-range-segment": { + "action": null, + "severity": null, + "status": null, + "log": null, + "max-range-segment": null + }, + "param-length": { + "action": null, + "status": null, + "length": null, + "log": null, + "severity": null + }, + "malformed": { + "action": null, + "status": null, + "log": null, + "severity": null + }, + "max-url-param": { + "action": null, + "status": null, + "max-url-param": null, + "log": null, + "severity": null + }, + "header-length": { + "action": null, + "status": null, + "length": null, + "log": null, + "severity": null + }, + "exception": { + "regex": null, + "header-length": null, + "content-length": null, + "max-cookie": null, + "pattern": null, + "hostname": null, + "line-length": null, + "max-range-segment": null, + "url-param-length": null, + "version": null, + "param-length": null, + "malformed": null, + "address": null, + "max-url-param": null, + "max-header-line": null, + "method": null + }, + "hostname": { + "action": null, + "status": null, + "log": null, + "severity": null + }, + "url-param-length": { + "action": null, + "status": null, + "length": null, + "log": null, + "severity": null + }, + "version": { + "action": null, + "status": null, + "log": null, + "severity": null + }, + "max-header-line": { + "action": null, + "status": null, + "max-header-line": null, + "log": null, + "severity": null + }, + "method": { + "action": null, + "status": null, + "log": null, + "severity": null + } + }, + "mode": "set", + "signature": { + "custom-signature": { + "status": null, + "direction": null, + "log": null, + "severity": null, + "target": null, + "action": null, + "pattern": null, + "case-sensitivity": null, + "name": null + }, + "credit-card-detection-threshold": null, + "main-class": { + "action": null, + "status": null, + "log": null, + "severity": null + }, + "disabled-signature": null, + "disabled-sub-class": null + }, + "method": { + "status": null, + "default-allowed-methods": null, + "method-policy": { + "regex": null, + "pattern": null, + "allowed-methods": null, + "address": null + }, + "log": null, + "severity": null + } + }, + "post_method": "set" + } + ] +} diff --git a/test/units/modules/network/fortimanager/test_fmgr_secprof_waf.py b/test/units/modules/network/fortimanager/test_fmgr_secprof_waf.py new file mode 100644 index 00000000000..e1f0b35625c --- /dev/null +++ b/test/units/modules/network/fortimanager/test_fmgr_secprof_waf.py @@ -0,0 +1,131 @@ +# Copyright 2018 Fortinet, Inc. +# +# This program 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. +# +# This program 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 . + +# Make coding more python3-ish +from __future__ import (absolute_import, division, print_function) +__metaclass__ = type + +import os +import json +from pyFMG.fortimgr import FortiManager +import pytest + +try: + from ansible.modules.network.fortimanager import fmgr_secprof_waf +except ImportError: + pytest.skip("Could not load required modules for testing", allow_module_level=True) + +fmg_instance = FortiManager("1.1.1.1", "admin", "") + + +def load_fixtures(): + fixture_path = os.path.join( + os.path.dirname(__file__), + 'fixtures') + "/{filename}.json".format( + filename=os.path.splitext( + os.path.basename(__file__))[0]) + try: + with open(fixture_path, "r") as fixture_file: + fixture_data = json.load(fixture_file) + except IOError: + return [] + return [fixture_data] + + +@pytest.fixture(scope="function", params=load_fixtures()) +def fixture_data(request): + func_name = request.function.__name__.replace("test_", "") + return request.param.get(func_name, None) + + +def test_fmgr_waf_profile_addsetdelete(fixture_data, mocker): + mocker.patch("pyFMG.fortimgr.FortiManager._post_request", side_effect=fixture_data) + # Fixture sets used:########################### + + ################################################## + # comment: Created by Ansible Module TEST + # name: Ansible_WAF_Profile + # adom: root + # address-list: {'blocked-address': None, 'status': None, 'severity': None, 'blocked-log': None, + # 'trusted-address': None} + # constraint: {'header-length': {'action': None, 'status': None, 'length': None, 'log': None, 'severity': None}, + # 'content-length': {'action': None, 'status': None, 'length': None, 'log': None, 'severity': None}, + # 'max-cookie': {'action': None, 'status': None, 'max-cookie': None, 'log': None, 'severity': None}, + # 'url-param-length': {'action': None, 'status': None, 'length': None, 'log': None, 'severity': None}, + # 'hostname': {'action': None, 'status': None, 'log': None, 'severity': None}, + # 'line-length': {'action': None, 'status': None, 'length': None, 'log': None, 'severity': None}, + # 'exception': {'regex': None, 'header-length': None, 'content-length': None, 'max-cookie': None, 'pattern': None, + # 'hostname': None, 'line-length': None, 'max-range-segment': None, 'url-param-length': None, 'version': None, + # 'param-length': None, 'malformed': None, 'address': None, 'max-url-param': None, 'max-header-line': None, + # 'method': None}, 'max-range-segment': {'action': None, 'status': None, 'max-range-segment': None, + # 'severity': None, 'log': None}, 'version': {'action': None, 'status': None, 'log': None, 'severity': None}, + # 'param-length': {'action': None, 'status': None, 'length': None, 'log': None, 'severity': None}, + # 'malformed': {'action': None, 'status': None, 'log': None, 'severity': None}, 'max-url-param': {'action': None, + # 'status': None, 'max-url-param': None, 'log': None, 'severity': None}, 'max-header-line': {'action': None, + # 'status': None, 'max-header-line': None, 'log': None, 'severity': None}, 'method': {'action': None, + # 'status': None, 'log': None, 'severity': None}} + # extended-log: None + # url-access: {'action': None, 'address': None, 'severity': None, 'access-pattern': {'negate': None, + # 'pattern': None, 'srcaddr': None, 'regex': None}, 'log': None} + # external: None + # signature: {'custom-signature': {'status': None, 'direction': None, 'target': None, 'severity': None, + # 'case-sensitivity': None, 'name': None, 'pattern': None, 'action': None, 'log': None}, + # 'credit-card-detection-threshold': None, 'main-class': {'action': None, 'status': None, 'log': None, + # 'severity': None}, 'disabled-signature': None, 'disabled-sub-class': None} + # method: {'status': None, 'severity': None, 'default-allowed-methods': None, 'log': None, + # 'method-policy': {'regex': None, 'pattern': None, 'allowed-methods': None, 'address': None}} + # mode: delete + ################################################## + ################################################## + # comment: Created by Ansible Module TEST + # adom: root + # address-list: {'blocked-address': None, 'status': None, 'severity': None, 'blocked-log': None, + # 'trusted-address': None} + # extended-log: None + # url-access: {'action': None, 'severity': None, 'log': None, 'access-pattern': {'negate': None, 'pattern': None, + # 'srcaddr': None, 'regex': None}, 'address': None} + # external: None + # name: Ansible_WAF_Profile + # constraint: {'content-length': {'action': None, 'status': None, 'length': None, 'log': None, 'severity': None}, + # 'max-cookie': {'action': None, 'status': None, 'max-cookie': None, 'log': None, 'severity': None}, + # 'line-length': {'action': None, 'status': None, 'length': None, 'log': None, 'severity': None}, + # 'max-range-segment': {'action': None, 'severity': None, 'status': None, 'log': None, 'max-range-segment': None}, + # 'param-length': {'action': None, 'status': None, 'length': None, 'log': None, 'severity': None}, + # 'malformed': {'action': None, 'status': None, 'log': None, 'severity': None}, 'max-url-param': {'action': None, + # 'status': None, 'max-url-param': None, 'log': None, 'severity': None}, 'header-length': {'action': None, + # 'status': None, 'length': None, 'log': None, 'severity': None}, 'exception': {'regex': None, + # 'header-length': None, 'content-length': None, 'max-cookie': None, 'pattern': None, 'hostname': None, + # 'line-length': None, 'max-range-segment': None, 'url-param-length': None, 'version': None, 'param-length': None, + # 'malformed': None, 'address': None, 'max-url-param': None, 'max-header-line': None, 'method': None}, + # 'hostname': {'action': None, 'status': None, 'log': None, 'severity': None}, + # 'url-param-length': {'action': None, 'status': None, 'length': None, 'log': None, 'severity': None}, + # 'version': {'action': None, 'status': None, 'log': None, 'severity': None}, 'max-header-line': {'action': None, + # 'status': None, 'max-header-line': None, 'log': None, 'severity': None}, 'method': {'action': None, + # 'status': None, 'log': None, 'severity': None}} + # mode: set + # signature: {'custom-signature': {'status': None, 'direction': None, 'log': None, 'severity': None, 'target': None, + # 'action': None, 'pattern': None, 'case-sensitivity': None, 'name': None}, + # 'credit-card-detection-threshold': None, 'main-class': {'action': None, 'status': None, 'log': None, + # 'severity': None}, 'disabled-signature': None, 'disabled-sub-class': None} + # method: {'status': None, 'default-allowed-methods': None, 'method-policy': {'regex': None, 'pattern': None, + # 'allowed-methods': None, 'address': None}, 'log': None, 'severity': None} + ################################################## + + # Test using fixture 1 # + output = fmgr_secprof_waf.fmgr_waf_profile_addsetdelete(fmg_instance, fixture_data[0]['paramgram_used']) + assert output['raw_response']['status']['code'] == 0 + # Test using fixture 2 # + output = fmgr_secprof_waf.fmgr_waf_profile_addsetdelete(fmg_instance, fixture_data[1]['paramgram_used']) + assert output['raw_response']['status']['code'] == 0