On Friday, 2 December 2011 01:40:22 Nick Urbanik wrote:
> Dear Chris,
> 
> On 01/12/11 07:12 +0000, Chris Ridd wrote:
> >On 30 Nov 2011, at 22:21, Nick Urbanik wrote:
> >> Dear Folks,
> >> 
> >> I am writing Perl software to manage our OpenLDAP cluster using the
> >> slapo.ppolicy overlay to manage password policy.  I'm hoping to get
> >> this into production very soon.

I haven't had time to do all I wanted with this, but I developed a simple 
Catalyst app for managing users for an open-source project, covering 
registration, some user self-service, some minimal user/group management. I 
wanted to add some features regarding password expiry, locked out users etc., 
but the password change / reset feature does use/support ppolicy.

I should probably also revert some of the theming changes and host it in a 
more neutral place, but you can find it here:

http://svnweb.mageia.org/soft/identity/CatDap/trunk/

The 'stable' instance runs here:
https://identity.mageia.org/

I can probably hook up a demo one somewhere if you don't want to register on 
this one to see what it looks like after login ...

I hope to be able to spend some time on it this month to address needs of my 
$dayjob.

There is a long list of issues that still need to be addressed:
https://wiki.mageia.org/en/Web_Identity
but it is useful in its current form, and I did most development just to get 
us to the point where we could centralise all user accessible apps (such as 
the wiki where this page is) in one place.

I would be interested to know what you are doing/using and whether it is open 
source or not.

> >> 
> >> My problem: how to efficiently search for users who have expired?
> >> 
> >> I have seen the code written by Buchan Milne at
> >> http://staff.telkomsa.net/~bgmilne/ldap/, but he is munging with the
> >> operational attributes directly.


That statement is a bit unfair. The script to search for users whose passwords 
have expired does search on the ppolicy attributes directly (there seems to be 
no other way), but the password changing script available from the same 
location uses only Net::LDAP::Control::PasswordPolicy (and doesn't rely on any 
specific attributes).

At present I don't believe there is any way to use the ppolicy overlay to list 
users with expired passwords. I believe there was talk of having a compare 
operation with PasswordPolicy control attached be useful for determining if a 
specific user's password had expired, but this would still require one 
operation per user, whereas one search per policy is sufficient if we use the 
ppolicy attributes.

> >> The code I've written so far has
> >> mostly avoided working with these directly; I've been using
> >> Net::LDAP::Control::PasswordPolicy so far, and am trying to understand
> >> how to construct a suitable filter using this control.
> >> 
> >> Can anyone point me in the right direction?
> >
> >The PP control is very much meant for telling a user about *his*
> >password status. If you combined it with proxy auth, you could
> >imagine writing something that iterated through every user, and did
> >some kind of operation (something simple like whoami perhaps)
> >proxying as that user and requesting PP status back.
> >
> >That may give you what you want. It may even work! :-)
> 
> Well, I have thousands of users, and simply want to create a filter
> that will fetch the unexpired users who match various other criteria.
> 
> >I suspect Buchan's approach is better as it doesn't involve iterating
> >through each user and the attributes it is using are defined in the
> >same draft as the PP control, i.e. reasonably standardised.
> 
> The problem is that I cannot construct one filter that works for
> everyone, because I have multiple policies; I have code like this:
> 
> sub _read_ppolicy_times {
>      my ( $self ) = @_;
>      my $ldap = _ldapopen $self or return;
>      my $search = _search $self, $ldap, $POLICY_DN,
> '(objectclass=pwdPolicy)', 'sub', [ qw( cn pwdMaxAge pwdExpireWarning
> pwdGraceAuthnLimit ) ] or return;
>      if ( $search->code() or $search->count() == 0 ) {
>          _log_trans_fail $self, $ERR_LDAP_SEARCH_FAIL, 'cannot find ANY
> ppolicy'; return;
>      }
>      my %expires;
>      foreach my $entry ( $search->entries() ) {
>          my $pwd_max_age = $entry->get_value( 'pwdMaxAge' ) or next;
> 
>          $expires{ $entry->get_value( 'cn' ) }
>              = strftime '%Y%m%d%H%M%SZ', gmtime( time - $pwd_max_age );
>      }
>      return \%expires;
> }
> 
> And then I conduct a search,

You don't show what search you are doing, but you should construct a search 
filter based on the current date, the expiry/warn time of your policy. This is 
what my code does (granted, a lot of the code in this script is ugly, but I 
think the approach is correct).

It has something like this:
mesg = $ldap->search(base => $config{'basedn'},
                filter => '(objectclass=pwdPolicy)',
                attrs => 
['cn','pwdMaxAge','pwdExpireWarning','pwdGraceAuthnLimit'],
        );
$mesg->code && carp $mesg->error;
foreach my $entry ($mesg->entries) 

[...]

        if ( $policydn eq $config{'defaultpolicy'} ) {
                $filter = "(&(|(!(pwdPolicySubEntry=*))
(pwdPolicySubEntry=$config{'defaultpolicy'}))(pwdChangedTime<=$filterwarn)
(pwdChangedTime>=$filterexpire))";
        } else {
                $filter = "(&(pwdPolicySubEntry=$policydn)
(pwdChangedTime<=$filterwarn)(pwdChangedTime>=$filterexpire))"
        }
        $mesg = $ldap->search(base => $config{'basedn'},
                filter => $filter,
                attrs => ['cn', 'uid', 'mail', 'pwdChangedTime', 
'pwdGraceUseTime'],
        );

> and loop through the entries removing
> those that are expired, like this:
> 
>      foreach my $entry ( $search->entries() ) {
>          my $ppolicy = _dns_to_cns $entry->get_value( 'pwdPolicySubentry'
> ); my $last_changed = $entry->get_value( 'pwdChangedTime' );
> 
>          if ( exists $expires->{$ppolicy} ) {
>              next if $no_expired and $expires->{$ppolicy} > $last_changed;
>          }
>          else {
>              $self->log( "Cannot find '$ppolicy' in " . Dumper( $expires )
> ) }
>          push @entries, $entry;
>      }
> 
> This is ugly, especially when I want to have other code to fetch only
> users who *have* expired.  Do I really need to do this sort of thing?
> The server knows which ones have expired;

Not really. It has an overlay that intercepts specific operations (bind, 
passmod, modify) and evaluates the password policy only for these operations. 
It doesn't maintain a list of which DNs have expired or similar.

> it would be great if I had a
> way of asking it to filter them out for me.

Ah, but you can to some degree ... although you do need to search once per 
policy (most of my environments have two to 5 policies).


Regards,
Buchan

Reply via email to