Draft kadmind plugin API (2007-04-27)
Russ Allbery
rra at stanford.edu
Fri Apr 27 14:56:11 EDT 2007
Still looking for feedback on the open issues collected at the end,
particularly how best to implement configuration of mandatory plugins in
krb5.conf.
kadmind Plugin API
Written by Russ Allbery <rra at stanford.edu>
Last Updated April 27, 2007
Background
Administrative actions on an MIT Kerberos KDC, including account
creation, deletion, flag changes, and user password changes, are done
via a daemon called kadmind. Most of the code implementing kadmind is
in the libkadm5srv library.
MIT Kerberos currently supports a degree of password strength checking
by checking passwords against a word list and requiring variation in the
types of characters used in the passwords. Stanford University requires
stronger password strength checking than this, wanting to use code based
on CrackLib.
Stanford University also maintains a separate Active Directory Kerberos
realm slaved to the MIT Kerberos realm for user accounts and requires
automatic propagation of user password changes and account enables and
disables (via the DISALLOW_ALL_TIX flag) from MIT Kerberos to Active
Directory.
The purpose of this document is to define a kadmind plugin API and the
necessary calls into that API so that these two requirements can be met
by writing plugins maintained separately from the MIT Kerberos code
base. The goal is to provide a general mechanism for pre-commit and
post-commit hooks on password changes and account flag changes that can
be used to implement this functionality. This mechanism must be
sufficient for all of the interfaces used by the software packages:
http://www.eyrie.org/~eagle/software/krb5-strength/
http://www.eyrie.org/~eagle/software/krb5-sync/
to be provided via plugins.
Assumptions
Plugins are implemented in the form of shared objects that provide one
or more externally visible structs with standard names. The name of the
struct determines the API for that plugin type. For each part of the
code that uses plugins, there will be one or more corresponding plugin
directories providing relevant plugins. An attempt should be made by
programs or libraries using that type of plugin to load all files in
that directory using dlopen and check for relevant struct names.
All calls should take a krb5_context as their first argument in addition
to any other parameters. Verbose error messages can be stored in the
krb5_context with krb5_set_error_message.
Core Code Changes
Some of the kadmind plugins (such as password strength checking) will be
mandatory, in the sense that kadmind startup should fail if those
plugins are not loaded. This can be handled by adding configuration to
krb5.conf indicating what plugins are mandatory. The proposed syntax
for that configuration is:
[plugins]
kadm5srv = {
pwstrength.so = {
require = "kadmin_strength_0"
}
}
In other words, a new plugins section of the configuration file, a
subsection in it for each class of plugins (corresponding to a directory
in the plugins directory and a "filebase" in the current plugin support
code), and for each of those that should be mandatory, a subsection
named for the filename of the plugin (relative to the filebase
directory) and containing the key "require" with a value of the symbol
that must be found in that plugin. There may be multiple require keys.
When loading plugins, this configuration would be checked and (in the
above example) any program loading kadm5srv plugins would fail plugin
initialization unless a plugin named pwstrength.so could be opened
correctly and exported the symbol kadmin_strength_0. [1]
Strength Checking Module API
All strength checking plugins must export a variable named
kadmin_strength_0 which holds the following struct:
struct kadmin_strength_0 {
int minor_version; /* currently 0 */
/*
* Initialize the plugin. Sets a binary blob that must be
* passed to other functions, including fini.
*/
krb5_error_code (*init)(krb5_context, void **);
void (*fini)(krb5_context, void *);
/*
* The call that does the work. On failure, the call will
* return non-zero and put an error message in errstr, which is
* a buffer of size errstrlen, truncating if necessary.
*/
krb5_error_code (*check)(krb5_context, void *, krb5_principal,
const char *password);
}
The minor_version field is currently unused but may be used later for
the libkadm5srv code to distinguish between ABI-compatible variations in
the interface.
init must be called after loading the plugin and should be passed a
krb5_context, which it may use to read additional configuration
information, and a pointer to a pointer for opaque module data. The
resulting value of the second argument should be passed in as the first
argument to all subsequent check and fini calls. The module may use
this pointer to store private data. If init returns non-zero,
initialization of the library must abort and fail.
check is called to check a password and takes the opaque data set by
init, the principal, and the password as a nul-terminated string. [2]
check will return zero for a sufficiently strong password, a
KADM5_PASS_Q_* error code if the password didn't pass strength checking,
or some other appropriate error code on any other failure. [3] The
verbose error message will be stored in the passed krb5_context using
krb5_set_error_message. Following the current policy for password
checking in kadmind, check should not be called unless the principal
whose password is being changed has a password policy applied to it.
The krb5_context passed to check may not be the same as the one passed
to init.
The existing password checking code in kadmind will also be called if
so configured in the password policy and in the KDC configuration. In
essence, it will be stacked with this module. (It may be worthwhile to
move the dictionary check in the current code into a plugin.)
fini should be called on library shutdown and is responsible for freeing
the opaque token set by init if necessary. The krb5_context passed to
fini may not be the same one passed to init or check.
Update Module API
This module provides general precommit and postcommit hooks for password
changes and a postcommit hook for account activation or deactivation
(via the setting or clearing of DISALLOW_ALL_TIX). This flag is singled
out to avoid requiring modules to know the internal flag values used by
the KDC and since it's the one flag that we were the most interested in
propagating to other environments.
The precommit hook should be able to reject the change before it can be
committed to the local KDC. This allows a module to require that an
external synchronization be successful before commiting the change
locally (perhaps on the grounds that the local commit is most likely to
succeed and that ordering minimizes the chances of desynchronization
between two environments). The postcommit hooks only log error messages
when they fail but do not block the operation.
If any of the hooks are NULL, kadmind and libkadm5srv will skip calling
that hook for that module and treat it as if it returned success.
All update plugins must export a variable named kadmin_update_0 which
holds the following struct:
struct kadmin_update_0 {
int minor_version; /* currently 0 */
/*
* Initialize the plugin. Sets a binary blob that must be
* passed to other functions, including fini.
*/
krb5_error_code (*init)(krb5_context, void **);
void (*fini)(krb5_context, void *);
/*
* The hooks. On failure, the calls will return non-zero and
* put an error message in errstr, which is a buffer of size
* errstrlen, truncating if necessary.
*/
krb5_error_code (*precommit_create)
(krb5_context, void *, krb5_principal, char *password);
krb5_error_code (*postcommit_create)
(krb5_context, void *, krb5_principal, char *password);
krb5_error_code (*precommit_delete)
(krb5_context, void *, krb5_principal);
krb5_error_code (*postcommit_delete)
(krb5_context, void *, krb5_principal);
krb5_error_code (*precommit_password)
(krb5_context, void *, krb5_principal, char *password);
krb5_error_code (*postcommit_password)
(krb5_context, void *, krb5_principal, char *password);
krb5_error_code (*precommit_status)
(krb5_context, void *, krb5_principal, int enabled);
krb5_error_code (*postcommit_status)
(krb5_context, void *, krb5_principal, int enabled);
}
The minor_version field is currently unused but may be used later for
the libkadm5srv code to distinguish between ABI-compatible variations in
the interface.
init must be called after loading the plugin and should be passed a
krb5_context, which it may use to read additional configuration
information, and a pointer to a pointer for opaque module data. The
resulting value of the second argument should be passed in as the first
argument to all subsequent check and fini calls. The module may use
this pointer to store private data. If init returns non-zero,
initialization of the library must abort and fail.
precommit_create and postcommit_create are called before and after
creation of a new principal is done locally. password change is made
locally. They will return zero on success or some appropriate error
code on failure. If precommit_create fails, the account creation should
fail. If postcommit_create fails, the only action should be to log the
failure message (as stored in the passed krb5_context with
krb5_set_error_message).
precommit_delete and postcommit_delete, and precommit_password and
postcommit_password, are the same except for account deletion and
password changes respectively. [4]
precommit_status and postcommit_status is called whenever the
DISALLOW_ALL_TIX flag changes on an account. The enabled parameter is a
boolean set to true if the flag is not set and set to false if the flag
is set. Error handling behavior is the same as the other hooks.
fini should be called on library shutdown and is responsible for freeing
the opaque token set by init if necessary.
The module may make no assumptions that the krb5_context passed in to
any previous call, including init, is the same as the krb5_context
passed to subsequent calls, including the call to fini.
libkadm5srv Changes
The libkadm5srv code would be modified to load kadm5srv plugins on
startup and to look for kadmin_strength_0 and kadmin_update_0 symbols in
each plugin available. During startup, the init functions of any
plugins found should be called and startup aborted if init fails for any
plugin. Then when changing passwords of an account that has a password
policy set, the password should first be passed to the strength checking
function of all kadmin_strength_0 plugins and rejected as insecure if
any of them fail. It should then be passed to the password_precommit
function of any kadmin_update_0 plugins and the change aborted if any of
them fail. Finally, the password will be changed and then the new
password passed to each password_postcommit function of any
kadmin_update_0 plugins.
When creating or deleting an account, the precommit_create or
precommit_delete hooks for any kadmin_update_0 plugins are called first,
and the change aborted if any of them return non-zero. As soon as one
plugin returns failure, processing stops and later plugins are not
called. If all the precommit calls succeed, the change is made to the
local KDC database and then all postcommit hooks for that operation are
called, logging errors for any that return failure.
Whenever the flags of an account are changed, libkadm5srv should check
to see if the DISALLOW_ALL_TIX flag is being set or cleared. If so, it
should call precommit_status of any kadmin_update_0 plugins before the
change is made and abort the change if that call returns non-zero.
Then, after the change is made, it will call postcommit_status of any
kadmin_update_0 plugins.
Finally, on shutdown, the fini functions of any plugins found should be
called.
Open Issues
[1] It's not clear how to implement this in the code, since the plugin
support code right now doesn't take a Kerberos context and can't
because it exists at a lower level. I'm therefore not sure if it
can architecturally get access to the profile library and the
[plugins] section of the configuration file. If this has to be
implemented at a higher level inside the Kerberos library instead of
the support library, we may need an additional wrapper around the
current plugin loading code.
[2] Passwords are handled here as nul-terminated strings. Is this safe
to do or do we need to provide a separate argument to specify the
length? It looked like other parts of Kerberos were assuming that
passwords could be safely treated as nul-terminated strings.
Likewise with the update plugin.
[3] The KADM5_PASS_Q_* error codes are currently internal to the library
build and aren't visible in any installed header files, so external
plugins don't have access to them. How should we handle this?
[4] Should we specify hooks for principal renamings as well, given that
the protocol supports it, or omit them for the time being since the
current MIT Kerberos code doesn't implement renamings?
--
Russ Allbery (rra at stanford.edu) <http://www.eyrie.org/~eagle/>
More information about the krbdev
mailing list