]> git.ipfire.org Git - thirdparty/openldap.git/commitdiff
ITS#9343 Allow a list of default policies
authorOndřej Kuzník <ondra@mistotebe.net>
Tue, 8 Feb 2022 16:46:59 +0000 (16:46 +0000)
committerQuanah Gibson-Mount <quanah@openldap.org>
Mon, 7 Mar 2022 14:54:39 +0000 (14:54 +0000)
doc/man/man5/slapo-ppolicy.5
servers/slapd/overlays/ppolicy.c
tests/data/ppolicy.ldif
tests/data/slapd-ppolicy.conf
tests/scripts/test022-ppolicy

index 1aa2a4f500817003787df4036a42446fd37a13ed..a57ae603382398ecb3798b5b74c530f339976d84 100644 (file)
@@ -55,9 +55,20 @@ after the
 .B overlay
 directive.
 .TP
+.B ppolicy_rules <LDAP URI> <policyDN>
+Specify which pwdPolicy object to use when no specific policy is set on
+a given user's entry. If there is no pwdPolicySubentry set, the URIs are
+checked in order and the first one to match will apply. If one is selected
+and the object at
+.B policyDN
+does not exist or is not a password policy, then no policies will be
+enforced.
+.TP
 .B ppolicy_default <policyDN>
 Specify the DN of the pwdPolicy object to use when no specific policy is
-set on a given user's entry. If there is no specific policy for an entry
+set on a given user's entry and none of the
+.B ppolicy_rules
+apply. If there is no specific policy for an entry
 and no default is given, then no policies will be enforced.
 .TP
 .B ppolicy_forward_updates
index f36124a67f3a52ef0e36160c29adbd1d7ae21784..66b2ec0baed56797e2470fbcc8b5312a26c86321 100644 (file)
 typedef        int (check_func)( char *passwd, struct berval *errmsg, Entry *ent, struct berval *arg );
 #define ERRBUFSIZ      256
 
+typedef struct policy_rule {
+       struct berval uri; /* straight from configuration, unparsed below */
+       struct berval base;
+       int scope;
+       Filter *filter;
+       struct berval policy_dn; /* DN of policy entry to select */
+       struct policy_rule *next;
+} policy_rule;
+
 /* Per-instance configuration information */
 typedef struct pp_info {
        struct berval def_policy;       /* DN of default policy subentry */
+       struct policy_rule *policy_rules;
        int use_lockout;                /* send AccountLocked result? */
        int hash_passwords;             /* transparently hash cleartext pwds */
        int forward_updates;    /* use frontend for policy state updates */
@@ -444,9 +454,10 @@ enum {
        PPOLICY_USE_LOCKOUT,
        PPOLICY_DISABLE_WRITE,
        PPOLICY_CHECK_MODULE,
+       PPOLICY_DEFAULT_RULES,
 };
 
-static ConfigDriver ppolicy_cf_default, ppolicy_cf_checkmod;
+static ConfigDriver ppolicy_cf_default, ppolicy_cf_rule, ppolicy_cf_checkmod;
 
 static ConfigTable ppolicycfg[] = {
        { "ppolicy_default", "policyDN", 2, 2, 0,
@@ -501,6 +512,13 @@ static ConfigTable ppolicycfg[] = {
          "EQUALITY caseExactIA5Match "
          "SYNTAX OMsIA5String "
          "SINGLE-VALUE )", NULL, NULL },
+       { "ppolicy_rules", "URL> <policyDN", 3, 3, 0,
+         ARG_QUOTE|ARG_MAGIC|PPOLICY_DEFAULT_RULES,
+         ppolicy_cf_rule,
+         "( OLcfgOvAt:12.8 NAME 'olcPPolicyRules' "
+         "DESC 'rules to apply the right ppolicy object for entry' "
+         "EQUALITY caseIgnoreMatch "
+         "SYNTAX OMsDirectoryString X-ORDERED 'VALUES' )", NULL, NULL },
        { NULL, NULL, 0, 0, 0, ARG_IGNORED }
 };
 
@@ -512,7 +530,7 @@ static ConfigOCs ppolicyocs[] = {
          "MAY ( olcPPolicyDefault $ olcPPolicyHashCleartext $ "
          "olcPPolicyUseLockout $ olcPPolicyForwardUpdates $ "
          "olcPPolicyDisableWrite $ olcPPolicySendNetscapeControls $ "
-         "olcPPolicyCheckModule ) )",
+         "olcPPolicyCheckModule $ olcPPolicyRules ) )",
          Cft_Overlay, ppolicycfg },
        { NULL, 0, NULL }
 };
@@ -568,6 +586,122 @@ ppolicy_cf_default( ConfigArgs *c )
        return rc;
 }
 
