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_user_peer
27short_description: Configure peer users 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 user feature and peer 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    user_peer:
76        description:
77            - Configure peer users.
78        default: null
79        type: dict
80        suboptions:
81            ca:
82                description:
83                    - Name of the CA certificate as returned by the execute vpn certificate ca list command. Source vpn.certificate.ca.name.
84                type: str
85            cn:
86                description:
87                    - Peer certificate common name.
88                type: str
89            cn_type:
90                description:
91                    - Peer certificate common name type.
92                type: str
93                choices:
94                    - string
95                    - email
96                    - FQDN
97                    - ipv4
98                    - ipv6
99            ldap_mode:
100                description:
101                    - Mode for LDAP peer authentication.
102                type: str
103                choices:
104                    - password
105                    - principal-name
106            ldap_password:
107                description:
108                    - Password for LDAP server bind.
109                type: str
110            ldap_server:
111                description:
112                    - Name of an LDAP server defined under the user ldap command. Performs client access rights check. Source user.ldap.name.
113                type: str
114            ldap_username:
115                description:
116                    - Username for LDAP server bind.
117                type: str
118            mandatory_ca_verify:
119                description:
120                    - Determine what happens to the peer if the CA certificate is not installed. Disable to automatically consider the peer certificate as
121                       valid.
122                type: str
123                choices:
124                    - enable
125                    - disable
126            name:
127                description:
128                    - Peer name.
129                required: true
130                type: str
131            ocsp_override_server:
132                description:
133                    - Online Certificate Status Protocol (OCSP) server for certificate retrieval. Source vpn.certificate.ocsp-server.name.
134                type: str
135            passwd:
136                description:
137                    - Peer"s password used for two-factor authentication.
138                type: str
139            subject:
140                description:
141                    - Peer certificate name constraints.
142                type: str
143            two_factor:
144                description:
145                    - Enable/disable two-factor authentication, applying certificate and password-based authentication.
146                type: str
147                choices:
148                    - enable
149                    - disable
150'''
151
152EXAMPLES = '''
153- hosts: fortigates
154  collections:
155    - fortinet.fortios
156  connection: httpapi
157  vars:
158   vdom: "root"
159   ansible_httpapi_use_ssl: yes
160   ansible_httpapi_validate_certs: no
161   ansible_httpapi_port: 443
162  tasks:
163  - name: Configure peer users.
164    fortios_user_peer:
165      vdom:  "{{ vdom }}"
166      state: "present"
167      access_token: "<your_own_value>"
168      user_peer:
169        ca: "<your_own_value> (source vpn.certificate.ca.name)"
170        cn: "<your_own_value>"
171        cn_type: "string"
172        ldap_mode: "password"
173        ldap_password: "<your_own_value>"
174        ldap_server: "<your_own_value> (source user.ldap.name)"
175        ldap_username: "<your_own_value>"
176        mandatory_ca_verify: "enable"
177        name: "default_name_11"
178        ocsp_override_server: "<your_own_value> (source vpn.certificate.ocsp-server.name)"
179        passwd: "<your_own_value>"
180        subject: "<your_own_value>"
181        two_factor: "enable"
182
183'''
184
185RETURN = '''
186build:
187  description: Build number of the fortigate image
188  returned: always
189  type: str
190  sample: '1547'
191http_method:
192  description: Last method used to provision the content into FortiGate
193  returned: always
194  type: str
195  sample: 'PUT'
196http_status:
197  description: Last result given by FortiGate on last operation applied
198  returned: always
199  type: str
200  sample: "200"
201mkey:
202  description: Master key (id) used in the last call to FortiGate
203  returned: success
204  type: str
205  sample: "id"
206name:
207  description: Name of the table used to fulfill the request
208  returned: always
209  type: str
210  sample: "urlfilter"
211path:
212  description: Path of the table used to fulfill the request
213  returned: always
214  type: str
215  sample: "webfilter"
216revision:
217  description: Internal revision number
218  returned: always
219  type: str
220  sample: "17.0.2.10658"
221serial:
222  description: Serial number of the unit
223  returned: always
224  type: str
225  sample: "FGVMEVYYQT3AB5352"
226status:
227  description: Indication of the operation's result
228  returned: always
229  type: str
230  sample: "success"
231vdom:
232  description: Virtual domain used
233  returned: always
234  type: str
235  sample: "root"
236version:
237  description: Version of the FortiGate
238  returned: always
239  type: str
240  sample: "v5.6.3"
241
242'''
243from ansible.module_utils.basic import AnsibleModule
244from ansible.module_utils.connection import Connection
245from ansible_collections.fortinet.fortios.plugins.module_utils.fortios.fortios import FortiOSHandler
246from ansible_collections.fortinet.fortios.plugins.module_utils.fortios.fortios import check_legacy_fortiosapi
247from ansible_collections.fortinet.fortios.plugins.module_utils.fortios.fortios import schema_to_module_spec
248from ansible_collections.fortinet.fortios.plugins.module_utils.fortios.fortios import check_schema_versioning
249from ansible_collections.fortinet.fortios.plugins.module_utils.fortimanager.common import FAIL_SOCKET_MSG
250from ansible_collections.fortinet.fortios.plugins.module_utils.fortios.comparison import is_same_comparison
251from ansible_collections.fortinet.fortios.plugins.module_utils.fortios.comparison import serialize
252
253
254def filter_user_peer_data(json):
255    option_list = ['ca', 'cn', 'cn_type',
256                   'ldap_mode', 'ldap_password', 'ldap_server',
257                   'ldap_username', 'mandatory_ca_verify', 'name',
258                   'ocsp_override_server', 'passwd', 'subject',
259                   'two_factor']
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 i, elem in enumerate(data):
272            data[i] = 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 user_peer(data, fos, check_mode=False):
283
284    vdom = data['vdom']
285
286    state = data['state']
287
288    user_peer_data = data['user_peer']
289    filtered_data = underscore_to_hyphen(filter_user_peer_data(user_peer_data))
290
291    # check_mode starts from here
292    if check_mode:
293        mkey = fos.get_mkey('system', 'interface', filtered_data, vdom=vdom)
294        current_data = fos.get('system', 'interface', vdom=vdom, mkey=mkey)
295        is_existed = current_data and current_data.get('http_status') == 200 \
296            and isinstance(current_data.get('results'), list) \
297            and len(current_data['results']) > 0
298
299        # 2. if it exists and the state is 'present' then compare current settings with desired
300        if state == 'present' or state is True:
301            if mkey is None:
302                return False, True, filtered_data
303
304            # if mkey exists then compare each other
305            # record exits and they're matched or not
306            if is_existed:
307                is_same = is_same_comparison(
308                    serialize(current_data['results'][0]), serialize(filtered_data))
309                return False, not is_same, filtered_data
310
311            # record does not exist
312            return False, True, filtered_data
313
314        if state == 'absent':
315            if mkey is None:
316                return False, False, filtered_data
317
318            if is_existed:
319                return False, True, filtered_data
320            return False, False, filtered_data
321
322        return True, False, {'reason: ': 'Must provide state parameter'}
323
324    if state == "present" or state is True:
325        return fos.set('user',
326                       'peer',
327                       data=filtered_data,
328                       vdom=vdom)
329
330    elif state == "absent":
331        return fos.delete('user',
332                          'peer',
333                          mkey=filtered_data['name'],
334                          vdom=vdom)
335    else:
336        fos._module.fail_json(msg='state must be present or absent!')
337
338
339def is_successful_status(status):
340    return status['status'] == "success" or \
341        status['http_method'] == "DELETE" and status['http_status'] == 404
342
343
344def fortios_user(data, fos, check_mode):
345
346    if data['user_peer']:
347        resp = user_peer(data, fos, check_mode)
348    else:
349        fos._module.fail_json(msg='missing task body: %s' % ('user_peer'))
350    if check_mode:
351        return resp
352    return not is_successful_status(resp), \
353        resp['status'] == "success" and \
354        (resp['revision_changed'] if 'revision_changed' in resp else True), \
355        resp
356
357
358versioned_schema = {
359    "type": "list",
360    "children": {
361        "ocsp_override_server": {
362            "type": "string",
363            "revisions": {
364                "v6.0.0": True,
365                "v7.0.0": True,
366                "v6.0.5": True,
367                "v6.4.4": True,
368                "v6.4.0": True,
369                "v6.4.1": True,
370                "v6.2.0": True,
371                "v6.2.3": True,
372                "v6.2.5": True,
373                "v6.2.7": True,
374                "v6.0.11": True
375            }
376        },
377        "cn": {
378            "type": "string",
379            "revisions": {
380                "v6.0.0": True,
381                "v7.0.0": True,
382                "v6.0.5": True,
383                "v6.4.4": True,
384                "v6.4.0": True,
385                "v6.4.1": True,
386                "v6.2.0": True,
387                "v6.2.3": True,
388                "v6.2.5": True,
389                "v6.2.7": True,
390                "v6.0.11": True
391            }
392        },
393        "name": {
394            "type": "string",
395            "revisions": {
396                "v6.0.0": True,
397                "v7.0.0": True,
398                "v6.0.5": True,
399                "v6.4.4": True,
400                "v6.4.0": True,
401                "v6.4.1": True,
402                "v6.2.0": True,
403                "v6.2.3": True,
404                "v6.2.5": True,
405                "v6.2.7": True,
406                "v6.0.11": True
407            }
408        },
409        "passwd": {
410            "type": "string",
411            "revisions": {
412                "v6.0.0": True,
413                "v7.0.0": True,
414                "v6.0.5": True,
415                "v6.4.4": True,
416                "v6.4.0": True,
417                "v6.4.1": True,
418                "v6.2.0": True,
419                "v6.2.3": True,
420                "v6.2.5": True,
421                "v6.2.7": True,
422                "v6.0.11": True
423            }
424        },
425        "two_factor": {
426            "type": "string",
427            "options": [
428                {
429                    "value": "enable",
430                    "revisions": {
431                        "v6.0.0": True,
432                        "v7.0.0": True,
433                        "v6.0.5": True,
434                        "v6.4.4": True,
435                        "v6.4.0": True,
436                        "v6.4.1": True,
437                        "v6.2.0": True,
438                        "v6.2.3": True,
439                        "v6.2.5": True,
440                        "v6.2.7": True,
441                        "v6.0.11": True
442                    }
443                },
444                {
445                    "value": "disable",
446                    "revisions": {
447                        "v6.0.0": True,
448                        "v7.0.0": True,
449                        "v6.0.5": True,
450                        "v6.4.4": True,
451                        "v6.4.0": True,
452                        "v6.4.1": True,
453                        "v6.2.0": True,
454                        "v6.2.3": True,
455                        "v6.2.5": True,
456                        "v6.2.7": True,
457                        "v6.0.11": True
458                    }
459                }
460            ],
461            "revisions": {
462                "v6.0.0": True,
463                "v7.0.0": True,
464                "v6.0.5": True,
465                "v6.4.4": True,
466                "v6.4.0": True,
467                "v6.4.1": True,
468                "v6.2.0": True,
469                "v6.2.3": True,
470                "v6.2.5": True,
471                "v6.2.7": True,
472                "v6.0.11": True
473            }
474        },
475        "ldap_server": {
476            "type": "string",
477            "revisions": {
478                "v6.0.0": True,
479                "v7.0.0": True,
480                "v6.0.5": True,
481                "v6.4.4": True,
482                "v6.4.0": True,
483                "v6.4.1": True,
484                "v6.2.0": True,
485                "v6.2.3": True,
486                "v6.2.5": True,
487                "v6.2.7": True,
488                "v6.0.11": True
489            }
490        },
491        "cn_type": {
492            "type": "string",
493            "options": [
494                {
495                    "value": "string",
496                    "revisions": {
497                        "v6.0.0": True,
498                        "v7.0.0": True,
499                        "v6.0.5": True,
500                        "v6.4.4": True,
501                        "v6.4.0": True,
502                        "v6.4.1": True,
503                        "v6.2.0": True,
504                        "v6.2.3": True,
505                        "v6.2.5": True,
506                        "v6.2.7": True,
507                        "v6.0.11": True
508                    }
509                },
510                {
511                    "value": "email",
512                    "revisions": {
513                        "v6.0.0": True,
514                        "v7.0.0": True,
515                        "v6.0.5": True,
516                        "v6.4.4": True,
517                        "v6.4.0": True,
518                        "v6.4.1": True,
519                        "v6.2.0": True,
520                        "v6.2.3": True,
521                        "v6.2.5": True,
522                        "v6.2.7": True,
523                        "v6.0.11": True
524                    }
525                },
526                {
527                    "value": "FQDN",
528                    "revisions": {
529                        "v6.0.0": True,
530                        "v7.0.0": True,
531                        "v6.0.5": True,
532                        "v6.4.4": True,
533                        "v6.4.0": True,
534                        "v6.4.1": True,
535                        "v6.2.0": True,
536                        "v6.2.3": True,
537                        "v6.2.5": True,
538                        "v6.2.7": True,
539                        "v6.0.11": True
540                    }
541                },
542                {
543                    "value": "ipv4",
544                    "revisions": {
545                        "v6.0.0": True,
546                        "v7.0.0": True,
547                        "v6.0.5": True,
548                        "v6.4.4": True,
549                        "v6.4.0": True,
550                        "v6.4.1": True,
551                        "v6.2.0": True,
552                        "v6.2.3": True,
553                        "v6.2.5": True,
554                        "v6.2.7": True,
555                        "v6.0.11": True
556                    }
557                },
558                {
559                    "value": "ipv6",
560                    "revisions": {
561                        "v6.0.0": True,
562                        "v7.0.0": True,
563                        "v6.0.5": True,
564                        "v6.4.4": True,
565                        "v6.4.0": True,
566                        "v6.4.1": True,
567                        "v6.2.0": True,
568                        "v6.2.3": True,
569                        "v6.2.5": True,
570                        "v6.2.7": True,
571                        "v6.0.11": True
572                    }
573                }
574            ],
575            "revisions": {
576                "v6.0.0": True,
577                "v7.0.0": True,
578                "v6.0.5": True,
579                "v6.4.4": True,
580                "v6.4.0": True,
581                "v6.4.1": True,
582                "v6.2.0": True,
583                "v6.2.3": True,
584                "v6.2.5": True,
585                "v6.2.7": True,
586                "v6.0.11": True
587            }
588        },
589        "ldap_username": {
590            "type": "string",
591            "revisions": {
592                "v6.0.0": True,
593                "v7.0.0": True,
594                "v6.0.5": True,
595                "v6.4.4": True,
596                "v6.4.0": True,
597                "v6.4.1": True,
598                "v6.2.0": True,
599                "v6.2.3": True,
600                "v6.2.5": True,
601                "v6.2.7": True,
602                "v6.0.11": True
603            }
604        },
605        "ldap_password": {
606            "type": "string",
607            "revisions": {
608                "v6.0.0": True,
609                "v7.0.0": True,
610                "v6.0.5": True,
611                "v6.4.4": True,
612                "v6.4.0": True,
613                "v6.4.1": True,
614                "v6.2.0": True,
615                "v6.2.3": True,
616                "v6.2.5": True,
617                "v6.2.7": True,
618                "v6.0.11": True
619            }
620        },
621        "mandatory_ca_verify": {
622            "type": "string",
623            "options": [
624                {
625                    "value": "enable",
626                    "revisions": {
627                        "v6.0.0": True,
628                        "v7.0.0": True,
629                        "v6.0.5": True,
630                        "v6.4.4": True,
631                        "v6.4.0": True,
632                        "v6.4.1": True,
633                        "v6.2.0": True,
634                        "v6.2.3": True,
635                        "v6.2.5": True,
636                        "v6.2.7": True,
637                        "v6.0.11": True
638                    }
639                },
640                {
641                    "value": "disable",
642                    "revisions": {
643                        "v6.0.0": True,
644                        "v7.0.0": True,
645                        "v6.0.5": True,
646                        "v6.4.4": True,
647                        "v6.4.0": True,
648                        "v6.4.1": True,
649                        "v6.2.0": True,
650                        "v6.2.3": True,
651                        "v6.2.5": True,
652                        "v6.2.7": True,
653                        "v6.0.11": True
654                    }
655                }
656            ],
657            "revisions": {
658                "v6.0.0": True,
659                "v7.0.0": True,
660                "v6.0.5": True,
661                "v6.4.4": True,
662                "v6.4.0": True,
663                "v6.4.1": True,
664                "v6.2.0": True,
665                "v6.2.3": True,
666                "v6.2.5": True,
667                "v6.2.7": True,
668                "v6.0.11": True
669            }
670        },
671        "ldap_mode": {
672            "type": "string",
673            "options": [
674                {
675                    "value": "password",
676                    "revisions": {
677                        "v6.0.0": True,
678                        "v7.0.0": True,
679                        "v6.0.5": True,
680                        "v6.4.4": True,
681                        "v6.4.0": True,
682                        "v6.4.1": True,
683                        "v6.2.0": True,
684                        "v6.2.3": True,
685                        "v6.2.5": True,
686                        "v6.2.7": True,
687                        "v6.0.11": True
688                    }
689                },
690                {
691                    "value": "principal-name",
692                    "revisions": {
693                        "v6.0.0": True,
694                        "v7.0.0": True,
695                        "v6.0.5": True,
696                        "v6.4.4": True,
697                        "v6.4.0": True,
698                        "v6.4.1": True,
699                        "v6.2.0": True,
700                        "v6.2.3": True,
701                        "v6.2.5": True,
702                        "v6.2.7": True,
703                        "v6.0.11": True
704                    }
705                }
706            ],
707            "revisions": {
708                "v6.0.0": True,
709                "v7.0.0": True,
710                "v6.0.5": True,
711                "v6.4.4": True,
712                "v6.4.0": True,
713                "v6.4.1": True,
714                "v6.2.0": True,
715                "v6.2.3": True,
716                "v6.2.5": True,
717                "v6.2.7": True,
718                "v6.0.11": True
719            }
720        },
721        "ca": {
722            "type": "string",
723            "revisions": {
724                "v6.0.0": True,
725                "v7.0.0": True,
726                "v6.0.5": True,
727                "v6.4.4": True,
728                "v6.4.0": True,
729                "v6.4.1": True,
730                "v6.2.0": True,
731                "v6.2.3": True,
732                "v6.2.5": True,
733                "v6.2.7": True,
734                "v6.0.11": True
735            }
736        },
737        "subject": {
738            "type": "string",
739            "revisions": {
740                "v6.0.0": True,
741                "v7.0.0": True,
742                "v6.0.5": True,
743                "v6.4.4": True,
744                "v6.4.0": True,
745                "v6.4.1": True,
746                "v6.2.0": True,
747                "v6.2.3": True,
748                "v6.2.5": True,
749                "v6.2.7": True,
750                "v6.0.11": True
751            }
752        }
753    },
754    "revisions": {
755        "v6.0.0": True,
756        "v7.0.0": True,
757        "v6.0.5": True,
758        "v6.4.4": True,
759        "v6.4.0": True,
760        "v6.4.1": True,
761        "v6.2.0": True,
762        "v6.2.3": True,
763        "v6.2.5": True,
764        "v6.2.7": True,
765        "v6.0.11": True
766    }
767}
768
769
770def main():
771    module_spec = schema_to_module_spec(versioned_schema)
772    mkeyname = 'name'
773    fields = {
774        "access_token": {"required": False, "type": "str", "no_log": True},
775        "enable_log": {"required": False, "type": bool},
776        "vdom": {"required": False, "type": "str", "default": "root"},
777        "state": {"required": True, "type": "str",
778                  "choices": ["present", "absent"]},
779        "user_peer": {
780            "required": False, "type": "dict", "default": None,
781            "options": {
782            }
783        }
784    }
785    for attribute_name in module_spec['options']:
786        fields["user_peer"]['options'][attribute_name] = module_spec['options'][attribute_name]
787        if mkeyname and mkeyname == attribute_name:
788            fields["user_peer"]['options'][attribute_name]['required'] = True
789
790    check_legacy_fortiosapi()
791    module = AnsibleModule(argument_spec=fields,
792                           supports_check_mode=True)
793
794    versions_check_result = None
795    if module._socket_path:
796        connection = Connection(module._socket_path)
797        if 'access_token' in module.params:
798            connection.set_option('access_token', module.params['access_token'])
799
800        if 'enable_log' in module.params:
801            connection.set_option('enable_log', module.params['enable_log'])
802        else:
803            connection.set_option('enable_log', False)
804        fos = FortiOSHandler(connection, module, mkeyname)
805        versions_check_result = check_schema_versioning(fos, versioned_schema, "user_peer")
806
807        is_error, has_changed, result = fortios_user(module.params, fos, module.check_mode)
808
809    else:
810        module.fail_json(**FAIL_SOCKET_MSG)
811
812    if versions_check_result and versions_check_result['matched'] is False:
813        module.warn("Ansible has detected version mismatch between FortOS system and your playbook, see more details by specifying option -vvv")
814
815    if not is_error:
816        if versions_check_result and versions_check_result['matched'] is False:
817            module.exit_json(changed=has_changed, version_check_warning=versions_check_result, meta=result)
818        else:
819            module.exit_json(changed=has_changed, meta=result)
820    else:
821        if versions_check_result and versions_check_result['matched'] is False:
822            module.fail_json(msg="Error in repo", version_check_warning=versions_check_result, meta=result)
823        else:
824            module.fail_json(msg="Error in repo", meta=result)
825
826
827if __name__ == '__main__':
828    main()
829