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