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_ips_urlfilter_dns
27short_description: Configure IPS URL filter DNS servers 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 ips_urlfilter_dns 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_ips_urlfilter_dns:
76        description:
77            - Configure IPS URL filter DNS servers.
78        default: null
79        type: dict
80        suboptions:
81            address:
82                description:
83                    - DNS server IP address.
84                required: true
85                type: str
86            ipv6_capability:
87                description:
88                    - Enable/disable this server for IPv6 queries.
89                type: str
90                choices:
91                    - enable
92                    - disable
93            status:
94                description:
95                    - Enable/disable using this DNS server for IPS URL filter DNS queries.
96                type: str
97                choices:
98                    - enable
99                    - disable
100'''
101
102EXAMPLES = '''
103- hosts: fortigates
104  collections:
105    - fortinet.fortios
106  connection: httpapi
107  vars:
108   vdom: "root"
109   ansible_httpapi_use_ssl: yes
110   ansible_httpapi_validate_certs: no
111   ansible_httpapi_port: 443
112  tasks:
113  - name: Configure IPS URL filter DNS servers.
114    fortios_system_ips_urlfilter_dns:
115      vdom:  "{{ vdom }}"
116      state: "present"
117      access_token: "<your_own_value>"
118      system_ips_urlfilter_dns:
119        address: "<your_own_value>"
120        ipv6_capability: "enable"
121        status: "enable"
122
123'''
124
125RETURN = '''
126build:
127  description: Build number of the fortigate image
128  returned: always
129  type: str
130  sample: '1547'
131http_method:
132  description: Last method used to provision the content into FortiGate
133  returned: always
134  type: str
135  sample: 'PUT'
136http_status:
137  description: Last result given by FortiGate on last operation applied
138  returned: always
139  type: str
140  sample: "200"
141mkey:
142  description: Master key (id) used in the last call to FortiGate
143  returned: success
144  type: str
145  sample: "id"
146name:
147  description: Name of the table used to fulfill the request
148  returned: always
149  type: str
150  sample: "urlfilter"
151path:
152  description: Path of the table used to fulfill the request
153  returned: always
154  type: str
155  sample: "webfilter"
156revision:
157  description: Internal revision number
158  returned: always
159  type: str
160  sample: "17.0.2.10658"
161serial:
162  description: Serial number of the unit
163  returned: always
164  type: str
165  sample: "FGVMEVYYQT3AB5352"
166status:
167  description: Indication of the operation's result
168  returned: always
169  type: str
170  sample: "success"
171vdom:
172  description: Virtual domain used
173  returned: always
174  type: str
175  sample: "root"
176version:
177  description: Version of the FortiGate
178  returned: always
179  type: str
180  sample: "v5.6.3"
181
182'''
183from ansible.module_utils.basic import AnsibleModule
184from ansible.module_utils.connection import Connection
185from ansible_collections.fortinet.fortios.plugins.module_utils.fortios.fortios import FortiOSHandler
186from ansible_collections.fortinet.fortios.plugins.module_utils.fortios.fortios import check_legacy_fortiosapi
187from ansible_collections.fortinet.fortios.plugins.module_utils.fortios.fortios import schema_to_module_spec
188from ansible_collections.fortinet.fortios.plugins.module_utils.fortios.fortios import check_schema_versioning
189from ansible_collections.fortinet.fortios.plugins.module_utils.fortimanager.common import FAIL_SOCKET_MSG
190from ansible_collections.fortinet.fortios.plugins.module_utils.fortios.comparison import is_same_comparison
191from ansible_collections.fortinet.fortios.plugins.module_utils.fortios.comparison import serialize
192
193
194def filter_system_ips_urlfilter_dns_data(json):
195    option_list = ['address', 'ipv6_capability', 'status']
196    dictionary = {}
197
198    for attribute in option_list:
199        if attribute in json and json[attribute] is not None:
200            dictionary[attribute] = json[attribute]
201
202    return dictionary
203
204
205def underscore_to_hyphen(data):
206    if isinstance(data, list):
207        for i, elem in enumerate(data):
208            data[i] = underscore_to_hyphen(elem)
209    elif isinstance(data, dict):
210        new_data = {}
211        for k, v in data.items():
212            new_data[k.replace('_', '-')] = underscore_to_hyphen(v)
213        data = new_data
214
215    return data
216
217
218def system_ips_urlfilter_dns(data, fos, check_mode=False):
219
220    vdom = data['vdom']
221
222    state = data['state']
223
224    system_ips_urlfilter_dns_data = data['system_ips_urlfilter_dns']
225    filtered_data = underscore_to_hyphen(filter_system_ips_urlfilter_dns_data(system_ips_urlfilter_dns_data))
226
227    # check_mode starts from here
228    if check_mode:
229        mkey = fos.get_mkey('system', 'interface', filtered_data, vdom=vdom)
230        current_data = fos.get('system', 'interface', vdom=vdom, mkey=mkey)
231        is_existed = current_data and current_data.get('http_status') == 200 \
232            and isinstance(current_data.get('results'), list) \
233            and len(current_data['results']) > 0
234
235        # 2. if it exists and the state is 'present' then compare current settings with desired
236        if state == 'present' or state is True:
237            if mkey is None:
238                return False, True, filtered_data
239
240            # if mkey exists then compare each other
241            # record exits and they're matched or not
242            if is_existed:
243                is_same = is_same_comparison(
244                    serialize(current_data['results'][0]), serialize(filtered_data))
245                return False, not is_same, filtered_data
246
247            # record does not exist
248            return False, True, filtered_data
249
250        if state == 'absent':
251            if mkey is None:
252                return False, False, filtered_data
253
254            if is_existed:
255                return False, True, filtered_data
256            return False, False, filtered_data
257
258        return True, False, {'reason: ': 'Must provide state parameter'}
259
260    if state == "present" or state is True:
261        return fos.set('system',
262                       'ips-urlfilter-dns',
263                       data=filtered_data,
264                       vdom=vdom)
265
266    elif state == "absent":
267        return fos.delete('system',
268                          'ips-urlfilter-dns',
269                          mkey=filtered_data['address'],
270                          vdom=vdom)
271    else:
272        fos._module.fail_json(msg='state must be present or absent!')
273
274
275def is_successful_status(status):
276    return status['status'] == "success" or \
277        status['http_method'] == "DELETE" and status['http_status'] == 404
278
279
280def fortios_system(data, fos, check_mode):
281
282    if data['system_ips_urlfilter_dns']:
283        resp = system_ips_urlfilter_dns(data, fos, check_mode)
284    else:
285        fos._module.fail_json(msg='missing task body: %s' % ('system_ips_urlfilter_dns'))
286    if check_mode:
287        return resp
288    return not is_successful_status(resp), \
289        resp['status'] == "success" and \
290        (resp['revision_changed'] if 'revision_changed' in resp else True), \
291        resp
292
293
294versioned_schema = {
295    "type": "list",
296    "children": {
297        "status": {
298            "type": "string",
299            "options": [
300                {
301                    "value": "enable",
302                    "revisions": {
303                        "v6.0.0": True,
304                        "v7.0.0": True,
305                        "v6.0.5": True,
306                        "v6.4.4": True,
307                        "v6.4.0": True,
308                        "v6.4.1": True,
309                        "v6.2.0": True,
310                        "v6.2.3": True,
311                        "v6.2.5": True,
312                        "v6.2.7": True,
313                        "v6.0.11": True
314                    }
315                },
316                {
317                    "value": "disable",
318                    "revisions": {
319                        "v6.0.0": True,
320                        "v7.0.0": True,
321                        "v6.0.5": True,
322                        "v6.4.4": True,
323                        "v6.4.0": True,
324                        "v6.4.1": True,
325                        "v6.2.0": True,
326                        "v6.2.3": True,
327                        "v6.2.5": True,
328                        "v6.2.7": True,
329                        "v6.0.11": True
330                    }
331                }
332            ],
333            "revisions": {
334                "v6.0.0": True,
335                "v7.0.0": True,
336                "v6.0.5": True,
337                "v6.4.4": True,
338                "v6.4.0": True,
339                "v6.4.1": True,
340                "v6.2.0": True,
341                "v6.2.3": True,
342                "v6.2.5": True,
343                "v6.2.7": True,
344                "v6.0.11": True
345            }
346        },
347        "address": {
348            "type": "string",
349            "revisions": {
350                "v6.0.0": True,
351                "v7.0.0": True,
352                "v6.0.5": True,
353                "v6.4.4": True,
354                "v6.4.0": True,
355                "v6.4.1": True,
356                "v6.2.0": True,
357                "v6.2.3": True,
358                "v6.2.5": True,
359                "v6.2.7": True,
360                "v6.0.11": True
361            }
362        },
363        "ipv6_capability": {
364            "type": "string",
365            "options": [
366                {
367                    "value": "enable",
368                    "revisions": {
369                        "v6.0.0": True,
370                        "v7.0.0": True,
371                        "v6.0.5": True,
372                        "v6.4.4": True,
373                        "v6.4.0": True,
374                        "v6.4.1": True,
375                        "v6.2.0": True,
376                        "v6.2.3": True,
377                        "v6.2.5": True,
378                        "v6.2.7": True,
379                        "v6.0.11": True
380                    }
381                },
382                {
383                    "value": "disable",
384                    "revisions": {
385                        "v6.0.0": True,
386                        "v7.0.0": True,
387                        "v6.0.5": True,
388                        "v6.4.4": True,
389                        "v6.4.0": True,
390                        "v6.4.1": True,
391                        "v6.2.0": True,
392                        "v6.2.3": True,
393                        "v6.2.5": True,
394                        "v6.2.7": True,
395                        "v6.0.11": True
396                    }
397                }
398            ],
399            "revisions": {
400                "v6.0.0": True,
401                "v7.0.0": True,
402                "v6.0.5": 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                "v6.0.11": True
411            }
412        }
413    },
414    "revisions": {
415        "v6.0.0": True,
416        "v7.0.0": True,
417        "v6.0.5": True,
418        "v6.4.4": True,
419        "v6.4.0": True,
420        "v6.4.1": True,
421        "v6.2.0": True,
422        "v6.2.3": True,
423        "v6.2.5": True,
424        "v6.2.7": True,
425        "v6.0.11": True
426    }
427}
428
429
430def main():
431    module_spec = schema_to_module_spec(versioned_schema)
432    mkeyname = 'address'
433    fields = {
434        "access_token": {"required": False, "type": "str", "no_log": True},
435        "enable_log": {"required": False, "type": bool},
436        "vdom": {"required": False, "type": "str", "default": "root"},
437        "state": {"required": True, "type": "str",
438                  "choices": ["present", "absent"]},
439        "system_ips_urlfilter_dns": {
440            "required": False, "type": "dict", "default": None,
441            "options": {
442            }
443        }
444    }
445    for attribute_name in module_spec['options']:
446        fields["system_ips_urlfilter_dns"]['options'][attribute_name] = module_spec['options'][attribute_name]
447        if mkeyname and mkeyname == attribute_name:
448            fields["system_ips_urlfilter_dns"]['options'][attribute_name]['required'] = True
449
450    check_legacy_fortiosapi()
451    module = AnsibleModule(argument_spec=fields,
452                           supports_check_mode=True)
453
454    versions_check_result = None
455    if module._socket_path:
456        connection = Connection(module._socket_path)
457        if 'access_token' in module.params:
458            connection.set_option('access_token', module.params['access_token'])
459
460        if 'enable_log' in module.params:
461            connection.set_option('enable_log', module.params['enable_log'])
462        else:
463            connection.set_option('enable_log', False)
464        fos = FortiOSHandler(connection, module, mkeyname)
465        versions_check_result = check_schema_versioning(fos, versioned_schema, "system_ips_urlfilter_dns")
466
467        is_error, has_changed, result = fortios_system(module.params, fos, module.check_mode)
468
469    else:
470        module.fail_json(**FAIL_SOCKET_MSG)
471
472    if versions_check_result and versions_check_result['matched'] is False:
473        module.warn("Ansible has detected version mismatch between FortOS system and your playbook, see more details by specifying option -vvv")
474
475    if not is_error:
476        if versions_check_result and versions_check_result['matched'] is False:
477            module.exit_json(changed=has_changed, version_check_warning=versions_check_result, meta=result)
478        else:
479            module.exit_json(changed=has_changed, meta=result)
480    else:
481        if versions_check_result and versions_check_result['matched'] is False:
482            module.fail_json(msg="Error in repo", version_check_warning=versions_check_result, meta=result)
483        else:
484            module.fail_json(msg="Error in repo", meta=result)
485
486
487if __name__ == '__main__':
488    main()
489