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_user_peergrp 27short_description: Configure peer groups 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 user feature and peergrp 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 user_peergrp: 76 description: 77 - Configure peer groups. 78 default: null 79 type: dict 80 suboptions: 81 member: 82 description: 83 - Peer group members. 84 type: list 85 suboptions: 86 name: 87 description: 88 - Peer group member name. Source user.peer.name. 89 required: true 90 type: str 91 name: 92 description: 93 - Peer group name. 94 required: true 95 type: str 96''' 97 98EXAMPLES = ''' 99- hosts: fortigates 100 collections: 101 - fortinet.fortios 102 connection: httpapi 103 vars: 104 vdom: "root" 105 ansible_httpapi_use_ssl: yes 106 ansible_httpapi_validate_certs: no 107 ansible_httpapi_port: 443 108 tasks: 109 - name: Configure peer groups. 110 fortios_user_peergrp: 111 vdom: "{{ vdom }}" 112 state: "present" 113 access_token: "<your_own_value>" 114 user_peergrp: 115 member: 116 - 117 name: "default_name_4 (source user.peer.name)" 118 name: "default_name_5" 119 120''' 121 122RETURN = ''' 123build: 124 description: Build number of the fortigate image 125 returned: always 126 type: str 127 sample: '1547' 128http_method: 129 description: Last method used to provision the content into FortiGate 130 returned: always 131 type: str 132 sample: 'PUT' 133http_status: 134 description: Last result given by FortiGate on last operation applied 135 returned: always 136 type: str 137 sample: "200" 138mkey: 139 description: Master key (id) used in the last call to FortiGate 140 returned: success 141 type: str 142 sample: "id" 143name: 144 description: Name of the table used to fulfill the request 145 returned: always 146 type: str 147 sample: "urlfilter" 148path: 149 description: Path of the table used to fulfill the request 150 returned: always 151 type: str 152 sample: "webfilter" 153revision: 154 description: Internal revision number 155 returned: always 156 type: str 157 sample: "17.0.2.10658" 158serial: 159 description: Serial number of the unit 160 returned: always 161 type: str 162 sample: "FGVMEVYYQT3AB5352" 163status: 164 description: Indication of the operation's result 165 returned: always 166 type: str 167 sample: "success" 168vdom: 169 description: Virtual domain used 170 returned: always 171 type: str 172 sample: "root" 173version: 174 description: Version of the FortiGate 175 returned: always 176 type: str 177 sample: "v5.6.3" 178 179''' 180from ansible.module_utils.basic import AnsibleModule 181from ansible.module_utils.connection import Connection 182from ansible_collections.fortinet.fortios.plugins.module_utils.fortios.fortios import FortiOSHandler 183from ansible_collections.fortinet.fortios.plugins.module_utils.fortios.fortios import check_legacy_fortiosapi 184from ansible_collections.fortinet.fortios.plugins.module_utils.fortios.fortios import schema_to_module_spec 185from ansible_collections.fortinet.fortios.plugins.module_utils.fortios.fortios import check_schema_versioning 186from ansible_collections.fortinet.fortios.plugins.module_utils.fortimanager.common import FAIL_SOCKET_MSG 187from ansible_collections.fortinet.fortios.plugins.module_utils.fortios.comparison import is_same_comparison 188from ansible_collections.fortinet.fortios.plugins.module_utils.fortios.comparison import serialize 189 190 191def filter_user_peergrp_data(json): 192 option_list = ['member', 'name'] 193 dictionary = {} 194 195 for attribute in option_list: 196 if attribute in json and json[attribute] is not None: 197 dictionary[attribute] = json[attribute] 198 199 return dictionary 200 201 202def underscore_to_hyphen(data): 203 if isinstance(data, list): 204 for i, elem in enumerate(data): 205 data[i] = underscore_to_hyphen(elem) 206 elif isinstance(data, dict): 207 new_data = {} 208 for k, v in data.items(): 209 new_data[k.replace('_', '-')] = underscore_to_hyphen(v) 210 data = new_data 211 212 return data 213 214 215def user_peergrp(data, fos, check_mode=False): 216 217 vdom = data['vdom'] 218 219 state = data['state'] 220 221 user_peergrp_data = data['user_peergrp'] 222 filtered_data = underscore_to_hyphen(filter_user_peergrp_data(user_peergrp_data)) 223 224 # check_mode starts from here 225 if check_mode: 226 mkey = fos.get_mkey('system', 'interface', filtered_data, vdom=vdom) 227 current_data = fos.get('system', 'interface', vdom=vdom, mkey=mkey) 228 is_existed = current_data and current_data.get('http_status') == 200 \ 229 and isinstance(current_data.get('results'), list) \ 230 and len(current_data['results']) > 0 231 232 # 2. if it exists and the state is 'present' then compare current settings with desired 233 if state == 'present' or state is True: 234 if mkey is None: 235 return False, True, filtered_data 236 237 # if mkey exists then compare each other 238 # record exits and they're matched or not 239 if is_existed: 240 is_same = is_same_comparison( 241 serialize(current_data['results'][0]), serialize(filtered_data)) 242 return False, not is_same, filtered_data 243 244 # record does not exist 245 return False, True, filtered_data 246 247 if state == 'absent': 248 if mkey is None: 249 return False, False, filtered_data 250 251 if is_existed: 252 return False, True, filtered_data 253 return False, False, filtered_data 254 255 return True, False, {'reason: ': 'Must provide state parameter'} 256 257 if state == "present" or state is True: 258 return fos.set('user', 259 'peergrp', 260 data=filtered_data, 261 vdom=vdom) 262 263 elif state == "absent": 264 return fos.delete('user', 265 'peergrp', 266 mkey=filtered_data['name'], 267 vdom=vdom) 268 else: 269 fos._module.fail_json(msg='state must be present or absent!') 270 271 272def is_successful_status(status): 273 return status['status'] == "success" or \ 274 status['http_method'] == "DELETE" and status['http_status'] == 404 275 276 277def fortios_user(data, fos, check_mode): 278 279 if data['user_peergrp']: 280 resp = user_peergrp(data, fos, check_mode) 281 else: 282 fos._module.fail_json(msg='missing task body: %s' % ('user_peergrp')) 283 if check_mode: 284 return resp 285 return not is_successful_status(resp), \ 286 resp['status'] == "success" and \ 287 (resp['revision_changed'] if 'revision_changed' in resp else True), \ 288 resp 289 290 291versioned_schema = { 292 "type": "list", 293 "children": { 294 "member": { 295 "type": "list", 296 "children": { 297 "name": { 298 "type": "string", 299 "revisions": { 300 "v6.0.0": True, 301 "v7.0.0": True, 302 "v6.0.5": True, 303 "v6.4.4": True, 304 "v6.4.0": True, 305 "v6.4.1": True, 306 "v6.2.0": True, 307 "v6.2.3": True, 308 "v6.2.5": True, 309 "v6.2.7": True, 310 "v6.0.11": True 311 } 312 } 313 }, 314 "revisions": { 315 "v6.0.0": True, 316 "v7.0.0": True, 317 "v6.0.5": True, 318 "v6.4.4": True, 319 "v6.4.0": True, 320 "v6.4.1": True, 321 "v6.2.0": True, 322 "v6.2.3": True, 323 "v6.2.5": True, 324 "v6.2.7": True, 325 "v6.0.11": True 326 } 327 }, 328 "name": { 329 "type": "string", 330 "revisions": { 331 "v6.0.0": True, 332 "v7.0.0": True, 333 "v6.0.5": True, 334 "v6.4.4": True, 335 "v6.4.0": True, 336 "v6.4.1": True, 337 "v6.2.0": True, 338 "v6.2.3": True, 339 "v6.2.5": True, 340 "v6.2.7": True, 341 "v6.0.11": True 342 } 343 } 344 }, 345 "revisions": { 346 "v6.0.0": True, 347 "v7.0.0": True, 348 "v6.0.5": True, 349 "v6.4.4": True, 350 "v6.4.0": True, 351 "v6.4.1": True, 352 "v6.2.0": True, 353 "v6.2.3": True, 354 "v6.2.5": True, 355 "v6.2.7": True, 356 "v6.0.11": True 357 } 358} 359 360 361def main(): 362 module_spec = schema_to_module_spec(versioned_schema) 363 mkeyname = 'name' 364 fields = { 365 "access_token": {"required": False, "type": "str", "no_log": True}, 366 "enable_log": {"required": False, "type": bool}, 367 "vdom": {"required": False, "type": "str", "default": "root"}, 368 "state": {"required": True, "type": "str", 369 "choices": ["present", "absent"]}, 370 "user_peergrp": { 371 "required": False, "type": "dict", "default": None, 372 "options": { 373 } 374 } 375 } 376 for attribute_name in module_spec['options']: 377 fields["user_peergrp"]['options'][attribute_name] = module_spec['options'][attribute_name] 378 if mkeyname and mkeyname == attribute_name: 379 fields["user_peergrp"]['options'][attribute_name]['required'] = True 380 381 check_legacy_fortiosapi() 382 module = AnsibleModule(argument_spec=fields, 383 supports_check_mode=True) 384 385 versions_check_result = None 386 if module._socket_path: 387 connection = Connection(module._socket_path) 388 if 'access_token' in module.params: 389 connection.set_option('access_token', module.params['access_token']) 390 391 if 'enable_log' in module.params: 392 connection.set_option('enable_log', module.params['enable_log']) 393 else: 394 connection.set_option('enable_log', False) 395 fos = FortiOSHandler(connection, module, mkeyname) 396 versions_check_result = check_schema_versioning(fos, versioned_schema, "user_peergrp") 397 398 is_error, has_changed, result = fortios_user(module.params, fos, module.check_mode) 399 400 else: 401 module.fail_json(**FAIL_SOCKET_MSG) 402 403 if versions_check_result and versions_check_result['matched'] is False: 404 module.warn("Ansible has detected version mismatch between FortOS system and your playbook, see more details by specifying option -vvv") 405 406 if not is_error: 407 if versions_check_result and versions_check_result['matched'] is False: 408 module.exit_json(changed=has_changed, version_check_warning=versions_check_result, meta=result) 409 else: 410 module.exit_json(changed=has_changed, meta=result) 411 else: 412 if versions_check_result and versions_check_result['matched'] is False: 413 module.fail_json(msg="Error in repo", version_check_warning=versions_check_result, meta=result) 414 else: 415 module.fail_json(msg="Error in repo", meta=result) 416 417 418if __name__ == '__main__': 419 main() 420