kadm5: move password quality checks out of daemons and into libkadm5

Note that this has a slight behavior change to c89d3f3b in order to continue
allow kadmin in local mode to bypass password quality checks. Password quality
checks are always bypassed if the *client* kadmin principal is kadmin/admin,
i.e. that of the kadmin service itself. This is the case when running kadmin in
local mode. As this is the equivalent of a superuser account, one would
anticipate that deployments would use specific administrator instances for
appropriate ACLs for day-to-day administration; operations by these will be
subject to password quality checks if enforce_on_admin_set is TRUE, or if the
user is changing their own password.
This commit is contained in:
Luke Howard
2018-12-26 16:44:25 +11:00
committed by Nico Williams
parent 62c1790bf5
commit c6bf100b43
6 changed files with 80 additions and 93 deletions

@ -472,12 +472,14 @@ number, special characters.
@item enforce_on_admin_set
The enforce_on_admin_set check validates that administrative password changes
via kpasswdd or kadmind are also subject to the password policy. Note that
@command{kadmin} in local mode can still bypass these. An administrative
password change is one where the identity of the authenticating principal
differs from the subject of the password change. Default value if not given is
true.
The enforce_on_admin_set check subjects administrative password updates to the
password policy. An administrative password update is a create principal or
change password request via @command{kadmind}, or a set password request via
@command{kpasswdd}. (A set password request is one where the authenticating
principal differs from the principal whose password is being changed.) Password
policies are always ignored if the authenticating principal is the kadmin
service itself, for example when running @command{kadmin} in local mode. The
default value for enforce_on_admin_set if not given is true.
@end itemize

@ -38,9 +38,6 @@ static kadm5_ret_t check_aliases(kadm5_server_context *,
kadm5_principal_ent_rec *,
kadm5_principal_ent_rec *);
static krb5_boolean
enforce_pwqual_on_admin_set_p(kadm5_server_context *contextp);
static kadm5_ret_t
kadmind_dispatch(void *kadm_handlep, krb5_boolean initial,
krb5_data *in, krb5_data *out)
@ -181,24 +178,6 @@ kadmind_dispatch(void *kadm_handlep, krb5_boolean initial,
}
krb5_unparse_name_fixed(contextp->context, ent.principal,
name, sizeof(name));
if (enforce_pwqual_on_admin_set_p(contextp)) {
krb5_data pwd_data;
const char *pwd_reason;
pwd_data.data = password;
pwd_data.length = strlen(password);
pwd_reason = kadm5_check_password_quality (contextp->context,
ent.principal, &pwd_data);
if (pwd_reason != NULL)
ret = KADM5_PASS_Q_DICT;
else
ret = 0;
if (ret) {
kadm5_free_principal_ent(kadm_handlep, &ent);
goto fail;
}
}
krb5_warnx(contextp->context, "%s: %s %s", client, op, name);
ret = _kadm5_acl_check_permission(contextp, KADM5_PRIV_ADD,
ent.principal);
@ -354,30 +333,6 @@ kadmind_dispatch(void *kadm_handlep, krb5_boolean initial,
}
}
/*
* Change password requests are subject to password quality checks if
* the principal is changing their own password, or the enforce_on_admin_set
* configuration option is TRUE (the default).
*/
if (is_self_cpw || enforce_pwqual_on_admin_set_p(contextp)) {
krb5_data pwd_data;
const char *pwd_reason;
pwd_data.data = password;
pwd_data.length = strlen(password);
pwd_reason = kadm5_check_password_quality (contextp->context,
princ, &pwd_data);
if (pwd_reason != NULL)
ret = KADM5_PASS_Q_DICT;
else
ret = 0;
if (ret) {
krb5_free_principal(contextp->context, princ);
goto fail;
}
}
ret = kadm5_chpass_principal_3(kadm_handlep, princ, keepold, 0, NULL,
password);
krb5_free_principal(contextp->context, princ);
@ -874,10 +829,3 @@ kadmind_loop(krb5_context contextp,
return 0;
}
static krb5_boolean
enforce_pwqual_on_admin_set_p(kadm5_server_context *contextp)
{
return krb5_config_get_bool_default(contextp->context, NULL, TRUE,
"password_quality",
"enforce_on_admin_set", NULL);
}

