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_system_gre_tunnel
27short_description: Configure GRE tunnel 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 system feature and gre_tunnel 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    system_gre_tunnel:
76        description:
77            - Configure GRE tunnel.
78        default: null
79        type: dict
80        suboptions:
81            checksum_reception:
82                description:
83                    - Enable/disable validating checksums in received GRE packets.
84                type: str
85                choices:
86                    - disable
87                    - enable
88            checksum_transmission:
89                description:
90                    - Enable/disable including checksums in transmitted GRE packets.
91                type: str
92                choices:
93                    - disable
94                    - enable
95            diffservcode:
96                description:
97                    - DiffServ setting to be applied to GRE tunnel outer IP header.
98                type: str
99            dscp_copying:
100                description:
101                    - Enable/disable DSCP copying.
102                type: str
103                choices:
104                    - disable
105                    - enable
106            interface:
107                description:
108                    - Interface name. Source system.interface.name.
109                type: str
110            ip_version:
111                description:
112                    - IP version to use for VPN interface.
113                type: str
114                choices:
115                    - 4
116                    - 6
117            keepalive_failtimes:
118                description:
119                    - Number of consecutive unreturned keepalive messages before a GRE connection is considered down (1 - 255).
120                type: int
121            keepalive_interval:
122                description:
123                    - Keepalive message interval (0 - 32767, 0 = disabled).
124                type: int
125            key_inbound:
126                description:
127                    - Require received GRE packets contain this key (0 - 4294967295).
128                type: int
129            key_outbound:
130                description:
131                    - Include this key in transmitted GRE packets (0 - 4294967295).
132                type: int
133            local_gw:
134                description:
135                    - IP address of the local gateway.
136                type: str
137            local_gw6:
138                description:
139                    - IPv6 address of the local gateway.
140                type: str
141            name:
142                description:
143                    - Tunnel name.
144                required: true
145                type: str
146            remote_gw:
147                description:
148                    - IP address of the remote gateway.
149                type: str
150            remote_gw6:
151                description:
152                    - IPv6 address of the remote gateway.
153                type: str
154            sequence_number_reception:
155                description:
156                    - Enable/disable validating sequence numbers in received GRE packets.
157                type: str
158                choices:
159                    - disable
160                    - enable
161            sequence_number_transmission:
162                description:
163                    - Enable/disable including of sequence numbers in transmitted GRE packets.
164                type: str
165                choices:
166                    - disable
167                    - enable
168            use_sdwan:
169                description:
170                    - Enable/disable use of SD-WAN to reach remote gateway.
171                type: str
172                choices:
173                    - disable
174                    - enable
175'''
176
177EXAMPLES = '''
178- hosts: fortigates
179  collections:
180    - fortinet.fortios
181  connection: httpapi
182  vars:
183   vdom: "root"
184   ansible_httpapi_use_ssl: yes
185   ansible_httpapi_validate_certs: no
186   ansible_httpapi_port: 443
187  tasks:
188  - name: Configure GRE tunnel.
189    fortios_system_gre_tunnel:
190      vdom:  "{{ vdom }}"
191      state: "present"
192      access_token: "<your_own_value>"
193      system_gre_tunnel:
194        checksum_reception: "disable"
195        checksum_transmission: "disable"
196        diffservcode: "<your_own_value>"
197        dscp_copying: "disable"
198        interface: "<your_own_value> (source system.interface.name)"
199        ip_version: "4"
200        keepalive_failtimes: "9"
201        keepalive_interval: "10"
202        key_inbound: "11"
203        key_outbound: "12"
204        local_gw: "<your_own_value>"
205        local_gw6: "<your_own_value>"
206        name: "default_name_15"
207        remote_gw: "<your_own_value>"
208        remote_gw6: "<your_own_value>"
209        sequence_number_reception: "disable"
210        sequence_number_transmission: "disable"
211        use_sdwan: "disable"
212
213'''
214
215RETURN = '''
216build:
217  description: Build number of the fortigate image
218  returned: always
219  type: str
220  sample: '1547'
221http_method:
222  description: Last method used to provision the content into FortiGate
223  returned: always
224  type: str
225  sample: 'PUT'
226http_status:
227  description: Last result given by FortiGate on last operation applied
228  returned: always
229  type: str
230  sample: "200"
231mkey:
232  description: Master key (id) used in the last call to FortiGate
233  returned: success
234  type: str
235  sample: "id"
236name:
237  description: Name of the table used to fulfill the request
238  returned: always
239  type: str
240  sample: "urlfilter"
241path:
242  description: Path of the table used to fulfill the request
243  returned: always
244  type: str
245  sample: "webfilter"
246revision:
247  description: Internal revision number
248  returned: always
249  type: str
250  sample: "17.0.2.10658"
251serial:
252  description: Serial number of the unit
253  returned: always
254  type: str
255  sample: "FGVMEVYYQT3AB5352"
256status:
257  description: Indication of the operation's result
258  returned: always
259  type: str
260  sample: "success"
261vdom:
262  description: Virtual domain used
263  returned: always
264  type: str
265  sample: "root"
266version:
267  description: Version of the FortiGate
268  returned: always
269  type: str
270  sample: "v5.6.3"
271
272'''
273from ansible.module_utils.basic import AnsibleModule
274from ansible.module_utils.connection import Connection
275from ansible_collections.fortinet.fortios.plugins.module_utils.fortios.fortios import FortiOSHandler
276from ansible_collections.fortinet.fortios.plugins.module_utils.fortios.fortios import check_legacy_fortiosapi
277from ansible_collections.fortinet.fortios.plugins.module_utils.fortios.fortios import schema_to_module_spec
278from ansible_collections.fortinet.fortios.plugins.module_utils.fortios.fortios import check_schema_versioning
279from ansible_collections.fortinet.fortios.plugins.module_utils.fortimanager.common import FAIL_SOCKET_MSG
280from ansible_collections.fortinet.fortios.plugins.module_utils.fortios.comparison import is_same_comparison
281from ansible_collections.fortinet.fortios.plugins.module_utils.fortios.comparison import serialize
282
283
284def filter_system_gre_tunnel_data(json):
285    option_list = ['checksum_reception', 'checksum_transmission', 'diffservcode',
286                   'dscp_copying', 'interface', 'ip_version',
287                   'keepalive_failtimes', 'keepalive_interval', 'key_inbound',
288                   'key_outbound', 'local_gw', 'local_gw6',
289                   'name', 'remote_gw', 'remote_gw6',
290                   'sequence_number_reception', 'sequence_number_transmission', 'use_sdwan']
291    dictionary = {}
292
293    for attribute in option_list:
294        if attribute in json and json[attribute] is not None:
295            dictionary[attribute] = json[attribute]
296
297    return dictionary
298
299
300def underscore_to_hyphen(data):
301    if isinstance(data, list):
302        for i, elem in enumerate(data):
303            data[i] = underscore_to_hyphen(elem)
304    elif isinstance(data, dict):
305        new_data = {}
306        for k, v in data.items():
307            new_data[k.replace('_', '-')] = underscore_to_hyphen(v)
308        data = new_data
309
310    return data
311
312
313def system_gre_tunnel(data, fos, check_mode=False):
314
315    vdom = data['vdom']
316
317    state = data['state']
318
319    system_gre_tunnel_data = data['system_gre_tunnel']
320    filtered_data = underscore_to_hyphen(filter_system_gre_tunnel_data(system_gre_tunnel_data))
321
322    # check_mode starts from here
323    if check_mode:
324        mkey = fos.get_mkey('system', 'interface', filtered_data, vdom=vdom)
325        current_data = fos.get('system', 'interface', vdom=vdom, mkey=mkey)
326        is_existed = current_data and current_data.get('http_status') == 200 \
327            and isinstance(current_data.get('results'), list) \
328            and len(current_data['results']) > 0
329
330        # 2. if it exists and the state is 'present' then compare current settings with desired
331        if state == 'present' or state is True:
332            if mkey is None:
333                return False, True, filtered_data
334
335            # if mkey exists then compare each other
336            # record exits and they're matched or not
337            if is_existed:
338                is_same = is_same_comparison(
339                    serialize(current_data['results'][0]), serialize(filtered_data))
340                return False, not is_same, filtered_data
341
342            # record does not exist
343            return False, True, filtered_data
344
345        if state == 'absent':
346            if mkey is None:
347                return False, False, filtered_data
348
349            if is_existed:
350                return False, True, filtered_data
351            return False, False, filtered_data
352
353        return True, False, {'reason: ': 'Must provide state parameter'}
354
355    if state == "present" or state is True:
356        return fos.set('system',
357                       'gre-tunnel',
358                       data=filtered_data,
359                       vdom=vdom)
360
361    elif state == "absent":
362        return fos.delete('system',
363                          'gre-tunnel',
364                          mkey=filtered_data['name'],
365                          vdom=vdom)
366    else:
367        fos._module.fail_json(msg='state must be present or absent!')
368
369
370def is_successful_status(status):
371    return status['status'] == "success" or \
372        status['http_method'] == "DELETE" and status['http_status'] == 404
373
374
375def fortios_system(data, fos, check_mode):
376
377    if data['system_gre_tunnel']:
378        resp = system_gre_tunnel(data, fos, check_mode)
379    else:
380        fos._module.fail_json(msg='missing task body: %s' % ('system_gre_tunnel'))
381    if check_mode:
382        return resp
383    return not is_successful_status(resp), \
384        resp['status'] == "success" and \
385        (resp['revision_changed'] if 'revision_changed' in resp else True), \
386        resp
387
388
389versioned_schema = {
390    "type": "list",
391    "children": {
392        "use_sdwan": {
393            "type": "string",
394            "options": [
395                {
396                    "value": "disable",
397                    "revisions": {
398                        "v7.0.0": True
399                    }
400                },
401                {
402                    "value": "enable",
403                    "revisions": {
404                        "v7.0.0": True
405                    }
406                }
407            ],
408            "revisions": {
409                "v7.0.0": True
410            }
411        },
412        "keepalive_interval": {
413            "type": "integer",
414            "revisions": {
415                "v6.0.0": True,
416                "v7.0.0": True,
417                "v6.0.5": True,
418                "v6.4.4": True,
419                "v6.4.0": True,
420                "v6.4.1": True,
421                "v6.2.0": True,
422                "v6.2.3": True,
423                "v6.2.5": True,
424                "v6.2.7": True,
425                "v6.0.11": True
426            }
427        },
428        "sequence_number_reception": {
429            "type": "string",
430            "options": [
431                {
432                    "value": "disable",
433                    "revisions": {
434                        "v7.0.0": True,
435                        "v6.4.0": True
436                    }
437                },
438                {
439                    "value": "enable",
440                    "revisions": {
441                        "v7.0.0": True,
442                        "v6.4.0": True
443                    }
444                }
445            ],
446            "revisions": {
447                "v6.4.4": False,
448                "v7.0.0": True,
449                "v6.4.0": True,
450                "v6.4.1": False
451            }
452        },
453        "key_inbound": {
454            "type": "integer",
455            "revisions": {
456                "v6.4.4": False,
457                "v7.0.0": True,
458                "v6.4.0": True,
459                "v6.4.1": False
460            }
461        },
462        "name": {
463            "type": "string",
464            "revisions": {
465                "v6.0.0": True,
466                "v7.0.0": True,
467                "v6.0.5": True,
468                "v6.4.4": True,
469                "v6.4.0": True,
470                "v6.4.1": True,
471                "v6.2.0": True,
472                "v6.2.3": True,
473                "v6.2.5": True,
474                "v6.2.7": True,
475                "v6.0.11": True
476            }
477        },
478        "key_outbound": {
479            "type": "integer",
480            "revisions": {
481                "v6.4.4": False,
482                "v7.0.0": True,
483                "v6.4.0": True,
484                "v6.4.1": False
485            }
486        },
487        "keepalive_failtimes": {
488            "type": "integer",
489            "revisions": {
490                "v6.0.0": True,
491                "v7.0.0": True,
492                "v6.0.5": True,
493                "v6.4.4": True,
494                "v6.4.0": True,
495                "v6.4.1": True,
496                "v6.2.0": True,
497                "v6.2.3": True,
498                "v6.2.5": True,
499                "v6.2.7": True,
500                "v6.0.11": True
501            }
502        },
503        "local_gw6": {
504            "type": "string",
505            "revisions": {
506                "v6.0.0": True,
507                "v7.0.0": True,
508                "v6.0.5": True,
509                "v6.4.4": True,
510                "v6.4.0": True,
511                "v6.4.1": True,
512                "v6.2.0": True,
513                "v6.2.3": True,
514                "v6.2.5": True,
515                "v6.2.7": True,
516                "v6.0.11": True
517            }
518        },
519        "local_gw": {
520            "type": "string",
521            "revisions": {
522                "v6.0.0": True,
523                "v7.0.0": True,
524                "v6.0.5": True,
525                "v6.4.4": True,
526                "v6.4.0": True,
527                "v6.4.1": True,
528                "v6.2.0": True,
529                "v6.2.3": True,
530                "v6.2.5": True,
531                "v6.2.7": True,
532                "v6.0.11": True
533            }
534        },
535        "interface": {
536            "type": "string",
537            "revisions": {
538                "v6.0.0": True,
539                "v7.0.0": True,
540                "v6.0.5": True,
541                "v6.4.4": True,
542                "v6.4.0": True,
543                "v6.4.1": True,
544                "v6.2.0": True,
545                "v6.2.3": True,
546                "v6.2.5": True,
547                "v6.2.7": True,
548                "v6.0.11": True
549            }
550        },
551        "diffservcode": {
552            "type": "string",
553            "revisions": {
554                "v7.0.0": True,
555                "v6.4.4": True,
556                "v6.4.0": True,
557                "v6.4.1": True,
558                "v6.2.0": True,
559                "v6.2.3": True,
560                "v6.2.5": True,
561                "v6.2.7": True
562            }
563        },
564        "checksum_reception": {
565            "type": "string",
566            "options": [
567                {
568                    "value": "disable",
569                    "revisions": {
570                        "v7.0.0": True,
571                        "v6.4.0": True
572                    }
573                },
574                {
575                    "value": "enable",
576                    "revisions": {
577                        "v7.0.0": True,
578                        "v6.4.0": True
579                    }
580                }
581            ],
582            "revisions": {
583                "v6.4.4": False,
584                "v7.0.0": True,
585                "v6.4.0": True,
586                "v6.4.1": False
587            }
588        },
589        "ip_version": {
590            "type": "string",
591            "options": [
592                {
593                    "value": "4",
594                    "revisions": {
595                        "v6.0.0": True,
596                        "v7.0.0": True,
597                        "v6.0.5": True,
598                        "v6.4.4": True,
599                        "v6.4.0": True,
600                        "v6.4.1": True,
601                        "v6.2.0": True,
602                        "v6.2.3": True,
603                        "v6.2.5": True,
604                        "v6.2.7": True,
605                        "v6.0.11": True
606                    }
607                },
608                {
609                    "value": "6",
610                    "revisions": {
611                        "v6.0.0": True,
612                        "v7.0.0": True,
613                        "v6.0.5": True,
614                        "v6.4.4": True,
615                        "v6.4.0": True,
616                        "v6.4.1": True,
617                        "v6.2.0": True,
618                        "v6.2.3": True,
619                        "v6.2.5": True,
620                        "v6.2.7": True,
621                        "v6.0.11": True
622                    }
623                }
624            ],
625            "revisions": {
626                "v6.0.0": True,
627                "v7.0.0": True,
628                "v6.0.5": True,
629                "v6.4.4": True,
630                "v6.4.0": True,
631                "v6.4.1": True,
632                "v6.2.0": True,
633                "v6.2.3": True,
634                "v6.2.5": True,
635                "v6.2.7": True,
636                "v6.0.11": True
637            }
638        },
639        "sequence_number_transmission": {
640            "type": "string",
641            "options": [
642                {
643                    "value": "disable",
644                    "revisions": {
645                        "v7.0.0": True,
646                        "v6.4.0": True
647                    }
648                },
649                {
650                    "value": "enable",
651                    "revisions": {
652                        "v7.0.0": True,
653                        "v6.4.0": True
654                    }
655                }
656            ],
657            "revisions": {
658                "v6.4.4": False,
659                "v7.0.0": True,
660                "v6.4.0": True,
661                "v6.4.1": False
662            }
663        },
664        "remote_gw": {
665            "type": "string",
666            "revisions": {
667                "v6.0.0": True,
668                "v7.0.0": True,
669                "v6.0.5": True,
670                "v6.4.4": True,
671                "v6.4.0": True,
672                "v6.4.1": True,
673                "v6.2.0": True,
674                "v6.2.3": True,
675                "v6.2.5": True,
676                "v6.2.7": True,
677                "v6.0.11": True
678            }
679        },
680        "checksum_transmission": {
681            "type": "string",
682            "options": [
683                {
684                    "value": "disable",
685                    "revisions": {
686                        "v7.0.0": True,
687                        "v6.4.0": True
688                    }
689                },
690                {
691                    "value": "enable",
692                    "revisions": {
693                        "v7.0.0": True,
694                        "v6.4.0": True
695                    }
696                }
697            ],
698            "revisions": {
699                "v6.4.4": False,
700                "v7.0.0": True,
701                "v6.4.0": True,
702                "v6.4.1": False
703            }
704        },
705        "dscp_copying": {
706            "type": "string",
707            "options": [
708                {
709                    "value": "disable",
710                    "revisions": {
711                        "v6.0.0": True,
712                        "v7.0.0": True,
713                        "v6.0.5": True,
714                        "v6.4.4": True,
715                        "v6.4.0": True,
716                        "v6.4.1": True,
717                        "v6.2.0": True,
718                        "v6.2.3": True,
719                        "v6.2.5": True,
720                        "v6.2.7": True,
721                        "v6.0.11": True
722                    }
723                },
724                {
725                    "value": "enable",
726                    "revisions": {
727                        "v6.0.0": True,
728                        "v7.0.0": True,
729                        "v6.0.5": True,
730                        "v6.4.4": True,
731                        "v6.4.0": True,
732                        "v6.4.1": True,
733                        "v6.2.0": True,
734                        "v6.2.3": True,
735                        "v6.2.5": True,
736                        "v6.2.7": True,
737                        "v6.0.11": True
738                    }
739                }
740            ],
741            "revisions": {
742                "v6.0.0": True,
743                "v7.0.0": True,
744                "v6.0.5": True,
745                "v6.4.4": True,
746                "v6.4.0": True,
747                "v6.4.1": True,
748                "v6.2.0": True,
749                "v6.2.3": True,
750                "v6.2.5": True,
751                "v6.2.7": True,
752                "v6.0.11": True
753            }
754        },
755        "remote_gw6": {
756            "type": "string",
757            "revisions": {
758                "v6.0.0": True,
759                "v7.0.0": True,
760                "v6.0.5": True,
761                "v6.4.4": True,
762                "v6.4.0": True,
763                "v6.4.1": True,
764                "v6.2.0": True,
765                "v6.2.3": True,
766                "v6.2.5": True,
767                "v6.2.7": True,
768                "v6.0.11": True
769            }
770        }
771    },
772    "revisions": {
773        "v6.0.0": True,
774        "v7.0.0": True,
775        "v6.0.5": True,
776        "v6.4.4": True,
777        "v6.4.0": True,
778        "v6.4.1": True,
779        "v6.2.0": True,
780        "v6.2.3": True,
781        "v6.2.5": True,
782        "v6.2.7": True,
783        "v6.0.11": True
784    }
785}
786
787
788def main():
789    module_spec = schema_to_module_spec(versioned_schema)
790    mkeyname = 'name'
791    fields = {
792        "access_token": {"required": False, "type": "str", "no_log": True},
793        "enable_log": {"required": False, "type": bool},
794        "vdom": {"required": False, "type": "str", "default": "root"},
795        "state": {"required": True, "type": "str",
796                  "choices": ["present", "absent"]},
797        "system_gre_tunnel": {
798            "required": False, "type": "dict", "default": None,
799            "options": {
800            }
801        }
802    }
803    for attribute_name in module_spec['options']:
804        fields["system_gre_tunnel"]['options'][attribute_name] = module_spec['options'][attribute_name]
805        if mkeyname and mkeyname == attribute_name:
806            fields["system_gre_tunnel"]['options'][attribute_name]['required'] = True
807
808    check_legacy_fortiosapi()
809    module = AnsibleModule(argument_spec=fields,
810                           supports_check_mode=True)
811
812    versions_check_result = None
813    if module._socket_path:
814        connection = Connection(module._socket_path)
815        if 'access_token' in module.params:
816            connection.set_option('access_token', module.params['access_token'])
817
818        if 'enable_log' in module.params:
819            connection.set_option('enable_log', module.params['enable_log'])
820        else:
821            connection.set_option('enable_log', False)
822        fos = FortiOSHandler(connection, module, mkeyname)
823        versions_check_result = check_schema_versioning(fos, versioned_schema, "system_gre_tunnel")
824
825        is_error, has_changed, result = fortios_system(module.params, fos, module.check_mode)
826
827    else:
828        module.fail_json(**FAIL_SOCKET_MSG)
829
830    if versions_check_result and versions_check_result['matched'] is False:
831        module.warn("Ansible has detected version mismatch between FortOS system and your playbook, see more details by specifying option -vvv")
832
833    if not is_error:
834        if versions_check_result and versions_check_result['matched'] is False:
835            module.exit_json(changed=has_changed, version_check_warning=versions_check_result, meta=result)
836        else:
837            module.exit_json(changed=has_changed, meta=result)
838    else:
839        if versions_check_result and versions_check_result['matched'] is False:
840            module.fail_json(msg="Error in repo", version_check_warning=versions_check_result, meta=result)
841        else:
842            module.fail_json(msg="Error in repo", meta=result)
843
844
845if __name__ == '__main__':
846    main()
847