Skip to content

Commit 09df758

Browse files
simo5frozencemetery
authored andcommitted
Add option to control timeout for Basic Auth
Adds new option and tests. Adds optional dependency on libfaketime to test this feature. Fixes: #210 Signed-off-by: Simo Sorce <simo@redhat.com> Merges: #217 Reviewed-by: Robbie Harwood <rharwood@redhat.com>
1 parent eb6de7e commit 09df758

File tree

7 files changed

+191
-5
lines changed

7 files changed

+191
-5
lines changed

README

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -97,6 +97,7 @@ Configuration Directives
9797
[GssapiAllowedMech](#gssapiallowedmech)<br>
9898
[GssapiBasicAuth](#gssapibasicauth)<br>
9999
[GssapiBasicAuthMech](#gssapibasicauthmech)<br>
100+
[GssapiBasicTicketTimeout](#gssapibasicticketvalidity)<br>
100101
[GssapiConnectionBound](#gssapiconnectionbound)<br>
101102
[GssapiCredStore](#gssapicredstore)<br>
102103
[GssapiDelegCcacheDir](#gssapidelegccachedir)<br>
@@ -519,3 +520,26 @@ Note: The GSS_C_NT_HOSTBASED_SERVICE format is used for names (see example).
519520
GssapiAcceptorName HTTP@www.example.com
520521

521522

523+
### GssapiBasicTicketTimeout
524+
525+
This option controls the ticket validity time requested for the user TGT by the
526+
Basic Auth method.
527+
528+
Normally basic auth is repeated by the browser on each request so a short
529+
validity period is used to reduce the scope of the ticket as it will be
530+
replaced quickly.
531+
However in cases where the authentication page is separate and the session
532+
is used by other pages the validity can be changed to arbitrary duration.
533+
534+
Note: the validity of a ticket is still capped by KDC configuration.
535+
536+
Note: the value is specified in seconds.
537+
538+
- **Default:** GssapiBasicTicketTimeout 300
539+
540+
#### Example
541+
GssapiBasicTicketTimeout 36000
542+
543+
Sets ticket/session validity to 10 hours.
544+
545+

src/mod_auth_gssapi.c

Lines changed: 23 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
1-
/* Copyright (C) 2014, 2016 mod_auth_gssapi contributors - See COPYING for (C) terms */
1+
/* Copyright (C) 2014, 2016, 2020 mod_auth_gssapi contributors
2+
* See COPYING for (C) terms */
23

34
#include "mod_auth_gssapi.h"
45
#include "mag_parse.h"
@@ -605,7 +606,7 @@ static int mag_auth_basic(struct mag_req_cfg *req_cfg, struct mag_conn *mc,
605606
}
606607

607608
maj = gss_acquire_cred_with_password(&min, user, &ba_pwd,
608-
GSS_C_INDEFINITE,
609+
cfg->basic_timeout,
609610
allowed_mechs,
610611
GSS_C_INITIATE,
611612
&user_cred, &actual_mechs, NULL);
@@ -624,8 +625,8 @@ static int mag_auth_basic(struct mag_req_cfg *req_cfg, struct mag_conn *mc,
624625

625626
for (int i = 0; i < actual_mechs->count; i++) {
626627
maj = mag_context_loop(&min, req, cfg, user_cred, server_cred,
627-
&actual_mechs->elements[i], 300, &client,
628-
&vtime, &delegated_cred);
628+
&actual_mechs->elements[i], cfg->basic_timeout,
629+
&client, &vtime, &delegated_cred);
629630
if (maj == GSS_S_COMPLETE) {
630631
ret = mag_complete(req_cfg, mc, client, &actual_mechs->elements[i],
631632
vtime, delegated_cred);
@@ -1318,6 +1319,7 @@ static void *mag_create_dir_config(apr_pool_t *p, char *dir)
13181319
#ifdef HAVE_CRED_STORE
13191320
cfg->ccname_envvar = "KRB5CCNAME";
13201321
#endif
1322+
cfg->basic_timeout = 300;
13211323

13221324
return cfg;
13231325
}
@@ -1808,6 +1810,21 @@ static const char *mag_acceptor_name(cmd_parms *parms, void *mconfig,
18081810
return NULL;
18091811
}
18101812

1813+
static const char *mag_basic_timeout(cmd_parms *parms, void *mconfig,
1814+
const char *w)
1815+
{
1816+
struct mag_config *cfg = (struct mag_config *)mconfig;
1817+
unsigned long int value;
1818+
1819+
value = strtoul(w, NULL, 10);
1820+
if (value >= UINT32_MAX) {
1821+
cfg->basic_timeout = GSS_C_INDEFINITE;
1822+
return NULL;
1823+
}
1824+
cfg->basic_timeout = value;
1825+
return NULL;
1826+
}
1827+
18111828
static void *mag_create_server_config(apr_pool_t *p, server_rec *s)
18121829
{
18131830
struct mag_server_config *scfg;
@@ -1884,6 +1901,8 @@ static const command_rec mag_commands[] = {
18841901
"Publish GSSAPI Errors in Envionment Variables"),
18851902
AP_INIT_RAW_ARGS("GssapiAcceptorName", mag_acceptor_name, NULL, OR_AUTHCFG,
18861903
"Name of the acceptor credentials."),
1904+
AP_INIT_TAKE1("GssapiBasicTicketTimeout", mag_basic_timeout, NULL,
1905+
OR_AUTHCFG, "Ticket Validity Timeout with Basic Auth."),
18871906
{ NULL }
18881907
};
18891908

src/mod_auth_gssapi.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -97,6 +97,7 @@ struct mag_config {
9797
int enverrs;
9898
gss_name_t acceptor_name;
9999
bool acceptor_name_from_req;
100+
uint32_t basic_timeout;
100101
};
101102

102103
struct mag_server_config {

tests/Makefile.am

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ EXTRA_DIST = \
1111
t_basic_k5.py \
1212
t_basic_k5_two_users.py \
1313
t_basic_proxy.py \
14+
t_basic_timeout.py \
1415
t_localname.py \
1516
t_hostname_acceptor.py \
1617
t_nonego.py \

tests/httpd.conf

Lines changed: 31 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -111,7 +111,7 @@ DocumentRoot "{HTTPROOT}/html"
111111
PidFile "{HTTPROOT}/logs/httpd.pid"
112112

113113
<IfModule log_config_module>
114-
LogFormat "%h %l %u %t \"%r\" %>s %b \"%{{Referer}}i\" \"%{{User-Agent}}i\"" combined
114+
LogFormat "%h %l %u %t \"%r\" %>s %b \"%{{Referer}}i\" \"%{{User-Agent}}i\" \"%{{Cookie}}i\"" combined
115115
CustomLog "logs/access_log" combined
116116
</IfModule>
117117

@@ -301,3 +301,33 @@ CoreDumpDirectory "{HTTPROOT}"
301301
Require valid-user
302302
</Proxy>
303303
</VirtualHost>
304+
305+
<Location /basic_auth_timeout/auth>
306+
Options +Includes
307+
AddOutputFilter INCLUDES .html
308+
AuthType GSSAPI
309+
AuthName "Password Login"
310+
GssapiSSLonly Off
311+
GssapiUseSessions On
312+
Session On
313+
SessionCookieName gssapi_session path=/basic_auth_timeout;httponly
314+
GssapiSessionKey file:{HTTPROOT}/session.key
315+
GssapiCredStore keytab:{HTTPROOT}/http.keytab
316+
GssapiBasicAuth On
317+
GssapiBasicAuthMech krb5
318+
GssapiBasicTicketTimeout 400
319+
GssapiDelegCcacheDir {HTTPROOT}
320+
Require valid-user
321+
</Location>
322+
<Location /basic_auth_timeout/session>
323+
Options +Includes
324+
AddOutputFilter INCLUDES .html
325+
AuthType GSSAPI
326+
AuthName "Session Login"
327+
GssapiSSLonly Off
328+
GssapiUseSessions On
329+
Session On
330+
SessionCookieName gssapi_session path=/basic_auth_timeout;httponly
331+
GssapiSessionKey file:{HTTPROOT}/session.key
332+
Require valid-user
333+
</Location>

tests/magtests.py

Lines changed: 77 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,11 +3,13 @@
33

44
import argparse
55
import os
6+
import os.path
67
import random
78
import shutil
89
import signal
910
import subprocess
1011
import sys
12+
import time
1113
import traceback
1214

1315
# check that we can import requests (for use in test scripts)
@@ -63,6 +65,7 @@ def setup_wrappers(base):
6365
f.write('maguser:x:1:1:maguser:/maguser:/bin/sh')
6466
f.write('maguser2:x:2:2:maguser2:/maguser2:/bin/sh')
6567
f.write('maguser3:x:3:3:maguser3:/maguser3:/bin/sh')
68+
f.write('timeoutusr:x:4:4:timeoutusr:/timeoutusr:/bin/sh')
6669

6770
wenv = {'LD_PRELOAD': 'libsocket_wrapper.so libnss_wrapper.so',
6871
'SOCKET_WRAPPER_DIR': wrapdir,
@@ -350,6 +353,7 @@ def kadmin_local(cmd, env, logfile):
350353
USR_NAME_3 = "maguser3"
351354
SVC_KTNAME = "httpd/http.keytab"
352355
KEY_TYPE = "aes256-cts-hmac-sha1-96:normal"
356+
USR_NAME_4 = "timeoutusr"
353357

354358

355359
def setup_keys(tesdir, env):
@@ -370,6 +374,9 @@ def setup_keys(tesdir, env):
370374
cmd = "addprinc -pw %s -e %s %s" % (USR_PWD_2, KEY_TYPE, USR_NAME_2)
371375
kadmin_local(cmd, env, logfile)
372376

377+
cmd = "addprinc -pw %s -e %s %s" % (USR_PWD, KEY_TYPE, USR_NAME_4)
378+
kadmin_local(cmd, env, logfile)
379+
373380
# alias for multinamed hosts testing
374381
alias_name = "HTTP/%s" % WRAP_ALIASNAME
375382
cmd = "addprinc -randkey -e %s %s" % (KEY_TYPE, alias_name)
@@ -608,6 +615,30 @@ def test_basic_auth_krb5(testdir, testenv, logfile):
608615
return error_count
609616

610617

618+
def test_basic_auth_timeout(testdir, testenv, logfile):
619+
httpdir = os.path.join(testdir, 'httpd')
620+
timeoutdir = os.path.join(httpdir, 'html', 'basic_auth_timeout')
621+
os.mkdir(timeoutdir)
622+
authdir = os.path.join(timeoutdir, 'auth')
623+
os.mkdir(authdir)
624+
sessdir = os.path.join(timeoutdir, 'session')
625+
os.mkdir(sessdir)
626+
shutil.copy('tests/index.html', os.path.join(authdir))
627+
shutil.copy('tests/index.html', os.path.join(sessdir))
628+
629+
basictout = subprocess.Popen(["tests/t_basic_timeout.py"],
630+
stdout=logfile, stderr=logfile,
631+
env=testenv, preexec_fn=os.setsid)
632+
basictout.wait()
633+
if basictout.returncode != 0:
634+
sys.stderr.write('BASIC Timeout Behavior: FAILED\n')
635+
return 1
636+
else:
637+
sys.stderr.write('BASIC Timeout Behavior: SUCCESS\n')
638+
639+
return 0
640+
641+
611642
def test_bad_acceptor_name(testdir, testenv, logfile):
612643
bandir = os.path.join(testdir, 'httpd', 'html', 'bad_acceptor_name')
613644
os.mkdir(bandir)
@@ -703,6 +734,33 @@ def test_gss_localname(testdir, testenv, logfile):
703734
return error_count
704735

705736

737+
def faketime_setup(testenv):
738+
libfaketime = '/usr/lib64/faketime/libfaketime.so.1'
739+
# optional faketime
740+
if not os.path.isfile(libfaketime):
741+
raise NotImplementedError
742+
743+
# spedup x100
744+
fakeenv = {'FAKETIME': '+0 x100'}
745+
fakeenv.update(testenv)
746+
fakeenv['LD_PRELOAD'] = ' '.join((testenv['LD_PRELOAD'], libfaketime))
747+
return fakeenv
748+
749+
750+
def http_restart(testdir, so_dir, testenv):
751+
752+
httpenv = {'PATH': '/sbin:/bin:/usr/sbin:/usr/bin',
753+
'MALLOC_CHECK_': '3',
754+
'MALLOC_PERTURB_': str(random.randint(0, 32767) % 255 + 1)}
755+
httpenv.update(testenv)
756+
757+
httpd = "httpd" if os.path.exists("/etc/httpd/modules") else "apache2"
758+
config = os.path.join(testdir, 'httpd', 'httpd.conf')
759+
httpproc = subprocess.Popen([httpd, '-DFOREGROUND', '-f', config],
760+
env=httpenv, preexec_fn=os.setsid)
761+
return httpproc
762+
763+
706764
if __name__ == '__main__':
707765
args = parse_args()
708766

@@ -767,6 +825,25 @@ def test_gss_localname(testdir, testenv, logfile):
767825
errs += test_basic_auth_krb5(testdir, testenv, logfile)
768826

769827
errs += test_no_negotiate(testdir, testenv, logfile)
828+
829+
# After this point we need to speed up httpd to test creds timeout
830+
try:
831+
fakeenv = faketime_setup(kdcenv)
832+
timeenv = {'TIMEOUT_USER': USR_NAME_4,
833+
'MAG_USER_PASSWORD': USR_PWD}
834+
timeenv.update(fakeenv)
835+
curporc = httpproc
836+
pid = processes['HTTPD(%d)' % httpproc.pid].pid
837+
os.killpg(pid, signal.SIGTERM)
838+
time.sleep(1)
839+
del processes['HTTPD(%d)' % httpproc.pid]
840+
httpproc = http_restart(testdir, so_dir, timeenv)
841+
processes['HTTPD(%d)' % httpproc.pid] = httpproc
842+
843+
errs += test_basic_auth_timeout(testdir, timeenv, logfile)
844+
except NotImplementedError:
845+
sys.stderr.write('BASIC Timeout Behavior: SKIPPED\n')
846+
770847
except Exception:
771848
traceback.print_exc()
772849
finally:

tests/t_basic_timeout.py

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
#!/usr/bin/env python
2+
# Copyright (C) 2020 - mod_auth_gssapi contributors, see COPYING for license.
3+
4+
import os
5+
import time
6+
7+
import requests
8+
from requests.auth import HTTPBasicAuth
9+
10+
11+
if __name__ == '__main__':
12+
s = requests.Session()
13+
url = 'http://{}/basic_auth_timeout/auth/'.format(
14+
os.environ['NSS_WRAPPER_HOSTNAME']
15+
)
16+
url2 = 'http://{}/basic_auth_timeout/session/'.format(
17+
os.environ['NSS_WRAPPER_HOSTNAME']
18+
)
19+
20+
r = s.get(url, auth=HTTPBasicAuth(os.environ['TIMEOUT_USER'],
21+
os.environ['MAG_USER_PASSWORD']))
22+
if r.status_code != 200:
23+
raise ValueError('Basic Auth Failed')
24+
25+
time.sleep(301)
26+
r = s.get(url2)
27+
if r.status_code != 200:
28+
raise ValueError('Session Auth Failed')
29+
30+
time.sleep(401)
31+
32+
r = s.get(url2)
33+
if r.status_code == 200:
34+
raise ValueError('Timeout check Failed')

0 commit comments

Comments
 (0)