summaryrefslogtreecommitdiffstats
path: root/ansible_collections/community/general/plugins/modules/ipa_config.py
blob: 871643fd7bdaa366062802557d09ece9dae7476a (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
#!/usr/bin/python
# -*- coding: utf-8 -*-
# Copyright (c) 2018, Fran Fitzpatrick <francis.x.fitzpatrick@gmail.com>
# GNU General Public License v3.0+ (see LICENSES/GPL-3.0-or-later.txt or https://www.gnu.org/licenses/gpl-3.0.txt)
# SPDX-License-Identifier: GPL-3.0-or-later

from __future__ import absolute_import, division, print_function
__metaclass__ = type

DOCUMENTATION = r'''
---
module: ipa_config
author: Fran Fitzpatrick (@fxfitz)
short_description: Manage Global FreeIPA Configuration Settings
description:
  - Modify global configuration settings of a FreeIPA Server.
attributes:
  check_mode:
    support: full
  diff_mode:
    support: none
options:
  ipaconfigstring:
    description: Extra hashes to generate in password plug-in.
    aliases: ["configstring"]
    type: list
    elements: str
    choices: ["AllowNThash", "KDC:Disable Last Success", "KDC:Disable Lockout", "KDC:Disable Default Preauth for SPNs"]
    version_added: '2.5.0'
  ipadefaultloginshell:
    description: Default shell for new users.
    aliases: ["loginshell"]
    type: str
  ipadefaultemaildomain:
    description: Default e-mail domain for new users.
    aliases: ["emaildomain"]
    type: str
  ipadefaultprimarygroup:
    description: Default group for new users.
    aliases: ["primarygroup"]
    type: str
    version_added: '2.5.0'
  ipagroupobjectclasses:
    description: A list of group objectclasses.
    aliases: ["groupobjectclasses"]
    type: list
    elements: str
    version_added: '7.3.0'
  ipagroupsearchfields:
    description: A list of fields to search in when searching for groups.
    aliases: ["groupsearchfields"]
    type: list
    elements: str
    version_added: '2.5.0'
  ipahomesrootdir:
    description: Default location of home directories.
    aliases: ["homesrootdir"]
    type: str
    version_added: '2.5.0'
  ipakrbauthzdata:
    description: Default types of PAC supported for services.
    aliases: ["krbauthzdata"]
    type: list
    elements: str
    choices: ["MS-PAC", "PAD", "nfs:NONE"]
    version_added: '2.5.0'
  ipamaxusernamelength:
    description: Maximum length of usernames.
    aliases: ["maxusernamelength"]
    type: int
    version_added: '2.5.0'
  ipapwdexpadvnotify:
    description: Notice of impending password expiration, in days.
    aliases: ["pwdexpadvnotify"]
    type: int
    version_added: '2.5.0'
  ipasearchrecordslimit:
    description: Maximum number of records to search (-1 or 0 is unlimited).
    aliases: ["searchrecordslimit"]
    type: int
    version_added: '2.5.0'
  ipasearchtimelimit:
    description: Maximum amount of time (seconds) for a search (-1 or 0 is unlimited).
    aliases: ["searchtimelimit"]
    type: int
    version_added: '2.5.0'
  ipaselinuxusermaporder:
    description: The SELinux user map order (order in increasing priority of SELinux users).
    aliases: ["selinuxusermaporder"]
    type: list
    elements: str
    version_added: '3.7.0'
  ipauserauthtype:
    description:
      - The authentication type to use by default.
      - The choice V(idp) has been added in community.general 7.3.0.
      - The choice V(passkey) has been added in community.general 8.1.0.
    aliases: ["userauthtype"]
    choices: ["password", "radius", "otp", "pkinit", "hardened", "idp", "passkey", "disabled"]
    type: list
    elements: str
    version_added: '2.5.0'
  ipauserobjectclasses:
    description: A list of user objectclasses.
    aliases: ["userobjectclasses"]
    type: list
    elements: str
    version_added: '7.3.0'
  ipausersearchfields:
    description: A list of fields to search in when searching for users.
    aliases: ["usersearchfields"]
    type: list
    elements: str
    version_added: '2.5.0'
extends_documentation_fragment:
  - community.general.ipa.documentation
  - community.general.attributes

'''

EXAMPLES = r'''
- name: Ensure password plugin features DC:Disable Last Success and KDC:Disable Lockout are enabled
  community.general.ipa_config:
    ipaconfigstring: ["KDC:Disable Last Success", "KDC:Disable Lockout"]
    ipa_host: localhost
    ipa_user: admin
    ipa_pass: supersecret

- name: Ensure the default login shell is bash
  community.general.ipa_config:
    ipadefaultloginshell: /bin/bash
    ipa_host: localhost
    ipa_user: admin
    ipa_pass: supersecret

- name: Ensure the default e-mail domain is ansible.com
  community.general.ipa_config:
    ipadefaultemaildomain: ansible.com
    ipa_host: localhost
    ipa_user: admin
    ipa_pass: supersecret

- name: Ensure the default primary group is set to ipausers
  community.general.ipa_config:
    ipadefaultprimarygroup: ipausers
    ipa_host: localhost
    ipa_user: admin
    ipa_pass: supersecret

- name: Ensure the group search fields are set to 'cn,description'
  community.general.ipa_config:
    ipagroupsearchfields: ['cn', 'description']
    ipa_host: localhost
    ipa_user: admin
    ipa_pass: supersecret

- name: Ensure the home directory location is set to /home
  community.general.ipa_config:
    ipahomesrootdir: /home
    ipa_host: localhost
    ipa_user: admin
    ipa_pass: supersecret

- name: Ensure the default types of PAC supported for services is set to MS-PAC and PAD
  community.general.ipa_config:
    ipakrbauthzdata: ["MS-PAC", "PAD"]
    ipa_host: localhost
    ipa_user: admin
    ipa_pass: supersecret

- name: Ensure the maximum user name length is set to 32
  community.general.ipa_config:
    ipamaxusernamelength: 32
    ipa_host: localhost
    ipa_user: admin
    ipa_pass: supersecret

- name: Ensure the password expiration notice is set to 4 days
  community.general.ipa_config:
    ipapwdexpadvnotify: 4
    ipa_host: localhost
    ipa_user: admin
    ipa_pass: supersecret

- name: Ensure the search record limit is set to 100
  community.general.ipa_config:
    ipasearchrecordslimit: 100
    ipa_host: localhost
    ipa_user: admin
    ipa_pass: supersecret

- name: Ensure the search time limit is set to 2 seconds
  community.general.ipa_config:
    ipasearchtimelimit: 2
    ipa_host: localhost
    ipa_user: admin
    ipa_pass: supersecret

- name: Ensure the default user auth type is password
  community.general.ipa_config:
    ipauserauthtype: ['password']
    ipa_host: localhost
    ipa_user: admin
    ipa_pass: supersecret

- name: Ensure the user search fields is set to 'uid,givenname,sn,ou,title'
  community.general.ipa_config:
    ipausersearchfields: ['uid', 'givenname', 'sn', 'ou', 'title']
    ipa_host: localhost
    ipa_user: admin
    ipa_pass: supersecret

- name: Ensure the SELinux user map order is set
  community.general.ipa_config:
    ipaselinuxusermaporder:
      - "guest_u:s0"
      - "xguest_u:s0"
      - "user_u:s0"
      - "staff_u:s0-s0:c0.c1023"
      - "unconfined_u:s0-s0:c0.c1023"
    ipa_host: localhost
    ipa_user: admin
    ipa_pass: supersecret
'''

RETURN = r'''
config:
  description: Configuration as returned by IPA API.
  returned: always
  type: dict
'''

import traceback

from ansible.module_utils.basic import AnsibleModule
from ansible_collections.community.general.plugins.module_utils.ipa import IPAClient, ipa_argument_spec
from ansible.module_utils.common.text.converters import to_native


class ConfigIPAClient(IPAClient):
    def __init__(self, module, host, port, protocol):
        super(ConfigIPAClient, self).__init__(module, host, port, protocol)

    def config_show(self):
        return self._post_json(method='config_show', name=None)

    def config_mod(self, name, item):
        return self._post_json(method='config_mod', name=name, item=item)


def get_config_dict(ipaconfigstring=None, ipadefaultloginshell=None,
                    ipadefaultemaildomain=None, ipadefaultprimarygroup=None,
                    ipagroupsearchfields=None, ipagroupobjectclasses=None,
                    ipahomesrootdir=None, ipakrbauthzdata=None,
                    ipamaxusernamelength=None, ipapwdexpadvnotify=None,
                    ipasearchrecordslimit=None, ipasearchtimelimit=None,
                    ipaselinuxusermaporder=None, ipauserauthtype=None,
                    ipausersearchfields=None, ipauserobjectclasses=None):
    config = {}
    if ipaconfigstring is not None:
        config['ipaconfigstring'] = ipaconfigstring
    if ipadefaultloginshell is not None:
        config['ipadefaultloginshell'] = ipadefaultloginshell
    if ipadefaultemaildomain is not None:
        config['ipadefaultemaildomain'] = ipadefaultemaildomain
    if ipadefaultprimarygroup is not None:
        config['ipadefaultprimarygroup'] = ipadefaultprimarygroup
    if ipagroupobjectclasses is not None:
        config['ipagroupobjectclasses'] = ipagroupobjectclasses
    if ipagroupsearchfields is not None:
        config['ipagroupsearchfields'] = ','.join(ipagroupsearchfields)
    if ipahomesrootdir is not None:
        config['ipahomesrootdir'] = ipahomesrootdir
    if ipakrbauthzdata is not None:
        config['ipakrbauthzdata'] = ipakrbauthzdata
    if ipamaxusernamelength is not None:
        config['ipamaxusernamelength'] = str(ipamaxusernamelength)
    if ipapwdexpadvnotify is not None:
        config['ipapwdexpadvnotify'] = str(ipapwdexpadvnotify)
    if ipasearchrecordslimit is not None:
        config['ipasearchrecordslimit'] = str(ipasearchrecordslimit)
    if ipasearchtimelimit is not None:
        config['ipasearchtimelimit'] = str(ipasearchtimelimit)
    if ipaselinuxusermaporder is not None:
        config['ipaselinuxusermaporder'] = '$'.join(ipaselinuxusermaporder)
    if ipauserauthtype is not None:
        config['ipauserauthtype'] = ipauserauthtype
    if ipauserobjectclasses is not None:
        config['ipauserobjectclasses'] = ipauserobjectclasses
    if ipausersearchfields is not None:
        config['ipausersearchfields'] = ','.join(ipausersearchfields)

    return config


def get_config_diff(client, ipa_config, module_config):
    return client.get_diff(ipa_data=ipa_config, module_data=module_config)


def ensure(module, client):
    module_config = get_config_dict(
        ipaconfigstring=module.params.get('ipaconfigstring'),
        ipadefaultloginshell=module.params.get('ipadefaultloginshell'),
        ipadefaultemaildomain=module.params.get('ipadefaultemaildomain'),
        ipadefaultprimarygroup=module.params.get('ipadefaultprimarygroup'),
        ipagroupobjectclasses=module.params.get('ipagroupobjectclasses'),
        ipagroupsearchfields=module.params.get('ipagroupsearchfields'),
        ipahomesrootdir=module.params.get('ipahomesrootdir'),
        ipakrbauthzdata=module.params.get('ipakrbauthzdata'),
        ipamaxusernamelength=module.params.get('ipamaxusernamelength'),
        ipapwdexpadvnotify=module.params.get('ipapwdexpadvnotify'),
        ipasearchrecordslimit=module.params.get('ipasearchrecordslimit'),
        ipasearchtimelimit=module.params.get('ipasearchtimelimit'),
        ipaselinuxusermaporder=module.params.get('ipaselinuxusermaporder'),
        ipauserauthtype=module.params.get('ipauserauthtype'),
        ipausersearchfields=module.params.get('ipausersearchfields'),
        ipauserobjectclasses=module.params.get('ipauserobjectclasses'),
    )
    ipa_config = client.config_show()
    diff = get_config_diff(client, ipa_config, module_config)

    changed = False
    new_config = {}
    for module_key in diff:
        if module_config.get(module_key) != ipa_config.get(module_key, None):
            changed = True
            new_config.update({module_key: module_config.get(module_key)})

    if changed and not module.check_mode:
        client.config_mod(name=None, item=new_config)

    return changed, client.config_show()


def main():
    argument_spec = ipa_argument_spec()
    argument_spec.update(
        ipaconfigstring=dict(type='list', elements='str',
                             choices=['AllowNThash',
                                      'KDC:Disable Last Success',
                                      'KDC:Disable Lockout',
                                      'KDC:Disable Default Preauth for SPNs'],
                             aliases=['configstring']),
        ipadefaultloginshell=dict(type='str', aliases=['loginshell']),
        ipadefaultemaildomain=dict(type='str', aliases=['emaildomain']),
        ipadefaultprimarygroup=dict(type='str', aliases=['primarygroup']),
        ipagroupobjectclasses=dict(type='list', elements='str',
                                   aliases=['groupobjectclasses']),
        ipagroupsearchfields=dict(type='list', elements='str',
                                  aliases=['groupsearchfields']),
        ipahomesrootdir=dict(type='str', aliases=['homesrootdir']),
        ipakrbauthzdata=dict(type='list', elements='str',
                             choices=['MS-PAC', 'PAD', 'nfs:NONE'],
                             aliases=['krbauthzdata']),
        ipamaxusernamelength=dict(type='int', aliases=['maxusernamelength']),
        ipapwdexpadvnotify=dict(type='int', aliases=['pwdexpadvnotify']),
        ipasearchrecordslimit=dict(type='int', aliases=['searchrecordslimit']),
        ipasearchtimelimit=dict(type='int', aliases=['searchtimelimit']),
        ipaselinuxusermaporder=dict(type='list', elements='str',
                                    aliases=['selinuxusermaporder']),
        ipauserauthtype=dict(type='list', elements='str',
                             aliases=['userauthtype'],
                             choices=["password", "radius", "otp", "pkinit",
                                      "hardened", "idp", "passkey", "disabled"]),
        ipausersearchfields=dict(type='list', elements='str',
                                 aliases=['usersearchfields']),
        ipauserobjectclasses=dict(type='list', elements='str',
                                  aliases=['userobjectclasses']),
    )

    module = AnsibleModule(
        argument_spec=argument_spec,
        supports_check_mode=True
    )

    client = ConfigIPAClient(
        module=module,
        host=module.params['ipa_host'],
        port=module.params['ipa_port'],
        protocol=module.params['ipa_prot']
    )

    try:
        client.login(
            username=module.params['ipa_user'],
            password=module.params['ipa_pass']
        )
        changed, user = ensure(module, client)
        module.exit_json(changed=changed, user=user)
    except Exception as e:
        module.fail_json(msg=to_native(e), exception=traceback.format_exc())


if __name__ == '__main__':
    main()