1#!/usr/bin/python 2from __future__ import (absolute_import, division, print_function) 3# Copyright 2019 Fortinet, Inc. 4# 5# This program is free software: you can redistribute it and/or modify 6# it under the terms of the GNU General Public License as published by 7# the Free Software Foundation, either version 3 of the License, or 8# (at your option) any later version. 9# 10# This program is distributed in the hope that it will be useful, 11# but WITHOUT ANY WARRANTY; without even the implied warranty of 12# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13# GNU General Public License for more details. 14# 15# You should have received a copy of the GNU General Public License 16# along with this program. If not, see <https://www.gnu.org/licenses/>. 17 18__metaclass__ = type 19 20ANSIBLE_METADATA = {'status': ['preview'], 21 'supported_by': 'community', 22 'metadata_version': '1.1'} 23 24DOCUMENTATION = ''' 25--- 26module: fortios_firewall_service_category 27short_description: Configure service categories in Fortinet's FortiOS and FortiGate. 28description: 29 - This module is able to configure a FortiGate or FortiOS (FOS) device by allowing the 30 user to set and modify firewall_service feature and category category. 31 Examples include all parameters and values need to be adjusted to datasources before usage. 32 Tested with FOS v6.0.5 33version_added: "2.8" 34author: 35 - Miguel Angel Munoz (@mamunozgonzalez) 36 - Nicolas Thomas (@thomnico) 37notes: 38 - Requires fortiosapi library developed by Fortinet 39 - Run as a local_action in your playbook 40requirements: 41 - fortiosapi>=0.9.8 42options: 43 host: 44 description: 45 - FortiOS or FortiGate IP address. 46 type: str 47 required: false 48 username: 49 description: 50 - FortiOS or FortiGate username. 51 type: str 52 required: false 53 password: 54 description: 55 - FortiOS or FortiGate password. 56 type: str 57 default: "" 58 vdom: 59 description: 60 - Virtual domain, among those defined previously. A vdom is a 61 virtual instance of the FortiGate that can be configured and 62 used as a different unit. 63 type: str 64 default: root 65 https: 66 description: 67 - Indicates if the requests towards FortiGate must use HTTPS protocol. 68 type: bool 69 default: true 70 ssl_verify: 71 description: 72 - Ensures FortiGate certificate must be verified by a proper CA. 73 type: bool 74 default: true 75 version_added: 2.9 76 state: 77 description: 78 - Indicates whether to create or remove the object. 79 This attribute was present already in previous version in a deeper level. 80 It has been moved out to this outer level. 81 type: str 82 required: false 83 choices: 84 - present 85 - absent 86 version_added: 2.9 87 firewall_service_category: 88 description: 89 - Configure service categories. 90 default: null 91 type: dict 92 suboptions: 93 state: 94 description: 95 - B(Deprecated) 96 - Starting with Ansible 2.9 we recommend using the top-level 'state' parameter. 97 - HORIZONTALLINE 98 - Indicates whether to create or remove the object. 99 type: str 100 required: false 101 choices: 102 - present 103 - absent 104 comment: 105 description: 106 - Comment. 107 type: str 108 name: 109 description: 110 - Service category name. 111 required: true 112 type: str 113''' 114 115EXAMPLES = ''' 116- hosts: localhost 117 vars: 118 host: "192.168.122.40" 119 username: "admin" 120 password: "" 121 vdom: "root" 122 ssl_verify: "False" 123 tasks: 124 - name: Configure service categories. 125 fortios_firewall_service_category: 126 host: "{{ host }}" 127 username: "{{ username }}" 128 password: "{{ password }}" 129 vdom: "{{ vdom }}" 130 https: "False" 131 state: "present" 132 firewall_service_category: 133 comment: "Comment." 134 name: "default_name_4" 135''' 136 137RETURN = ''' 138build: 139 description: Build number of the fortigate image 140 returned: always 141 type: str 142 sample: '1547' 143http_method: 144 description: Last method used to provision the content into FortiGate 145 returned: always 146 type: str 147 sample: 'PUT' 148http_status: 149 description: Last result given by FortiGate on last operation applied 150 returned: always 151 type: str 152 sample: "200" 153mkey: 154 description: Master key (id) used in the last call to FortiGate 155 returned: success 156 type: str 157 sample: "id" 158name: 159 description: Name of the table used to fulfill the request 160 returned: always 161 type: str 162 sample: "urlfilter" 163path: 164 description: Path of the table used to fulfill the request 165 returned: always 166 type: str 167 sample: "webfilter" 168revision: 169 description: Internal revision number 170 returned: always 171 type: str 172 sample: "17.0.2.10658" 173serial: 174 description: Serial number of the unit 175 returned: always 176 type: str 177 sample: "FGVMEVYYQT3AB5352" 178status: 179 description: Indication of the operation's result 180 returned: always 181 type: str 182 sample: "success" 183vdom: 184 description: Virtual domain used 185 returned: always 186 type: str 187 sample: "root" 188version: 189 description: Version of the FortiGate 190 returned: always 191 type: str 192 sample: "v5.6.3" 193 194''' 195 196from ansible.module_utils.basic import AnsibleModule 197from ansible.module_utils.connection import Connection 198from ansible.module_utils.network.fortios.fortios import FortiOSHandler 199from ansible.module_utils.network.fortimanager.common import FAIL_SOCKET_MSG 200 201 202def login(data, fos): 203 host = data['host'] 204 username = data['username'] 205 password = data['password'] 206 ssl_verify = data['ssl_verify'] 207 208 fos.debug('on') 209 if 'https' in data and not data['https']: 210 fos.https('off') 211 else: 212 fos.https('on') 213 214 fos.login(host, username, password, verify=ssl_verify) 215 216 217def filter_firewall_service_category_data(json): 218 option_list = ['comment', 'name'] 219 dictionary = {} 220 221 for attribute in option_list: 222 if attribute in json and json[attribute] is not None: 223 dictionary[attribute] = json[attribute] 224 225 return dictionary 226 227 228def underscore_to_hyphen(data): 229 if isinstance(data, list): 230 for elem in data: 231 elem = underscore_to_hyphen(elem) 232 elif isinstance(data, dict): 233 new_data = {} 234 for k, v in data.items(): 235 new_data[k.replace('_', '-')] = underscore_to_hyphen(v) 236 data = new_data 237 238 return data 239 240 241def firewall_service_category(data, fos): 242 vdom = data['vdom'] 243 if 'state' in data and data['state']: 244 state = data['state'] 245 elif 'state' in data['firewall_service_category'] and data['firewall_service_category']: 246 state = data['firewall_service_category']['state'] 247 else: 248 state = True 249 firewall_service_category_data = data['firewall_service_category'] 250 filtered_data = underscore_to_hyphen(filter_firewall_service_category_data(firewall_service_category_data)) 251 252 if state == "present": 253 return fos.set('firewall.service', 254 'category', 255 data=filtered_data, 256 vdom=vdom) 257 258 elif state == "absent": 259 return fos.delete('firewall.service', 260 'category', 261 mkey=filtered_data['name'], 262 vdom=vdom) 263 264 265def is_successful_status(status): 266 return status['status'] == "success" or \ 267 status['http_method'] == "DELETE" and status['http_status'] == 404 268 269 270def fortios_firewall_service(data, fos): 271 272 if data['firewall_service_category']: 273 resp = firewall_service_category(data, fos) 274 275 return not is_successful_status(resp), \ 276 resp['status'] == "success", \ 277 resp 278 279 280def main(): 281 fields = { 282 "host": {"required": False, "type": "str"}, 283 "username": {"required": False, "type": "str"}, 284 "password": {"required": False, "type": "str", "default": "", "no_log": True}, 285 "vdom": {"required": False, "type": "str", "default": "root"}, 286 "https": {"required": False, "type": "bool", "default": True}, 287 "ssl_verify": {"required": False, "type": "bool", "default": True}, 288 "state": {"required": False, "type": "str", 289 "choices": ["present", "absent"]}, 290 "firewall_service_category": { 291 "required": False, "type": "dict", "default": None, 292 "options": { 293 "state": {"required": False, "type": "str", 294 "choices": ["present", "absent"]}, 295 "comment": {"required": False, "type": "str"}, 296 "name": {"required": True, "type": "str"} 297 298 } 299 } 300 } 301 302 module = AnsibleModule(argument_spec=fields, 303 supports_check_mode=False) 304 305 # legacy_mode refers to using fortiosapi instead of HTTPAPI 306 legacy_mode = 'host' in module.params and module.params['host'] is not None and \ 307 'username' in module.params and module.params['username'] is not None and \ 308 'password' in module.params and module.params['password'] is not None 309 310 if not legacy_mode: 311 if module._socket_path: 312 connection = Connection(module._socket_path) 313 fos = FortiOSHandler(connection) 314 315 is_error, has_changed, result = fortios_firewall_service(module.params, fos) 316 else: 317 module.fail_json(**FAIL_SOCKET_MSG) 318 else: 319 try: 320 from fortiosapi import FortiOSAPI 321 except ImportError: 322 module.fail_json(msg="fortiosapi module is required") 323 324 fos = FortiOSAPI() 325 326 login(module.params, fos) 327 is_error, has_changed, result = fortios_firewall_service(module.params, fos) 328 fos.logout() 329 330 if not is_error: 331 module.exit_json(changed=has_changed, meta=result) 332 else: 333 module.fail_json(msg="Error in repo", meta=result) 334 335 336if __name__ == '__main__': 337 main() 338