- Set passwordgracelimit to match global policy on group pw policies

- Fix dns resolver for nameservers with ports
- webui: Allow grace login limit
- Disabling gracelimit does not prevent LDAP binds
This commit is contained in:
Thomas Woerner 2022-08-24 13:22:14 +02:00
parent 4c13a8ea64
commit 7ca049e5b2
5 changed files with 560 additions and 1 deletions

View File

@ -0,0 +1,125 @@
From 1bb4ff9ed2313fb3c2bd1418258c5bcec557b6a5 Mon Sep 17 00:00:00 2001
From: Rob Crittenden <rcritten@redhat.com>
Date: Thu, 21 Jul 2022 09:28:46 -0400
Subject: [PATCH] Disabling gracelimit does not prevent LDAP binds
Originally the code treated 0 as disabled. This was
changed during the review process to -1 but one remnant
was missed effetively allowing gracelimit 0 to also mean
disabled.
Add explicit tests for testing with gracelimit = 0 and
gracelimit = -1.
Also remove some extranous "str(self.master.domain.basedn)"
lines from some of the tests.
Fixes: https://pagure.io/freeipa/issue/9206
Signed-off-by: Rob Crittenden <rcritten@redhat.com>
Reviewed-By: Francisco Trivino <ftrivino@redhat.com>
---
.../ipa-graceperiod/ipa_graceperiod.c | 2 +-
ipatests/test_integration/test_pwpolicy.py | 55 ++++++++++++++++++-
2 files changed, 53 insertions(+), 4 deletions(-)
diff --git a/daemons/ipa-slapi-plugins/ipa-graceperiod/ipa_graceperiod.c b/daemons/ipa-slapi-plugins/ipa-graceperiod/ipa_graceperiod.c
index a3f57cb4b..345e1dee7 100644
--- a/daemons/ipa-slapi-plugins/ipa-graceperiod/ipa_graceperiod.c
+++ b/daemons/ipa-slapi-plugins/ipa-graceperiod/ipa_graceperiod.c
@@ -479,7 +479,7 @@ static int ipagraceperiod_preop(Slapi_PBlock *pb)
if (pwresponse_requested) {
slapi_pwpolicy_make_response_control(pb, -1, grace_limit - grace_user_time , -1);
}
- } else if ((grace_limit > 0) && (grace_user_time >= grace_limit)) {
+ } else if (grace_user_time >= grace_limit) {
LOG_TRACE("%s password is expired and out of grace limit\n", dn);
errstr = "Password is expired.\n";
ret = LDAP_INVALID_CREDENTIALS;
diff --git a/ipatests/test_integration/test_pwpolicy.py b/ipatests/test_integration/test_pwpolicy.py
index 6d6698284..41d6e9070 100644
--- a/ipatests/test_integration/test_pwpolicy.py
+++ b/ipatests/test_integration/test_pwpolicy.py
@@ -36,7 +36,7 @@ class TestPWPolicy(IntegrationTest):
cls.master.run_command(['ipa', 'group-add-member', POLICY,
'--users', USER])
cls.master.run_command(['ipa', 'pwpolicy-add', POLICY,
- '--priority', '1'])
+ '--priority', '1', '--gracelimit', '-1'])
cls.master.run_command(['ipa', 'passwd', USER],
stdin_text='{password}\n{password}\n'.format(
password=PASSWORD
@@ -265,7 +265,6 @@ class TestPWPolicy(IntegrationTest):
def test_graceperiod_expired(self):
"""Test the LDAP bind grace period"""
- str(self.master.domain.basedn)
dn = "uid={user},cn=users,cn=accounts,{base_dn}".format(
user=USER, base_dn=str(self.master.domain.basedn))
@@ -308,7 +307,6 @@ class TestPWPolicy(IntegrationTest):
def test_graceperiod_not_replicated(self):
"""Test that the grace period is reset on password reset"""
- str(self.master.domain.basedn)
dn = "uid={user},cn=users,cn=accounts,{base_dn}".format(
user=USER, base_dn=str(self.master.domain.basedn))
@@ -341,3 +339,54 @@ class TestPWPolicy(IntegrationTest):
)
assert 'passwordgraceusertime: 0' in result.stdout_text.lower()
self.reset_password(self.master)
+
+ def test_graceperiod_zero(self):
+ """Test the LDAP bind with zero grace period"""
+ dn = "uid={user},cn=users,cn=accounts,{base_dn}".format(
+ user=USER, base_dn=str(self.master.domain.basedn))
+
+ self.master.run_command(
+ ["ipa", "pwpolicy-mod", POLICY, "--gracelimit", "0", ],
+ )
+
+ # Resetting the password will mark it as expired
+ self.reset_password(self.master)
+
+ # Now grace is done and binds should fail.
+ result = self.master.run_command(
+ ["ldapsearch", "-e", "ppolicy", "-D", dn,
+ "-w", PASSWORD, "-b", dn], raiseonerr=False
+ )
+ assert result.returncode == 49
+
+ assert 'Password is expired' in result.stderr_text
+ assert 'Password expired, 0 grace logins remain' in result.stderr_text
+
+ def test_graceperiod_disabled(self):
+ """Test the LDAP bind with grace period disabled (-1)"""
+ str(self.master.domain.basedn)
+ dn = "uid={user},cn=users,cn=accounts,{base_dn}".format(
+ user=USER, base_dn=str(self.master.domain.basedn))
+
+ # This can fail if gracelimit is already -1 so ignore it
+ self.master.run_command(
+ ["ipa", "pwpolicy-mod", POLICY, "--gracelimit", "-1",],
+ raiseonerr=False,
+ )
+
+ # Ensure the password is expired
+ self.reset_password(self.master)
+
+ result = self.kinit_as_user(self.master, PASSWORD, PASSWORD)
+
+ for _i in range(0, 10):
+ result = self.master.run_command(
+ ["ldapsearch", "-e", "ppolicy", "-D", dn,
+ "-w", PASSWORD, "-b", dn]
+ )
+
+ # With graceperiod disabled it should not increment
+ result = tasks.ldapsearch_dm(
+ self.master, dn, ['passwordgraceusertime',],
+ )
+ assert 'passwordgraceusertime: 0' in result.stdout_text.lower()
--
2.37.1

View File

@ -0,0 +1,56 @@
From 7a1e1d9f1cb13679c28f12d05b156a08bcc4d856 Mon Sep 17 00:00:00 2001
From: Carla Martinez <carlmart@redhat.com>
Date: Fri, 29 Jul 2022 13:16:16 +0200
Subject: [PATCH] webui: Allow grace login limit
There was no support for setting the grace login limit on the WebUI. The
only way to so was only via CLI:
`ipa pwpolicy-mod --gracelimit=2 global_policy`
Thus, the grace login limit must be updated from the policy section and
this will reflect also on the user settings (under the 'Password Policy'
section)
Fixes: https://pagure.io/freeipa/issue/9211
Signed-off-by: Carla Martinez <carlmart@redhat.com>
Reviewed-By: Florence Blanc-Renaud <flo@redhat.com>
---
install/ui/src/freeipa/policy.js | 3 +++
install/ui/src/freeipa/user.js | 5 +++++
2 files changed, 8 insertions(+)
diff --git a/install/ui/src/freeipa/policy.js b/install/ui/src/freeipa/policy.js
index fa2028a52..7ec103636 100644
--- a/install/ui/src/freeipa/policy.js
+++ b/install/ui/src/freeipa/policy.js
@@ -72,6 +72,9 @@ return {
{
name: 'cospriority',
required: true
+ },
+ {
+ name: 'passwordgracelimit'
}
]
}]
diff --git a/install/ui/src/freeipa/user.js b/install/ui/src/freeipa/user.js
index a580db035..b47c97f72 100644
--- a/install/ui/src/freeipa/user.js
+++ b/install/ui/src/freeipa/user.js
@@ -318,6 +318,11 @@ return {
label: '@mo-param:pwpolicy:krbpwdlockoutduration:label',
read_only: true,
measurement_unit: 'seconds'
+ },
+ {
+ name: 'passwordgracelimit',
+ label: '@mo-param:pwpolicy:passwordgracelimit:label',
+ read_only: true
}
]
},
--
2.37.1

