Extend IPA pwquality plugin to include libpwquality support

Add options to support maxrepeat, maxsequence, dictcheck and
usercheck pwquality options.

https://pagure.io/freeipa/issue/6964
https://pagure.io/freeipa/issue/5948
https://pagure.io/freeipa/issue/2445
https://pagure.io/freeipa/issue/298

Signed-off-by: Rob Crittenden <rcritten@redhat.com>
Reviewed-By: Alexander Bokovoy <abokovoy@redhat.com>
Reviewed-By: Christian Heimes <cheimes@redhat.com>
This commit is contained in:
Rob Crittenden
2020-09-22 16:43:50 -04:00
parent 41021c278a
commit 6b452e5404
4 changed files with 115 additions and 14 deletions

View File

@@ -227,13 +227,13 @@ aci: (targetattr = "businesscategory || cn || createtimestamp || description ||
dn: cn=privileges,cn=pbac,dc=ipa,dc=example
aci: (targetfilter = "(objectclass=groupofnames)")(version 3.0;acl "permission:System: Remove Privileges";allow (delete) groupdn = "ldap:///cn=System: Remove Privileges,cn=permissions,cn=pbac,dc=ipa,dc=example";)
dn: cn=IPA.EXAMPLE,cn=kerberos,dc=ipa,dc=example
aci: (targetfilter = "(objectclass=krbpwdpolicy)")(version 3.0;acl "permission:System: Add Group Password Policy";allow (add) groupdn = "ldap:///cn=System: Add Group Password Policy,cn=permissions,cn=pbac,dc=ipa,dc=example";)
aci: (targetfilter = "(|(objectclass=ipapwdpolicy)(objectclass=krbpwdpolicy))")(version 3.0;acl "permission:System: Add Group Password Policy";allow (add) groupdn = "ldap:///cn=System: Add Group Password Policy,cn=permissions,cn=pbac,dc=ipa,dc=example";)
dn: cn=IPA.EXAMPLE,cn=kerberos,dc=ipa,dc=example
aci: (targetfilter = "(objectclass=krbpwdpolicy)")(version 3.0;acl "permission:System: Delete Group Password Policy";allow (delete) groupdn = "ldap:///cn=System: Delete Group Password Policy,cn=permissions,cn=pbac,dc=ipa,dc=example";)
aci: (targetfilter = "(|(objectclass=ipapwdpolicy)(objectclass=krbpwdpolicy))")(version 3.0;acl "permission:System: Delete Group Password Policy";allow (delete) groupdn = "ldap:///cn=System: Delete Group Password Policy,cn=permissions,cn=pbac,dc=ipa,dc=example";)
dn: cn=IPA.EXAMPLE,cn=kerberos,dc=ipa,dc=example
aci: (targetattr = "krbmaxpwdlife || krbminpwdlife || krbpwdfailurecountinterval || krbpwdhistorylength || krbpwdlockoutduration || krbpwdmaxfailure || krbpwdmindiffchars || krbpwdminlength")(targetfilter = "(objectclass=krbpwdpolicy)")(version 3.0;acl "permission:System: Modify Group Password Policy";allow (write) groupdn = "ldap:///cn=System: Modify Group Password Policy,cn=permissions,cn=pbac,dc=ipa,dc=example";)
aci: (targetattr = "ipapwddictcheck || ipapwdmaxrepeat || ipapwdmaxsequence || ipapwdusercheck || krbmaxpwdlife || krbminpwdlife || krbpwdfailurecountinterval || krbpwdhistorylength || krbpwdlockoutduration || krbpwdmaxfailure || krbpwdmindiffchars || krbpwdminlength")(targetfilter = "(|(objectclass=ipapwdpolicy)(objectclass=krbpwdpolicy))")(version 3.0;acl "permission:System: Modify Group Password Policy";allow (write) groupdn = "ldap:///cn=System: Modify Group Password Policy,cn=permissions,cn=pbac,dc=ipa,dc=example";)
dn: cn=IPA.EXAMPLE,cn=kerberos,dc=ipa,dc=example
aci: (targetattr = "cn || cospriority || createtimestamp || entryusn || krbmaxpwdlife || krbminpwdlife || krbpwdfailurecountinterval || krbpwdhistorylength || krbpwdlockoutduration || krbpwdmaxfailure || krbpwdmindiffchars || krbpwdminlength || modifytimestamp || objectclass")(targetfilter = "(objectclass=krbpwdpolicy)")(version 3.0;acl "permission:System: Read Group Password Policy";allow (compare,read,search) groupdn = "ldap:///cn=System: Read Group Password Policy,cn=permissions,cn=pbac,dc=ipa,dc=example";)
aci: (targetattr = "cn || cospriority || createtimestamp || entryusn || ipapwddictcheck || ipapwdmaxrepeat || ipapwdmaxsequence || ipapwdusercheck || krbmaxpwdlife || krbminpwdlife || krbpwdfailurecountinterval || krbpwdhistorylength || krbpwdlockoutduration || krbpwdmaxfailure || krbpwdmindiffchars || krbpwdminlength || modifytimestamp || objectclass")(targetfilter = "(|(objectclass=ipapwdpolicy)(objectclass=krbpwdpolicy))")(version 3.0;acl "permission:System: Read Group Password Policy";allow (compare,read,search) groupdn = "ldap:///cn=System: Read Group Password Policy,cn=permissions,cn=pbac,dc=ipa,dc=example";)
dn: cn=radiusproxy,dc=ipa,dc=example
aci: (targetattr = "cn || createtimestamp || description || entryusn || ipatokenradiusretries || ipatokenradiusserver || ipatokenradiustimeout || ipatokenusermapattribute || modifytimestamp || objectclass")(targetfilter = "(objectclass=ipatokenradiusconfiguration)")(version 3.0;acl "permission:System: Read Radius Servers";allow (compare,read,search) groupdn = "ldap:///cn=System: Read Radius Servers,cn=permissions,cn=pbac,dc=ipa,dc=example";)
dn: cn=Realm Domains,cn=ipa,cn=etc,dc=ipa,dc=example

18
API.txt
View File

@@ -3960,11 +3960,15 @@ output: Entry('result')
output: Output('summary', type=[<type 'unicode'>, <type 'NoneType'>])
output: PrimaryKey('value')
command: pwpolicy_add/1
args: 1,14,3
args: 1,18,3
arg: Str('cn', cli_name='group')
option: Str('addattr*', cli_name='addattr')
option: Flag('all', autofill=True, cli_name='all', default=False)
option: Int('cospriority', cli_name='priority')
option: Bool('ipapwddictcheck?', cli_name='dictcheck', default=False)
option: Int('ipapwdmaxrepeat?', cli_name='maxrepeat', default=0)
option: Int('ipapwdmaxsequence?', cli_name='maxsequence', default=0)
option: Bool('ipapwdusercheck?', cli_name='usercheck', default=False)
option: Int('krbmaxpwdlife?', cli_name='maxlife')
option: Int('krbminpwdlife?', cli_name='minlife')
option: Int('krbpwdfailurecountinterval?', cli_name='failinterval')
@@ -3988,11 +3992,15 @@ output: Output('result', type=[<type 'dict'>])
output: Output('summary', type=[<type 'unicode'>, <type 'NoneType'>])
output: ListOfPrimaryKeys('value')
command: pwpolicy_find/1
args: 1,16,4
args: 1,20,4
arg: Str('criteria?')
option: Flag('all', autofill=True, cli_name='all', default=False)
option: Str('cn?', autofill=False, cli_name='group')
option: Int('cospriority?', autofill=False, cli_name='priority')
option: Bool('ipapwddictcheck?', autofill=False, cli_name='dictcheck', default=False)
option: Int('ipapwdmaxrepeat?', autofill=False, cli_name='maxrepeat', default=0)
option: Int('ipapwdmaxsequence?', autofill=False, cli_name='maxsequence', default=0)
option: Bool('ipapwdusercheck?', autofill=False, cli_name='usercheck', default=False)
option: Int('krbmaxpwdlife?', autofill=False, cli_name='maxlife')
option: Int('krbminpwdlife?', autofill=False, cli_name='minlife')
option: Int('krbpwdfailurecountinterval?', autofill=False, cli_name='failinterval')
@@ -4011,12 +4019,16 @@ output: ListOfEntries('result')
output: Output('summary', type=[<type 'unicode'>, <type 'NoneType'>])
output: Output('truncated', type=[<type 'bool'>])
command: pwpolicy_mod/1
args: 1,16,3
args: 1,20,3
arg: Str('cn?', cli_name='group')
option: Str('addattr*', cli_name='addattr')
option: Flag('all', autofill=True, cli_name='all', default=False)
option: Int('cospriority?', autofill=False, cli_name='priority')
option: Str('delattr*', cli_name='delattr')
option: Bool('ipapwddictcheck?', autofill=False, cli_name='dictcheck', default=False)
option: Int('ipapwdmaxrepeat?', autofill=False, cli_name='maxrepeat', default=0)
option: Int('ipapwdmaxsequence?', autofill=False, cli_name='maxsequence', default=0)
option: Bool('ipapwdusercheck?', autofill=False, cli_name='usercheck', default=False)
option: Int('krbmaxpwdlife?', autofill=False, cli_name='maxlife')
option: Int('krbminpwdlife?', autofill=False, cli_name='minlife')
option: Int('krbpwdfailurecountinterval?', autofill=False, cli_name='failinterval')

View File

@@ -86,8 +86,8 @@ define(IPA_DATA_VERSION, 20100614120000)
# #
########################################################
define(IPA_API_VERSION_MAJOR, 2)
define(IPA_API_VERSION_MINOR, 239)
# Last change: allow ID overrides for users to be members of groups and roles
define(IPA_API_VERSION_MINOR, 240)
# Last change: add pwquality options to pwpolicy
########################################################

View File

@@ -21,7 +21,7 @@
import logging
from ipalib import api
from ipalib import Int, Str, DNParam
from ipalib import Int, Str, DNParam, Bool
from ipalib import errors
from .baseldap import (
LDAPObject,
@@ -236,13 +236,15 @@ class pwpolicy(LDAPObject):
container_dn = DN(('cn', api.env.realm), ('cn', 'kerberos'))
object_name = _('password policy')
object_name_plural = _('password policies')
object_class = ['top', 'nscontainer', 'krbpwdpolicy']
permission_filter_objectclasses = ['krbpwdpolicy']
object_class = ['top', 'nscontainer', 'krbpwdpolicy', 'ipapwdpolicy']
permission_filter_objectclasses = ['krbpwdpolicy', 'ipapwdpolicy']
default_attributes = [
'cn', 'cospriority', 'krbmaxpwdlife', 'krbminpwdlife',
'krbpwdhistorylength', 'krbpwdmindiffchars', 'krbpwdminlength',
'krbpwdmaxfailure', 'krbpwdfailurecountinterval',
'krbpwdlockoutduration',
'krbpwdlockoutduration', 'ipapwdmaxrepeat',
'ipapwdmaxsequence', 'ipapwddictcheck',
'ipapwdusercheck',
]
managed_permissions = {
'System: Read Group Password Policy': {
@@ -254,6 +256,8 @@ class pwpolicy(LDAPObject):
'krbpwdfailurecountinterval', 'krbpwdhistorylength',
'krbpwdlockoutduration', 'krbpwdmaxfailure',
'krbpwdmindiffchars', 'krbpwdminlength', 'objectclass',
'ipapwdmaxrepeat', 'ipapwdmaxsequence', 'ipapwddictcheck',
'ipapwdusercheck',
},
'default_privileges': {
'Password Policy Readers',
@@ -279,7 +283,9 @@ class pwpolicy(LDAPObject):
'ipapermdefaultattr': {
'krbmaxpwdlife', 'krbminpwdlife', 'krbpwdfailurecountinterval',
'krbpwdhistorylength', 'krbpwdlockoutduration',
'krbpwdmaxfailure', 'krbpwdmindiffchars', 'krbpwdminlength'
'krbpwdmaxfailure', 'krbpwdmindiffchars', 'krbpwdminlength',
'ipapwdmaxrepeat', 'ipapwdmaxsequence', 'ipapwddictcheck',
'ipapwdusercheck',
},
'replaces': [
'(targetattr = "krbmaxpwdlife || krbminpwdlife || krbpwdhistorylength || krbpwdmindiffchars || krbpwdminlength || krbpwdmaxfailure || krbpwdfailurecountinterval || krbpwdlockoutduration")(target = "ldap:///cn=*,cn=$REALM,cn=kerberos,$SUFFIX")(version 3.0;acl "permission:Modify Group Password Policy";allow (write) groupdn = "ldap:///cn=Modify Group Password Policy,cn=permissions,cn=pbac,$SUFFIX";)',
@@ -358,6 +364,38 @@ class pwpolicy(LDAPObject):
doc=_('Period for which lockout is enforced (seconds)'),
minvalue=0,
),
Int(
'ipapwdmaxrepeat?',
cli_name='maxrepeat',
label=_('Max repeat'),
doc=_('Maximum number of same consecutive characters'),
minvalue=0,
maxvalue=256,
default=0,
),
Int(
'ipapwdmaxsequence?',
cli_name='maxsequence',
label=_('Max sequence'),
doc=_('The max. length of monotonic character sequences (abcd)'),
minvalue=0,
maxvalue=256,
default=0,
),
Bool(
'ipapwddictcheck?',
cli_name='dictcheck',
label=_('Dictionary check'),
doc=_('Check if the password is a dictionary word'),
default=False,
),
Bool(
'ipapwdusercheck?',
cli_name='usercheck',
label=_('User check'),
doc=_('Check if the password contains the username'),
default=False,
),
)
def get_dn(self, *keys, **options):
@@ -387,6 +425,51 @@ class pwpolicy(LDAPObject):
if 'krbminpwdlife' in entry_attrs and entry_attrs['krbminpwdlife']:
entry_attrs['krbminpwdlife'] = entry_attrs['krbminpwdlife'] * 3600
def validate_minlength(self, ldap, entry_attrs, add=False, *keys):
"""
If any of the libpwquality options are used then the minimum
length must be >= 6 which is the built-in default of libpwquality.
Allowing a lower value to be set will result in a failed policy
check and a generic error message.
"""
def get_val(entry, attr):
"""Get a single value from a list or a string"""
val = entry.get(attr, 0)
if isinstance(val, list):
val = val[0]
return val
def has_pwquality_set(entry):
for attr in ['ipapwdmaxrepeat', 'ipapwdmaxsequence',
'ipapwddictcheck', 'ipapwdusercheck']:
val = get_val(entry, attr)
if val not in ('FALSE', '0', 0, None):
return True
return False
has_pwquality_value = False
if not add:
if len(keys) > 0:
existing_entry = self.api.Command.pwpolicy_show(
keys[-1], all=True,)['result']
else:
existing_entry = self.api.Command.pwpolicy_show(
all=True,)['result']
existing_entry.update(entry_attrs)
min_length = int(get_val(existing_entry, 'krbpwdminlength'))
has_pwquality_value = has_pwquality_set(existing_entry)
else:
min_length = int(get_val(entry_attrs, 'krbpwdminlength'))
has_pwquality_value = has_pwquality_set(entry_attrs)
if min_length and min_length < 6 and has_pwquality_value:
raise errors.ValidationError(
name='minlength',
error=_('Minimum length must be >= 6 if maxrepeat, '
'maxsequence, dictcheck or usercheck are defined')
)
def validate_lifetime(self, entry_attrs, add=False, *keys):
"""
Ensure that the maximum lifetime is greater than the minimum.
@@ -435,6 +518,7 @@ class pwpolicy_add(LDAPCreate):
assert isinstance(dn, DN)
self.obj.convert_time_on_input(entry_attrs)
self.obj.validate_lifetime(entry_attrs, True)
self.obj.validate_minlength(ldap, entry_attrs, True)
self.api.Command.cosentry_add(
keys[-1], krbpwdpolicyreference=dn,
cospriority=options.get('cospriority')
@@ -486,7 +570,12 @@ class pwpolicy_mod(LDAPUpdate):
def pre_callback(self, ldap, dn, entry_attrs, attrs_list, *keys, **options):
assert isinstance(dn, DN)
old_entry_attrs = ldap.get_entry(dn, ['objectclass'])
if 'ipapwdpolicy' not in old_entry_attrs['objectclass']:
old_entry_attrs['objectclass'].append('ipapwdpolicy')
entry_attrs['objectclass'] = old_entry_attrs['objectclass']
self.obj.convert_time_on_input(entry_attrs)
self.obj.validate_minlength(ldap, entry_attrs, False, *keys)
self.obj.validate_lifetime(entry_attrs, False, *keys)
setattr(context, 'cosupdate', False)
if options.get('cospriority') is not None: