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