diff --git a/README b/README
index 136c730..a914a39 100644
--- a/README
+++ b/README
@@ -97,6 +97,7 @@ Configuration Directives
[GssapiAllowedMech](#gssapiallowedmech)
[GssapiBasicAuth](#gssapibasicauth)
[GssapiBasicAuthMech](#gssapibasicauthmech)
+[GssapiBasicTicketTimeout](#gssapibasicticketvalidity)
[GssapiConnectionBound](#gssapiconnectionbound)
[GssapiCredStore](#gssapicredstore)
[GssapiDelegCcacheDir](#gssapidelegccachedir)
@@ -519,3 +520,26 @@ Note: The GSS_C_NT_HOSTBASED_SERVICE format is used for names (see example).
GssapiAcceptorName HTTP@www.example.com
+### GssapiBasicTicketTimeout
+
+This option controls the ticket validity time requested for the user TGT by the
+Basic Auth method.
+
+Normally basic auth is repeated by the browser on each request so a short
+validity period is used to reduce the scope of the ticket as it will be
+replaced quickly.
+However in cases where the authentication page is separate and the session
+is used by other pages the validity can be changed to arbitrary duration.
+
+Note: the validity of a ticket is still capped by KDC configuration.
+
+Note: the value is specified in seconds.
+
+- **Default:** GssapiBasicTicketTimeout 300
+
+#### Example
+ GssapiBasicTicketTimeout 36000
+
+Sets ticket/session validity to 10 hours.
+
+
diff --git a/src/mod_auth_gssapi.c b/src/mod_auth_gssapi.c
index a7de591..0b9a983 100644
--- a/src/mod_auth_gssapi.c
+++ b/src/mod_auth_gssapi.c
@@ -1,4 +1,5 @@
-/* Copyright (C) 2014, 2016 mod_auth_gssapi contributors - See COPYING for (C) terms */
+/* Copyright (C) 2014, 2016, 2020 mod_auth_gssapi contributors
+ * See COPYING for (C) terms */
#include "mod_auth_gssapi.h"
#include "mag_parse.h"
@@ -605,7 +606,7 @@ static int mag_auth_basic(struct mag_req_cfg *req_cfg, struct mag_conn *mc,
}
maj = gss_acquire_cred_with_password(&min, user, &ba_pwd,
- GSS_C_INDEFINITE,
+ cfg->basic_timeout,
allowed_mechs,
GSS_C_INITIATE,
&user_cred, &actual_mechs, NULL);
@@ -624,8 +625,8 @@ static int mag_auth_basic(struct mag_req_cfg *req_cfg, struct mag_conn *mc,
for (int i = 0; i < actual_mechs->count; i++) {
maj = mag_context_loop(&min, req, cfg, user_cred, server_cred,
- &actual_mechs->elements[i], 300, &client,
- &vtime, &delegated_cred);
+ &actual_mechs->elements[i], cfg->basic_timeout,
+ &client, &vtime, &delegated_cred);
if (maj == GSS_S_COMPLETE) {
ret = mag_complete(req_cfg, mc, client, &actual_mechs->elements[i],
vtime, delegated_cred);
@@ -1318,6 +1319,7 @@ static void *mag_create_dir_config(apr_pool_t *p, char *dir)
#ifdef HAVE_CRED_STORE
cfg->ccname_envvar = "KRB5CCNAME";
#endif
+ cfg->basic_timeout = 300;
return cfg;
}
@@ -1808,6 +1810,21 @@ static const char *mag_acceptor_name(cmd_parms *parms, void *mconfig,
return NULL;
}
+static const char *mag_basic_timeout(cmd_parms *parms, void *mconfig,
+ const char *w)
+{
+ struct mag_config *cfg = (struct mag_config *)mconfig;
+ unsigned long int value;
+
+ value = strtoul(w, NULL, 10);
+ if (value >= UINT32_MAX) {
+ cfg->basic_timeout = GSS_C_INDEFINITE;
+ return NULL;
+ }
+ cfg->basic_timeout = value;
+ return NULL;
+}
+
static void *mag_create_server_config(apr_pool_t *p, server_rec *s)
{
struct mag_server_config *scfg;
@@ -1884,6 +1901,8 @@ static const command_rec mag_commands[] = {
"Publish GSSAPI Errors in Envionment Variables"),
AP_INIT_RAW_ARGS("GssapiAcceptorName", mag_acceptor_name, NULL, OR_AUTHCFG,
"Name of the acceptor credentials."),
+ AP_INIT_TAKE1("GssapiBasicTicketTimeout", mag_basic_timeout, NULL,
+ OR_AUTHCFG, "Ticket Validity Timeout with Basic Auth."),
{ NULL }
};
diff --git a/src/mod_auth_gssapi.h b/src/mod_auth_gssapi.h
index 136cefd..a9a09d7 100644
--- a/src/mod_auth_gssapi.h
+++ b/src/mod_auth_gssapi.h
@@ -97,6 +97,7 @@ struct mag_config {
int enverrs;
gss_name_t acceptor_name;
bool acceptor_name_from_req;
+ uint32_t basic_timeout;
};
struct mag_server_config {
diff --git a/tests/Makefile.am b/tests/Makefile.am
index 70754fb..c830e95 100644
--- a/tests/Makefile.am
+++ b/tests/Makefile.am
@@ -4,12 +4,15 @@ EXTRA_DIST = \
401.html \
httpd.conf \
index.html \
+ localname.html \
magtests.py \
t_bad_acceptor_name.py \
t_basic_k5_fail_second.py \
t_basic_k5.py \
t_basic_k5_two_users.py \
t_basic_proxy.py \
+ t_basic_timeout.py \
+ t_localname.py \
t_hostname_acceptor.py \
t_nonego.py \
t_required_name_attr.py \
diff --git a/tests/httpd.conf b/tests/httpd.conf
index c274622..b377757 100644
--- a/tests/httpd.conf
+++ b/tests/httpd.conf
@@ -111,7 +111,7 @@ DocumentRoot "{HTTPROOT}/html"
PidFile "{HTTPROOT}/logs/httpd.pid"
-LogFormat "%h %l %u %t \"%r\" %>s %b \"%{{Referer}}i\" \"%{{User-Agent}}i\"" combined
+LogFormat "%h %l %u %t \"%r\" %>s %b \"%{{Referer}}i\" \"%{{User-Agent}}i\" \"%{{Cookie}}i\"" combined
CustomLog "logs/access_log" combined
@@ -301,3 +301,33 @@ CoreDumpDirectory "{HTTPROOT}"
Require valid-user
+
+
+ Options +Includes
+ AddOutputFilter INCLUDES .html
+ AuthType GSSAPI
+ AuthName "Password Login"
+ GssapiSSLonly Off
+ GssapiUseSessions On
+ Session On
+ SessionCookieName gssapi_session path=/basic_auth_timeout;httponly
+ GssapiSessionKey file:{HTTPROOT}/session.key
+ GssapiCredStore keytab:{HTTPROOT}/http.keytab
+ GssapiBasicAuth On
+ GssapiBasicAuthMech krb5
+ GssapiBasicTicketTimeout 400
+ GssapiDelegCcacheDir {HTTPROOT}
+ Require valid-user
+
+
+ Options +Includes
+ AddOutputFilter INCLUDES .html
+ AuthType GSSAPI
+ AuthName "Session Login"
+ GssapiSSLonly Off
+ GssapiUseSessions On
+ Session On
+ SessionCookieName gssapi_session path=/basic_auth_timeout;httponly
+ GssapiSessionKey file:{HTTPROOT}/session.key
+ Require valid-user
+
diff --git a/tests/magtests.py b/tests/magtests.py
index 5bd23f2..2f8b0ae 100755
--- a/tests/magtests.py
+++ b/tests/magtests.py
@@ -3,15 +3,18 @@
import argparse
import os
+import os.path
import random
import shutil
import signal
import subprocess
import sys
+import time
import traceback
# check that we can import requests (for use in test scripts)
import requests
+
import requests_gssapi
del requests
del requests_gssapi
@@ -62,6 +65,7 @@ def setup_wrappers(base):
f.write('maguser:x:1:1:maguser:/maguser:/bin/sh')
f.write('maguser2:x:2:2:maguser2:/maguser2:/bin/sh')
f.write('maguser3:x:3:3:maguser3:/maguser3:/bin/sh')
+ f.write('timeoutusr:x:4:4:timeoutusr:/timeoutusr:/bin/sh')
wenv = {'LD_PRELOAD': 'libsocket_wrapper.so libnss_wrapper.so',
'SOCKET_WRAPPER_DIR': wrapdir,
@@ -349,6 +353,7 @@ def kadmin_local(cmd, env, logfile):
USR_NAME_3 = "maguser3"
SVC_KTNAME = "httpd/http.keytab"
KEY_TYPE = "aes256-cts-hmac-sha1-96:normal"
+USR_NAME_4 = "timeoutusr"
def setup_keys(tesdir, env):
@@ -369,6 +374,9 @@ def setup_keys(tesdir, env):
cmd = "addprinc -pw %s -e %s %s" % (USR_PWD_2, KEY_TYPE, USR_NAME_2)
kadmin_local(cmd, env, logfile)
+ cmd = "addprinc -pw %s -e %s %s" % (USR_PWD, KEY_TYPE, USR_NAME_4)
+ kadmin_local(cmd, env, logfile)
+
# alias for multinamed hosts testing
alias_name = "HTTP/%s" % WRAP_ALIASNAME
cmd = "addprinc -randkey -e %s %s" % (KEY_TYPE, alias_name)
@@ -607,6 +615,30 @@ def test_basic_auth_krb5(testdir, testenv, logfile):
return error_count
+def test_basic_auth_timeout(testdir, testenv, logfile):
+ httpdir = os.path.join(testdir, 'httpd')
+ timeoutdir = os.path.join(httpdir, 'html', 'basic_auth_timeout')
+ os.mkdir(timeoutdir)
+ authdir = os.path.join(timeoutdir, 'auth')
+ os.mkdir(authdir)
+ sessdir = os.path.join(timeoutdir, 'session')
+ os.mkdir(sessdir)
+ shutil.copy('tests/index.html', os.path.join(authdir))
+ shutil.copy('tests/index.html', os.path.join(sessdir))
+
+ basictout = subprocess.Popen(["tests/t_basic_timeout.py"],
+ stdout=logfile, stderr=logfile,
+ env=testenv, preexec_fn=os.setsid)
+ basictout.wait()
+ if basictout.returncode != 0:
+ sys.stderr.write('BASIC Timeout Behavior: FAILED\n')
+ return 1
+ else:
+ sys.stderr.write('BASIC Timeout Behavior: SUCCESS\n')
+
+ return 0
+
+
def test_bad_acceptor_name(testdir, testenv, logfile):
bandir = os.path.join(testdir, 'httpd', 'html', 'bad_acceptor_name')
os.mkdir(bandir)
@@ -702,6 +734,33 @@ def test_gss_localname(testdir, testenv, logfile):
return error_count
+def faketime_setup(testenv):
+ libfaketime = '/usr/lib64/faketime/libfaketime.so.1'
+ # optional faketime
+ if not os.path.isfile(libfaketime):
+ raise NotImplementedError
+
+ # spedup x100
+ fakeenv = {'FAKETIME': '+0 x100'}
+ fakeenv.update(testenv)
+ fakeenv['LD_PRELOAD'] = ' '.join((testenv['LD_PRELOAD'], libfaketime))
+ return fakeenv
+
+
+def http_restart(testdir, so_dir, testenv):
+
+ httpenv = {'PATH': '/sbin:/bin:/usr/sbin:/usr/bin',
+ 'MALLOC_CHECK_': '3',
+ 'MALLOC_PERTURB_': str(random.randint(0, 32767) % 255 + 1)}
+ httpenv.update(testenv)
+
+ httpd = "httpd" if os.path.exists("/etc/httpd/modules") else "apache2"
+ config = os.path.join(testdir, 'httpd', 'httpd.conf')
+ httpproc = subprocess.Popen([httpd, '-DFOREGROUND', '-f', config],
+ env=httpenv, preexec_fn=os.setsid)
+ return httpproc
+
+
if __name__ == '__main__':
args = parse_args()
@@ -766,6 +825,25 @@ def test_gss_localname(testdir, testenv, logfile):
errs += test_basic_auth_krb5(testdir, testenv, logfile)
errs += test_no_negotiate(testdir, testenv, logfile)
+
+ # After this point we need to speed up httpd to test creds timeout
+ try:
+ fakeenv = faketime_setup(kdcenv)
+ timeenv = {'TIMEOUT_USER': USR_NAME_4,
+ 'MAG_USER_PASSWORD': USR_PWD}
+ timeenv.update(fakeenv)
+ curporc = httpproc
+ pid = processes['HTTPD(%d)' % httpproc.pid].pid
+ os.killpg(pid, signal.SIGTERM)
+ time.sleep(1)
+ del processes['HTTPD(%d)' % httpproc.pid]
+ httpproc = http_restart(testdir, so_dir, timeenv)
+ processes['HTTPD(%d)' % httpproc.pid] = httpproc
+
+ errs += test_basic_auth_timeout(testdir, timeenv, logfile)
+ except NotImplementedError:
+ sys.stderr.write('BASIC Timeout Behavior: SKIPPED\n')
+
except Exception:
traceback.print_exc()
finally:
diff --git a/tests/t_bad_acceptor_name.py b/tests/t_bad_acceptor_name.py
index 41ee48b..0e8c0fa 100755
--- a/tests/t_bad_acceptor_name.py
+++ b/tests/t_bad_acceptor_name.py
@@ -2,6 +2,7 @@
# Copyright (C) 2015 - mod_auth_gssapi contributors, see COPYING for license.
import os
+
import requests
from requests_gssapi import HTTPKerberosAuth, OPTIONAL # noqa
diff --git a/tests/t_basic_k5.py b/tests/t_basic_k5.py
index e499eac..aaa60d5 100755
--- a/tests/t_basic_k5.py
+++ b/tests/t_basic_k5.py
@@ -2,6 +2,7 @@
# Copyright (C) 2015 - mod_auth_gssapi contributors, see COPYING for license.
import os
+
import requests
from requests.auth import HTTPBasicAuth
diff --git a/tests/t_basic_k5_fail_second.py b/tests/t_basic_k5_fail_second.py
index 273e9a5..de35707 100755
--- a/tests/t_basic_k5_fail_second.py
+++ b/tests/t_basic_k5_fail_second.py
@@ -2,6 +2,7 @@
# Copyright (C) 2015 - mod_auth_gssapi contributors, see COPYING for license.
import os
+
import requests
diff --git a/tests/t_basic_k5_two_users.py b/tests/t_basic_k5_two_users.py
index 41ffe98..ed98255 100755
--- a/tests/t_basic_k5_two_users.py
+++ b/tests/t_basic_k5_two_users.py
@@ -2,6 +2,7 @@
# Copyright (C) 2015 - mod_auth_gssapi contributors, see COPYING for license.
import os
+
import requests
diff --git a/tests/t_basic_proxy.py b/tests/t_basic_proxy.py
index 5370314..14e90aa 100755
--- a/tests/t_basic_proxy.py
+++ b/tests/t_basic_proxy.py
@@ -2,6 +2,7 @@
# Copyright (C) 2015 - mod_auth_gssapi contributors, see COPYING for license.
import os
+
import requests
from requests.auth import HTTPBasicAuth
diff --git a/tests/t_basic_timeout.py b/tests/t_basic_timeout.py
new file mode 100755
index 0000000..983dfd2
--- /dev/null
+++ b/tests/t_basic_timeout.py
@@ -0,0 +1,34 @@
+#!/usr/bin/env python
+# Copyright (C) 2020 - mod_auth_gssapi contributors, see COPYING for license.
+
+import os
+import time
+
+import requests
+from requests.auth import HTTPBasicAuth
+
+
+if __name__ == '__main__':
+ s = requests.Session()
+ url = 'http://{}/basic_auth_timeout/auth/'.format(
+ os.environ['NSS_WRAPPER_HOSTNAME']
+ )
+ url2 = 'http://{}/basic_auth_timeout/session/'.format(
+ os.environ['NSS_WRAPPER_HOSTNAME']
+ )
+
+ r = s.get(url, auth=HTTPBasicAuth(os.environ['TIMEOUT_USER'],
+ os.environ['MAG_USER_PASSWORD']))
+ if r.status_code != 200:
+ raise ValueError('Basic Auth Failed')
+
+ time.sleep(301)
+ r = s.get(url2)
+ if r.status_code != 200:
+ raise ValueError('Session Auth Failed')
+
+ time.sleep(401)
+
+ r = s.get(url2)
+ if r.status_code == 200:
+ raise ValueError('Timeout check Failed')
diff --git a/tests/t_localname.py b/tests/t_localname.py
index c9f08b1..4950869 100755
--- a/tests/t_localname.py
+++ b/tests/t_localname.py
@@ -2,12 +2,16 @@
# Copyright (C) 2020 - mod_auth_gssapi contributors, see COPYING for license.
import os
-import gssapi
-import requests
import subprocess
import sys
+
+import gssapi
+
+import requests
+
from requests_gssapi import HTTPSPNEGOAuth
+
def use_requests(auth):
sess = requests.Session()
url = 'http://%s/gss_localname/' % os.environ['NSS_WRAPPER_HOSTNAME']
@@ -39,14 +43,14 @@ def use_curl():
if len(sys.argv) > 1:
mech_name = sys.argv[1]
- mech=None
+ mech = None
if mech_name is not None:
mech = gssapi.mechs.Mechanism.from_sasl_name(mech_name)
try:
auth = HTTPSPNEGOAuth(mech=mech)
use_requests(auth)
- except TypeError as e:
+ except TypeError:
# odler version of requests that does not support mechs
if mech_name == 'SPNEGO':
use_curl()
@@ -55,4 +59,4 @@ def use_curl():
auth = HTTPSPNEGOAuth()
use_requests(auth)
else:
- sys.exit(42) # SKIP
+ sys.exit(42) # SKIP
diff --git a/tests/t_nonego.py b/tests/t_nonego.py
index 430001a..6cb7770 100755
--- a/tests/t_nonego.py
+++ b/tests/t_nonego.py
@@ -2,6 +2,7 @@
# Copyright (C) 2015 - mod_auth_gssapi contributors, see COPYING for license.
import os
+
import requests
diff --git a/tests/t_required_name_attr.py b/tests/t_required_name_attr.py
index bbfdc19..b0c9724 100755
--- a/tests/t_required_name_attr.py
+++ b/tests/t_required_name_attr.py
@@ -2,6 +2,7 @@
# Copyright (C) 2015 - mod_auth_gssapi contributors, see COPYING for license.
import os
+
import requests
from requests_gssapi import HTTPKerberosAuth, OPTIONAL # noqa
diff --git a/tests/t_spnego_negotiate_once.py b/tests/t_spnego_negotiate_once.py
index e8eb601..4e57b95 100755
--- a/tests/t_spnego_negotiate_once.py
+++ b/tests/t_spnego_negotiate_once.py
@@ -2,6 +2,7 @@
# Copyright (C) 2015 - mod_auth_gssapi contributors, see COPYING for license.
import os
+
import requests
from requests_gssapi import HTTPKerberosAuth, OPTIONAL # noqa
diff --git a/tests/t_spnego_no_auth.py b/tests/t_spnego_no_auth.py
index abcccdf..ac04b0b 100755
--- a/tests/t_spnego_no_auth.py
+++ b/tests/t_spnego_no_auth.py
@@ -2,6 +2,7 @@
# Copyright (C) 2015 - mod_auth_gssapi contributors, see COPYING for license.
import os
+
import requests
from requests_gssapi import HTTPKerberosAuth, OPTIONAL # noqa
diff --git a/tests/t_spnego_proxy.py b/tests/t_spnego_proxy.py
index c47558b..9db9a4f 100755
--- a/tests/t_spnego_proxy.py
+++ b/tests/t_spnego_proxy.py
@@ -5,6 +5,7 @@
from base64 import b64encode
import gssapi
+
import requests
diff --git a/tests/t_spnego_rewrite.py b/tests/t_spnego_rewrite.py
index 2ed1d3e..4205a92 100755
--- a/tests/t_spnego_rewrite.py
+++ b/tests/t_spnego_rewrite.py
@@ -2,6 +2,7 @@
# Copyright (C) 2015 - mod_auth_gssapi contributors, see COPYING for license.
import os
+
import requests
from requests_gssapi import HTTPKerberosAuth, OPTIONAL # noqa