Blame SOURCES/Allow-admins-to-selectively-suppress-negotiation.patch

f9ac4e
From 96a8f82b0b9a84a7ba7ba84965978ab27674b802 Mon Sep 17 00:00:00 2001
f9ac4e
From: Simo Sorce <simo@redhat.com>
f9ac4e
Date: Mon, 24 Apr 2017 15:40:33 -0400
f9ac4e
Subject: [PATCH] Allow admins to selectively suppress negotiation
f9ac4e
f9ac4e
If the admin sets the gssapi-no-negotiate requets enironemnt variable,
f9ac4e
then we suppress the ability to send Negotiate headers.
f9ac4e
This is useful to slectively send negotiate only to specific whielisted
f9ac4e
or blacklisted browsers, clients, IP Addresses, etc... based on
f9ac4e
directives like BrowserMatch or SetEnvIf.
f9ac4e
f9ac4e
Signed-off-by: Simo Sorce <simo@redhat.com>
f9ac4e
Resolves #135
f9ac4e
(cherry picked from commit 114e4408523ca4d06da32c265680b9faa74ad882)
f9ac4e
---
f9ac4e
 src/mod_auth_gssapi.c | 13 ++++++++++---
f9ac4e
 tests/httpd.conf      | 13 +++++++++++++
f9ac4e
 tests/magtests.py     | 19 +++++++++++++++++++
f9ac4e
 tests/t_nonego.py     | 29 +++++++++++++++++++++++++++++
f9ac4e
 4 files changed, 71 insertions(+), 3 deletions(-)
f9ac4e
 create mode 100755 tests/t_nonego.py
f9ac4e
f9ac4e
diff --git a/src/mod_auth_gssapi.c b/src/mod_auth_gssapi.c
f9ac4e
index 755654d..59120d1 100644
f9ac4e
--- a/src/mod_auth_gssapi.c
f9ac4e
+++ b/src/mod_auth_gssapi.c
f9ac4e
@@ -833,7 +833,7 @@ static int mag_auth(request_rec *req)
f9ac4e
     gss_OID_set desired_mechs = GSS_C_NO_OID_SET;
f9ac4e
     struct mag_conn *mc = NULL;
f9ac4e
     int i;
f9ac4e
-    bool send_auth_header = true;
f9ac4e
+    bool send_nego_header = true;
f9ac4e
 
f9ac4e
     type = ap_auth_type(req);
f9ac4e
     if ((type == NULL) || (strcasecmp(type, "GSSAPI") != 0)) {
f9ac4e
@@ -907,6 +907,11 @@ static int mag_auth(request_rec *req)
f9ac4e
         }
f9ac4e
     }
f9ac4e
 
