1#!/usr/bin/python
2from __future__ import (absolute_import, division, print_function)
3# Copyright 2019 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_addrgrp
27short_description: Configure IPv4 address groups 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 addrgrp category.
31      Examples include all parameters and values need to be adjusted to datasources before usage.
32      Tested with FOS v6.0.5
33version_added: "2.8"
34author:
35    - Miguel Angel Munoz (@mamunozgonzalez)
36    - Nicolas Thomas (@thomnico)
37notes:
38    - Requires fortiosapi library developed by Fortinet
39    - Run as a local_action in your playbook
40requirements:
41    - fortiosapi>=0.9.8
42options:
43    host:
44        description:
45            - FortiOS or FortiGate IP address.
46        type: str
47        required: false
48    username:
49        description:
50            - FortiOS or FortiGate username.
51        type: str
52        required: false
53    password:
54        description:
55            - FortiOS or FortiGate password.
56        type: str
57        default: ""
58    vdom:
59        description:
60            - Virtual domain, among those defined previously. A vdom is a
61              virtual instance of the FortiGate that can be configured and
62              used as a different unit.
63        type: str
64        default: root
65    https:
66        description:
67            - Indicates if the requests towards FortiGate must use HTTPS protocol.
68        type: bool
69        default: true
70    ssl_verify:
71        description:
72            - Ensures FortiGate certificate must be verified by a proper CA.
73        type: bool
74        default: true
75        version_added: 2.9
76    state:
77        description:
78            - Indicates whether to create or remove the object.
79              This attribute was present already in previous version in a deeper level.
80              It has been moved out to this outer level.
81        type: str
82        required: false
83        choices:
84            - present
85            - absent
86        version_added: 2.9
87    firewall_addrgrp:
88        description:
89            - Configure IPv4 address groups.
90        default: null
91        type: dict
92        suboptions:
93            state:
94                description:
95                    - B(Deprecated)
96                    - Starting with Ansible 2.9 we recommend using the top-level 'state' parameter.
97                    - HORIZONTALLINE
98                    - Indicates whether to create or remove the object.
99                type: str
100                required: false
101                choices:
102                    - present
103                    - absent
104            allow_routing:
105                description:
106                    - Enable/disable use of this group in the static route configuration.
107                type: str
108                choices:
109                    - enable
110                    - disable
111            color:
112                description:
113                    - Color of icon on the GUI.
114                type: int
115            comment:
116                description:
117                    - Comment.
118                type: str
119            member:
120                description:
121                    - Address objects contained within the group.
122                type: list
123                suboptions:
124                    name:
125                        description:
126                            - Address name. Source firewall.address.name firewall.addrgrp.name.
127                        required: true
128                        type: str
129            name:
130                description:
131                    - Address group name.
132                required: true
133                type: str
134            tagging:
135                description:
136                    - Config object tagging.
137                type: list
138                suboptions:
139                    category:
140                        description:
141                            - Tag category. Source system.object-tagging.category.
142                        type: str
143                    name:
144                        description:
145                            - Tagging entry name.
146                        required: true
147                        type: str
148                    tags:
149                        description:
150                            - Tags.
151                        type: list
152                        suboptions:
153                            name:
154                                description:
155                                    - Tag name. Source system.object-tagging.tags.name.
156                                required: true
157                                type: str
158            uuid:
159                description:
160                    - Universally Unique Identifier (UUID; automatically assigned but can be manually reset).
161                type: str
162            visibility:
163                description:
164                    - Enable/disable address visibility in the GUI.
165                type: str
166                choices:
167                    - enable
168                    - disable
169'''
170
171EXAMPLES = '''
172- hosts: localhost
173  vars:
174   host: "192.168.122.40"
175   username: "admin"
176   password: ""
177   vdom: "root"
178   ssl_verify: "False"
179  tasks:
180  - name: Configure IPv4 address groups.
181    fortios_firewall_addrgrp:
182      host:  "{{ host }}"
183      username: "{{ username }}"
184      password: "{{ password }}"
185      vdom:  "{{ vdom }}"
186      https: "False"
187      state: "present"
188      firewall_addrgrp:
189        allow_routing: "enable"
190        color: "4"
191        comment: "Comment."
192        member:
193         -
194            name: "default_name_7 (source firewall.address.name firewall.addrgrp.name)"
195        name: "default_name_8"
196        tagging:
197         -
198            category: "<your_own_value> (source system.object-tagging.category)"
199            name: "default_name_11"
200            tags:
201             -
202                name: "default_name_13 (source system.object-tagging.tags.name)"
203        uuid: "<your_own_value>"
204        visibility: "enable"
205'''
206
207RETURN = '''
208build:
209  description: Build number of the fortigate image
210  returned: always
211  type: str
212  sample: '1547'
213http_method:
214  description: Last method used to provision the content into FortiGate
215  returned: always
216  type: str
217  sample: 'PUT'
218http_status:
219  description: Last result given by FortiGate on last operation applied
220  returned: always
221  type: str
222  sample: "200"
223mkey:
224  description: Master key (id) used in the last call to FortiGate
225  returned: success
226  type: str
227  sample: "id"
228name:
229  description: Name of the table used to fulfill the request
230  returned: always
231  type: str
232  sample: "urlfilter"
233path:
234  description: Path of the table used to fulfill the request
235  returned: always
236  type: str
237  sample: "webfilter"
238revision:
239  description: Internal revision number
240  returned: always
241  type: str
242  sample: "17.0.2.10658"
243serial:
244  description: Serial number of the unit
245  returned: always
246  type: str
247  sample: "FGVMEVYYQT3AB5352"
248status:
249  description: Indication of the operation's result
250  returned: always
251  type: str
252  sample: "success"
253vdom:
254  description: Virtual domain used
255  returned: always
256  type: str
257  sample: "root"
258version:
259  description: Version of the FortiGate
260  returned: always
261  type: str
262  sample: "v5.6.3"
263
264'''
265
266from ansible.module_utils.basic import AnsibleModule
267from ansible.module_utils.connection import Connection
268from ansible.module_utils.network.fortios.fortios import FortiOSHandler
269from ansible.module_utils.network.fortimanager.common import FAIL_SOCKET_MSG
270
271
272def login(data, fos):
273    host = data['host']
274    username = data['username']
275    password = data['password']
276    ssl_verify = data['ssl_verify']
277
278    fos.debug('on')
279    if 'https' in data and not data['https']:
280        fos.https('off')
281    else:
282        fos.https('on')
283
284    fos.login(host, username, password, verify=ssl_verify)
285
286
287def filter_firewall_addrgrp_data(json):
288    option_list = ['allow_routing', 'color', 'comment',
289                   'member', 'name', 'tagging',
290                   'uuid', 'visibility']
291    dictionary = {}
292
293    for attribute in option_list:
294        if attribute in json and json[attribute] is not None:
295            dictionary[attribute] = json[attribute]
296
297    return dictionary
298
299
300def underscore_to_hyphen(data):
301    if isinstance(data, list):
302        for elem in data:
303            elem = underscore_to_hyphen(elem)
304    elif isinstance(data, dict):
305        new_data = {}
306        for k, v in data.items():
307            new_data[k.replace('_', '-')] = underscore_to_hyphen(v)
308        data = new_data
309
310    return data
311
312
313def firewall_addrgrp(data, fos):
314    vdom = data['vdom']
315    if 'state' in data and data['state']:
316        state = data['state']
317    elif 'state' in data['firewall_addrgrp'] and data['firewall_addrgrp']:
318        state = data['firewall_addrgrp']['state']
319    else:
320        state = True
321    firewall_addrgrp_data = data['firewall_addrgrp']
322    filtered_data = underscore_to_hyphen(filter_firewall_addrgrp_data(firewall_addrgrp_data))
323
324    if state == "present":
325        return fos.set('firewall',
326                       'addrgrp',
327                       data=filtered_data,
328                       vdom=vdom)
329
330    elif state == "absent":
331        return fos.delete('firewall',
332                          'addrgrp',
333                          mkey=filtered_data['name'],
334                          vdom=vdom)
335
336
337def is_successful_status(status):
338    return status['status'] == "success" or \
339        status['http_method'] == "DELETE" and status['http_status'] == 404
340
341
342def fortios_firewall(data, fos):
343
344    if data['firewall_addrgrp']:
345        resp = firewall_addrgrp(data, fos)
346
347    return not is_successful_status(resp), \
348        resp['status'] == "success", \
349        resp
350
351
352def main():
353    fields = {
354        "host": {"required": False, "type": "str"},
355        "username": {"required": False, "type": "str"},
356        "password": {"required": False, "type": "str", "default": "", "no_log": True},
357        "vdom": {"required": False, "type": "str", "default": "root"},
358        "https": {"required": False, "type": "bool", "default": True},
359        "ssl_verify": {"required": False, "type": "bool", "default": True},
360        "state": {"required": False, "type": "str",
361                  "choices": ["present", "absent"]},
362        "firewall_addrgrp": {
363            "required": False, "type": "dict", "default": None,
364            "options": {
365                "state": {"required": False, "type": "str",
366                          "choices": ["present", "absent"]},
367                "allow_routing": {"required": False, "type": "str",
368                                  "choices": ["enable", "disable"]},
369                "color": {"required": False, "type": "int"},
370                "comment": {"required": False, "type": "str"},
371                "member": {"required": False, "type": "list",
372                           "options": {
373                               "name": {"required": True, "type": "str"}
374                           }},
375                "name": {"required": True, "type": "str"},
376                "tagging": {"required": False, "type": "list",
377                            "options": {
378                                "category": {"required": False, "type": "str"},
379                                "name": {"required": True, "type": "str"},
380                                "tags": {"required": False, "type": "list",
381                                         "options": {
382                                             "name": {"required": True, "type": "str"}
383                                         }}
384                            }},
385                "uuid": {"required": False, "type": "str"},
386                "visibility": {"required": False, "type": "str",
387                               "choices": ["enable", "disable"]}
388
389            }
390        }
391    }
392
393    module = AnsibleModule(argument_spec=fields,
394                           supports_check_mode=False)
395
396    # legacy_mode refers to using fortiosapi instead of HTTPAPI
397    legacy_mode = 'host' in module.params and module.params['host'] is not None and \
398                  'username' in module.params and module.params['username'] is not None and \
399                  'password' in module.params and module.params['password'] is not None
400
401    if not legacy_mode:
402        if module._socket_path:
403            connection = Connection(module._socket_path)
404            fos = FortiOSHandler(connection)
405
406            is_error, has_changed, result = fortios_firewall(module.params, fos)
407        else:
408            module.fail_json(**FAIL_SOCKET_MSG)
409    else:
410        try:
411            from fortiosapi import FortiOSAPI
412        except ImportError:
413            module.fail_json(msg="fortiosapi module is required")
414
415        fos = FortiOSAPI()
416
417        login(module.params, fos)
418        is_error, has_changed, result = fortios_firewall(module.params, fos)
419        fos.logout()
420
421    if not is_error:
422        module.exit_json(changed=has_changed, meta=result)
423    else:
424        module.fail_json(msg="Error in repo", meta=result)
425
426
427if __name__ == '__main__':
428    main()
429