+static int
+ppolicy_cf_rule( ConfigArgs *c )
+{
+       slap_overinst *on = (slap_overinst *)c->bi;
+       pp_info *pi = (pp_info *)on->on_bi.bi_private;
+       policy_rule *pr = NULL, **prp;
+       LDAPURLDesc *lud = NULL;
+       struct berval bv;
+       int i, rc = ARG_BAD_CONF;
+
+       assert( c->type == PPOLICY_DEFAULT_RULES );
+       Debug( LDAP_DEBUG_TRACE, "==> ppolicy_rules\n" );
+
+       if ( c->op == SLAP_CONFIG_EMIT ) {
+               if ( pi->policy_rules ) {
+                       Attribute a = {
+                               .a_desc = c->ca_desc->ad,
+                               .a_vals = c->rvalue_vals,
+                       };
+
+                       for ( pr = pi->policy_rules; pr; pr = pr->next ) {
+                               bv.bv_len = pr->uri.bv_len + pr->policy_dn.bv_len +
+                                       STRLENOF("\"\" \"\"");
+                               bv.bv_val = ch_malloc( bv.bv_len + 1 );
+
+                               snprintf( bv.bv_val, bv.bv_len + 1, "\"%s\" \"%s\"",
+                                               pr->uri.bv_val, pr->policy_dn.bv_val );
+                               ber_bvarray_add( &a.a_vals, &bv );
+                               a.a_numvals++;
+                       }
+
+                       ordered_value_renumber( &a );
+                       c->rvalue_vals = a.a_vals;
+                       return LDAP_SUCCESS;
+               }
+               return 1;
+       } else if ( c->op == LDAP_MOD_DELETE ) {
+               if ( pi->policy_rules ) {
+                       for ( prp = &pi->policy_rules, i=0; *prp; i++ ) {
+                               pr = *prp;
+
+                               if ( c->valx == -1 || i == c->valx ) {
+                                       *prp = pr->next;
+                                       pr->next = NULL;
+
+                                       ch_free( pr->uri.bv_val );
+                                       ch_free( pr->base.bv_val );
+                                       ch_free( pr->policy_dn.bv_val );
+                                       filter_free( pr->filter );
+                                       ch_free( pr );
+
+                                       if ( i == c->valx )
+                                               break;
+                               } else {
+                                       prp = &pr->next;
+                               }
+                       }
+               }
+               return LDAP_SUCCESS;
+       }
+
+       if ( ldap_url_parse_ext( c->argv[1], &lud, 0 ) != LDAP_SUCCESS ) {
+               snprintf( c->cr_msg, sizeof( c->cr_msg ),
+                               "ppolicy_rules: bad policy URL");
+               return rc;
+       }
+
+       pr = ch_calloc( 1, sizeof(policy_rule) );
+       ber_str2bv( c->argv[1], 0, 1, &pr->uri );
+       pr->scope = lud->lud_scope;
+
+       ber_str2bv( lud->lud_dn, 0, 0, &bv );
+       if ( dnNormalize( 0, NULL, NULL, &bv, &pr->base, NULL ) ) {
+               snprintf( c->cr_msg, sizeof( c->cr_msg ),
+                               "ppolicy_rules: bad URL base" );
+               rc = ARG_BAD_CONF;
+               goto done;
+       }
+
+       if ( lud->lud_filter ) {
+               pr->filter = str2filter( lud->lud_filter );
+               if ( !pr->filter ) {
+                       snprintf( c->cr_msg, sizeof( c->cr_msg ),
+                                       "ppolicy_rules: bad filter" );
+                       rc = ARG_BAD_CONF;
+                       goto done;
+               }
+       }
+
+       ber_str2bv( c->argv[2], 0, 0, &bv );
+       if ( dnNormalize( 0, NULL, NULL, &bv, &pr->policy_dn, NULL ) ) {
+               snprintf( c->cr_msg, sizeof( c->cr_msg ),
+                               "ppolicy_rules: bad policy DN" );
+               rc = ARG_BAD_CONF;
+               goto done;
+       }
+
+       rc = LDAP_SUCCESS;
+       for ( i = 0, prp = &pi->policy_rules;
+               *prp && ( c->valx < 0 || i < c->valx );
+               prp = &(*prp)->next, i++ )
+               /* advance to the desired position */ ;
+       pr->next = *prp;
+       *prp = pr;
+
+done:
+       ldap_free_urldesc( lud );
+       if ( rc != LDAP_SUCCESS ) {
+               ch_free( pr->uri.bv_val );
+               ch_free( pr->policy_dn.bv_val );
+               filter_free( pr->filter );
+               ch_free( pr );
+       }
+       return rc;
+}
+
 #ifdef SLAPD_MODULES
 static int
 ppolicy_cf_checkmod( ConfigArgs *c )
