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_router_prefix_list
27short_description: Configure IPv4 prefix lists 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 router feature and prefix_list 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    router_prefix_list:
88        description:
89            - Configure IPv4 prefix lists.
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            comments:
105                description:
106                    - Comment.
107                type: str
108            name:
109                description:
110                    - Name.
111                required: true
112                type: str
113            rule:
114                description:
115                    - IPv4 prefix list rule.
116                type: list
117                suboptions:
118                    action:
119                        description:
120                            - Permit or deny this IP address and netmask prefix.
121                        type: str
122                        choices:
123                            - permit
124                            - deny
125                    flags:
126                        description:
127                            - Flags.
128                        type: int
129                    ge:
130                        description:
131                            - Minimum prefix length to be matched (0 - 32).
132                        type: int
133                    id:
134                        description:
135                            - Rule ID.
136                        required: true
137                        type: int
138                    le:
139                        description:
140                            - Maximum prefix length to be matched (0 - 32).
141                        type: int
142                    prefix:
143                        description:
144                            - IPv4 prefix to define regular filter criteria, such as "any" or subnets.
145                        type: str
146'''
147
148EXAMPLES = '''
149- hosts: localhost
150  vars:
151   host: "192.168.122.40"
152   username: "admin"
153   password: ""
154   vdom: "root"
155   ssl_verify: "False"
156  tasks:
157  - name: Configure IPv4 prefix lists.
158    fortios_router_prefix_list:
159      host:  "{{ host }}"
160      username: "{{ username }}"
161      password: "{{ password }}"
162      vdom:  "{{ vdom }}"
163      https: "False"
164      state: "present"
165      router_prefix_list:
166        comments: "<your_own_value>"
167        name: "default_name_4"
168        rule:
169         -
170            action: "permit"
171            flags: "7"
172            ge: "8"
173            id:  "9"
174            le: "10"
175            prefix: "<your_own_value>"
176'''
177
178RETURN = '''
179build:
180  description: Build number of the fortigate image
181  returned: always
182  type: str
183  sample: '1547'
184http_method:
185  description: Last method used to provision the content into FortiGate
186  returned: always
187  type: str
188  sample: 'PUT'
189http_status:
190  description: Last result given by FortiGate on last operation applied
191  returned: always
192  type: str
193  sample: "200"
194mkey:
195  description: Master key (id) used in the last call to FortiGate
196  returned: success
197  type: str
198  sample: "id"
199name:
200  description: Name of the table used to fulfill the request
201  returned: always
202  type: str
203  sample: "urlfilter"
204path:
205  description: Path of the table used to fulfill the request
206  returned: always
207  type: str
208  sample: "webfilter"
209revision:
210  description: Internal revision number
211  returned: always
212  type: str
213  sample: "17.0.2.10658"
214serial:
215  description: Serial number of the unit
216  returned: always
217  type: str
218  sample: "FGVMEVYYQT3AB5352"
219status:
220  description: Indication of the operation's result
221  returned: always
222  type: str
223  sample: "success"
224vdom:
225  description: Virtual domain used
226  returned: always
227  type: str
228  sample: "root"
229version:
230  description: Version of the FortiGate
231  returned: always
232  type: str
233  sample: "v5.6.3"
234
235'''
236
237from ansible.module_utils.basic import AnsibleModule
238from ansible.module_utils.connection import Connection
239from ansible.module_utils.network.fortios.fortios import FortiOSHandler
240from ansible.module_utils.network.fortimanager.common import FAIL_SOCKET_MSG
241
242
243def login(data, fos):
244    host = data['host']
245    username = data['username']
246    password = data['password']
247    ssl_verify = data['ssl_verify']
248
249    fos.debug('on')
250    if 'https' in data and not data['https']:
251        fos.https('off')
252    else:
253        fos.https('on')
254
255    fos.login(host, username, password, verify=ssl_verify)
256
257
258def filter_router_prefix_list_data(json):
259    option_list = ['comments', 'name', 'rule']
260    dictionary = {}
261
262    for attribute in option_list:
263        if attribute in json and json[attribute] is not None:
264            dictionary[attribute] = json[attribute]
265
266    return dictionary
267
268
269def underscore_to_hyphen(data):
270    if isinstance(data, list):
271        for elem in data:
272            elem = underscore_to_hyphen(elem)
273    elif isinstance(data, dict):
274        new_data = {}
275        for k, v in data.items():
276            new_data[k.replace('_', '-')] = underscore_to_hyphen(v)
277        data = new_data
278
279    return data
280
281
282def router_prefix_list(data, fos):
283    vdom = data['vdom']
284    if 'state' in data and data['state']:
285        state = data['state']
286    elif 'state' in data['router_prefix_list'] and data['router_prefix_list']:
287        state = data['router_prefix_list']['state']
288    else:
289        state = True
290    router_prefix_list_data = data['router_prefix_list']
291    filtered_data = underscore_to_hyphen(filter_router_prefix_list_data(router_prefix_list_data))
292
293    if state == "present":
294        return fos.set('router',
295                       'prefix-list',
296                       data=filtered_data,
297                       vdom=vdom)
298
299    elif state == "absent":
300        return fos.delete('router',
301                          'prefix-list',
302                          mkey=filtered_data['name'],
303                          vdom=vdom)
304
305
306def is_successful_status(status):
307    return status['status'] == "success" or \
308        status['http_method'] == "DELETE" and status['http_status'] == 404
309
310
311def fortios_router(data, fos):
312
313    if data['router_prefix_list']:
314        resp = router_prefix_list(data, fos)
315
316    return not is_successful_status(resp), \
317        resp['status'] == "success", \
318        resp
319
320
321def main():
322    fields = {
323        "host": {"required": False, "type": "str"},
324        "username": {"required": False, "type": "str"},
325        "password": {"required": False, "type": "str", "default": "", "no_log": True},
326        "vdom": {"required": False, "type": "str", "default": "root"},
327        "https": {"required": False, "type": "bool", "default": True},
328        "ssl_verify": {"required": False, "type": "bool", "default": True},
329        "state": {"required": False, "type": "str",
330                  "choices": ["present", "absent"]},
331        "router_prefix_list": {
332            "required": False, "type": "dict", "default": None,
333            "options": {
334                "state": {"required": False, "type": "str",
335                          "choices": ["present", "absent"]},
336                "comments": {"required": False, "type": "str"},
337                "name": {"required": True, "type": "str"},
338                "rule": {"required": False, "type": "list",
339                         "options": {
340                             "action": {"required": False, "type": "str",
341                                        "choices": ["permit", "deny"]},
342                             "flags": {"required": False, "type": "int"},
343                             "ge": {"required": False, "type": "int"},
344                             "id": {"required": True, "type": "int"},
345                             "le": {"required": False, "type": "int"},
346                             "prefix": {"required": False, "type": "str"}
347                         }}
348
349            }
350        }
351    }
352
353    module = AnsibleModule(argument_spec=fields,
354                           supports_check_mode=False)
355
356    # legacy_mode refers to using fortiosapi instead of HTTPAPI
357    legacy_mode = 'host' in module.params and module.params['host'] is not None and \
358                  'username' in module.params and module.params['username'] is not None and \
359                  'password' in module.params and module.params['password'] is not None
360
361    if not legacy_mode:
362        if module._socket_path:
363            connection = Connection(module._socket_path)
364            fos = FortiOSHandler(connection)
365
366            is_error, has_changed, result = fortios_router(module.params, fos)
367        else:
368            module.fail_json(**FAIL_SOCKET_MSG)
369    else:
370        try:
371            from fortiosapi import FortiOSAPI
372        except ImportError:
373            module.fail_json(msg="fortiosapi module is required")
374
375        fos = FortiOSAPI()
376
377        login(module.params, fos)
378        is_error, has_changed, result = fortios_router(module.params, fos)
379        fos.logout()
380
381    if not is_error:
382        module.exit_json(changed=has_changed, meta=result)
383    else:
384        module.fail_json(msg="Error in repo", meta=result)
385
386
387if __name__ == '__main__':
388    main()
389