Bug 36215 - Deny posix logins if user password expiration date has been reached
Deny posix logins if user password expiration date has been reached
Status: CLOSED FIXED
Product: UCS
Classification: Unclassified
Component: LDAP
UCS 4.1
Other Linux
: P5 normal (vote)
: UCS 4.2
Assigned To: Felix Botner
Arvid Requate
: interim-2
: 36318 (view as bug list)
Depends on: 36317 36318 36319 38494 41786 45760
Blocks: 44189 46350 42114 42267 43384
  Show dependency treegraph
 
Reported: 2014-10-16 15:10 CEST by Sönke Schwardt-Krummrich
Modified: 2018-02-20 18:02 CET (History)
4 users (show)

See Also:
What kind of report is it?: ---
What type of bug is this?: ---
Who will be affected by this bug?: ---
How will those affected feel about the bug?: ---
User Pain:
Enterprise Customer affected?:
School Customer affected?:
ISV affected?:
Waiting Support:
Ticket number:
Bug group (optional):
Max CVSS v3 score:


Attachments
patch for 97_shadowbind_overlay.quilt (3.03 KB, patch)
2017-03-03 11:07 CET, Felix Botner
Details | Diff

Note You need to log in before you can comment on or make changes to this bug.
Description Sönke Schwardt-Krummrich univentionstaff 2014-10-16 15:10:38 CEST
Currently a LDAP bind is possible if a user password has expired/reached the expiration date. Every application using LDAP bind for authentication has to check on its own if the password has expired. There are some 3rd party applications that do not check the shadowLastChange/shadowMax attributes.

There should be an optional cron job that check regulary if a user password has expired and then disables the posix login.
Comment 1 Sönke Schwardt-Krummrich univentionstaff 2014-10-17 16:30:49 CEST
The package univention-ldap now adds the attributes shadowMax and shadowExpire to the LDAP indices:
- shadowMax → pres
- shadowExpire → eq
This way, the object lookup will much faster is large environments. The shadowMax attribute is required for Bug #35088 that has been also fixed.

YAML: 2014-10-17-univention-ldap.yaml

In univention-server-master (src pkg: univention-server) a new cron job has been added that would call the new script
/usr/share/univention-directory-manager-tools/lock_expired_passwords on a daily basis if the cron job is enabled.
The cron job is scheduled for execution every night at 03:23 (this may be changed via UCR but is undocumented on purpose; if unset 03:23 is used).
The cron job may be enabled via directory/manager/user/lock_expired_passwords/cron/enabled. By default, the cron job is disabled.
If enabled, user accounts with expired password will be locked automatically via the cron job and therefore the users are unable to authenticate via e.g. LDAP simple bind.

YAML: 2014-10-17-univention-server.yaml

The script looks for user accounts that have expired user passwords (shadowLastChange + shadowMax <= today). For all accounts found, the POSIX account will be locked so a simple LDAP bind will be no longer possible.
The password expiration date is handled correctly by Windows/kerberos, so no action is required here.

Please note that this script may need a lot of resources. It looks for users with shadowMax=* and then calculates the password expiration date via UDM.

YAML: 2014-10-15-univention-directory-manager-modules.yaml
Comment 3 Sönke Schwardt-Krummrich univentionstaff 2014-10-21 09:44:22 CEST
Changes have been also merged to UCS 4.

