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_router_community_list
27short_description: Configure community 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 community_list 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    router_community_list:
76        description:
77            - Configure community lists.
78        default: null
79        type: dict
80        suboptions:
81            name:
82                description:
83                    - Community list name.
84                required: true
85                type: str
86            rule:
87                description:
88                    - Community list rule.
89                type: list
90                suboptions:
91                    action:
92                        description:
93                            - Permit or deny route-based operations, based on the route"s COMMUNITY attribute.
94                        type: str
95                        choices:
96                            - deny
97                            - permit
98                    id:
99                        description:
100                            - ID.
101                        required: true
102                        type: int
103                    match:
104                        description:
105                            - Community specifications for matching a reserved community.
106                        type: str
107                    regexp:
108                        description:
109                            - Ordered list of COMMUNITY attributes as a regular expression.
110                        type: str
111            type:
112                description:
113                    - Community list type (standard or expanded).
114                type: str
115                choices:
116                    - standard
117                    - expanded
118'''
119
120EXAMPLES = '''
121- hosts: fortigates
122  collections:
123    - fortinet.fortios
124  connection: httpapi
125  vars:
126   vdom: "root"
127   ansible_httpapi_use_ssl: yes
128   ansible_httpapi_validate_certs: no
129   ansible_httpapi_port: 443
130  tasks:
131  - name: Configure community lists.
132    fortios_router_community_list:
133      vdom:  "{{ vdom }}"
134      state: "present"
135      access_token: "<your_own_value>"
136      router_community_list:
137        name: "default_name_3"
138        rule:
139         -
140            action: "deny"
141            id:  "6"
142            match: "<your_own_value>"
143            regexp: "<your_own_value>"
144        type: "standard"
145
146'''
147
148RETURN = '''
149build:
150  description: Build number of the fortigate image
151  returned: always
152  type: str
153  sample: '1547'
154http_method:
155  description: Last method used to provision the content into FortiGate
156  returned: always
157  type: str
158  sample: 'PUT'
159http_status:
160  description: Last result given by FortiGate on last operation applied
161  returned: always
162  type: str
163  sample: "200"
164mkey:
165  description: Master key (id) used in the last call to FortiGate
166  returned: success
167  type: str
168  sample: "id"
169name:
170  description: Name of the table used to fulfill the request
171  returned: always
172  type: str
173  sample: "urlfilter"
174path:
175  description: Path of the table used to fulfill the request
176  returned: always
177  type: str
178  sample: "webfilter"
179revision:
180  description: Internal revision number
181  returned: always
182  type: str
183  sample: "17.0.2.10658"
184serial:
185  description: Serial number of the unit
186  returned: always
187  type: str
188  sample: "FGVMEVYYQT3AB5352"
189status:
190  description: Indication of the operation's result
191  returned: always
192  type: str
193  sample: "success"
194vdom:
195  description: Virtual domain used
196  returned: always
197  type: str
198  sample: "root"
199version:
200  description: Version of the FortiGate
201  returned: always
202  type: str
203  sample: "v5.6.3"
204
205'''
206from ansible.module_utils.basic import AnsibleModule
207from ansible.module_utils.connection import Connection
208from ansible_collections.fortinet.fortios.plugins.module_utils.fortios.fortios import FortiOSHandler
209from ansible_collections.fortinet.fortios.plugins.module_utils.fortios.fortios import check_legacy_fortiosapi
210from ansible_collections.fortinet.fortios.plugins.module_utils.fortios.fortios import schema_to_module_spec
211from ansible_collections.fortinet.fortios.plugins.module_utils.fortios.fortios import check_schema_versioning
212from ansible_collections.fortinet.fortios.plugins.module_utils.fortimanager.common import FAIL_SOCKET_MSG
213from ansible_collections.fortinet.fortios.plugins.module_utils.fortios.comparison import is_same_comparison
214from ansible_collections.fortinet.fortios.plugins.module_utils.fortios.comparison import serialize
215
216
217def filter_router_community_list_data(json):
218    option_list = ['name', 'rule', 'type']
219    dictionary = {}
220
221    for attribute in option_list:
222        if attribute in json and json[attribute] is not None:
223            dictionary[attribute] = json[attribute]
224
225    return dictionary
226
227
228def underscore_to_hyphen(data):
229    if isinstance(data, list):
230        for i, elem in enumerate(data):
231            data[i] = underscore_to_hyphen(elem)
232    elif isinstance(data, dict):
233        new_data = {}
234        for k, v in data.items():
235            new_data[k.replace('_', '-')] = underscore_to_hyphen(v)
236        data = new_data
237
238    return data
239
240
241def router_community_list(data, fos, check_mode=False):
242
243    vdom = data['vdom']
244
245    state = data['state']
246
247    router_community_list_data = data['router_community_list']
248    filtered_data = underscore_to_hyphen(filter_router_community_list_data(router_community_list_data))
249
250    # check_mode starts from here
251    if check_mode:
252        mkey = fos.get_mkey('system', 'interface', filtered_data, vdom=vdom)
253        current_data = fos.get('system', 'interface', vdom=vdom, mkey=mkey)
254        is_existed = current_data and current_data.get('http_status') == 200 \
255            and isinstance(current_data.get('results'), list) \
256            and len(current_data['results']) > 0
257
258        # 2. if it exists and the state is 'present' then compare current settings with desired
259        if state == 'present' or state is True:
260            if mkey is None:
261                return False, True, filtered_data
262
263            # if mkey exists then compare each other
264            # record exits and they're matched or not
265            if is_existed:
266                is_same = is_same_comparison(
267                    serialize(current_data['results'][0]), serialize(filtered_data))
268                return False, not is_same, filtered_data
269
270            # record does not exist
271            return False, True, filtered_data
272
273        if state == 'absent':
274            if mkey is None:
275                return False, False, filtered_data
276
277            if is_existed:
278                return False, True, filtered_data
279            return False, False, filtered_data
280
281        return True, False, {'reason: ': 'Must provide state parameter'}
282
283    if state == "present" or state is True:
284        return fos.set('router',
285                       'community-list',
286                       data=filtered_data,
287                       vdom=vdom)
288
289    elif state == "absent":
290        return fos.delete('router',
291                          'community-list',
292                          mkey=filtered_data['name'],
293                          vdom=vdom)
294    else:
295        fos._module.fail_json(msg='state must be present or absent!')
296
297
298def is_successful_status(status):
299    return status['status'] == "success" or \
300        status['http_method'] == "DELETE" and status['http_status'] == 404
301
302
303def fortios_router(data, fos, check_mode):
304
305    if data['router_community_list']:
306        resp = router_community_list(data, fos, check_mode)
307    else:
308        fos._module.fail_json(msg='missing task body: %s' % ('router_community_list'))
309    if check_mode:
310        return resp
311    return not is_successful_status(resp), \
312        resp['status'] == "success" and \
313        (resp['revision_changed'] if 'revision_changed' in resp else True), \
314        resp
315
316
317versioned_schema = {
318    "type": "list",
319    "children": {
320        "type": {
321            "type": "string",
322            "options": [
323                {
324                    "value": "standard",
325                    "revisions": {
326                        "v6.0.0": True,
327                        "v7.0.0": True,
328                        "v6.0.5": True,
329                        "v6.4.4": True,
330                        "v6.4.0": True,
331                        "v6.4.1": True,
332                        "v6.2.0": True,
333                        "v6.2.3": True,
334                        "v6.2.5": True,
335                        "v6.2.7": True,
336                        "v6.0.11": True
337                    }
338                },
339                {
340                    "value": "expanded",
341                    "revisions": {
342                        "v6.0.0": True,
343                        "v7.0.0": True,
344                        "v6.0.5": True,
345                        "v6.4.4": True,
346                        "v6.4.0": True,
347                        "v6.4.1": True,
348                        "v6.2.0": True,
349                        "v6.2.3": True,
350                        "v6.2.5": True,
351                        "v6.2.7": True,
352                        "v6.0.11": True
353                    }
354                }
355            ],
356            "revisions": {
357                "v6.0.0": True,
358                "v7.0.0": True,
359                "v6.0.5": True,
360                "v6.4.4": True,
361                "v6.4.0": True,
362                "v6.4.1": True,
363                "v6.2.0": True,
364                "v6.2.3": True,
365                "v6.2.5": True,
366                "v6.2.7": True,
367                "v6.0.11": True
368            }
369        },
370        "name": {
371            "type": "string",
372            "revisions": {
373                "v6.0.0": True,
374                "v7.0.0": True,
375                "v6.0.5": True,
376                "v6.4.4": True,
377                "v6.4.0": True,
378                "v6.4.1": True,
379                "v6.2.0": True,
380                "v6.2.3": True,
381                "v6.2.5": True,
382                "v6.2.7": True,
383                "v6.0.11": True
384            }
385        },
386        "rule": {
387            "type": "list",
388            "children": {
389                "action": {
390                    "type": "string",
391                    "options": [
392                        {
393                            "value": "deny",
394                            "revisions": {
395                                "v6.0.0": True,
396                                "v7.0.0": True,
397                                "v6.0.5": True,
398                                "v6.4.4": True,
399                                "v6.4.0": True,
400                                "v6.4.1": True,
401                                "v6.2.0": True,
402                                "v6.2.3": True,
403                                "v6.2.5": True,
404                                "v6.2.7": True,
405                                "v6.0.11": True
406                            }
407                        },
408                        {
409                            "value": "permit",
410                            "revisions": {
411                                "v6.0.0": True,
412                                "v7.0.0": True,
413                                "v6.0.5": True,
414                                "v6.4.4": True,
415                                "v6.4.0": True,
416                                "v6.4.1": True,
417                                "v6.2.0": True,
418                                "v6.2.3": True,
419                                "v6.2.5": True,
420                                "v6.2.7": True,
421                                "v6.0.11": True
422                            }
423                        }
424                    ],
425                    "revisions": {
426                        "v6.0.0": True,
427                        "v7.0.0": True,
428                        "v6.0.5": True,
429                        "v6.4.4": True,
430                        "v6.4.0": True,
431                        "v6.4.1": True,
432                        "v6.2.0": True,
433                        "v6.2.3": True,
434                        "v6.2.5": True,
435                        "v6.2.7": True,
436                        "v6.0.11": True
437                    }
438                },
439                "regexp": {
440                    "type": "string",
441                    "revisions": {
442                        "v6.0.0": True,
443                        "v7.0.0": True,
444                        "v6.0.5": True,
445                        "v6.4.4": True,
446                        "v6.4.0": True,
447                        "v6.4.1": True,
448                        "v6.2.0": True,
449                        "v6.2.3": True,
450                        "v6.2.5": True,
451                        "v6.2.7": True,
452                        "v6.0.11": True
453                    }
454                },
455                "id": {
456                    "type": "integer",
457                    "revisions": {
458                        "v6.0.0": True,
459                        "v7.0.0": True,
460                        "v6.0.5": True,
461                        "v6.4.4": True,
462                        "v6.4.0": True,
463                        "v6.4.1": True,
464                        "v6.2.0": True,
465                        "v6.2.3": True,
466                        "v6.2.5": True,
467                        "v6.2.7": True,
468                        "v6.0.11": True
469                    }
470                },
471                "match": {
472                    "type": "string",
473                    "revisions": {
474                        "v6.0.0": True,
475                        "v7.0.0": True,
476                        "v6.0.5": True,
477                        "v6.4.4": True,
478                        "v6.4.0": True,
479                        "v6.4.1": True,
480                        "v6.2.0": True,
481                        "v6.2.3": True,
482                        "v6.2.5": True,
483                        "v6.2.7": True,
484                        "v6.0.11": True
485                    }
486                }
487            },
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
518
519def main():
520    module_spec = schema_to_module_spec(versioned_schema)
521    mkeyname = 'name'
522    fields = {
523        "access_token": {"required": False, "type": "str", "no_log": True},
524        "enable_log": {"required": False, "type": bool},
525        "vdom": {"required": False, "type": "str", "default": "root"},
526        "state": {"required": True, "type": "str",
527                  "choices": ["present", "absent"]},
528        "router_community_list": {
529            "required": False, "type": "dict", "default": None,
530            "options": {
531            }
532        }
533    }
534    for attribute_name in module_spec['options']:
535        fields["router_community_list"]['options'][attribute_name] = module_spec['options'][attribute_name]
536        if mkeyname and mkeyname == attribute_name:
537            fields["router_community_list"]['options'][attribute_name]['required'] = True
538
539    check_legacy_fortiosapi()
540    module = AnsibleModule(argument_spec=fields,
541                           supports_check_mode=True)
542
543    versions_check_result = None
544    if module._socket_path:
545        connection = Connection(module._socket_path)
546        if 'access_token' in module.params:
547            connection.set_option('access_token', module.params['access_token'])
548
549        if 'enable_log' in module.params:
550            connection.set_option('enable_log', module.params['enable_log'])
551        else:
552            connection.set_option('enable_log', False)
553        fos = FortiOSHandler(connection, module, mkeyname)
554        versions_check_result = check_schema_versioning(fos, versioned_schema, "router_community_list")
555
556        is_error, has_changed, result = fortios_router(module.params, fos, module.check_mode)
557
558    else:
559        module.fail_json(**FAIL_SOCKET_MSG)
560
561    if versions_check_result and versions_check_result['matched'] is False:
562        module.warn("Ansible has detected version mismatch between FortOS system and your playbook, see more details by specifying option -vvv")
563
564    if not is_error:
565        if versions_check_result and versions_check_result['matched'] is False:
566            module.exit_json(changed=has_changed, version_check_warning=versions_check_result, meta=result)
567        else:
568            module.exit_json(changed=has_changed, meta=result)
569    else:
570        if versions_check_result and versions_check_result['matched'] is False:
571            module.fail_json(msg="Error in repo", version_check_warning=versions_check_result, meta=result)
572        else:
573            module.fail_json(msg="Error in repo", meta=result)
574
575
576if __name__ == '__main__':
577    main()
578