Skip to content

Support Forward Proxy Authentication #48

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
wants to merge 6 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
74 changes: 51 additions & 23 deletions src/mod_auth_gssapi.c
Original file line number Diff line number Diff line change
Expand Up @@ -292,8 +292,12 @@ static bool parse_auth_header(apr_pool_t *pool, const char **auth_header,
return true;
}

static bool is_mech_allowed(gss_OID_set allowed_mechs, gss_const_OID mech)
static bool is_mech_allowed(gss_OID_set allowed_mechs, gss_const_OID mech,
bool multi_step_supported)
{
if (!multi_step_supported && gss_oid_equal(&gss_mech_ntlmssp, mech))
return false;

if (allowed_mechs == GSS_C_NO_OID_SET) return true;

for (int i = 0; i < allowed_mechs->count; i++) {
Expand Down Expand Up @@ -615,11 +619,41 @@ static bool mag_auth_basic(request_rec *req,
return ret;
}

struct mag_req_cfg *mag_init_cfg(request_rec *req)
{
struct mag_req_cfg *req_cfg = apr_pcalloc(req->pool,
sizeof(struct mag_req_cfg));
req_cfg->cfg = ap_get_module_config(req->per_dir_config,
&auth_gssapi_module);

if (req_cfg->cfg->allowed_mechs) {
req_cfg->desired_mechs = req_cfg->cfg->allowed_mechs;
} else {
struct mag_server_config *scfg;
/* Try to fetch the default set if not explicitly configured */
scfg = ap_get_module_config(req->server->module_config,
&auth_gssapi_module);
req_cfg->desired_mechs = scfg->default_mechs;
}

if (req->proxyreq == PROXYREQ_PROXY) {
req_cfg->req_proto = "Proxy-Authorization";
req_cfg->rep_proto = "Proxy-Authenticate";
} else {
req_cfg->req_proto = "Authorization";
req_cfg->rep_proto = "WWW-Authenticate";
req_cfg->use_sessions = req_cfg->cfg->use_sessions;
req_cfg->send_persist = req_cfg->cfg->send_persist;
}

return req_cfg;
}

static int mag_auth(request_rec *req)
{
const char *type;
int auth_type = -1;
struct mag_req_cfg *req_cfg;
struct mag_config *cfg;
const char *auth_header;
char *auth_header_type;
Expand Down Expand Up @@ -652,17 +686,11 @@ static int mag_auth(request_rec *req)
return DECLINED;
}

cfg = ap_get_module_config(req->per_dir_config, &auth_gssapi_module);
req_cfg = mag_init_cfg(req);

if (cfg->allowed_mechs) {
desired_mechs = cfg->allowed_mechs;
} else {
struct mag_server_config *scfg;
/* Try to fetch the default set if not explicitly configured */
scfg = ap_get_module_config(req->server->module_config,
&auth_gssapi_module);
desired_mechs = scfg->default_mechs;
}
cfg = req_cfg->cfg;

desired_mechs = req_cfg->desired_mechs;

/* implicit auth for subrequests if main auth already happened */
if (!ap_is_initial_req(req) && req->main != NULL) {
Expand Down Expand Up @@ -714,11 +742,11 @@ static int mag_auth(request_rec *req)
}

/* if available, session always supersedes connection bound data */
if (cfg->use_sessions) {
if (req_cfg->use_sessions) {
mag_check_session(req, cfg, &mc);
}

auth_header = apr_table_get(req->headers_in, "Authorization");
auth_header = apr_table_get(req->headers_in, req_cfg->req_proto);

if (mc) {
if (mc->established &&
Expand Down Expand Up @@ -785,7 +813,7 @@ static int mag_auth(request_rec *req)
break;

case AUTH_TYPE_RAW_NTLM:
if (!is_mech_allowed(desired_mechs, &gss_mech_ntlmssp)) {
if (!is_mech_allowed(desired_mechs, &gss_mech_ntlmssp, (mc != NULL))) {
ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, req,
"NTLM Authentication is not allowed!");
goto done;
Expand Down Expand Up @@ -920,18 +948,19 @@ static int mag_auth(request_rec *req)
if (auth_type == AUTH_TYPE_BASIC) {
mag_basic_cache(cfg, mc, ba_user, ba_pwd);
}
if (cfg->use_sessions) {
if (req_cfg->use_sessions) {
mag_attempt_session(req, cfg, mc);
}
}

if (cfg->send_persist)
if (req_cfg->send_persist)
apr_table_set(req->headers_out, "Persistent-Auth",
cfg->gss_conn_ctx ? "true" : "false");

ret = OK;

done:

if ((auth_type != AUTH_TYPE_BASIC) && (output.length != 0)) {
int prefixlen = strlen(auth_types[auth_type]) + 1;
replen = apr_base64_encode_len(output.length) + 1;
Expand All @@ -940,17 +969,16 @@ static int mag_auth(request_rec *req)
memcpy(reply, auth_types[auth_type], prefixlen - 1);
reply[prefixlen - 1] = ' ';
apr_base64_encode(&reply[prefixlen], output.value, output.length);
apr_table_add(req->err_headers_out,
"WWW-Authenticate", reply);
apr_table_add(req->err_headers_out, req_cfg->rep_proto, reply);
}
} else if (ret == HTTP_UNAUTHORIZED) {
apr_table_add(req->err_headers_out, "WWW-Authenticate", "Negotiate");
if (is_mech_allowed(desired_mechs, &gss_mech_ntlmssp)) {
apr_table_add(req->err_headers_out, "WWW-Authenticate", "NTLM");
apr_table_add(req->err_headers_out, req_cfg->rep_proto, "Negotiate");

if (is_mech_allowed(desired_mechs, &gss_mech_ntlmssp, (mc != NULL))) {
apr_table_add(req->err_headers_out, req_cfg->rep_proto, "NTLM");
}
if (cfg->use_basic_auth) {
apr_table_add(req->err_headers_out,
"WWW-Authenticate",
apr_table_add(req->err_headers_out, req_cfg->rep_proto,
apr_psprintf(req->pool, "Basic realm=\"%s\"",
ap_auth_name(req)));
}
Expand Down
9 changes: 9 additions & 0 deletions src/mod_auth_gssapi.h
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,15 @@ struct mag_server_config {
gss_OID_set default_mechs;
};

struct mag_req_cfg {
struct mag_config *cfg;
gss_OID_set desired_mechs;
bool use_sessions;
bool send_persist;
const char *req_proto;
const char *rep_proto;
};

struct mag_conn {
apr_pool_t *pool;
gss_ctx_id_t ctx;
Expand Down
28 changes: 26 additions & 2 deletions tests/httpd.conf
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
ServerRoot "${HTTPROOT}"
ServerName "${HTTPNAME}"
Listen ${HTTPADDR}:${HTTPPORT}
Listen ${HTTPADDR}:${PROXYPORT}

LoadModule access_compat_module modules/mod_access_compat.so
LoadModule actions_module modules/mod_actions.so
Expand Down Expand Up @@ -62,13 +63,16 @@ LoadModule unixd_module modules/mod_unixd.so
LoadModule userdir_module modules/mod_userdir.so
LoadModule version_module modules/mod_version.so
LoadModule vhost_alias_module modules/mod_vhost_alias.so

LoadModule mpm_prefork_module modules/mod_mpm_prefork.so
LoadModule proxy_module modules/mod_proxy.so
LoadModule proxy_http_module modules/mod_proxy_http.so

LoadModule auth_gssapi_module mod_auth_gssapi.so


<Directory />
Options +Includes
AddOutputFilter INCLUDES .html
AllowOverride none
Require all denied
</Directory>
Expand Down Expand Up @@ -117,7 +121,10 @@ IncludeOptional conf.d/*.conf

CoreDumpDirectory /tmp


<Location /spnego>
Options +Includes
AddOutputFilter INCLUDES .html
AuthType GSSAPI
AuthName "Login"
GssapiSSLonly Off
Expand All @@ -127,12 +134,14 @@ CoreDumpDirectory /tmp
GssapiCredStore ccache:${HTTPROOT}/tmp/httpd_krb5_ccache
GssapiCredStore client_keytab:${HTTPROOT}/http.keytab
GssapiCredStore keytab:${HTTPROOT}/http.keytab
GssapiBasicAuth Off
GssapiBasicAuth On
GssapiAllowedMech krb5
Require valid-user
</Location>

<Location /basic_auth_krb5>
Options +Includes
AddOutputFilter INCLUDES .html
AuthType GSSAPI
AuthName "Password Login"
GssapiSSLonly Off
Expand All @@ -141,6 +150,21 @@ CoreDumpDirectory /tmp
GssapiCredStore keytab:${HTTPROOT}/http.keytab
GssapiBasicAuth On
GssapiBasicAuthMech krb5
GssapiConnectionBound On
Require valid-user
</Location>

<VirtualHost *:${PROXYPORT}>
ProxyRequests On
ProxyVia On

<Proxy *>
AuthType GSSAPI
AuthName "Proxy Login"
GssapiCredStore ccache:${HTTPROOT}/tmp/httpd_krb5_ccache
GssapiCredStore client_keytab:${HTTPROOT}/http.keytab
GssapiCredStore keytab:${HTTPROOT}/http.keytab
GssapiBasicAuth On
Require valid-user
</Proxy>
</VirtualHost>
2 changes: 1 addition & 1 deletion tests/index.html
Original file line number Diff line number Diff line change
@@ -1 +1 @@
WORKS
<!--#echo var="GSS_NAME" -->
65 changes: 61 additions & 4 deletions tests/magtests.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
import subprocess
import sys
import time
import shlex


def parse_args():
Expand All @@ -23,6 +24,8 @@ def parse_args():

WRAP_HOSTNAME = "kdc.mag.dev"
WRAP_IPADDR = '127.0.0.9'
WRAP_HTTP_PORT = '80'
WRAP_PROXY_PORT = '8080'

def setup_wrappers(base):

Expand All @@ -47,6 +50,7 @@ def setup_wrappers(base):
wenv = {'LD_PRELOAD': 'libsocket_wrapper.so libnss_wrapper.so',
'SOCKET_WRAPPER_DIR': wrapdir,
'SOCKET_WRAPPER_DEFAULT_IFACE': '9',
'WRAP_PROXY_PORT': WRAP_PROXY_PORT,
'NSS_WRAPPER_HOSTNAME': WRAP_HOSTNAME,
'NSS_WRAPPER_HOSTS': hosts_file}

Expand All @@ -73,8 +77,8 @@ def setup_wrappers(base):
}

[domain_realm]
.mag.dev = MAG.DEV
mag.dev = MAG.DEV
.mag.dev = ${TESTREALM}
mag.dev = ${TESTREALM}

[dbmodules]
${TESTREALM} = {
Expand Down Expand Up @@ -167,6 +171,8 @@ def kadmin_local(cmd, env, logfile):

USR_NAME = "maguser"
USR_PWD = "magpwd"
USR_NAME_2 = "maguser2"
USR_PWD_2 = "magpwd2"
SVC_KTNAME = "httpd/http.keytab"
KEY_TYPE = "aes256-cts-hmac-sha1-96:normal"

Expand All @@ -188,6 +194,10 @@ def setup_keys(tesdir, env):
with (open(testlog, 'a')) as logfile:
kadmin_local(cmd, env, logfile)

cmd = "addprinc -pw %s -e %s %s" % (USR_PWD_2, KEY_TYPE, USR_NAME_2)
with (open(testlog, 'a')) as logfile:
kadmin_local(cmd, env, logfile)

keys_env = { "KRB5_KTNAME": svc_keytab }
keys_env.update(env)

Expand All @@ -212,7 +222,8 @@ def setup_http(testdir, wrapenv):
text = t.substitute({'HTTPROOT': httpdir,
'HTTPNAME': WRAP_HOSTNAME,
'HTTPADDR': WRAP_IPADDR,
'HTTPPORT': '80'})
'PROXYPORT': WRAP_PROXY_PORT,
'HTTPPORT': WRAP_HTTP_PORT})
config = os.path.join(httpdir, 'httpd.conf')
with open(config, 'w+') as f:
f.write(text)
Expand Down Expand Up @@ -263,6 +274,20 @@ def test_spnego_auth(testdir, testenv, testlog):
else:
sys.stderr.write('SPNEGO: SUCCESS\n')

curl = "curl -vf http://%s:%s@%s/spnego/ -x http://%s:%s -U: --proxy-negotiate" % (
USR_NAME, USR_PWD, WRAP_HOSTNAME, WRAP_HOSTNAME, WRAP_PROXY_PORT)
curl = shlex.split(curl)

with (open(testlog, 'a')) as logfile:
spnego = subprocess.Popen(curl,
stdout=logfile, stderr=logfile,
env=testenv, preexec_fn=os.setsid)
spnego.wait()
if spnego.returncode != 0:
sys.stderr.write('SPNEGO Proxy Auth: FAILED\n')
else:
sys.stderr.write('SPNEGO Proxy Auth: SUCCESS\n')


def test_basic_auth_krb5(testdir, testenv, testlog):

Expand All @@ -280,6 +305,36 @@ def test_basic_auth_krb5(testdir, testenv, testlog):
else:
sys.stderr.write('BASIC-AUTH: SUCCESS\n')

with (open(testlog, 'a')) as logfile:
basick5 = subprocess.Popen(["tests/t_basic_k5_two_users.py"],
stdout=logfile, stderr=logfile,
env=testenv, preexec_fn=os.setsid)
basick5.wait()
if basick5.returncode != 0:
sys.stderr.write('BASIC-AUTH Two Users: FAILED\n')
else:
sys.stderr.write('BASIC-AUTH Two Users: SUCCESS\n')

with (open(testlog, 'a')) as logfile:
basick5 = subprocess.Popen(["tests/t_basic_k5_fail_second.py"],
stdout=logfile, stderr=logfile,
env=testenv, preexec_fn=os.setsid)
basick5.wait()
if basick5.returncode != 0:
sys.stderr.write('BASIC Fail Second User: FAILED\n')
else:
sys.stderr.write('BASIC Fail Secnond User: SUCCESS\n')

with (open(testlog, 'a')) as logfile:
basick5 = subprocess.Popen(["tests/t_basic_proxy.py"],
stdout=logfile, stderr=logfile,
env=testenv, preexec_fn=os.setsid)
basick5.wait()
if basick5.returncode != 0:
sys.stderr.write('BASIC Proxy Auth: FAILED\n')
else:
sys.stderr.write('BASIC Proxy Auth: SUCCESS\n')


if __name__ == '__main__':

Expand Down Expand Up @@ -310,7 +365,9 @@ def test_basic_auth_krb5(testdir, testenv, testlog):


testenv = {'MAG_USER_NAME': USR_NAME,
'MAG_USER_PASSWORD': USR_PWD}
'MAG_USER_PASSWORD': USR_PWD,
'MAG_USER_NAME_2': USR_NAME_2,
'MAG_USER_PASSWORD_2': USR_PWD_2}
testenv.update(kdcenv)
test_basic_auth_krb5(testdir, testenv, testlog)

Expand Down
Loading