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_endpoint_control_forticlient_ems
27short_description: Configure FortiClient Enterprise Management Server (EMS) entries 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 endpoint_control feature and forticlient_ems 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    endpoint_control_forticlient_ems:
76        description:
77            - Configure FortiClient Enterprise Management Server (EMS) entries.
78        default: null
79        type: dict
80        suboptions:
81            address:
82                description:
83                    - Firewall address name. Source firewall.address.name.
84                type: str
85            admin_password:
86                description:
87                    - FortiClient EMS admin password.
88                type: str
89            admin_type:
90                description:
91                    - FortiClient EMS admin type.
92                type: str
93                choices:
94                    - Windows
95                    - LDAP
96            admin_username:
97                description:
98                    - FortiClient EMS admin username.
99                type: str
100            https_port:
101                description:
102                    - 'FortiClient EMS HTTPS access port number. (1 - 65535).'
103                type: int
104            listen_port:
105                description:
106                    - 'FortiClient EMS telemetry listen port number. (1 - 65535).'
107                type: int
108            name:
109                description:
110                    - FortiClient Enterprise Management Server (EMS) name.
111                required: true
112                type: str
113            rest_api_auth:
114                description:
115                    - FortiClient EMS REST API authentication.
116                type: str
117                choices:
118                    - disable
119                    - userpass
120            serial_number:
121                description:
122                    - FortiClient EMS Serial Number.
123                type: str
124            upload_port:
125                description:
126                    - 'FortiClient EMS telemetry upload port number. (1 - 65535).'
127                type: int
128'''
129
130EXAMPLES = '''
131- hosts: fortigates
132  collections:
133    - fortinet.fortios
134  connection: httpapi
135  vars:
136   vdom: "root"
137   ansible_httpapi_use_ssl: yes
138   ansible_httpapi_validate_certs: no
139   ansible_httpapi_port: 443
140  tasks:
141  - name: Configure FortiClient Enterprise Management Server (EMS) entries.
142    fortios_endpoint_control_forticlient_ems:
143      vdom:  "{{ vdom }}"
144      state: "present"
145      access_token: "<your_own_value>"
146      endpoint_control_forticlient_ems:
147        address: "<your_own_value> (source firewall.address.name)"
148        admin_password: "<your_own_value>"
149        admin_type: "Windows"
150        admin_username: "<your_own_value>"
151        https_port: "7"
152        listen_port: "8"
153        name: "default_name_9"
154        rest_api_auth: "disable"
155        serial_number: "<your_own_value>"
156        upload_port: "12"
157
158'''
159
160RETURN = '''
161build:
162  description: Build number of the fortigate image
163  returned: always
164  type: str
165  sample: '1547'
166http_method:
167  description: Last method used to provision the content into FortiGate
168  returned: always
169  type: str
170  sample: 'PUT'
171http_status:
172  description: Last result given by FortiGate on last operation applied
173  returned: always
174  type: str
175  sample: "200"
176mkey:
177  description: Master key (id) used in the last call to FortiGate
178  returned: success
179  type: str
180  sample: "id"
181name:
182  description: Name of the table used to fulfill the request
183  returned: always
184  type: str
185  sample: "urlfilter"
186path:
187  description: Path of the table used to fulfill the request
188  returned: always
189  type: str
190  sample: "webfilter"
191revision:
192  description: Internal revision number
193  returned: always
194  type: str
195  sample: "17.0.2.10658"
196serial:
197  description: Serial number of the unit
198  returned: always
199  type: str
200  sample: "FGVMEVYYQT3AB5352"
201status:
202  description: Indication of the operation's result
203  returned: always
204  type: str
205  sample: "success"
206vdom:
207  description: Virtual domain used
208  returned: always
209  type: str
210  sample: "root"
211version:
212  description: Version of the FortiGate
213  returned: always
214  type: str
215  sample: "v5.6.3"
216
217'''
218from ansible.module_utils.basic import AnsibleModule
219from ansible.module_utils.connection import Connection
220from ansible_collections.fortinet.fortios.plugins.module_utils.fortios.fortios import FortiOSHandler
221from ansible_collections.fortinet.fortios.plugins.module_utils.fortios.fortios import check_legacy_fortiosapi
222from ansible_collections.fortinet.fortios.plugins.module_utils.fortios.fortios import schema_to_module_spec
223from ansible_collections.fortinet.fortios.plugins.module_utils.fortios.fortios import check_schema_versioning
224from ansible_collections.fortinet.fortios.plugins.module_utils.fortimanager.common import FAIL_SOCKET_MSG
225from ansible_collections.fortinet.fortios.plugins.module_utils.fortios.comparison import is_same_comparison
226from ansible_collections.fortinet.fortios.plugins.module_utils.fortios.comparison import serialize
227
228
229def filter_endpoint_control_forticlient_ems_data(json):
230    option_list = ['address', 'admin_password', 'admin_type',
231                   'admin_username', 'https_port', 'listen_port',
232                   'name', 'rest_api_auth', 'serial_number',
233                   'upload_port']
234    dictionary = {}
235
236    for attribute in option_list:
237        if attribute in json and json[attribute] is not None:
238            dictionary[attribute] = json[attribute]
239
240    return dictionary
241
242
243def underscore_to_hyphen(data):
244    if isinstance(data, list):
245        for i, elem in enumerate(data):
246            data[i] = underscore_to_hyphen(elem)
247    elif isinstance(data, dict):
248        new_data = {}
249        for k, v in data.items():
250            new_data[k.replace('_', '-')] = underscore_to_hyphen(v)
251        data = new_data
252
253    return data
254
255
256def endpoint_control_forticlient_ems(data, fos, check_mode=False):
257
258    vdom = data['vdom']
259
260    state = data['state']
261
262    endpoint_control_forticlient_ems_data = data['endpoint_control_forticlient_ems']
263    filtered_data = underscore_to_hyphen(filter_endpoint_control_forticlient_ems_data(endpoint_control_forticlient_ems_data))
264
265    # check_mode starts from here
266    if check_mode:
267        mkey = fos.get_mkey('system', 'interface', filtered_data, vdom=vdom)
268        current_data = fos.get('system', 'interface', vdom=vdom, mkey=mkey)
269        is_existed = current_data and current_data.get('http_status') == 200 \
270            and isinstance(current_data.get('results'), list) \
271            and len(current_data['results']) > 0
272
273        # 2. if it exists and the state is 'present' then compare current settings with desired
274        if state == 'present' or state is True:
275            if mkey is None:
276                return False, True, filtered_data
277
278            # if mkey exists then compare each other
279            # record exits and they're matched or not
280            if is_existed:
281                is_same = is_same_comparison(
282                    serialize(current_data['results'][0]), serialize(filtered_data))
283                return False, not is_same, filtered_data
284
285            # record does not exist
286            return False, True, filtered_data
287
288        if state == 'absent':
289            if mkey is None:
290                return False, False, filtered_data
291
292            if is_existed:
293                return False, True, filtered_data
294            return False, False, filtered_data
295
296        return True, False, {'reason: ': 'Must provide state parameter'}
297
298    if state == "present" or state is True:
299        return fos.set('endpoint-control',
300                       'forticlient-ems',
301                       data=filtered_data,
302                       vdom=vdom)
303
304    elif state == "absent":
305        return fos.delete('endpoint-control',
306                          'forticlient-ems',
307                          mkey=filtered_data['name'],
308                          vdom=vdom)
309    else:
310        fos._module.fail_json(msg='state must be present or absent!')
311
312
313def is_successful_status(status):
314    return status['status'] == "success" or \
315        status['http_method'] == "DELETE" and status['http_status'] == 404
316
317
318def fortios_endpoint_control(data, fos, check_mode):
319
320    if data['endpoint_control_forticlient_ems']:
321        resp = endpoint_control_forticlient_ems(data, fos, check_mode)
322    else:
323        fos._module.fail_json(msg='missing task body: %s' % ('endpoint_control_forticlient_ems'))
324    if check_mode:
325        return resp
326    return not is_successful_status(resp), \
327        resp['status'] == "success" and \
328        (resp['revision_changed'] if 'revision_changed' in resp else True), \
329        resp
330
331
332versioned_schema = {
333    "type": "list",
334    "children": {
335        "name": {
336            "type": "string",
337            "revisions": {
338                "v6.0.11": True,
339                "v6.0.0": True,
340                "v6.0.5": True
341            }
342        },
343        "listen_port": {
344            "type": "integer",
345            "revisions": {
346                "v6.0.11": True,
347                "v6.0.0": True,
348                "v6.0.5": True
349            }
350        },
351        "admin_type": {
352            "type": "string",
353            "options": [
354                {
355                    "value": "Windows",
356                    "revisions": {
357                        "v6.0.11": True,
358                        "v6.0.0": True,
359                        "v6.0.5": True
360                    }
361                },
362                {
363                    "value": "LDAP",
364                    "revisions": {
365                        "v6.0.11": True,
366                        "v6.0.0": True,
367                        "v6.0.5": True
368                    }
369                }
370            ],
371            "revisions": {
372                "v6.0.11": True,
373                "v6.0.0": True,
374                "v6.0.5": True
375            }
376        },
377        "rest_api_auth": {
378            "type": "string",
379            "options": [
380                {
381                    "value": "disable",
382                    "revisions": {
383                        "v6.0.11": True,
384                        "v6.0.0": True,
385                        "v6.0.5": True
386                    }
387                },
388                {
389                    "value": "userpass",
390                    "revisions": {
391                        "v6.0.11": True,
392                        "v6.0.0": True,
393                        "v6.0.5": True
394                    }
395                }
396            ],
397            "revisions": {
398                "v6.0.11": True,
399                "v6.0.0": True,
400                "v6.0.5": True
401            }
402        },
403        "admin_username": {
404            "type": "string",
405            "revisions": {
406                "v6.0.11": True,
407                "v6.0.0": True,
408                "v6.0.5": True
409            }
410        },
411        "admin_password": {
412            "type": "string",
413            "revisions": {
414                "v6.0.11": True,
415                "v6.0.0": True,
416                "v6.0.5": True
417            }
418        },
419        "address": {
420            "type": "string",
421            "revisions": {
422                "v6.0.11": True,
423                "v6.0.0": True,
424                "v6.0.5": True
425            }
426        },
427        "serial_number": {
428            "type": "string",
429            "revisions": {
430                "v6.0.11": True,
431                "v6.0.0": True,
432                "v6.0.5": True
433            }
434        },
435        "upload_port": {
436            "type": "integer",
437            "revisions": {
438                "v6.0.11": True,
439                "v6.0.0": True,
440                "v6.0.5": True
441            }
442        },
443        "https_port": {
444            "type": "integer",
445            "revisions": {
446                "v6.0.11": True,
447                "v6.0.0": True,
448                "v6.0.5": True
449            }
450        }
451    },
452    "revisions": {
453        "v6.0.11": True,
454        "v6.0.0": True,
455        "v6.0.5": True
456    }
457}
458
459
460def main():
461    module_spec = schema_to_module_spec(versioned_schema)
462    mkeyname = 'name'
463    fields = {
464        "access_token": {"required": False, "type": "str", "no_log": True},
465        "enable_log": {"required": False, "type": bool},
466        "vdom": {"required": False, "type": "str", "default": "root"},
467        "state": {"required": True, "type": "str",
468                  "choices": ["present", "absent"]},
469        "endpoint_control_forticlient_ems": {
470            "required": False, "type": "dict", "default": None,
471            "options": {
472            }
473        }
474    }
475    for attribute_name in module_spec['options']:
476        fields["endpoint_control_forticlient_ems"]['options'][attribute_name] = module_spec['options'][attribute_name]
477        if mkeyname and mkeyname == attribute_name:
478            fields["endpoint_control_forticlient_ems"]['options'][attribute_name]['required'] = True
479
480    check_legacy_fortiosapi()
481    module = AnsibleModule(argument_spec=fields,
482                           supports_check_mode=True)
483
484    versions_check_result = None
485    if module._socket_path:
486        connection = Connection(module._socket_path)
487        if 'access_token' in module.params:
488            connection.set_option('access_token', module.params['access_token'])
489
490        if 'enable_log' in module.params:
491            connection.set_option('enable_log', module.params['enable_log'])
492        else:
493            connection.set_option('enable_log', False)
494        fos = FortiOSHandler(connection, module, mkeyname)
495        versions_check_result = check_schema_versioning(fos, versioned_schema, "endpoint_control_forticlient_ems")
496
497        is_error, has_changed, result = fortios_endpoint_control(module.params, fos, module.check_mode)
498
499    else:
500        module.fail_json(**FAIL_SOCKET_MSG)
501
502    if versions_check_result and versions_check_result['matched'] is False:
503        module.warn("Ansible has detected version mismatch between FortOS system and your playbook, see more details by specifying option -vvv")
504
505    if not is_error:
506        if versions_check_result and versions_check_result['matched'] is False:
507            module.exit_json(changed=has_changed, version_check_warning=versions_check_result, meta=result)
508        else:
509            module.exit_json(changed=has_changed, meta=result)
510    else:
511        if versions_check_result and versions_check_result['matched'] is False:
512            module.fail_json(msg="Error in repo", version_check_warning=versions_check_result, meta=result)
513        else:
514            module.fail_json(msg="Error in repo", meta=result)
515
516
517if __name__ == '__main__':
518    main()
519