@@ -969,10 +1103,22 @@ ppolicy_get( Operation *op, Entry *e, PassPolicy *pp )
 
        ad = ad_pwdPolicySubentry;
        if ( (a = attr_find( e->e_attrs, ad )) == NULL ) {
+               policy_rule *pr = pi->policy_rules;
                /*
-                * entry has no password policy assigned - use default
+                * entry has no password policy assigned - find the default one
                 */
-               vals = &pi->def_policy;
+               for ( pr = pi->policy_rules; pr; pr = pr->next ) {
+                       if ( !dnIsSuffixScope( &e->e_nname, &pr->base, pr->scope ) ) continue;
+                       if ( pr->filter && test_filter( op, e, pr->filter ) != LDAP_COMPARE_TRUE ) continue;
+
+                       /* We found a match */
+                       break;
+               }
+               if ( pr ) {
+                       vals = &pr->policy_dn;
+               } else {
+                       vals = &pi->def_policy;
+               }
                if ( !vals->bv_val )
                        goto defaultpol;
        } else {
@@ -3358,10 +3504,21 @@ ppolicy_db_destroy(
 {
        slap_overinst *on = (slap_overinst *) be->bd_info;
        pp_info *pi = on->on_bi.bi_private;
+       policy_rule *pr = pi->policy_rules, *next;
 
        on->on_bi.bi_private = NULL;
        ldap_pvt_thread_mutex_destroy( &pi->pwdFailureTime_mutex );
        free( pi->def_policy.bv_val );
+       while ( pr ) {
+               next = pr->next;
+
+               ch_free( pr->uri.bv_val );
+               ch_free( pr->base.bv_val );
+               ch_free( pr->policy_dn.bv_val );
+               filter_free( pr->filter );
+               ch_free( pr );
+               pr = next;
+       }
        free( pi );
 
        ov_count--;
index d4d697dd48b76a64473d132eb17f8539e5f3ad6a..4f2ba936a9f112f24c67020e6dd833247c0178f0 100644 (file)
@@ -36,6 +36,47 @@ pwdFailureCountInterval: 120
 pwdSafeModify: TRUE
 pwdLockout: TRUE
 
+dn: cn=Idle Expiration Policy, ou=Policies, dc=example, dc=com
+objectClass: top
+objectClass: device
+objectClass: pwdPolicy
+cn: Idle Expiration Policy
+pwdAttribute: 2.5.4.35
+pwdLockoutDuration: 15
+pwdInHistory: 6
+pwdCheckQuality: 2
+pwdExpireWarning: 10
+pwdMaxIdle: 15
+pwdMinLength: 5
+pwdMaxLength: 13
+pwdGraceAuthnLimit: 3
+pwdAllowUserChange: TRUE
+pwdMustChange: TRUE
+pwdMaxFailure: 3
+pwdFailureCountInterval: 120
+pwdSafeModify: TRUE
+pwdLockout: TRUE
+
+dn: cn=Stricter Policy, ou=Policies, dc=example, dc=com
+objectClass: top
+objectClass: device
+objectClass: pwdPolicy
+cn: Stricter Policy
+pwdAttribute: 2.5.4.35
+pwdLockoutDuration: 15
+pwdInHistory: 6
+pwdCheckQuality: 2
+pwdExpireWarning: 10
+pwdMaxAge: 15
+pwdMinLength: 5
+pwdMaxLength: 13
+pwdAllowUserChange: TRUE
+pwdMustChange: TRUE
+pwdMaxFailure: 3
+pwdFailureCountInterval: 120
+pwdSafeModify: TRUE
+pwdLockout: TRUE
+
 dn: uid=nd, ou=People, dc=example, dc=com
 objectClass: top
 objectClass: person
index 561e7cefcc2314dd942ee65765074f1632a771c5..a3b4054507544560804831dc914ebb46a54451af 100644 (file)
@@ -38,6 +38,8 @@ rootpw                secret
 lastbind on
 
 overlay                ppolicy
+ppolicy_rules ldap:///uid=ndadmin,ou=People,dc=example,dc=com??base "cn=No Policy,ou=Policies,dc=example,dc=com"
+ppolicy_rules "ldap:///???(description=idle)" "cn=Idle Expiration Policy, ou=Policies, dc=example, dc=com"
 ppolicy_default        "cn=Standard Policy,ou=Policies,dc=example,dc=com"
 ppolicy_use_lockout
 
index 6a9cc6caed6f85da413d37f06339dfee8c54c226..2b2c8887de98c7f7b8242fe52e39696fc4893ae3 100755 (executable)
@@ -244,6 +244,16 @@ fi
 
 echo "Testing failed logins when password/policy missing..."
 
+$LDAPSEARCH -e ppolicy -H $URI1 \
+       -D "$PWADMIN" -w hasnopolicy \
+       -b "$BASEDN" -s base > $SEARCHOUT 2>&1
+RC=$?
+if test $RC = 0 ; then
+       echo "Password accepted ($RC)!"
+       test $KILLSERVERS != no && kill -HUP $KILLPIDS
+       exit 1
+fi
+
 $LDAPSEARCH -e ppolicy -H $URI1 \
        -D "uid=test, ou=People,$BASEDN" -w hasnopolicy \
        -b "$BASEDN" -s base > $SEARCHOUT 2>&1
@@ -470,15 +480,13 @@ if test $RC = 0 ; then
 fi
 
 echo "Testing idle password expiration"
-echo "Reconfiguring policy to replace expiration with idle expiration..."
+echo "Switching to a policy with idle expiration..."
 $LDAPMODIFY -v -D "$MANAGERDN" -H $URI1 -w $PASSWD >> \
        $TESTOUT 2>&1 << EOMODS
-dn: cn=Standard Policy, ou=Policies, dc=example, dc=com
+dn: $USER
 changetype: modify
-delete: pwdMaxAge
--
-add: pwdMaxIdle
-pwdMaxIdle: 15
+add: description
+description: idle
 
 EOMODS
 RC=$?
@@ -508,15 +516,13 @@ if test $RC != 49 ; then
        exit 1
 fi
 
-echo "Reverting policy changes..."
+echo "Reverting to Standard policy..."
 $LDAPMODIFY -v -D "$MANAGERDN" -H $URI1 -w $PASSWD >> \
        $TESTOUT 2>&1 << EOMODS
-dn: cn=Standard Policy, ou=Policies, dc=example, dc=com
+dn: $USER
 changetype: modify
-delete: pwdMaxIdle
--
-add: pwdMaxAge
-pwdMaxAge: 30
+delete: description
+description: idle
 
 EOMODS
 RC=$?
@@ -698,15 +704,29 @@ if test $RC != 0 ; then
 fi
 
 echo "Reconfiguring policy to remove grace logins..."
-$LDAPMODIFY -v -D "$MANAGERDN" -H $URI1 -w $PASSWD >> \
+$LDAPMODIFY -v -D cn=config -H $URI1 -y $CONFIGPWF >> \
        $TESTOUT 2>&1 << EOMODS
-dn: cn=Standard Policy, ou=Policies, dc=example, dc=com
+dn: olcOverlay={0}ppolicy,olcDatabase={1}$BACKEND,cn=config
 changetype: modify
-delete: pwdGraceAuthnLimit
--
-replace: pwdMaxAge
-pwdMaxAge: 15
--
+add: olcPPolicyRules
+olcPPolicyRules: {0}"ldap:///dc=example,dc=com???(!(description=grace))"
+  "cn=Stricter Policy, ou=Policies, dc=example, dc=com"
+
+EOMODS
+RC=$?
+if test $RC != 0 ; then
+       echo "ldapmodify failed ($RC)!"
+       test $KILLSERVERS != no && kill -HUP $KILLPIDS
+       exit $RC
+fi
+
+$LDAPMODIFY -v -D cn=config -H $URI2 -y $CONFIGPWF >> \
+       $TESTOUT 2>&1 << EOMODS
+dn: olcOverlay={0}ppolicy,olcDatabase={1}$BACKEND,cn=config
+changetype: modify
+add: olcPPolicyRules
+olcPPolicyRules: {0}"ldap:///dc=example,dc=com???(!(description=grace))"
+  "cn=Stricter Policy, ou=Policies, dc=example, dc=com"
 
 EOMODS
 RC=$?