1#!/usr/local/bin/python3.8 2from __future__ import (absolute_import, division, print_function) 3# Copyright 2019-2020 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_antivirus_notification 27short_description: Configure AntiVirus notification lists 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 antivirus feature and notification category. 31 Examples include all parameters and values need to be adjusted to datasources before usage. 32 Tested with FOS v6.0.0 33version_added: "2.10" 34author: 35 - Link Zheng (@chillancezen) 36 - Jie Xue (@JieX19) 37 - Hongbin Lu (@fgtdev-hblu) 38 - Frank Shen (@frankshen01) 39 - Miguel Angel Munoz (@mamunozgonzalez) 40 - Nicolas Thomas (@thomnico) 41notes: 42 - Legacy fortiosapi has been deprecated, httpapi is the preferred way to run playbooks 43 44requirements: 45 - ansible>=2.9.0 46options: 47 access_token: 48 description: 49 - Token-based authentication. 50 Generated from GUI of Fortigate. 51 type: str 52 required: false 53 enable_log: 54 description: 55 - Enable/Disable logging for task. 56 type: bool 57 required: false 58 default: false 59 vdom: 60 description: 61 - Virtual domain, among those defined previously. A vdom is a 62 virtual instance of the FortiGate that can be configured and 63 used as a different unit. 64 type: str 65 default: root 66 67 state: 68 description: 69 - Indicates whether to create or remove the object. 70 type: str 71 required: true 72 choices: 73 - present 74 - absent 75 antivirus_notification: 76 description: 77 - Configure AntiVirus notification lists. 78 default: null 79 type: dict 80 suboptions: 81 comment: 82 description: 83 - Optional comments. 84 type: str 85 entries: 86 description: 87 - modify this antivirus notification list 88 type: list 89 suboptions: 90 name: 91 description: 92 - virus name or prefix 93 required: true 94 type: str 95 prefix: 96 description: 97 - enable prefix matches based on this name 98 type: str 99 choices: 100 - enable 101 - disable 102 status: 103 description: 104 - enable this entry for notifications 105 type: str 106 choices: 107 - enable 108 - disable 109 id: 110 description: 111 - ID. 112 required: true 113 type: int 114 name: 115 description: 116 - Name of table. 117 type: str 118''' 119 120EXAMPLES = ''' 121- hosts: fortigates 122 collections: 123 - fortinet.fortios 124 connection: httpapi 125 vars: 126 vdom: "root" 127 ansible_httpapi_use_ssl: yes 128 ansible_httpapi_validate_certs: no 129 ansible_httpapi_port: 443 130 tasks: 131 - name: Configure AntiVirus notification lists. 132 fortios_antivirus_notification: 133 vdom: "{{ vdom }}" 134 state: "present" 135 access_token: "<your_own_value>" 136 antivirus_notification: 137 comment: "Optional comments." 138 entries: 139 - 140 name: "default_name_5" 141 prefix: "enable" 142 status: "enable" 143 id: "8" 144 name: "default_name_9" 145 146''' 147 148RETURN = ''' 149build: 150 description: Build number of the fortigate image 151 returned: always 152 type: str 153 sample: '1547' 154http_method: 155 description: Last method used to provision the content into FortiGate 156 returned: always 157 type: str 158 sample: 'PUT' 159http_status: 160 description: Last result given by FortiGate on last operation applied 161 returned: always 162 type: str 163 sample: "200" 164mkey: 165 description: Master key (id) used in the last call to FortiGate 166 returned: success 167 type: str 168 sample: "id" 169name: 170 description: Name of the table used to fulfill the request 171 returned: always 172 type: str 173 sample: "urlfilter" 174path: 175 description: Path of the table used to fulfill the request 176 returned: always 177 type: str 178 sample: "webfilter" 179revision: 180 description: Internal revision number 181 returned: always 182 type: str 183 sample: "17.0.2.10658" 184serial: 185 description: Serial number of the unit 186 returned: always 187 type: str 188 sample: "FGVMEVYYQT3AB5352" 189status: 190 description: Indication of the operation's result 191 returned: always 192 type: str 193 sample: "success" 194vdom: 195 description: Virtual domain used 196 returned: always 197 type: str 198 sample: "root" 199version: 200 description: Version of the FortiGate 201 returned: always 202 type: str 203 sample: "v5.6.3" 204 205''' 206from ansible.module_utils.basic import AnsibleModule 207from ansible.module_utils.connection import Connection 208from ansible_collections.fortinet.fortios.plugins.module_utils.fortios.fortios import FortiOSHandler 209from ansible_collections.fortinet.fortios.plugins.module_utils.fortios.fortios import check_legacy_fortiosapi 210from ansible_collections.fortinet.fortios.plugins.module_utils.fortios.fortios import schema_to_module_spec 211from ansible_collections.fortinet.fortios.plugins.module_utils.fortios.fortios import check_schema_versioning 212from ansible_collections.fortinet.fortios.plugins.module_utils.fortimanager.common import FAIL_SOCKET_MSG 213from ansible_collections.fortinet.fortios.plugins.module_utils.fortios.comparison import is_same_comparison 214from ansible_collections.fortinet.fortios.plugins.module_utils.fortios.comparison import serialize 215 216 217def filter_antivirus_notification_data(json): 218 option_list = ['comment', 'entries', 'id', 219 'name'] 220 dictionary = {} 221 222 for attribute in option_list: 223 if attribute in json and json[attribute] is not None: 224 dictionary[attribute] = json[attribute] 225 226 return dictionary 227 228 229def underscore_to_hyphen(data): 230 if isinstance(data, list): 231 for i, elem in enumerate(data): 232 data[i] = underscore_to_hyphen(elem) 233 elif isinstance(data, dict): 234 new_data = {} 235 for k, v in data.items(): 236 new_data[k.replace('_', '-')] = underscore_to_hyphen(v) 237 data = new_data 238 239 return data 240 241 242def antivirus_notification(data, fos, check_mode=False): 243 244 vdom = data['vdom'] 245 246 state = data['state'] 247 248 antivirus_notification_data = data['antivirus_notification'] 249 filtered_data = underscore_to_hyphen(filter_antivirus_notification_data(antivirus_notification_data)) 250 251 # check_mode starts from here 252 if check_mode: 253 mkey = fos.get_mkey('system', 'interface', filtered_data, vdom=vdom) 254 current_data = fos.get('system', 'interface', vdom=vdom, mkey=mkey) 255 is_existed = current_data and current_data.get('http_status') == 200 \ 256 and isinstance(current_data.get('results'), list) \ 257 and len(current_data['results']) > 0 258 259 # 2. if it exists and the state is 'present' then compare current settings with desired 260 if state == 'present' or state is True: 261 if mkey is None: 262 return False, True, filtered_data 263 264 # if mkey exists then compare each other 265 # record exits and they're matched or not 266 if is_existed: 267 is_same = is_same_comparison( 268 serialize(current_data['results'][0]), serialize(filtered_data)) 269 return False, not is_same, filtered_data 270 271 # record does not exist 272 return False, True, filtered_data 273 274 if state == 'absent': 275 if mkey is None: 276 return False, False, filtered_data 277 278 if is_existed: 279 return False, True, filtered_data 280 return False, False, filtered_data 281 282 return True, False, {'reason: ': 'Must provide state parameter'} 283 284 if state == "present" or state is True: 285 return fos.set('antivirus', 286 'notification', 287 data=filtered_data, 288 vdom=vdom) 289 290 elif state == "absent": 291 return fos.delete('antivirus', 292 'notification', 293 mkey=filtered_data['id'], 294 vdom=vdom) 295 else: 296 fos._module.fail_json(msg='state must be present or absent!') 297 298 299def is_successful_status(status): 300 return status['status'] == "success" or \ 301 status['http_method'] == "DELETE" and status['http_status'] == 404 302 303 304def fortios_antivirus(data, fos, check_mode): 305 306 if data['antivirus_notification']: 307 resp = antivirus_notification(data, fos, check_mode) 308 else: 309 fos._module.fail_json(msg='missing task body: %s' % ('antivirus_notification')) 310 if check_mode: 311 return resp 312 return not is_successful_status(resp), \ 313 resp['status'] == "success" and \ 314 (resp['revision_changed'] if 'revision_changed' in resp else True), \ 315 resp 316 317 318versioned_schema = { 319 "type": "list", 320 "children": { 321 "comment": { 322 "type": "string", 323 "revisions": { 324 "v6.0.0": True, 325 "v6.0.5": True, 326 "v6.2.0": True, 327 "v6.2.3": True, 328 "v6.2.5": True, 329 "v6.2.7": True, 330 "v6.0.11": True 331 } 332 }, 333 "entries": { 334 "type": "list", 335 "children": { 336 "status": { 337 "type": "string", 338 "options": [ 339 { 340 "value": "enable", 341 "revisions": { 342 "v6.0.0": True, 343 "v6.0.5": True, 344 "v6.2.0": True, 345 "v6.2.3": True, 346 "v6.2.5": True, 347 "v6.2.7": True, 348 "v6.0.11": True 349 } 350 }, 351 { 352 "value": "disable", 353 "revisions": { 354 "v6.0.0": True, 355 "v6.0.5": True, 356 "v6.2.0": True, 357 "v6.2.3": True, 358 "v6.2.5": True, 359 "v6.2.7": True, 360 "v6.0.11": True 361 } 362 } 363 ], 364 "revisions": { 365 "v6.0.0": True, 366 "v6.0.5": True, 367 "v6.2.0": True, 368 "v6.2.3": True, 369 "v6.2.5": True, 370 "v6.2.7": True, 371 "v6.0.11": True 372 } 373 }, 374 "prefix": { 375 "type": "string", 376 "options": [ 377 { 378 "value": "enable", 379 "revisions": { 380 "v6.0.0": True, 381 "v6.0.5": True, 382 "v6.2.0": True, 383 "v6.2.3": True, 384 "v6.2.5": True, 385 "v6.2.7": True, 386 "v6.0.11": True 387 } 388 }, 389 { 390 "value": "disable", 391 "revisions": { 392 "v6.0.0": True, 393 "v6.0.5": True, 394 "v6.2.0": True, 395 "v6.2.3": True, 396 "v6.2.5": True, 397 "v6.2.7": True, 398 "v6.0.11": True 399 } 400 } 401 ], 402 "revisions": { 403 "v6.0.0": True, 404 "v6.0.5": True, 405 "v6.2.0": True, 406 "v6.2.3": True, 407 "v6.2.5": True, 408 "v6.2.7": True, 409 "v6.0.11": True 410 } 411 }, 412 "name": { 413 "type": "string", 414 "revisions": { 415 "v6.0.0": True, 416 "v6.0.5": True, 417 "v6.2.0": True, 418 "v6.2.3": True, 419 "v6.2.5": True, 420 "v6.2.7": True, 421 "v6.0.11": True 422 } 423 } 424 }, 425 "revisions": { 426 "v6.0.0": True, 427 "v6.0.5": True, 428 "v6.2.0": True, 429 "v6.2.3": True, 430 "v6.2.5": True, 431 "v6.2.7": True, 432 "v6.0.11": True 433 } 434 }, 435 "id": { 436 "type": "integer", 437 "revisions": { 438 "v6.0.0": True, 439 "v6.0.5": True, 440 "v6.2.0": True, 441 "v6.2.3": True, 442 "v6.2.5": True, 443 "v6.2.7": True, 444 "v6.0.11": True 445 } 446 }, 447 "name": { 448 "type": "string", 449 "revisions": { 450 "v6.0.0": True, 451 "v6.0.5": True, 452 "v6.2.0": True, 453 "v6.2.3": True, 454 "v6.2.5": True, 455 "v6.2.7": True, 456 "v6.0.11": True 457 } 458 } 459 }, 460 "revisions": { 461 "v6.0.0": True, 462 "v6.0.5": True, 463 "v6.2.0": True, 464 "v6.2.3": True, 465 "v6.2.5": True, 466 "v6.2.7": True, 467 "v6.0.11": True 468 } 469} 470 471 472def main(): 473 module_spec = schema_to_module_spec(versioned_schema) 474 mkeyname = 'id' 475 fields = { 476 "access_token": {"required": False, "type": "str", "no_log": True}, 477 "enable_log": {"required": False, "type": bool}, 478 "vdom": {"required": False, "type": "str", "default": "root"}, 479 "state": {"required": True, "type": "str", 480 "choices": ["present", "absent"]}, 481 "antivirus_notification": { 482 "required": False, "type": "dict", "default": None, 483 "options": { 484 } 485 } 486 } 487 for attribute_name in module_spec['options']: 488 fields["antivirus_notification"]['options'][attribute_name] = module_spec['options'][attribute_name] 489 if mkeyname and mkeyname == attribute_name: 490 fields["antivirus_notification"]['options'][attribute_name]['required'] = True 491 492 check_legacy_fortiosapi() 493 module = AnsibleModule(argument_spec=fields, 494 supports_check_mode=True) 495 496 versions_check_result = None 497 if module._socket_path: 498 connection = Connection(module._socket_path) 499 if 'access_token' in module.params: 500 connection.set_option('access_token', module.params['access_token']) 501 502 if 'enable_log' in module.params: 503 connection.set_option('enable_log', module.params['enable_log']) 504 else: 505 connection.set_option('enable_log', False) 506 fos = FortiOSHandler(connection, module, mkeyname) 507 versions_check_result = check_schema_versioning(fos, versioned_schema, "antivirus_notification") 508 509 is_error, has_changed, result = fortios_antivirus(module.params, fos, module.check_mode) 510 511 else: 512 module.fail_json(**FAIL_SOCKET_MSG) 513 514 if versions_check_result and versions_check_result['matched'] is False: 515 module.warn("Ansible has detected version mismatch between FortOS system and your playbook, see more details by specifying option -vvv") 516 517 if not is_error: 518 if versions_check_result and versions_check_result['matched'] is False: 519 module.exit_json(changed=has_changed, version_check_warning=versions_check_result, meta=result) 520 else: 521 module.exit_json(changed=has_changed, meta=result) 522 else: 523 if versions_check_result and versions_check_result['matched'] is False: 524 module.fail_json(msg="Error in repo", version_check_warning=versions_check_result, meta=result) 525 else: 526 module.fail_json(msg="Error in repo", meta=result) 527 528 529if __name__ == '__main__': 530 main() 531