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