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