Blame SOURCES/Fix-LDAP-policy-enforcement-of-pw_expiration.patch

3eb3db
From dc475c320677d767d33603afb1bf4067fd16cfb1 Mon Sep 17 00:00:00 2001
3eb3db
From: Robbie Harwood <rharwood@redhat.com>
3eb3db
Date: Tue, 17 Dec 2019 17:37:41 -0500
3eb3db
Subject: [PATCH] Fix LDAP policy enforcement of pw_expiration
3eb3db
3eb3db
In the LDAP backend, the change mask is used to determine what LDAP
3eb3db
attributes to update.  As a result, password expiration was not set
3eb3db
from policy when running during addprinc, among other issues.
3eb3db
However, when the mask did not contain KADM5_PRINCIPAL, pw_expiration
3eb3db
would be applied regardless, which meant that (for instance) changing
3eb3db
the password would cause the password application to be applied.
3eb3db
3eb3db
Remove the check for KADM5_PRINCIPAL, and fix the mask to contain
3eb3db
KADM5_PW_EXPIRATION where appropriate.  Add a regression test to
3eb3db
t_kdb.py.
3eb3db
3eb3db
[ghudson@mit.edu: also set KADM5_ATTRIBUTES for randkey and setkey
3eb3db
since they both unset KRB5_KDB_REQUIRES_PWCHANGE; edited comments and
3eb3db
commit message]
3eb3db
3eb3db
(cherry picked from commit 6b004dd5739bded71be4290c11e7ac3a816c7e09)
3eb3db
3eb3db
ticket: 8861
3eb3db
version_fixed: 1.17.2
3eb3db
3eb3db
(cherry picked from commit 90a13e6d9763c510339c7a40fbc4ecb30041f421)
3eb3db
---
3eb3db
 src/lib/kadm5/srv/svr_principal.c             | 89 +++++++++----------
3eb3db
 .../kdb/ldap/libkdb_ldap/ldap_principal2.c    | 13 ---
3eb3db
 src/tests/t_kdb.py                            | 17 ++++
3eb3db
 3 files changed, 60 insertions(+), 59 deletions(-)
