diff --git a/src/mod_auth_gssapi.c b/src/mod_auth_gssapi.c index 763b625..b7664b2 100644 --- a/src/mod_auth_gssapi.c +++ b/src/mod_auth_gssapi.c @@ -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++) { @@ -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; @@ -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) { @@ -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 && @@ -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; @@ -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; @@ -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))); } diff --git a/src/mod_auth_gssapi.h b/src/mod_auth_gssapi.h index 5beda2d..46e5c6a 100644 --- a/src/mod_auth_gssapi.h +++ b/src/mod_auth_gssapi.h @@ -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; diff --git a/tests/httpd.conf b/tests/httpd.conf index 77701f9..8c6370e 100644 --- a/tests/httpd.conf +++ b/tests/httpd.conf @@ -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 @@ -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 + Options +Includes + AddOutputFilter INCLUDES .html AllowOverride none Require all denied @@ -117,7 +121,10 @@ IncludeOptional conf.d/*.conf CoreDumpDirectory /tmp + + Options +Includes + AddOutputFilter INCLUDES .html AuthType GSSAPI AuthName "Login" GssapiSSLonly Off @@ -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 + Options +Includes + AddOutputFilter INCLUDES .html AuthType GSSAPI AuthName "Password Login" GssapiSSLonly Off @@ -141,6 +150,21 @@ CoreDumpDirectory /tmp GssapiCredStore keytab:${HTTPROOT}/http.keytab GssapiBasicAuth On GssapiBasicAuthMech krb5 + GssapiConnectionBound On Require valid-user + + ProxyRequests On + ProxyVia On + + + 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 + + diff --git a/tests/index.html b/tests/index.html index c5ad10e..9416405 100644 --- a/tests/index.html +++ b/tests/index.html @@ -1 +1 @@ -WORKS + diff --git a/tests/magtests.py b/tests/magtests.py index 27f55f2..a38783d 100755 --- a/tests/magtests.py +++ b/tests/magtests.py @@ -11,6 +11,7 @@ import subprocess import sys import time +import shlex def parse_args(): @@ -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): @@ -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} @@ -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} = { @@ -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" @@ -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) @@ -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) @@ -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): @@ -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__': @@ -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) diff --git a/tests/t_basic_k5_fail_second.py b/tests/t_basic_k5_fail_second.py new file mode 100755 index 0000000..cd78c4f --- /dev/null +++ b/tests/t_basic_k5_fail_second.py @@ -0,0 +1,36 @@ +#!/usr/bin/python +# Copyright (C) 2015 - mod_auth_gssapi contributors, see COPYING for license. + +import os +import requests +from requests.auth import HTTPBasicAuth + + +if __name__ == '__main__': + s = requests.Session() + + url = 'http://%s:%s@%s/basic_auth_krb5/' % (os.environ['MAG_USER_NAME'], + os.environ['MAG_USER_PASSWORD'], + os.environ['NSS_WRAPPER_HOSTNAME']) + r = s.get(url) + if r.status_code != 200: + raise ValueError('Basic Auth Failed') + + url = 'http://%s:%s@%s/basic_auth_krb5/' % (os.environ['MAG_USER_NAME_2'], + os.environ['MAG_USER_PASSWORD'], + os.environ['NSS_WRAPPER_HOSTNAME']) + r = s.get(url) + if r.status_code == 200: + raise ValueError('Basic Auth fatal error') + + url = 'http://%s:%s@%s/basic_auth_krb5/' % (os.environ['MAG_USER_NAME_2'], + os.environ['MAG_USER_PASSWORD_2'], + os.environ['NSS_WRAPPER_HOSTNAME']) + r = s.get(url) + if r.status_code != 200: + raise ValueError('Basic Auth Failed') + + url = 'http://%s/basic_auth_krb5/' % os.environ['NSS_WRAPPER_HOSTNAME'] + r = s.get(url) + if r.status_code == 200: + raise ValueError('Basic Auth fatal error') diff --git a/tests/t_basic_k5_two_users.py b/tests/t_basic_k5_two_users.py new file mode 100755 index 0000000..0d3d45b --- /dev/null +++ b/tests/t_basic_k5_two_users.py @@ -0,0 +1,27 @@ +#!/usr/bin/python +# Copyright (C) 2015 - mod_auth_gssapi contributors, see COPYING for license. + +import os +import requests +from requests.auth import HTTPBasicAuth + + +if __name__ == '__main__': + s = requests.Session() + + url = 'http://%s:%s@%s/basic_auth_krb5/' % (os.environ['MAG_USER_NAME'], + os.environ['MAG_USER_PASSWORD'], + os.environ['NSS_WRAPPER_HOSTNAME']) + r = s.get(url) + if r.status_code != 200: + raise ValueError('Basic Auth Failed') + + url = 'http://%s:%s@%s/basic_auth_krb5/' % (os.environ['MAG_USER_NAME_2'], + os.environ['MAG_USER_PASSWORD_2'], + os.environ['NSS_WRAPPER_HOSTNAME']) + r2 = s.get(url) + if r2.status_code != 200: + raise ValueError('Basic Auth failed') + + if r.text == r2.text: + raise ValueError('Basic Auth fatal error') diff --git a/tests/t_basic_proxy.py b/tests/t_basic_proxy.py new file mode 100755 index 0000000..4290695 --- /dev/null +++ b/tests/t_basic_proxy.py @@ -0,0 +1,20 @@ +#!/usr/bin/python +# Copyright (C) 2015 - mod_auth_gssapi contributors, see COPYING for license. + +import os +import requests +from requests.auth import HTTPBasicAuth + + +if __name__ == '__main__': + proxy = 'http://%s:%s@%s:%s' % (os.environ['MAG_USER_NAME'], + os.environ['MAG_USER_PASSWORD'], + os.environ['NSS_WRAPPER_HOSTNAME'], + os.environ['WRAP_PROXY_PORT']) + proxies = { "http": proxy, } + url = 'http://%s/basic_auth_krb5/' % os.environ['NSS_WRAPPER_HOSTNAME'] + r = requests.get(url, proxies=proxies, + auth=HTTPBasicAuth(os.environ['MAG_USER_NAME_2'], + os.environ['MAG_USER_PASSWORD_2'])) + if r.status_code != 200: + raise ValueError('Basic Proxy Auth Failed')