View File

@ -0,0 +1,137 @@
From 77803587d6e15b41a66fc0ee0a87ad55ee196dfe Mon Sep 17 00:00:00 2001
From: Thomas Woerner <twoerner@redhat.com>
Date: Wed, 3 Aug 2022 18:22:47 +0200
Subject: [PATCH] DNSResolver: Fix use of nameservers with ports
IPA DNS zone and forwardzone commands allow to use nameservers with ports
as "SERVER_IP port PORT_NUMBER". bind is supporting this syntax, but the
Resolver in dnspython that is used to verify the list of forwarders
(nameservers) is only allowing to have IP addresses in this list. With
dnspython version 2.20 there is a new validator in dns.resolver.BaseResolver
that ensures this.
Refs:
- https://bind9.readthedocs.io/en/v9_18_4/reference.html#zone-statement-grammar
- https://github.com/rthalley/dnspython/blob/master/dns/resolver.py#L1094
ipapython/dnsutil.DNSResolver derives from dns.resolver.Resolver. The setter
for nameservers has been overloaded in the DNSResolver class to split out
the port numbers into the nameserver_ports dict { SERVER_IP: PORT_NUMBER }.
After the setter for nameservers succeeded, nameserver_ports is set.
nameserver_ports is used in the resolve() method of dns.resolver.Resolver.
Additional tests have been added to verify that nameservers and also
nameserver_ports are properly set and also valid.
Fixes: https://pagure.io/freeipa/issue/9158
Signed-off-by: Thomas Woerner <twoerner@redhat.com>
Reviewed-By: Alexander Bokovoy <abokovoy@redhat.com>
---
ipapython/dnsutil.py | 41 +++++++++++++++++++++++++
ipatests/test_ipapython/test_dnsutil.py | 40 ++++++++++++++++++++++++
2 files changed, 81 insertions(+)
diff --git a/ipapython/dnsutil.py b/ipapython/dnsutil.py
index 4baeaf8cc..58de365ab 100644
--- a/ipapython/dnsutil.py
+++ b/ipapython/dnsutil.py
@@ -144,6 +144,47 @@ class DNSResolver(dns.resolver.Resolver):
nameservers.remove(ipv4_loopback)
self.nameservers = nameservers
+ @dns.resolver.Resolver.nameservers.setter
+ def nameservers(self, nameservers):
+ """
+ *nameservers*, a ``list`` of nameservers with optional ports:
+ "SERVER_IP port PORT_NUMBER".
+
+ Overloads dns.resolver.Resolver.nameservers setter to split off ports
+ into nameserver_ports after setting nameservers successfully with the
+ setter in dns.resolver.Resolver.
+ """
+ # Get nameserver_ports if it is already set
+ if hasattr(self, "nameserver_ports"):
+ nameserver_ports = self.nameserver_ports
+ else:
+ nameserver_ports = {}
+
+ # Check nameserver items in list and split out converted port number
+ # into nameserver_ports: { nameserver: port }
+ if isinstance(nameservers, list):
+ _nameservers = []
+ for nameserver in nameservers:
+ splits = nameserver.split()
+ if len(splits) == 3 and splits[1] == "port":
+ nameserver = splits[0]
+ try:
+ port = int(splits[2])
+ if port < 0 or port > 65535:
+ raise ValueError()
+ except ValueError:
+ raise ValueError(
+ "invalid nameserver: %s is not a valid port" %
+ splits[2])
+ nameserver_ports[nameserver] = port
+ _nameservers.append(nameserver)
+ nameservers = _nameservers
+
+ # Call dns.resolver.Resolver.nameservers setter
+ dns.resolver.Resolver.nameservers.__set__(self, nameservers)
+ # Set nameserver_ports after successfull call to setter
+ self.nameserver_ports = nameserver_ports
+
class DNSZoneAlreadyExists(dns.exception.DNSException):
supp_kwargs = {'zone', 'ns'}
diff --git a/ipatests/test_ipapython/test_dnsutil.py b/ipatests/test_ipapython/test_dnsutil.py
index 5e7a46197..09463c69d 100644
--- a/ipatests/test_ipapython/test_dnsutil.py
+++ b/ipatests/test_ipapython/test_dnsutil.py
@@ -101,3 +101,43 @@ class TestSortURI:
assert dnsutil.sort_prio_weight([h3, h2, h1]) == [h1, h2, h3]
assert dnsutil.sort_prio_weight([h3, h3, h3]) == [h3]
assert dnsutil.sort_prio_weight([h2, h2, h1, h1]) == [h1, h2]
+
+
+class TestDNSResolver:
+ def test_nameservers(self):
+ res = dnsutil.DNSResolver()
+ res.nameservers = ["4.4.4.4", "8.8.8.8"]
+ assert res.nameservers == ["4.4.4.4", "8.8.8.8"]
+
+ def test_nameservers_with_ports(self):
+ res = dnsutil.DNSResolver()
+ res.nameservers = ["4.4.4.4 port 53", "8.8.8.8 port 8053"]
+ assert res.nameservers == ["4.4.4.4", "8.8.8.8"]
+ assert res.nameserver_ports == {"4.4.4.4": 53, "8.8.8.8": 8053}
+
+ res.nameservers = ["4.4.4.4 port 53", "8.8.8.8 port 8053"]
+ assert res.nameservers == ["4.4.4.4", "8.8.8.8"]
+ assert res.nameserver_ports == {"4.4.4.4": 53, "8.8.8.8": 8053}
+
+ def test_nameservers_with_bad_ports(self):
+ res = dnsutil.DNSResolver()
+ try:
+ res.nameservers = ["4.4.4.4 port a"]
+ except ValueError:
+ pass
+ else:
+ pytest.fail("No fail on bad port a")
+
+ try:
+ res.nameservers = ["4.4.4.4 port -1"]
+ except ValueError:
+ pass
+ else:
+ pytest.fail("No fail on bad port -1")
+
+ try:
+ res.nameservers = ["4.4.4.4 port 65536"]
+ except ValueError:
+ pass
+ else:
+ pytest.fail("No fail on bad port 65536")
--
2.37.1

