4

My employer has been running RHEL 6.x and Apache httpd 2.2 for many years. We are currently in the process of migrating to new hardware running RHEL 7.1 and Apache httpd 2.4. Our current web site has various locations that contain downloadable material for different sets of clients. Clients all have system accounts on the server box. We currently control access to the locations based on client user's group membership.

For example:

<Location /xyzzy/*>
    AuthName "xyzzy product support"
    AuthShadow on
    AuthType Basic
    require group xyzzy
    Options Includes ExecCGI Indexes FollowSymLinks MultiViews
</Location>

We have been successfully using mod_auth_shadow to implement this access control under Apache 2.2. However, we've found that this module won't load under 2.4 because the module calls ap_requires(), which is not present under 2.4.

We've noticed that RHEL 7 by default runs

/usr/sbin/saslauthd -m /run/saslauthd -a pam

so I've been looking at using PAM through mod_authn_sasl as a replacement for mod_auth_shadow. I've had partial success with this apache configuration:

<Location /xyzzy/*>
    AuthType Basic
    AuthName "xyzzy product support"
    AuthBasicProvider sasl
    AuthBasicAuthoritative On
    AuthSaslPwcheckMethod saslauthd
    Require valid-user
</Location>

combined with this /etc/pam.d/http file:

#%PAM-1.0
auth       include      password-auth
auth       include      pam_group
account    include      password-auth

With this combination any user with valid login credentials can access the xyzzy location. I believe this validates that the basic connection between Apache -> saslauthd -> PAM is working. But that's not the level of granularity we're looking for.

This alternative httpd configuration:

<Location /xyzzy/*>
    AuthType Basic
    AuthName "xyzzy product support"
    AuthBasicProvider sasl
    AuthBasicAuthoritative On
    AuthSaslPwcheckMethod saslauthd
    Require group xyzzy
</Location>

generates this error in the httpd log:

AH01664: No group file was specified in the configuration

This suggests that httpd is not going through saslauthd in order to validate group membership. So far, I haven't found an httpd directive that would force group authentication through sasl in the way that user/password authentication does.

(Why am I using the system passwd, shadow and group files for authentication instead of a separate database for http? Some clients prefer to download their support files via ftp rather than http. So we use the system in order to give our clients relatively easy switching between the two protocols)

As a last resort I'm prepared to try updating mod_auth_shadow for 2.4. But I've never coded or debugged an apache module, so there's an unknown learning curve involved in that approach. So I'm completely open to suggestions!

3 Answers3

1

It looks like you've already explored one option, here are a couple more possibilities although it looks like both will require some work.

mod_auth_external: https://github.com/phokz/mod-auth-external
mod_auth_kerb: http://modauthkerb.sourceforge.net/

Unbeliever
  • 2,286
  • 1
  • 9
  • 17
1

After looking at some alternatives, including those suggested by Unbeliever, I've decided to go ahead and try to rewrite the original mod_auth_shadow to be compatible with the current authentication/authorization architecture. I've created a very basic, stripped-down module modeled somewhat after the mod_authnz_ldap module.

/*
 * mod_auth_shadow.c
 *
 * An apache module to authenticate using the /etc/shadow file.
 * This module interacts with another program "validate", which
 * is setuid root.  Thus the /etc/shadow file can remain 
 * root:root 0400.
 *
 * Author: Brian Duggan <bduggan@matatu.org>
 * Some code was taken from the sample code supplied with
 * _Apache Modules_ by Stein and MacEachern.  Parts of this
 * were also influenced by mod_auth.c.
 *
 * Adapted for Apache2: Bernard du Breuil 
 *  <bernard.l.dubreuil@erdc.usace.army.mil>
 * I went back to mod_auth.c to see how it was converted.
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 2 of the License, or
 * (at your option) any later version.
 * 
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 * 
 * You should have received a copy of the GNU General Public License along
 * with this program; if not, write to the Free Software Foundation, Inc.,
 * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
 * 
 */

#include "apr_strings.h"
#include "ap_config.h"

#include "httpd.h"  
#include "http_config.h"  
#include "http_core.h"  
#include "http_log.h"  
#include "http_protocol.h"  
#include "http_request.h"  

#include "mod_auth.h"

#include <shadow.h>
#include <string.h>
#include <stdio.h>
#include <errno.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/wait.h>
#include <pwd.h>
#include <grp.h>
#include "validate.h"


static const char module_name[] = "authnz_shadow_module";


static authn_status authn_shadow_check_password(request_rec *r, const char *user,
                                              const char *password)
{
    return AUTH_GRANTED;
}


static authz_status valid_user_check_authorization(request_rec *r,
                                              const char *require_args,
                                              const void *parsed_require_args)
{
    return AUTH_GRANTED;
}


static authz_status group_check_authorization(request_rec *r,
                                              const char *require_args,
                                              const void *parsed_require_args)
{
    return AUTH_GRANTED;
}




typedef struct
{
    int auth_shadow_flag; /* 1 for yes, 0 for no */
}
auth_shadow_config_rec;


