1#!/usr/bin/python
2#
3# This file is part of Ansible
4#
5# Ansible 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# Ansible 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 Ansible.  If not, see <http://www.gnu.org/licenses/>.
17#
18
19from __future__ import absolute_import, division, print_function
20__metaclass__ = type
21
22ANSIBLE_METADATA = {
23    "metadata_version": "1.1",
24    "status": ["preview"],
25    "supported_by": "community"
26}
27
28DOCUMENTATION = '''
29---
30module: fmgr_fwpol_package
31version_added: "2.8"
32notes:
33    - Full Documentation at U(https://ftnt-ansible-docs.readthedocs.io/en/latest/).
34author:
35    - Luke Weighall (@lweighall)
36    - Andrew Welsh (@Ghilli3)
37    - Jim Huber (@p4r4n0y1ng)
38short_description: Manages FortiManager Firewall Policies Packages.
39description:
40  -  Manages FortiManager Firewall Policies Packages. Policy Packages contain one or more Firewall Policies/Rules and
41     are distritbuted via FortiManager to Fortigates.
42  -  This module controls the creation/edit/delete/assign of these packages.
43
44options:
45  adom:
46    description:
47      - The ADOM the configuration should belong to.
48    required: false
49    default: root
50
51  mode:
52    description:
53      - Sets one of three modes for managing the object.
54    choices: ['add', 'set', 'delete']
55    default: add
56
57  name:
58    description:
59      - Name of the FortiManager package or folder.
60    required: True
61
62  object_type:
63    description:
64      - Are we managing packages or folders, or installing packages?
65    required: True
66    choices: ['pkg','folder','install']
67
68  package_folder:
69    description:
70      - Name of the folder you want to put the package into.
71    required: false
72
73  central_nat:
74    description:
75      - Central NAT setting.
76    required: false
77    choices: ['enable', 'disable']
78    default: disable
79
80  fwpolicy_implicit_log:
81    description:
82      - Implicit Log setting for all IPv4 policies in package.
83    required: false
84    choices: ['enable', 'disable']
85    default: disable
86
87  fwpolicy6_implicit_log:
88    description:
89      - Implicit Log setting for all IPv6 policies in package.
90    required: false
91    choices: ['enable', 'disable']
92    default: disable
93
94  inspection_mode:
95    description:
96      - Inspection mode setting for the policies flow or proxy.
97    required: false
98    choices: ['flow', 'proxy']
99    default: flow
100
101  ngfw_mode:
102    description:
103      - NGFW mode setting for the policies flow or proxy.
104    required: false
105    choices: ['profile-based', 'policy-based']
106    default: profile-based
107
108  ssl_ssh_profile:
109    description:
110      - if policy-based ngfw-mode, refer to firewall ssl-ssh-profile.
111    required: false
112
113  scope_members:
114    description:
115      - The devices or scope that you want to assign this policy package to.
116    required: false
117
118  scope_members_vdom:
119    description:
120      - The members VDOM you want to assign the package to.
121    required: false
122    default: root
123
124  parent_folder:
125    description:
126      - The parent folder name you want to add this object under.
127    required: false
128
129'''
130
131
132EXAMPLES = '''
133- name: CREATE BASIC POLICY PACKAGE
134  fmgr_fwpol_package:
135    adom: "ansible"
136    mode: "add"
137    name: "testPackage"
138    object_type: "pkg"
139
140- name: ADD PACKAGE WITH TARGETS
141  fmgr_fwpol_package:
142    mode: "add"
143    adom: "ansible"
144    name: "ansibleTestPackage1"
145    object_type: "pkg"
146    inspection_mode: "flow"
147    ngfw_mode: "profile-based"
148    scope_members: "seattle-fgt02, seattle-fgt03"
149
150- name: ADD FOLDER
151  fmgr_fwpol_package:
152    mode: "add"
153    adom: "ansible"
154    name: "ansibleTestFolder1"
155    object_type: "folder"
156
157- name: ADD PACKAGE INTO PARENT FOLDER
158  fmgr_fwpol_package:
159    mode: "set"
160    adom: "ansible"
161    name: "ansibleTestPackage2"
162    object_type: "pkg"
163    parent_folder: "ansibleTestFolder1"
164
165- name: ADD FOLDER INTO PARENT FOLDER
166  fmgr_fwpol_package:
167    mode: "set"
168    adom: "ansible"
169    name: "ansibleTestFolder2"
170    object_type: "folder"
171    parent_folder: "ansibleTestFolder1"
172
173- name: INSTALL PACKAGE
174  fmgr_fwpol_package:
175    mode: "set"
176    adom: "ansible"
177    name: "ansibleTestPackage1"
178    object_type: "install"
179    scope_members: "seattle-fgt03, seattle-fgt02"
180
181- name: REMOVE PACKAGE
182  fmgr_fwpol_package:
183    mode: "delete"
184    adom: "ansible"
185    name: "ansibleTestPackage1"
186    object_type: "pkg"
187
188- name: REMOVE NESTED PACKAGE
189  fmgr_fwpol_package:
190    mode: "delete"
191    adom: "ansible"
192    name: "ansibleTestPackage2"
193    object_type: "pkg"
194    parent_folder: "ansibleTestFolder1"
195
196- name: REMOVE NESTED FOLDER
197  fmgr_fwpol_package:
198    mode: "delete"
199    adom: "ansible"
200    name: "ansibleTestFolder2"
201    object_type: "folder"
202    parent_folder: "ansibleTestFolder1"
203
204- name: REMOVE FOLDER
205  fmgr_fwpol_package:
206    mode: "delete"
207    adom: "ansible"
208    name: "ansibleTestFolder1"
209    object_type: "folder"
210'''
211RETURN = """
212api_result:
213  description: full API response, includes status code and message
214  returned: always
215  type: str
216"""
217
218from ansible.module_utils.basic import AnsibleModule
219from ansible.module_utils.connection import Connection
220from ansible.module_utils.network.fortimanager.fortimanager import FortiManagerHandler
221from ansible.module_utils.network.fortimanager.common import FMGBaseException
222from ansible.module_utils.network.fortimanager.common import FMGRCommon
223from ansible.module_utils.network.fortimanager.common import DEFAULT_RESULT_OBJ
224from ansible.module_utils.network.fortimanager.common import FAIL_SOCKET_MSG
225from ansible.module_utils.network.fortimanager.common import FMGRMethods
226
227
228def fmgr_fwpol_package(fmgr, paramgram):
229    """
230    This function will create FMGR Firewall Policy Packages, or delete them. It is also capable of assigning packages.
231    This function DOES NOT install the package. See the function fmgr_fwpol_package_install()
232
233    :param fmgr: The fmgr object instance from fmgr_utils.py
234    :type fmgr: class object
235    :param paramgram: The formatted dictionary of options to process
236    :type paramgram: dict
237
238    :return: The response from the FortiManager
239    :rtype: dict
240    """
241    if paramgram["mode"] in ['set', 'add']:
242        url = '/pm/pkg/adom/{adom}'.format(adom=paramgram["adom"])
243        members_list = []
244
245        # CHECK FOR SCOPE MEMBERS AND CREATE THAT DICT
246        if paramgram["scope_members"] is not None:
247            members = FMGRCommon.split_comma_strings_into_lists(paramgram["scope_members"])
248            for member in members:
249                scope_dict = {
250                    "name": member,
251                    "vdom": paramgram["scope_members_vdom"],
252                }
253                members_list.append(scope_dict)
254
255        # IF PARENT FOLDER IS NOT DEFINED
256        if paramgram["parent_folder"] is None:
257            datagram = {
258                "type": paramgram["object_type"],
259                "name": paramgram["name"],
260                "scope member": members_list,
261                "package settings": {
262                    "central-nat": paramgram["central-nat"],
263                    "fwpolicy-implicit-log": paramgram["fwpolicy-implicit-log"],
264                    "fwpolicy6-implicit-log": paramgram["fwpolicy6-implicit-log"],
265                    "inspection-mode": paramgram["inspection-mode"],
266                    "ngfw-mode": paramgram["ngfw-mode"],
267                }
268            }
269
270            if paramgram["ngfw-mode"] == "policy-based" and paramgram["ssl-ssh-profile"] is not None:
271                datagram["package settings"]["ssl-ssh-profile"] = paramgram["ssl-ssh-profile"]
272
273        # IF PARENT FOLDER IS DEFINED
274        if paramgram["parent_folder"] is not None:
275            datagram = {
276                "type": "folder",
277                "name": paramgram["parent_folder"],
278                "subobj": [{
279                    "name": paramgram["name"],
280                    "scope member": members_list,
281                    "type": "pkg",
282                    "package settings": {
283                        "central-nat": paramgram["central-nat"],
284                        "fwpolicy-implicit-log": paramgram["fwpolicy-implicit-log"],
285                        "fwpolicy6-implicit-log": paramgram["fwpolicy6-implicit-log"],
286                        "inspection-mode": paramgram["inspection-mode"],
287                        "ngfw-mode": paramgram["ngfw-mode"],
288                    }
289                }]
290            }
291
292    # NORMAL DELETE NO PARENT
293    if paramgram["mode"] == "delete" and paramgram["parent_folder"] is None:
294        datagram = {
295            "name": paramgram["name"]
296        }
297        # SET DELETE URL
298        url = '/pm/pkg/adom/{adom}/{name}'.format(adom=paramgram["adom"], name=paramgram["name"])
299
300    # DELETE WITH PARENT
301    if paramgram["mode"] == "delete" and paramgram["parent_folder"] is not None:
302        datagram = {
303            "name": paramgram["name"]
304        }
305        # SET DELETE URL
306        url = '/pm/pkg/adom/{adom}/{parent_folder}/{name}'.format(adom=paramgram["adom"],
307                                                                  name=paramgram["name"],
308                                                                  parent_folder=paramgram["parent_folder"])
309
310    response = fmgr.process_request(url, datagram, paramgram["mode"])
311    return response
312
313
314def fmgr_fwpol_package_folder(fmgr, paramgram):
315    """
316    This function will create folders for firewall packages. It can create down to two levels deep.
317    We haven't yet tested for any more layers below two levels.
318    parent_folders for multiple levels may need to defined as "level1/level2/level3" for the URL parameters and such.
319
320    :param fmgr: The fmgr object instance from fmgr_utils.py
321    :type fmgr: class object
322    :param paramgram: The formatted dictionary of options to process
323    :type paramgram: dict
324
325    :return: The response from the FortiManager
326    :rtype: dict
327    """
328    if paramgram["mode"] in ['set', 'add']:
329        url = '/pm/pkg/adom/{adom}'.format(adom=paramgram["adom"])
330        # IF PARENT FOLDER IS NOT DEFINED
331        if paramgram["parent_folder"] is None:
332            datagram = {
333                "type": paramgram["object_type"],
334                "name": paramgram["name"],
335            }
336
337        # IF PARENT FOLDER IS DEFINED
338        if paramgram["parent_folder"] is not None:
339            datagram = {
340                "type": paramgram["object_type"],
341                "name": paramgram["parent_folder"],
342                "subobj": [{
343                    "name": paramgram["name"],
344                    "type": paramgram["object_type"],
345
346                }]
347            }
348    # NORMAL DELETE NO PARENT
349    if paramgram["mode"] == "delete" and paramgram["parent_folder"] is None:
350        datagram = {
351            "name": paramgram["name"]
352        }
353        # SET DELETE URL
354        url = '/pm/pkg/adom/{adom}/{name}'.format(adom=paramgram["adom"], name=paramgram["name"])
355
356    # DELETE WITH PARENT
357    if paramgram["mode"] == "delete" and paramgram["parent_folder"] is not None:
358        datagram = {
359            "name": paramgram["name"]
360        }
361        # SET DELETE URL
362        url = '/pm/pkg/adom/{adom}/{parent_folder}/{name}'.format(adom=paramgram["adom"],
363                                                                  name=paramgram["name"],
364                                                                  parent_folder=paramgram["parent_folder"])
365
366    response = fmgr.process_request(url, datagram, paramgram["mode"])
367    return response
368
369
370def fmgr_fwpol_package_install(fmgr, paramgram):
371    """
372    This method/function installs FMGR FW Policy Packages to the scope members defined in the playbook.
373
374    :param fmgr: The fmgr object instance from fmgr_utils.py
375    :type fmgr: class object
376    :param paramgram: The formatted dictionary of options to process
377    :type paramgram: dict
378
379    :return: The response from the FortiManager
380    :rtype: dict
381    """
382    # INIT BLANK MEMBERS LIST
383    members_list = []
384    # USE THE PARSE CSV FUNCTION TO GET A LIST FORMAT OF THE MEMBERS
385    members = FMGRCommon.split_comma_strings_into_lists(paramgram["scope_members"])
386    # USE THAT LIST TO BUILD THE DICTIONARIES NEEDED, AND ADD TO THE BLANK MEMBERS LIST
387    for member in members:
388        scope_dict = {
389            "name": member,
390            "vdom": paramgram["scope_members_vdom"],
391        }
392        members_list.append(scope_dict)
393    # THEN FOR THE DATAGRAM, USING THE MEMBERS LIST CREATED ABOVE
394    datagram = {
395        "adom": paramgram["adom"],
396        "pkg": paramgram["name"],
397        "scope": members_list
398    }
399    # EXECUTE THE INSTALL REQUEST
400    url = '/securityconsole/install/package'
401    response = fmgr.process_request(url, datagram, FMGRMethods.EXEC)
402    return response
403
404
405def main():
406    argument_spec = dict(
407        adom=dict(required=False, type="str", default="root"),
408        mode=dict(choices=["add", "set", "delete"], type="str", default="add"),
409
410        name=dict(required=False, type="str"),
411        object_type=dict(required=True, type="str", choices=['pkg', 'folder', 'install']),
412        package_folder=dict(required=False, type="str"),
413        central_nat=dict(required=False, type="str", default="disable", choices=['enable', 'disable']),
414        fwpolicy_implicit_log=dict(required=False, type="str", default="disable", choices=['enable', 'disable']),
415        fwpolicy6_implicit_log=dict(required=False, type="str", default="disable", choices=['enable', 'disable']),
416        inspection_mode=dict(required=False, type="str", default="flow", choices=['flow', 'proxy']),
417        ngfw_mode=dict(required=False, type="str", default="profile-based", choices=['profile-based', 'policy-based']),
418        ssl_ssh_profile=dict(required=False, type="str"),
419        scope_members=dict(required=False, type="str"),
420        scope_members_vdom=dict(required=False, type="str", default="root"),
421        parent_folder=dict(required=False, type="str"),
422
423    )
424
425    module = AnsibleModule(argument_spec=argument_spec, supports_check_mode=False, )
426    # MODULE DATAGRAM
427    paramgram = {
428        "adom": module.params["adom"],
429        "name": module.params["name"],
430        "mode": module.params["mode"],
431        "object_type": module.params["object_type"],
432        "package-folder": module.params["package_folder"],
433        "central-nat": module.params["central_nat"],
434        "fwpolicy-implicit-log": module.params["fwpolicy_implicit_log"],
435        "fwpolicy6-implicit-log": module.params["fwpolicy6_implicit_log"],
436        "inspection-mode": module.params["inspection_mode"],
437        "ngfw-mode": module.params["ngfw_mode"],
438        "ssl-ssh-profile": module.params["ssl_ssh_profile"],
439        "scope_members": module.params["scope_members"],
440        "scope_members_vdom": module.params["scope_members_vdom"],
441        "parent_folder": module.params["parent_folder"],
442    }
443    module.paramgram = paramgram
444    fmgr = None
445    if module._socket_path:
446        connection = Connection(module._socket_path)
447        fmgr = FortiManagerHandler(connection, module)
448        fmgr.tools = FMGRCommon()
449    else:
450        module.fail_json(**FAIL_SOCKET_MSG)
451
452    # BEGIN MODULE-SPECIFIC LOGIC -- THINGS NEED TO HAPPEN DEPENDING ON THE ENDPOINT AND OPERATION
453    results = DEFAULT_RESULT_OBJ
454
455    try:
456        if paramgram["object_type"] == "pkg":
457            results = fmgr_fwpol_package(fmgr, paramgram)
458            fmgr.govern_response(module=module, results=results,
459                                 ansible_facts=fmgr.construct_ansible_facts(results, module.params, paramgram))
460    except Exception as err:
461        raise FMGBaseException(err)
462
463    try:
464        # IF THE object_type IS FOLDER LETS RUN THAT METHOD
465        if paramgram["object_type"] == "folder":
466            results = fmgr_fwpol_package_folder(fmgr, paramgram)
467            fmgr.govern_response(module=module, results=results,
468                                 ansible_facts=fmgr.construct_ansible_facts(results, module.params, paramgram))
469    except Exception as err:
470        raise FMGBaseException(err)
471
472    try:
473        # IF THE object_type IS INSTALL AND NEEDED PARAMETERS ARE DEFINED INSTALL THE PACKAGE
474        if paramgram["scope_members"] is not None and paramgram["name"] is not None and\
475                paramgram["object_type"] == "install":
476            results = fmgr_fwpol_package_install(fmgr, paramgram)
477            fmgr.govern_response(module=module, results=results,
478                                 ansible_facts=fmgr.construct_ansible_facts(results, module.params, paramgram))
479    except Exception as err:
480        raise FMGBaseException(err)
481
482    return module.exit_json(**results[1])
483
484
485if __name__ == "__main__":
486    main()
487