View File

@ -0,0 +1,231 @@
From 1aa39529cda4ab9620539dbad705cedd23c21b42 Mon Sep 17 00:00:00 2001
From: Rob Crittenden <rcritten@redhat.com>
Date: Thu, 18 Aug 2022 08:21:58 -0400
Subject: [PATCH] doc: Update LDAP grace period design with default values
New group password policies will get -1 (unlimited) on creation
by default.
Existing group password policies will remain untouched and
those created prior will be treated as no BIND allowed.
Fixes: https://pagure.io/freeipa/issue/9212
Signed-off-by: Rob Crittenden <rcritten@redhat.com>
Reviewed-By: Florence Blanc-Renaud <frenaud@redhat.com>
---
doc/designs/ldap_grace_period.md | 17 ++++++++++++++++-
1 file changed, 16 insertions(+), 1 deletion(-)
diff --git a/doc/designs/ldap_grace_period.md b/doc/designs/ldap_grace_period.md
index 4b9db3424..e26aedda9 100644
--- a/doc/designs/ldap_grace_period.md
+++ b/doc/designs/ldap_grace_period.md
@@ -51,7 +51,22 @@ The basic flow is:
On successful password reset (by anyone) reset the user's passwordGraceUserTime to 0.
-The default value on install/upgrade will be -1 to retail existing behavior.
+Range values for passwordgracelimit are:
+
+-1 : password grace checking is disabled
+ 0 : no grace BIND are allowed at all post-expiration
+ 1..MAXINT: the number of BIND allowed post-expiration
+
+The default value for the global policy on install/upgrade will be -1 to
+retain existing behavior.
+
+New group password policies will default to -1 to retain previous
+behavior.
+
+Existing group policies with no grace limit set are updated to use
+the default unlimited value, -1. This is done because lack of value in
+LDAP is treated as 0 so any existing group policies would not allow
+post-expiration BIND so this will avoid confusion.
The per-user attempts will not be replicated.
--
2.37.1
From 45e6d49b94da78cd82eb016b3266a17a1359a087 Mon Sep 17 00:00:00 2001
From: Rob Crittenden <rcritten@redhat.com>
Date: Thu, 4 Aug 2022 12:04:22 -0400
Subject: [PATCH 1/2] Set default gracelimit on group password policies to -1
This will retain previous behavior of unlimited LDAP BIND
post-expiration.
Fixes: https://pagure.io/freeipa/issue/9212
Signed-off-by: Rob Crittenden <rcritten@redhat.com>
Reviewed-By: Florence Blanc-Renaud <frenaud@redhat.com>
---
API.txt | 2 +-
ipaserver/plugins/pwpolicy.py | 2 ++
ipatests/test_xmlrpc/test_pwpolicy_plugin.py | 2 ++
3 files changed, 5 insertions(+), 1 deletion(-)
diff --git a/API.txt b/API.txt
index 66929b921..210bfc495 100644
--- a/API.txt
+++ b/API.txt
@@ -4076,7 +4076,7 @@ option: Int('krbpwdlockoutduration?', cli_name='lockouttime')
option: Int('krbpwdmaxfailure?', cli_name='maxfail')
option: Int('krbpwdmindiffchars?', cli_name='minclasses')
option: Int('krbpwdminlength?', cli_name='minlength')
-option: Int('passwordgracelimit?', cli_name='gracelimit', default=-1)
+option: Int('passwordgracelimit?', autofill=True, cli_name='gracelimit', default=-1)
option: Flag('raw', autofill=True, cli_name='raw', default=False)
option: Str('setattr*', cli_name='setattr')
option: Str('version?')
diff --git a/ipaserver/plugins/pwpolicy.py b/ipaserver/plugins/pwpolicy.py
index 4428aede2..f4ebffd5c 100644
--- a/ipaserver/plugins/pwpolicy.py
+++ b/ipaserver/plugins/pwpolicy.py
@@ -408,6 +408,7 @@ class pwpolicy(LDAPObject):
minvalue=-1,
maxvalue=Int.MAX_UINT32,
default=-1,
+ autofill=True,
),
)
@@ -539,6 +540,7 @@ class pwpolicy_add(LDAPCreate):
keys[-1], krbpwdpolicyreference=dn,
cospriority=options.get('cospriority')
)
+
return dn
def post_callback(self, ldap, dn, entry_attrs, *keys, **options):
diff --git a/ipatests/test_xmlrpc/test_pwpolicy_plugin.py b/ipatests/test_xmlrpc/test_pwpolicy_plugin.py
index 8eee69c18..fc785223b 100644
--- a/ipatests/test_xmlrpc/test_pwpolicy_plugin.py
+++ b/ipatests/test_xmlrpc/test_pwpolicy_plugin.py
@@ -387,6 +387,7 @@ class test_pwpolicy_mod_cospriority(Declarative):
krbpwdhistorylength=[u'10'],
krbpwdmindiffchars=[u'3'],
krbpwdminlength=[u'8'],
+ passwordgracelimit=[u'-1'],
objectclass=objectclasses.pwpolicy,
),
summary=None,
@@ -417,6 +418,7 @@ class test_pwpolicy_mod_cospriority(Declarative):
krbpwdhistorylength=[u'10'],
krbpwdmindiffchars=[u'3'],
krbpwdminlength=[u'8'],
+ passwordgracelimit=[u'-1'],
),
summary=None,
value=u'ipausers',
--
2.37.1
From de6f074538f6641fd9d84bed204a3d4d50eccbe5 Mon Sep 17 00:00:00 2001
From: Rob Crittenden <rcritten@redhat.com>
Date: Thu, 4 Aug 2022 12:04:41 -0400
Subject: [PATCH 2/2] Set default on group pwpolicy with no grace limit in
upgrade
If an existing group policy lacks a password grace limit
update it to -1 on upgrade.
Fixes: https://pagure.io/freeipa/issue/9212
Signed-off-by: Rob Crittenden <rcritten@redhat.com>
Reviewed-By: Florence Blanc-Renaud <frenaud@redhat.com>
---
.../updates/90-post_upgrade_plugins.update | 1 +
ipaserver/install/plugins/update_pwpolicy.py | 66 +++++++++++++++++++
2 files changed, 67 insertions(+)
diff --git a/install/updates/90-post_upgrade_plugins.update b/install/updates/90-post_upgrade_plugins.update
index c7ec71d49..6fe91aa6c 100644
--- a/install/updates/90-post_upgrade_plugins.update
+++ b/install/updates/90-post_upgrade_plugins.update
@@ -26,6 +26,7 @@ plugin: update_ra_cert_store
plugin: update_mapping_Guests_to_nobody
plugin: fix_kra_people_entry
plugin: update_pwpolicy
+plugin: update_pwpolicy_grace
# last
# DNS version 1
diff --git a/ipaserver/install/plugins/update_pwpolicy.py b/ipaserver/install/plugins/update_pwpolicy.py
index dca44ce43..4185f0343 100644
--- a/ipaserver/install/plugins/update_pwpolicy.py
+++ b/ipaserver/install/plugins/update_pwpolicy.py
@@ -78,3 +78,69 @@ class update_pwpolicy(Updater):
return False, []
return False, []
+
+
+@register()
+class update_pwpolicy_grace(Updater):
+ """
+ Ensure all group policies have a grace period set.
+ """
+
+ def execute(self, **options):
+ ldap = self.api.Backend.ldap2
+
+ base_dn = DN(('cn', self.api.env.realm), ('cn', 'kerberos'),
+ self.api.env.basedn)
+ search_filter = (
+ "(&(objectClass=krbpwdpolicy)(!(passwordgracelimit=*)))"
+ )
+
+ while True:
+ # Run the search in loop to avoid issues when LDAP limits are hit
+ # during update
+
+ try:
+ (entries, truncated) = ldap.find_entries(
+ search_filter, ['objectclass'], base_dn, time_limit=0,
+ size_limit=0)
+
+ except errors.EmptyResult:
+ logger.debug("update_pwpolicy: no policies without "
+ "passwordgracelimit set")
+ return False, []
+
+ except errors.ExecutionError as e:
+ logger.error("update_pwpolicy: cannot retrieve list "
+ "of policies missing passwordgracelimit: %s", e)
+ return False, []
+
+ logger.debug("update_pwpolicy: found %d "
+ "policies to update, truncated: %s",
+ len(entries), truncated)
+
+ error = False
+
+ for entry in entries:
+ # Set unlimited BIND by default
+ entry['passwordgracelimit'] = -1
+ try:
+ ldap.update_entry(entry)
+ except (errors.EmptyModlist, errors.NotFound):
+ pass
+ except errors.ExecutionError as e:
+ logger.debug("update_pwpolicy: cannot "
+ "update policy: %s", e)
+ error = True
+
+ if error:
+ # Exit loop to avoid infinite cycles
+ logger.error("update_pwpolicy: error(s) "
+ "detected during pwpolicy update")
+ return False, []
+
+ elif not truncated:
+ # All affected entries updated, exit the loop
+ logger.debug("update_pwpolicy: all policies updated")
+ return False, []
+
+ return False, []
--
2.37.1

