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()
|