static const authn_provider authn_shadow_provider =
{
    &authn_shadow_check_password,
    NULL,
};


static const authz_provider authz_valid_user_provider =
{
    &valid_user_check_authorization,
    NULL,
};


static const authz_provider authz_group_provider =
{
    &group_check_authorization,
    NULL,
};


static void register_hooks(apr_pool_t *p)
{
    /* Register authn provider */
    ap_register_auth_provider(p, AUTHN_PROVIDER_GROUP, "shadow",
                              AUTHN_PROVIDER_VERSION,
                              &authn_shadow_provider, AP_AUTH_INTERNAL_PER_CONF);

    /* Register authz providers */
    ap_register_auth_provider(p, AUTHZ_PROVIDER_GROUP, "shadow-valid-user",
                              AUTHZ_PROVIDER_VERSION,
                              &authz_valid_user_provider,
                              AP_AUTH_INTERNAL_PER_CONF);
    ap_register_auth_provider(p, AUTHZ_PROVIDER_GROUP, "shadow-group",
                              AUTHZ_PROVIDER_VERSION,
                              &authz_group_provider,
                              AP_AUTH_INTERNAL_PER_CONF);
}


static void *create_auth_shadow_dir_config(apr_pool_t *p, char *d)
{
    auth_shadow_config_rec *sec =
    (auth_shadow_config_rec *) apr_palloc(p, sizeof(*sec));
    sec->auth_shadow_flag = 0;  
    return sec;
}


static const command_rec auth_shadow_cmds[] =
{
    AP_INIT_FLAG("AuthShadow", ap_set_flag_slot,
    (void *)APR_OFFSETOF(auth_shadow_config_rec, auth_shadow_flag),
        OR_AUTHCFG, "On or Off depending on whether to use /etc/shadow"),
    {NULL}
};


module AP_MODULE_DECLARE_DATA authnz_shadow_module = 
{
    STANDARD20_MODULE_STUFF, 
    create_auth_shadow_dir_config , /* dir config creator */
    NULL,                  /* merge  per-dir    config structures */
    NULL,                  /* create per-server config structures */
    NULL,                  /* merge  per-server config structures */
    auth_shadow_cmds,      /* [config file] command table */
    register_hooks     /* register hooks */
};

Right now, I'm just trying to understand the overall logic sequence of auth modules. I've created a secure test directory on my web server defined as:

<Location /secure>
    AuthName SecureDocument
    AuthType Basic
    AuthBasicProvider shadow
    AuthShadow on
    Require shadow-valid-user
    Require shadow-group xyzzy
</Location>

And here's where I get very confused: during testing, I find that the valid_user_check_authorization and group_check_authorization routines are called with r->user set to NULL. But the authentication routine authn_shadow_check_password is never invoked.

As coded, the mod_authnz_ldap.c module seems to imply that authentication and authorization can be combined into a single module. But I'm assuming that the authentication function, if present, will always be called before any authorization functions. And I'm further assuming that if the credentials supplied by the browser are valid according to the authentication function, the userid will be passed along to subsequent authorization functions. But I haven't found any definitive documentation on that topic one way or another.

I'm open to suggestions, hints and corrections of my assumptions!

  • 1
    I should have mentioned more specifically that valid_user_check_authorization is called first, group_check_authorization is called second and authn_shadow_check_password is *never* called. – Eric Chevalier Sep 28 '16 at 19:57
0

I've also run into this restriction of mod_authn_sasl. It is however reasonably easy to achieve the desired effect by enforcing the group restriction at the PAM level instead of the Apache level.

In the original example, leave the Apache configuration requiring only valid-user but add the restriction to the desired group in the relevant PAM service configuration, /etc/pam.d/http:

#%PAM-1.0
auth       require      pam_succeed_if.so user ingroup xyzzy
auth       include      password-auth
auth       include      pam_group
account    include      password-auth

To restrict to any of a list of groups / users make that first auth line something like:

auth       require      pam_listfile.so onerr=fail item=group sense=allow file=/path/to/allowed.groups

Plenty more possibilities available; see man pam_succeed_if and man pam_listfile...

For the case of multiple locations each needing different restrictions create separate PAM services for them, selected via AuthSaslServiceName in the Apache configuration. E.g. rename the above /etc/pam.d/http to /etc/pam.d/http-xyzzy, copy to e.g. /etc/pam.d/http-abcd and modify to restrict to group abcd, then configure Apache thus:

<Location /abcd/*>
    AuthType Basic
    AuthName "abcd product support"
    AuthBasicProvider sasl
    AuthBasicAuthoritative On
    AuthSaslPwcheckMethod saslauthd
    AuthSaslServiceName http-abcd
    Require valid-user
</Location>
<Location /xyzzy/*>
    AuthType Basic
    AuthName "xyzzy product support"
    AuthBasicProvider sasl
    AuthBasicAuthoritative On
    AuthSaslPwcheckMethod saslauthd
    AuthSaslServiceName http-xyzzy
    Require valid-user
</Location>