View File

@ -188,7 +188,7 @@
Name: %{package_name}
Version: %{IPA_VERSION}
Release: 3%{?rc_version:.%rc_version}%{?dist}
Release: 4%{?rc_version:.%rc_version}%{?dist}
Summary: The Identity, Policy and Audit system
License: GPLv3+
@ -206,6 +206,10 @@ Source1: https://releases.pagure.org/freeipa/freeipa-%{version}%{?rc_vers
# RHEL spec file only: END: Change branding to IPA and Identity Management
Patch0001: 0001-Only-calculate-LDAP-password-grace-when-the-password.patch
Patch0002: 0002-Disabling-gracelimit-does-not-prevent-LDAP-binds.patch
Patch0003: 0003-webui-Allow-grace-login-limit.patch
Patch0004: 0004-DNSResolver-Fix-use-of-nameservers-with-ports.patch
Patch0005: 0005_Set_passwordgracelimit_to_match_global_policy_on_group_pw_policies_#6419.patch
# RHEL spec file only: START
%if %{NON_DEVELOPER_BUILD}
@ -1718,6 +1722,12 @@ fi
%endif
%changelog
* Wed Aug 24 2022 Thomas Woerner <twoerner@redhat.com> - 4.10.0-4
- Disabling gracelimit does not prevent LDAP binds
- webui: Allow grace login limit
- Fix dns resolver for nameservers with ports
- Set passwordgracelimit to match global policy on group pw policies
* Tue Aug 09 2022 Adam Williamson <awilliam@redhat.com> - 4.10.0-3
- Rebuild against new libndr