3eb3db
3eb3db
diff --git a/src/lib/kadm5/srv/svr_principal.c b/src/lib/kadm5/srv/svr_principal.c
3eb3db
index a44d53f03..50db8bbcd 100644
3eb3db
--- a/src/lib/kadm5/srv/svr_principal.c
3eb3db
+++ b/src/lib/kadm5/srv/svr_principal.c
3eb3db
@@ -356,6 +356,11 @@ kadm5_create_principal_3(void *server_handle,
3eb3db
     kdb = calloc(1, sizeof(*kdb));
3eb3db
     if (kdb == NULL)
3eb3db
         return ENOMEM;
3eb3db
+
3eb3db
+    /* In all cases the principal entry is new and key data is set; let the
3eb3db
+     * database provider know. */
3eb3db
+    kdb->mask = mask | KADM5_KEY_DATA | KADM5_PRINCIPAL;
3eb3db
+
3eb3db
     memset(&adb, 0, sizeof(osa_princ_ent_rec));
3eb3db
 
3eb3db
     /*
3eb3db
@@ -405,14 +410,12 @@ kadm5_create_principal_3(void *server_handle,
3eb3db
         kdb->expiration = handle->params.expiration;
3eb3db
 
3eb3db
     kdb->pw_expiration = 0;
3eb3db
-    if (have_polent) {
3eb3db
-        if(polent.pw_max_life)
3eb3db
-            kdb->pw_expiration = ts_incr(now, polent.pw_max_life);
3eb3db
-        else
3eb3db
-            kdb->pw_expiration = 0;
3eb3db
-    }
3eb3db
-    if ((mask & KADM5_PW_EXPIRATION))
3eb3db
+    if (mask & KADM5_PW_EXPIRATION) {
3eb3db
         kdb->pw_expiration = entry->pw_expiration;
3eb3db
+    } else if (have_polent && polent.pw_max_life) {
3eb3db
+        kdb->mask |= KADM5_PW_EXPIRATION;
3eb3db
+        kdb->pw_expiration = ts_incr(now, polent.pw_max_life);
3eb3db
+    }
3eb3db
 
3eb3db
     kdb->last_success = 0;
3eb3db
     kdb->last_failed = 0;
3eb3db
@@ -499,9 +502,6 @@ kadm5_create_principal_3(void *server_handle,
3eb3db
         adb.policy = entry->policy;
3eb3db
     }
3eb3db
 
3eb3db
-    /* In all cases key and the principal data is set, let the database provider know */
3eb3db
-    kdb->mask = mask | KADM5_KEY_DATA | KADM5_PRINCIPAL ;
3eb3db
-
3eb3db
     /* store the new db entry */
3eb3db
     ret = kdb_put_entry(handle, kdb, &adb);
3eb3db
 
3eb3db
@@ -597,6 +597,9 @@ kadm5_modify_principal(void *server_handle,
3eb3db
     if (ret)
3eb3db
         return(ret);
3eb3db
 
3eb3db
+    /* Let the mask propagate to the database provider. */
3eb3db
+    kdb->mask = mask;
3eb3db
+
3eb3db
     /*
3eb3db
      * This is pretty much the same as create ...
3eb3db
      */
3eb3db
@@ -612,11 +615,15 @@ kadm5_modify_principal(void *server_handle,
3eb3db
             free(adb.policy);
3eb3db
         adb.policy = strdup(entry->policy);
3eb3db
     }
3eb3db
-    if (have_pol) {
3eb3db
+
3eb3db
+    if (mask & KADM5_PW_EXPIRATION) {
3eb3db
+        kdb->pw_expiration = entry->pw_expiration;
3eb3db
+    } else if (have_pol) {
3eb3db
         /* set pw_max_life based on new policy */
3eb3db
+        kdb->mask |= KADM5_PW_EXPIRATION;
3eb3db
         if (pol.pw_max_life) {
3eb3db
             ret = krb5_dbe_lookup_last_pwd_change(handle->context, kdb,
3eb3db
-                                                  &(kdb->pw_expiration));
3eb3db
+                                                  &kdb->pw_expiration);
3eb3db
             if (ret)
3eb3db
                 goto done;
3eb3db
             kdb->pw_expiration = ts_incr(kdb->pw_expiration, pol.pw_max_life);
3eb3db
@@ -638,8 +645,6 @@ kadm5_modify_principal(void *server_handle,
3eb3db
         kdb->max_life = entry->max_life;
3eb3db
     if ((mask & KADM5_PRINC_EXPIRE_TIME))
3eb3db
         kdb->expiration = entry->princ_expire_time;
3eb3db
-    if (mask & KADM5_PW_EXPIRATION)
3eb3db
-        kdb->pw_expiration = entry->pw_expiration;
3eb3db
     if (mask & KADM5_MAX_RLIFE)
3eb3db
         kdb->max_renewable_life = entry->max_renewable_life;
3eb3db
 
3eb3db
@@ -678,9 +683,6 @@ kadm5_modify_principal(void *server_handle,
3eb3db
         kdb->fail_auth_count = 0;
3eb3db
     }
3eb3db
 
3eb3db
-    /* let the mask propagate to the database provider */
3eb3db
-    kdb->mask = mask;
3eb3db
-
3eb3db
     ret = k5_kadm5_hook_modify(handle->context, handle->hook_handles,
3eb3db
                                KADM5_HOOK_STAGE_PRECOMMIT, entry, mask);
3eb3db
     if (ret)
3eb3db
@@ -1358,6 +1360,11 @@ kadm5_chpass_principal_3(void *server_handle,
3eb3db
     if ((ret = kdb_get_entry(handle, principal, &kdb, &adb)))
3eb3db
         return(ret);
3eb3db
 
3eb3db
+    /* We will always be changing the key data, attributes, auth failure count,
3eb3db
+     * and password expiration time. */
3eb3db
+    kdb->mask = KADM5_KEY_DATA | KADM5_ATTRIBUTES | KADM5_FAIL_AUTH_COUNT |
3eb3db
+        KADM5_PW_EXPIRATION;
3eb3db
+
3eb3db
     ret = apply_keysalt_policy(handle, adb.policy, n_ks_tuple, ks_tuple,
3eb3db
                                &new_n_ks_tuple, &new_ks_tuple);
3eb3db
     if (ret)
3eb3db
@@ -1403,6 +1410,7 @@ kadm5_chpass_principal_3(void *server_handle,
3eb3db
     if (ret)
3eb3db
         goto done;
3eb3db
 
3eb3db
+    kdb->pw_expiration = 0;
3eb3db
     if ((adb.aux_attributes & KADM5_POLICY)) {
3eb3db
         /* the policy was loaded before */
3eb3db
 
3eb3db
@@ -1435,10 +1443,6 @@ kadm5_chpass_principal_3(void *server_handle,
3eb3db
 
3eb3db
         if (pol.pw_max_life)
3eb3db
             kdb->pw_expiration = ts_incr(now, pol.pw_max_life);
3eb3db
-        else
3eb3db
-            kdb->pw_expiration = 0;
3eb3db
-    } else {
3eb3db
-        kdb->pw_expiration = 0;
3eb3db
     }
3eb3db
 
3eb3db
 #ifdef USE_PASSWORD_SERVER
3eb3db
@@ -1477,11 +1481,6 @@ kadm5_chpass_principal_3(void *server_handle,
3eb3db
     /* unlock principal on this KDC */
3eb3db
     kdb->fail_auth_count = 0;
3eb3db
 
3eb3db
-    /* key data and attributes changed, let the database provider know */
3eb3db
-    kdb->mask = KADM5_KEY_DATA | KADM5_ATTRIBUTES |
3eb3db
-        KADM5_FAIL_AUTH_COUNT;
3eb3db
-    /* | KADM5_CPW_FUNCTION */
3eb3db
-
3eb3db
     if (hist_added)
3eb3db
         kdb->mask |= KADM5_KEY_HIST;
3eb3db
 
3eb3db
@@ -1556,6 +1555,11 @@ kadm5_randkey_principal_3(void *server_handle,
3eb3db
     if ((ret = kdb_get_entry(handle, principal, &kdb, &adb)))
3eb3db
         return(ret);
3eb3db
 
3eb3db
+    /* We will always be changing the key data, attributes, auth failure count,
3eb3db
+     * and password expiration time. */
3eb3db
+    kdb->mask = KADM5_KEY_DATA | KADM5_ATTRIBUTES | KADM5_FAIL_AUTH_COUNT |
3eb3db
+        KADM5_PW_EXPIRATION;
3eb3db
+
3eb3db
     ret = apply_keysalt_policy(handle, adb.policy, n_ks_tuple, ks_tuple,
3eb3db
                                &new_n_ks_tuple, &new_ks_tuple);
3eb3db
     if (ret)
3eb3db
@@ -1593,14 +1597,10 @@ kadm5_randkey_principal_3(void *server_handle,
3eb3db
         if (ret)
3eb3db
             goto done;
3eb3db
     }
3eb3db
-    if (have_pol) {
3eb3db
-        if (pol.pw_max_life)
3eb3db
-            kdb->pw_expiration = ts_incr(now, pol.pw_max_life);
3eb3db
-        else
3eb3db
-            kdb->pw_expiration = 0;
3eb3db
-    } else {
3eb3db
-        kdb->pw_expiration = 0;
3eb3db
-    }
3eb3db
+
3eb3db
+    kdb->pw_expiration = 0;
3eb3db
+    if (have_pol && pol.pw_max_life)
3eb3db
+        kdb->pw_expiration = ts_incr(now, pol.pw_max_life);
3eb3db
 
3eb3db
     ret = krb5_dbe_update_last_pwd_change(handle->context, kdb, now);
3eb3db
     if (ret)
3eb3db
@@ -1618,10 +1618,6 @@ kadm5_randkey_principal_3(void *server_handle,
3eb3db
             goto done;
3eb3db
     }
3eb3db
 
3eb3db
-    /* key data changed, let the database provider know */
3eb3db
-    kdb->mask = KADM5_KEY_DATA | KADM5_FAIL_AUTH_COUNT;
3eb3db
-    /* | KADM5_RANDKEY_USED */;
3eb3db
-
3eb3db
     ret = k5_kadm5_hook_chpass(handle->context, handle->hook_handles,
3eb3db
                                KADM5_HOOK_STAGE_PRECOMMIT, principal, keepold,
3eb3db
                                new_n_ks_tuple, new_ks_tuple, NULL);
3eb3db
@@ -1872,6 +1868,11 @@ kadm5_setkey_principal_4(void *server_handle, krb5_principal principal,
3eb3db
     if (ret)
3eb3db
         return ret;
3eb3db
 
3eb3db
+    /* We will always be changing the key data, attributes, auth failure count,
3eb3db
+     * and password expiration time. */
3eb3db
+    kdb->mask = KADM5_KEY_DATA | KADM5_ATTRIBUTES | KADM5_FAIL_AUTH_COUNT |
3eb3db
+        KADM5_PW_EXPIRATION;
3eb3db
+
3eb3db
     if (kvno == 0) {
3eb3db
         /* Pick the next kvno. */
3eb3db
         for (i = 0; i < kdb->n_key_data; i++) {
3eb3db
@@ -1973,14 +1974,10 @@ kadm5_setkey_principal_4(void *server_handle, krb5_principal principal,
3eb3db
         if (ret)
3eb3db
             goto done;
3eb3db
     }
3eb3db
-    if (have_pol) {
3eb3db
-        if (pol.pw_max_life)
3eb3db
-            kdb->pw_expiration = ts_incr(now, pol.pw_max_life);
3eb3db
-        else
3eb3db
-            kdb->pw_expiration = 0;
3eb3db
-    } else {
3eb3db
-        kdb->pw_expiration = 0;
3eb3db
-    }
3eb3db
+
3eb3db
+    kdb->pw_expiration = 0;
3eb3db
+    if (have_pol && pol.pw_max_life)
3eb3db
+        kdb->pw_expiration = ts_incr(now, pol.pw_max_life);
3eb3db
 
3eb3db
     ret = krb5_dbe_update_last_pwd_change(handle->context, kdb, now);
3eb3db
     if (ret)
3eb3db
diff --git a/src/plugins/kdb/ldap/libkdb_ldap/ldap_principal2.c b/src/plugins/kdb/ldap/libkdb_ldap/ldap_principal2.c
3eb3db
index b7c9212cb..8b98a4482 100644
3eb3db
--- a/src/plugins/kdb/ldap/libkdb_ldap/ldap_principal2.c
3eb3db
+++ b/src/plugins/kdb/ldap/libkdb_ldap/ldap_principal2.c
3eb3db
@@ -1233,19 +1233,6 @@ krb5_ldap_put_principal(krb5_context context, krb5_db_entry *entry,
3eb3db
                 goto cleanup;
3eb3db
         }
3eb3db
 
3eb3db
-        if (!(entry->mask & KADM5_PRINCIPAL)) {
3eb3db
-            memset(strval, 0, sizeof(strval));
3eb3db
-            if ((strval[0]=getstringtime(entry->pw_expiration)) == NULL)
3eb3db
-                goto cleanup;
3eb3db
-            if ((st=krb5_add_str_mem_ldap_mod(&mods,
3eb3db
-                                              "krbpasswordexpiration",
3eb3db
-                                              LDAP_MOD_REPLACE, strval)) != 0) {
3eb3db
-                free (strval[0]);
3eb3db
-                goto cleanup;
3eb3db
-            }
3eb3db
-            free (strval[0]);
3eb3db
-        }
3eb3db
-
3eb3db
         /* Update last password change whenever a new key is set */
3eb3db
         {
3eb3db
             krb5_timestamp last_pw_changed;
3eb3db
diff --git a/src/tests/t_kdb.py b/src/tests/t_kdb.py
3eb3db
index 319687ff3..38a2fd020 100755
3eb3db
--- a/src/tests/t_kdb.py
3eb3db
+++ b/src/tests/t_kdb.py
3eb3db
@@ -467,6 +467,23 @@ else:
3eb3db
     realm.run([kadminl, 'modprinc', '-pwexpire', '2040-02-03', 'user'])
3eb3db
     realm.run([kadminl, 'getprinc', 'user'], expected_msg=' 2040\n')
3eb3db
 
3eb3db
+# Regression test for #8861 (pw_expiration policy enforcement).
3eb3db
+mark('pw_expiration propogation')
3eb3db
+# Create a policy with a max life and verify its application.
3eb3db
+realm.run([kadminl, 'addpol', '-maxlife', '1s', 'pw_e'])
3eb3db
+realm.run([kadminl, 'addprinc', '-policy', 'pw_e', '-pw', 'password',
3eb3db
+           'pwuser'])
3eb3db
+out = realm.run([kadminl, 'getprinc', 'pwuser'],
3eb3db
+                expected_msg='Password expiration date: ')
3eb3db
+if 'Password expiration date: [never]' in out:
3eb3db
+    fail('pw_expiration not applied at principal creation')
3eb3db
+# Unset the policy max life and verify its application during password
3eb3db
+# change.
3eb3db
+realm.run([kadminl, 'modpol', '-maxlife', '0', 'pw_e'])
3eb3db
+realm.run([kadminl, 'cpw', '-pw', 'password_', 'pwuser'])
3eb3db
+realm.run([kadminl, 'getprinc', 'pwuser'],
3eb3db
+          expected_msg='Password expiration date: [never]')
3eb3db
+
3eb3db
 realm.stop()
3eb3db
 
3eb3db
 # Briefly test dump and load.