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_vpn_l2tp
27short_description: Configure L2TP 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 vpn feature and l2tp 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    vpn_l2tp:
68        description:
69            - Configure L2TP.
70        default: null
71        type: dict
72        suboptions:
73            compress:
74                description:
75                    - Enable/disable data compression.
76                type: str
77                choices:
78                    - enable
79                    - disable
80            eip:
81                description:
82                    - End IP.
83                type: str
84            enforce_ipsec:
85                description:
86                    - Enable/disable IPsec enforcement.
87                type: str
88                choices:
89                    - enable
90                    - disable
91            lcp_echo_interval:
92                description:
93                    - Time in seconds between PPPoE Link Control Protocol (LCP) echo requests.
94                type: int
95            lcp_max_echo_fails:
96                description:
97                    - Maximum number of missed LCP echo messages before disconnect.
98                type: int
99            sip:
100                description:
101                    - Start IP.
102                type: str
103            status:
104                description:
105                    - Enable/disable FortiGate as a L2TP gateway.
106                type: str
107                choices:
108                    - enable
109                    - disable
110            usrgrp:
111                description:
112                    - User group. Source user.group.name.
113                type: str
114'''
115
116EXAMPLES = '''
117- hosts: fortigates
118  collections:
119    - fortinet.fortios
120  connection: httpapi
121  vars:
122   vdom: "root"
123   ansible_httpapi_use_ssl: yes
124   ansible_httpapi_validate_certs: no
125   ansible_httpapi_port: 443
126  tasks:
127  - name: Configure L2TP.
128    fortios_vpn_l2tp:
129      vdom:  "{{ vdom }}"
130      vpn_l2tp:
131        compress: "enable"
132        eip: "<your_own_value>"
133        enforce_ipsec: "enable"
134        lcp_echo_interval: "6"
135        lcp_max_echo_fails: "7"
136        sip: "<your_own_value>"
137        status: "enable"
138        usrgrp: "<your_own_value> (source user.group.name)"
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_vpn_l2tp_data(json):
212    option_list = ['compress', 'eip', 'enforce_ipsec',
213                   'lcp_echo_interval', 'lcp_max_echo_fails', 'sip',
214                   'status', 'usrgrp']
215    dictionary = {}
216
217    for attribute in option_list:
218        if attribute in json and json[attribute] is not None:
219            dictionary[attribute] = json[attribute]
220
221    return dictionary
222
223
224def underscore_to_hyphen(data):
225    if isinstance(data, list):
226        for i, elem in enumerate(data):
227            data[i] = underscore_to_hyphen(elem)
228    elif isinstance(data, dict):
229        new_data = {}
230        for k, v in data.items():
231            new_data[k.replace('_', '-')] = underscore_to_hyphen(v)
232        data = new_data
233
234    return data
235
236
237def vpn_l2tp(data, fos):
238    vdom = data['vdom']
239    vpn_l2tp_data = data['vpn_l2tp']
240    filtered_data = underscore_to_hyphen(filter_vpn_l2tp_data(vpn_l2tp_data))
241
242    return fos.set('vpn',
243                   'l2tp',
244                   data=filtered_data,
245                   vdom=vdom)
246
247
248def is_successful_status(status):
249    return status['status'] == "success" or \
250        status['http_method'] == "DELETE" and status['http_status'] == 404
251
252
253def fortios_vpn(data, fos):
254
255    if data['vpn_l2tp']:
256        resp = vpn_l2tp(data, fos)
257    else:
258        fos._module.fail_json(msg='missing task body: %s' % ('vpn_l2tp'))
259
260    return not is_successful_status(resp), \
261        resp['status'] == "success" and \
262        (resp['revision_changed'] if 'revision_changed' in resp else True), \
263        resp
264
265
266versioned_schema = {
267    "type": "dict",
268    "children": {
269        "status": {
270            "type": "string",
271            "options": [
272                {
273                    "value": "enable",
274                    "revisions": {
275                        "v6.0.0": True,
276                        "v7.0.0": True,
277                        "v6.0.5": True,
278                        "v6.4.4": True,
279                        "v6.4.0": True,
280                        "v6.4.1": True,
281                        "v6.2.0": True,
282                        "v6.2.3": True,
283                        "v6.2.5": True,
284                        "v6.2.7": True,
285                        "v6.0.11": True
286                    }
287                },
288                {
289                    "value": "disable",
290                    "revisions": {
291                        "v6.0.0": True,
292                        "v7.0.0": True,
293                        "v6.0.5": True,
294                        "v6.4.4": True,
295                        "v6.4.0": True,
296                        "v6.4.1": True,
297                        "v6.2.0": True,
298                        "v6.2.3": True,
299                        "v6.2.5": True,
300                        "v6.2.7": True,
301                        "v6.0.11": True
302                    }
303                }
304            ],
305            "revisions": {
306                "v6.0.0": True,
307                "v7.0.0": True,
308                "v6.0.5": True,
309                "v6.4.4": True,
310                "v6.4.0": True,
311                "v6.4.1": True,
312                "v6.2.0": True,
313                "v6.2.3": True,
314                "v6.2.5": True,
315                "v6.2.7": True,
316                "v6.0.11": True
317            }
318        },
319        "eip": {
320            "type": "string",
321            "revisions": {
322                "v6.0.0": True,
323                "v7.0.0": True,
324                "v6.0.5": True,
325                "v6.4.4": True,
326                "v6.4.0": True,
327                "v6.4.1": True,
328                "v6.2.0": True,
329                "v6.2.3": True,
330                "v6.2.5": True,
331                "v6.2.7": True,
332                "v6.0.11": True
333            }
334        },
335        "sip": {
336            "type": "string",
337            "revisions": {
338                "v6.0.0": True,
339                "v7.0.0": True,
340                "v6.0.5": True,
341                "v6.4.4": True,
342                "v6.4.0": True,
343                "v6.4.1": True,
344                "v6.2.0": True,
345                "v6.2.3": True,
346                "v6.2.5": True,
347                "v6.2.7": True,
348                "v6.0.11": True
349            }
350        },
351        "usrgrp": {
352            "type": "string",
353            "revisions": {
354                "v6.0.0": True,
355                "v7.0.0": True,
356                "v6.0.5": True,
357                "v6.4.4": True,
358                "v6.4.0": True,
359                "v6.4.1": True,
360                "v6.2.0": True,
361                "v6.2.3": True,
362                "v6.2.5": True,
363                "v6.2.7": True,
364                "v6.0.11": True
365            }
366        },
367        "compress": {
368            "type": "string",
369            "options": [
370                {
371                    "value": "enable",
372                    "revisions": {
373                        "v7.0.0": True,
374                        "v6.4.4": True,
375                        "v6.4.0": True,
376                        "v6.4.1": True,
377                        "v6.2.0": True,
378                        "v6.2.5": True,
379                        "v6.2.7": True
380                    }
381                },
382                {
383                    "value": "disable",
384                    "revisions": {
385                        "v7.0.0": True,
386                        "v6.4.4": True,
387                        "v6.4.0": True,
388                        "v6.4.1": True,
389                        "v6.2.0": True,
390                        "v6.2.5": True,
391                        "v6.2.7": True
392                    }
393                }
394            ],
395            "revisions": {
396                "v7.0.0": True,
397                "v6.4.4": True,
398                "v6.4.0": True,
399                "v6.4.1": True,
400                "v6.2.0": True,
401                "v6.2.3": False,
402                "v6.2.5": True,
403                "v6.2.7": True
404            }
405        },
406        "lcp_echo_interval": {
407            "type": "integer",
408            "revisions": {
409                "v6.4.4": True,
410                "v7.0.0": True,
411                "v6.4.0": True,
412                "v6.4.1": False
413            }
414        },
415        "enforce_ipsec": {
416            "type": "string",
417            "options": [
418                {
419                    "value": "enable",
420                    "revisions": {
421                        "v6.0.0": True,
422                        "v7.0.0": True,
423                        "v6.0.5": True,
424                        "v6.4.4": True,
425                        "v6.4.0": True,
426                        "v6.4.1": True,
427                        "v6.2.0": True,
428                        "v6.2.3": True,
429                        "v6.2.5": True,
430                        "v6.2.7": True,
431                        "v6.0.11": True
432                    }
433                },
434                {
435                    "value": "disable",
436                    "revisions": {
437                        "v6.0.0": True,
438                        "v7.0.0": True,
439                        "v6.0.5": True,
440                        "v6.4.4": True,
441                        "v6.4.0": True,
442                        "v6.4.1": True,
443                        "v6.2.0": True,
444                        "v6.2.3": True,
445                        "v6.2.5": True,
446                        "v6.2.7": True,
447                        "v6.0.11": True
448                    }
449                }
450            ],
451            "revisions": {
452                "v6.0.0": True,
453                "v7.0.0": True,
454                "v6.0.5": True,
455                "v6.4.4": True,
456                "v6.4.0": True,
457                "v6.4.1": True,
458                "v6.2.0": True,
459                "v6.2.3": True,
460                "v6.2.5": True,
461                "v6.2.7": True,
462                "v6.0.11": True
463            }
464        },
465        "lcp_max_echo_fails": {
466            "type": "integer",
467            "revisions": {
468                "v6.4.4": True,
469                "v7.0.0": True,
470                "v6.4.0": True,
471                "v6.4.1": False
472            }
473        }
474    },
475    "revisions": {
476        "v6.0.0": True,
477        "v7.0.0": True,
478        "v6.0.5": True,
479        "v6.4.4": True,
480        "v6.4.0": True,
481        "v6.4.1": True,
482        "v6.2.0": True,
483        "v6.2.3": True,
484        "v6.2.5": True,
485        "v6.2.7": True,
486        "v6.0.11": True
487    }
488}
489
490
491def main():
492    module_spec = schema_to_module_spec(versioned_schema)
493    mkeyname = None
494    fields = {
495        "access_token": {"required": False, "type": "str", "no_log": True},
496        "enable_log": {"required": False, "type": bool},
497        "vdom": {"required": False, "type": "str", "default": "root"},
498        "vpn_l2tp": {
499            "required": False, "type": "dict", "default": None,
500            "options": {
501            }
502        }
503    }
504    for attribute_name in module_spec['options']:
505        fields["vpn_l2tp"]['options'][attribute_name] = module_spec['options'][attribute_name]
506        if mkeyname and mkeyname == attribute_name:
507            fields["vpn_l2tp"]['options'][attribute_name]['required'] = True
508
509    check_legacy_fortiosapi()
510    module = AnsibleModule(argument_spec=fields,
511                           supports_check_mode=False)
512
513    versions_check_result = None
514    if module._socket_path:
515        connection = Connection(module._socket_path)
516        if 'access_token' in module.params:
517            connection.set_option('access_token', module.params['access_token'])
518
519        if 'enable_log' in module.params:
520            connection.set_option('enable_log', module.params['enable_log'])
521        else:
522            connection.set_option('enable_log', False)
523        fos = FortiOSHandler(connection, module, mkeyname)
524        versions_check_result = check_schema_versioning(fos, versioned_schema, "vpn_l2tp")
525
526        is_error, has_changed, result = fortios_vpn(module.params, fos)
527
528    else:
529        module.fail_json(**FAIL_SOCKET_MSG)
530
531    if versions_check_result and versions_check_result['matched'] is False:
532        module.warn("Ansible has detected version mismatch between FortOS system and your playbook, see more details by specifying option -vvv")
533
534    if not is_error:
535        if versions_check_result and versions_check_result['matched'] is False:
536            module.exit_json(changed=has_changed, version_check_warning=versions_check_result, meta=result)
537        else:
538            module.exit_json(changed=has_changed, meta=result)
539    else:
540        if versions_check_result and versions_check_result['matched'] is False:
541            module.fail_json(msg="Error in repo", version_check_warning=versions_check_result, meta=result)
542        else:
543            module.fail_json(msg="Error in repo", meta=result)
544
545
546if __name__ == '__main__':
547    main()
548