Blame SOURCES/Add-option-to-control-timeout-for-Basic-Auth.patch

8f2224
From b4ddd657ccc7793df9378209433f0142195a94d1 Mon Sep 17 00:00:00 2001
8f2224
From: Simo Sorce <simo@redhat.com>
8f2224
Date: Thu, 14 May 2020 09:19:37 -0400
8f2224
Subject: [PATCH] Add option to control timeout for Basic Auth
8f2224
8f2224
Adds new option and tests.
8f2224
Adds optional dependency on libfaketime to test this feature.
8f2224
8f2224
Fixes: #210
8f2224
Signed-off-by: Simo Sorce <simo@redhat.com>
8f2224
Merges: #217
8f2224
Reviewed-by: Robbie Harwood <rharwood@redhat.com>
8f2224
(cherry picked from commit 09df7584b4abadbfea411adafdcc825da5b720d3)
8f2224
[rharwood@redhat.com: git got confused by not having localname test]
8f2224
---
8f2224
 README                   | 24 +++++++++++++
8f2224
 src/mod_auth_gssapi.c    | 27 +++++++++++---
8f2224
 src/mod_auth_gssapi.h    |  1 +
8f2224
 tests/Makefile.am        |  1 +
8f2224
 tests/httpd.conf         | 32 ++++++++++++++++-
8f2224
 tests/magtests.py        | 76 ++++++++++++++++++++++++++++++++++++++++
8f2224
 tests/t_basic_timeout.py | 34 ++++++++++++++++++
8f2224
 7 files changed, 190 insertions(+), 5 deletions(-)
8f2224
 create mode 100755 tests/t_basic_timeout.py
