Skip to content
Snippets Groups Projects
Commit 410682a0 authored by Rafael Guterres Jeffman's avatar Rafael Guterres Jeffman
Browse files

pwpolicy: Allow clearing policy values.

All values for pwpolicy can be cleared with an empty string in IPA CLI,
and this behavior was missing in ansible-freeipa.

As of today, there is an issue in FreeIPA that does not allow clearing
'minlength' policy. The is is tracked by the FreeIPA project through
https://pagure.io/freeipa/issue/9297

Fixes https://bugzilla.redhat.com/show_bug.cgi?id=2150334
parent 82e176af
No related branches found
No related tags found
No related merge requests found
...@@ -46,82 +46,82 @@ options: ...@@ -46,82 +46,82 @@ options:
aliases: ["cn"] aliases: ["cn"]
maxlife: maxlife:
description: Maximum password lifetime (in days) description: Maximum password lifetime (in days)
type: int type: str
required: false required: false
aliases: ["krbmaxpwdlife"] aliases: ["krbmaxpwdlife"]
minlife: minlife:
description: Minimum password lifetime (in hours) description: Minimum password lifetime (in hours)
type: int type: str
required: false required: false
aliases: ["krbminpwdlife"] aliases: ["krbminpwdlife"]
history: history:
description: Password history size description: Password history size
type: int type: str
required: false required: false
aliases: ["krbpwdhistorylength"] aliases: ["krbpwdhistorylength"]
minclasses: minclasses:
description: Minimum number of character classes description: Minimum number of character classes
type: int type: str
required: false required: false
aliases: ["krbpwdmindiffchars"] aliases: ["krbpwdmindiffchars"]
minlength: minlength:
description: Minimum length of password description: Minimum length of password
type: int type: str
required: false required: false
aliases: ["krbpwdminlength"] aliases: ["krbpwdminlength"]
priority: priority:
description: Priority of the policy (higher number means lower priority) description: Priority of the policy (higher number means lower priority)
type: int type: str
required: false required: false
aliases: ["cospriority"] aliases: ["cospriority"]
maxfail: maxfail:
description: Consecutive failures before lockout description: Consecutive failures before lockout
type: int type: str
required: false required: false
aliases: ["krbpwdmaxfailure"] aliases: ["krbpwdmaxfailure"]
failinterval: failinterval:
description: Period after which failure count will be reset (seconds) description: Period after which failure count will be reset (seconds)
type: int type: str
required: false required: false
aliases: ["krbpwdfailurecountinterval"] aliases: ["krbpwdfailurecountinterval"]
lockouttime: lockouttime:
description: Period for which lockout is enforced (seconds) description: Period for which lockout is enforced (seconds)
type: int type: str
required: false required: false
aliases: ["krbpwdlockoutduration"] aliases: ["krbpwdlockoutduration"]
maxrepeat: maxrepeat:
description: > description: >
Maximum number of same consecutive characters. Maximum number of same consecutive characters.
Requires IPA 4.9+ Requires IPA 4.9+
type: int type: str
required: false required: false
aliases: ["ipapwdmaxrepeat"] aliases: ["ipapwdmaxrepeat"]
maxsequence: maxsequence:
description: > description: >
The maximum length of monotonic character sequences (abcd). The maximum length of monotonic character sequences (abcd).
Requires IPA 4.9+ Requires IPA 4.9+
type: int type: str
required: false required: false
aliases: ["ipapwdmaxsequence"] aliases: ["ipapwdmaxsequence"]
dictcheck: dictcheck:
description: > description: >
Check if the password is a dictionary word. Check if the password is a dictionary word.
Requires IPA 4.9+ Requires IPA 4.9+
type: bool type: str
required: false required: false
aliases: ["ipapwdictcheck"] aliases: ["ipapwdictcheck"]
usercheck: usercheck:
description: > description: >
Check if the password contains the username. Check if the password contains the username.
Requires IPA 4.9+ Requires IPA 4.9+
type: bool type: str
required: false required: false
aliases: ["ipapwdusercheck"] aliases: ["ipapwdusercheck"]
gracelimit: gracelimit:
description: > description: >
Number of LDAP authentications allowed after expiration. Number of LDAP authentications allowed after expiration.
Requires IPA 4.10.1+ Requires IPA 4.10.1+
type: int type: str
required: false required: false
aliases: ["passwordgracelimit"] aliases: ["passwordgracelimit"]
state: state:
...@@ -242,31 +242,31 @@ def main(): ...@@ -242,31 +242,31 @@ def main():
default=None, required=False), default=None, required=False),
# present # present
maxlife=dict(type="int", aliases=["krbmaxpwdlife"], default=None), maxlife=dict(type="str", aliases=["krbmaxpwdlife"], default=None),
minlife=dict(type="int", aliases=["krbminpwdlife"], default=None), minlife=dict(type="str", aliases=["krbminpwdlife"], default=None),
history=dict(type="int", aliases=["krbpwdhistorylength"], history=dict(type="str", aliases=["krbpwdhistorylength"],
default=None), default=None),
minclasses=dict(type="int", aliases=["krbpwdmindiffchars"], minclasses=dict(type="str", aliases=["krbpwdmindiffchars"],
default=None), default=None),
minlength=dict(type="int", aliases=["krbpwdminlength"], minlength=dict(type="str", aliases=["krbpwdminlength"],
default=None), default=None),
priority=dict(type="int", aliases=["cospriority"], default=None), priority=dict(type="str", aliases=["cospriority"], default=None),
maxfail=dict(type="int", aliases=["krbpwdmaxfailure"], maxfail=dict(type="str", aliases=["krbpwdmaxfailure"],
default=None), default=None),
failinterval=dict(type="int", failinterval=dict(type="str",
aliases=["krbpwdfailurecountinterval"], aliases=["krbpwdfailurecountinterval"],
default=None), default=None),
lockouttime=dict(type="int", aliases=["krbpwdlockoutduration"], lockouttime=dict(type="str", aliases=["krbpwdlockoutduration"],
default=None), default=None),
maxrepeat=dict(type="int", aliases=["ipapwdmaxrepeat"], maxrepeat=dict(type="str", aliases=["ipapwdmaxrepeat"],
default=None), default=None),
maxsequence=dict(type="int", aliases=["ipapwdmaxsequence"], maxsequence=dict(type="str", aliases=["ipapwdmaxsequence"],
default=None), default=None),
dictcheck=dict(type="bool", aliases=["ipapwdictcheck"], dictcheck=dict(type="str", aliases=["ipapwdictcheck"],
default=None), default=None),
usercheck=dict(type="bool", aliases=["ipapwusercheck"], usercheck=dict(type="str", aliases=["ipapwusercheck"],
default=None), default=None),
gracelimit=dict(type="int", aliases=["passwordgracelimit"], gracelimit=dict(type="str", aliases=["passwordgracelimit"],
default=None), default=None),
# state # state
state=dict(type="str", default="present", state=dict(type="str", default="present",
...@@ -325,7 +325,48 @@ def main(): ...@@ -325,7 +325,48 @@ def main():
ansible_module.params_fail_used_invalid(invalid, state) ansible_module.params_fail_used_invalid(invalid, state)
if gracelimit is not None: # Ensure parameter values are valid and have proper type.
def int_or_empty_param(value, param):
if value is not None and value != "":
try:
value = int(value)
except ValueError:
ansible_module.fail_json(
msg="Invalid value '%s' for argument '%s'" % (value, param)
)
return value
maxlife = int_or_empty_param(maxlife, "maxlife")
minlife = int_or_empty_param(minlife, "minlife")
history = int_or_empty_param(history, "history")
minclasses = int_or_empty_param(minclasses, "minclasses")
minlength = int_or_empty_param(minlength, "minlength")
priority = int_or_empty_param(priority, "priority")
maxfail = int_or_empty_param(maxfail, "maxfail")
failinterval = int_or_empty_param(failinterval, "failinterval")
lockouttime = int_or_empty_param(lockouttime, "lockouttime")
maxrepeat = int_or_empty_param(maxrepeat, "maxrepeat")
maxsequence = int_or_empty_param(maxsequence, "maxsequence")
gracelimit = int_or_empty_param(gracelimit, "gracelimit")
def bool_param(value, param): # pylint: disable=R1710
# As of Ansible 2.14, values True, False, Yes an No, with variable
# capitalization are accepted by Ansible.
if not value:
return value
if value in ["TRUE", "True", "true", "YES", "Yes", "yes"]:
return True
if value in ["FALSE", "False", "false", "NO", "No", "no"]:
return False
ansible_module.fail_json(
msg="Invalid value '%s' for argument '%s'." % (value, param)
)
dictcheck = bool_param(dictcheck, "dictcheck")
usercheck = bool_param(usercheck, "usercheck")
# Ensure gracelimit has proper limit.
if gracelimit:
if gracelimit < -1: if gracelimit < -1:
ansible_module.fail_json( ansible_module.fail_json(
msg="'gracelimit' must be no less than -1") msg="'gracelimit' must be no less than -1")
......
...@@ -121,7 +121,75 @@ ...@@ -121,7 +121,75 @@
register: result register: result
failed_when: result.changed or result.failed failed_when: result.changed or result.failed
- name: Ensure presence of pwpolicies for group ops
ipapwpolicy:
ipaadmin_password: SomeADMINpassword
ipaapi_context: "{{ ipa_context | default(omit) }}"
name: ops
minlife: 7
maxlife: 49
history: 5
priority: 1
lockouttime: 300
minlength: 8
minclasses: 5
maxfail: 3
failinterval: 5
- name: Ensure policies are cleared
ipapwpolicy:
ipaadmin_password: SomeADMINpassword
ipaapi_context: "{{ ipa_context | default(omit) }}"
name: ops
minlife: ""
maxlife: ""
history: ""
# priority: ""
lockouttime: ""
minclasses: ""
maxfail: ""
failinterval: ""
register: result
failed_when: not result.changed or result.failed
- name: Ensure policies are cleared, again
ipapwpolicy:
ipaadmin_password: SomeADMINpassword
ipaapi_context: "{{ ipa_context | default(omit) }}"
name: ops
minlife: ""
maxlife: ""
history: ""
# priority: ""
lockouttime: ""
minclasses: ""
maxfail: ""
failinterval: ""
register: result
failed_when: result.changed or result.failed
- name: Ensure minlength is not cleared due to FreeIPA issue
ipapwpolicy:
ipaadmin_password: SomeADMINpassword
ipaapi_context: "{{ ipa_context | default(omit) }}"
name: ops
minlength: ""
register: result
failed_when: result.changed or (result.failed and "int() argument must be a string, a bytes-like object" not in result.msg)
when: ipa_version is version("4.9", ">=")
- name: Ensure minlength is not cleared due to FreeIPA issue
ipapwpolicy:
ipaadmin_password: SomeADMINpassword
ipaapi_context: "{{ ipa_context | default(omit) }}"
name: ops
minlength: ""
register: result
failed_when: not result.changed or result.failed
when: ipa_version is version("4.7", "<")
- name: Execute tests if ipa_version >= 4.9.0 - name: Execute tests if ipa_version >= 4.9.0
when: ipa_version is version("4.9", ">=")
block: block:
- name: Ensure maxrepeat of 2 for global_policy - name: Ensure maxrepeat of 2 for global_policy
ipapwpolicy: ipapwpolicy:
...@@ -171,6 +239,13 @@ ...@@ -171,6 +239,13 @@
register: result register: result
failed_when: not result.changed or result.failed failed_when: not result.changed or result.failed
- name: Ensure usercheck and dictcheck have known values
ipapwpolicy:
ipaadmin_password: SomeADMINpassword
ipaapi_context: "{{ ipa_context | default(omit) }}"
dictcheck: false
usercheck: false
- name: Ensure dictcheck is set for global_policy - name: Ensure dictcheck is set for global_policy
ipapwpolicy: ipapwpolicy:
ipaadmin_password: SomeADMINpassword ipaadmin_password: SomeADMINpassword
...@@ -219,9 +294,26 @@ ...@@ -219,9 +294,26 @@
register: result register: result
failed_when: not result.changed or result.failed failed_when: not result.changed or result.failed
when: ipa_version is version("4.9", ">=") - name: Ensure usercheck and dictcheck are cleared for global_policy
ipapwpolicy:
ipaadmin_password: SomeADMINpassword
ipaapi_context: "{{ ipa_context | default(omit) }}"
dictcheck: ""
usercheck: ""
register: result
failed_when: not result.changed or result.failed
- name: Ensure usercheck and dictcheck are cleared for global_policy, again
ipapwpolicy:
ipaadmin_password: SomeADMINpassword
ipaapi_context: "{{ ipa_context | default(omit) }}"
dictcheck: ""
usercheck: ""
register: result
failed_when: result.changed or result.failed
- name: Execute tests if ipa_version >= 4.9.10 - name: Execute tests if ipa_version >= 4.9.10
when: ipa_version is version("4.9.10", ">=")
block: block:
- name: Ensure grace limit is set to 10 for global_policy - name: Ensure grace limit is set to 10 for global_policy
ipapwpolicy: ipapwpolicy:
...@@ -255,6 +347,22 @@ ...@@ -255,6 +347,22 @@
register: result register: result
failed_when: not result.changed or result.failed failed_when: not result.changed or result.failed
- name: Ensure grace limit is cleared for global_policy
ipapwpolicy:
ipaadmin_password: SomeADMINpassword
ipaapi_context: "{{ ipa_context | default(omit) }}"
gracelimit: ""
register: result
failed_when: not result.changed or result.failed
- name: Ensure grace limit is cleared for global_policy, again
ipapwpolicy:
ipaadmin_password: SomeADMINpassword
ipaapi_context: "{{ ipa_context | default(omit) }}"
gracelimit: ""
register: result
failed_when: result.changed or result.failed
- name: Ensure grace limit is not set to -2 for global_policy - name: Ensure grace limit is not set to -2 for global_policy
ipapwpolicy: ipapwpolicy:
ipaadmin_password: SomeADMINpassword ipaadmin_password: SomeADMINpassword
...@@ -262,5 +370,3 @@ ...@@ -262,5 +370,3 @@
gracelimit: -2 gracelimit: -2
register: result register: result
failed_when: not result.failed and "must be at least -1" not in result.msg failed_when: not result.failed and "must be at least -1" not in result.msg
when: ipa_version is version("4.9.10", ">=")
---
- name: Test pwpolicy invalid data types
hosts: "{{ ipa_test_host | default('ipaserver') }}"
become: true
gather_facts: false
tasks:
- name: Setup FreeIPA test facts.
ansible.builtin.import_tasks: ../env_freeipa_facts.yml
- name: Ensure presence of group ops
ipagroup:
ipaadmin_password: SomeADMINpassword
ipaapi_context: "{{ ipa_context | default(omit) }}"
name: ops
state: present
- name: Ensure invalid values raise proper error for argument minlife
ipapwpolicy:
ipaadmin_password: SomeADMINpassword
ipaapi_context: "{{ ipa_context | default(omit) }}"
name: ops
minlife: "error"
register: result
failed_when: result.changed or (result.failed and "Invalid value 'error' for argument 'minlife'" not in result.msg)
- name: Ensure invalid values raise proper error for argument maxlife
ipapwpolicy:
ipaadmin_password: SomeADMINpassword
ipaapi_context: "{{ ipa_context | default(omit) }}"
name: ops
maxlife: "error"
register: result
failed_when: result.changed or (result.failed and "Invalid value 'error' for argument 'maxlife'" not in result.msg)
- name: Ensure invalid values raise proper error for argument history
ipapwpolicy:
ipaadmin_password: SomeADMINpassword
ipaapi_context: "{{ ipa_context | default(omit) }}"
name: ops
history: "error"
register: result
failed_when: result.changed or (result.failed and "Invalid value 'error' for argument 'history'" not in result.msg)
- name: Ensure invalid values raise proper error for argument priority
ipapwpolicy:
ipaadmin_password: SomeADMINpassword
ipaapi_context: "{{ ipa_context | default(omit) }}"
name: ops
priority: "error"
register: result
failed_when: result.changed or (result.failed and "Invalid value 'error' for argument 'priority'" not in result.msg)
- name: Ensure invalid values raise proper error for argument lockouttime
ipapwpolicy:
ipaadmin_password: SomeADMINpassword
ipaapi_context: "{{ ipa_context | default(omit) }}"
name: ops
lockouttime: "error"
register: result
failed_when: result.changed or (result.failed and "Invalid value 'error' for argument 'lockouttime'" not in result.msg)
- name: Ensure invalid values raise proper error for argument minlength
ipapwpolicy:
ipaadmin_password: SomeADMINpassword
ipaapi_context: "{{ ipa_context | default(omit) }}"
name: ops
minlength: "error"
register: result
failed_when: result.changed or (result.failed and "Invalid value 'error' for argument 'minlength'" not in result.msg)
- name: Ensure invalid values raise proper error for argument minclasses
ipapwpolicy:
ipaadmin_password: SomeADMINpassword
ipaapi_context: "{{ ipa_context | default(omit) }}"
name: ops
minclasses: "error"
register: result
failed_when: result.changed or (result.failed and "Invalid value 'error' for argument 'minclasses'" not in result.msg)
- name: Ensure invalid values raise proper error for argument maxfail
ipapwpolicy:
ipaadmin_password: SomeADMINpassword
ipaapi_context: "{{ ipa_context | default(omit) }}"
name: ops
maxfail: "error"
register: result
failed_when: result.changed or (result.failed and "Invalid value 'error' for argument 'maxfail'" not in result.msg)
- name: Ensure invalid values raise proper error for argument failinterval
ipapwpolicy:
ipaadmin_password: SomeADMINpassword
ipaapi_context: "{{ ipa_context | default(omit) }}"
name: ops
failinterval: "error"
register: result
failed_when: result.changed or (result.failed and "Invalid value 'error' for argument 'failinterval'" not in result.msg)
- name: Ensure invalid values for dictcheck raise proper error.
ipapwpolicy:
ipaadmin_password: SomeADMINpassword
ipaapi_context: "{{ ipa_context | default(omit) }}"
name: ops
dictcheck: "error"
register: result
failed_when: result.changed or (result.failed and "Invalid value 'error' for argument 'dictcheck" not in result.msg)
when: ipa_version is version("4.9", ">=")
- name: Ensure invalid values for usercheck raise proper error.
ipapwpolicy:
ipaadmin_password: SomeADMINpassword
ipaapi_context: "{{ ipa_context | default(omit) }}"
name: ops
usercheck: "error"
register: result
failed_when: result.changed or (result.failed and "Invalid value 'error' for argument 'usercheck'" not in result.msg)
when: ipa_version is version("4.9", ">=")
- name: Ensure invalid values for gracelimit raise proper error.
ipapwpolicy:
ipaadmin_password: SomeADMINpassword
ipaapi_context: "{{ ipa_context | default(omit) }}"
name: ops
gracelimit: "error"
register: result
failed_when: result.changed or (result.failed and "Invalid value 'error' for argument 'gracelimit'" not in result.msg)
when: ipa_version is version("4.9.10", ">=")
- name: Ensure absence of group ops
ipagroup:
ipaadmin_password: SomeADMINpassword
ipaapi_context: "{{ ipa_context | default(omit) }}"
name: ops
state: absent
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment