Draft specification for kadmind plugin API
Russ Allbery
rra at stanford.edu
Fri Apr 20 19:24:45 EDT 2007
This is a first draft. I welcome feedback and critique, particularly
around the open issues.
kadmind Plugin API
Written by Russ Allbery <rra at stanford.edu>
Last Updated April 20, 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.
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. The dictionary path should be taken
* from the dict_file setting in the kadmind/KDC configuration.
* Sets a binary blob that must be passed to other functions,
* including fini.
*/
krb5_error_code (*init)(const char *dictionary, void **);
void (*fini)(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)(void *, const char *password,
const char *principal,
char *errstr, int errstrlen);
}
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 the
dict_path setting (from kdc.conf or its krb5.conf equivalent) and a
pointer to a pointer for opaque module data. [2] 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 password as a nul-terminated string, the principal in its
string representation, and an error buffer of length errstrlen. [3]
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. [4]
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.
If any module with a check function is loaded, the existing dictionary
checking code should not be called. [5]
fini should be called on library shutdown and is responsible for freeing
the opaque token set by init if necessary.
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.
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)(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_password)
(void *, krb5_principal, char *password,
char *errstr, int errstrlen);
krb5_error_code (*postcommit_password)
(void *, krb5_principal, char *password,
char *errstr, int errstrlen);
krb5_error_code (*postcommit_status)
(void *, krb5_principal, int enabled,
char *errstr, int errstrlen);
}
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_password and postcommit_password are called before and after
the password change is made locally. They will return zero on success
or some appropriate error code on failure. If precommit_password fails,
the password change should fail. If postcommit_password fails, the only
action should be to log the failure message.
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. It similarly will
return zero on success or some appropriate error code on failure. If
postcommit_status fails, the only action should be to log the failure
message.
fini should be called on library shutdown and is responsible for freeing
the opaque token set by init if necessary.
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, 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.
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,
after the change is made, it will call status_postcommit 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] Should the init function of the password strength checking plugin
take a krb5_context instead of the dictionary path so that it can
read its own configuration? The only reason why I hadn't done that
is because I didn't know how to get dict_path out of kdc.conf
currently using the regular profile calls. See also [5].
[3] 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.
[4] 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?
[5] Should we be disabling the current directory check? This is an
artifact of our original implementation and it makes some sense, but
it's only driven by the reuse of dict_path. If the module uses its
own separate configuration to find its dictionary, we could retain
the existing dict_path use and just stack the internal dictionary
check with anything provided by the plugins. That may be a better
approach.
--
Russ Allbery (rra at stanford.edu) <http://www.eyrie.org/~eagle/>
More information about the krbdev
mailing list