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:

committed by
Nico Williams

parent
62c1790bf5
commit
c6bf100b43
@ -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;
|
||||
|
Reference in New Issue
Block a user