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
27short_description: Show Internet Service application 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 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:
76        description:
77            - Show Internet Service application.
78        default: null
79        type: dict
80        suboptions:
81            database:
82                description:
83                    - Database name this Internet Service belongs to.
84                type: str
85                choices:
86                    - isdb
87                    - irdb
88            direction:
89                description:
90                    - How this service may be used in a firewall policy (source, destination or both).
91                type: str
92                choices:
93                    - src
94                    - dst
95                    - both
96            entry:
97                description:
98                    - Entries in the Internet Service database.
99                type: list
100                suboptions:
101                    id:
102                        description:
103                            - Entry ID.
104                        required: true
105                        type: int
106                    ip_number:
107                        description:
108                            - Total number of IP addresses.
109                        type: int
110                    ip_range_number:
111                        description:
112                            - Total number of IP ranges.
113                        type: int
114                    port:
115                        description:
116                            - Integer value for the TCP/IP port (0 - 65535).
117                        type: int
118                    protocol:
119                        description:
120                            - Integer value for the protocol type as defined by IANA (0 - 255).
121                        type: int
122            extra_ip_range_number:
123                description:
124                    - Extra number of IP ranges.
125                type: int
126            icon_id:
127                description:
128                    - Icon ID of Internet Service.
129                type: int
130            id:
131                description:
132                    - Internet Service ID.
133                required: true
134                type: int
135            ip_number:
136                description:
137                    - Total number of IP addresses.
138                type: int
139            ip_range_number:
140                description:
141                    - Number of IP ranges.
142                type: int
143            name:
144                description:
145                    - Internet Service name.
146                type: str
147            obsolete:
148                description:
149                    - Indicates whether the Internet Service can be used.
150                type: int
151            offset:
152                description:
153                    - Offset of Internet Service ID.
154                type: int
155            reputation:
156                description:
157                    - Reputation level of the Internet Service.
158                type: int
159            singularity:
160                description:
161                    - Singular level of the Internet Service.
162                type: int
163            sld_id:
164                description:
165                    - Second Level Domain.
166                type: int
167'''
168
169EXAMPLES = '''
170- hosts: fortigates
171  collections:
172    - fortinet.fortios
173  connection: httpapi
174  vars:
175   vdom: "root"
176   ansible_httpapi_use_ssl: yes
177   ansible_httpapi_validate_certs: no
178   ansible_httpapi_port: 443
179  tasks:
180  - name: Show Internet Service application.
181    fortios_firewall_internet_service:
182      vdom:  "{{ vdom }}"
183      state: "present"
184      access_token: "<your_own_value>"
185      firewall_internet_service:
186        database: "isdb"
187        direction: "src"
188        entry:
189         -
190            id:  "6"
191            ip_number: "7"
192            ip_range_number: "8"
193            port: "9"
194            protocol: "10"
195        extra_ip_range_number: "11"
196        icon_id: "12"
197        id:  "13"
198        ip_number: "14"
199        ip_range_number: "15"
200        name: "default_name_16"
201        obsolete: "17"
202        offset: "18"
203        reputation: "19"
204        singularity: "20"
205        sld_id: "21"
206
207'''
208
209RETURN = '''
210build:
211  description: Build number of the fortigate image
212  returned: always
213  type: str
214  sample: '1547'
215http_method:
216  description: Last method used to provision the content into FortiGate
217  returned: always
218  type: str
219  sample: 'PUT'
220http_status:
221  description: Last result given by FortiGate on last operation applied
222  returned: always
223  type: str
224  sample: "200"
225mkey:
226  description: Master key (id) used in the last call to FortiGate
227  returned: success
228  type: str
229  sample: "id"
230name:
231  description: Name of the table used to fulfill the request
232  returned: always
233  type: str
234  sample: "urlfilter"
235path:
236  description: Path of the table used to fulfill the request
237  returned: always
238  type: str
239  sample: "webfilter"
240revision:
241  description: Internal revision number
242  returned: always
243  type: str
244  sample: "17.0.2.10658"
245serial:
246  description: Serial number of the unit
247  returned: always
248  type: str
249  sample: "FGVMEVYYQT3AB5352"
250status:
251  description: Indication of the operation's result
252  returned: always
253  type: str
254  sample: "success"
255vdom:
256  description: Virtual domain used
257  returned: always
258  type: str
259  sample: "root"
260version:
261  description: Version of the FortiGate
262  returned: always
263  type: str
264  sample: "v5.6.3"
265
266'''
267from ansible.module_utils.basic import AnsibleModule
268from ansible.module_utils.connection import Connection
269from ansible_collections.fortinet.fortios.plugins.module_utils.fortios.fortios import FortiOSHandler
270from ansible_collections.fortinet.fortios.plugins.module_utils.fortios.fortios import check_legacy_fortiosapi
271from ansible_collections.fortinet.fortios.plugins.module_utils.fortios.fortios import schema_to_module_spec
272from ansible_collections.fortinet.fortios.plugins.module_utils.fortios.fortios import check_schema_versioning
273from ansible_collections.fortinet.fortios.plugins.module_utils.fortimanager.common import FAIL_SOCKET_MSG
274from ansible_collections.fortinet.fortios.plugins.module_utils.fortios.comparison import is_same_comparison
275from ansible_collections.fortinet.fortios.plugins.module_utils.fortios.comparison import serialize
276
277
278def filter_firewall_internet_service_data(json):
279    option_list = ['database', 'direction', 'entry',
280                   'extra_ip_range_number', 'icon_id', 'id',
281                   'ip_number', 'ip_range_number', 'name',
282                   'obsolete', 'offset', 'reputation',
283                   'singularity', 'sld_id']
284    dictionary = {}
285
286    for attribute in option_list:
287        if attribute in json and json[attribute] is not None:
288            dictionary[attribute] = json[attribute]
289
290    return dictionary
291
292
293def underscore_to_hyphen(data):
294    if isinstance(data, list):
295        for i, elem in enumerate(data):
296            data[i] = underscore_to_hyphen(elem)
297    elif isinstance(data, dict):
298        new_data = {}
299        for k, v in data.items():
300            new_data[k.replace('_', '-')] = underscore_to_hyphen(v)
301        data = new_data
302
303    return data
304
305
306def firewall_internet_service(data, fos, check_mode=False):
307
308    vdom = data['vdom']
309
310    state = data['state']
311
312    firewall_internet_service_data = data['firewall_internet_service']
313    filtered_data = underscore_to_hyphen(filter_firewall_internet_service_data(firewall_internet_service_data))
314
315    # check_mode starts from here
316    if check_mode:
317        mkey = fos.get_mkey('system', 'interface', filtered_data, vdom=vdom)
318        current_data = fos.get('system', 'interface', vdom=vdom, mkey=mkey)
319        is_existed = current_data and current_data.get('http_status') == 200 \
320            and isinstance(current_data.get('results'), list) \
321            and len(current_data['results']) > 0
322
323        # 2. if it exists and the state is 'present' then compare current settings with desired
324        if state == 'present' or state is True:
325            if mkey is None:
326                return False, True, filtered_data
327
328            # if mkey exists then compare each other
329            # record exits and they're matched or not
330            if is_existed:
331                is_same = is_same_comparison(
332                    serialize(current_data['results'][0]), serialize(filtered_data))
333                return False, not is_same, filtered_data
334
335            # record does not exist
336            return False, True, filtered_data
337
338        if state == 'absent':
339            if mkey is None:
340                return False, False, filtered_data
341
342            if is_existed:
343                return False, True, filtered_data
344            return False, False, filtered_data
345
346        return True, False, {'reason: ': 'Must provide state parameter'}
347
348    if state == "present" or state is True:
349        return fos.set('firewall',
350                       'internet-service',
351                       data=filtered_data,
352                       vdom=vdom)
353
354    elif state == "absent":
355        return fos.delete('firewall',
356                          'internet-service',
357                          mkey=filtered_data['id'],
358                          vdom=vdom)
359    else:
360        fos._module.fail_json(msg='state must be present or absent!')
361
362
363def is_successful_status(status):
364    return status['status'] == "success" or \
365        status['http_method'] == "DELETE" and status['http_status'] == 404
366
367
368def fortios_firewall(data, fos, check_mode):
369
370    if data['firewall_internet_service']:
371        resp = firewall_internet_service(data, fos, check_mode)
372    else:
373        fos._module.fail_json(msg='missing task body: %s' % ('firewall_internet_service'))
374    if check_mode:
375        return resp
376    return not is_successful_status(resp), \
377        resp['status'] == "success" and \
378        (resp['revision_changed'] if 'revision_changed' in resp else True), \
379        resp
380
381
382versioned_schema = {
383    "type": "list",
384    "children": {
385        "direction": {
386            "type": "string",
387            "options": [
388                {
389                    "value": "src",
390                    "revisions": {
391                        "v6.0.0": True,
392                        "v7.0.0": True,
393                        "v6.0.5": True,
394                        "v6.4.4": True,
395                        "v6.4.0": True,
396                        "v6.4.1": True,
397                        "v6.2.0": True,
398                        "v6.2.3": True,
399                        "v6.2.5": True,
400                        "v6.2.7": True,
401                        "v6.0.11": True
402                    }
403                },
404                {
405                    "value": "dst",
406                    "revisions": {
407                        "v6.0.0": True,
408                        "v7.0.0": True,
409                        "v6.0.5": True,
410                        "v6.4.4": True,
411                        "v6.4.0": True,
412                        "v6.4.1": True,
413                        "v6.2.0": True,
414                        "v6.2.3": True,
415                        "v6.2.5": True,
416                        "v6.2.7": True,
417                        "v6.0.11": True
418                    }
419                },
420                {
421                    "value": "both",
422                    "revisions": {
423                        "v6.0.0": True,
424                        "v7.0.0": True,
425                        "v6.0.5": True,
426                        "v6.4.4": True,
427                        "v6.4.0": True,
428                        "v6.4.1": True,
429                        "v6.2.0": True,
430                        "v6.2.3": True,
431                        "v6.2.5": True,
432                        "v6.2.7": True,
433                        "v6.0.11": True
434                    }
435                }
436            ],
437            "revisions": {
438                "v6.0.0": True,
439                "v7.0.0": True,
440                "v6.0.5": 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                "v6.0.11": True
449            }
450        },
451        "name": {
452            "type": "string",
453            "revisions": {
454                "v6.0.0": True,
455                "v7.0.0": True,
456                "v6.0.5": True,
457                "v6.4.4": True,
458                "v6.4.0": True,
459                "v6.4.1": True,
460                "v6.2.0": True,
461                "v6.2.3": True,
462                "v6.2.5": True,
463                "v6.2.7": True,
464                "v6.0.11": True
465            }
466        },
467        "database": {
468            "type": "string",
469            "options": [
470                {
471                    "value": "isdb",
472                    "revisions": {
473                        "v6.0.0": True,
474                        "v7.0.0": True,
475                        "v6.0.5": True,
476                        "v6.4.4": True,
477                        "v6.4.0": True,
478                        "v6.4.1": True,
479                        "v6.2.0": True,
480                        "v6.2.3": True,
481                        "v6.2.5": True,
482                        "v6.2.7": True,
483                        "v6.0.11": True
484                    }
485                },
486                {
487                    "value": "irdb",
488                    "revisions": {
489                        "v6.0.0": True,
490                        "v7.0.0": True,
491                        "v6.0.5": True,
492                        "v6.4.4": True,
493                        "v6.4.0": True,
494                        "v6.4.1": True,
495                        "v6.2.0": True,
496                        "v6.2.3": True,
497                        "v6.2.5": True,
498                        "v6.2.7": True,
499                        "v6.0.11": True
500                    }
501                }
502            ],
503            "revisions": {
504                "v6.0.0": True,
505                "v7.0.0": True,
506                "v6.0.5": True,
507                "v6.4.4": True,
508                "v6.4.0": True,
509                "v6.4.1": True,
510                "v6.2.0": True,
511                "v6.2.3": True,
512                "v6.2.5": True,
513                "v6.2.7": True,
514                "v6.0.11": True
515            }
516        },
517        "extra_ip_range_number": {
518            "type": "integer",
519            "revisions": {
520                "v7.0.0": True,
521                "v6.4.4": True,
522                "v6.4.0": True,
523                "v6.4.1": True,
524                "v6.2.0": True,
525                "v6.2.3": True,
526                "v6.2.5": True,
527                "v6.2.7": True
528            }
529        },
530        "icon_id": {
531            "type": "integer",
532            "revisions": {
533                "v6.0.0": True,
534                "v7.0.0": True,
535                "v6.0.5": 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                "v6.0.11": True
544            }
545        },
546        "sld_id": {
547            "type": "integer",
548            "revisions": {
549                "v6.0.0": True,
550                "v7.0.0": False,
551                "v6.0.5": True,
552                "v6.4.4": False,
553                "v6.4.0": False,
554                "v6.4.1": False,
555                "v6.2.0": True,
556                "v6.2.3": True,
557                "v6.2.5": True,
558                "v6.2.7": True,
559                "v6.0.11": True
560            }
561        },
562        "obsolete": {
563            "type": "integer",
564            "revisions": {
565                "v7.0.0": True,
566                "v6.4.4": True,
567                "v6.4.0": True,
568                "v6.4.1": True,
569                "v6.2.0": True,
570                "v6.2.3": True,
571                "v6.2.5": True,
572                "v6.2.7": True
573            }
574        },
575        "reputation": {
576            "type": "integer",
577            "revisions": {
578                "v6.0.0": True,
579                "v7.0.0": False,
580                "v6.0.5": True,
581                "v6.4.4": False,
582                "v6.4.0": False,
583                "v6.4.1": False,
584                "v6.2.0": True,
585                "v6.2.3": True,
586                "v6.2.5": True,
587                "v6.2.7": True,
588                "v6.0.11": True
589            }
590        },
591        "ip_number": {
592            "type": "integer",
593            "revisions": {
594                "v7.0.0": True,
595                "v6.4.4": True,
596                "v6.4.0": True,
597                "v6.4.1": True,
598                "v6.2.0": True,
599                "v6.2.3": True,
600                "v6.2.5": True,
601                "v6.2.7": True
602            }
603        },
604        "offset": {
605            "type": "integer",
606            "revisions": {
607                "v6.0.0": True,
608                "v7.0.0": False,
609                "v6.0.5": True,
610                "v6.4.4": False,
611                "v6.4.0": False,
612                "v6.4.1": False,
613                "v6.2.0": False,
614                "v6.2.3": False,
615                "v6.2.5": False,
616                "v6.2.7": False,
617                "v6.0.11": True
618            }
619        },
620        "entry": {
621            "type": "list",
622            "children": {
623                "protocol": {
624                    "type": "integer",
625                    "revisions": {
626                        "v6.0.11": True,
627                        "v6.0.0": True,
628                        "v6.0.5": True
629                    }
630                },
631                "ip_range_number": {
632                    "type": "integer",
633                    "revisions": {
634                        "v6.0.11": True,
635                        "v6.0.0": True,
636                        "v6.0.5": True
637                    }
638                },
639                "id": {
640                    "type": "integer",
641                    "revisions": {
642                        "v6.0.11": True,
643                        "v6.0.0": True,
644                        "v6.0.5": True
645                    }
646                },
647                "ip_number": {
648                    "type": "integer",
649                    "revisions": {
650                        "v6.0.11": True,
651                        "v6.0.0": True,
652                        "v6.0.5": True
653                    }
654                },
655                "port": {
656                    "type": "integer",
657                    "revisions": {
658                        "v6.0.11": True,
659                        "v6.0.0": True,
660                        "v6.0.5": True
661                    }
662                }
663            },
664            "revisions": {
665                "v6.0.0": True,
666                "v7.0.0": False,
667                "v6.0.5": True,
668                "v6.4.4": False,
669                "v6.4.0": False,
670                "v6.4.1": False,
671                "v6.2.0": False,
672                "v6.2.3": False,
673                "v6.2.5": False,
674                "v6.2.7": False,
675                "v6.0.11": True
676            }
677        },
678        "singularity": {
679            "type": "integer",
680            "revisions": {
681                "v7.0.0": True,
682                "v6.4.4": True,
683                "v6.4.0": True,
684                "v6.4.1": True,
685                "v6.2.0": True,
686                "v6.2.3": True,
687                "v6.2.5": True,
688                "v6.2.7": True
689            }
690        },
691        "ip_range_number": {
692            "type": "integer",
693            "revisions": {
694                "v7.0.0": True,
695                "v6.4.4": True,
696                "v6.4.0": True,
697                "v6.4.1": True,
698                "v6.2.0": True,
699                "v6.2.3": True,
700                "v6.2.5": True,
701                "v6.2.7": True
702            }
703        },
704        "id": {
705            "type": "integer",
706            "revisions": {
707                "v6.0.0": True,
708                "v7.0.0": True,
709                "v6.0.5": True,
710                "v6.4.4": True,
711                "v6.4.0": True,
712                "v6.4.1": True,
713                "v6.2.0": True,
714                "v6.2.3": True,
715                "v6.2.5": True,
716                "v6.2.7": True,
717                "v6.0.11": True
718            }
719        }
720    },
721    "revisions": {
722        "v6.0.0": True,
723        "v7.0.0": True,
724        "v6.0.5": True,
725        "v6.4.4": True,
726        "v6.4.0": True,
727        "v6.4.1": True,
728        "v6.2.0": True,
729        "v6.2.3": True,
730        "v6.2.5": True,
731        "v6.2.7": True,
732        "v6.0.11": True
733    }
734}
735
736
737def main():
738    module_spec = schema_to_module_spec(versioned_schema)
739    mkeyname = 'id'
740    fields = {
741        "access_token": {"required": False, "type": "str", "no_log": True},
742        "enable_log": {"required": False, "type": bool},
743        "vdom": {"required": False, "type": "str", "default": "root"},
744        "state": {"required": True, "type": "str",
745                  "choices": ["present", "absent"]},
746        "firewall_internet_service": {
747            "required": False, "type": "dict", "default": None,
748            "options": {
749            }
750        }
751    }
752    for attribute_name in module_spec['options']:
753        fields["firewall_internet_service"]['options'][attribute_name] = module_spec['options'][attribute_name]
754        if mkeyname and mkeyname == attribute_name:
755            fields["firewall_internet_service"]['options'][attribute_name]['required'] = True
756
757    check_legacy_fortiosapi()
758    module = AnsibleModule(argument_spec=fields,
759                           supports_check_mode=True)
760
761    versions_check_result = None
762    if module._socket_path:
763        connection = Connection(module._socket_path)
764        if 'access_token' in module.params:
765            connection.set_option('access_token', module.params['access_token'])
766
767        if 'enable_log' in module.params:
768            connection.set_option('enable_log', module.params['enable_log'])
769        else:
770            connection.set_option('enable_log', False)
771        fos = FortiOSHandler(connection, module, mkeyname)
772        versions_check_result = check_schema_versioning(fos, versioned_schema, "firewall_internet_service")
773
774        is_error, has_changed, result = fortios_firewall(module.params, fos, module.check_mode)
775
776    else:
777        module.fail_json(**FAIL_SOCKET_MSG)
778
779    if versions_check_result and versions_check_result['matched'] is False:
780        module.warn("Ansible has detected version mismatch between FortOS system and your playbook, see more details by specifying option -vvv")
781
782    if not is_error:
783        if versions_check_result and versions_check_result['matched'] is False:
784            module.exit_json(changed=has_changed, version_check_warning=versions_check_result, meta=result)
785        else:
786            module.exit_json(changed=has_changed, meta=result)
787    else:
788        if versions_check_result and versions_check_result['matched'] is False:
789            module.fail_json(msg="Error in repo", version_check_warning=versions_check_result, meta=result)
790        else:
791            module.fail_json(msg="Error in repo", meta=result)
792
793
794if __name__ == '__main__':
795    main()
796