8f2224
8f2224
diff --git a/README b/README
8f2224
index 700b57e..5eac94f 100644
8f2224
--- a/README
8f2224
+++ b/README
8f2224
@@ -97,6 +97,7 @@ Configuration Directives
8f2224
 [GssapiAllowedMech](#gssapiallowedmech)
8f2224
 [GssapiBasicAuth](#gssapibasicauth)
8f2224
 [GssapiBasicAuthMech](#gssapibasicauthmech)
8f2224
+[GssapiBasicTicketTimeout](#gssapibasicticketvalidity)
8f2224
 [GssapiConnectionBound](#gssapiconnectionbound)
8f2224
 [GssapiCredStore](#gssapicredstore)
8f2224
 [GssapiDelegCcacheDir](#gssapidelegccachedir)
8f2224
@@ -503,3 +504,26 @@ Note: The GSS_C_NT_HOSTBASED_SERVICE format is used for names (see example).
8f2224
     GssapiAcceptorName HTTP@www.example.com
8f2224
 
8f2224
 
8f2224
+### GssapiBasicTicketTimeout
8f2224
+
8f2224
+This option controls the ticket validity time requested for the user TGT by the
8f2224
+Basic Auth method.
8f2224
+
8f2224
+Normally basic auth is repeated by the browser on each request so a short
8f2224
+validity period is used to reduce the scope of the ticket as it will be
8f2224
+replaced quickly.
8f2224
+However in cases where the authentication page is separate and the session
8f2224
+is used by other pages the validity can be changed to arbitrary duration.
8f2224
+
8f2224
+Note: the validity of a ticket is still capped by KDC configuration.
8f2224
+
8f2224
+Note: the value is specified in seconds.
8f2224
+
8f2224
+- **Default:** GssapiBasicTicketTimeout 300
8f2224
+
8f2224
+#### Example
8f2224
+    GssapiBasicTicketTimeout 36000
8f2224
+
8f2224
+Sets ticket/session validity to 10 hours.
8f2224
+
8f2224
+
8f2224
diff --git a/src/mod_auth_gssapi.c b/src/mod_auth_gssapi.c
8f2224
index 9e42ef4..b099973 100644
8f2224
--- a/src/mod_auth_gssapi.c
8f2224
+++ b/src/mod_auth_gssapi.c
8f2224
@@ -1,4 +1,5 @@
8f2224
-/* Copyright (C) 2014, 2016 mod_auth_gssapi contributors - See COPYING for (C) terms */
8f2224
+/* Copyright (C) 2014, 2016, 2020 mod_auth_gssapi contributors
8f2224
+ * See COPYING for (C) terms */
8f2224
 
8f2224
 #include "mod_auth_gssapi.h"
8f2224
 #include "mag_parse.h"
8f2224
@@ -600,7 +601,7 @@ static int mag_auth_basic(struct mag_req_cfg *req_cfg, struct mag_conn *mc,
8f2224
     }
8f2224
 
8f2224
     maj = gss_acquire_cred_with_password(&min, user, &ba_pwd,
8f2224
-                                         GSS_C_INDEFINITE,
8f2224
+                                         cfg->basic_timeout,
8f2224
                                          allowed_mechs,
8f2224
                                          GSS_C_INITIATE,
8f2224
                                          &user_cred, &actual_mechs, NULL);
8f2224
@@ -619,8 +620,8 @@ static int mag_auth_basic(struct mag_req_cfg *req_cfg, struct mag_conn *mc,
8f2224
 
8f2224
     for (int i = 0; i < actual_mechs->count; i++) {
8f2224
         maj = mag_context_loop(&min, req, cfg, user_cred, server_cred,
8f2224
-                               &actual_mechs->elements[i], 300, &client,
8f2224
-                               &vtime, &delegated_cred);
8f2224
+                               &actual_mechs->elements[i], cfg->basic_timeout,
8f2224
+                               &client, &vtime, &delegated_cred);
8f2224
         if (maj == GSS_S_COMPLETE) {
8f2224
             ret = mag_complete(req_cfg, mc, client, &actual_mechs->elements[i],
8f2224
                                vtime, delegated_cred);
8f2224
@@ -1299,6 +1300,7 @@ static void *mag_create_dir_config(apr_pool_t *p, char *dir)
8f2224
 #ifdef HAVE_CRED_STORE
8f2224
     cfg->ccname_envvar = "KRB5CCNAME";
8f2224
 #endif
8f2224
+    cfg->basic_timeout = 300;
8f2224
 
8f2224
     return cfg;
8f2224
 }
8f2224
@@ -1789,6 +1791,21 @@ static const char *mag_acceptor_name(cmd_parms *parms, void *mconfig,
8f2224
     return NULL;
8f2224
 }
8f2224
 
8f2224
+static const char *mag_basic_timeout(cmd_parms *parms, void *mconfig,
8f2224
+                                     const char *w)
8f2224
+{
8f2224
+    struct mag_config *cfg = (struct mag_config *)mconfig;
8f2224
+    unsigned long int value;
8f2224
+
8f2224
+    value = strtoul(w, NULL, 10);
8f2224
+    if (value >= UINT32_MAX) {
8f2224
+        cfg->basic_timeout = GSS_C_INDEFINITE;
8f2224
+        return NULL;
8f2224
+    }
8f2224
+    cfg->basic_timeout = value;
8f2224
+    return NULL;
8f2224
+}
8f2224
+
8f2224
 static void *mag_create_server_config(apr_pool_t *p, server_rec *s)
8f2224
 {
8f2224
     struct mag_server_config *scfg;
8f2224
@@ -1865,6 +1882,8 @@ static const command_rec mag_commands[] = {
8f2224
                  "Publish GSSAPI Errors in Envionment Variables"),
8f2224
     AP_INIT_RAW_ARGS("GssapiAcceptorName", mag_acceptor_name, NULL, OR_AUTHCFG,
8f2224
                      "Name of the acceptor credentials."),
8f2224
+    AP_INIT_TAKE1("GssapiBasicTicketTimeout", mag_basic_timeout, NULL,
8f2224
+                  OR_AUTHCFG, "Ticket Validity Timeout with Basic Auth."),
8f2224
     { NULL }
8f2224
 };
8f2224
 
8f2224
diff --git a/src/mod_auth_gssapi.h b/src/mod_auth_gssapi.h
8f2224
index 8c0b972..2312ab5 100644
8f2224
--- a/src/mod_auth_gssapi.h
8f2224
+++ b/src/mod_auth_gssapi.h
8f2224
@@ -93,6 +93,7 @@ struct mag_config {
8f2224
     int enverrs;
8f2224
     gss_name_t acceptor_name;
8f2224
     bool acceptor_name_from_req;
8f2224
+    uint32_t basic_timeout;
8f2224
 };
8f2224
 
8f2224
 struct mag_server_config {
8f2224
diff --git a/tests/Makefile.am b/tests/Makefile.am
8f2224
index 16d87e9..c830e95 100644
8f2224
--- a/tests/Makefile.am
8f2224
+++ b/tests/Makefile.am
8f2224
@@ -11,6 +11,7 @@ EXTRA_DIST = \
8f2224
 	t_basic_k5.py \
8f2224
 	t_basic_k5_two_users.py \
8f2224
 	t_basic_proxy.py \
8f2224
+	t_basic_timeout.py \
8f2224
 	t_localname.py \
8f2224
 	t_hostname_acceptor.py \
8f2224
 	t_nonego.py \
8f2224
diff --git a/tests/httpd.conf b/tests/httpd.conf
8f2224
index 8c91e1c..f76f2b6 100644
8f2224
--- a/tests/httpd.conf
8f2224
+++ b/tests/httpd.conf
8f2224
@@ -111,7 +111,7 @@ DocumentRoot "{HTTPROOT}/html"
8f2224
 PidFile "{HTTPROOT}/logs/httpd.pid"
8f2224
 
8f2224
 <IfModule log_config_module>
8f2224
-LogFormat "%h %l %u %t \"%r\" %>s %b \"%{{Referer}}i\" \"%{{User-Agent}}i\"" combined
8f2224
+LogFormat "%h %l %u %t \"%r\" %>s %b \"%{{Referer}}i\" \"%{{User-Agent}}i\" \"%{{Cookie}}i\"" combined
8f2224
 CustomLog "logs/access_log" combined
8f2224
 </IfModule>
8f2224
 
8f2224
@@ -288,3 +288,33 @@ CoreDumpDirectory "{HTTPROOT}"
8f2224
     Require valid-user
8f2224
   </Proxy>
8f2224
 </VirtualHost>
8f2224
+
8f2224
+<Location /basic_auth_timeout/auth>
8f2224
+  Options +Includes
8f2224
+  AddOutputFilter INCLUDES .html
8f2224
+  AuthType GSSAPI
8f2224
+  AuthName "Password Login"
8f2224
+  GssapiSSLonly Off
8f2224
+  GssapiUseSessions On
8f2224
+  Session On
8f2224
+  SessionCookieName gssapi_session path=/basic_auth_timeout;httponly
8f2224
+  GssapiSessionKey file:{HTTPROOT}/session.key
8f2224
+  GssapiCredStore keytab:{HTTPROOT}/http.keytab
8f2224
+  GssapiBasicAuth On
8f2224
+  GssapiBasicAuthMech krb5
8f2224
+  GssapiBasicTicketTimeout 400
8f2224
+  GssapiDelegCcacheDir {HTTPROOT}
8f2224
+  Require valid-user
8f2224
+</Location>
8f2224
+<Location /basic_auth_timeout/session>
8f2224
+  Options +Includes
8f2224
+  AddOutputFilter INCLUDES .html
8f2224
+  AuthType GSSAPI
8f2224
+  AuthName "Session Login"
8f2224
+  GssapiSSLonly Off
8f2224
+  GssapiUseSessions On
8f2224
+  Session On
8f2224
+  SessionCookieName gssapi_session path=/basic_auth_timeout;httponly
8f2224
+  GssapiSessionKey file:{HTTPROOT}/session.key
8f2224
+  Require valid-user
8f2224
+</Location>
8f2224
diff --git a/tests/magtests.py b/tests/magtests.py
8f2224
index a4842a0..da1cca7 100755
8f2224
--- a/tests/magtests.py
8f2224
+++ b/tests/magtests.py
8f2224
@@ -3,11 +3,13 @@
8f2224
 
8f2224
 import argparse
8f2224
 import os
8f2224
+import os.path
8f2224
 import random
8f2224
 import shutil
8f2224
 import signal
8f2224
 import subprocess
8f2224
 import sys
8f2224
+import time
8f2224
 import traceback
8f2224
 
8f2224
 # check that we can import requests (for use in test scripts)
8f2224
@@ -341,6 +343,7 @@ USR_PWD_2 = "magpwd2"
8f2224
 USR_NAME_3 = "maguser3"
8f2224
 SVC_KTNAME = "httpd/http.keytab"
8f2224
 KEY_TYPE = "aes256-cts-hmac-sha1-96:normal"
8f2224
+USR_NAME_4 = "timeoutusr"
8f2224
 
8f2224
 
8f2224
 def setup_keys(tesdir, env):
8f2224
@@ -361,6 +364,9 @@ def setup_keys(tesdir, env):
8f2224
     cmd = "addprinc -pw %s -e %s %s" % (USR_PWD_2, KEY_TYPE, USR_NAME_2)
8f2224
     kadmin_local(cmd, env, logfile)
8f2224
 
8f2224
+    cmd = "addprinc -pw %s -e %s %s" % (USR_PWD, KEY_TYPE, USR_NAME_4)
8f2224
+    kadmin_local(cmd, env, logfile)
8f2224
+
8f2224
     # alias for multinamed hosts testing
8f2224
     alias_name = "HTTP/%s" % WRAP_ALIASNAME
8f2224
     cmd = "addprinc -randkey -e %s %s" % (KEY_TYPE, alias_name)
8f2224
@@ -600,6 +606,30 @@ def test_basic_auth_krb5(testdir, testenv, logfile):
8f2224
     return error_count
8f2224
 
8f2224
 
8f2224
+def test_basic_auth_timeout(testdir, testenv, logfile):
8f2224
+    httpdir = os.path.join(testdir, 'httpd')
8f2224
+    timeoutdir = os.path.join(httpdir, 'html', 'basic_auth_timeout')
8f2224
+    os.mkdir(timeoutdir)
8f2224
+    authdir = os.path.join(timeoutdir, 'auth')
8f2224
+    os.mkdir(authdir)
8f2224
+    sessdir = os.path.join(timeoutdir, 'session')
8f2224
+    os.mkdir(sessdir)
8f2224
+    shutil.copy('tests/index.html', os.path.join(authdir))
8f2224
+    shutil.copy('tests/index.html', os.path.join(sessdir))
8f2224
+
8f2224
+    basictout = subprocess.Popen(["tests/t_basic_timeout.py"],
8f2224
+                                 stdout=logfile, stderr=logfile,
8f2224
+                                 env=testenv, preexec_fn=os.setsid)
8f2224
+    basictout.wait()
8f2224
+    if basictout.returncode != 0:
8f2224
+        sys.stderr.write('BASIC Timeout Behavior: FAILED\n')
8f2224
+        return 1
8f2224
+    else:
8f2224
+        sys.stderr.write('BASIC Timeout Behavior: SUCCESS\n')
8f2224
+
8f2224
+    return 0
8f2224
+
8f2224
+
8f2224
 def test_bad_acceptor_name(testdir, testenv, logfile):
8f2224
     bandir = os.path.join(testdir, 'httpd', 'html', 'bad_acceptor_name')
8f2224
     os.mkdir(bandir)
8f2224
@@ -661,6 +691,33 @@ def test_hostname_acceptor(testdir, testenv, logfile):
8f2224
     return 0
8f2224
 
8f2224
 
8f2224
+def faketime_setup(testenv):
8f2224
+    libfaketime = '/usr/lib64/faketime/libfaketime.so.1'
8f2224
+    # optional faketime
8f2224
+    if not os.path.isfile(libfaketime):
8f2224
+        raise NotImplementedError
8f2224
+
8f2224
+    # spedup x100
8f2224
+    fakeenv = {'FAKETIME': '+0 x100'}
8f2224
+    fakeenv.update(testenv)
8f2224
+    fakeenv['LD_PRELOAD'] = ' '.join((testenv['LD_PRELOAD'], libfaketime))
8f2224
+    return fakeenv
8f2224
+
8f2224
+
8f2224
+def http_restart(testdir, so_dir, testenv):
8f2224
+
8f2224
+    httpenv = {'PATH': '/sbin:/bin:/usr/sbin:/usr/bin',
8f2224
+               'MALLOC_CHECK_': '3',
8f2224
+               'MALLOC_PERTURB_': str(random.randint(0, 32767) % 255 + 1)}
8f2224
+    httpenv.update(testenv)
8f2224
+
8f2224
+    httpd = "httpd" if os.path.exists("/etc/httpd/modules") else "apache2"
8f2224
+    config = os.path.join(testdir, 'httpd', 'httpd.conf')
8f2224
+    httpproc = subprocess.Popen([httpd, '-DFOREGROUND', '-f', config],
8f2224
+                                env=httpenv, preexec_fn=os.setsid)
8f2224
+    return httpproc
8f2224
+
8f2224
+
8f2224
 if __name__ == '__main__':
8f2224
     args = parse_args()
8f2224
 
8f2224
@@ -722,6 +779,25 @@ if __name__ == '__main__':
8f2224
         errs += test_basic_auth_krb5(testdir, testenv, logfile)
8f2224
 
8f2224
         errs += test_no_negotiate(testdir, testenv, logfile)
8f2224
+
8f2224
+        # After this point we need to speed up httpd to test creds timeout
8f2224
+        try:
8f2224
+            fakeenv = faketime_setup(kdcenv)
8f2224
+            timeenv = {'TIMEOUT_USER': USR_NAME_4,
8f2224
+                       'MAG_USER_PASSWORD': USR_PWD}
8f2224
+            timeenv.update(fakeenv)
8f2224
+            curporc = httpproc
8f2224
+            pid = processes['HTTPD(%d)' % httpproc.pid].pid
8f2224
+            os.killpg(pid, signal.SIGTERM)
8f2224
+            time.sleep(1)
8f2224
+            del processes['HTTPD(%d)' % httpproc.pid]
8f2224
+            httpproc = http_restart(testdir, so_dir, timeenv)
8f2224
+            processes['HTTPD(%d)' % httpproc.pid] = httpproc
8f2224
+
8f2224
+            errs += test_basic_auth_timeout(testdir, timeenv, logfile)
8f2224
+        except NotImplementedError:
8f2224
+            sys.stderr.write('BASIC Timeout Behavior: SKIPPED\n')
8f2224
+
8f2224
     except Exception:
8f2224
         traceback.print_exc()
8f2224
     finally:
8f2224
diff --git a/tests/t_basic_timeout.py b/tests/t_basic_timeout.py
8f2224
new file mode 100755
8f2224
index 0000000..983dfd2
8f2224
--- /dev/null
8f2224
+++ b/tests/t_basic_timeout.py
8f2224
@@ -0,0 +1,34 @@
8f2224
+#!/usr/bin/env python
8f2224
+# Copyright (C) 2020 - mod_auth_gssapi contributors, see COPYING for license.
8f2224
+
8f2224
+import os
8f2224
+import time
8f2224
+
8f2224
+import requests
8f2224
+from requests.auth import HTTPBasicAuth
8f2224
+
8f2224
+
8f2224
+if __name__ == '__main__':
8f2224
+    s = requests.Session()
8f2224
+    url = 'http://{}/basic_auth_timeout/auth/'.format(
8f2224
+            os.environ['NSS_WRAPPER_HOSTNAME']
8f2224
+    )
8f2224
+    url2 = 'http://{}/basic_auth_timeout/session/'.format(
8f2224
+            os.environ['NSS_WRAPPER_HOSTNAME']
8f2224
+    )
8f2224
+
8f2224
+    r = s.get(url, auth=HTTPBasicAuth(os.environ['TIMEOUT_USER'],
8f2224
+                                      os.environ['MAG_USER_PASSWORD']))
8f2224
+    if r.status_code != 200:
8f2224
+        raise ValueError('Basic Auth Failed')
8f2224
+
8f2224
+    time.sleep(301)
8f2224
+    r = s.get(url2)
8f2224
+    if r.status_code != 200:
8f2224
+        raise ValueError('Session Auth Failed')
8f2224
+
8f2224
+    time.sleep(401)
8f2224
+
8f2224
+    r = s.get(url2)
8f2224
+    if r.status_code == 200:
8f2224
+        raise ValueError('Timeout check Failed')