pax_global_header00006660000000000000000000000064150101236430014505gustar00rootroot0000000000000052 comment=005723a3f5cadcf2da6ebbde8caad758555da11b spnego-http-auth-nginx-module-1.1.3/000077500000000000000000000000001501012364300173225ustar00rootroot00000000000000spnego-http-auth-nginx-module-1.1.3/.github/000077500000000000000000000000001501012364300206625ustar00rootroot00000000000000spnego-http-auth-nginx-module-1.1.3/.github/workflows/000077500000000000000000000000001501012364300227175ustar00rootroot00000000000000spnego-http-auth-nginx-module-1.1.3/.github/workflows/build-test.yml000066400000000000000000000056311501012364300255230ustar00rootroot00000000000000name: Build and test on: [push] jobs: build-test: # nginx-dev doesn't exist on releases before 24.04 strategy: matrix: os: - ubuntu-24.04 runs-on: ${{ matrix.os }} steps: - name: Update APT package index run: | sudo apt-get update -qq - name: Install packages run: | sudo apt-get install \ nginx nginx-dev build-essential libkrb5-dev curl \ slapd ldap-utils \ krb5-admin-server krb5-kdc krb5-kdc-ldap \ libsasl2-modules-gssapi-mit \ php-fpm php-ldap - name: Check out repository code uses: actions/checkout@v4 - name: Create build directory run: | mkdir "${{ github.workspace }}/build" - name: Run configure script run: | cd /usr/share/nginx/src . ./conf_flags ./configure \ --with-cc-opt="-fPIC" \ --with-ld-opt="-Wl,-z,relro" \ "${NGX_CONF_FLAGS[@]}" \ --add-dynamic-module="${{ github.workspace }}" \ --builddir="${{ github.workspace }}/build" - name: Build module run: | cd "${{ github.workspace }}/build" make \ -f "${{ github.workspace }}/build/Makefile" \ -C "/usr/share/nginx/src" \ modules - name: List files in the repository and build dir run: | echo "=== Workspace: ${{ github.workspace }} ===" ls -al "${{ github.workspace }}" echo "=== Build dir: ${{ github.workspace }}/build ===" ls -al "${{ github.workspace }}/build" - name: Install module run: | sudo mkdir -p /usr/lib/nginx/modules/ sudo cp "${{ github.workspace }}/build/ngx_http_auth_spnego_module.so" /usr/lib/nginx/modules/ sudo mkdir -p /usr/share/nginx/modules-available/ echo "load_module modules/ngx_http_auth_spnego_module.so;" >> "${{ github.workspace }}/build/mod-http-auth-spnego.conf" sudo cp "${{ github.workspace }}/build/mod-http-auth-spnego.conf" /usr/share/nginx/modules-available/ sudo mkdir -p /etc/nginx/modules-enabled/ sudo ln -sf /usr/share/nginx/modules-available/mod-http-auth-spnego.conf /etc/nginx/modules-enabled/50-mod-http-auth-spnego.conf - name: Run test script run: | sudo bash "${{ github.workspace }}/scripts/kerberos_ldap" spnego-http-auth-nginx-module-1.1.3/.gitignore000066400000000000000000000000141501012364300213050ustar00rootroot00000000000000*.[oa] *.so spnego-http-auth-nginx-module-1.1.3/ChangeLog000066400000000000000000000461661501012364300211110ustar00rootroot00000000000000Author: Sean Timothy Noonan Date: Mon Mar 16 15:23:37 2015 -0400 Link to libgssapi in FreeBSD This fixes the inability of this module to compile on FreeBSD 10.1 commit e59a22d3cf78fe756d7b381ad0b88dfd7fa9aad6 Author: Sean Timothy Noonan Date: Tue Oct 21 21:13:12 2014 -0400 Add additional log and debug statements for bogus auth commit fa728d2de80001d41f539783b21977a4aa493c65 Author: Sean Timothy Noonan Date: Wed Oct 1 20:54:21 2014 -0400 Describe bogus auth header in readme Closes #25 commit 7292bce3d890f67288464939e0ce4c775e2337fe Merge: 02df21c b916c72 Author: Sean Timothy Noonan Date: Thu Aug 21 22:01:02 2014 -0400 Merge pull request #24 from neirbowj/illuminated_bogus Hint at the origin of the bogus password commit b916c7235fa1e3db669e57c89db1baae79151574 Author: John W. O'Brien Date: Thu Aug 21 21:29:39 2014 -0400 Hint at the origin of the bogus password commit 02df21c146858ac1a424ee525341027c18e022a6 Merge: d9af45f d536d2e Author: Sean Timothy Noonan Date: Tue Aug 19 21:49:10 2014 -0400 Merge branch 'gsskrb5_register_acceptor_identity' Closes #22 commit d536d2e80ea47fe9425a7bddb67f61b63560ccde Author: Sean Timothy Noonan Date: Mon Aug 18 20:54:18 2014 -0400 Correct all arguments and logic for keytabs Must have been three-quarters asleep when I wrote that code. kt_env for the env variable, kt for the gsskrb5 call. commit 1ecea2aa9f2c239db94af21ecabe033125e53baa Author: Sean Timothy Noonan Date: Tue Aug 12 22:01:31 2014 -0400 Test using gsskrb5_register_acceptor_identity commit d9af45feb24fd63c6436c3323247ed9065f84cbc Author: Sean Timothy Noonan Date: Fri Aug 8 10:12:05 2014 -0400 Check return value of putenv commit 54398b3718241107fb57d175619e62913a6e4590 Author: Sean Timothy Noonan Date: Sat Jul 19 08:54:27 2014 -0400 Less specific versions in readme, add help section commit 2723627ed875a722a805149e4458f1f3ddbba05e Author: Sean Timothy Noonan Date: Sat Jul 19 08:12:47 2014 -0400 update ChangeLog commit e1bd59d1308f2ecccd883758ac61bcd0ba73c854 Author: Sean Timothy Noonan Date: Sat Jul 19 07:57:17 2014 -0400 Use strlchr and strncmp where needed commit de1924735de6c3fec862929efa9684d44c088f6e Author: Sean Timothy Noonan Date: Sat Jul 19 07:33:46 2014 -0400 Convert remaining sprintf to snprintf commit 9519ab75de5b63f82fc018c91884b147bd70a670 Merge: c1ce1d6 52d9c89 Author: Sean Timothy Noonan Date: Fri Jul 11 09:07:15 2014 -0400 Merge pull request #21 from aroth-arsoft/master fix for issue remote_user and realm #19 commit 52d9c89447a27da64743e2e7e40f4ea783565163 Author: Andreas Roth Date: Fri Jul 11 14:01:06 2014 +0200 use ngx_strncmp instead of ngx_strcmp to ensure to compare only the valid part of the realm commit c1ce1d642ef38b59896182ef4a34418283132644 Merge: 9430baa 70f4095 Author: Sean Timothy Noonan Date: Tue Jun 24 21:41:06 2014 -0400 Merge branch 'master' of github.com:stnoonan/spnego-http-auth-nginx-module commit 9430baa9ec1bf2d537ddbb93c166e98d3db89057 Author: Sean Timothy Noonan Date: Tue Jun 24 21:40:55 2014 -0400 Update ChangeLog commit 8cc8aa56e402da9057a66374040c0b8604069dec Author: Sean Timothy Noonan Date: Tue Jun 24 21:40:40 2014 -0400 Add missing semicolon in spnego_log_krb5_error This resolves compilation errors when --with-debug is passed to the configure script. Closes #18 commit 70f4095ce0dc9e9469e244bd1d238231c7e27b40 Author: Sean Timothy Noonan Date: Wed Jun 18 20:57:32 2014 -0400 Clarify the behavior of the module when realm is specified The default realm is stripped, auth_gss_format_full can be used to override this behavior. commit 34b74d3185b38720754cf677b2a8eb82682587e3 Author: Sean Timothy Noonan Date: Wed Jun 18 19:44:07 2014 -0400 Replace deprecated krb5_get_init_creds_opt_init() The preferred method is to use krb5_get_init_creds_opt_{alloc,free}, as it removes a static structure that is unable to be extended. With credit to MAXuk commit cf04148da10b1e4ce295f9617dcf822045bf9dbf Author: Sean Timothy Noonan Date: Wed Jun 18 19:35:44 2014 -0400 Use krb5_get_error_message Contributed by planbnet. This should provide extended error messages from the underlying krb5 library when there are failures. commit 4a43f2f35749b38c36ffff7474284f91c1a59b8e Author: Sean Timothy Noonan Date: Wed Jun 18 19:06:20 2014 -0400 Add dependency information to README.md Closes #16. commit 50b24e59c0925b42a0b3e60401ee3741b1d46717 Author: Sean Timothy Noonan Date: Tue Apr 22 09:10:26 2014 -0400 allow auth_gss directives in limit_except blocks commit 46e400cb87f3576a27731cce11c2f6f421a7cefd Author: Sean Timothy Noonan Date: Wed Oct 16 21:55:08 2013 -0400 Update ChangeLog and README commit d335e081f6823276c18c1fce1cc195e57a3c83af Author: Sean Timothy Noonan Date: Wed Oct 16 21:43:55 2013 -0400 Return 403 when user is unauthorized These changes disallow the continuous retries and provide for significantly better behavior when used with Chrome Fixes stnoonan/spnego-http-auth-nginx-module#9 commit 52bd58009d68d9e94664ac8c77139431267e9cf6 Merge: a18b188 60ab7e3 Author: Sean Timothy Noonan Date: Wed Oct 16 06:15:36 2013 -0700 Merge pull request #10 from MAXuk/patch-1 Include com_err.h to define error_message Remove make error: "warning: implicit declaration of function 'error_message'" commit 60ab7e330f06c2dc329f1e82e99d0fb9a94cfac5 Author: MAXuk Date: Wed Oct 16 16:58:26 2013 +0400 Remove "warning: implicit declaration of function 'error_message'" " cc -c -pipe -O -W -Wall -Wpointer-arith -Wno-unused-parameter -Werror -g -I /usr/local/include -I src/core -I src/event -I src/event/modules -I src/os/unix -I zlib-1.2.8 -I objs -I src/http -I src/http/modules -I src/mail -o objs/addon/spnego-http-auth-nginx-module-master/ngx_http_auth_spnego_module.o spnego-http-auth-nginx-module-master/ngx_http_auth_spnego_module.c cc1: warnings being treated as errors spnego-http-auth-nginx-module-master/ngx_http_auth_spnego_module.c: In function 'ngx_http_auth_spnego_basic': spnego-http-auth-nginx-module-master/ngx_http_auth_spnego_module.c:496: warning: implicit declaration of function 'error_message' *** [objs/addon/spnego-http-auth-nginx-module-master/ngx_http_auth_spnego_module.o] Error code 1 " Remove this error where make nginx --add-module=spnego-http-auth-nginx-module-master commit a18b18801f0ea75723dd14ad912596788f8cef0d Author: Sean Timothy Noonan Date: Tue Oct 8 16:11:35 2013 -0400 ChangeLog update commit 56109515cbef41fe603aaba08f3e0895e3b158f7 Author: Sean Timothy Noonan Date: Tue Oct 8 16:08:16 2013 -0400 Sane header behavior for multiple browsers Sending a token in a 401 causes Chrome to choke Sending multiple NEGOTIATE headers causes Firefox to loop indefinitely trying to authenticate. Added a new ctx entry to track the output token. This allows the header to be set only once. Future improvement would be to stop passing both the token and the ctx. commit 6d90271edb3179055c604ce4d588754513627b78 Author: Sean Timothy Noonan Date: Tue Oct 8 12:04:50 2013 -0400 Update ChangeLog/README to reflect recent changes commit 13507c52e5d6fdcdf8ff219230a45338a5847173 Author: Sean Timothy Noonan Date: Tue Oct 8 12:02:39 2013 -0400 Do not send token in reponse unless authenticated This causes a parsing error in Google Chrome, causing it to display a 320 instead of a 401. commit c6c51e7fd02d946a6863106fc27b054252e746a7 Author: Sean Noonan Date: Mon Aug 12 17:52:53 2013 +0000 pass data, not data address for auth_princs commit 827a36357998625c9a19ece717bc4c5360984695 Author: Sean Timothy Noonan Date: Sat Aug 10 15:28:41 2013 -0400 add size check of authorized principal The ngx_str*cmp family of functions are thin wrappers around their str*cmp equivalents. Thus, I am doing the same style check I would do with those. Removes the need for sys/param.h. Also updated the license file to have the same set of authors. commit 21eea473622bbf8aca13da88430ef68dcb5ba50a Author: Sean Timothy Noonan Date: Sat Aug 10 14:01:21 2013 -0400 Correct usage of ngx_strncmp with ngx_str_t commit 4d68e56121fc8bce663ab7b84857a6f4dd2281f2 Author: Sean Timothy Noonan Date: Sat Aug 10 12:51:46 2013 -0400 Provide a way to disable basic auth While some of us would like to think every communication occurs over SSL, this is a pipe dream. SPNEGO can be perfectly fine over an unencrypted channel. Basic auth cannot be. Closes #6. commit 88c10bd2e72745f46c87c3f802f01a2248491abd Author: Sean Timothy Noonan Date: Sat Aug 10 12:17:00 2013 -0400 Update ChangeLog commit 524f70b58e776d7471aa3fda820b7bf5011a6fff Author: Sean Timothy Noonan Date: Sat Aug 10 12:16:40 2013 -0400 Correct comparison of authorized principal len Fixes #7 commit ee771851afaba99ebadc616b909bc41762a852b0 Author: Sean Noonan Date: Tue Jul 16 17:38:56 2013 +0000 Add auth_gss_authorized_principal support This can be used to specify a list of principals that are allowed to authenticate commit 0b40e9adaf2873578ef459ba22fda502b0e4f078 Author: Sean Noonan Date: Fri Jul 12 20:11:26 2013 +0000 Fix compile error after haphazard NTLM addition commit 2592edb043f996bbfb3a1c8792752d6bcfb9427c Author: Sean Timothy Noonan Date: Fri Jul 12 16:01:55 2013 -0400 Update README.md commit 9fb57c5b043076be41d25281bd48769f4052a3e5 Author: Sean Noonan Date: Fri Jul 12 19:54:57 2013 +0000 Update documentation commit 21995ec282ce04299d630e4fa87f2c9357b8328e Author: Sean Noonan Date: Fri Jul 12 19:48:27 2013 +0000 Update documentation commit 81a26f9e98eb2311ba2c6e81e9adcad85b2d59be Author: Sean Noonan Date: Fri Jul 12 19:10:30 2013 +0000 Use proper credentials when none are specified Rather than construct specific principal names, the correct behavior is to use GSS_C_NO_CREDENTIAL. Additionally, always send both basic auth and negotiate headers commit d6ecb02b9fe1bfb7e7eac6b98be6bc133edb9b51 Author: Sean Timothy Noonan Date: Thu Jul 11 19:23:49 2013 -0400 Remove hostname directive from configuration commit cc5514fd4c002cf70446f447decf806a3c3bd57d Merge: 5afe502 a00bb5d Author: Sean Timothy Noonan Date: Thu Jun 6 19:48:31 2013 -0400 Merge changes due to pull request commit 5afe5024390e02cf92bbe79eb2131c0a1ad46384 Author: Sean Timothy Noonan Date: Thu Jun 6 19:40:46 2013 -0400 Modify README and remove TODO While the majority usage of this module will be against a Windows domain, that is certainly not the only use case. We need to be clear that the requirements for Windows are just those and do not necessarily apply across the board. commit 7f8ac65c7e2683c9a50cca52f3ecaaa738807746 Author: Sean Timothy Noonan Date: Thu Jun 6 19:18:16 2013 -0400 Formatting cleanup commit a00bb5d4f6c7d8dbedd9cebd8f372e7de2c5ad69 Merge: 87bb358 413012a Author: Sean Timothy Noonan Date: Thu Jun 6 16:34:41 2013 -0700 Merge pull request #5 from rbarrois/fix_auth_basic Fix auth basic fallback. commit 413012a2aa5bbdec4138448d0ff26546a1587b9d Author: Raphaël Barrois Date: Fri Jun 7 01:28:10 2013 +0200 Properly send back WWW-Authenticate header on 401 (Closes #4). commit 5062c25c64cfb0db8eaf3acb03e70b2dc40dbf22 Author: Raphaël Barrois Date: Fri Jun 7 00:27:14 2013 +0200 Remove buggy calls to ngx_strlen on ngx_str_t. commit 87bb3581c4764a83517b37210f1c5119c25dc67d Merge: f6f5271 cec02d0 Author: Sean Noonan Date: Thu Jun 6 20:25:33 2013 +0000 Merge basic auth changes from pyhalov commit f6f5271d6d5ed9297673760e0d263ec2ab6d642d Merge: 4e8f13e 57cb988 Author: Sean Timothy Noonan Date: Mon May 13 20:09:23 2013 -0700 Merge pull request #2 from Roguelazer/fix_segfault fix segfault on log commit 57cb9883d011f3b251a7a18adf318ab2aae166be Author: James Brown Date: Mon May 13 19:50:48 2013 -0700 fix segfault on log commit 4e8f13e16363fc90ecbd0477ec728c96d5f4a8d3 Author: Sean Timothy Noonan Date: Mon May 6 11:13:16 2013 -0300 Update README.md Removed Windows specific references, as they are severely misleading to people working in an MIT/Heimdal only environment. commit cec02d072286bcfdaefc1edff6f4aaf983369100 Author: Alexander Pyhalov Date: Wed Apr 10 00:24:41 2013 +0400 Behave like apache mod_auth_krb5: if Negotiate failed, then try pure basic. Otherwise Windows clients, which doesn't belong to domain, fail. commit b2f14e233626cda825e3fc0da115a5dbc5ed4031 Author: Alexander Pyhalov Date: Mon Apr 8 17:55:49 2013 +0400 Made basic authorization work. Added posibility for using GSSAPI on non-default ports. Alway pass realm to nginx if forced commit 9bd83699a325a37f17475c3dc106b46ecf86b18e Merge: df0769d f332f1c Author: Sean Timothy Noonan Date: Fri Apr 5 20:37:04 2013 -0700 Merge pull request #1 from ifad/master Documentation, enhanced debugging, BOF fixes commit f332f1c53456f79fd772f017c500aeda9e173ff7 Author: Marcello Barnaba Date: Tue Feb 19 18:31:37 2013 +0100 README commit 6631c8aab6a94236a1470507c086be0595fb40ba Author: Marcello Barnaba Date: Tue Feb 19 14:29:36 2013 +0100 Add myself to the list of authors commit af8c31d8cab62f417234f47b3365b24e2709f50a Author: Marcello Barnaba Date: Tue Feb 19 14:29:30 2013 +0100 Log the input_token, to debug failures due to NTLMSSP commit af3a2444207c88d894c39eb08f4a90cbfbefda5c Author: Marcello Barnaba Date: Tue Feb 19 14:29:10 2013 +0100 Silence pointer qualifier ignore warning commit 70f88935812165ce3f1d68b3bfec7214b7e469ac Author: Marcello Barnaba Date: Tue Feb 19 14:28:45 2013 +0100 Build fully-qualified SPN, using host name if it is not specified in config commit b8dd3c0fbd885afb5adb93e393d4642fdc40c07a Author: Marcello Barnaba Date: Tue Feb 19 14:26:38 2013 +0100 Fix another BOF commit 8b1ac33bfad41d3ad81888ff8c2a1b1de11b8676 Author: Marcello Barnaba Date: Tue Feb 19 14:26:16 2013 +0100 Fix buffer overflow commit 3b6b171c1cb8baf0c95379c608090acceed600bc Author: Marcello Barnaba Date: Mon Feb 18 19:13:32 2013 +0100 Remove redundant -Wl option commit df0769d7341981be88bf39b82250ad6ef0edfedd Author: Sean Noonan Date: Thu Sep 27 14:37:58 2012 +0000 Correct the length of the host name when there is no port specified commit 9f04ee459b469627fc92d0c573c4ae1f8933cf82 Author: Sean Noonan Date: Wed Sep 26 21:43:19 2012 +0000 Update README and bump version in Makefile commit 44bbdef3f10aa719fa3a150001314ff508e08f87 Author: Sean Noonan Date: Wed Sep 26 21:10:52 2012 +0000 remove spnegohelp commit fc2a777d4eb65d9f3bf14f3d6fac1a7d21f94a6a Author: Sean Noonan Date: Wed Sep 26 20:46:37 2012 +0000 fixes GSS Negotiate fallback; reformat code for consistency commit 0cd52c0a5ac46591b0a59b117c37939ec034ac27 Author: Sean Noonan Date: Wed Sep 26 18:56:35 2012 +0000 NULL == commit 68ef07d74e62d04e7c07d5021c7a220a71ee63c4 Author: Sean Noonan Date: Wed Sep 26 18:26:09 2012 +0000 replace references to unsigned char with u_char since we have it commit 6f2a2bb8eca53d11844aa83ba50ef43968c0ce98 Author: Sean Noonan Date: Wed Sep 26 17:30:44 2012 +0000 Revert changes that required gss_nt_krb5_name instead of GSS_C_NT_HOSTBASED_SERVICE commit 3ed44df57ae51cf2595550acbe65ec6ecb6d2f76 Author: Sean Noonan Date: Wed Sep 26 17:12:44 2012 +0000 Work properly when run on non-standard ports commit 117fbd8b11b9c153fc909797cbae43407f52a754 Author: Pavel Plesov Date: Wed Feb 15 17:58:35 2012 +0400 Fix memory allocation in Basic auth commit c2035a7b1c900491fb3df2ea50f52fcb34f6be2f Author: Pavel Plesov Date: Wed Feb 15 17:40:20 2012 +0400 Fix build and link with MIT Kerberos 1.9.2 commit 6c4193f009614c047f94b68b8e1b0c2ef806836d Merge: 3cfbb2d 8ac7acf Author: Pavel Plesov Date: Wed Feb 15 18:11:41 2012 +0400 Merge remote-tracking branch 'muhgatus/master' commit 3cfbb2d04d992ab69dd51f49bde9827aa4f88693 Author: Pavel Plesov Date: Wed Feb 15 17:35:29 2012 +0400 Fix link with local build of spnegohelp commit 06a9a5feffd8fb164c188f7ffbea40351672df3b Merge: d96a011 8c18ff7 Author: Pavel Plesov Date: Wed Feb 15 18:07:35 2012 +0400 Merge remote-tracking branch 'solj/master' commit d96a011d9561bd2a7845e17f4b89309d7c064d8f Author: Pavel Plesov Date: Wed Feb 15 13:54:53 2012 +0400 Add FreeBSD to build configuration commit 8ac7acf97196c7f8233b88ddabbef1e132182698 Author: muhgatus Date: Wed Feb 8 10:02:14 2012 +0100 added basic auth via kerberos commit 8c18ff7bd857ba838ee4136663165ee6e646d001 Author: Sol Jerome Date: Thu Sep 29 17:25:09 2011 -0500 gitignore: Ignore non-source files Signed-off-by: Sol Jerome commit ba87108541e7b2b1e9470265b74ef93a43f811cf Author: Michael Shadle Date: Thu Jun 10 02:01:36 2010 -0700 Minor typo in README commit 27ff31a356dad663dcc480f9b088520af9ba165a Author: Michael Shadle Date: Thu Jun 10 01:56:50 2010 -0700 initial commit, please read README! spnego-http-auth-nginx-module-1.1.3/LICENSE000066400000000000000000000030111501012364300203220ustar00rootroot00000000000000/* * Copyright (C) 2009 Michal Kowalski * Copyright (C) 2012-2013 Sean Timothy Noonan * Copyright (C) 2013 Marcello Barnaba * Copyright (C) 2013 Alexander Pyhalov * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * * THIS SOFTWARE IS PROVIDED BY AUTHOR AND CONTRIBUTORS ``AS IS'' AND * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE * ARE DISCLAIMED. IN NO EVENT SHALL AUTHOR OR CONTRIBUTORS BE LIABLE * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF * SUCH DAMAGE. * */ spnego-http-auth-nginx-module-1.1.3/Makefile000066400000000000000000000017141501012364300207650ustar00rootroot00000000000000 NAME=ngx_http_auth_spnego_module VERSION=1.0.0 NPKG=$(NAME)-$(VERSION) NHEAD=$(NAME)-HEAD NCURRENT=$(NAME)-current GIT-FILES:=$(shell git ls-files | grep -v ChangeLog) FILES=ChangeLog $(GIT-FILES) ChangeLog: $(GIT-FILES) git log | sed 1d> "$@" arch-release: rm -f ../$(NPKG).tar.gz ../$(NPKG).zip scripts/link-files-to .tmp/$(NPKG) $(FILES) git log > .tmp/$(NPKG)/ChangeLog tar cvzf ../$(NPKG).tar.gz -C .tmp $(NPKG) cd .tmp && zip -r ../../$(NPKG).zip $(NPKG) rm -rf .tmp arch-current: rm -f ../$(NCURRENT).tar.gz ../$(NCURRENT).zip scripts/link-files-to .tmp/$(NCURRENT) $(FILES) git log > .tmp/$(NCURRENT)/ChangeLog tar cvzf ../$(NCURRENT).tar.gz -C .tmp $(NCURRENT) cd .tmp && zip -r ../../$(NCURRENT).zip $(NCURRENT) rm -rf .tmp arch-head: rm -f ../$(NNHEAD).tar.gz ../$(NHEAD).zip git archive --format=zip --prefix=$(NHEAD)/ HEAD > ../$(NHEAD).zip git archive --format=tar --prefix=$(NHEAD)/ HEAD | gzip > ../$(NHEAD).tar.gz clean: rm -f *~ spnego-http-auth-nginx-module-1.1.3/README.Windows.md000066400000000000000000000155471501012364300222460ustar00rootroot00000000000000Crash course to Windows KDC Configuration ========================================= On the AD side, you need to: * Create a new user, whose name should be the service name you'll be using Kerberos authentication on. E.g. `app.example`. * Set the "User cannot change password" and "Password never expires" options on the account. If you are using AES-128 or AES-256, set also the "This account supports Kerberos AES 128 bit encryption" option or the 256 bit one. It is recommended to use AES, see [here](https://blogs.technet.microsoft.com/petergu/2013/04/14/interpreting-the-supportedencryptiontypes-registry-key/) for more details. * Set a strong password on it. Feel free to forget it, you are not going to insert it anywhere. * From a Windows `cmd.exe` window, generate the service principals and keytabs for this user using [`ktpass`](https://docs.microsoft.com/en-us/windows-server/administration/windows-commands/ktpass). You need an SPN named `host/foo.example.com`, and another named `HTTP/foo.example.com`. It is **crucial** that `foo.example.com` is the DNS name of your web site in the intranet, and it is an `A` record. Given that `app.example` is the account name you created, you would execute, **in this exact order**: C:\> ktpass -princ host/foo.example.com@EXAMPLE.COM -mapuser EXAMPLECOM\app.example -pass * -out host.foo.keytab -ptype KRB5_NT_PRINCIPAL -crypto AES256-SHA1 C:\> ktpass -princ HTTP/foo.example.com@EXAMPLE.COM -mapuser EXAMPLECOM\app.example -pass * -out http.foo.keytab -ptype KRB5_NT_PRINCIPAL -crypto AES256-SHA1 * If you need to support multiple host names using the same service account, you can issue further `ktpass` commands, but **ensure to issue the `host/` one first and the `HTTP/` one after**. Example: C:\> ktpass -princ host/bar.example.com@EXAMPLE.COM -mapuser EXAMPLECOM\app.example -pass * -out host.bar.keytab -ptype KRB5_NT_PRINCIPAL -crypto AES256-SHA1 C:\> ktpass -princ HTTP/bar.example.com@EXAMPLE.COM -mapuser EXAMPLECOM\app.example -pass * -out http.bar.keytab -ptype KRB5_NT_PRINCIPAL -crypto AES256-SHA1 * Verify that the correct SPNs are created using [`setspn`](https://social.technet.microsoft.com/wiki/contents/articles/717.service-principal-names-spns-setspn-syntax-setspn-exe.aspx) C:\> setspn -Q */foo.example.com it should yield both the `HTTP/` and `host/` SPNs, both mapped to the `app.example` user. Example with a service accessible via both `foo.example.com` and `bar.example.com`: C:\Temp>setspn -Q */foo.example.com Checking domain DC=example,DC=com CN=app.example,OU=Service Accounts,DC=example,DC=com HTTP/bar.example.com host/bar.example.com HTTP/foo.example.com host/bar.example.com `setspn` lists the entries in the reverse order they were created. So **it is crucial that the HTTP and host entries are in pairs, in that order**. Crash course to UNIX KRB5 and nginx configuration ------------------------------------------------- * Verify that your UNIX machine is using the same DNS server as your DC, most likely it'll use the DC itself. * Create an `/etc/krb5.conf` configuration file, replacing the realm and kdc host names with your own AD setup: [libdefaults] default_tgs_enctypes = aes256-cts-hmac-sha1-96 default_tkt_enctypes = aes256-cts-hmac-sha1-96 default_keytab_name = FILE:/etc/krb5.keytab default_realm = EXAMPLE.COM ticket_lifetime = 24h kdc_timesync = 1 ccache_type = 4 forwardable = false proxiable = false [realms] EXAMPLE.COM = { kdc = dc.example.com admin_server = dc.example.com default_domain = example.com } [domain_realm] .kerberos.server = EXAMPLE.COM .example.com = EXAMPLE.COM * Copy the keytab files (`host.foo.keytab`, `http.foo.keytab`, etc) created with `ktpass` on Windows to your UNIX machine. * Create a `krb5.keytab` using `ktutil`, concatenating together the keytabs generated by `ktpass`: # ktutil ktutil: rkt host.foo.keytab ktutil: rkt http.foo.keytab ktutil: rkt host.bar.keytab ktutil: rkt http.bar.keytab ktutil: wkt /etc/krb5.keytab ktutil: quit * Verify that the created keytab file has been built correctly: # klist -kt /etc/krb5.keytab Keytab name: FILE:/etc/krb5.keytab KVNO Timestamp Principal ---- ----------------- -------------------------------------------------------- 1 02/19/13 04:02:48 host/foo.example.com@EXAMPLE.COM 2 02/19/13 04:02:48 HTTP/foo.example.com@EXAMPLE.COM 3 02/19/13 04:02:48 host/bar.example.com@EXAMPLE.COM 4 02/19/13 04:02:48 HTTP/bar.example.com@EXAMPLE.COM Key version numbers (`KVNO`) will be different in your case. * Verify that you are able to authenticate using the keytab, without password: # kinit -5 -V -k -t /etc/krb5.keytab HTTP/bar.example.com Authenticated to Kerberos v5 # klist Ticket cache: FILE:/tmp/krb5cc_0 Default principal: HTTP/foo.example.com@EXAMPLE.COM Valid starting Expires Service principal 02/19/13 17:37:42 02/20/13 03:37:40 krbtgt/EXAMPLE.COM@EXAMPLE.COM renew until 02/20/13 17:37:42 Please note that you will be able to authenticate **only** with the **LAST** SPN that has been configured via `ktpass`, as windows user accounts keep the mapping only with a single SPN, and that is the one displayed in the "User logon name" field in the "Account" pane of an user account details screen of Active Directory Users and Computers, or the first result of `setspn -Q`. * Make the keytab file readable only by root and the nginx group: # chmod 440 /etc/krb5.keytab # chown root:nginx /etc/krb5.keytab * Configure a SPNEGO-protected location in the nginx configuration file: server { server_name foo.example.com; ssl on; ssl_certificate example.com.crt; ssl_certificate_key example.com.crt; location / { root /some/where; index index.html; auth_gss on; auth_gss_realm EXAMPLE.COM; auth_gss_keytab /etc/krb5.keytab; auth_gss_service_name HTTP; } } The SPN will be built as follows: $auth_gss_service_name / $server_name @ $auth_gss_realm In the above example, it'll be `HTTP/foo.example.com@EXAMPLE.COM`. You can specify a fully-qualified SPN in the `auth_gss_service_name` configuration option, in this case the `server_name` won't be added automatically. spnego-http-auth-nginx-module-1.1.3/README.md000066400000000000000000000175711501012364300206140ustar00rootroot00000000000000Nginx module for HTTP SPNEGO auth ================================= This module implements adds [SPNEGO](http://tools.ietf.org/html/rfc4178) support to nginx(http://nginx.org). It currently supports only Kerberos authentication via [GSSAPI](http://en.wikipedia.org/wiki/GSSAPI) Prerequisites ------------- Authentication has been tested with (at least) the following: * Nginx 1.2 through 1.15 * Internet Explorer 8 and above * Firefox 10 and above * Chrome 20 and above * Curl 7.x (GSS-Negotiate), 7.x (SPNEGO/fbopenssl) The underlying kerberos library used for these tests was MIT KRB5 v1.12. Installation ------------ 1. Download [nginx source](http://www.nginx.org/en/download.html) 1. Extract to a directory 1. Clone this module into the directory 1. Follow the [nginx install documentation](http://nginx.org/en/docs/install.html) and pass an `--add-module` option to nginx configure: ./configure --add-module=spnego-http-auth-nginx-module Note that if it isn't clear, you do need KRB5 (MIT or Heimdal) header files installed. On Debian based distributions, including Ubuntu, this is the krb5-multidev, libkrb5-dev, heimdal-dev, or heimdal-multidev package depending on your environment. On other Linux distributions, you want the development libraries that provide gssapi_krb5.h. Configuration reference ----------------------- You can configure GSS authentication on a per-location and/or a global basis: These options are required. * `auth_gss`: on/off, for ease of unsecuring while leaving other options in the config file * `auth_gss_keytab`: absolute path-name to keytab file containing service credentials These options should ONLY be specified if you have a keytab containing privileged principals. In nearly all cases, you should not put these in the configuration file, as `gss_accept_sec_context` will do the right thing. * `auth_gss_realm`: Kerberos realm name. If this is specified, the realm is only passed to the nginx variable $remote_user if it differs from this default. To override this behavior, set *auth_gss_format_full* to 1 in your configuration. * `auth_gss_service_name`: service principal name to use when acquiring credentials. If you would like to authorize only a specific set of principals, you can use the `auth_gss_authorized_principal` directive. The configuration syntax supports multiple entries, one per line. auth_gss_authorized_principal @ auth_gss_authorized_principal @ Principals can also be authorized using a regex pattern via the `auth_gss_authorized_principal_regex` directive. This directive can be used together with the `auth_gss_authorized_principal` directive. auth_gss_authorized_principal @ auth_gss_authorized_principal_regex ^()/()@$ The remote user header in nginx can only be set by doing basic authentication. Thus, this module sets a bogus basic auth header that will reach your backend application in order to set this header/nginx variable. The easiest way to disable this behavior is to add the following configuration to your location config. proxy_set_header Authorization ""; A future version of the module may make this behavior an option, but this should be a sufficient workaround for now. If you would like to enable GSS local name rules to rewrite usernames, you can specify the `auth_gss_map_to_local` option. Credential Delegation ----------------------------- User credentials can be delegated to nginx using the `auth_gss_delegate_credentials` directive. This directive will enable unconstrained delegation if the user chooses to delegate their credentials. Constrained delegation (S4U2proxy) can also be enabled using the `auth_gss_constrained_delegation` directive together with the `auth_gss_delegate_credentials` directive. To specify the ccache file name to store the service ticket used for constrained delegation, set the `auth_gss_service_ccache` directive. Otherwise, the default ccache name will be used. auth_gss_service_ccache /tmp/krb5cc_0; auth_gss_delegate_credentials on; auth_gss_constrained_delegation on; The delegated credentials will be stored within the systems tmp directory. Once the request is completed, the credentials file will be destroyed. The name of the credentials file will be specified within the nginx variable `$krb5_cc_name`. Usage of the variable can include passing it to a fcgi program using the `fastcgi_param` directive. fastcgi_param KRB5CCNAME $krb5_cc_name; Constrained delegation is currently only supported using the negotiate authentication scheme and has only been testing with MIT Kerberos (Use at your own risk if using Heimdal Kerberos). Basic authentication fallback ----------------------------- The module falls back to basic authentication by default if no negotiation is attempted by the client. If you are using SPNEGO without SSL, it is recommended you disable basic authentication fallback, as the password would be sent in plaintext. This is done by setting `auth_gss_allow_basic_fallback` in the config file. auth_gss_allow_basic_fallback off These options affect the operation of basic authentication: * `auth_gss_realm`: Kerberos realm name. If this is specified, the realm is only passed to the nginx variable $remote_user if it differs from this default. To override this behavior, set *auth_gss_format_full* to 1 in your configuration. * `auth_gss_force_realm`: Forcibly authenticate using the realm configured in `auth_gss_realm` or the system default realm if `auth_gss_realm` is not set. This will rewrite $remote_user if the client provided a different realm. If *auth_gss_format_full* is not set, $remote_user will not include a realm even if one was specified by the client. Troubleshooting --------------- ### Check the logs. If you see a mention of NTLM, your client is attempting to connect using [NTLMSSP](http://en.wikipedia.org/wiki/NTLMSSP), which is unsupported and insecure. ### Verify that you have an HTTP principal in your keytab ### #### MIT Kerberos utilities #### $ KRB5_KTNAME=FILE: klist -k or $ ktutil ktutil: read_kt ktutil: list #### Heimdal Kerberos utilities #### $ ktutil -k list ### Obtain an HTTP principal If you find that you do not have the HTTP service principal, are running in an Active Directory environment, and are bound to the domain such that Samba tools work properly $ env KRB5_KTNAME=FILE: net ads -P keytab add HTTP If you are running in a different kerberos environment, you can likely run $ env KRB5_KTNAME=FILE: krb5_keytab HTTP ### Increase maximum allowed header size In Active Directory environment, SPNEGO token in the Authorization header includes PAC (Privilege Access Certificate) information, which includes all security groups the user belongs to. This may cause the header to grow beyond default 8kB limit and causes following error message: 400 Bad Request Request Header Or Cookie Too Large For performance reasons, best solution is to reduce the number of groups the user belongs to. When this is impractical, you may also choose to increase the allowed header size by explicitly setting the number and size of Nginx header buffers: large_client_header_buffers 8 32k; Debugging --------- The module prints all sort of debugging information if nginx is compiled with the `--with-debug` option, and the `error_log` directive has a `debug` level. NTLM ---- Note that the module does not support [NTLMSSP](http://en.wikipedia.org/wiki/NTLMSSP) in Negotiate. NTLM, both v1 and v2, is an exploitable protocol and should be avoided where possible. Windows ------- For Windows KDC/AD environments, see the documentation [here](README.Windows.md). Help ---- If you're unable to figure things out, please feel free to open an issue on Github and I'll do my best to help you. spnego-http-auth-nginx-module-1.1.3/clang-format000066400000000000000000000116101501012364300216160ustar00rootroot00000000000000--- Language: Cpp # BasedOnStyle: LLVM AccessModifierOffset: -2 AlignAfterOpenBracket: Align AlignArrayOfStructures: None AlignConsecutiveMacros: None AlignConsecutiveAssignments: None AlignConsecutiveBitFields: None AlignConsecutiveDeclarations: None AlignEscapedNewlines: Right AlignOperands: Align AlignTrailingComments: true AllowAllArgumentsOnNextLine: true AllowAllConstructorInitializersOnNextLine: true AllowAllParametersOfDeclarationOnNextLine: true AllowShortEnumsOnASingleLine: true AllowShortBlocksOnASingleLine: Never AllowShortCaseLabelsOnASingleLine: false AllowShortFunctionsOnASingleLine: All AllowShortLambdasOnASingleLine: All AllowShortIfStatementsOnASingleLine: Never AllowShortLoopsOnASingleLine: false AlwaysBreakAfterDefinitionReturnType: None AlwaysBreakAfterReturnType: None AlwaysBreakBeforeMultilineStrings: false AlwaysBreakTemplateDeclarations: MultiLine AttributeMacros: - __capability BinPackArguments: true BinPackParameters: true BraceWrapping: AfterCaseLabel: false AfterClass: false AfterControlStatement: Never AfterEnum: false AfterFunction: false AfterNamespace: false AfterObjCDeclaration: false AfterStruct: false AfterUnion: false AfterExternBlock: false BeforeCatch: false BeforeElse: false BeforeLambdaBody: false BeforeWhile: false IndentBraces: false SplitEmptyFunction: true SplitEmptyRecord: true SplitEmptyNamespace: true BreakBeforeBinaryOperators: None BreakBeforeConceptDeclarations: true BreakBeforeBraces: Attach BreakBeforeInheritanceComma: false BreakInheritanceList: BeforeColon BreakBeforeTernaryOperators: true BreakConstructorInitializersBeforeComma: false BreakConstructorInitializers: BeforeColon BreakAfterJavaFieldAnnotations: false BreakStringLiterals: true ColumnLimit: 80 CommentPragmas: '^ IWYU pragma:' CompactNamespaces: false ConstructorInitializerAllOnOneLineOrOnePerLine: false ConstructorInitializerIndentWidth: 4 ContinuationIndentWidth: 4 Cpp11BracedListStyle: true DeriveLineEnding: true DerivePointerAlignment: false DisableFormat: false EmptyLineAfterAccessModifier: Never EmptyLineBeforeAccessModifier: LogicalBlock ExperimentalAutoDetectBinPacking: false FixNamespaceComments: true ForEachMacros: - foreach - Q_FOREACH - BOOST_FOREACH IfMacros: - KJ_IF_MAYBE IncludeBlocks: Preserve IncludeCategories: - Regex: '^"(llvm|llvm-c|clang|clang-c)/' Priority: 2 SortPriority: 0 CaseSensitive: false - Regex: '^(<|"(gtest|gmock|isl|json)/)' Priority: 3 SortPriority: 0 CaseSensitive: false - Regex: '.*' Priority: 1 SortPriority: 0 CaseSensitive: false IncludeIsMainRegex: '(Test)?$' IncludeIsMainSourceRegex: '' IndentAccessModifiers: false IndentCaseLabels: false IndentCaseBlocks: false IndentGotoLabels: true IndentPPDirectives: None IndentExternBlock: AfterExternBlock IndentRequires: false IndentWidth: 4 IndentWrappedFunctionNames: false InsertTrailingCommas: None JavaScriptQuotes: Leave JavaScriptWrapImports: true KeepEmptyLinesAtTheStartOfBlocks: true LambdaBodyIndentation: Signature MacroBlockBegin: '' MacroBlockEnd: '' MaxEmptyLinesToKeep: 1 NamespaceIndentation: None ObjCBinPackProtocolList: Auto ObjCBlockIndentWidth: 2 ObjCBreakBeforeNestedBlockParam: true ObjCSpaceAfterProperty: false ObjCSpaceBeforeProtocolList: true PenaltyBreakAssignment: 2 PenaltyBreakBeforeFirstCallParameter: 19 PenaltyBreakComment: 300 PenaltyBreakFirstLessLess: 120 PenaltyBreakString: 1000 PenaltyBreakTemplateDeclaration: 10 PenaltyExcessCharacter: 1000000 PenaltyReturnTypeOnItsOwnLine: 60 PenaltyIndentedWhitespace: 0 PointerAlignment: Right PPIndentWidth: -1 ReferenceAlignment: Pointer ReflowComments: true ShortNamespaceLines: 1 SortIncludes: CaseSensitive SortJavaStaticImport: Before SortUsingDeclarations: true SpaceAfterCStyleCast: false SpaceAfterLogicalNot: false SpaceAfterTemplateKeyword: true SpaceBeforeAssignmentOperators: true SpaceBeforeCaseColon: false SpaceBeforeCpp11BracedList: false SpaceBeforeCtorInitializerColon: true SpaceBeforeInheritanceColon: true SpaceBeforeParens: ControlStatements SpaceAroundPointerQualifiers: Default SpaceBeforeRangeBasedForLoopColon: true SpaceInEmptyBlock: false SpaceInEmptyParentheses: false SpacesBeforeTrailingComments: 1 SpacesInAngles: Never SpacesInConditionalStatement: false SpacesInContainerLiterals: true SpacesInCStyleCastParentheses: false SpacesInLineCommentPrefix: Minimum: 1 Maximum: -1 SpacesInParentheses: false SpacesInSquareBrackets: false SpaceBeforeSquareBrackets: false BitFieldColonSpacing: Both Standard: Latest StatementAttributeLikeMacros: - Q_EMIT StatementMacros: - Q_UNUSED - QT_REQUIRE_VERSION TabWidth: 8 UseCRLF: false UseTab: Never WhitespaceSensitiveMacros: - STRINGIZE - PP_STRINGIZE - BOOST_PP_STRINGIZE - NS_SWIFT_NAME - CF_SWIFT_NAME ... spnego-http-auth-nginx-module-1.1.3/config000066400000000000000000000012761501012364300205200ustar00rootroot00000000000000ngx_addon_name=ngx_http_auth_spnego_module ngx_feature_libs="-lgssapi_krb5 -lkrb5 -lcom_err" if uname -o | grep -q FreeBSD; then ngx_feature_libs="$ngx_feature_libs -lgssapi" fi if uname -a | grep -q NetBSD; then ngx_feature_libs="-lgssapi -lkrb5 -lcom_err" fi if test -n "$ngx_module_link"; then ngx_module_type=HTTP ngx_module_name=ngx_http_auth_spnego_module ngx_module_srcs="$ngx_addon_dir/ngx_http_auth_spnego_module.c" ngx_module_libs="$ngx_feature_libs" . auto/module else HTTP_MODULES="$HTTP_MODULES ngx_http_auth_spnego_module" NGX_ADDON_SRCS="$NGX_ADDON_SRCS $ngx_addon_dir/ngx_http_auth_spnego_module.c" CORE_LIBS="$CORE_LIBS $ngx_feature_libs" fi spnego-http-auth-nginx-module-1.1.3/ngx_http_auth_spnego_module.c000066400000000000000000001751041501012364300252720ustar00rootroot00000000000000/* * Copyright (C) 2009 Michal Kowalski * Copyright (C) 2012-2013 Sean Timothy Noonan * Copyright (C) 2013 Marcello Barnaba * Copyright (C) 2013 Alexander Pyhalov * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * * THIS SOFTWARE IS PROVIDED BY AUTHOR AND CONTRIBUTORS ``AS IS'' AND * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE * ARE DISCLAIMED. IN NO EVENT SHALL AUTHOR OR CONTRIBUTORS BE LIABLE * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF * SUCH DAMAGE. * */ #include #include #include #include #include #include #include #define CCACHE_VARIABLE_NAME "krb5_cc_name" #define SHM_ZONE_NAME "shm_zone" #define RENEWAL_TIME 60 #define discard_const(ptr) ((void *)((uintptr_t)(ptr))) #define spnego_log_krb5_error(context, code) \ { \ const char *___kerror = krb5_get_error_message(context, code); \ spnego_debug2("Kerberos error: %d, %s", code, ___kerror); \ krb5_free_error_message(context, ___kerror); \ } #define spnego_error(code) \ ret = code; \ goto end #define spnego_debug0(msg) \ ngx_log_debug0(NGX_LOG_DEBUG_HTTP, r->connection->log, 0, msg) #define spnego_debug1(msg, one) \ ngx_log_debug1(NGX_LOG_DEBUG_HTTP, r->connection->log, 0, msg, one) #define spnego_debug2(msg, one, two) \ ngx_log_debug2(NGX_LOG_DEBUG_HTTP, r->connection->log, 0, msg, one, two) #define spnego_debug3(msg, one, two, three) \ ngx_log_debug3(NGX_LOG_DEBUG_HTTP, r->connection->log, 0, msg, one, two, \ three) #define spnego_log_error(fmt, args...) \ ngx_log_error(NGX_LOG_ERR, r->connection->log, 0, fmt, ##args) #ifndef krb5_realm_length #define krb5_realm_length(r) ((r).length) #define krb5_realm_data(r) ((r).data) #endif /* Module handler */ static ngx_int_t ngx_http_auth_spnego_handler(ngx_http_request_t *); static void *ngx_http_auth_spnego_create_loc_conf(ngx_conf_t *); static char *ngx_http_auth_spnego_merge_loc_conf(ngx_conf_t *, void *, void *); static ngx_int_t ngx_http_auth_spnego_init(ngx_conf_t *); #if (NGX_PCRE) static char *ngx_conf_set_regex_array_slot(ngx_conf_t *cf, ngx_command_t *cmd, void *conf); #endif ngx_int_t ngx_http_auth_spnego_set_bogus_authorization(ngx_http_request_t *r); const char *get_gss_error(ngx_pool_t *p, OM_uint32 error_status, char *prefix) { OM_uint32 maj_stat, min_stat; OM_uint32 msg_ctx = 0; gss_buffer_desc status_string; char buf[1024]; size_t len; ngx_str_t str; ngx_snprintf((u_char *)buf, sizeof(buf), "%s: %Z", prefix); len = ngx_strlen(buf); do { maj_stat = gss_display_status(&min_stat, error_status, GSS_C_MECH_CODE, GSS_C_NO_OID, &msg_ctx, &status_string); if (sizeof(buf) > len + status_string.length + 1) { ngx_sprintf((u_char *)buf + len, "%s:%Z", (char *)status_string.value); len += (status_string.length + 1); } gss_release_buffer(&min_stat, &status_string); } while (!GSS_ERROR(maj_stat) && msg_ctx != 0); str.len = len + 1; /* "include" '\0' */ str.data = (u_char *)buf; return (char *)(ngx_pstrdup(p, &str)); } static ngx_shm_zone_t *shm_zone; typedef enum { TYPE_KRB5_CREDS, TYPE_GSS_CRED_ID_T } creds_type; typedef struct { void *data; creds_type type; } creds_info; /* per request/connection */ typedef struct { ngx_str_t token; /* decoded Negotiate token */ ngx_int_t head; /* non-zero flag if headers set */ ngx_int_t ret; /* current return code */ ngx_str_t token_out_b64; /* base64 encoded output tokent */ } ngx_http_auth_spnego_ctx_t; typedef struct { ngx_flag_t protect; ngx_str_t realm; ngx_str_t keytab; ngx_str_t service_ccache; ngx_str_t srvcname; ngx_str_t shm_zone_name; ngx_flag_t fqun; ngx_flag_t force_realm; ngx_flag_t allow_basic; ngx_array_t *auth_princs; #if (NGX_PCRE) ngx_array_t *auth_princs_regex; #endif ngx_flag_t map_to_local; ngx_flag_t delegate_credentials; ngx_flag_t constrained_delegation; } ngx_http_auth_spnego_loc_conf_t; #define SPNEGO_NGX_CONF_FLAGS \ NGX_HTTP_MAIN_CONF \ | NGX_HTTP_SRV_CONF | NGX_HTTP_LOC_CONF | NGX_HTTP_LMT_CONF | NGX_CONF_FLAG /* Module Directives */ static ngx_command_t ngx_http_auth_spnego_commands[] = { {ngx_string("auth_gss"), SPNEGO_NGX_CONF_FLAGS, ngx_conf_set_flag_slot, NGX_HTTP_LOC_CONF_OFFSET, offsetof(ngx_http_auth_spnego_loc_conf_t, protect), NULL}, {ngx_string("auth_gss_zone_name"), NGX_HTTP_MAIN_CONF|NGX_CONF_TAKE1, ngx_conf_set_str_slot, NGX_HTTP_LOC_CONF_OFFSET, offsetof(ngx_http_auth_spnego_loc_conf_t, shm_zone_name), NULL}, {ngx_string("auth_gss_realm"), SPNEGO_NGX_CONF_FLAGS, ngx_conf_set_str_slot, NGX_HTTP_LOC_CONF_OFFSET, offsetof(ngx_http_auth_spnego_loc_conf_t, realm), NULL}, {ngx_string("auth_gss_keytab"), SPNEGO_NGX_CONF_FLAGS, ngx_conf_set_str_slot, NGX_HTTP_LOC_CONF_OFFSET, offsetof(ngx_http_auth_spnego_loc_conf_t, keytab), NULL}, {ngx_string("auth_gss_service_ccache"), SPNEGO_NGX_CONF_FLAGS, ngx_conf_set_str_slot, NGX_HTTP_LOC_CONF_OFFSET, offsetof(ngx_http_auth_spnego_loc_conf_t, service_ccache), NULL}, {ngx_string("auth_gss_service_name"), SPNEGO_NGX_CONF_FLAGS, ngx_conf_set_str_slot, NGX_HTTP_LOC_CONF_OFFSET, offsetof(ngx_http_auth_spnego_loc_conf_t, srvcname), NULL}, {ngx_string("auth_gss_format_full"), SPNEGO_NGX_CONF_FLAGS, ngx_conf_set_flag_slot, NGX_HTTP_LOC_CONF_OFFSET, offsetof(ngx_http_auth_spnego_loc_conf_t, fqun), NULL}, {ngx_string("auth_gss_force_realm"), SPNEGO_NGX_CONF_FLAGS, ngx_conf_set_flag_slot, NGX_HTTP_LOC_CONF_OFFSET, offsetof(ngx_http_auth_spnego_loc_conf_t, force_realm), NULL}, {ngx_string("auth_gss_allow_basic_fallback"), SPNEGO_NGX_CONF_FLAGS, ngx_conf_set_flag_slot, NGX_HTTP_LOC_CONF_OFFSET, offsetof(ngx_http_auth_spnego_loc_conf_t, allow_basic), NULL}, {ngx_string("auth_gss_authorized_principal"), SPNEGO_NGX_CONF_FLAGS | NGX_CONF_1MORE, ngx_conf_set_str_array_slot, NGX_HTTP_LOC_CONF_OFFSET, offsetof(ngx_http_auth_spnego_loc_conf_t, auth_princs), NULL}, #if (NGX_PCRE) {ngx_string("auth_gss_authorized_principal_regex"), SPNEGO_NGX_CONF_FLAGS | NGX_CONF_1MORE, ngx_conf_set_regex_array_slot, NGX_HTTP_LOC_CONF_OFFSET, offsetof(ngx_http_auth_spnego_loc_conf_t, auth_princs_regex), NULL}, #endif {ngx_string("auth_gss_map_to_local"), SPNEGO_NGX_CONF_FLAGS, ngx_conf_set_flag_slot, NGX_HTTP_LOC_CONF_OFFSET, offsetof(ngx_http_auth_spnego_loc_conf_t, map_to_local), NULL}, {ngx_string("auth_gss_delegate_credentials"), SPNEGO_NGX_CONF_FLAGS, ngx_conf_set_flag_slot, NGX_HTTP_LOC_CONF_OFFSET, offsetof(ngx_http_auth_spnego_loc_conf_t, delegate_credentials), NULL}, {ngx_string("auth_gss_constrained_delegation"), SPNEGO_NGX_CONF_FLAGS, ngx_conf_set_flag_slot, NGX_HTTP_LOC_CONF_OFFSET, offsetof(ngx_http_auth_spnego_loc_conf_t, constrained_delegation), NULL}, ngx_null_command}; /* Module Context */ static ngx_http_module_t ngx_http_auth_spnego_module_ctx = { NULL, /* preconf */ ngx_http_auth_spnego_init, /* postconf */ NULL, /* create main conf (defaults) */ NULL, /* init main conf (what's in nginx.conf) */ NULL, /* create server conf */ NULL, /* merge with main */ ngx_http_auth_spnego_create_loc_conf, /* create location conf */ ngx_http_auth_spnego_merge_loc_conf, /* merge with server */ }; /* Module Definition */ ngx_module_t ngx_http_auth_spnego_module = { /* ngx_uint_t ctx_index, index, spare{0-3}, version; */ NGX_MODULE_V1, /* 0, 0, 0, 0, 0, 0, 1 */ &ngx_http_auth_spnego_module_ctx, /* void *ctx */ ngx_http_auth_spnego_commands, /* ngx_command_t *commands */ NGX_HTTP_MODULE, /* ngx_uint_t type = 0x50545448 */ NULL, /* ngx_int_t (*init_master)(ngx_log_t *log) */ NULL, /* ngx_int_t (*init_module)(ngx_cycle_t *cycle) */ NULL, /* ngx_int_t (*init_process)(ngx_cycle_t *cycle) */ NULL, /* ngx_int_t (*init_thread)(ngx_cycle_t *cycle) */ NULL, /* void (*exit_thread)(ngx_cycle_t *cycle) */ NULL, /* void (*exit_process)(ngx_cycle_t *cycle) */ NULL, /* void (*exit_master)(ngx_cycle_t *cycle) */ NGX_MODULE_V1_PADDING, /* 0, 0, 0, 0, 0, 0, 0, 0 */ /* uintptr_t spare_hook{0-7}; */ }; #if (NGX_PCRE) static char *ngx_conf_set_regex_array_slot(ngx_conf_t *cf, ngx_command_t *cmd, void *conf) { char *p = conf; u_char errstr[NGX_MAX_CONF_ERRSTR]; ngx_str_t *value; ngx_regex_elt_t *re; ngx_regex_compile_t rc; ngx_array_t **a; ngx_conf_post_t *post; a = (ngx_array_t **)(p + cmd->offset); if (*a == NGX_CONF_UNSET_PTR) { *a = ngx_array_create(cf->pool, 4, sizeof(ngx_regex_elt_t)); if (*a == NULL) { return NGX_CONF_ERROR; } } re = ngx_array_push(*a); if (re == NULL) { return NGX_CONF_ERROR; } value = cf->args->elts; ngx_memzero(&rc, sizeof(ngx_regex_compile_t)); rc.pattern = value[1]; rc.pool = cf->pool; rc.err.len = NGX_MAX_CONF_ERRSTR; rc.err.data = errstr; if (ngx_regex_compile(&rc) != NGX_OK) { return NGX_CONF_ERROR; } re->regex = rc.regex; re->name = value[1].data; if (cmd->post) { post = cmd->post; return post->post_handler(cf, post, re); } return NGX_CONF_OK; } #endif static void *ngx_http_auth_spnego_create_loc_conf(ngx_conf_t *cf) { ngx_http_auth_spnego_loc_conf_t *conf; conf = ngx_pcalloc(cf->pool, sizeof(ngx_http_auth_spnego_loc_conf_t)); if (NULL == conf) { return NGX_CONF_ERROR; } conf->protect = NGX_CONF_UNSET; conf->fqun = NGX_CONF_UNSET; conf->force_realm = NGX_CONF_UNSET; conf->allow_basic = NGX_CONF_UNSET; conf->auth_princs = NGX_CONF_UNSET_PTR; #if (NGX_PCRE) conf->auth_princs_regex = NGX_CONF_UNSET_PTR; #endif conf->map_to_local = NGX_CONF_UNSET; conf->delegate_credentials = NGX_CONF_UNSET; conf->constrained_delegation = NGX_CONF_UNSET; return conf; } static ngx_int_t ngx_http_auth_spnego_init_shm_zone(ngx_shm_zone_t *shm_zone, void *data) { if (data) { shm_zone->data = data; return NGX_OK; } shm_zone->data = shm_zone->shm.addr; return NGX_OK; } static ngx_int_t ngx_http_auth_spnego_create_shm_zone(ngx_conf_t *cf, ngx_str_t *name) { if (shm_zone != NULL) return NGX_OK; shm_zone = ngx_shared_memory_add(cf, name, 65536, &ngx_http_auth_spnego_module); if (shm_zone == NULL) { return NGX_ERROR; } shm_zone->init = ngx_http_auth_spnego_init_shm_zone; return NGX_OK; } static char *ngx_http_auth_spnego_merge_loc_conf(ngx_conf_t *cf, void *parent, void *child) { ngx_http_auth_spnego_loc_conf_t *prev = parent; ngx_http_auth_spnego_loc_conf_t *conf = child; /* "off" by default */ ngx_conf_merge_off_value(conf->protect, prev->protect, 0); ngx_conf_merge_str_value(conf->shm_zone_name, prev->shm_zone_name, SHM_ZONE_NAME); if (conf->protect != 0) { if (ngx_http_auth_spnego_create_shm_zone(cf, &conf->shm_zone_name) != NGX_OK) { ngx_conf_log_error(NGX_LOG_INFO, cf, 0, "auth_spnego: failed to create shared memory zone"); return NGX_CONF_ERROR; } } ngx_conf_merge_str_value(conf->realm, prev->realm, ""); ngx_conf_merge_str_value(conf->keytab, prev->keytab, "/etc/krb5.keytab"); ngx_conf_merge_str_value(conf->service_ccache, prev->service_ccache, ""); ngx_conf_merge_str_value(conf->srvcname, prev->srvcname, ""); ngx_conf_merge_off_value(conf->fqun, prev->fqun, 0); ngx_conf_merge_off_value(conf->force_realm, prev->force_realm, 0); ngx_conf_merge_off_value(conf->allow_basic, prev->allow_basic, 1); ngx_conf_merge_ptr_value(conf->auth_princs, prev->auth_princs, NGX_CONF_UNSET_PTR); #if (NGX_PCRE) ngx_conf_merge_ptr_value(conf->auth_princs_regex, prev->auth_princs_regex, NGX_CONF_UNSET_PTR); #endif ngx_conf_merge_off_value(conf->map_to_local, prev->map_to_local, 0); ngx_conf_merge_off_value(conf->delegate_credentials, prev->delegate_credentials, 0); ngx_conf_merge_off_value(conf->constrained_delegation, prev->constrained_delegation, 0); #if (NGX_DEBUG) ngx_conf_log_error(NGX_LOG_DEBUG, cf, 0, "auth_spnego: protect = %i", conf->protect); ngx_conf_log_error(NGX_LOG_DEBUG, cf, 0, "auth_spnego: realm@0x%p = %s", conf->realm.data, conf->realm.data); ngx_conf_log_error(NGX_LOG_DEBUG, cf, 0, "auth_spnego: keytab@0x%p = %s", conf->keytab.data, conf->keytab.data); ngx_conf_log_error(NGX_LOG_DEBUG, cf, 0, "auth_spnego: service_ccache@0x%p = %s", conf->service_ccache.data, conf->service_ccache.data); ngx_conf_log_error(NGX_LOG_DEBUG, cf, 0, "auth_spnego: srvcname@0x%p = %s", conf->srvcname.data, conf->srvcname.data); ngx_conf_log_error(NGX_LOG_DEBUG, cf, 0, "auth_spnego: fqun = %i", conf->fqun); ngx_conf_log_error(NGX_LOG_DEBUG, cf, 0, "auth_spnego: allow_basic = %i", conf->allow_basic); ngx_conf_log_error(NGX_LOG_DEBUG, cf, 0, "auth_spnego: force_realm = %i", conf->force_realm); if (NGX_CONF_UNSET_PTR != conf->auth_princs) { size_t ii = 0; ngx_str_t *auth_princs = conf->auth_princs->elts; for (; ii < conf->auth_princs->nelts; ++ii) { ngx_conf_log_error(NGX_LOG_DEBUG, cf, 0, "auth_spnego: auth_princs = %.*s", auth_princs[ii].len, auth_princs[ii].data); } } #if (NGX_PCRE) if (NGX_CONF_UNSET_PTR != conf->auth_princs_regex) { size_t ii = 0; ngx_regex_elt_t *auth_princs_regex = conf->auth_princs_regex->elts; for (; ii < conf->auth_princs_regex->nelts; ++ii) { ngx_conf_log_error(NGX_LOG_DEBUG, cf, 0, "auth_spnego: auth_princs_regex = %.*s", ngx_strlen(auth_princs_regex[ii].name), auth_princs_regex[ii].name); } } #endif ngx_conf_log_error(NGX_LOG_DEBUG, cf, 0, "auth_spnego: map_to_local = %i", conf->map_to_local); ngx_conf_log_error(NGX_LOG_DEBUG, cf, 0, "auth_spnego: delegate_credentials = %i", conf->delegate_credentials); ngx_conf_log_error(NGX_LOG_DEBUG, cf, 0, "auth_spnego: constrained_delegation = %i", conf->constrained_delegation); #endif return NGX_CONF_OK; } static ngx_int_t ngx_http_auth_spnego_get_handler(ngx_http_request_t *r, ngx_http_variable_value_t *v, uintptr_t data) { return NGX_OK; } static ngx_int_t ngx_http_auth_spnego_set_variable(ngx_http_request_t *r, ngx_str_t *name, ngx_str_t *value) { u_char *lowercase = ngx_palloc(r->pool, name->len); ngx_uint_t key = ngx_hash_strlow(lowercase, name->data, name->len); ngx_pfree(r->pool, lowercase); ngx_http_variable_value_t *v = ngx_http_get_variable(r, name, key); if (v == NULL) { return NGX_ERROR; } v->len = value->len; v->data = value->data; return NGX_OK; } static ngx_int_t ngx_http_auth_spnego_add_variable(ngx_conf_t *cf, ngx_str_t *name) { ngx_http_variable_t *var = ngx_http_add_variable(cf, name, NGX_HTTP_VAR_NOCACHEABLE); if (var == NULL) { return NGX_ERROR; } var->get_handler = ngx_http_auth_spnego_get_handler; var->data = 0; return NGX_OK; } static ngx_int_t ngx_http_auth_spnego_init(ngx_conf_t *cf) { ngx_http_handler_pt *h; ngx_http_core_main_conf_t *cmcf; cmcf = ngx_http_conf_get_module_main_conf(cf, ngx_http_core_module); h = ngx_array_push(&cmcf->phases[NGX_HTTP_ACCESS_PHASE].handlers); if (NULL == h) { return NGX_ERROR; } *h = ngx_http_auth_spnego_handler; ngx_str_t var_name = ngx_string(CCACHE_VARIABLE_NAME); if (ngx_http_auth_spnego_add_variable(cf, &var_name) != NGX_OK) { return NGX_ERROR; } return NGX_OK; } static ngx_int_t ngx_http_auth_spnego_headers_basic_only(ngx_http_request_t *r, ngx_http_auth_spnego_ctx_t *ctx, ngx_http_auth_spnego_loc_conf_t *alcf) { ngx_str_t value = ngx_null_string; value.len = sizeof("Basic realm=\"\"") - 1 + alcf->realm.len; value.data = ngx_pcalloc(r->pool, value.len); if (NULL == value.data) { return NGX_ERROR; } ngx_snprintf(value.data, value.len, "Basic realm=\"%V\"", &alcf->realm); r->headers_out.www_authenticate = ngx_list_push(&r->headers_out.headers); if (NULL == r->headers_out.www_authenticate) { return NGX_ERROR; } r->headers_out.www_authenticate->hash = 1; #if defined(nginx_version) && nginx_version >= 1023000 r->headers_out.www_authenticate->next = NULL; #endif r->headers_out.www_authenticate->key.len = sizeof("WWW-Authenticate") - 1; r->headers_out.www_authenticate->key.data = (u_char *)"WWW-Authenticate"; r->headers_out.www_authenticate->value.len = value.len; r->headers_out.www_authenticate->value.data = value.data; ctx->head = 1; return NGX_OK; } static ngx_int_t ngx_http_auth_spnego_headers(ngx_http_request_t *r, ngx_http_auth_spnego_ctx_t *ctx, ngx_str_t *token, ngx_http_auth_spnego_loc_conf_t *alcf) { ngx_str_t value = ngx_null_string; /* only use token if authorized as there appears to be a bug in * Google Chrome when parsing a 401 Negotiate with a token */ if (NULL == token || ctx->ret != NGX_OK) { value.len = sizeof("Negotiate") - 1; value.data = (u_char *)"Negotiate"; } else { value.len = sizeof("Negotiate") + token->len; /* space accounts for \0 */ value.data = ngx_pcalloc(r->pool, value.len); if (NULL == value.data) { return NGX_ERROR; } ngx_snprintf(value.data, value.len, "Negotiate %V", token); } r->headers_out.www_authenticate = ngx_list_push(&r->headers_out.headers); if (NULL == r->headers_out.www_authenticate) { return NGX_ERROR; } r->headers_out.www_authenticate->hash = 1; #if defined(nginx_version) && nginx_version >= 1023000 r->headers_out.www_authenticate->next = NULL; #endif r->headers_out.www_authenticate->key.len = sizeof("WWW-Authenticate") - 1; r->headers_out.www_authenticate->key.data = (u_char *)"WWW-Authenticate"; r->headers_out.www_authenticate->value.len = value.len; r->headers_out.www_authenticate->value.data = value.data; if (alcf->allow_basic) { ngx_str_t value2 = ngx_null_string; value2.len = sizeof("Basic realm=\"\"") - 1 + alcf->realm.len; value2.data = ngx_pcalloc(r->pool, value2.len); if (NULL == value2.data) { return NGX_ERROR; } ngx_snprintf(value2.data, value2.len, "Basic realm=\"%V\"", &alcf->realm); r->headers_out.www_authenticate = ngx_list_push(&r->headers_out.headers); if (NULL == r->headers_out.www_authenticate) { return NGX_ERROR; } r->headers_out.www_authenticate->hash = 2; #if defined(nginx_version) && nginx_version >= 1023000 r->headers_out.www_authenticate->next = NULL; #endif r->headers_out.www_authenticate->key.len = sizeof("WWW-Authenticate") - 1; r->headers_out.www_authenticate->key.data = (u_char *)"WWW-Authenticate"; r->headers_out.www_authenticate->value.len = value2.len; r->headers_out.www_authenticate->value.data = value2.data; } ctx->head = 1; return NGX_OK; } static bool ngx_spnego_authorized_principal(ngx_http_request_t *r, ngx_str_t *princ, ngx_http_auth_spnego_loc_conf_t *alcf) { if (NGX_CONF_UNSET_PTR == alcf->auth_princs #if (NGX_PCRE) && NGX_CONF_UNSET_PTR == alcf->auth_princs_regex #endif ) { return true; } if (NGX_CONF_UNSET_PTR != alcf->auth_princs) { spnego_debug1("Testing against %d auth princs", alcf->auth_princs->nelts); ngx_str_t *auth_princs = alcf->auth_princs->elts; size_t i = 0; for (; i < alcf->auth_princs->nelts; ++i) { if (auth_princs[i].len != princ->len) { continue; } if (ngx_strncmp(auth_princs[i].data, princ->data, princ->len) == 0) { spnego_debug2("Authorized user %.*s", princ->len, princ->data); return true; } } } #if (NGX_PCRE) if (NGX_CONF_UNSET_PTR != alcf->auth_princs_regex) { spnego_debug1("Testing against %d auth princs regex", alcf->auth_princs_regex->nelts); if (ngx_regex_exec_array(alcf->auth_princs_regex, princ, r->connection->log) == NGX_OK) { return true; } } #endif return false; } ngx_int_t ngx_http_auth_spnego_token(ngx_http_request_t *r, ngx_http_auth_spnego_ctx_t *ctx) { ngx_str_t token; ngx_str_t decoded; size_t nego_sz = sizeof("Negotiate"); if (NULL == r->headers_in.authorization) { return NGX_DECLINED; } /* but don't decode second time? */ if (ctx->token.len) return NGX_OK; token = r->headers_in.authorization->value; if (token.len < nego_sz || ngx_strncasecmp(token.data, (u_char *)"Negotiate ", nego_sz) != 0) { if (ngx_strncasecmp(token.data, (u_char *)"NTLM", sizeof("NTLM")) == 0) { spnego_log_error("Detected unsupported mechanism: NTLM"); } return NGX_DECLINED; } token.len -= nego_sz; token.data += nego_sz; while (token.len && token.data[0] == ' ') { token.len--; token.data++; } if (token.len == 0) { return NGX_DECLINED; } decoded.len = ngx_base64_decoded_length(token.len); decoded.data = ngx_pnalloc(r->pool, decoded.len); if (NULL == decoded.data) { return NGX_ERROR; } if (ngx_decode_base64(&decoded, &token) != NGX_OK) { return NGX_DECLINED; } ctx->token.len = decoded.len; ctx->token.data = decoded.data; spnego_debug2("Token decoded: %*s", token.len, token.data); return NGX_OK; } static krb5_error_code ngx_http_auth_spnego_store_krb5_creds( ngx_http_request_t *r, krb5_context kcontext, krb5_principal principal, krb5_ccache ccache, krb5_creds creds) { krb5_error_code kerr = 0; if ((kerr = krb5_cc_initialize(kcontext, ccache, principal))) { spnego_log_error("Kerberos error: Cannot initialize ccache"); spnego_log_krb5_error(kcontext, kerr); return kerr; } if ((kerr = krb5_cc_store_cred(kcontext, ccache, &creds))) { spnego_log_error("Kerberos error: Cannot store credentials"); spnego_log_krb5_error(kcontext, kerr); return kerr; } return kerr; } static krb5_error_code ngx_http_auth_spnego_store_gss_creds( ngx_http_request_t *r, krb5_context kcontext, krb5_principal principal, krb5_ccache ccache, gss_cred_id_t creds) { OM_uint32 major_status, minor_status; krb5_error_code kerr = 0; if ((kerr = krb5_cc_initialize(kcontext, ccache, principal))) { spnego_log_error("Kerberos error: Cannot initialize ccache"); spnego_log_krb5_error(kcontext, kerr); return kerr; } major_status = gss_krb5_copy_ccache(&minor_status, creds, ccache); if (GSS_ERROR(major_status)) { const char *gss_error = get_gss_error(r->pool, minor_status, "ngx_http_auth_spnego_store_gss_creds() failed"); spnego_log_error("%s", gss_error); spnego_debug1("%s", gss_error); return KRB5_CC_WRITE; } return kerr; } static void ngx_http_auth_spnego_krb5_destroy_ccache(void *data) { krb5_context kcontext; krb5_ccache ccache; krb5_error_code kerr = 0; char *ccname = (char *)data; if ((kerr = krb5_init_context(&kcontext))) { goto done; } if ((kerr = krb5_cc_resolve(kcontext, ccname, &ccache))) { goto done; } krb5_cc_destroy(kcontext, ccache); done: if (kcontext) krb5_free_context(kcontext); } static char *ngx_http_auth_spnego_replace(ngx_http_request_t *r, char *str, char find, char replace) { char *result = (char *)ngx_palloc(r->pool, ngx_strlen(str) + 1); ngx_memcpy(result, str, ngx_strlen(str) + 1); char *index = NULL; while ((index = ngx_strchr(result, find)) != NULL) { *index = replace; } return result; } static ngx_int_t ngx_http_auth_spnego_store_delegated_creds(ngx_http_request_t *r, ngx_str_t *principal_name, creds_info delegated_creds) { krb5_context kcontext = NULL; krb5_principal principal = NULL; krb5_ccache ccache = NULL; krb5_error_code kerr = 0; char *ccname = NULL; char *escaped = NULL; if (!delegated_creds.data) { spnego_log_error( "ngx_http_auth_spnego_store_delegated_creds() NULL credentials"); spnego_debug0( "ngx_http_auth_spnego_store_delegated_creds() NULL credentials"); goto done; } if ((kerr = krb5_init_context(&kcontext))) { spnego_log_error("Kerberos error: Cannot initialize kerberos context"); spnego_log_krb5_error(kcontext, kerr); goto done; } if ((kerr = krb5_parse_name(kcontext, (char *)principal_name->data, &principal))) { spnego_log_error("Kerberos error: Cannot parse principal %s", principal_name); spnego_log_krb5_error(kcontext, kerr); goto done; } escaped = ngx_http_auth_spnego_replace(r, (char *)principal_name->data, '/', '_'); size_t ccname_size = (ngx_strlen("FILE:") + ngx_strlen(P_tmpdir) + ngx_strlen("/") + ngx_strlen(escaped)) + 1; ccname = (char *)ngx_pcalloc(r->pool, ccname_size); if (NULL == ccname) { return NGX_ERROR; } ngx_snprintf((u_char *)ccname, ccname_size, "FILE:%s/%*s", P_tmpdir, ngx_strlen(escaped), escaped); if ((kerr = krb5_cc_resolve(kcontext, ccname, &ccache))) { spnego_log_error("Kerberos error: Cannot resolve ccache %s", ccname); spnego_log_krb5_error(kcontext, kerr); goto done; } switch (delegated_creds.type) { case TYPE_GSS_CRED_ID_T: kerr = ngx_http_auth_spnego_store_gss_creds( r, kcontext, principal, ccache, (gss_cred_id_t)delegated_creds.data); break; case TYPE_KRB5_CREDS: kerr = ngx_http_auth_spnego_store_krb5_creds( r, kcontext, principal, ccache, (*(krb5_creds *)delegated_creds.data)); break; default: kerr = KRB5KRB_ERR_GENERIC; } if (kerr) goto done; ngx_str_t var_name = ngx_string(CCACHE_VARIABLE_NAME); ngx_str_t var_value = ngx_null_string; var_value.data = (u_char *)ccname; var_value.len = ngx_strlen(ccname); ngx_http_auth_spnego_set_variable(r, &var_name, &var_value); ngx_pool_cleanup_t *cln = ngx_pool_cleanup_add(r->pool, 0); if (NULL == cln) { return NGX_ERROR; } cln->handler = ngx_http_auth_spnego_krb5_destroy_ccache; cln->data = ccname; done: if (escaped) ngx_pfree(r->pool, escaped); if (ccname) ngx_pfree(r->pool, ccname); if (principal) krb5_free_principal(kcontext, principal); if (ccache) krb5_cc_close(kcontext, ccache); if (kcontext) krb5_free_context(kcontext); return kerr ? NGX_ERROR : NGX_OK; } ngx_int_t ngx_http_auth_spnego_basic(ngx_http_request_t *r, ngx_http_auth_spnego_ctx_t *ctx, ngx_http_auth_spnego_loc_conf_t *alcf) { ngx_str_t host_name; ngx_str_t service; ngx_str_t user; user.data = NULL; ngx_str_t new_user; ngx_int_t ret = NGX_DECLINED; krb5_context kcontext = NULL; krb5_error_code code; krb5_principal client = NULL; krb5_principal server = NULL; krb5_creds creds; krb5_get_init_creds_opt *gic_options = NULL; char *name = NULL; unsigned char *p = NULL; code = krb5_init_context(&kcontext); if (code) { spnego_debug0("Kerberos error: Cannot initialize kerberos context"); return NGX_ERROR; } host_name = r->headers_in.host->value; service.len = alcf->srvcname.len + alcf->realm.len + 3; if (ngx_strchr(alcf->srvcname.data, '/')) { service.data = ngx_palloc(r->pool, service.len); if (NULL == service.data) { spnego_error(NGX_ERROR); } ngx_snprintf(service.data, service.len, "%V@%V%Z", &alcf->srvcname, &alcf->realm); } else { service.len += host_name.len; service.data = ngx_palloc(r->pool, service.len); if (NULL == service.data) { spnego_error(NGX_ERROR); } ngx_snprintf(service.data, service.len, "%V/%V@%V%Z", &alcf->srvcname, &host_name, &alcf->realm); } code = krb5_parse_name(kcontext, (const char *)service.data, &server); if (code) { spnego_log_error("Kerberos error: Unable to parse service name"); spnego_log_krb5_error(kcontext, code); spnego_error(NGX_ERROR); } code = krb5_unparse_name(kcontext, server, &name); if (code) { spnego_log_error("Kerberos error: Cannot unparse servicename"); spnego_log_krb5_error(kcontext, code); spnego_error(NGX_ERROR); } free(name); name = NULL; p = ngx_strlchr(r->headers_in.user.data, r->headers_in.user.data + r->headers_in.user.len, '@'); user.len = r->headers_in.user.len + 1; if (NULL == p) { if (alcf->force_realm && alcf->realm.len && alcf->realm.data) { user.len += alcf->realm.len + 1; /* +1 for @ */ user.data = ngx_palloc(r->pool, user.len); if (NULL == user.data) { spnego_log_error("Not enough memory"); spnego_error(NGX_ERROR); } ngx_snprintf(user.data, user.len, "%V@%V%Z", &r->headers_in.user, &alcf->realm); } else { user.data = ngx_palloc(r->pool, user.len); if (NULL == user.data) { spnego_log_error("Not enough memory"); spnego_error(NGX_ERROR); } ngx_snprintf(user.data, user.len, "%V%Z", &r->headers_in.user); } } else { if (alcf->realm.len && alcf->realm.data && ngx_strncmp(p + 1, alcf->realm.data, alcf->realm.len) == 0) { user.data = ngx_palloc(r->pool, user.len); if (NULL == user.data) { spnego_log_error("Not enough memory"); spnego_error(NGX_ERROR); } ngx_snprintf(user.data, user.len, "%V%Z", &r->headers_in.user); if (alcf->fqun == 0) { /* * Specified realm is identical to configured realm. * Truncate $remote_user to strip @REALM. */ r->headers_in.user.len -= alcf->realm.len + 1; } } else if (alcf->force_realm) { *p = '\0'; user.len = ngx_strlen(r->headers_in.user.data) + 1; if (alcf->realm.len && alcf->realm.data) user.len += alcf->realm.len + 1; user.data = ngx_pcalloc(r->pool, user.len); if (NULL == user.data) { spnego_log_error("Not enough memory"); spnego_error(NGX_ERROR); } if (alcf->realm.len && alcf->realm.data) ngx_snprintf(user.data, user.len, "%s@%V%Z", r->headers_in.user.data, &alcf->realm); else ngx_snprintf(user.data, user.len, "%s%Z", r->headers_in.user.data); /* * Rewrite $remote_user with the forced realm. * If the forced realm is shorter than the * specified realm, we can reuse the original * buffer. */ if (r->headers_in.user.len >= user.len - 1) r->headers_in.user.len = user.len - 1; else { new_user.len = user.len - 1; new_user.data = ngx_palloc(r->pool, new_user.len); if (NULL == new_user.data) { spnego_log_error("Not enough memory"); spnego_error(NGX_ERROR); } ngx_pfree(r->pool, r->headers_in.user.data); r->headers_in.user.data = new_user.data; r->headers_in.user.len = new_user.len; } ngx_memcpy(r->headers_in.user.data, user.data, r->headers_in.user.len); } else { user.data = ngx_palloc(r->pool, user.len); if (NULL == user.data) { spnego_log_error("Not enough memory"); spnego_error(NGX_ERROR); } ngx_snprintf(user.data, user.len, "%V%Z", &r->headers_in.user); } } spnego_debug1("Attempting authentication with principal %s", (const char *)user.data); code = krb5_parse_name(kcontext, (const char *)user.data, &client); if (code) { spnego_log_error("Kerberos error: Unable to parse username"); spnego_debug1("username is %s.", (const char *)user.data); spnego_log_krb5_error(kcontext, code); spnego_error(NGX_ERROR); } memset(&creds, 0, sizeof(creds)); code = krb5_unparse_name(kcontext, client, &name); if (code) { spnego_log_error("Kerberos error: Cannot unparse username"); spnego_log_krb5_error(kcontext, code); spnego_error(NGX_ERROR); } krb5_get_init_creds_opt_alloc(kcontext, &gic_options); code = krb5_get_init_creds_password(kcontext, &creds, client, (char *)r->headers_in.passwd.data, NULL, NULL, 0, NULL, gic_options); if (code) { spnego_log_error("Kerberos error: Credentials failed"); spnego_log_krb5_error(kcontext, code); spnego_error(NGX_DECLINED); } if (alcf->delegate_credentials) { creds_info delegated_creds = {&creds, TYPE_KRB5_CREDS}; ngx_str_t principal_name = ngx_null_string; principal_name.data = (u_char *)name; principal_name.len = ngx_strlen(name); ngx_http_auth_spnego_store_delegated_creds(r, &principal_name, delegated_creds); } krb5_free_cred_contents(kcontext, &creds); /* Try to add the system realm to $remote_user if needed. */ if (alcf->fqun && !ngx_strlchr(r->headers_in.user.data, r->headers_in.user.data + r->headers_in.user.len, '@')) { #ifdef krb5_princ_realm /* * MIT does not have krb5_principal_get_realm() but its * krb5_princ_realm() is a macro that effectively points * to a char *. */ const char *realm = krb5_princ_realm(kcontext, client)->data; #else const char *realm = krb5_principal_get_realm(kcontext, client); #endif if (realm) { new_user.len = r->headers_in.user.len + 1 + ngx_strlen(realm); new_user.data = ngx_palloc(r->pool, new_user.len); if (NULL == new_user.data) { spnego_log_error("Not enough memory"); spnego_error(NGX_ERROR); } ngx_snprintf(new_user.data, new_user.len, "%V@%s", &r->headers_in.user, realm); ngx_pfree(r->pool, r->headers_in.user.data); r->headers_in.user.data = new_user.data; r->headers_in.user.len = new_user.len; } } spnego_debug1("Setting $remote_user to %V", &r->headers_in.user); if (ngx_http_auth_spnego_set_bogus_authorization(r) != NGX_OK) spnego_log_error("Failed to set $remote_user"); spnego_debug0("ngx_http_auth_spnego_basic: returning NGX_OK"); ret = NGX_OK; end: if (name) free(name); if (client) krb5_free_principal(kcontext, client); if (server) krb5_free_principal(kcontext, server); if (service.data) ngx_pfree(r->pool, service.data); if (user.data) ngx_pfree(r->pool, user.data); krb5_get_init_creds_opt_free(kcontext, gic_options); krb5_free_context(kcontext); return ret; } /* * Because 'remote_user' is assumed to be provided by basic authorization * (see ngx_http_variable_remote_user) we are forced to create bogus * non-Negotiate authorization header. This may possibly clobber Negotiate * token too soon. */ ngx_int_t ngx_http_auth_spnego_set_bogus_authorization(ngx_http_request_t *r) { const char *bogus_passwd = "bogus_auth_gss_passwd"; ngx_str_t plain, encoded, final; if (r->headers_in.user.len == 0) { spnego_debug0("ngx_http_auth_spnego_set_bogus_authorization: no user " "NGX_DECLINED"); return NGX_DECLINED; } /* +1 because of the ":" in "user:password" */ plain.len = r->headers_in.user.len + ngx_strlen(bogus_passwd) + 1; plain.data = ngx_pnalloc(r->pool, plain.len); if (NULL == plain.data) { return NGX_ERROR; } ngx_snprintf(plain.data, plain.len, "%V:%s", &r->headers_in.user, bogus_passwd); encoded.len = ngx_base64_encoded_length(plain.len); encoded.data = ngx_pnalloc(r->pool, encoded.len); if (NULL == encoded.data) { return NGX_ERROR; } ngx_encode_base64(&encoded, &plain); final.len = sizeof("Basic ") + encoded.len - 1; final.data = ngx_pnalloc(r->pool, final.len); if (NULL == final.data) { return NGX_ERROR; } ngx_snprintf(final.data, final.len, "Basic %V", &encoded); /* WARNING clobbering authorization header value */ r->headers_in.authorization->value.len = final.len; r->headers_in.authorization->value.data = final.data; spnego_debug0( "ngx_http_auth_spnego_set_bogus_authorization: bogus user set"); return NGX_OK; } static bool use_keytab(ngx_http_request_t *r, ngx_str_t *keytab) { size_t kt_sz = keytab->len + 1; char *kt = (char *)ngx_pcalloc(r->pool, kt_sz); if (NULL == kt) { return false; } ngx_snprintf((u_char *)kt, kt_sz, "%V%Z", keytab); OM_uint32 major_status, minor_status = 0; major_status = gsskrb5_register_acceptor_identity(kt); if (GSS_ERROR(major_status)) { spnego_log_error( "%s failed to register keytab", get_gss_error(r->pool, minor_status, "gsskrb5_register_acceptor_identity() failed")); return false; } spnego_debug1("Use keytab %V", keytab); return true; } static krb5_error_code ngx_http_auth_spnego_verify_server_credentials( ngx_http_request_t *r, krb5_context kcontext, ngx_str_t *principal_name, krb5_ccache ccache) { krb5_creds match_creds; krb5_creds creds; krb5_timestamp now; krb5_error_code kerr = 0; krb5_principal principal = NULL; char *tgs_principal_name = NULL; char *princ_name = NULL; memset(&match_creds, 0, sizeof(match_creds)); memset(&creds, 0, sizeof(creds)); if ((kerr = krb5_cc_get_principal(kcontext, ccache, &principal))) { spnego_log_error("Kerberos error: Cannot get principal from ccache"); spnego_log_krb5_error(kcontext, kerr); goto done; } if ((kerr = krb5_unparse_name(kcontext, principal, &princ_name))) { spnego_log_error("Kerberos error: Cannot unparse principal"); spnego_log_krb5_error(kcontext, kerr); goto done; } if (ngx_strncmp(principal_name->data, princ_name, ngx_strlen(princ_name)) != 0) { spnego_log_error("Kerberos error: Principal name mismatch"); spnego_debug0("Kerberos error: Principal name mismatch"); kerr = KRB5KRB_ERR_GENERIC; goto done; } size_t tgs_principal_name_size = (ngx_strlen(KRB5_TGS_NAME) + (krb5_realm_length(principal->realm) * 2) + 2) + 1; tgs_principal_name = (char *)ngx_pcalloc(r->pool, tgs_principal_name_size); ngx_snprintf((u_char *)tgs_principal_name, tgs_principal_name_size, "%s/%*s@%*s", KRB5_TGS_NAME, krb5_realm_length(principal->realm), krb5_realm_data(principal->realm), krb5_realm_length(principal->realm), krb5_realm_data(principal->realm)); if ((kerr = krb5_parse_name(kcontext, tgs_principal_name, &match_creds.server))) { spnego_log_error("Kerberos error: Cannot parse principal: %s", tgs_principal_name); spnego_log_krb5_error(kcontext, kerr); goto done; } match_creds.client = principal; if ((kerr = krb5_cc_retrieve_cred(kcontext, ccache, 0, &match_creds, &creds))) { spnego_log_error("Kerberos error: Cannot retrieve credentials"); spnego_log_krb5_error(kcontext, kerr); goto done; } if ((kerr = krb5_timeofday(kcontext, &now))) { spnego_log_error("Kerberos error: Could not get current time"); spnego_log_krb5_error(kcontext, kerr); goto done; } if ((now + RENEWAL_TIME) > creds.times.endtime) { spnego_debug2("Credentials for %s have expired or will expire soon at " "%d - renewing", princ_name, creds.times.endtime); kerr = KRB5KRB_AP_ERR_TKT_EXPIRED; } else { spnego_debug2("Credentials for %s will expire at %d", princ_name, creds.times.endtime); } done: if (principal) krb5_free_principal(kcontext, principal); if (match_creds.server) krb5_free_principal(kcontext, match_creds.server); if (creds.client) krb5_free_cred_contents(kcontext, &creds); return kerr; } static ngx_int_t ngx_http_auth_spnego_obtain_server_credentials( ngx_http_request_t *r, ngx_str_t *service_name, ngx_str_t *keytab_path, ngx_str_t *service_ccache) { krb5_context kcontext = NULL; krb5_keytab keytab = NULL; krb5_ccache ccache = NULL; krb5_error_code kerr = 0; krb5_principal principal = NULL; krb5_get_init_creds_opt gicopts; krb5_creds creds; #ifdef HEIMDAL_DEPRECATED // only used to call krb5_get_init_creds_opt_alloc() in newer heimdal krb5_get_init_creds_opt *gicopts_l; #endif char *principal_name = NULL; char *tgs_principal_name = NULL; char kt_path[1024]; char cc_name[1024]; memset(&creds, 0, sizeof(creds)); if ((kerr = krb5_init_context(&kcontext))) { spnego_log_error("Kerberos error: Cannot initialize kerberos context"); spnego_log_krb5_error(kcontext, kerr); goto done; } if (service_ccache->len && service_ccache->data) { ngx_snprintf((u_char *)cc_name, sizeof(cc_name), "FILE:%V%Z", service_ccache); if ((kerr = krb5_cc_resolve(kcontext, cc_name, &ccache))) { spnego_log_error("Kerberos error: Cannot resolve ccache %s", cc_name); spnego_log_krb5_error(kcontext, kerr); goto done; } } else { if ((kerr = krb5_cc_default(kcontext, &ccache))) { spnego_log_error("Kerberos error: Cannot get default ccache"); spnego_log_krb5_error(kcontext, kerr); goto done; } ngx_snprintf((u_char *)cc_name, sizeof(cc_name), "%s:%s", krb5_cc_get_type(kcontext, ccache), krb5_cc_get_name(kcontext, ccache)); } if ((kerr = ngx_http_auth_spnego_verify_server_credentials( r, kcontext, service_name, ccache))) { if (kerr == KRB5_FCC_NOFILE || kerr == KRB5KRB_AP_ERR_TKT_EXPIRED) { if ((kerr = krb5_parse_name(kcontext, (char *)service_name->data, &principal))) { spnego_log_error("Kerberos error: Cannot parse principal %s", (char *)service_name->data); spnego_log_krb5_error(kcontext, kerr); goto done; } if ((kerr = krb5_unparse_name(kcontext, principal, &principal_name))) { spnego_log_error("Kerberos error: Cannot unparse principal"); spnego_log_krb5_error(kcontext, kerr); goto done; } } else { spnego_log_error( "Kerberos error: Error verifying server credentials"); spnego_log_krb5_error(kcontext, kerr); goto done; } } else { spnego_debug0("Server credentials valid"); goto done; } ngx_slab_pool_t *shpool = (ngx_slab_pool_t *)shm_zone->shm.addr; ngx_shmtx_lock(&shpool->mutex); kerr = ngx_http_auth_spnego_verify_server_credentials(r, kcontext, service_name, ccache); if ((kerr != KRB5_FCC_NOFILE && kerr != KRB5KRB_AP_ERR_TKT_EXPIRED)) goto unlock; ngx_snprintf((u_char *)kt_path, sizeof(kt_path), "FILE:%V%Z", keytab_path); if ((kerr = krb5_kt_resolve(kcontext, kt_path, &keytab))) { spnego_log_error("Kerberos error: Cannot resolve keytab %s", kt_path); spnego_log_krb5_error(kcontext, kerr); goto unlock; } spnego_debug1("Obtaining new credentials for %s", principal_name); #ifndef HEIMDAL_DEPRECATED krb5_get_init_creds_opt_init(&gicopts); #else gicopts_l = &gicopts; krb5_get_init_creds_opt_alloc(kcontext, &gicopts_l); #endif krb5_get_init_creds_opt_set_forwardable(&gicopts, 1); size_t tgs_principal_name_size = (ngx_strlen(KRB5_TGS_NAME) + ((size_t)krb5_realm_length(principal->realm) * 2) + 2) + 1; tgs_principal_name = (char *)ngx_pcalloc(r->pool, tgs_principal_name_size); ngx_snprintf((u_char *)tgs_principal_name, tgs_principal_name_size, "%s/%*s@%*s", KRB5_TGS_NAME, krb5_realm_length(principal->realm), krb5_realm_data(principal->realm), krb5_realm_length(principal->realm), krb5_realm_data(principal->realm)); kerr = krb5_get_init_creds_keytab(kcontext, &creds, principal, keytab, 0, tgs_principal_name, &gicopts); if (kerr) { spnego_log_error( "Kerberos error: Cannot obtain credentials for principal %s", principal_name); spnego_log_krb5_error(kcontext, kerr); goto unlock; } if ((kerr = ngx_http_auth_spnego_store_krb5_creds(r, kcontext, principal, ccache, creds))) { spnego_debug0("ngx_http_auth_spnego_store_krb5_creds() failed"); goto unlock; } unlock: ngx_shmtx_unlock(&shpool->mutex); done: if (!kerr) { spnego_debug0("Successfully obtained server credentials"); setenv("KRB5CCNAME", cc_name, 1); } else { spnego_debug0("Failed to obtain server credentials"); } if (tgs_principal_name) ngx_pfree(r->pool, tgs_principal_name); if (creds.client) krb5_free_cred_contents(kcontext, &creds); if (keytab) krb5_kt_close(kcontext, keytab); if (ccache) krb5_cc_close(kcontext, ccache); if (kcontext) krb5_free_context(kcontext); return kerr ? NGX_ERROR : NGX_OK; } ngx_int_t ngx_http_auth_spnego_auth_user_gss(ngx_http_request_t *r, ngx_http_auth_spnego_ctx_t *ctx, ngx_http_auth_spnego_loc_conf_t *alcf) { ngx_int_t ret = NGX_DECLINED; u_char *pu; ngx_str_t spnego_token = ngx_null_string; OM_uint32 major_status, minor_status, minor_status2; gss_buffer_desc service = GSS_C_EMPTY_BUFFER; gss_name_t my_gss_name = GSS_C_NO_NAME; gss_cred_id_t my_gss_creds = GSS_C_NO_CREDENTIAL; gss_cred_id_t delegated_creds = GSS_C_NO_CREDENTIAL; gss_buffer_desc input_token = GSS_C_EMPTY_BUFFER; gss_ctx_id_t gss_context = GSS_C_NO_CONTEXT; gss_name_t client_name = GSS_C_NO_NAME; gss_buffer_desc output_token = GSS_C_EMPTY_BUFFER; if (NULL == ctx || ctx->token.len == 0) return ret; spnego_debug0("GSSAPI authorizing"); if (!use_keytab(r, &alcf->keytab)) { spnego_debug0("Failed to specify keytab"); spnego_error(NGX_ERROR); } if (alcf->srvcname.len > 0) { /* if there is a specific service prinicipal set in the configuration * file, we need to use it. Otherwise, use the default of no * credentials */ service.length = alcf->srvcname.len + alcf->realm.len + 2; service.value = ngx_palloc(r->pool, service.length); if (NULL == service.value) { spnego_error(NGX_ERROR); } ngx_snprintf(service.value, service.length, "%V@%V%Z", &alcf->srvcname, &alcf->realm); spnego_debug1("Using service principal: %s", service.value); major_status = gss_import_name(&minor_status, &service, (gss_OID)GSS_KRB5_NT_PRINCIPAL_NAME, &my_gss_name); if (GSS_ERROR(major_status)) { spnego_log_error("%s Used service principal: %s", get_gss_error(r->pool, minor_status, "gss_import_name() failed"), (u_char *)service.value); spnego_error(NGX_ERROR); } gss_buffer_desc human_readable_gss_name = GSS_C_EMPTY_BUFFER; major_status = gss_display_name(&minor_status, my_gss_name, &human_readable_gss_name, NULL); if (GSS_ERROR(major_status)) { spnego_log_error("%s Used service principal: %s ", get_gss_error(r->pool, minor_status, "gss_display_name() failed"), (u_char *)service.value); } spnego_debug1("my_gss_name %s", human_readable_gss_name.value); if (alcf->constrained_delegation) { ngx_str_t service_name = ngx_null_string; service_name.data = (u_char *)service.value; service_name.len = service.length; ngx_http_auth_spnego_obtain_server_credentials( r, &service_name, &alcf->keytab, &alcf->service_ccache); } /* Obtain credentials */ major_status = gss_acquire_cred( &minor_status, my_gss_name, GSS_C_INDEFINITE, GSS_C_NO_OID_SET, (alcf->constrained_delegation ? GSS_C_BOTH : GSS_C_ACCEPT), &my_gss_creds, NULL, NULL); if (GSS_ERROR(major_status)) { spnego_log_error("%s Used service principal: %s", get_gss_error(r->pool, minor_status, "gss_acquire_cred() failed"), (u_char *)service.value); spnego_error(NGX_ERROR); } } input_token.length = ctx->token.len; input_token.value = (void *)ctx->token.data; major_status = gss_accept_sec_context( &minor_status, &gss_context, my_gss_creds, &input_token, GSS_C_NO_CHANNEL_BINDINGS, &client_name, NULL, &output_token, NULL, NULL, &delegated_creds); if (GSS_ERROR(major_status)) { spnego_debug1("%s", get_gss_error(r->pool, minor_status, "gss_accept_sec_context() failed")); spnego_error(NGX_DECLINED); } if (major_status & GSS_S_CONTINUE_NEEDED) { spnego_debug0("only one authentication iteration allowed"); spnego_error(NGX_DECLINED); } if (output_token.length) { spnego_token.data = (u_char *)output_token.value; spnego_token.len = output_token.length; ctx->token_out_b64.len = ngx_base64_encoded_length(spnego_token.len); ctx->token_out_b64.data = ngx_pcalloc(r->pool, ctx->token_out_b64.len + 1); if (NULL == ctx->token_out_b64.data) { spnego_log_error("Not enough memory"); gss_release_buffer(&minor_status2, &output_token); spnego_error(NGX_ERROR); } ngx_encode_base64(&ctx->token_out_b64, &spnego_token); gss_release_buffer(&minor_status2, &output_token); } else { ctx->token_out_b64.len = 0; } /* getting user name at the other end of the request */ major_status = gss_display_name(&minor_status, client_name, &output_token, NULL); if (GSS_ERROR(major_status)) { spnego_log_error("%s", get_gss_error(r->pool, minor_status, "gss_display_name() failed")); spnego_error(NGX_ERROR); } if (output_token.length) { /* Apply local rules to map Kerberos Principals to short names */ if (alcf->map_to_local) { gss_OID mech_type = discard_const(gss_mech_krb5); output_token = (gss_buffer_desc)GSS_C_EMPTY_BUFFER; major_status = gss_localname(&minor_status, client_name, mech_type, &output_token); if (GSS_ERROR(major_status)) { spnego_log_error("%s", get_gss_error(r->pool, minor_status, "gss_localname() failed")); spnego_error(NGX_ERROR); } } /* TOFIX dirty quick trick for now (no "-1" i.e. include '\0' */ ngx_str_t user = {output_token.length, (u_char *)output_token.value}; r->headers_in.user.data = ngx_pstrdup(r->pool, &user); if (NULL == r->headers_in.user.data) { spnego_log_error("ngx_pstrdup failed to allocate"); spnego_error(NGX_ERROR); } r->headers_in.user.len = user.len; if (alcf->fqun == 0) { pu = ngx_strlchr(r->headers_in.user.data, r->headers_in.user.data + r->headers_in.user.len, '@'); if (pu != NULL && ngx_strncmp(pu + 1, alcf->realm.data, alcf->realm.len) == 0) { *pu = '\0'; r->headers_in.user.len = ngx_strlen(r->headers_in.user.data); } } /* this for the sake of ngx_http_variable_remote_user */ if (ngx_http_auth_spnego_set_bogus_authorization(r) != NGX_OK) { spnego_log_error("Failed to set remote_user"); } spnego_debug1("user is %V", &r->headers_in.user); } if (alcf->delegate_credentials) { creds_info creds = {delegated_creds, TYPE_GSS_CRED_ID_T}; ngx_str_t principal_name = ngx_null_string; principal_name.data = (u_char *)output_token.value; principal_name.len = output_token.length; ngx_http_auth_spnego_store_delegated_creds(r, &principal_name, creds); } gss_release_buffer(&minor_status, &output_token); ret = NGX_OK; goto end; end: if (output_token.length) gss_release_buffer(&minor_status, &output_token); if (client_name != GSS_C_NO_NAME) gss_release_name(&minor_status, &client_name); if (gss_context != GSS_C_NO_CONTEXT) gss_delete_sec_context(&minor_status, &gss_context, GSS_C_NO_BUFFER); if (my_gss_name != GSS_C_NO_NAME) gss_release_name(&minor_status, &my_gss_name); if (my_gss_creds != GSS_C_NO_CREDENTIAL) gss_release_cred(&minor_status, &my_gss_creds); if (delegated_creds != GSS_C_NO_CREDENTIAL) gss_release_cred(&minor_status, &delegated_creds); return ret; } static ngx_int_t ngx_http_auth_spnego_handler(ngx_http_request_t *r) { ngx_int_t ret = NGX_DECLINED; ngx_http_auth_spnego_ctx_t *ctx; ngx_http_auth_spnego_loc_conf_t *alcf; alcf = ngx_http_get_module_loc_conf(r, ngx_http_auth_spnego_module); if (alcf->protect == 0) { return NGX_DECLINED; } ctx = ngx_http_get_module_ctx(r, ngx_http_auth_spnego_module); if (NULL == ctx) { ctx = ngx_palloc(r->pool, sizeof(ngx_http_auth_spnego_ctx_t)); if (NULL == ctx) { return NGX_HTTP_INTERNAL_SERVER_ERROR; } ctx->token.len = 0; ctx->token.data = NULL; ctx->head = 0; ctx->ret = NGX_HTTP_UNAUTHORIZED; ngx_http_set_ctx(r, ctx, ngx_http_auth_spnego_module); } spnego_debug3("SSO auth handling IN: token.len=%d, head=%d, ret=%d", ctx->token.len, ctx->head, ctx->ret); if (ctx->token.len && ctx->head) { spnego_debug1("Found token and head, returning %d", ctx->ret); return ctx->ret; } if (NULL != r->headers_in.user.data) { spnego_debug0("User header set"); return NGX_OK; } spnego_debug0("Begin auth"); if (alcf->allow_basic) { spnego_debug0("Detect basic auth"); ret = ngx_http_auth_basic_user(r); if (NGX_OK == ret) { spnego_debug0("Basic auth credentials supplied by client"); /* If basic auth is enabled and basic creds are supplied * attempt basic auth. If we attempt basic auth, we do * not fall through to real SPNEGO */ if (NGX_OK != ngx_http_auth_spnego_basic(r, ctx, alcf)) { spnego_debug0("Basic auth failed"); if (NGX_ERROR == ngx_http_auth_spnego_headers_basic_only(r, ctx, alcf)) { spnego_debug0("Error setting headers"); return (ctx->ret = NGX_HTTP_INTERNAL_SERVER_ERROR); } return (ctx->ret = NGX_HTTP_UNAUTHORIZED); } if (!ngx_spnego_authorized_principal(r, &r->headers_in.user, alcf)) { spnego_debug0("User not authorized"); return (ctx->ret = NGX_HTTP_FORBIDDEN); } spnego_debug0("Basic auth succeeded"); return (ctx->ret = NGX_OK); } } /* Basic auth either disabled or not supplied by client */ spnego_debug0("Detect SPNEGO token"); ret = ngx_http_auth_spnego_token(r, ctx); if (NGX_OK == ret) { spnego_debug0("Client sent a reasonable Negotiate header"); ret = ngx_http_auth_spnego_auth_user_gss(r, ctx, alcf); if (NGX_ERROR == ret) { spnego_debug0("GSSAPI failed"); return (ctx->ret = NGX_HTTP_INTERNAL_SERVER_ERROR); } /* There are chances that client knows about Negotiate * but doesn't support GSSAPI. We could attempt to fall * back to basic here... */ if (NGX_DECLINED == ret) { spnego_debug0("GSSAPI failed"); if (!alcf->allow_basic) { return (ctx->ret = NGX_HTTP_FORBIDDEN); } if (NGX_ERROR == ngx_http_auth_spnego_headers_basic_only(r, ctx, alcf)) { spnego_debug0("Error setting headers"); return (ctx->ret = NGX_HTTP_INTERNAL_SERVER_ERROR); } return (ctx->ret = NGX_HTTP_UNAUTHORIZED); } if (!ngx_spnego_authorized_principal(r, &r->headers_in.user, alcf)) { spnego_debug0("User not authorized"); return (ctx->ret = NGX_HTTP_FORBIDDEN); } spnego_debug0("GSSAPI auth succeeded"); } ngx_str_t *token_out_b64 = NULL; switch (ret) { case NGX_DECLINED: /* DECLINED, but not yet FORBIDDEN */ ctx->ret = NGX_HTTP_UNAUTHORIZED; break; case NGX_OK: ctx->ret = NGX_OK; token_out_b64 = &ctx->token_out_b64; break; case NGX_ERROR: default: ctx->ret = NGX_HTTP_INTERNAL_SERVER_ERROR; break; } if (NGX_ERROR == ngx_http_auth_spnego_headers(r, ctx, token_out_b64, alcf)) { spnego_debug0("Error setting headers"); ctx->ret = NGX_HTTP_INTERNAL_SERVER_ERROR; } spnego_debug3("SSO auth handling OUT: token.len=%d, head=%d, ret=%d", ctx->token.len, ctx->head, ctx->ret); return ctx->ret; } spnego-http-auth-nginx-module-1.1.3/scripts/000077500000000000000000000000001501012364300210115ustar00rootroot00000000000000spnego-http-auth-nginx-module-1.1.3/scripts/kerberos_ldap000066400000000000000000000522211501012364300235520ustar00rootroot00000000000000#!/bin/sh # Test that regular authentication is working, and that constrained delegation # is working. In order for this to be doable, we set up a Kerberos KDC with # a LDAP-based backend (the LDAP DB driver has support for the necessesary # hooks that enable constrained delegation, the standard MIT DB2 backend does # not). Furthermore, we use a simple PHP script to demonstrate the delegation # by connecting to the LDAP server using the client principals passed in via # the HTTP connection. # # Note that this script is written to be usable both in autopkgtest and Github # workflows (which also implies that it needs to be compatible with Debian and # Ubuntu). # # Copyright (C) 2025 David Härdeman EX=0 CURL_OUTPUT="http_body" CURL_NONEGOTIATE="curl --max-time 60 --silent --fail-with-body -o ${CURL_OUTPUT}" CURL_NEGOTIATE="${CURL_NONEGOTIATE} --negotiate -u :" DOMAIN="example.com" TEST_HOST="server" TEST_HOST_FQDN="${TEST_HOST}.${DOMAIN}" LDAP_BASE_DN="$(echo "${DOMAIN}" | sed 's/\./,/;s/\([^,]\+\)/dc=\1/g')" LDAP_SERVICES_DN="ou=Services,${LDAP_BASE_DN}" LDAP_KRB_DN="ou=kerberos,${LDAP_SERVICES_DN}" LDAP_KRB_CONTAINER_DN="cn=krbContainer,${LDAP_KRB_DN}" LDAP_KDC_DN="uid=kdc,${LDAP_KRB_DN}" LDAP_KDC_PW="kdctest" LDAP_KADMIN_DN="uid=kadmin,${LDAP_KRB_DN}" LDAP_KADMIN_PW="kadmintest" LDAP_ADMIN_DN="cn=admin,${LDAP_BASE_DN}" LDAP_ADMIN_PW="test" KRB_BOB_PW="bob@BOB@123" KERBEROS_REALM="$(echo "${DOMAIN}" | tr "[:lower:]" "[:upper:]")" export LC_ALL=C export DEBIAN_FRONTEND=noninteractive die() { echo "FAILED" exit 1 } if [ -n "${AUTOPKGTEST_TMP}" ]; then cd "${AUTOPKGTEST_TMP}" || exit 1 elif [ -n "${GITHUB_WORKSPACE}" ]; then # Yeah, this env variable won't be passed on by sudo... cd "${GITHUB_WORKSPACE}" || exit 1 fi cat <> /etc/hosts || die if ! hostnamectl hostname "${TEST_HOST_FQDN}"; then if ! echo "${TEST_HOST_FQDN}" >> /etc/hostname; then echo "FAILED (but continuing anyway)" else echo "OK" fi else echo "OK" fi printf "Reconfiguring slapd ... " if ! debconf-set-selections < /dev/null 2>&1 || die echo "OK" printf "Verifying LDAP base DN ... " CFG_DOMAIN="$(ldapsearch -x -LLL -s base -b "" namingContexts | grep namingContexts | cut -d" " -f2)" if [ "${CFG_DOMAIN}" != "${LDAP_BASE_DN}" ]; then printf "%s != %s ... " "${CFG_DOMAIN}" "${LDAP_BASE_DN}" die fi echo "${CFG_DOMAIN} ... OK" printf "Enabling LDAP logging ... " if ! ldapmodify -Q -H ldapi:/// -Y EXTERNAL > /dev/null < /dev/null || die echo "OK" printf "Creating basic Kerberos LDAP structure ... " if ! ldapadd -x -D "${LDAP_ADMIN_DN}" -w "${LDAP_ADMIN_PW}" > /dev/null < /dev/null < /etc/krb5.conf [libdefaults] default_realm = ${KERBEROS_REALM} dns_lookup_realm = false dns_lookup_kdc = false ticket_lifetime = 24h forwardable = true proxiable = true rdns = false [realms] ${KERBEROS_REALM} = { kdc = ${TEST_HOST_FQDN} admin_server = ${TEST_HOST_FQDN} default_domain = ${DOMAIN} } EOF then die fi echo "OK" printf "Writing /etc/krb5kdc/kdc.conf ... " if ! cat < /etc/krb5kdc/kdc.conf [realms] ${KERBEROS_REALM} = { database_module = openldap_ldapconf } [dbmodules] openldap_ldapconf = { db_library = kldap ldap_kerberos_container_dn = ${LDAP_KRB_CONTAINER_DN} # if either of these is false, then the ldap_kdc_dn needs to # have write access as explained above disable_last_success = true disable_lockout = true ldap_conns_per_server = 5 ldap_servers = ldapi:/// # this object needs to have read rights on # the realm container, principal container and realm sub-trees ldap_kdc_dn = "${LDAP_KDC_DN}" # this object needs to have read and write rights on # the realm container, principal container and realm sub-trees ldap_kadmind_dn = "${LDAP_KADMIN_DN}" # this file will be used to store plaintext passwords used # to connect to the LDAP server ldap_service_password_file = /etc/krb5kdc/service.keyfile # OR, comment out ldap_kdc_dn, ldap_kadmind_dn and # ldap_service_password_file above and enable the following # two lines, if you skipped the step of creating entries/users # for the Kerberos servers #ldap_kdc_sasl_mech = EXTERNAL #ldap_kadmind_sasl_mech = EXTERNAL #ldap_servers = ldapi:/// } EOF then die fi echo "OK" printf "Writing /etc/krb5kdc/kadm5.acl ... " if ! cat < /etc/krb5kdc/kadm5.acl */admin@${KERBEROS_REALM} * EOF then die fi echo "OK" # This will create two new entries in the LDAP DIT: # ${LDAP_KRB_CONTAINER_DN} # cn=${KERBEROS_REALM},${LDAP_KRB_CONTAINER_DN} # e.g.: # cn=krbContainer,ou=kerberos,ou=Services,dc=example,dc=com # cn=EXAMPLE.COM,cn=krbContainer,ou=kerberos,ou=Services,dc=example,dc=com printf "Creating Kerberos realm %s ... " "${KERBEROS_REALM}" kdb5_ldap_util -D "${LDAP_ADMIN_DN}" -w "${LDAP_ADMIN_PW}" -P kdcmasterpw \ create -subtrees "${LDAP_BASE_DN}" -r "${KERBEROS_REALM}" -s -H ldapi:/// \ > /dev/null || die echo "OK" printf "Stashing KDC password ... " printf "%s\n%s\n" "${LDAP_KDC_PW}" "${LDAP_KDC_PW}" | kdb5_ldap_util \ -D "${LDAP_ADMIN_DN}" -w "${LDAP_ADMIN_PW}" \ stashsrvpw -f /etc/krb5kdc/service.keyfile \ "${LDAP_KDC_DN}" \ > /dev/null || die echo "OK" printf "Stashing kadmin password ... " printf "%s\n%s\n" "${LDAP_KADMIN_PW}" "${LDAP_KADMIN_PW}" | kdb5_ldap_util \ -D "${LDAP_ADMIN_DN}" -w "${LDAP_ADMIN_PW}" \ stashsrvpw -f /etc/krb5kdc/service.keyfile \ "${LDAP_KADMIN_DN}" \ > /dev/null || die echo "OK" printf "Restarting KDC ... " invoke-rc.d krb5-kdc restart || die echo "OK" printf "Restarting kadmind ... " invoke-rc.d krb5-admin-server restart || die echo "OK" printf "Creating default Kerberos password policy ... " kadmin.local -q "addpol -minlength 1 defaultpol" > /dev/null || die echo "OK" printf "Creating test user principals ... " kadmin.local -q "addprinc -randkey -policy defaultpol alice" > /dev/null || die kadmin.local -q "ktadd -k krb5.alice.keytab alice" > /dev/null || die kadmin.local -q "addprinc -pw ${KRB_BOB_PW} -policy defaultpol bob" > /dev/null || die kadmin.local -q "addprinc -randkey -policy defaultpol mallory" > /dev/null || die kadmin.local -q "ktadd -k krb5.mallory.keytab mallory" > /dev/null || die echo "OK" printf "Creating LDAP server principal ... " kadmin.local -q "addprinc -randkey -policy defaultpol ldap/${TEST_HOST_FQDN}" > /dev/null || die kadmin.local -q "ktadd -k /etc/krb5.ldap.keytab ldap/${TEST_HOST_FQDN}" > /dev/null || die chown root:openldap /etc/krb5.ldap.keytab || die chmod 0640 /etc/krb5.ldap.keytab || die sed -i '/KRB5_KTNAME=/d' /etc/default/slapd || die # sysv init echo "export KRB5_KTNAME=/etc/krb5.ldap.keytab" >> /etc/default/slapd # systemd echo "KRB5_KTNAME=/etc/krb5.ldap.keytab" >> /etc/default/slapd echo "OK" printf "Updating apparmor profile for slapd ... " if [ -e /etc/apparmor.d/usr.sbin.slapd ]; then mkdir -p /etc/apparmor.d/local echo "/etc/krb5.ldap.keytab kr," >> /etc/apparmor.d/local/usr.sbin.slapd apparmor_parser -r /etc/apparmor.d/usr.sbin.slapd fi echo "OK" printf "Restarting slapd ... " invoke-rc.d slapd restart || die echo "OK" printf "Creating HTTP server principal ... " kadmin.local -q "addprinc -randkey -policy defaultpol HTTP/${TEST_HOST_FQDN}" > /dev/null || die kadmin.local -q "modprinc +ok_as_delegate HTTP/${TEST_HOST_FQDN}" > /dev/null || die kadmin.local -q "modprinc +ok_to_auth_as_delegate HTTP/${TEST_HOST_FQDN}" > /dev/null || die kadmin.local -q "ktadd -k /etc/krb5.http.keytab HTTP/${TEST_HOST_FQDN}" > /dev/null || die chown root:www-data /etc/krb5.http.keytab || die chmod 0640 /etc/krb5.http.keytab || die echo "OK" printf "Setting delegation permissions via LDAP ... " if ! ldapmodify -x -D "${LDAP_ADMIN_DN}" -w "${LDAP_ADMIN_PW}" > /dev/null < /etc/nginx/sites-available/kerberos # SPNEGO/Kerberos server test configuration # server { listen 8080; listen [::]:8080; root /var/www/kerberos; index index.php; server_name ${TEST_HOST_FQDN}; location /noauth.php { include snippets/fastcgi-php.conf; fastcgi_pass unix:/run/php/php-fpm.sock; auth_gss off; } location /auth.php { include snippets/fastcgi-php.conf; fastcgi_pass unix:/run/php/php-fpm.sock; auth_gss on; auth_gss_realm ${KERBEROS_REALM}; auth_gss_keytab /etc/krb5.http.keytab; auth_gss_service_name HTTP/${TEST_HOST_FQDN}; auth_gss_allow_basic_fallback off; auth_gss_authorized_principal alice@${KERBEROS_REALM}; auth_gss_format_full on; fastcgi_param HTTP_AUTHORIZATION ""; fastcgi_param KRB5CCNAME \$krb5_cc_name; auth_gss_service_ccache /tmp/krb5cc_nginx; } location /fallback.php { include snippets/fastcgi-php.conf; fastcgi_pass unix:/run/php/php-fpm.sock; auth_gss on; auth_gss_realm ${KERBEROS_REALM}; auth_gss_keytab /etc/krb5.http.keytab; auth_gss_service_name HTTP/${TEST_HOST_FQDN}; auth_gss_allow_basic_fallback on; auth_gss_authorized_principal bob@${KERBEROS_REALM}; auth_gss_format_full on; fastcgi_param HTTP_AUTHORIZATION ""; fastcgi_param KRB5CCNAME \$krb5_cc_name; auth_gss_service_ccache /tmp/krb5cc_nginx; } location /delegate.php { include snippets/fastcgi-php.conf; fastcgi_pass unix:/run/php/php-fpm.sock; auth_gss on; auth_gss_realm ${KERBEROS_REALM}; auth_gss_keytab /etc/krb5.http.keytab; auth_gss_service_name HTTP/${TEST_HOST_FQDN}; auth_gss_allow_basic_fallback off; auth_gss_authorized_principal alice@${KERBEROS_REALM}; auth_gss_format_full on; fastcgi_param HTTP_AUTHORIZATION ""; fastcgi_param KRB5CCNAME \$krb5_cc_name; auth_gss_service_ccache /tmp/krb5cc_nginx; auth_gss_delegate_credentials on; auth_gss_constrained_delegation on; } } EOF then die fi ln -s /etc/nginx/sites-available/kerberos /etc/nginx/sites-enabled/ || die mkdir -p /var/www/kerberos || die echo "OK" printf "Writing noauth.php ... " if ! cat <<'EOF' > /var/www/kerberos/noauth.php EOF then die fi echo "OK" printf "Writing auth.php ... " if ! cat <<'EOF' > /var/www/kerberos/auth.php EOF then die fi echo "OK" printf "Writing fallback.php ... " if ! cat <<'EOF' > /var/www/kerberos/fallback.php EOF then die fi echo "OK" printf "Writing delegate.php ... " if ! cat < /var/www/kerberos/delegate.php EOF then die fi echo "OK" # For example, if php-fpm was already running when libsasl2-modules-gssapi-mit # was installed, it won't pick up the new GSSAPI capabilities until it has been # restarted...so let's restart all services that might use SASL/GSSAPI in our # tests. printf "Restarting nginx and PHP-FPM ... " systemctl restart nginx systemctl restart "php*-fpm.service" sleep 5 echo "OK" echo "" echo "=== Setup complete, start tests ===" echo "" test_path() { SUBURL="$1" EXPECT1="$2" EXPECT2="$3" printf "curl %s, no negotiate: http status (expect %s)=" "${SUBURL}" "${EXPECT1}" rm -f "${CURL_OUTPUT}" CODE="$($CURL_NONEGOTIATE -w "%{http_code}" "http://${TEST_HOST_FQDN}:8080/${SUBURL}")" || true printf "%s ... " "${CODE}" if [ "$CODE" = "${EXPECT1}" ]; then echo "OK" else EX=1 echo "FAILED" if [ -e "${CURL_OUTPUT}" ]; then echo "HTTP body:" cat "${CURL_OUTPUT}" echo "" fi fi printf "curl %s, negotiate: http status (expect %s)=" "${SUBURL}" "${EXPECT2}" rm -f "${CURL_OUTPUT}" CODE="$($CURL_NEGOTIATE -w "%{http_code}" "http://${TEST_HOST_FQDN}:8080/${SUBURL}")" || true printf "%s ... " "${CODE}" if [ "$CODE" = "${EXPECT2}" ]; then echo "OK" else EX=1 echo "FAILED" if [ -e "${CURL_OUTPUT}" ]; then echo "HTTP body:" cat "${CURL_OUTPUT}" echo "" fi fi } test_basic() { SUBURL="$1" EXPECT1="$2" EXPECT2="$3" printf "curl %s, incorrect basic auth: http status (expect %s)=" "${SUBURL}" "${EXPECT1}" rm -f "${CURL_OUTPUT}" CODE="$($CURL_NONEGOTIATE -u "bob:InVaLiD" -w "%{http_code}" "http://${TEST_HOST_FQDN}:8080/${SUBURL}")" || true printf "%s ... " "${CODE}" if [ "$CODE" = "${EXPECT1}" ]; then echo "OK" else EX=1 echo "FAILED" if [ -e "${CURL_OUTPUT}" ]; then echo "HTTP body:" cat "${CURL_OUTPUT}" echo "" fi fi printf "curl %s, basic auth: http status (expect %s)=" "${SUBURL}" "${EXPECT2}" rm -f "${CURL_OUTPUT}" CODE="$($CURL_NONEGOTIATE -u "bob:${KRB_BOB_PW}" -w "%{http_code}" "http://${TEST_HOST_FQDN}:8080/${SUBURL}")" || true printf "%s ... " "${CODE}" if [ "$CODE" = "${EXPECT2}" ]; then echo "OK" else EX=1 echo "FAILED" if [ -e "${CURL_OUTPUT}" ]; then echo "HTTP body:" cat "${CURL_OUTPUT}" echo "" fi fi } test_ldapwhoami() { LDAP_EXPECTED="dn:uid=${1},cn=gss-spnego,cn=auth" printf "Result of ldapwhoami via delegation ... " if [ -e "${CURL_OUTPUT}" ]; then LDAP_WHOAMI="$(cat "${CURL_OUTPUT}")" if [ "${LDAP_WHOAMI}" != "${LDAP_EXPECTED}" ]; then printf "%s != %s ... " "${LDAP_WHOAMI}" "${LDAP_EXPECTED}" EX=1 echo "FAILED" else printf "%s ... " "${LDAP_WHOAMI}" echo "OK" fi else EX=1 echo "FAILED" fi } printf "Destroying Kerberos tickets ... " kdestroy -q > /dev/null 2>&1 || true echo "OK" test_basic "fallback.php" 401 200 test_path "fallback.php" 401 401 test_path "noauth.php" 200 200 test_path "auth.php" 401 401 test_path "delegate.php" 401 401 echo "" printf "Obtaining Kerberos ticket for alice ... " if kinit -kt krb5.alice.keytab alice; then echo "OK" else EX=1 echo "FAILED" fi test_basic "fallback.php" 401 200 test_path "fallback.php" 401 403 test_path "noauth.php" 200 200 test_path "auth.php" 401 200 test_path "delegate.php" 401 200 test_ldapwhoami "alice" echo "" printf "Obtaining Kerberos ticket for mallory ... " kdestroy -q > /dev/null 2>&1 || true if kinit -kt krb5.mallory.keytab mallory; then echo "OK" else EX=1 echo "FAILED" fi test_basic "fallback.php" 401 200 test_path "fallback.php" 401 403 test_path "noauth.php" 200 200 test_path "auth.php" 401 403 test_path "delegate.php" 401 403 echo "" printf "Obtaining Kerberos ticket for bob ... " kdestroy -q > /dev/null 2>&1 || true if echo "${KRB_BOB_PW}" | kinit bob > /dev/null 2>&1; then echo "OK" else EX=1 echo "FAILED" fi test_basic "fallback.php" 401 200 test_path "fallback.php" 401 200 test_path "noauth.php" 200 200 test_path "auth.php" 401 403 test_path "delegate.php" 401 403 echo "" printf "Removing delegation permissions via LDAP ... " if ! ldapmodify -x -D "${LDAP_ADMIN_DN}" -w "${LDAP_ADMIN_PW}" > /dev/null < /dev/null 2>&1 || true echo "OK" test_path "delegate.php" 401 401 echo "" printf "Obtaining Kerberos ticket for alice ... " if kinit -kt krb5.alice.keytab alice; then echo "OK" else EX=1 echo "FAILED" fi test_path "delegate.php" 401 500 echo "" printf "Obtaining Kerberos ticket for mallory ... " kdestroy -q > /dev/null 2>&1 || true if kinit -kt krb5.mallory.keytab mallory; then echo "OK" else EX=1 echo "FAILED" fi test_path "delegate.php" 401 403 echo "" printf "Re-adding delegation permissions via LDAP ... " if ! ldapmodify -x -D "${LDAP_ADMIN_DN}" -w "${LDAP_ADMIN_PW}" > /dev/null < /dev/null 2>&1 || true if kinit -kt krb5.alice.keytab alice; then echo "OK" else EX=1 echo "FAILED" fi test_path "delegate.php" 401 200 test_ldapwhoami "alice" echo "" if [ "${EX}" -ne 0 ]; then echo "=== journalctl nginx ===" journalctl -n all -xu nginx.service || true echo "=== /etc/nginx/sites-available/kerberos ===" cat /etc/nginx/sites-available/kerberos echo "=== error.log ===" cat /var/log/nginx/error.log echo "=== access.log ===" cat /var/log/nginx/access.log echo "=== journalctl slapd ===" journalctl -n all -xu slapd.service || true echo "=== slapcat ===" slapcat echo "=== ldapwhoami ===" ldapwhoami -Y GSSAPI -v -H "ldap://${TEST_HOST_FQDN}/" echo "=== klist ===" klist echo "=== /etc/krb* ===" ls -al /etc/krb* fi exit ${EX}