@ -40,6 +40,7 @@ RCSID("$Id$");
#endif
#include <hdb.h>
#include <kadm5/private.h>
#include <kadm5/kadm5_err.h>
static krb5_context context;
static krb5_log_facility *log_facility;
@ -246,14 +247,12 @@ change (krb5_auth_context auth_context,
{
krb5_error_code ret;
char *client = NULL, *admin = NULL;
const char *pwd_reason;
kadm5_config_params conf;
void *kadm5_handle = NULL;
krb5_principal principal = NULL;
krb5_data *pwd_data = NULL;
char *tmp;
ChangePasswdDataMS chpw;
krb5_boolean enforce_pwqual_on_admin_set;
memset (&conf, 0, sizeof(conf));
memset(&chpw, 0, sizeof(chpw));
@ -383,29 +382,6 @@ change (krb5_auth_context auth_context,
krb5_warnx (context, "Changing password for %s", client);
}
/*
* Change password requests are subject to password quality checks if
* the principal is changing their own password, or the enforce_on_admin_set
* configuration option is TRUE (the default).
*/
enforce_pwqual_on_admin_set =
krb5_config_get_bool_default(context, NULL, TRUE,
"password_quality",
"enforce_on_admin_set", NULL);
if (krb5_principal_compare(context, admin_principal, principal) == TRUE ||
enforce_pwqual_on_admin_set == TRUE) {
pwd_reason = kadm5_check_password_quality (context, principal,
pwd_data);
if (pwd_reason != NULL ) {
krb5_warnx (context,
"%s didn't pass password quality check with error: %s",
client, pwd_reason);
reply_priv (auth_context, s, sa, sa_size,
KRB5_KPASSWD_SOFTERROR, pwd_reason);
goto out;
}
}
ret = krb5_data_realloc(pwd_data, pwd_data->length + 1);
if (ret) {
krb5_warn (context, ret, "malloc: out of memory");
@ -421,7 +397,14 @@ change (krb5_auth_context auth_context,
pwd_data = NULL;
if (ret) {
const char *str = krb5_get_error_message(context, ret);
krb5_warnx(context, "kadm5_s_chpass_principal_cond: %s", str);
if (ret == KADM5_PASS_Q_DICT) {
krb5_warnx(context,
"%s didn't pass password quality check with error: %s",
client, str);
} else {
krb5_warnx(context, "kadm5_s_chpass_principal_cond: %s", str);
}
reply_priv (auth_context, s, sa, sa_size, KRB5_KPASSWD_SOFTERROR,
str ? str : "Internal error");
krb5_free_error_message(context, str);

@ -141,6 +141,21 @@ fetch_acl (kadm5_server_context *context,
return ret;
}
krb5_boolean
_kadm5_is_kadmin_service_p(kadm5_server_context *context)
{
krb5_boolean ret;
krb5_principal princ;
if (krb5_parse_name(context->context, KADM5_ADMIN_SERVICE, &princ) != 0)
return FALSE;
ret = krb5_principal_compare(context->context, context->caller, princ);
krb5_free_principal(context->context, princ);
return ret;
}
/*
* set global acl flags in `context' for the current caller.
* return 0 on success or an error
@ -149,15 +164,7 @@ fetch_acl (kadm5_server_context *context,
kadm5_ret_t
_kadm5_acl_init(kadm5_server_context *context)
{
krb5_principal princ;
krb5_error_code ret;
ret = krb5_parse_name(context->context, KADM5_ADMIN_SERVICE, &princ);
if (ret)
return ret;
ret = krb5_principal_compare(context->context, context->caller, princ);
krb5_free_principal(context->context, princ);
if(ret != 0) {
if (_kadm5_is_kadmin_service_p(context)) {
context->acl_flags = KADM5_PRIV_ALL;
return 0;
}

@ -88,6 +88,23 @@ change(void *server_handle,
uint32_t hook_flags = 0;
memset(&ent, 0, sizeof(ent));
if (krb5_principal_compare(context->context, princ, context->caller) ||
_kadm5_enforce_pwqual_on_admin_set_p(context)) {
krb5_data pwd_data;
const char *pwd_reason;
pwd_data.data = rk_UNCONST(password);
pwd_data.length = strlen(password);
pwd_reason = kadm5_check_password_quality(context->context,
princ, &pwd_data);
if (pwd_reason != NULL) {
krb5_set_error_message(context->context, KADM5_PASS_Q_DICT, "%s", pwd_reason);
return KADM5_PASS_Q_DICT;
}
}
if (!context->keep_open) {
ret = context->db->hdb_open(context->context, context->db, O_RDWR, 0);
if(ret)
@ -324,3 +341,18 @@ kadm5_s_chpass_principal_with_key(void *server_handle,
}
return _kadm5_error_code(ret);
}
/*
* Returns TRUE if password quality should be checked when passwords are
* being set or changed by administrators. This includes principal creation.
*/
krb5_boolean
_kadm5_enforce_pwqual_on_admin_set_p(kadm5_server_context *contextp)
{
if (_kadm5_is_kadmin_service_p(contextp))
return FALSE;
return krb5_config_get_bool_default(contextp->context, NULL, TRUE,
"password_quality",
"enforce_on_admin_set", NULL);
}

@ -212,6 +212,21 @@ kadm5_s_create_principal(void *server_handle,
hdb_entry_ex ent;
kadm5_server_context *context = server_handle;
if (_kadm5_enforce_pwqual_on_admin_set_p(context)) {
krb5_data pwd_data;
const char *pwd_reason;
pwd_data.data = rk_UNCONST(password);
pwd_data.length = strlen(password);
pwd_reason = kadm5_check_password_quality(context->context,
princ->principal, &pwd_data);
if (pwd_reason != NULL) {
krb5_set_error_message(context->context, KADM5_PASS_Q_DICT, "%s", pwd_reason);
return KADM5_PASS_Q_DICT;
}
}
if ((mask & KADM5_KVNO) == 0) {
/* create_principal() through _kadm5_setup_entry(), will need this */
princ->kvno = 1;