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