Updated draft for kadmind plugin API

Russ Allbery rra at stanford.edu
Sat Apr 21 18:25:12 EDT 2007


Here's a new version of the draft that includes create and delete hooks.
I also standardized the interfaces a bit more and redid the password
strength checking interface to stop overloading dict_path and instead use
its own separate configuration.

Please send any further comments, particularly on the open issues.


                            kadmind Plugin API
                Written by Russ Allbery <rra at stanford.edu>
                       Last Updated April 21, 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.  Sets a binary blob that must be
           * passed to other functions, including fini.
           */
          krb5_error_code (*init)(krb5_principal, 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 *, krb5_principal,
                                   const char *password,
                                   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.

  check is called to check a password and takes the opaque data set by
  init, the principal, the password as a nul-terminated string, and an
  error buffer of length errstrlen.  [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]  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 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.

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.

      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_create)
              (void *, krb5_principal, char *password,
               char *errstr, int errstrlen);
          krb5_error_code (*postcommit_create)
              (void *, krb5_principal, char *password,
               char *errstr, int errstrlen);

          krb5_error_code (*precommit_delete)
              (void *, krb5_principal, char *errstr, int errstrlen);
          krb5_error_code (*postcommit_delete)
              (void *, krb5_principal, char *errstr, int errstrlen);

          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 (*precommit_status)
              (void *, krb5_principal, int enabled,
               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_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.

  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.

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