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_internet_service_custom 27short_description: Configure custom Internet Services 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 feature and internet_service_custom 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_internet_service_custom: 88 description: 89 - Configure custom Internet Services. 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 disable_entry: 109 description: 110 - Disable entries in the Internet Service database. 111 type: list 112 suboptions: 113 id: 114 description: 115 - Disable entry ID. 116 required: true 117 type: int 118 ip_range: 119 description: 120 - IP ranges in the disable entry. 121 type: list 122 suboptions: 123 end_ip: 124 description: 125 - End IP address. 126 type: str 127 id: 128 description: 129 - Disable entry range ID. 130 required: true 131 type: int 132 start_ip: 133 description: 134 - Start IP address. 135 type: str 136 port: 137 description: 138 - Integer value for the TCP/IP port (0 - 65535). 139 type: int 140 protocol: 141 description: 142 - Integer value for the protocol type as defined by IANA (0 - 255). 143 type: int 144 entry: 145 description: 146 - Entries added to the Internet Service database and custom database. 147 type: list 148 suboptions: 149 dst: 150 description: 151 - Destination address or address group name. 152 type: list 153 suboptions: 154 name: 155 description: 156 - Select the destination address or address group object from available options. Source firewall.address.name firewall 157 .addrgrp.name. 158 required: true 159 type: str 160 id: 161 description: 162 - Entry ID(1-255). 163 required: true 164 type: int 165 port_range: 166 description: 167 - Port ranges in the custom entry. 168 type: list 169 suboptions: 170 end_port: 171 description: 172 - Integer value for ending TCP/UDP/SCTP destination port in range (1 to 65535). 173 type: int 174 id: 175 description: 176 - Custom entry port range ID. 177 required: true 178 type: int 179 start_port: 180 description: 181 - Integer value for starting TCP/UDP/SCTP destination port in range (1 to 65535). 182 type: int 183 protocol: 184 description: 185 - Integer value for the protocol type as defined by IANA (0 - 255). 186 type: int 187 master_service_id: 188 description: 189 - Internet Service ID in the Internet Service database. Source firewall.internet-service.id. 190 type: int 191 name: 192 description: 193 - Internet Service name. 194 required: true 195 type: str 196''' 197 198EXAMPLES = ''' 199- hosts: localhost 200 vars: 201 host: "192.168.122.40" 202 username: "admin" 203 password: "" 204 vdom: "root" 205 ssl_verify: "False" 206 tasks: 207 - name: Configure custom Internet Services. 208 fortios_firewall_internet_service_custom: 209 host: "{{ host }}" 210 username: "{{ username }}" 211 password: "{{ password }}" 212 vdom: "{{ vdom }}" 213 https: "False" 214 state: "present" 215 firewall_internet_service_custom: 216 comment: "Comment." 217 disable_entry: 218 - 219 id: "5" 220 ip_range: 221 - 222 end_ip: "<your_own_value>" 223 id: "8" 224 start_ip: "<your_own_value>" 225 port: "10" 226 protocol: "11" 227 entry: 228 - 229 dst: 230 - 231 name: "default_name_14 (source firewall.address.name firewall.addrgrp.name)" 232 id: "15" 233 port_range: 234 - 235 end_port: "17" 236 id: "18" 237 start_port: "19" 238 protocol: "20" 239 master_service_id: "21 (source firewall.internet-service.id)" 240 name: "default_name_22" 241''' 242 243RETURN = ''' 244build: 245 description: Build number of the fortigate image 246 returned: always 247 type: str 248 sample: '1547' 249http_method: 250 description: Last method used to provision the content into FortiGate 251 returned: always 252 type: str 253 sample: 'PUT' 254http_status: 255 description: Last result given by FortiGate on last operation applied 256 returned: always 257 type: str 258 sample: "200" 259mkey: 260 description: Master key (id) used in the last call to FortiGate 261 returned: success 262 type: str 263 sample: "id" 264name: 265 description: Name of the table used to fulfill the request 266 returned: always 267 type: str 268 sample: "urlfilter" 269path: 270 description: Path of the table used to fulfill the request 271 returned: always 272 type: str 273 sample: "webfilter" 274revision: 275 description: Internal revision number 276 returned: always 277 type: str 278 sample: "17.0.2.10658" 279serial: 280 description: Serial number of the unit 281 returned: always 282 type: str 283 sample: "FGVMEVYYQT3AB5352" 284status: 285 description: Indication of the operation's result 286 returned: always 287 type: str 288 sample: "success" 289vdom: 290 description: Virtual domain used 291 returned: always 292 type: str 293 sample: "root" 294version: 295 description: Version of the FortiGate 296 returned: always 297 type: str 298 sample: "v5.6.3" 299 300''' 301 302from ansible.module_utils.basic import AnsibleModule 303from ansible.module_utils.connection import Connection 304from ansible.module_utils.network.fortios.fortios import FortiOSHandler 305from ansible.module_utils.network.fortimanager.common import FAIL_SOCKET_MSG 306 307 308def login(data, fos): 309 host = data['host'] 310 username = data['username'] 311 password = data['password'] 312 ssl_verify = data['ssl_verify'] 313 314 fos.debug('on') 315 if 'https' in data and not data['https']: 316 fos.https('off') 317 else: 318 fos.https('on') 319 320 fos.login(host, username, password, verify=ssl_verify) 321 322 323def filter_firewall_internet_service_custom_data(json): 324 option_list = ['comment', 'disable_entry', 'entry', 325 'master_service_id', 'name'] 326 dictionary = {} 327 328 for attribute in option_list: 329 if attribute in json and json[attribute] is not None: 330 dictionary[attribute] = json[attribute] 331 332 return dictionary 333 334 335def underscore_to_hyphen(data): 336 if isinstance(data, list): 337 for elem in data: 338 elem = underscore_to_hyphen(elem) 339 elif isinstance(data, dict): 340 new_data = {} 341 for k, v in data.items(): 342 new_data[k.replace('_', '-')] = underscore_to_hyphen(v) 343 data = new_data 344 345 return data 346 347 348def firewall_internet_service_custom(data, fos): 349 vdom = data['vdom'] 350 if 'state' in data and data['state']: 351 state = data['state'] 352 elif 'state' in data['firewall_internet_service_custom'] and data['firewall_internet_service_custom']: 353 state = data['firewall_internet_service_custom']['state'] 354 else: 355 state = True 356 firewall_internet_service_custom_data = data['firewall_internet_service_custom'] 357 filtered_data = underscore_to_hyphen(filter_firewall_internet_service_custom_data(firewall_internet_service_custom_data)) 358 359 if state == "present": 360 return fos.set('firewall', 361 'internet-service-custom', 362 data=filtered_data, 363 vdom=vdom) 364 365 elif state == "absent": 366 return fos.delete('firewall', 367 'internet-service-custom', 368 mkey=filtered_data['name'], 369 vdom=vdom) 370 371 372def is_successful_status(status): 373 return status['status'] == "success" or \ 374 status['http_method'] == "DELETE" and status['http_status'] == 404 375 376 377def fortios_firewall(data, fos): 378 379 if data['firewall_internet_service_custom']: 380 resp = firewall_internet_service_custom(data, fos) 381 382 return not is_successful_status(resp), \ 383 resp['status'] == "success", \ 384 resp 385 386 387def main(): 388 fields = { 389 "host": {"required": False, "type": "str"}, 390 "username": {"required": False, "type": "str"}, 391 "password": {"required": False, "type": "str", "default": "", "no_log": True}, 392 "vdom": {"required": False, "type": "str", "default": "root"}, 393 "https": {"required": False, "type": "bool", "default": True}, 394 "ssl_verify": {"required": False, "type": "bool", "default": True}, 395 "state": {"required": False, "type": "str", 396 "choices": ["present", "absent"]}, 397 "firewall_internet_service_custom": { 398 "required": False, "type": "dict", "default": None, 399 "options": { 400 "state": {"required": False, "type": "str", 401 "choices": ["present", "absent"]}, 402 "comment": {"required": False, "type": "str"}, 403 "disable_entry": {"required": False, "type": "list", 404 "options": { 405 "id": {"required": True, "type": "int"}, 406 "ip_range": {"required": False, "type": "list", 407 "options": { 408 "end_ip": {"required": False, "type": "str"}, 409 "id": {"required": True, "type": "int"}, 410 "start_ip": {"required": False, "type": "str"} 411 }}, 412 "port": {"required": False, "type": "int"}, 413 "protocol": {"required": False, "type": "int"} 414 }}, 415 "entry": {"required": False, "type": "list", 416 "options": { 417 "dst": {"required": False, "type": "list", 418 "options": { 419 "name": {"required": True, "type": "str"} 420 }}, 421 "id": {"required": True, "type": "int"}, 422 "port_range": {"required": False, "type": "list", 423 "options": { 424 "end_port": {"required": False, "type": "int"}, 425 "id": {"required": True, "type": "int"}, 426 "start_port": {"required": False, "type": "int"} 427 }}, 428 "protocol": {"required": False, "type": "int"} 429 }}, 430 "master_service_id": {"required": False, "type": "int"}, 431 "name": {"required": True, "type": "str"} 432 433 } 434 } 435 } 436 437 module = AnsibleModule(argument_spec=fields, 438 supports_check_mode=False) 439 440 # legacy_mode refers to using fortiosapi instead of HTTPAPI 441 legacy_mode = 'host' in module.params and module.params['host'] is not None and \ 442 'username' in module.params and module.params['username'] is not None and \ 443 'password' in module.params and module.params['password'] is not None 444 445 if not legacy_mode: 446 if module._socket_path: 447 connection = Connection(module._socket_path) 448 fos = FortiOSHandler(connection) 449 450 is_error, has_changed, result = fortios_firewall(module.params, fos) 451 else: 452 module.fail_json(**FAIL_SOCKET_MSG) 453 else: 454 try: 455 from fortiosapi import FortiOSAPI 456 except ImportError: 457 module.fail_json(msg="fortiosapi module is required") 458 459 fos = FortiOSAPI() 460 461 login(module.params, fos) 462 is_error, has_changed, result = fortios_firewall(module.params, fos) 463 fos.logout() 464 465 if not is_error: 466 module.exit_json(changed=has_changed, meta=result) 467 else: 468 module.fail_json(msg="Error in repo", meta=result) 469 470 471if __name__ == '__main__': 472 main() 473