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