f9ac4e
+    /* check if admin wants to disable negotiate with this client */
f9ac4e
+    if (apr_table_get(req->subprocess_env, "gssapi-no-negotiate")) {
f9ac4e
+        send_nego_header = false;
f9ac4e
+    }
f9ac4e
+
f9ac4e
     if (cfg->ssl_only) {
f9ac4e
         if (!mag_conn_is_https(req->connection)) {
f9ac4e
             mag_post_error(req, cfg, MAG_AUTH_NOT_ALLOWED, 0, 0,
f9ac4e
@@ -965,7 +970,9 @@ static int mag_auth(request_rec *req)
f9ac4e
     }
f9ac4e
 
f9ac4e
     /* We got auth header, sending auth header would mean re-auth */
f9ac4e
-    send_auth_header = !cfg->negotiate_once;
f9ac4e
+    if (cfg->negotiate_once) {
f9ac4e
+            send_nego_header = false;
f9ac4e
+    }
f9ac4e
 
f9ac4e
     for (i = 0; auth_types[i] != NULL; i++) {
f9ac4e
         if (strcasecmp(auth_header_type, auth_types[i]) == 0) {
f9ac4e
@@ -1126,7 +1133,7 @@ done:
f9ac4e
             apr_table_add(req->err_headers_out, req_cfg->rep_proto, reply);
f9ac4e
         }
f9ac4e
     } else if (ret == HTTP_UNAUTHORIZED) {
f9ac4e
-        if (send_auth_header) {
f9ac4e
+        if (send_nego_header) {
f9ac4e
             apr_table_add(req->err_headers_out,
f9ac4e
                           req_cfg->rep_proto, "Negotiate");
f9ac4e
             if (is_mech_allowed(desired_mechs, gss_mech_ntlmssp,
f9ac4e
diff --git a/tests/httpd.conf b/tests/httpd.conf
f9ac4e
index 7879727..e17cf0a 100644
f9ac4e
--- a/tests/httpd.conf
f9ac4e
+++ b/tests/httpd.conf
f9ac4e
@@ -211,6 +211,19 @@ CoreDumpDirectory "${HTTPROOT}"
f9ac4e
   Require valid-user
f9ac4e
 </Location>
f9ac4e
 
f9ac4e
+<Location /nonego>
f9ac4e
+  BrowserMatch NONEGO gssapi-no-negotiate
f9ac4e
+  AuthType GSSAPI
f9ac4e
+  AuthName "Login"
f9ac4e
+  GssapiSSLonly Off
f9ac4e
+  GssapiCredStore ccache:${HTTPROOT}/tmp/httpd_krb5_ccache
f9ac4e
+  GssapiCredStore client_keytab:${HTTPROOT}/http.keytab
f9ac4e
+  GssapiCredStore keytab:${HTTPROOT}/http.keytab
f9ac4e
+  GssapiBasicAuth On
f9ac4e
+  GssapiAllowedMech krb5
f9ac4e
+  Require valid-user
f9ac4e
+</Location>
f9ac4e
+
f9ac4e
 <VirtualHost *:${PROXYPORT}>
f9ac4e
   ProxyRequests On
f9ac4e
   ProxyVia On
f9ac4e
diff --git a/tests/magtests.py b/tests/magtests.py
f9ac4e
index a008d81..4d276df 100755
f9ac4e
--- a/tests/magtests.py
f9ac4e
+++ b/tests/magtests.py
f9ac4e
@@ -410,6 +410,23 @@ def test_bad_acceptor_name(testdir, testenv, testlog):
f9ac4e
             sys.stderr.write('BAD ACCEPTOR: FAILED\n')
f9ac4e
 
f9ac4e
 
f9ac4e
+def test_no_negotiate(testdir, testenv, testlog):
f9ac4e
+
f9ac4e
+    nonego_dir = os.path.join(testdir, 'httpd', 'html', 'nonego')
f9ac4e
+    os.mkdir(nonego_dir)
f9ac4e
+    shutil.copy('tests/index.html', nonego_dir)
f9ac4e
+
f9ac4e
+    with (open(testlog, 'a')) as logfile:
f9ac4e
+        spnego = subprocess.Popen(["tests/t_nonego.py"],
f9ac4e
+                                  stdout=logfile, stderr=logfile,
f9ac4e
+                                  env=testenv, preexec_fn=os.setsid)
f9ac4e
+        spnego.wait()
f9ac4e
+        if spnego.returncode != 0:
f9ac4e
+            sys.stderr.write('NO Negotiate: FAILED\n')
f9ac4e
+        else:
f9ac4e
+            sys.stderr.write('NO Negotiate: SUCCESS\n')
f9ac4e
+
f9ac4e
+
f9ac4e
 if __name__ == '__main__':
f9ac4e
 
f9ac4e
     args = parse_args()
f9ac4e
@@ -454,6 +471,8 @@ if __name__ == '__main__':
f9ac4e
         testenv.update(kdcenv)
f9ac4e
         test_basic_auth_krb5(testdir, testenv, testlog)
f9ac4e
 
f9ac4e
+        test_no_negotiate(testdir, testenv, testlog)
f9ac4e
+
f9ac4e
     finally:
f9ac4e
         with (open(testlog, 'a')) as logfile:
f9ac4e
             for name in processes:
f9ac4e
diff --git a/tests/t_nonego.py b/tests/t_nonego.py
f9ac4e
new file mode 100755
f9ac4e
index 0000000..c4f2bdd
f9ac4e
--- /dev/null
f9ac4e
+++ b/tests/t_nonego.py
f9ac4e
@@ -0,0 +1,29 @@
f9ac4e
+#!/usr/bin/python
f9ac4e
+# Copyright (C) 2015 - mod_auth_gssapi contributors, see COPYING for license.
f9ac4e
+
f9ac4e
+import os
f9ac4e
+import requests
f9ac4e
+
f9ac4e
+
f9ac4e
+if __name__ == '__main__':
f9ac4e
+    url = 'http://%s/nonego/' % (os.environ['NSS_WRAPPER_HOSTNAME'])
f9ac4e
+
f9ac4e
+    # ensure a 401 with the appropriate WWW-Authenticate header is returned
f9ac4e
+    # when no auth is provided
f9ac4e
+    r = requests.get(url)
f9ac4e
+    if r.status_code != 401:
f9ac4e
+        raise ValueError('NO Negotiate failed - 401 expected')
f9ac4e
+    if not (r.headers.get("WWW-Authenticate") and
f9ac4e
+            r.headers.get("WWW-Authenticate").startswith("Negotiate")):
f9ac4e
+        raise ValueError('NO Negotiate failed - WWW-Authenticate '
f9ac4e
+                         'Negotiate header is absent')
f9ac4e
+
f9ac4e
+    # ensure a 401 with the WWW-Authenticate Negotiate header is absent
f9ac4e
+    # when the special User-Agent is sent
f9ac4e
+    r = requests.get(url, headers={'User-Agent': 'NONEGO'})
f9ac4e
+    if r.status_code != 401:
f9ac4e
+        raise ValueError('NO Negotiate failed - 401 expected')
f9ac4e
+    if (r.headers.get("WWW-Authenticate") and
f9ac4e
+        r.headers.get("WWW-Authenticate").startswith("Negotiate")):
f9ac4e
+        raise ValueError('NO Negotiate failed - WWW-Authenticate '
f9ac4e
+                         'Negotiate header is present, should be absent')