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_user_device_group 27short_description: Configure device 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 device_group 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.9" 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 state: 76 description: 77 - Indicates whether to create or remove the object. 78 type: str 79 required: true 80 choices: 81 - present 82 - absent 83 user_device_group: 84 description: 85 - Configure device groups. 86 default: null 87 type: dict 88 suboptions: 89 comment: 90 description: 91 - Comment. 92 type: str 93 member: 94 description: 95 - Device group member. 96 type: list 97 suboptions: 98 name: 99 description: 100 - Device name. Source user.device.alias user.device-category.name. 101 required: true 102 type: str 103 name: 104 description: 105 - Device group name. 106 required: true 107 type: str 108 tagging: 109 description: 110 - Config object tagging. 111 type: list 112 suboptions: 113 category: 114 description: 115 - Tag category. Source system.object-tagging.category. 116 type: str 117 name: 118 description: 119 - Tagging entry name. 120 required: true 121 type: str 122 tags: 123 description: 124 - Tags. 125 type: list 126 suboptions: 127 name: 128 description: 129 - Tag name. Source system.object-tagging.tags.name. 130 required: true 131 type: str 132''' 133 134EXAMPLES = ''' 135- hosts: localhost 136 vars: 137 host: "192.168.122.40" 138 username: "admin" 139 password: "" 140 vdom: "root" 141 ssl_verify: "False" 142 tasks: 143 - name: Configure device groups. 144 fortios_user_device_group: 145 host: "{{ host }}" 146 username: "{{ username }}" 147 password: "{{ password }}" 148 vdom: "{{ vdom }}" 149 https: "False" 150 state: "present" 151 user_device_group: 152 comment: "Comment." 153 member: 154 - 155 name: "default_name_5 (source user.device.alias user.device-category.name)" 156 name: "default_name_6" 157 tagging: 158 - 159 category: "<your_own_value> (source system.object-tagging.category)" 160 name: "default_name_9" 161 tags: 162 - 163 name: "default_name_11 (source system.object-tagging.tags.name)" 164''' 165 166RETURN = ''' 167build: 168 description: Build number of the fortigate image 169 returned: always 170 type: str 171 sample: '1547' 172http_method: 173 description: Last method used to provision the content into FortiGate 174 returned: always 175 type: str 176 sample: 'PUT' 177http_status: 178 description: Last result given by FortiGate on last operation applied 179 returned: always 180 type: str 181 sample: "200" 182mkey: 183 description: Master key (id) used in the last call to FortiGate 184 returned: success 185 type: str 186 sample: "id" 187name: 188 description: Name of the table used to fulfill the request 189 returned: always 190 type: str 191 sample: "urlfilter" 192path: 193 description: Path of the table used to fulfill the request 194 returned: always 195 type: str 196 sample: "webfilter" 197revision: 198 description: Internal revision number 199 returned: always 200 type: str 201 sample: "17.0.2.10658" 202serial: 203 description: Serial number of the unit 204 returned: always 205 type: str 206 sample: "FGVMEVYYQT3AB5352" 207status: 208 description: Indication of the operation's result 209 returned: always 210 type: str 211 sample: "success" 212vdom: 213 description: Virtual domain used 214 returned: always 215 type: str 216 sample: "root" 217version: 218 description: Version of the FortiGate 219 returned: always 220 type: str 221 sample: "v5.6.3" 222 223''' 224 225from ansible.module_utils.basic import AnsibleModule 226from ansible.module_utils.connection import Connection 227from ansible.module_utils.network.fortios.fortios import FortiOSHandler 228from ansible.module_utils.network.fortimanager.common import FAIL_SOCKET_MSG 229 230 231def login(data, fos): 232 host = data['host'] 233 username = data['username'] 234 password = data['password'] 235 ssl_verify = data['ssl_verify'] 236 237 fos.debug('on') 238 if 'https' in data and not data['https']: 239 fos.https('off') 240 else: 241 fos.https('on') 242 243 fos.login(host, username, password, verify=ssl_verify) 244 245 246def filter_user_device_group_data(json): 247 option_list = ['comment', 'member', 'name', 248 'tagging'] 249 dictionary = {} 250 251 for attribute in option_list: 252 if attribute in json and json[attribute] is not None: 253 dictionary[attribute] = json[attribute] 254 255 return dictionary 256 257 258def underscore_to_hyphen(data): 259 if isinstance(data, list): 260 for elem in data: 261 elem = underscore_to_hyphen(elem) 262 elif isinstance(data, dict): 263 new_data = {} 264 for k, v in data.items(): 265 new_data[k.replace('_', '-')] = underscore_to_hyphen(v) 266 data = new_data 267 268 return data 269 270 271def user_device_group(data, fos): 272 vdom = data['vdom'] 273 state = data['state'] 274 user_device_group_data = data['user_device_group'] 275 filtered_data = underscore_to_hyphen(filter_user_device_group_data(user_device_group_data)) 276 277 if state == "present": 278 return fos.set('user', 279 'device-group', 280 data=filtered_data, 281 vdom=vdom) 282 283 elif state == "absent": 284 return fos.delete('user', 285 'device-group', 286 mkey=filtered_data['name'], 287 vdom=vdom) 288 289 290def is_successful_status(status): 291 return status['status'] == "success" or \ 292 status['http_method'] == "DELETE" and status['http_status'] == 404 293 294 295def fortios_user(data, fos): 296 297 if data['user_device_group']: 298 resp = user_device_group(data, fos) 299 300 return not is_successful_status(resp), \ 301 resp['status'] == "success", \ 302 resp 303 304 305def main(): 306 fields = { 307 "host": {"required": False, "type": "str"}, 308 "username": {"required": False, "type": "str"}, 309 "password": {"required": False, "type": "str", "default": "", "no_log": True}, 310 "vdom": {"required": False, "type": "str", "default": "root"}, 311 "https": {"required": False, "type": "bool", "default": True}, 312 "ssl_verify": {"required": False, "type": "bool", "default": True}, 313 "state": {"required": True, "type": "str", 314 "choices": ["present", "absent"]}, 315 "user_device_group": { 316 "required": False, "type": "dict", "default": None, 317 "options": { 318 "comment": {"required": False, "type": "str"}, 319 "member": {"required": False, "type": "list", 320 "options": { 321 "name": {"required": True, "type": "str"} 322 }}, 323 "name": {"required": True, "type": "str"}, 324 "tagging": {"required": False, "type": "list", 325 "options": { 326 "category": {"required": False, "type": "str"}, 327 "name": {"required": True, "type": "str"}, 328 "tags": {"required": False, "type": "list", 329 "options": { 330 "name": {"required": True, "type": "str"} 331 }} 332 }} 333 334 } 335 } 336 } 337 338 module = AnsibleModule(argument_spec=fields, 339 supports_check_mode=False) 340 341 # legacy_mode refers to using fortiosapi instead of HTTPAPI 342 legacy_mode = 'host' in module.params and module.params['host'] is not None and \ 343 'username' in module.params and module.params['username'] is not None and \ 344 'password' in module.params and module.params['password'] is not None 345 346 if not legacy_mode: 347 if module._socket_path: 348 connection = Connection(module._socket_path) 349 fos = FortiOSHandler(connection) 350 351 is_error, has_changed, result = fortios_user(module.params, fos) 352 else: 353 module.fail_json(**FAIL_SOCKET_MSG) 354 else: 355 try: 356 from fortiosapi import FortiOSAPI 357 except ImportError: 358 module.fail_json(msg="fortiosapi module is required") 359 360 fos = FortiOSAPI() 361 362 login(module.params, fos) 363 is_error, has_changed, result = fortios_user(module.params, fos) 364 fos.logout() 365 366 if not is_error: 367 module.exit_json(changed=has_changed, meta=result) 368 else: 369 module.fail_json(msg="Error in repo", meta=result) 370 371 372if __name__ == '__main__': 373 main() 374