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_router_prefix_list6
27short_description: Configure IPv6 prefix 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 router feature and prefix_list6 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    router_prefix_list6:
76        description:
77            - Configure IPv6 prefix lists.
78        default: null
79        type: dict
80        suboptions:
81            comments:
82                description:
83                    - Comment.
84                type: str
85            name:
86                description:
87                    - Name.
88                required: true
89                type: str
90            rule:
91                description:
92                    - IPv6 prefix list rule.
93                type: list
94                suboptions:
95                    action:
96                        description:
97                            - Permit or deny packets that match this rule.
98                        type: str
99                        choices:
100                            - permit
101                            - deny
102                    flags:
103                        description:
104                            - Flags.
105                        type: int
106                    ge:
107                        description:
108                            - Minimum prefix length to be matched (0 - 128).
109                        type: int
110                    id:
111                        description:
112                            - Rule ID.
113                        required: true
114                        type: int
115                    le:
116                        description:
117                            - Maximum prefix length to be matched (0 - 128).
118                        type: int
119                    prefix6:
120                        description:
121                            - IPv6 prefix to define regular filter criteria, such as "any" or subnets.
122                        type: str
123'''
124
125EXAMPLES = '''
126- hosts: fortigates
127  collections:
128    - fortinet.fortios
129  connection: httpapi
130  vars:
131   vdom: "root"
132   ansible_httpapi_use_ssl: yes
133   ansible_httpapi_validate_certs: no
134   ansible_httpapi_port: 443
135  tasks:
136  - name: Configure IPv6 prefix lists.
137    fortios_router_prefix_list6:
138      vdom:  "{{ vdom }}"
139      state: "present"
140      access_token: "<your_own_value>"
141      router_prefix_list6:
142        comments: "<your_own_value>"
143        name: "default_name_4"
144        rule:
145         -
146            action: "permit"
147            flags: "7"
148            ge: "8"
149            id:  "9"
150            le: "10"
151            prefix6: "<your_own_value>"
152
153'''
154
155RETURN = '''
156build:
157  description: Build number of the fortigate image
158  returned: always
159  type: str
160  sample: '1547'
161http_method:
162  description: Last method used to provision the content into FortiGate
163  returned: always
164  type: str
165  sample: 'PUT'
166http_status:
167  description: Last result given by FortiGate on last operation applied
168  returned: always
169  type: str
170  sample: "200"
171mkey:
172  description: Master key (id) used in the last call to FortiGate
173  returned: success
174  type: str
175  sample: "id"
176name:
177  description: Name of the table used to fulfill the request
178  returned: always
179  type: str
180  sample: "urlfilter"
181path:
182  description: Path of the table used to fulfill the request
183  returned: always
184  type: str
185  sample: "webfilter"
186revision:
187  description: Internal revision number
188  returned: always
189  type: str
190  sample: "17.0.2.10658"
191serial:
192  description: Serial number of the unit
193  returned: always
194  type: str
195  sample: "FGVMEVYYQT3AB5352"
196status:
197  description: Indication of the operation's result
198  returned: always
199  type: str
200  sample: "success"
201vdom:
202  description: Virtual domain used
203  returned: always
204  type: str
205  sample: "root"
206version:
207  description: Version of the FortiGate
208  returned: always
209  type: str
210  sample: "v5.6.3"
211
212'''
213from ansible.module_utils.basic import AnsibleModule
214from ansible.module_utils.connection import Connection
215from ansible_collections.fortinet.fortios.plugins.module_utils.fortios.fortios import FortiOSHandler
216from ansible_collections.fortinet.fortios.plugins.module_utils.fortios.fortios import check_legacy_fortiosapi
217from ansible_collections.fortinet.fortios.plugins.module_utils.fortios.fortios import schema_to_module_spec
218from ansible_collections.fortinet.fortios.plugins.module_utils.fortios.fortios import check_schema_versioning
219from ansible_collections.fortinet.fortios.plugins.module_utils.fortimanager.common import FAIL_SOCKET_MSG
220from ansible_collections.fortinet.fortios.plugins.module_utils.fortios.comparison import is_same_comparison
221from ansible_collections.fortinet.fortios.plugins.module_utils.fortios.comparison import serialize
222
223
224def filter_router_prefix_list6_data(json):
225    option_list = ['comments', 'name', 'rule']
226    dictionary = {}
227
228    for attribute in option_list:
229        if attribute in json and json[attribute] is not None:
230            dictionary[attribute] = json[attribute]
231
232    return dictionary
233
234
235def underscore_to_hyphen(data):
236    if isinstance(data, list):
237        for i, elem in enumerate(data):
238            data[i] = underscore_to_hyphen(elem)
239    elif isinstance(data, dict):
240        new_data = {}
241        for k, v in data.items():
242            new_data[k.replace('_', '-')] = underscore_to_hyphen(v)
243        data = new_data
244
245    return data
246
247
248def router_prefix_list6(data, fos, check_mode=False):
249
250    vdom = data['vdom']
251
252    state = data['state']
253
254    router_prefix_list6_data = data['router_prefix_list6']
255    filtered_data = underscore_to_hyphen(filter_router_prefix_list6_data(router_prefix_list6_data))
256
257    # check_mode starts from here
258    if check_mode:
259        mkey = fos.get_mkey('system', 'interface', filtered_data, vdom=vdom)
260        current_data = fos.get('system', 'interface', vdom=vdom, mkey=mkey)
261        is_existed = current_data and current_data.get('http_status') == 200 \
262            and isinstance(current_data.get('results'), list) \
263            and len(current_data['results']) > 0
264
265        # 2. if it exists and the state is 'present' then compare current settings with desired
266        if state == 'present' or state is True:
267            if mkey is None:
268                return False, True, filtered_data
269
270            # if mkey exists then compare each other
271            # record exits and they're matched or not
272            if is_existed:
273                is_same = is_same_comparison(
274                    serialize(current_data['results'][0]), serialize(filtered_data))
275                return False, not is_same, filtered_data
276
277            # record does not exist
278            return False, True, filtered_data
279
280        if state == 'absent':
281            if mkey is None:
282                return False, False, filtered_data
283
284            if is_existed:
285                return False, True, filtered_data
286            return False, False, filtered_data
287
288        return True, False, {'reason: ': 'Must provide state parameter'}
289
290    if state == "present" or state is True:
291        return fos.set('router',
292                       'prefix-list6',
293                       data=filtered_data,
294                       vdom=vdom)
295
296    elif state == "absent":
297        return fos.delete('router',
298                          'prefix-list6',
299                          mkey=filtered_data['name'],
300                          vdom=vdom)
301    else:
302        fos._module.fail_json(msg='state must be present or absent!')
303
304
305def is_successful_status(status):
306    return status['status'] == "success" or \
307        status['http_method'] == "DELETE" and status['http_status'] == 404
308
309
310def fortios_router(data, fos, check_mode):
311
312    if data['router_prefix_list6']:
313        resp = router_prefix_list6(data, fos, check_mode)
314    else:
315        fos._module.fail_json(msg='missing task body: %s' % ('router_prefix_list6'))
316    if check_mode:
317        return resp
318    return not is_successful_status(resp), \
319        resp['status'] == "success" and \
320        (resp['revision_changed'] if 'revision_changed' in resp else True), \
321        resp
322
323
324versioned_schema = {
325    "type": "list",
326    "children": {
327        "name": {
328            "type": "string",
329            "revisions": {
330                "v6.0.0": True,
331                "v7.0.0": True,
332                "v6.0.5": True,
333                "v6.4.4": True,
334                "v6.4.0": True,
335                "v6.4.1": True,
336                "v6.2.0": True,
337                "v6.2.3": True,
338                "v6.2.5": True,
339                "v6.2.7": True,
340                "v6.0.11": True
341            }
342        },
343        "rule": {
344            "type": "list",
345            "children": {
346                "le": {
347                    "type": "integer",
348                    "revisions": {
349                        "v6.0.0": True,
350                        "v7.0.0": True,
351                        "v6.0.5": True,
352                        "v6.4.4": True,
353                        "v6.4.0": True,
354                        "v6.4.1": True,
355                        "v6.2.0": True,
356                        "v6.2.3": True,
357                        "v6.2.5": True,
358                        "v6.2.7": True,
359                        "v6.0.11": True
360                    }
361                },
362                "prefix6": {
363                    "type": "string",
364                    "revisions": {
365                        "v6.0.0": True,
366                        "v7.0.0": True,
367                        "v6.0.5": True,
368                        "v6.4.4": True,
369                        "v6.4.0": True,
370                        "v6.4.1": True,
371                        "v6.2.0": True,
372                        "v6.2.3": True,
373                        "v6.2.5": True,
374                        "v6.2.7": True,
375                        "v6.0.11": True
376                    }
377                },
378                "ge": {
379                    "type": "integer",
380                    "revisions": {
381                        "v6.0.0": True,
382                        "v7.0.0": True,
383                        "v6.0.5": True,
384                        "v6.4.4": True,
385                        "v6.4.0": True,
386                        "v6.4.1": True,
387                        "v6.2.0": True,
388                        "v6.2.3": True,
389                        "v6.2.5": True,
390                        "v6.2.7": True,
391                        "v6.0.11": True
392                    }
393                },
394                "flags": {
395                    "type": "integer",
396                    "revisions": {
397                        "v6.0.0": True,
398                        "v7.0.0": True,
399                        "v6.0.5": True,
400                        "v6.4.4": True,
401                        "v6.4.0": True,
402                        "v6.4.1": True,
403                        "v6.2.0": True,
404                        "v6.2.3": True,
405                        "v6.2.5": True,
406                        "v6.2.7": True,
407                        "v6.0.11": True
408                    }
409                },
410                "action": {
411                    "type": "string",
412                    "options": [
413                        {
414                            "value": "permit",
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                            "value": "deny",
431                            "revisions": {
432                                "v6.0.0": True,
433                                "v7.0.0": True,
434                                "v6.0.5": True,
435                                "v6.4.4": True,
436                                "v6.4.0": True,
437                                "v6.4.1": True,
438                                "v6.2.0": True,
439                                "v6.2.3": True,
440                                "v6.2.5": True,
441                                "v6.2.7": True,
442                                "v6.0.11": True
443                            }
444                        }
445                    ],
446                    "revisions": {
447                        "v6.0.0": True,
448                        "v7.0.0": True,
449                        "v6.0.5": True,
450                        "v6.4.4": True,
451                        "v6.4.0": True,
452                        "v6.4.1": True,
453                        "v6.2.0": True,
454                        "v6.2.3": True,
455                        "v6.2.5": True,
456                        "v6.2.7": True,
457                        "v6.0.11": True
458                    }
459                },
460                "id": {
461                    "type": "integer",
462                    "revisions": {
463                        "v6.0.0": True,
464                        "v7.0.0": True,
465                        "v6.0.5": True,
466                        "v6.4.4": True,
467                        "v6.4.0": True,
468                        "v6.4.1": True,
469                        "v6.2.0": True,
470                        "v6.2.3": True,
471                        "v6.2.5": True,
472                        "v6.2.7": True,
473                        "v6.0.11": True
474                    }
475                }
476            },
477            "revisions": {
478                "v6.0.0": True,
479                "v7.0.0": True,
480                "v6.0.5": True,
481                "v6.4.4": True,
482                "v6.4.0": True,
483                "v6.4.1": True,
484                "v6.2.0": True,
485                "v6.2.3": True,
486                "v6.2.5": True,
487                "v6.2.7": True,
488                "v6.0.11": True
489            }
490        },
491        "comments": {
492            "type": "string",
493            "revisions": {
494                "v6.0.0": True,
495                "v7.0.0": True,
496                "v6.0.5": True,
497                "v6.4.4": True,
498                "v6.4.0": True,
499                "v6.4.1": True,
500                "v6.2.0": True,
501                "v6.2.3": True,
502                "v6.2.5": True,
503                "v6.2.7": True,
504                "v6.0.11": True
505            }
506        }
507    },
508    "revisions": {
509        "v6.0.0": True,
510        "v7.0.0": True,
511        "v6.0.5": True,
512        "v6.4.4": True,
513        "v6.4.0": True,
514        "v6.4.1": True,
515        "v6.2.0": True,
516        "v6.2.3": True,
517        "v6.2.5": True,
518        "v6.2.7": True,
519        "v6.0.11": True
520    }
521}
522
523
524def main():
525    module_spec = schema_to_module_spec(versioned_schema)
526    mkeyname = 'name'
527    fields = {
528        "access_token": {"required": False, "type": "str", "no_log": True},
529        "enable_log": {"required": False, "type": bool},
530        "vdom": {"required": False, "type": "str", "default": "root"},
531        "state": {"required": True, "type": "str",
532                  "choices": ["present", "absent"]},
533        "router_prefix_list6": {
534            "required": False, "type": "dict", "default": None,
535            "options": {
536            }
537        }
538    }
539    for attribute_name in module_spec['options']:
540        fields["router_prefix_list6"]['options'][attribute_name] = module_spec['options'][attribute_name]
541        if mkeyname and mkeyname == attribute_name:
542            fields["router_prefix_list6"]['options'][attribute_name]['required'] = True
543
544    check_legacy_fortiosapi()
545    module = AnsibleModule(argument_spec=fields,
546                           supports_check_mode=True)
547
548    versions_check_result = None
549    if module._socket_path:
550        connection = Connection(module._socket_path)
551        if 'access_token' in module.params:
552            connection.set_option('access_token', module.params['access_token'])
553
554        if 'enable_log' in module.params:
555            connection.set_option('enable_log', module.params['enable_log'])
556        else:
557            connection.set_option('enable_log', False)
558        fos = FortiOSHandler(connection, module, mkeyname)
559        versions_check_result = check_schema_versioning(fos, versioned_schema, "router_prefix_list6")
560
561        is_error, has_changed, result = fortios_router(module.params, fos, module.check_mode)
562
563    else:
564        module.fail_json(**FAIL_SOCKET_MSG)
565
566    if versions_check_result and versions_check_result['matched'] is False:
567        module.warn("Ansible has detected version mismatch between FortOS system and your playbook, see more details by specifying option -vvv")
568
569    if not is_error:
570        if versions_check_result and versions_check_result['matched'] is False:
571            module.exit_json(changed=has_changed, version_check_warning=versions_check_result, meta=result)
572        else:
573            module.exit_json(changed=has_changed, meta=result)
574    else:
575        if versions_check_result and versions_check_result['matched'] is False:
576            module.fail_json(msg="Error in repo", version_check_warning=versions_check_result, meta=result)
577        else:
578            module.fail_json(msg="Error in repo", meta=result)
579
580
581if __name__ == '__main__':
582    main()
583