[Rt-devel] PATCHES: Allow RT to authenticate against PAM

Graeme Fowler G.E.Fowler at lboro.ac.uk
Mon Nov 19 11:44:20 EST 2007


Hi all

I've just coded up an extension to RT to allow it to authenticate using
PAM (on a Linux system or other system where PAM is available, at
least). For those whom the term PAM is meaningless:

    http://www.kernel.org/pub/linux/libs/pam/

PAM can authenticate against a multitude of sources (backends) - an LDAP
system, Active Directory, Kerberos, local passwd/shadow files, a
separate MySQL instance... you name it, it's probably got a module for
it somewhere.
This will allow RT admins to use a flexible authentication source
*without* having to modify RT beyond a couple of configuration options.

The changes made are principally to the "IsPassword" function in
lib/RT/CurrentUser.pm such that it can use PAM to check a password, can
use the RT database to check a password, or fall through from PAM to
database if PAM fails.

Additionally, if PAM succeeds and the RT database differs from that
password, there's the option to set the RT password (thus keeping them
in sync).

In combination, if the PAM source goes away, the system can still
authenticate against the RT database using the most recently successful
PAM password. It's possible to turn off the password change and fallback
options, too.

There are both changes to CurrentUser.pm and some additional sections to
RT_SiteConfig.pm (see patches inline). Some of the logging statements
could be changed (it's a bit verbose at the moment) but otherwise it's
all as functional as it can be.

The only requirement is that Perl Authen::PAM module is installed on the
system. It's only loaded as and when required. The array defining
modules (see below) currently contains two module names - rt-krb5-auth
and rt-dummy-module. Any module name passed in this way containing the
word "dummy" is skipped - it's there purely for reference, to see that
the code looped as I wanted it to.

I've been testing it against our local AD (using Kerberos underneath
PAM) and it works as expected; I haven't found any boogs yet, but that
doesn't mean there aren't any :)

I've tested the fallback code by briefly firewalling out our domain
controllers; after a brief pause the code logged the appropriate message
("no Kerberos KDC is reachable", or similar) and fell through to the
database authentication. That then let me in.

Please give it a try (if it's relevant) and see what you think.

Graeme




--- etc/RT_SiteConfig.pm.pre-PAM        2007-11-19 10:41:00.000000000
+0000
+++ etc/RT_SiteConfig.pm        2007-11-19 14:55:30.000000000 +0000
@@ -316,6 +316,30 @@
 
 Set($WebExternalAuto , undef);
 
+# --- BEGIN MODIFICATION ---
+# If $PamAuth is defined, RT will attempt to use a PAM module on this
+# server to authenticate against, for example, Active Directory or
+# an LDAP server. The config of the PAM module is outside the scope
+# of this documentation. The name(s) of the PAM modules are listed
+# in the array $PamAuthModules
+
+Set($PamAuth , 1 );
+Set($PamAuthModules, [ 'rt-krb5-auth', 'rt-dummy-module' ]);
+
+# If $PamFallbackToInternal is defined, the password will be checked
+# against the RT internal database upon PAM auth failure. This permits
+# the use of RT when the external authentication source has "gone
away".
+
+Set($PamFallbackToInternal , 1 );
+
+# If $PamUpdateRTPassword is defined, successful authentication against
+# the configured PAM module(s) will update the local RT user's password
+# in the database. This permits the use of RT with the same passwords
+# when the external authentication source has "gone away".
+
+Set($PamUpdateRTPassword, 1 );
+# --- END MODIFICATION ---
+
 # $WebSessionClass is the class you wish to use for managing Sessions.
 # It defaults to use your SQL database, but if you are using MySQL 3.x
and
 # plans to use non-ascii Queue names, uncomment and add this line to


--- lib/RT/CurrentUser.pm       2007-04-24 18:21:42.000000000 +0100
+++ local/lib/RT/CurrentUser.pm 2007-11-19 14:57:13.000000000 +0000
@@ -294,9 +294,13 @@
 
 =head2 IsPassword
 
-Takes a password as a string.  Passes it off to IsPassword in this
-user's UserObj.  If it is the user's password and the user isn't
-disabled, returns 1.
+Takes a password as a string.  Either:
+ 1. Checks it against PAM if $RT::PamAuth is set.
+    - if $RT::PamFallbackToInternal is set and PAM fails, falls to step
2
+    - sets RT password if $RT::PamUpdateRTPassword is set
+ 2. Passes it off to IsPassword in this user's UserObj.
+
+If the password is validated and the user isn't disabled, returns 1.
 
 Otherwise, returns undef.
 
@@ -306,6 +310,47 @@
   my $self = shift;
   my $value = shift;
   
+  if ($RT::PamAuth) {
+    my $name = $self->UserObj->Name;
+
+    # First check that the user isn't disabled.
+    # There is no point authenticating if they are!
+
+    if ($self->PrincipalObj->Disabled) {
+      $RT::Logger->warning("User $name attempted PAM login but is
disabled in RT database.");
+    }
+
+    use Authen::PAM;
+    foreach my $pam_module (@$RT::PamAuthModules) {
+      $RT::Logger->debug("MODULE: " . $pam_module);
+      next if ($pam_module =~ /dummy/);
+      my $pam_conv_func = sub { my @res = (PAM_SUCCESS); unshift @res,
$value and unshift @res, 0 while( shift @_ && shift @_ ); @res };
+      my( $res, $pamh, );
+      if( ( $res = pam_start( $pam_module, $name, $pam_conv_func,
$pamh ) ) == PAM_SUCCESS
+       && ( $res = pam_authenticate( $pamh, 0 ) )
== PAM_SUCCESS
+       && ( $res = pam_end( $pamh, 0 ) )
== PAM_SUCCESS
+      ) {
+        # If $PamUpdateRTPassword is defined we need to set the RT
password
+        # but only if the old RT password differs
+        if ($RT::PamUpdateRTPassword && (!
$self->UserObj->IsPassword($value))) {
+          $RT::Logger->warning("Attempting to change RT password for
$name");
+          my ( $val, $msg ) = $self->UserObj->SetPassword($value);
+          if ($val) {
+            $RT::Logger->warning("User $name RT password changed to PAM
password on successful PAM login");
+          }
+        }
+        return ( 1 );
+      } else {
+        $RT::Logger->warning("User $name failed PAM authentication");
+        unless ($RT::PamFallbackToInternal) {
+          return ( 0 );
+        } else {
+          $RT::Logger->warning("User $name falling back to RT database
authentication");
+        }
+      }
+    }
+  }
+
   return ($self->UserObj->IsPassword($value)); 
 }


-----------------------

Our /etc/pam.d/rt-krb5-auth file contains:

#%PAM-1.0
auth        required      pam_krb5.so debug no_user_check

...and that's it.



More information about the Rt-devel mailing list