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_wireless_controller_access_control_list
27short_description: Configure WiFi bridge access control list 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 wireless_controller feature and access_control_list 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    wireless_controller_access_control_list:
76        description:
77            - Configure WiFi bridge access control list.
78        default: null
79        type: dict
80        suboptions:
81            comment:
82                description:
83                    - Description.
84                type: str
85            layer3_ipv4_rules:
86                description:
87                    - AP ACL layer3 ipv4 rule list.
88                type: list
89                suboptions:
90                    action:
91                        description:
92                            - Policy action (allow | deny).
93                        type: str
94                        choices:
95                            - allow
96                            - deny
97                    comment:
98                        description:
99                            - Description.
100                        type: str
101                    dstaddr:
102                        description:
103                            - Destination IP address (any | local-LAN | IPv4 address[/<network mask | mask length>]).
104                        type: str
105                    dstport:
106                        description:
107                            - Destination port (0 - 65535).
108                        type: int
109                    protocol:
110                        description:
111                            - Protocol type as defined by IANA (0 - 255).
112                        type: int
113                    rule_id:
114                        description:
115                            - Rule ID (1 - 65535).
116                        type: int
117                    srcaddr:
118                        description:
119                            - Source IP address (any | local-LAN | IPv4 address[/<network mask | mask length>]).
120                        type: str
121                    srcport:
122                        description:
123                            - Source port (0 - 65535).
124                        type: int
125            layer3_ipv6_rules:
126                description:
127                    - AP ACL layer3 ipv6 rule list.
128                type: list
129                suboptions:
130                    action:
131                        description:
132                            - Policy action (allow | deny).
133                        type: str
134                        choices:
135                            - allow
136                            - deny
137                    comment:
138                        description:
139                            - Description.
140                        type: str
141                    dstaddr:
142                        description:
143                            - Destination IPv6 address (any | local-LAN | IPv6 address[/prefix length]), default = any.
144                        type: str
145                    dstport:
146                        description:
147                            - Destination port (0 - 65535).
148                        type: int
149                    protocol:
150                        description:
151                            - Protocol type as defined by IANA (0 - 255).
152                        type: int
153                    rule_id:
154                        description:
155                            - Rule ID (1 - 65535).
156                        type: int
157                    srcaddr:
158                        description:
159                            - Source IPv6 address (any | local-LAN | IPv6 address[/prefix length]), default = any.
160                        type: str
161                    srcport:
162                        description:
163                            - Source port (0 - 65535).
164                        type: int
165            name:
166                description:
167                    - AP access control list name.
168                required: true
169                type: str
170'''
171
172EXAMPLES = '''
173- hosts: fortigates
174  collections:
175    - fortinet.fortios
176  connection: httpapi
177  vars:
178   vdom: "root"
179   ansible_httpapi_use_ssl: yes
180   ansible_httpapi_validate_certs: no
181   ansible_httpapi_port: 443
182  tasks:
183  - name: Configure WiFi bridge access control list.
184    fortios_wireless_controller_access_control_list:
185      vdom:  "{{ vdom }}"
186      state: "present"
187      access_token: "<your_own_value>"
188      wireless_controller_access_control_list:
189        comment: "Description."
190        layer3_ipv4_rules:
191         -
192            action: "allow"
193            comment: "Description."
194            dstaddr: "<your_own_value>"
195            dstport: "8"
196            protocol: "9"
197            rule_id: "10"
198            srcaddr: "<your_own_value>"
199            srcport: "12"
200        layer3_ipv6_rules:
201         -
202            action: "allow"
203            comment: "Description."
204            dstaddr: "<your_own_value>"
205            dstport: "17"
206            protocol: "18"
207            rule_id: "19"
208            srcaddr: "<your_own_value>"
209            srcport: "21"
210        name: "default_name_22"
211
212'''
213
214RETURN = '''
215build:
216  description: Build number of the fortigate image
217  returned: always
218  type: str
219  sample: '1547'
220http_method:
221  description: Last method used to provision the content into FortiGate
222  returned: always
223  type: str
224  sample: 'PUT'
225http_status:
226  description: Last result given by FortiGate on last operation applied
227  returned: always
228  type: str
229  sample: "200"
230mkey:
231  description: Master key (id) used in the last call to FortiGate
232  returned: success
233  type: str
234  sample: "id"
235name:
236  description: Name of the table used to fulfill the request
237  returned: always
238  type: str
239  sample: "urlfilter"
240path:
241  description: Path of the table used to fulfill the request
242  returned: always
243  type: str
244  sample: "webfilter"
245revision:
246  description: Internal revision number
247  returned: always
248  type: str
249  sample: "17.0.2.10658"
250serial:
251  description: Serial number of the unit
252  returned: always
253  type: str
254  sample: "FGVMEVYYQT3AB5352"
255status:
256  description: Indication of the operation's result
257  returned: always
258  type: str
259  sample: "success"
260vdom:
261  description: Virtual domain used
262  returned: always
263  type: str
264  sample: "root"
265version:
266  description: Version of the FortiGate
267  returned: always
268  type: str
269  sample: "v5.6.3"
270
271'''
272from ansible.module_utils.basic import AnsibleModule
273from ansible.module_utils.connection import Connection
274from ansible_collections.fortinet.fortios.plugins.module_utils.fortios.fortios import FortiOSHandler
275from ansible_collections.fortinet.fortios.plugins.module_utils.fortios.fortios import check_legacy_fortiosapi
276from ansible_collections.fortinet.fortios.plugins.module_utils.fortios.fortios import schema_to_module_spec
277from ansible_collections.fortinet.fortios.plugins.module_utils.fortios.fortios import check_schema_versioning
278from ansible_collections.fortinet.fortios.plugins.module_utils.fortimanager.common import FAIL_SOCKET_MSG
279from ansible_collections.fortinet.fortios.plugins.module_utils.fortios.comparison import is_same_comparison
280from ansible_collections.fortinet.fortios.plugins.module_utils.fortios.comparison import serialize
281
282
283def filter_wireless_controller_access_control_list_data(json):
284    option_list = ['comment', 'layer3_ipv4_rules', 'layer3_ipv6_rules',
285                   'name']
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 wireless_controller_access_control_list(data, fos, check_mode=False):
309
310    vdom = data['vdom']
311
312    state = data['state']
313
314    wireless_controller_access_control_list_data = data['wireless_controller_access_control_list']
315    filtered_data = underscore_to_hyphen(filter_wireless_controller_access_control_list_data(wireless_controller_access_control_list_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('wireless-controller',
352                       'access-control-list',
353                       data=filtered_data,
354                       vdom=vdom)
355
356    elif state == "absent":
357        return fos.delete('wireless-controller',
358                          'access-control-list',
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_wireless_controller(data, fos, check_mode):
371
372    if data['wireless_controller_access_control_list']:
373        resp = wireless_controller_access_control_list(data, fos, check_mode)
374    else:
375        fos._module.fail_json(msg='missing task body: %s' % ('wireless_controller_access_control_list'))
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        "comment": {
388            "type": "string",
389            "revisions": {
390                "v6.4.4": True,
391                "v7.0.0": True,
392                "v6.4.0": True,
393                "v6.4.1": True
394            }
395        },
396        "layer3_ipv6_rules": {
397            "type": "list",
398            "children": {
399                "comment": {
400                    "type": "string",
401                    "revisions": {
402                        "v6.4.4": True,
403                        "v7.0.0": True,
404                        "v6.4.0": True,
405                        "v6.4.1": True
406                    }
407                },
408                "dstaddr": {
409                    "type": "string",
410                    "revisions": {
411                        "v6.4.4": True,
412                        "v7.0.0": True,
413                        "v6.4.0": True,
414                        "v6.4.1": True
415                    }
416                },
417                "protocol": {
418                    "type": "integer",
419                    "revisions": {
420                        "v6.4.4": True,
421                        "v7.0.0": True,
422                        "v6.4.0": True,
423                        "v6.4.1": True
424                    }
425                },
426                "srcport": {
427                    "type": "integer",
428                    "revisions": {
429                        "v6.4.4": True,
430                        "v7.0.0": True,
431                        "v6.4.0": True,
432                        "v6.4.1": True
433                    }
434                },
435                "srcaddr": {
436                    "type": "string",
437                    "revisions": {
438                        "v6.4.4": True,
439                        "v7.0.0": True,
440                        "v6.4.0": True,
441                        "v6.4.1": True
442                    }
443                },
444                "action": {
445                    "type": "string",
446                    "options": [
447                        {
448                            "value": "allow",
449                            "revisions": {
450                                "v6.4.4": True,
451                                "v7.0.0": True,
452                                "v6.4.0": True,
453                                "v6.4.1": True
454                            }
455                        },
456                        {
457                            "value": "deny",
458                            "revisions": {
459                                "v6.4.4": True,
460                                "v7.0.0": True,
461                                "v6.4.0": True,
462                                "v6.4.1": True
463                            }
464                        }
465                    ],
466                    "revisions": {
467                        "v6.4.4": True,
468                        "v7.0.0": True,
469                        "v6.4.0": True,
470                        "v6.4.1": True
471                    }
472                },
473                "dstport": {
474                    "type": "integer",
475                    "revisions": {
476                        "v6.4.4": True,
477                        "v7.0.0": True,
478                        "v6.4.0": True,
479                        "v6.4.1": True
480                    }
481                },
482                "rule_id": {
483                    "type": "integer",
484                    "revisions": {
485                        "v6.4.4": True,
486                        "v7.0.0": True,
487                        "v6.4.0": True,
488                        "v6.4.1": True
489                    }
490                }
491            },
492            "revisions": {
493                "v6.4.4": True,
494                "v7.0.0": True,
495                "v6.4.0": True,
496                "v6.4.1": True
497            }
498        },
499        "name": {
500            "type": "string",
501            "revisions": {
502                "v6.4.4": True,
503                "v7.0.0": True,
504                "v6.4.0": True,
505                "v6.4.1": True
506            }
507        },
508        "layer3_ipv4_rules": {
509            "type": "list",
510            "children": {
511                "comment": {
512                    "type": "string",
513                    "revisions": {
514                        "v6.4.4": True,
515                        "v7.0.0": True,
516                        "v6.4.0": True,
517                        "v6.4.1": True
518                    }
519                },
520                "dstaddr": {
521                    "type": "string",
522                    "revisions": {
523                        "v6.4.4": True,
524                        "v7.0.0": True,
525                        "v6.4.0": True,
526                        "v6.4.1": True
527                    }
528                },
529                "protocol": {
530                    "type": "integer",
531                    "revisions": {
532                        "v6.4.4": True,
533                        "v7.0.0": True,
534                        "v6.4.0": True,
535                        "v6.4.1": True
536                    }
537                },
538                "srcport": {
539                    "type": "integer",
540                    "revisions": {
541                        "v6.4.4": True,
542                        "v7.0.0": True,
543                        "v6.4.0": True,
544                        "v6.4.1": True
545                    }
546                },
547                "srcaddr": {
548                    "type": "string",
549                    "revisions": {
550                        "v6.4.4": True,
551                        "v7.0.0": True,
552                        "v6.4.0": True,
553                        "v6.4.1": True
554                    }
555                },
556                "action": {
557                    "type": "string",
558                    "options": [
559                        {
560                            "value": "allow",
561                            "revisions": {
562                                "v6.4.4": True,
563                                "v7.0.0": True,
564                                "v6.4.0": True,
565                                "v6.4.1": True
566                            }
567                        },
568                        {
569                            "value": "deny",
570                            "revisions": {
571                                "v6.4.4": True,
572                                "v7.0.0": True,
573                                "v6.4.0": True,
574                                "v6.4.1": True
575                            }
576                        }
577                    ],
578                    "revisions": {
579                        "v6.4.4": True,
580                        "v7.0.0": True,
581                        "v6.4.0": True,
582                        "v6.4.1": True
583                    }
584                },
585                "dstport": {
586                    "type": "integer",
587                    "revisions": {
588                        "v6.4.4": True,
589                        "v7.0.0": True,
590                        "v6.4.0": True,
591                        "v6.4.1": True
592                    }
593                },
594                "rule_id": {
595                    "type": "integer",
596                    "revisions": {
597                        "v6.4.4": True,
598                        "v7.0.0": True,
599                        "v6.4.0": True,
600                        "v6.4.1": True
601                    }
602                }
603            },
604            "revisions": {
605                "v6.4.4": True,
606                "v7.0.0": True,
607                "v6.4.0": True,
608                "v6.4.1": True
609            }
610        }
611    },
612    "revisions": {
613        "v6.4.4": True,
614        "v7.0.0": True,
615        "v6.4.0": True,
616        "v6.4.1": True
617    }
618}
619
620
621def main():
622    module_spec = schema_to_module_spec(versioned_schema)
623    mkeyname = 'name'
624    fields = {
625        "access_token": {"required": False, "type": "str", "no_log": True},
626        "enable_log": {"required": False, "type": bool},
627        "vdom": {"required": False, "type": "str", "default": "root"},
628        "state": {"required": True, "type": "str",
629                  "choices": ["present", "absent"]},
630        "wireless_controller_access_control_list": {
631            "required": False, "type": "dict", "default": None,
632            "options": {
633            }
634        }
635    }
636    for attribute_name in module_spec['options']:
637        fields["wireless_controller_access_control_list"]['options'][attribute_name] = module_spec['options'][attribute_name]
638        if mkeyname and mkeyname == attribute_name:
639            fields["wireless_controller_access_control_list"]['options'][attribute_name]['required'] = True
640
641    check_legacy_fortiosapi()
642    module = AnsibleModule(argument_spec=fields,
643                           supports_check_mode=True)
644
645    versions_check_result = None
646    if module._socket_path:
647        connection = Connection(module._socket_path)
648        if 'access_token' in module.params:
649            connection.set_option('access_token', module.params['access_token'])
650
651        if 'enable_log' in module.params:
652            connection.set_option('enable_log', module.params['enable_log'])
653        else:
654            connection.set_option('enable_log', False)
655        fos = FortiOSHandler(connection, module, mkeyname)
656        versions_check_result = check_schema_versioning(fos, versioned_schema, "wireless_controller_access_control_list")
657
658        is_error, has_changed, result = fortios_wireless_controller(module.params, fos, module.check_mode)
659
660    else:
661        module.fail_json(**FAIL_SOCKET_MSG)
662
663    if versions_check_result and versions_check_result['matched'] is False:
664        module.warn("Ansible has detected version mismatch between FortOS system and your playbook, see more details by specifying option -vvv")
665
666    if not is_error:
667        if versions_check_result and versions_check_result['matched'] is False:
668            module.exit_json(changed=has_changed, version_check_warning=versions_check_result, meta=result)
669        else:
670            module.exit_json(changed=has_changed, meta=result)
671    else:
672        if versions_check_result and versions_check_result['matched'] is False:
673            module.fail_json(msg="Error in repo", version_check_warning=versions_check_result, meta=result)
674        else:
675            module.fail_json(msg="Error in repo", meta=result)
676
677
678if __name__ == '__main__':
679    main()
680