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