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