(In reply to Stefan Gohmann from comment #3)
> The latest Jenkins test failed:

Disabled some checks due to bug #36210 → FIXED ucs-test
Comment 4 Felix Botner univentionstaff 2014-10-27 11:52:33 CET
I have a user with an expired password. I executed ./lock_expired_passwords to disable the posix account (ldapsearch with that account is no longer possible). A login with that user on UMC still works and the UMC wants me to change the password. All OK up to now. 
But during the password change to posix lock is not removed, ldapsearch i still not possible for that user.
Comment 5 Felix Botner univentionstaff 2014-11-06 12:12:34 CET
please revert the changes, we have decided to move this to 4.0-0-errata
Comment 6 Stefan Gohmann univentionstaff 2014-11-06 13:17:06 CET
Changes have been reverted:

univention-server:
 3.2: r55435 + r55436 + r55439
 4.0: r55437 + r55438

univention-ldap:
 3.2: r55441 + r55442
 I've left the 4.0 change and added a changelog: r55445

univention-directory-manager-modules:
 3.2: r55443 + r55444 + r55446
 I've left the 4.0 change and added a changelog: r55445
Comment 7 Felix Botner univentionstaff 2014-11-07 10:18:27 CET
(In reply to Stefan Gohmann from comment #6)
> Changes have been reverted:
> 
> univention-server:
>  3.2: r55435 + r55436 + r55439
>  4.0: r55437 + r55438
> 
> univention-ldap:
>  3.2: r55441 + r55442
>  I've left the 4.0 change and added a changelog: r55445
> 
> univention-directory-manager-modules:
>  3.2: r55443 + r55444 + r55446
>  I've left the 4.0 change and added a changelog: r55445

ok
Comment 8 Stefan Gohmann univentionstaff 2014-11-07 11:57:53 CET
I've also removed the test cases in 3.2-3 and 3.2-4:
 r55493 + r55494
Comment 9 Stefan Gohmann univentionstaff 2015-06-28 15:20:19 CEST
*** Bug 36318 has been marked as a duplicate of this bug. ***
Comment 10 Felix Botner univentionstaff 2016-09-05 14:00:42 CEST
Added a new slapd overlay shadowmax (97_shadowbind_overlay_rules.patch, 97_shadowbind_overlay.quilt).

This overlay checks shadowExpire and shadowMax/shadowLastChange (of the bindDN object) during LDAP bind and denies the login if
 * now > shadowExpire
 * now > (shadowMax + shadowLastChange)
with LDAP_INSUFFICIENT_ACCESS->"password expired"/"account expired".
The overlay ignores the following objects:
 * rootdn/updatedn
 * objects without the shadowAccount objectClass
 * all objects that match the LDAP filter from the shadowbind-ignore-filter
   config parameter (default is (objectClass=univentionDomainController))

univention-ldap-server: added shadowmax overlay config in 40univention-ldap-server_database

openldap: cherry picked from 4.1 and rebuilt in 4.2 with new patches, built with special version 2.4.42+dfsg-2.9999.201609051147, otherwise the new 4.2 version (from the build-system) is smaller than in 4.1.

-> build-package-ng -P ucs -r 4.2-0-0 --no-pbuilder-update \
   -v 2.4.42+dfsg-2.9999.201609051147 -p openldap

tests:

-> time ucs-test -s ldap -E dangerous

without shadowbind
 fail "Test userPassword with K5KEY in combination with userexpiry."
 real    34m7.517s
 user    3m35.632s
 sys     0m40.872s
with shadowbind
 fail "Test userPassword with K5KEY in combination with userexpiry."
 real    34m22.515s
 user    3m33.060s
 sys     0m39.984s

-> python ldap bind

dn = 'uid=test1,cn=users,dc=w2k12,dc=test'
pw = 'univention1'
lo = ldap.open('10.200.7.150')
for i in xrange(1, 10000):
        try:
                lo.simple_bind_s(dn, pw)
        except ldap.INSUFFICIENT_ACCESS as err:
                pass

with shadowbind
 real    0m31.879s
 user    0m0.416s
 sys     0m0.272s
without shadowbind
 real    0m29.482s
 user    0m0.396s
 sys     0m0.208s


for i in xrange(1, 10000):
        lo, po = univention.admin.uldap.getAdminConnection()
        lo.lo.lo.unbind()

with shadowbind
 real    1m36.172s
 user    1m0.172s
 sys     0m3.480s
without shadowbind
 real    1m42.059s
 user    1m4.584s
 sys     0m3.808s
Comment 11 Florian Best univentionstaff 2017-02-01 12:05:51 CET
We need to fix Bug #41786 and the test case 60_umc/07_expired_password so that UMC can detect the new error messages and respond with an appropriate error.
Comment 12 Florian Best univentionstaff 2017-02-03 13:12:39 CET
debian/univention-ldap-server.postinst:»   ldap/shadowbind?true

This is by default set to true. Is this correct?
Comment 13 Florian Best univentionstaff 2017-02-21 11:11:34 CET
We have various places in the code which expect ldap.INVALID_CREDENTIALS instead of ldap.INSUFFICIENT_ACCESS (e.g. the UMC-Server crashes, the Self-Service doesn't function correctly anymore and raises Tracebacks to the frontend).
Why is that error code used? Is ldap.INVALID_CREDENTIALS incorrect? Should we adapt uldap.py to catch this also?
Comment 14 Felix Botner univentionstaff 2017-02-21 11:34:37 CET
(In reply to Florian Best from comment #12)
> debian/univention-ldap-server.postinst:»   ldap/shadowbind?true
> 
> This is by default set to true. Is this correct?

no, should be deactivated by default

(In reply to Florian Best from comment #13)
> We have various places in the code which expect ldap.INVALID_CREDENTIALS
> instead of ldap.INSUFFICIENT_ACCESS (e.g. the UMC-Server crashes, the
> Self-Service doesn't function correctly anymore and raises Tracebacks to the
> frontend).
> Why is that error code used? 

I thought it is more appropriate.

> Is ldap.INVALID_CREDENTIALS incorrect? 

I a certain way, yes, the credentials are NOT invalid, they account expired.

> Should we adapt uldap.py to catch this also?

We can change from ldap.INSUFFICIENT_ACCESS to ldap.INVALID_CREDENTIALS is that make things easier, please ask Stefan.
Comment 15 Florian Best univentionstaff 2017-02-21 11:45:15 CET
The description for both these status codes is:

===
0x31	49	LDAP_INVALID_CREDENTIALS	IESG	RFC 4511	DSA	Indicates that during a Bind Request operation one of the following occurred:
* The client passed either an incorrect DN or password.
* The password is incorrect because it has expired, Intruder Detection has locked the account, or some other similar reason.

0x32	50	LDAP_INSUFFICIENT_ACCESS	IESG	RFC 4511	DSA	Indicates that the caller does not have sufficient rights to perform the requested operation.
===
https://ldapwiki.com/wiki/LDAP%20Result%20Codes

Or this one:

===
49: Invalid Credentials

This indicates that the client attempted to bind as a user that does not exist, attempted to bind as a user that is not allowed to bind (e.g., because it has expired, because it has been locked because of too many failed authentication attempts, etc.), or attempted to bind with credentials that were not correct for the target user.

50: Insufficient Access Rights

This indicates that the client does not have permission to perform the requested operation.

https://www.ldap.com/ldap-result-code-reference
====


I think 49 is more correct as the password/account is expired and this happens during bind(). 50 looks more like an failing operation due to missing ACLs.
Comment 16 Arvid Requate univentionstaff 2017-02-21 11:45:50 CET
Which of both error codes does the OpenLDAP code return and in which situation?
Comment 17 Florian Best univentionstaff 2017-02-21 11:50:05 CET
(In reply to Arvid Requate from comment #16)
> Which of both error codes does the OpenLDAP code return and in which
> situation?
INVALID_CREDENTIALS during bind, INSUFFICIENT_ACCESS during ACL problems:

UCS 4.1:

>>> import univention.uldap
>>> x = univention.uldap.access(host='localhost')
>>> x.bind('cn=foo', 'bar')
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "/usr/lib/pymodules/python2.7/univention/uldap.py", line 165, in bind
    self.lo.simple_bind_s(self.binddn, self.__encode_pwd(self.bindpw))
  File "/usr/lib/python2.7/dist-packages/ldap/ldapobject.py", line 881, in simple_bind_s
    res = self._apply_method_s(SimpleLDAPObject.simple_bind_s,*args,**kwargs)
  File "/usr/lib/python2.7/dist-packages/ldap/ldapobject.py", line 862, in _apply_method_s
    return func(self,*args,**kwargs)
  File "/usr/lib/python2.7/dist-packages/ldap/ldapobject.py", line 215, in simple_bind_s
    resp_type, resp_data, resp_msgid, resp_ctrls = self.result3(msgid,all=1,timeout=self.timeout)
  File "/usr/lib/python2.7/dist-packages/ldap/ldapobject.py", line 476, in result3
    resp_ctrl_classes=resp_ctrl_classes
  File "/usr/lib/python2.7/dist-packages/ldap/ldapobject.py", line 483, in result4
    ldap_result = self._ldap_call(self._l.result4,msgid,all,timeout,add_ctrls,add_intermediates,add_extop)
  File "/usr/lib/python2.7/dist-packages/ldap/ldapobject.py", line 106, in _ldap_call
    result = func(*args,**kwargs)
ldap.INVALID_CREDENTIALS: {'desc': 'Invalid credentials'}

>>> x = univention.uldap.getMachineConnection()
>>> x.modify('cn=admin,dc=school,dc=local', [('objectClass', None, 'person')])
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "/usr/lib/pymodules/python2.7/univention/uldap.py", line 468, in modify
    self.modify_s(dn, ml)
  File "/usr/lib/pymodules/python2.7/univention/uldap.py", line 494, in modify_s
    self.lo.modify_s(dn, ml)
  File "/usr/lib/python2.7/dist-packages/ldap/ldapobject.py", line 364, in modify_s
    return self.result(msgid,all=1,timeout=self.timeout)
  File "/usr/lib/python2.7/dist-packages/ldap/ldapobject.py", line 465, in result
    resp_type, resp_data, resp_msgid = self.result2(msgid,all,timeout)
  File "/usr/lib/python2.7/dist-packages/ldap/ldapobject.py", line 469, in result2
    resp_type, resp_data, resp_msgid, resp_ctrls = self.result3(msgid,all,timeout)
  File "/usr/lib/python2.7/dist-packages/ldap/ldapobject.py", line 476, in result3
    resp_ctrl_classes=resp_ctrl_classes
  File "/usr/lib/python2.7/dist-packages/ldap/ldapobject.py", line 483, in result4
    ldap_result = self._ldap_call(self._l.result4,msgid,all,timeout,add_ctrls,add_intermediates,add_extop)
  File "/usr/lib/python2.7/dist-packages/ldap/ldapobject.py", line 106, in _ldap_call
    result = func(*args,**kwargs)
ldap.INSUFFICIENT_ACCESS: {'desc': 'Insufficient access'}
Comment 18 Arvid Requate univentionstaff 2017-02-21 12:58:18 CET
Ok that's a clear point for ldap.INVALID_CREDENTIALS I think.
Comment 19 Felix Botner univentionstaff 2017-02-21 13:09:59 CET
(In reply to Arvid Requate from comment #18)
> Ok that's a clear point for ldap.INVALID_CREDENTIALS I think.

yep, changed the overlay to return LDAP_INVALID_CREDENTIALS
Comment 20 Stefan Gohmann univentionstaff 2017-02-21 14:20:05 CET
(In reply to Felix Botner from comment #14)
> (In reply to Florian Best from comment #12)
> > debian/univention-ldap-server.postinst:»   ldap/shadowbind?true
> > 
> > This is by default set to true. Is this correct?
> 
> no, should be deactivated by default

As discussed, it should be enabled for new installed systems by default and deactivated for updated systems. Please add a note to the "Postprocessing of the update" chapter.
Comment 21 Felix Botner univentionstaff 2017-02-21 15:15:31 CET
(In reply to Stefan Gohmann from comment #20)
> (In reply to Felix Botner from comment #14)
> > (In reply to Florian Best from comment #12)
> > > debian/univention-ldap-server.postinst:»   ldap/shadowbind?true
> > > 
> > > This is by default set to true. Is this correct?
> > 
> > no, should be deactivated by default
> 
> As discussed, it should be enabled for new installed systems by default and
> deactivated for updated systems. Please add a note to the "Postprocessing of
> the update" chapter.

changelog/release-notes-4.2-0-de.xml
changelog/release-notes-4.2-0-en.xml
r76908 

univention-ldap: activate/deactivate ldap/shadowbind on installation/update
Comment 22 Felix Botner univentionstaff 2017-03-02 18:31:46 CET
@arvid  i have applied your patch and rebuilt openldap (2.4.42+dfsg-2.A~4.2.0.201703021814)
Comment 23 Arvid Requate univentionstaff 2017-03-02 20:58:03 CET
Looks pretty good, in the normal Samba/AD setup it works:

* udm policies/pwhistory modify --dn ... --set expiryInterval=10
* create a user directly in samba via samba-tool -> shadowLastChange is set
* artificially modify  shadowLastChange to 11 days earlier
* => ldapsearch denied
* kapsswd on that user => shadowLastChange is updated
* => ldapsearch works again


Now for the corner case we discussed: we probably need to exclude cases where
 userPassword: {KINIT}

This happens in AD-Connector mode:

On a UCS system configured as AD-member I adjusted the default UDM password policy to 10 days max (via UMC). When I create a new user in AD, the S4-Connector uses the UDM users/user module which apparently writes shadowLastChange and shadowMax:
==================================================================
dn: uid=wuser3,cn=users,dc=w2k8r2d2,dc=ar
uid: wuser3
krb5PrincipalName: wuser3@W2K8R2D2.AR
objectClass: krb5KDCEntry
objectClass: person
objectClass: automount
objectClass: top
objectClass: inetOrgPerson
objectClass: sambaSamAccount
objectClass: organizationalPerson
objectClass: univentionPWHistory
objectClass: univentionMail
objectClass: univentionSAMLEnabled
objectClass: shadowAccount
objectClass: krb5Principal
objectClass: posixAccount
objectClass: univentionObject
uidNumber: 2008
sambaAcctFlags: [U          ]
shadowMax: 10
krb5MaxLife: 86400
shadowLastChange: 17227
cn: wuser3 wname3
krb5PasswordEnd: 20170312000000Z
krb5Key: ...
krb5MaxRenew: 604800
krb5KeyVersionNumber: 1
loginShell: /bin/bash
univentionObjectType: users/user
krb5KDCFlags: 126
sambaPwdLastSet: 1488477369
sambaPasswordHistory: ...
displayName: wuser3 wname3
sambaSID: S-1-5-21-584594710-3545396725-2272022506-5016
gecos: wuser3 wname3
sn: wname3
pwhistory: ...
homeDirectory: /home/wuser3
givenName: wuser3
gidNumber: 5001
sambaPrimaryGroupSID: S-1-5-21-584594710-3545396725-2272022506-513
userPassword: {KINIT}
sambaNTPassword: NO PASSWORD*********************
univentionObjectFlag: synced
==================================================================

These values never get changed, even when I run kpasswd on that user.
As a test I've set shadowLastChange to 17227 - 11 = 17216:


==================================================================
root@master110:~# ldapsearch -D 'uid=wuser3,cn=users,dc=w2k8r2d2,dc=ar' \
                  -w Univention.2 -b "dc=w2k8r2d2,dc=ar" -s base
ldap_bind: Invalid credentials (49)
        additional info: password expired

root@master110:~# kpasswd wuser3
wuser3@W2K8R2D2.AR's Password: 
New password for wuser3@W2K8R2D2.AR: 
Verify password - New password for wuser3@W2K8R2D2.AR: 
Success

root@master110:~# ldapsearch -D 'uid=wuser3,cn=users,dc=w2k8r2d2,dc=ar' \
                  -w Univention.3 -b "dc=w2k8r2d2,dc=ar" -s base
ldap_bind: Invalid credentials (49)
        additional info: password expired
==================================================================
Comment 24 Felix Botner univentionstaff 2017-03-03 11:07:53 CET
Created attachment 8492 [details]
patch for 97_shadowbind_overlay.quilt

OK, here is a patch to add a test for userPassword={KINIT} and skip the shadow tests.

But i wonder if we should just modify to default shadowbind-ignore-filter to 
"(|(objectClass=univentionDomainController)(userPassword={KINIT}))"
to be more flexible. 

The pwd_scheme_kinit is activated by default, even without member mode. So are we sure {KINIT} is only used in member mode (shadow tests may make sense, if KINIT is actiavted but UCS is used as kerberos server)
Comment 25 Sönke Schwardt-Krummrich univentionstaff 2017-03-03 11:36:01 CET
Would be great if the checks for password expiration and account expiration could be activated independently. I see UCS@school+CSP scenarios where account expiration is desired but password expiration would lead to problems.
Comment 26 Felix Botner univentionstaff 2017-03-06 12:06:04 CET
(In reply to Sönke Schwardt-Krummrich from comment #25)
> Would be great if the checks for password expiration and account expiration
> could be activated independently. I see UCS@school+CSP scenarios where
> account expiration is desired but password expiration would lead to problems.

Yes, good point, but i think 4.2-0-errata is enough (see Bug #
43725, or?


(In reply to Arvid Requate from comment #23)
> Now for the corner case we discussed: we probably need to exclude cases where
>  userPassword: {KINIT}
> ...

As discusses, i changed the default ignore filter to also ignore userPassword={KINIT} objects.
Comment 27 Florian Best univentionstaff 2017-03-13 19:38:28 CET
The whole logic for the password and account expiry parsing is also solved by pam_ldap.

This overlay module will conflict with pam_ldap as now the bind() always fails but pam_ldap makes a bind() and can't detect then anymore if e.g. the account is expired, the password is expired, …. pam_ldap is therefore unable to respond with PAM_ACCT_EXPIRED / PAM_PERM_DENIED / PAM_AUTHTOKEN_REQD in PAM acct_mgmt() and chauthtok() is also broken then.
Comment 28 Arvid Requate univentionstaff 2017-03-13 23:23:29 CET
Hmm, maybe we can learn something from the way the ppolicy overlay responds and the way pam_ldap is supposed to handle that?
e.g. https://www.linux.com/blog/openldap-ppolicy-overlay-user-authentication

If we have to home-brew something then maybe it's possible to return some additional info along with the error code and adjust pam_ldap to evaluate that info. That info should only be returned from OpenLDAP if the authentication generally worked.
Comment 29 Felix Botner univentionstaff 2017-03-14 12:39:38 CET
OK, there is maybe a problem with clients using only pam_ldap for auth and account. With the shadowbind overlay activated pam auth no longer works and instead of "password expired" or something like that, they get "invalid credentials" (because pam ldap auth fails instead of pam ldap account). But this also depends on the pam_ldap.conf (see below).

But, UCS uses pam_krb instead of pam_ldap. So i see no problem there

 -> univention-ldapsearch  -D uid=test1,dc=four,dc=two -x -w Univention.99
    ldap_bind: Invalid credentials (49)
        additional info: password expired

-> ssh test1@10.200.7.50
Password: 
You are required to change your password immediately (password aged)
Current Kerberos password: 

 -> UMC password change also works

Maybe we have to recheck http://docs.software-univention.de/domain-4.1.html#ext-dom-ubuntu (auth_provider = krb5, id_provider = ldap)

Besides, even if i remove pam_krb from the pam stack, i get 

 -> su test1
 Passwort: 
 You are required to change your LDAP password immediately.
 su: Fehler beim Ändern des Authentifizierungstoken

from pam_ldap, because (at least in our pam_ldap.conf) we use a rootbindn, which is apparently used for the ldap lookup.

ppolicy:
There is no special handling im pam_ldap for ppolicy.

So is see no problem here.
Comment 30 Florian Best univentionstaff 2017-03-14 12:48:46 CET
(In reply to Felix Botner from comment #29)
> OK, there is maybe a problem with clients using only pam_ldap for auth and
> account. With the shadowbind overlay activated pam auth no longer works and
> instead of "password expired" or something like that, they get "invalid
> credentials" (because pam ldap auth fails instead of pam ldap account). But
> this also depends on the pam_ldap.conf (see below).
Yes

> But, UCS uses pam_krb instead of pam_ldap.
UMC uses pam_ldap (auth and account) for users/user accounts with only the option "Simple authentication account".

> So i see no problem there
> 
>  -> univention-ldapsearch  -D uid=test1,dc=four,dc=two -x -w Univention.99
>     ldap_bind: Invalid credentials (49)
>         additional info: password expired
but yes, this is probably not a problem.
Comment 31 Arvid Requate univentionstaff 2017-03-14 16:08:06 CET
Ok, works, also with UCC. Changelog ok.
Comment 32 Stefan Gohmann univentionstaff 2017-04-04 18:29:00 CEST
UCS 4.2 has been released:
 https://docs.software-univention.de/release-notes-4.2-0-en.html
 https://docs.software-univention.de/release-notes-4.2-0-de.html

If this error occurs again, please use "Clone This Bug".