pax_global_header00006660000000000000000000000064152125672770014530gustar00rootroot0000000000000052 comment=da2d99f47a593dab9f5ab34053ce8112c08fa30b TeslaGov-ngx-http-auth-jwt-module-da2d99f/000077500000000000000000000000001521256727700205535ustar00rootroot00000000000000TeslaGov-ngx-http-auth-jwt-module-da2d99f/.bin/000077500000000000000000000000001521256727700214015ustar00rootroot00000000000000TeslaGov-ngx-http-auth-jwt-module-da2d99f/.bin/git/000077500000000000000000000000001521256727700221645ustar00rootroot00000000000000TeslaGov-ngx-http-auth-jwt-module-da2d99f/.bin/git/hooks-wrapper000077500000000000000000000024161521256727700247160ustar00rootroot00000000000000#!/usr/bin/env bash # Runs all executable pre-commit-* hooks and exits after, # if any of them was not successful. # # Based on # https://github.com/ELLIOTTCABLE/Paws.js/blob/Master/Scripts/git-hooks/chain-hooks.sh # http://osdir.com/ml/git/2009-01/msg00308.html # # assumes your scripts are located at /bin/git/hooks exitcodes=() hookname=`basename $0` # our special hooks folder CUSTOM_HOOKS_DIR=$(git rev-parse --show-toplevel)/bin/git/hooks # find gits native hooks folder NATIVE_HOOKS_DIR=$(git rev-parse --show-toplevel)/.git/hooks # Run each hook, passing through STDIN and storing the exit code. # We don't want to bail at the first failure, as the user might # then bypass the hooks without knowing about additional issues. for hook in ${CUSTOM_HOOKS_DIR}/$(basename $0)-*; do test -x "$hook" || continue echo "Running custom hook '$hookname' ..." out=`$hook "$@"` exitcodes+=($?) echo "$out" done # check if there was a local hook that was moved previously if [ -f "${NATIVE_HOOKS_DIR}/$hookname.local" ]; then echo "Running native hook '$hookname' ..." out=`${NATIVE_HOOKS_DIR}/$hookname.local "$@"` exitcodes+=($?) echo "$out" fi # If any exit code isn't 0, bail. for i in "${exitcodes[@]}"; do [ "$i" == 0 ] || exit $i doneTeslaGov-ngx-http-auth-jwt-module-da2d99f/.bin/git/hooks/000077500000000000000000000000001521256727700233075ustar00rootroot00000000000000TeslaGov-ngx-http-auth-jwt-module-da2d99f/.bin/git/hooks/pre-push-build-and-test000077500000000000000000000006721521256727700276170ustar00rootroot00000000000000#!/usr/bin/env bash REPO_ROOT_DIR=$(git rev-parse --show-toplevel) CHANGE_COUNT=$(cd ${REPO_ROOT_DIR}; git diff --name-only origin/HEAD..HEAD -- resources/ src/ test/ Dockerfile scripts.sh |wc -l) if [[ "0" -ne "${CHANGE_COUNT}" ]]; then (cd ${REPO_ROOT_DIR}; ./scripts.sh rebuild_nginx rebuild_test test) else HOOK_NAME=$(basename $0) echo "Skipping hook '${HOOK_NAME}' -- no changes detected which would require tests to be run." fi TeslaGov-ngx-http-auth-jwt-module-da2d99f/.bin/git/init-hooks000077500000000000000000000020471521256727700242010ustar00rootroot00000000000000#!/usr/bin/env bash # based on http://stackoverflow.com/a/3464399/1383268 # assumes that the hooks-wrapper script is located at /bin/git/hooks-wrapper HOOK_NAMES="applypatch-msg pre-applypatch post-applypatch pre-commit prepare-commit-msg commit-msg post-commit pre-rebase post-checkout post-merge pre-receive update post-receive post-update pre-auto-gc pre-push" # find git's native hooks folder REPO_ROOT_DIR=$(git rev-parse --show-toplevel) HOOKS_DIR=$(git rev-parse --show-toplevel)/.git/hooks for hook in ${HOOK_NAMES}; do # If the hook already exists, is a file, and is not a symlink if [ ! -h ${HOOKS_DIR}/${hook} ] && [ -f ${HOOKS_DIR}/${hook} ]; then mv ${HOOKS_DIR}/${hook} ${HOOKS_DIR}/${hook}.local fi # create the symlink, overwriting the file if it exists # probably the only way this would happen is if you're using an old version of git # -- back when the sample hooks were not executable, instead of being named ____.sample ln -s -f ${REPO_ROOT_DIR}/bin/git/hooks-wrapper ${HOOKS_DIR}/${hook} doneTeslaGov-ngx-http-auth-jwt-module-da2d99f/.bin/init000077500000000000000000000000701521256727700222670ustar00rootroot00000000000000#!/usr/bin/env bash source $(dirname $0)/git/init-hooksTeslaGov-ngx-http-auth-jwt-module-da2d99f/.env000066400000000000000000000000431521256727700213410ustar00rootroot00000000000000COMPOSE_PROJECT_NAME=jwt-nginx-testTeslaGov-ngx-http-auth-jwt-module-da2d99f/.github/000077500000000000000000000000001521256727700221135ustar00rootroot00000000000000TeslaGov-ngx-http-auth-jwt-module-da2d99f/.github/workflows/000077500000000000000000000000001521256727700241505ustar00rootroot00000000000000TeslaGov-ngx-http-auth-jwt-module-da2d99f/.github/workflows/make-release.yml000066400000000000000000000060031521256727700272250ustar00rootroot00000000000000name: Make Release on: workflow_dispatch: jobs: meta: name: Get Metadata runs-on: ubuntu-latest outputs: tag: ${{steps.meta.outputs.tag}} steps: - name: Checkout Code uses: actions/checkout@v6 with: fetch-depth: 0 - name: Get Metadata id: meta run: | set -eu tag=$(git describe --tags --abbrev=0) echo "tag=${tag}" >> $GITHUB_OUTPUT build: name: "NGINX: ${{ matrix.nginx-version }}; libjwt: ${{ matrix.libjwt-version }}" needs: meta runs-on: ubuntu-latest strategy: matrix: nginx-version: - 1.28.3 # legacy - 1.30.2 # stable - 1.31.1 # mainline libjwt-version: - 1.10.2 # Debian - 1.17.2 # Ubuntu - 1.12.1 # UBI8 - 1.18.4 # Latest libjwt release. env: MODULE_VERSION: ${{needs.meta.outputs.tag}} NGINX_VERSION: ${{ matrix.nginx-version }} LIBJWT_VERSION: ${{ matrix.libjwt-version }} steps: - name: Checkout Code uses: actions/checkout@v6 with: fetch-depth: 0 - name: Build Module run: ./scripts build_module - name: Copy Binaries run: ./scripts cp_bin - name: Get Metadata id: meta run: | set -eu artifact="ngx-http-auth-jwt-module-${{needs.meta.outputs.tag}}_libjwt-${{matrix.libjwt-version}}_nginx-${{matrix.nginx-version}}" echo "artifact=${artifact}" >> $GITHUB_OUTPUT echo "filename=${artifact}.tgz" >> $GITHUB_OUTPUT - name: Create Release Archive run: tar czf ${{steps.meta.outputs.filename}} -C bin/usr/lib/nginx/modules ngx_http_auth_jwt_module_${{needs.meta.outputs.tag}}.so - name: Upload Build Artifact uses: actions/upload-artifact@v7 with: if-no-files-found: error name: ${{steps.meta.outputs.artifact}} path: ${{steps.meta.outputs.filename}} release: name: Create/Update Release needs: [meta, build] runs-on: ubuntu-latest permissions: contents: write steps: - name: Set-up Variables id: vars run: | echo "date_now=$(date --rfc-3339=seconds)" >> "${GITHUB_OUTPUT}" - name: Download Artifacts uses: actions/download-artifact@v8 with: path: artifacts - name: Create/Update Release uses: ncipollo/release-action@v1 with: tag: ${{needs.meta.outputs.tag}} name: "Pre-release: ${{needs.meta.outputs.tag}}" body: | > [!WARNING] > This is an automatically generated pre-release version of the module, which includes the latest master branch changes. > Please report any bugs you find. - Build Date: `${{ steps.vars.outputs.date_now }}` - Commit: `${{ github.sha }}` prerelease: true allowUpdates: true removeArtifacts: true artifactErrorsFailBuild: true artifacts: artifacts/**/* TeslaGov-ngx-http-auth-jwt-module-da2d99f/.github/workflows/test.yml000066400000000000000000000014311521256727700256510ustar00rootroot00000000000000name: Test on: workflow_dispatch: pull_request: types: [opened, synchronize, reopened] jobs: test: name: "NGINX: ${{ matrix.nginx-version }}; libjwt: ${{ matrix.libjwt-version }}" runs-on: ubuntu-latest strategy: matrix: nginx-version: - 1.28.3 # legacy - 1.30.2 # stable - 1.31.1 # mainline libjwt-version: - 1.10.2 # Debian - 1.17.2 # Ubuntu - 1.12.1 # UBI8 - 1.18.4 # Latest libjwt release steps: - name: Checkout Code uses: actions/checkout@v6 with: fetch-depth: 0 - name: Test run: | export NGINX_VERSION=${{ matrix.nginx-version }} export LIBJWT_VERSION=${{ matrix.libjwt-version }} ./scripts test TeslaGov-ngx-http-auth-jwt-module-da2d99f/.gitignore000066400000000000000000000000311521256727700225350ustar00rootroot00000000000000.idea .vscode bin releaseTeslaGov-ngx-http-auth-jwt-module-da2d99f/LICENSE000066400000000000000000000020611521256727700215570ustar00rootroot00000000000000MIT License Copyright (c) 2018 Tesla Government Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. TeslaGov-ngx-http-auth-jwt-module-da2d99f/README.md000066400000000000000000000377151521256727700220470ustar00rootroot00000000000000# Auth-JWT NGINX Module This is an NGINX module to check for a valid JWT and proxy to an upstream server or redirect to a login page. It supports additional features such as extracting claims from the JWT and placing them on the request/response headers. ## Breaking Changes with v2 The `v2` branch, which has now been merged to `master` includes breaking changes. Please see the initial v2 release for details, ## Dependencies This module depends on the [JWT C Library](https://github.com/benmcollins/libjwt). Transitively, that library depends on a JSON Parser called [Jansson](https://github.com/akheron/jansson) as well as the OpenSSL library. ## Directives This module requires several new `nginx.conf` directives, which can be specified at the `http`, `server`, or `location` levels. See the [example NGINX config file](examples/nginx.conf) for more info. | Directive | Description | | ------------------------------------ | ---------------------------------------------------------------------------------------------------------------------------------------------------------- | | `auth_jwt_key` | The key to use to decode/verify the JWT, *in binhex format* -- see below. | | `auth_jwt_redirect` | Set to "on" to redirect to `auth_jwt_loginurl` if authentication fails. | | `auth_jwt_loginurl` | The URL to redirect to if `auth_jwt_redirect` is enabled and authentication fails. | | `auth_jwt_enabled` | Set to "on" to enable JWT checking. | | `auth_jwt_algorithm` | The algorithm to use. One of: HS256, HS384, HS512, RS256, RS384, RS512 | | `auth_jwt_location` | Indicates where the JWT is located in the request -- see below. | | `auth_jwt_validate_sub` | Set to "on" to validate the `sub` claim (e.g. user id) in the JWT. | | `auth_jwt_extract_var_claims` | Set to a space-delimited list of claims to extract from the JWT and make available as NGINX variables. These will be accessible via e.g: `$jwt_claim_sub` | | `auth_jwt_extract_request_claims` | Set to a space-delimited list of claims to extract from the JWT and set as request headers. These will be accessible via e.g: `$http_jwt_sub` | | `auth_jwt_extract_response_claims` | Set to a space-delimited list of claims to extract from the JWT and set as response headers. These will be accessible via e.g: `$sent_http_jwt_sub` | | `auth_jwt_use_keyfile` | Set to "on" to read the key from a file rather than from the `auth_jwt_key` directive. | | `auth_jwt_keyfile_path` | Set to the path from which the key should be read when `auth_jwt_use_keyfile` is enabled. | ## Algorithms The default algorithm is `HS256`, for symmetric key validation. When using one of the `HS*` algorithms, the value for `auth_jwt_key` should be specified in binhex format. It is recommended to use at least 256 bits of data (32 pairs of hex characters or 64 characters in total). Note that using more than 512 bits will not increase the security. For key guidelines please see [NIST Special Publication 800-107 Recommendation for Applications Using Approved Hash Algorithms](https://csrc.nist.gov/publications/detail/sp/800-107/rev-1/final), Section 5.3.2 The HMAC Key. To generate a 256-bit key (32 pairs of hex characters; 64 characters in total): ```bash openssl rand -hex 32 ``` ### Additional Supported Algorithms The configuration also supports RSA public key validation via (e.g.) `auth_jwt_algorithm RS256`. When using the `RS*` alhorithms, the `auth_jwt_key` field must be set to your public key **OR** `auth_jwt_use_keyfile` should be set to `on` and `auth_jwt_keyfile_path` should point to the public key on disk. NGINX won't start if `auth_jwt_use_keyfile` is set to `on` and a key file is not provided. When using an `RS*` algorithm with an inline key, be sure to set `auth_jwt_key` to the _public key_, rather than a PEM certificate. E.g.: ```nginx auth_jwt_key "-----BEGIN PUBLIC KEY----- MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA0aPPpS7ufs0bGbW9+OFQ RvJwb58fhi2BuHMd7Ys6m8D1jHW/AhDYrYVZtUnA60lxwSJ/ZKreYOQMlNyZfdqA rhYyyUkedDn8e0WsDvH+ocY0cMcxCCN5jItCwhIbIkTO6WEGrDgWTY57UfWDqbMZ 4lMn42f77OKFoxsOA6CVvpsvrprBPIRPa25H2bJHODHEtDr/H519Y681/eCyeQE/ 1ibKL2cMN49O7nRAAaUNoFcO89Uc+GKofcad1TTwtTIwmSMbCLVkzGeExBCrBTQo wO6AxLijfWV/JnVxNMUiobiKGc/PP6T5PI70Uv67Y4FzzWTuhqmREb3/BlcbPwtM oQIDAQAB -----END PUBLIC KEY-----"; ``` When using an `RS*` algorithm with a public key file, do as follows: ```nginx auth_jwt_use_keyfile on; auth_jwt_keyfile_path "/path/to/pub_key.pem"; ``` A typical use case would be to specify the key and login URL at the `http` level, and then only turn JWT authentication on for the locations which you want to secure (or vice-versa). Unauthorized requests will result in a `302 Moved Temporarily` response with the `Location` header set to the URL specified in the `auth_jwt_loginurl` directive, and a querystring parameter `return_url` whose value is the current / attempted URL. If you prefer to return `401 Unauthorized` rather than redirect, you may turn `auth_jwt_redirect` off: ```nginx auth_jwt_redirect off; ``` ## JWT Locations By default, the`Authorization` header is used to provide a JWT for validation. However, you may use the `auth_jwt_location` directive to specify the name of the header or cookie which provides the JWT: ```nginx auth_jwt_location HEADER=auth-token; # get the JWT from the "auth-token" header auth_jwt_location COOKIE=auth-token; # get the JWT from the "auth-token" cookie ``` ## `sub` Validation Optionally, the module can validate that a `sub` claim (e.g. the user's id) exists in the JWT. You may enable this feature as follows: ```nginx auth_jwt_validate_sub on; ``` ## Extracting Claims from the JWT You may specify claims to be extracted from the JWT and placed on the request and/or response headers. This is especially handly because the claims will then also be available as NGINX variables. If you only wish to access a claim as an NGINX variable, you should use `auth_jwt_extract_var_claims` so that the claim does not end up being sent to the client as a response header. However, if you do want the claim to be sent to the client in the response, you may use `auth_jwt_extract_response_claims` instead. _Please note that `number`, `boolean`, `array`, and `object` claims are not supported at this time -- only `string` claims are supported._ An error will be thrown if you attempt to extract a non-string claim. ### Using Claims For example, you could configure an NGINX location which redirects to the current user's profile. Suppose `sub=abc-123`, the configuration below would redirect to `/profile/abc-123`. ```nginx location /profile/me { auth_jwt_extract_var_claims sub; return 301 /profile/$jwt_claim_sub; } ``` ### Using Response Claims Response claims are used in the same way, with the only differences being: - the variables are accessed via the `$sent_http_jwt_*` pattern, e.g. `$sent_http_jwt_sub`, and - the headers are sent to the client. ### Extracting Multiple Claims You may extract multiple claims by specifying all claims as arguments to a single directive, or by supplying multiple directives. The following two examples are equivalent. ```nginx auth_jwt_extract_request_claims sub firstName lastName; ``` ```nginx auth_jwt_extract_request_claims sub; auth_jwt_extract_request_claims firstName; auth_jwt_extract_request_claims lastName; ``` ## Pre-built Packages (Ubuntu / Debian) Pre-built packages for this module are freely available from the GetPageSpeed repository: ```bash # Install the repository keyring sudo install -d -m 0755 /etc/apt/keyrings curl -fsSL https://extras.getpagespeed.com/deb-archive-keyring.gpg \ | sudo tee /etc/apt/keyrings/getpagespeed.gpg >/dev/null # Add the repository (Ubuntu example - replace 'ubuntu' and 'jammy' for your distro) echo "deb [signed-by=/etc/apt/keyrings/getpagespeed.gpg] https://extras.getpagespeed.com/ubuntu jammy main" \ | sudo tee /etc/apt/sources.list.d/getpagespeed-extras.list # Install nginx and the module sudo apt-get update sudo apt-get install nginx nginx-module-teslagov-jwt ``` The module is automatically enabled after installation. Supported distributions include Debian 12/13 and Ubuntu 20.04/22.04/24.04 (both amd64 and arm64). See [the complete setup instructions](https://apt-nginx-extras.getpagespeed.com/apt-setup/). ## Versioning This module has historically not been versioned, however, we are now starting to version the module in order to add clarity. We will add releases here in GitHub with additional details. In the future we may also publish pre-built modules for a selection of NGINX versions. ## Contributing If you'd like to contribute to this repository, please first initiate the Git hooks by running `./.bin/init` (note the `.` before `bin`) -- this will ensure that tests are run before you push your changes. ### Environment Set-up for Visual Studio Code 1. Install the C/C++ extension from Microsoft. 2. Add a C/C++ config file at `.vscode/c_cpp_properties.json` with the following (or similar) content: ```json { "configurations": [ { "name": "Linux", "includePath": [ "${workspaceFolder}/**", "~/Projects/nginx/objs/**", "~/Projects/nginx/src/**", "~/Projects/libjwt/include/**", "~/Projects/jansson/src/**" ], "defines": [], "compilerPath": "/usr/bin/clang", "cStandard": "c17", "cppStandard": "c++14", "intelliSenseMode": "linux-clang-x64" } ], "version": 4 } ``` Note the `includePath` additions above -- please update them as appropriate. Next we need to pull these sources. #### Building NGINX 1. Download the NGINX release matching the version you're targeting. 2. Extract the NGINX archive to wherever you'd like. 3. Update the `includePath` entires shown above to match the location you chose. 4. Enter the directory where you extracted NGINX and run: `./configure --with-compat` #### Cloning `libjwt` 1. Clone this repository as follows (replace ``): `git clone git@github.com:benmcollins/libjwt.git ` 2. Enter the directory and switch to the latest tag: `git checkout $(git tag | sort -Vr | head -n 1)` 3. Update the `includePath` entires shown above to match the location you chose. #### Cloning `libjansson` 1. Clone this repository as follows (replace ``): `git clone git@github.com:akheron/jansson.git ` 2. Enter the directory and switch to the latest tag: `git checkout $(git tag | sort -Vr | head -n 1)` 3. Update the `includePath` entires shown above to match the location you chose. #### Verifying Compliation Once you save your changes to `.vscode/c_cpp_properties.json`, you should see that warnings and errors in the Problems panel go away, at least temprorarily. Hopfeully they don't come back, but if they do, make sure your include paths are set correctly. ### Building and Testing #### Prerequisites - Docker and Docker Compose - For **macOS users**: Bash 4.0+ is required due to the use of associative arrays. macOS ships with Bash 3.2 by default. You can upgrade with: ```shell # Install a newer bash version brew install bash # Update your shell or ensure the scripts use the new bash export PATH="/opt/homebrew/bin:$PATH" # or /usr/local/bin for Intel Macs ``` #### Available Commands The `./scripts` file contains multiple commands to make things easy: | Command | Description | | --------------------- | ----------------------------------------------------------------- | | `build_module` | Builds the NGINX image. | | `rebuild_module` | Re-builds the NGINX image. | | `start` | Starts the NGINX container. | | `stop` | Stops the NGINX container. | | `cp_bin` | Copies the compiled binaries out of the NGINX container. | | `build_test` | Builds the images used by the test stack. | | `rebuild_test` | Re-builds the images used by the test stack. | | `test` | Runs `test.sh` against the NGINX container. | | `test_now` | Runs `test.sh` without rebuilding. | You can run multiple commands in sequence by separating them with a space, e.g.: ```shell ./scripts build_module ./scripts test ``` To build the Docker images, module, start NGINX, and run the tests against it for all versions, you can simply do: ```shell ./scripts all ``` When you make a change to the module, running `./scripts test` should build a fresh module and run the tests. Note that `rebuild_module` is not often needed as Docker will automatically rebuild the image if the source files have changed. When you make a change to the test NGINX config or `test.sh`, run `./scripts test` to run the tests. The image produced with `./scripts build_module` only differs from the official NGINX image in two ways: - the JWT module itself, and - the `nginx.conf` file is overwritten with our own. The tests use a customized NGINX image, distinct from the main image, as well as a test runner image. By running `./scripts test`, the two test containers will be stood up via Docker Compose, then they'll be started, and the tests will run. At the end of the test run, both containers will be automatically stopped and destroyed. See below to learn how to trace test failures across runs. #### Tracing Test Failures After making changes and finding that some tests fail, it can be difficult to understand why. By default, logs are written to Docker's internal log mechanism, but they won't be persisted after the test run completes and the containers are removed. If you'd like to persist logs across test runs, you can configure the log driver. By default, logs use the `json-file` driver for cross-platform compatibility. On Linux systems, you may prefer to use `journald` for better integration with systemd. You can do this by setting the environment variable `LOG_DRIVER` before running the tests: ```shell # For Linux systems with systemd (optional) export LOG_DRIVER=journald # For other systems or if you prefer file-based logs (default) export LOG_DRIVER=json-file # rebuild the test images ./scripts rebuild_test # run the tests ./scripts test # check the logs -- adjust the container name as needed # For journald (Linux systems): journalctl -eu docker CONTAINER_NAME=nginx-auth-jwt-test-nginx # For json-file driver (all systems): docker logs nginx-auth-jwt-test-nginx ``` Now you'll be able to see logs from previous test runs. The best way to make use of this is to open two terminals, one where you run the tests, and one where you follow the logs: ```shell # terminal 1 ./scripts test # terminal 2 - choose based on your LOG_DRIVER setting: # For journald: journalctl -fu docker CONTAINER_NAME=jwt-nginx-test # For json-file (default): docker logs -f nginx-auth-jwt-test-nginx ``` TeslaGov-ngx-http-auth-jwt-module-da2d99f/config000066400000000000000000000006301521256727700217420ustar00rootroot00000000000000ngx_module_type=HTTP ngx_addon_name=ngx_http_auth_jwt_module ngx_module_name=$ngx_addon_name ngx_module_srcs="${ngx_addon_dir}/src/arrays.c ${ngx_addon_dir}/src/ngx_http_auth_jwt_binary_converters.c ${ngx_addon_dir}/src/ngx_http_auth_jwt_header_processing.c ${ngx_addon_dir}/src/ngx_http_auth_jwt_string.c ${ngx_addon_dir}/src/ngx_http_auth_jwt_module.c" ngx_module_libs="-ljansson -ljwt -lm" . auto/module TeslaGov-ngx-http-auth-jwt-module-da2d99f/examples/000077500000000000000000000000001521256727700223715ustar00rootroot00000000000000TeslaGov-ngx-http-auth-jwt-module-da2d99f/examples/nginx.conf000066400000000000000000000016331521256727700243660ustar00rootroot00000000000000user nginx; worker_processes auto; error_log /var/log/nginx/error.log notice; pid /var/run/nginx.pid; load_module /usr/lib64/nginx/modules/ngx_http_auth_jwt_module.so; events { worker_connections 1024; } http { include /etc/nginx/mime.types; default_type application/octet-stream; log_format main '$remote_addr - $remote_user [$time_local] "$request" ' '$status $body_bytes_sent "$http_referer" ' '"$http_user_agent" "$http_x_forwarded_for"'; access_log /var/log/nginx/access.log main; auth_jwt_enabled on; auth_jwt_algorithm 'put_algo_here'; auth_jwt_key 'put_key_here'; auth_jwt_location 'COOKIE=auth-token'; auth_jwt_redirect on; auth_jwt_loginurl 'put_login_url_here'; # Include other auth_jwt_* directives as needed. sendfile on; keepalive_timeout 65; include /etc/nginx/conf.d/*.conf; } TeslaGov-ngx-http-auth-jwt-module-da2d99f/nginx.dockerfile000066400000000000000000000075541521256727700237420ustar00rootroot00000000000000ARG BASE_IMAGE=${:?required}` FROM ${BASE_IMAGE} AS ngx_http_auth_jwt_builder LABEL stage=ngx_http_auth_jwt_builder ENV PATH="${PATH}:/etc/nginx" ENV LD_LIBRARY_PATH=/usr/local/lib ARG MODULE_VERSION ARG NGINX_VERSION ARG LIBJWT_VERSION RUN <<` set -e apt-get update apt-get upgrade -y ` RUN apt-get install -y curl git zlib1g-dev libpcre3-dev build-essential libpcre2-dev zlib1g-dev libpcre3-dev pkg-config cmake dh-autoreconf WORKDIR /root/build/libjansson RUN <<` set -e git clone --depth 1 --branch v2.14 https://github.com/akheron/jansson . cmake . -DJANSSON_BUILD_SHARED_LIBS=1 -DJANSSON_BUILD_DOCS=OFF make make check make install ` WORKDIR /root/build/libjwt RUN <<` set -e git clone --depth 1 --branch v${LIBJWT_VERSION} https://github.com/benmcollins/libjwt . autoreconf -i ./configure make all make install ` WORKDIR /root/build/ngx-http-auth-jwt-module ADD config ./ ADD src/*.h src/*.c ./src/ WORKDIR /root/build RUN <<` set -e mkdir nginx curl -O http://nginx.org/download/nginx-${NGINX_VERSION}.tar.gz tar -xzf nginx-${NGINX_VERSION}.tar.gz --strip-components 1 -C nginx ` WORKDIR /root/build/nginx RUN ./configure \ --prefix=/etc/nginx \ --sbin-path=/usr/sbin/nginx \ --modules-path=/usr/lib64/nginx/modules \ --conf-path=/etc/nginx/nginx.conf \ --error-log-path=/var/log/nginx/error.log \ --http-log-path=/var/log/nginx/access.log \ --pid-path=/var/run/nginx.pid \ --lock-path=/var/run/nginx.lock \ --http-client-body-temp-path=/var/cache/nginx/client_temp \ --http-proxy-temp-path=/var/cache/nginx/proxy_temp \ --http-fastcgi-temp-path=/var/cache/nginx/fastcgi_temp \ --http-uwsgi-temp-path=/var/cache/nginx/uwsgi_temp \ --http-scgi-temp-path=/var/cache/nginx/scgi_temp \ --user=nginx \ --group=nginx \ --with-compat \ --with-debug \ --with-file-aio \ --with-threads \ --with-http_addition_module \ --with-http_auth_request_module \ --with-http_dav_module \ --with-http_flv_module \ --with-http_gunzip_module \ --with-http_gzip_static_module \ --with-http_mp4_module \ --with-http_random_index_module \ --with-http_realip_module \ --with-http_secure_link_module \ --with-http_slice_module \ --with-http_ssl_module \ --with-http_stub_status_module \ --with-http_sub_module \ --with-http_v2_module \ --with-mail \ --with-mail_ssl_module \ --with-stream \ --with-stream_realip_module \ --with-stream_ssl_module \ --with-stream_ssl_preread_module \ --with-cc-opt="-g -O2 -ffile-prefix-map=/data/builder/debuild/nginx-1.25.4/debian/debuild-base/nginx-${NGINX_VERSION}=. -fstack-protector-strong -Wformat -Werror=format-security -Wp,-D_FORTIFY_SOURCE=2 -fPIC -DNGX_HTTP_AUTH_JWT_MODULE_VERSION=\\\"${MODULE_VERSION}\\\"" \ --with-ld-opt='-Wl,-z,relro -Wl,-z,now -Wl,--as-needed -pie' \ --add-dynamic-module=../ngx-http-auth-jwt-module RUN make modules RUN make install WORKDIR /usr/lib/nginx/modules RUN mv /root/build/nginx/objs/ngx_http_auth_jwt_module.so . RUN rm -rf /root/build RUN <<` set -e apt-get remove -y curl git zlib1g-dev libpcre3-dev build-essential libpcre2-dev zlib1g-dev libpcre3-dev pkg-config cmake dh-autoreconf # apt-get install -y gnupg2 ca-certificates lsb-release debian-archive-keyring apt-get clean ` RUN <<` set -e groupadd nginx useradd -g nginx nginx ` COPY <<` /etc/nginx/nginx.conf daemon off; user nginx; pid /var/run/nginx.pid; load_module /usr/lib/nginx/modules/ngx_http_auth_jwt_module.so; worker_processes 1; events { worker_connections 1024; } http { include mime.types; default_type application/octet-stream; log_format main '\$remote_addr - \$remote_user [\$time_local] "\$request" ' '\$status \$body_bytes_sent "\$http_referer" ' '"\$http_user_agent" "\$http_x_forwarded_for"'; access_log /var/log/nginx/access.log main; include conf.d/*.conf; } ` WORKDIR /var/cache/nginx RUN chown nginx:nginx . WORKDIR / CMD ["nginx"] TeslaGov-ngx-http-auth-jwt-module-da2d99f/openssl.dockerfile000066400000000000000000000024571521256727700242770ustar00rootroot00000000000000ARG BASE_IMAGE=debian:bookworm-slim FROM ${BASE_IMAGE} ARG SSL_VERSION=3.2.1 ENV SRC_DIR=/tmp/openssl-src ENV OUT_DIR=/usr/local/.openssl RUN chmod 1777 /tmp RUN <<` set -e apt-get update apt-get install -y curl build-essential libssl-dev libz-dev apt-get remove -y openssl apt-get clean ` WORKDIR ${SRC_DIR} RUN <<` set -ex SSL_VERSION_NO_DOTS=$(echo ${SSL_VERSION} | tr . _) # Support two different URL formats used by OpenSSL over time. curl --silent --location --fail -O https://github.com/openssl/openssl/releases/download/OpenSSL_${SSL_VERSION_NO_DOTS}/openssl-${SSL_VERSION}.tar.gz || curl --silent --location --fail -O https://github.com/openssl/openssl/releases/download/openssl-${SSL_VERSION}/openssl-${SSL_VERSION}.tar.gz tar -xf openssl-${SSL_VERSION}.tar.gz --strip-components=1 ` RUN ./config --prefix=${OUT_DIR} --openssldir=${OUT_DIR} shared zlib RUN <<` set -e make make test make install ` RUN <<` set -e echo "${OUT_DIR}/lib" > /etc/ld.so.conf.d/openssl-${SSL_VERSION}.conf ldconfig ln -sf ${OUT_DIR}/bin/openssl /usr/bin/openssl ln -sf ${OUT_DIR}/lib64/libssl.so.3 /lib/$(uname -m)-linux-gnu/libssl.so.3 ln -sf ${OUT_DIR}/lib64/libcrypto.so.3 /lib/$(uname -m)-linux-gnu/libcrypto.so.3 ` WORKDIR / RUN rm -rf ${SRC_DIR}TeslaGov-ngx-http-auth-jwt-module-da2d99f/scripts000077500000000000000000000174551521256727700222040ustar00rootroot00000000000000#!/bin/bash -eu MAGENTA='\u001b[35m' BLUE='\033[0;34m' GREEN='\033[0;32m' RED='\033[0;31m' NC='\033[0m' # supported SSL versions SSL_VERSION_1_1_1w='1.1.1w' SSL_VERSION_3_0_15='3.0.15' SSL_VERSION_3_2_1='3.2.1' SSL_VERSIONS=( ${SSL_VERSION_1_1_1w} ${SSL_VERSION_3_0_15} ${SSL_VERSION_3_2_1} ) declare -A SSL_IMAGE_MAP SSL_IMAGE_MAP[$SSL_VERSION_1_1_1w]="bullseye-slim:openssl-${SSL_VERSION_1_1_1w}" SSL_IMAGE_MAP[$SSL_VERSION_3_0_15]="bookworm-slim:openssl-${SSL_VERSION_3_0_15}" SSL_IMAGE_MAP[$SSL_VERSION_3_2_1]="bookworm-slim:openssl-${SSL_VERSION_3_2_1}" # supported NGINX versions -- for binary distribution -- see: https://nginx.org/en/download.html # - most recent legacy version # - current stable version # - current mainline version NGINX_VERSIONS=( 1.28.3 # legacy 1.30.2 # stable 1.31.1 # mainline ) # At the time of writing this, the following versions of libjwt are compatible: # * v1.0 - v1.12.0 # * v1.12.1 - v1.14.0 # * v1.15.0+ # And the following versions are available in common repos: # * Debian Bullseye (11): 1.10.2 # * Debian Bookworm (12): 1.10.2 # * Ubuntu Questing (25.10): 1.17.2 # * Ubuntu Resolute (26.04): 1.17.2 # * UBI8: 1.12.1 # This compiles against each version prior to a breaking change and the latest release. LIBJWT_VERSION_DEBIAN=1.10.2 LIBJWT_VERSION_UBUNTU=1.17.2 LIBJWT_VERSION_UBI8=1.12.1 LIBJWT_VERSION_LATEST=1.18.4 LIBJWT_VERSIONS=( ${LIBJWT_VERSION_DEBIAN} ${LIBJWT_VERSION_UBUNTU} ${LIBJWT_VERSION_UBI8} ${LIBJWT_VERSION_LATEST} ) MODULE_VERSION=$(git describe --tags --abbrev=0 2>/dev/null) SSL_VERSION=${SSL_VERSION:-$SSL_VERSION_3_0_15} NGINX_VERSION=${NGINX_VERSION:-${NGINX_VERSIONS[-1]}} LIBJWT_VERSION=${LIBJWT_VERSION:-${LIBJWT_VERSION_UBI8}} IMAGE_NAME=${IMAGE_NAME:-nginx-auth-jwt} FULL_IMAGE_NAME=${ORG_NAME:-teslagov}/${IMAGE_NAME} TEST_CONTAINER_NAME_PREFIX="${IMAGE_NAME}-test" TEST_COMPOSE_FILE='test/docker-compose-test.yml' all() { build_module build_test test_all } build_base_image() { local image=${SSL_IMAGE_MAP[$SSL_VERSION]} local baseImage=${image%%:*} if [ -z ${image} ]; then echo "Base image not set for SSL version :${SSL_VERSION}" exit 1 else printf "${MAGENTA}Building ${baseImage} base image for SSL ${SSL_VERSION}...${NC}\n" docker buildx build \ --platform linux/amd64 \ --build-arg BASE_IMAGE=debian:${baseImage} \ --build-arg SSL_VERSION=${SSL_VERSION} \ -f openssl.dockerfile \ -t ${image} \ . fi } build_module() { local baseImage=${SSL_IMAGE_MAP[$SSL_VERSION]} build_base_image printf "${MAGENTA}Building module for NGINX ${NGINX_VERSION}, libjwt ${LIBJWT_VERSION}...${NC}\n" docker buildx build \ --platform linux/amd64 \ -f nginx.dockerfile \ -t ${FULL_IMAGE_NAME}:${NGINX_VERSION} \ --build-arg BASE_IMAGE=${baseImage} \ --build-arg MODULE_VERSION=${MODULE_VERSION} \ --build-arg NGINX_VERSION=${NGINX_VERSION} \ --build-arg LIBJWT_VERSION=${LIBJWT_VERSION} \ . if [ "$?" -ne 0 ]; then printf "${RED}✘ Build failed ${NC}\n" else printf "${GREEN}✔ Successfully built NGINX module ${NC}\n" fi } rebuild_module() { docker rmi -f $(docker images --filter=label=stage=ngx_http_auth_jwt_builder --quiet) 2> /dev/null || true build_module } start() { local port=$(get_port) printf "${MAGENTA}Starting NGINX container (${IMAGE_NAME}) on port ${port}...${NC}\n" docker run --rm --name "${IMAGE_NAME}" -d -p ${port}:80 ${FULL_IMAGE_NAME}:${NGINX_VERSION} >/dev/null } stop() { docker stop "${IMAGE_NAME}" >/dev/null } cp_bin() { local destDir=bin local stopContainer=0; local moduleFileName="ngx_http_auth_jwt_module_${MODULE_VERSION}.so" if [ "$(docker container inspect -f '{{.State.Running}}' ${IMAGE_NAME} | true)" != "true" ]; then start stopContainer=1 fi printf "${MAGENTA}Copying binaries to: ${destDir}${NC}\n" rm -rf ${destDir}/* mkdir -p ${destDir} docker exec "${IMAGE_NAME}" sh -c "cd /; tar -chf - \ usr/lib/nginx/modules/ngx_http_auth_jwt_module.so \ usr/local/lib/libjansson.so.* \ usr/local/lib/libjwt.*" | tar -xf - -C ${destDir} &>/dev/null mv "${destDir}/usr/lib/nginx/modules/ngx_http_auth_jwt_module.so" "${destDir}/usr/lib/nginx/modules/${moduleFileName}" if [ $stopContainer ]; then printf "${MAGENTA}Stopping NGINX container (${IMAGE_NAME})...${NC}\n" stop fi } make_release() { local moduleFileName="ngx_http_auth_jwt_module_${MODULE_VERSION}.so" printf "${MAGENTA}Making release for version ${MODULE_VERSION} for NGINX ${NGINX_VERSION}...${NC}\n" rebuild_module rebuild_test test --no-build cp_bin mkdir -p release tar -czvf release/ngx-http-auth-jwt-module-${MODULE_VERSION}_libjwt-${LIBJWT_VERSION}_nginx-${NGINX_VERSION}.tgz \ README.md \ -C bin/usr/lib/nginx/modules ${moduleFileName} > /dev/null } # Create releases for all NGINX versions defined in `NGINX_VERSIONS`. make_releases() { rm -rf release/* for NGINX_VERSION in ${NGINX_VERSIONS[@]}; do for LIBJWT_VERSION in ${LIBJWT_VERSIONS[@]}; do export NGINX_VERSION LIBJWT_VERSION make_release done done } build_test() { local dockerArgs=${1:-} local port=$(get_port) local sslPort=$(get_port $((port + 1))) local runnerBaseImage=${SSL_IMAGE_MAP[$SSL_VERSION]} export TEST_CONTAINER_NAME_PREFIX export FULL_IMAGE_NAME export NGINX_VERSION printf "${MAGENTA}Building test NGINX & runner using port ${port}...${NC}\n" docker compose \ -p ${TEST_CONTAINER_NAME_PREFIX} \ -f ${TEST_COMPOSE_FILE} \ build \ --build-arg RUNNER_BASE_IMAGE=${runnerBaseImage} \ --build-arg PORT=${port} \ --build-arg SSL_PORT=${sslPort} \ --build-arg platform="linux/amd64" \ ${dockerArgs} } rebuild_test() { build_test --no-cache } test_all() { for SSL_VERSION in "${SSL_VERSIONS[@]}"; do for NGINX_VERSION in "${NGINX_VERSIONS[@]}"; do for LIBJWT_VERSION in ${LIBJWT_VERSIONS[@]}; do export SSL_VERSION NGINX_VERSION LIBJWT_VERSION test done done done } test() { if [[ ! "$*" =~ --no-build ]]; then build_module build_test fi trap 'test_cleanup' 0 printf "${MAGENTA}Running tests...${NC}\n" printf "\n" printf "Executing tests with the following options:\n" printf " SSL Version: ${SSL_VERSION}\n" printf " LIBJWT Version: ${LIBJWT_VERSION}\n" printf " NGINX Version: ${NGINX_VERSION}\n" docker compose \ -p ${TEST_CONTAINER_NAME_PREFIX} \ -f ${TEST_COMPOSE_FILE} up --abort-on-container-exit --exit-code-from runner } test_cleanup() { docker compose \ -p ${TEST_CONTAINER_NAME_PREFIX} \ -f ${TEST_COMPOSE_FILE} down } get_port() { startPort=${1:-8000} endPort=$((startPort + 100)) for p in $(seq ${startPort} ${endPort}); do if [ "$(uname)" == "Darwin" ]; then if ! lsof -i -P | grep LISTEN | grep -q ":${p} "; then echo ${p} break fi elif [ "$(expr substr $(uname -s) 1 5)" == "Linux" ]; then if ! ss -ln | grep -q ":${p} "; then echo ${p} break fi fi done } extract_version() { if [ $# -eq 0 ]; then printf "Usage: ./scripts extract_version \n" printf "Output: Prints the module version string embedded in the .so file\n" exit 1 fi if [ ! -f "$1" ]; then printf "${RED}Error: File not found: $1${NC}\n" >&2 exit 1 fi # Extract the version string using strings and grep # The version format is: ngx_http_auth_jwt_module_version= version=$(strings "$1" 2>/dev/null | grep '^ngx_http_auth_jwt_module_version=' | cut -d= -f2) if [ -z "$version" ]; then printf "${RED}No version metadata found in: $1${NC}\n" >&2 printf "unknown\n" exit 1 else printf "${GREEN}${version}${NC}\n" exit 0 fi } if [ $# -eq 0 ]; then all else fn=$1 shift ${fn} "$@" fi TeslaGov-ngx-http-auth-jwt-module-da2d99f/src/000077500000000000000000000000001521256727700213425ustar00rootroot00000000000000TeslaGov-ngx-http-auth-jwt-module-da2d99f/src/arrays.c000066400000000000000000000006001521256727700230030ustar00rootroot00000000000000#include "arrays.h" #include void merge_array(ngx_pool_t *pool, ngx_array_t **dest, const ngx_array_t *src, size_t size) { // only merge if dest is non-null and src is null if (src != NULL && *dest == NULL) { *dest = ngx_array_create(pool, src->nelts, size); ngx_memcpy((*dest)->elts, src->elts, src->nelts * size); (*dest)->nelts = src->nelts; } } TeslaGov-ngx-http-auth-jwt-module-da2d99f/src/arrays.h000066400000000000000000000002371521256727700230160ustar00rootroot00000000000000#ifndef _ARRAYS_H #define _ARRAYS_H #include void merge_array(ngx_pool_t *pool, ngx_array_t **dest, const ngx_array_t *src, size_t size); #endifTeslaGov-ngx-http-auth-jwt-module-da2d99f/src/ngx_http_auth_jwt_binary_converters.c000066400000000000000000000017101521256727700310630ustar00rootroot00000000000000/* * Copyright (C) 2018 Tesla Government * * This software may be modified and distributed under the terms * of the MIT license. See the LICENSE file for details. * * https://github.com/TeslaGov/ngx-http-auth-jwt-module */ #include "ngx_http_auth_jwt_binary_converters.h" #include int hex_char_to_binary(char ch, char *ret) { ch = tolower(ch); if (isdigit(ch)) { *ret = ch - '0'; } else if (ch >= 'a' && ch <= 'f') { *ret = (ch - 'a') + 10; } else if (ch >= 'A' && ch <= 'F') { *ret = (ch - 'A') + 10; } else { return -1; } return 0; } int hex_to_binary(const char *str, u_char *buf, int len) { int odd = len % 2; if (odd) { return -1; } else { u_char *cpy = buf; char low; char high; for (int i = 0; i < len; i += 2) { if (hex_char_to_binary(*(str + i), &high) != 0 || hex_char_to_binary(*(str + i + 1), &low) != 0) { return -2; } *cpy++ = low | (high << 4); } return 0; } }TeslaGov-ngx-http-auth-jwt-module-da2d99f/src/ngx_http_auth_jwt_binary_converters.h000066400000000000000000000007741521256727700311010ustar00rootroot00000000000000/* * Copyright (C) 2018 Tesla Government * * This software may be modified and distributed under the terms * of the MIT license. See the LICENSE file for details. * * https://github.com/TeslaGov/ngx-http-auth-jwt-module */ #ifndef _NGX_HTTP_AUTH_JWT_BINARY_CONVERTERS_H #define _NGX_HTTP_AUTH_JWT_BINARY_CONVERTERS_H #include int hex_char_to_binary( char ch, char* ret ); int hex_to_binary( const char* str, u_char* buf, int len ); #endif /* _NGX_HTTP_AUTH_JWT_BINARY_CONVERTERS_H */TeslaGov-ngx-http-auth-jwt-module-da2d99f/src/ngx_http_auth_jwt_header_processing.c000066400000000000000000000037771521256727700310300ustar00rootroot00000000000000/* * Copyright (C) 2018 Tesla Government * * This software may be modified and distributed under the terms * of the MIT license. See the LICENSE file for details. * * https://github.com/TeslaGov/ngx-http-auth-jwt-module */ #include #include #include "ngx_http_auth_jwt_header_processing.h" /** * Sample code from nginx. * https://www.nginx.com/resources/wiki/start/topics/examples/headers_management/?highlight=http%20settings */ ngx_table_elt_t *search_headers_in(ngx_http_request_t *r, u_char *name, size_t len) { ngx_list_part_t *part; ngx_table_elt_t *h; ngx_uint_t i; // Get the first part of the list. There is usual only one part. part = &r->headers_in.headers.part; h = part->elts; // Headers list array may consist of more than one part, so loop through all of it for (i = 0; /* void */; ++i) { if (i >= part->nelts) { if (part->next == NULL) { /* The last part, search is done. */ break; } part = part->next; h = part->elts; i = 0; } // Just compare the lengths and then the names case insensitively. if (len != h[i].key.len || ngx_strcasecmp(name, h[i].key.data) != 0) { /* This header doesn't match. */ continue; } /* * Ta-da, we got one! * Note, we've stopped the search at the first matched header * while more then one header may match. */ return &h[i]; } /* No headers found */ return NULL; } ngx_int_t set_request_header(ngx_http_request_t *r, ngx_str_t *key, ngx_str_t *value) { return set_header(ngx_list_push(&r->headers_in.headers), key, value); } ngx_int_t set_response_header(ngx_http_request_t *r, ngx_str_t *key, ngx_str_t *value) { return set_header(ngx_list_push(&r->headers_out.headers), key, value); } ngx_int_t set_header(ngx_table_elt_t *h, ngx_str_t *key, ngx_str_t *value) { if (h == NULL) { return NGX_ERROR; } else { h->key = *key; h->value = *value; h->hash = 1; return NGX_OK; } }TeslaGov-ngx-http-auth-jwt-module-da2d99f/src/ngx_http_auth_jwt_header_processing.h000066400000000000000000000012201521256727700310120ustar00rootroot00000000000000/* * Copyright (C) 2018 Tesla Government * * This software may be modified and distributed under the terms * of the MIT license. See the LICENSE file for details. */ #ifndef _NGX_HTTP_AUTH_JWT_HEADER_PROCESSING_H #define _NGX_HTTP_AUTH_JWT_HEADER_PROCESSING_H ngx_table_elt_t* search_headers_in(ngx_http_request_t *r, u_char *name, size_t len); ngx_int_t set_request_header(ngx_http_request_t *r, ngx_str_t *key, ngx_str_t *value); ngx_int_t set_response_header(ngx_http_request_t *r, ngx_str_t *key, ngx_str_t *value); ngx_int_t set_header(ngx_table_elt_t *h, ngx_str_t *key, ngx_str_t *value); #endif /* _NGX_HTTP_AUTH_JWT_HEADER_PROCESSING_H */TeslaGov-ngx-http-auth-jwt-module-da2d99f/src/ngx_http_auth_jwt_module.c000066400000000000000000000705231521256727700266220ustar00rootroot00000000000000/* * Copyright (C) 2018 Tesla Government * * This software may be modified and distributed under the terms * of the MIT license. See the LICENSE file for details. * * https://github.com/TeslaGov/ngx-http-auth-jwt-module */ #include #include #include #include #include #include "arrays.h" #include "ngx_http_auth_jwt_header_processing.h" #include "ngx_http_auth_jwt_binary_converters.h" #include "ngx_http_auth_jwt_string.h" #include #include /* Module version metadata - embedded at compile time */ #ifndef NGX_HTTP_AUTH_JWT_MODULE_VERSION #define NGX_HTTP_AUTH_JWT_MODULE_VERSION "unknown" #endif static const char ngx_http_auth_jwt_module_version[] __attribute__((used)) = "ngx_http_auth_jwt_module_version=" NGX_HTTP_AUTH_JWT_MODULE_VERSION; typedef struct { ngx_str_t loginurl; ngx_str_t key; ngx_http_complex_value_t *enabled; ngx_flag_t redirect; ngx_str_t jwt_location; ngx_str_t algorithm; ngx_flag_t validate_sub; ngx_array_t *extract_var_claims; ngx_array_t *extract_request_claims; ngx_array_t *extract_response_claims; ngx_str_t keyfile_path; ngx_flag_t use_keyfile; ngx_str_t _keyfile; } auth_jwt_conf_t; typedef struct { ngx_int_t validation_status; ngx_array_t *claim_values; } auth_jwt_ctx_t; static ngx_int_t init(ngx_conf_t *cf); static void *create_conf(ngx_conf_t *cf); static char *merge_conf(ngx_conf_t *cf, void *parent, void *child); static char *merge_extract_var_claims(ngx_conf_t *cf, ngx_command_t *cmd, void *c); static ngx_int_t get_jwt_var_claim(ngx_http_request_t *r, ngx_http_variable_value_t *v, uintptr_t data); static char *merge_extract_request_claims(ngx_conf_t *cf, ngx_command_t *cmd, void *c); static char *merge_extract_response_claims(ngx_conf_t *cf, ngx_command_t *cmd, void *c); static auth_jwt_ctx_t *get_or_init_jwt_module_ctx(ngx_http_request_t *r, auth_jwt_conf_t *jwtcf); static auth_jwt_ctx_t *get_request_jwt_ctx(ngx_http_request_t *r); static ngx_int_t handle_request(ngx_http_request_t *r); static int validate_alg(auth_jwt_conf_t *jwtcf, jwt_t *jwt); static int validate_exp(auth_jwt_conf_t *jwtcf, jwt_t *jwt); static int validate_sub(auth_jwt_conf_t *jwtcf, jwt_t *jwt); static ngx_int_t extract_var_claims(ngx_http_request_t *r, auth_jwt_conf_t *jwtcf, jwt_t *jwt, auth_jwt_ctx_t *ctx); static void extract_request_claims(ngx_http_request_t *r, auth_jwt_conf_t *jwtcf, jwt_t *jwt); static void extract_response_claims(ngx_http_request_t *r, auth_jwt_conf_t *jwtcf, jwt_t *jwt); static ngx_int_t redirect(ngx_http_request_t *r, auth_jwt_conf_t *jwtcf); static ngx_int_t load_public_key(ngx_conf_t *cf, auth_jwt_conf_t *conf); static char *get_jwt(ngx_http_request_t *r, ngx_str_t jwt_location); static const char *JWT_HEADER_PREFIX = "JWT-"; static ngx_command_t auth_jwt_directives[] = { {ngx_string("auth_jwt_loginurl"), NGX_HTTP_MAIN_CONF | NGX_HTTP_SRV_CONF | NGX_HTTP_LOC_CONF | NGX_CONF_TAKE1, ngx_conf_set_str_slot, NGX_HTTP_LOC_CONF_OFFSET, offsetof(auth_jwt_conf_t, loginurl), NULL}, {ngx_string("auth_jwt_key"), NGX_HTTP_MAIN_CONF | NGX_HTTP_SRV_CONF | NGX_HTTP_LOC_CONF | NGX_CONF_TAKE1, ngx_conf_set_str_slot, NGX_HTTP_LOC_CONF_OFFSET, offsetof(auth_jwt_conf_t, key), NULL}, {ngx_string("auth_jwt_enabled"), NGX_HTTP_MAIN_CONF | NGX_HTTP_SRV_CONF | NGX_HTTP_LOC_CONF | NGX_CONF_TAKE1, ngx_http_set_complex_value_slot, NGX_HTTP_LOC_CONF_OFFSET, offsetof(auth_jwt_conf_t, enabled), NULL}, {ngx_string("auth_jwt_redirect"), NGX_HTTP_MAIN_CONF | NGX_HTTP_SRV_CONF | NGX_HTTP_LOC_CONF | NGX_CONF_FLAG, ngx_conf_set_flag_slot, NGX_HTTP_LOC_CONF_OFFSET, offsetof(auth_jwt_conf_t, redirect), NULL}, {ngx_string("auth_jwt_location"), NGX_HTTP_MAIN_CONF | NGX_HTTP_SRV_CONF | NGX_HTTP_LOC_CONF | NGX_CONF_TAKE1, ngx_conf_set_str_slot, NGX_HTTP_LOC_CONF_OFFSET, offsetof(auth_jwt_conf_t, jwt_location), NULL}, {ngx_string("auth_jwt_algorithm"), NGX_HTTP_MAIN_CONF | NGX_HTTP_SRV_CONF | NGX_HTTP_LOC_CONF | NGX_CONF_TAKE1, ngx_conf_set_str_slot, NGX_HTTP_LOC_CONF_OFFSET, offsetof(auth_jwt_conf_t, algorithm), NULL}, {ngx_string("auth_jwt_validate_sub"), NGX_HTTP_MAIN_CONF | NGX_HTTP_SRV_CONF | NGX_HTTP_LOC_CONF | NGX_CONF_FLAG, ngx_conf_set_flag_slot, NGX_HTTP_LOC_CONF_OFFSET, offsetof(auth_jwt_conf_t, validate_sub), NULL}, {ngx_string("auth_jwt_extract_var_claims"), NGX_HTTP_MAIN_CONF | NGX_HTTP_SRV_CONF | NGX_HTTP_LOC_CONF | NGX_CONF_1MORE, merge_extract_var_claims, NGX_HTTP_LOC_CONF_OFFSET, offsetof(auth_jwt_conf_t, extract_var_claims), NULL}, {ngx_string("auth_jwt_extract_request_claims"), NGX_HTTP_MAIN_CONF | NGX_HTTP_SRV_CONF | NGX_HTTP_LOC_CONF | NGX_CONF_1MORE, merge_extract_request_claims, NGX_HTTP_LOC_CONF_OFFSET, offsetof(auth_jwt_conf_t, extract_request_claims), NULL}, {ngx_string("auth_jwt_extract_response_claims"), NGX_HTTP_MAIN_CONF | NGX_HTTP_SRV_CONF | NGX_HTTP_LOC_CONF | NGX_CONF_1MORE, merge_extract_response_claims, NGX_HTTP_LOC_CONF_OFFSET, offsetof(auth_jwt_conf_t, extract_response_claims), NULL}, {ngx_string("auth_jwt_keyfile_path"), NGX_HTTP_MAIN_CONF | NGX_HTTP_SRV_CONF | NGX_HTTP_LOC_CONF | NGX_CONF_TAKE1, ngx_conf_set_str_slot, NGX_HTTP_LOC_CONF_OFFSET, offsetof(auth_jwt_conf_t, keyfile_path), NULL}, {ngx_string("auth_jwt_use_keyfile"), NGX_HTTP_MAIN_CONF | NGX_HTTP_SRV_CONF | NGX_HTTP_LOC_CONF | NGX_CONF_FLAG, ngx_conf_set_flag_slot, NGX_HTTP_LOC_CONF_OFFSET, offsetof(auth_jwt_conf_t, use_keyfile), NULL}, ngx_null_command}; static ngx_http_module_t auth_jwt_context = { NULL, /* preconfiguration */ init, /* postconfiguration */ NULL, /* create main configuration */ NULL, /* init main configuration */ NULL, /* create server configuration */ NULL, /* merge server configuration */ create_conf, /* create location configuration */ merge_conf /* merge location configuration */ }; ngx_module_t ngx_http_auth_jwt_module = { NGX_MODULE_V1, &auth_jwt_context, /* module context */ auth_jwt_directives, /* module directives */ NGX_HTTP_MODULE, /* module type */ NULL, /* init master */ NULL, /* init module */ NULL, /* init process */ NULL, /* init thread */ NULL, /* exit thread */ NULL, /* exit process */ NULL, /* exit master */ NGX_MODULE_V1_PADDING}; static ngx_int_t init(ngx_conf_t *cf) { ngx_http_core_main_conf_t *cmcf = ngx_http_conf_get_module_main_conf(cf, ngx_http_core_module); ngx_http_handler_pt *h = ngx_array_push(&cmcf->phases[NGX_HTTP_ACCESS_PHASE].handlers); if (h == NULL) { return NGX_ERROR; } else { *h = handle_request; return NGX_OK; } } static void *create_conf(ngx_conf_t *cf) { auth_jwt_conf_t *conf = ngx_pcalloc(cf->pool, sizeof(auth_jwt_conf_t)); if (conf == NULL) { return NULL; } else { // ngx_str_t fields are initialized by the ngx_palloc call above -- only need to init flags and arrays here conf->enabled = NULL; conf->redirect = NGX_CONF_UNSET; conf->validate_sub = NGX_CONF_UNSET; conf->extract_var_claims = NULL; conf->extract_request_claims = NULL; conf->extract_response_claims = NULL; conf->use_keyfile = NGX_CONF_UNSET; return conf; } } static char *merge_conf(ngx_conf_t *cf, void *parent, void *child) { const auth_jwt_conf_t *prev = parent; auth_jwt_conf_t *conf = child; ngx_conf_merge_str_value(conf->loginurl, prev->loginurl, ""); ngx_conf_merge_str_value(conf->key, prev->key, ""); ngx_conf_merge_str_value(conf->jwt_location, prev->jwt_location, "HEADER=Authorization"); ngx_conf_merge_str_value(conf->algorithm, prev->algorithm, "HS256"); ngx_conf_merge_str_value(conf->keyfile_path, prev->keyfile_path, ""); ngx_conf_merge_off_value(conf->validate_sub, prev->validate_sub, 0); merge_array(cf->pool, &conf->extract_var_claims, prev->extract_var_claims, sizeof(ngx_str_t)); merge_array(cf->pool, &conf->extract_request_claims, prev->extract_request_claims, sizeof(ngx_str_t)); merge_array(cf->pool, &conf->extract_response_claims, prev->extract_response_claims, sizeof(ngx_str_t)); if (conf->enabled == NULL) { conf->enabled = prev->enabled; } ngx_conf_merge_off_value(conf->redirect, prev->redirect, 0); ngx_conf_merge_off_value(conf->use_keyfile, prev->use_keyfile, 0); // If the usage of the keyfile is specified, check if the key_path is also configured if (conf->use_keyfile == 1) { if (ngx_strcmp(conf->keyfile_path.data, "") != 0) { if (load_public_key(cf, conf) != NGX_OK) { return NGX_CONF_ERROR; } } else { ngx_log_error(NGX_LOG_ERR, cf->log, 0, "keyfile_path not specified"); return NGX_CONF_ERROR; } } return NGX_CONF_OK; } static char *merge_extract_var_claims(ngx_conf_t *cf, ngx_command_t *cmd, void *c) { auth_jwt_conf_t *conf = c; ngx_array_t *claims = conf->extract_var_claims; if (claims == NULL) { claims = ngx_array_create(cf->pool, 1, sizeof(ngx_str_t)); conf->extract_var_claims = claims; } ngx_str_t *values = cf->args->elts; // start at 1 because the first element is the directive (auth_jwt_extract_var_claims) for (ngx_uint_t i = 1; i < cf->args->nelts; ++i) { // add this claim's name to the config struct ngx_str_t *element = ngx_array_push(claims); *element = values[i]; // add an http variable for this claim size_t var_name_len = 10 + element->len; u_char *buf = ngx_palloc(cf->pool, sizeof(u_char) * var_name_len); if (buf == NULL) { return NGX_CONF_ERROR; } else { ngx_sprintf(buf, "jwt_claim_%V", element); ngx_str_t *var_name = ngx_palloc(cf->pool, sizeof(ngx_str_t)); if (var_name == NULL) { return NGX_CONF_ERROR; } else { var_name->data = buf; var_name->len = var_name_len; // NGX_HTTP_VAR_CHANGEABLE simplifies the required logic by assuming a JWT claim will always be the same for a given request ngx_http_variable_t *http_var = ngx_http_add_variable(cf, var_name, NGX_HTTP_VAR_CHANGEABLE); if (http_var == NULL) { ngx_log_error(NGX_LOG_ERR, cf->log, 0, "failed to add variable %V", var_name); return NGX_CONF_ERROR; } else { http_var->get_handler = get_jwt_var_claim; // store the index of this new claim in the claims array as the "data" that will be passed to the getter ngx_uint_t *claim_idx = ngx_palloc(cf->pool, sizeof(ngx_uint_t)); if (claim_idx == NULL) { return NGX_CONF_ERROR; } else { *claim_idx = claims->nelts - 1; http_var->data = (uintptr_t)claim_idx; } } } } } return NGX_CONF_OK; } static ngx_int_t get_jwt_var_claim(ngx_http_request_t *r, ngx_http_variable_value_t *v, uintptr_t data) { ngx_uint_t *claim_idx = (ngx_uint_t *)data; auth_jwt_ctx_t *ctx = get_request_jwt_ctx(r); ngx_log_debug(NGX_LOG_DEBUG_HTTP, r->connection->log, 0, "getting jwt var claim for var at index %l", *claim_idx); if (ctx == NULL || ctx->validation_status != NGX_OK) { ngx_log_debug(NGX_LOG_DEBUG_HTTP, r->connection->log, 0, "no module context found while getting jwt value"); return NGX_ERROR; } else { ngx_str_t claim_value = ((ngx_str_t *)ctx->claim_values->elts)[*claim_idx]; v->valid = 1; v->no_cacheable = 0; v->not_found = 0; v->len = claim_value.len; v->data = claim_value.data; return NGX_OK; } } static char *merge_extract_claims(ngx_conf_t *cf, ngx_array_t *claims) { ngx_str_t *values = cf->args->elts; // start at 1 because the first element is the directive (auth_jwt_extract_X_claims) for (ngx_uint_t i = 1; i < cf->args->nelts; ++i) { ngx_str_t *element = ngx_array_push(claims); *element = values[i]; } return NGX_CONF_OK; } static char *merge_extract_request_claims(ngx_conf_t *cf, ngx_command_t *cmd, void *c) { auth_jwt_conf_t *conf = c; ngx_array_t *claims = conf->extract_request_claims; if (claims == NULL) { claims = ngx_array_create(cf->pool, 1, sizeof(ngx_str_t)); conf->extract_request_claims = claims; } return merge_extract_claims(cf, claims); } static char *merge_extract_response_claims(ngx_conf_t *cf, ngx_command_t *cmd, void *c) { auth_jwt_conf_t *conf = c; ngx_array_t *claims = conf->extract_response_claims; if (claims == NULL) { claims = ngx_array_create(cf->pool, 1, sizeof(ngx_str_t)); conf->extract_response_claims = claims; } return merge_extract_claims(cf, claims); } static auth_jwt_ctx_t *get_or_init_jwt_module_ctx(ngx_http_request_t *r, auth_jwt_conf_t *jwtcf) { auth_jwt_ctx_t *ctx = ngx_http_get_module_ctx(r, ngx_http_auth_jwt_module); if (ctx != NULL) { return ctx; } else { ctx = ngx_pcalloc(r->pool, sizeof(auth_jwt_ctx_t)); if (ctx == NULL) { ngx_log_error(NGX_LOG_ERR, r->connection->log, 0, "error allocating jwt module context"); return ctx; } else { if (jwtcf->extract_var_claims != NULL) { ctx->claim_values = ngx_array_create(r->pool, jwtcf->extract_var_claims->nelts, sizeof(ngx_str_t)); if (ctx->claim_values == NULL) { ngx_log_error(NGX_LOG_ERR, r->connection->log, 0, "error initializing jwt module context"); return NULL; } } ctx->validation_status = NGX_AGAIN; ngx_http_set_ctx(r, ctx, ngx_http_auth_jwt_module); return ctx; } } } // this creates the module's context struct and extracts claim vars the first time it is called, // either from the access-phase handler or an http var getter static auth_jwt_ctx_t *get_request_jwt_ctx(ngx_http_request_t *r) { auth_jwt_conf_t *jwtcf = ngx_http_get_module_loc_conf(r, ngx_http_auth_jwt_module); ngx_int_t enabled = 1; if (jwtcf->enabled != NULL) { ngx_str_t cv; if (ngx_http_complex_value(r, jwtcf->enabled, &cv) == NGX_OK && cv.len > 0) { if (ngx_strncmp(cv.data, "off", 3) == 0) { enabled = 0; } } } if (!enabled) { return NULL; } else { auth_jwt_ctx_t *ctx = get_or_init_jwt_module_ctx(r, jwtcf); if (ctx == NULL) { return NULL; } else if (ctx->validation_status != NGX_AGAIN) { // we already validated and extacted everything we care about, so we just return the already-complete context return ctx; } else { char *jwtPtr = get_jwt(r, jwtcf->jwt_location); if (jwtPtr == NULL) { ngx_log_error(NGX_LOG_ERR, r->connection->log, 0, "failed to find a JWT"); ctx->validation_status = NGX_ERROR; return ctx; } else { ngx_str_t algorithm = jwtcf->algorithm; int keyLength; u_char *key; jwt_t *jwt = NULL; if (algorithm.len == 0 || (algorithm.len == 5 && ngx_strncmp(algorithm.data, "HS", 2) == 0)) { keyLength = jwtcf->key.len / 2; key = ngx_palloc(r->pool, keyLength); if (0 != hex_to_binary((char *)jwtcf->key.data, key, jwtcf->key.len)) { ngx_log_error(NGX_LOG_ERR, r->connection->log, 0, "failed to turn hex key into binary"); ctx->validation_status = NGX_ERROR; return ctx; } } else if (algorithm.len == 5 && (ngx_strncmp(algorithm.data, "RS", 2) == 0 || ngx_strncmp(algorithm.data, "ES", 2) == 0)) { if (jwtcf->use_keyfile == 1) { keyLength = jwtcf->_keyfile.len; key = (u_char *)jwtcf->_keyfile.data; } else { keyLength = jwtcf->key.len; key = jwtcf->key.data; } } else { ngx_log_error(NGX_LOG_ERR, r->connection->log, 0, "unsupported algorithm %s", algorithm); ctx->validation_status = NGX_ERROR; return ctx; } if (jwt_decode(&jwt, jwtPtr, key, keyLength) != 0 || !jwt) { ngx_log_error(NGX_LOG_ERR, r->connection->log, 0, "failed to parse JWT"); ctx->validation_status = NGX_ERROR; } else if (validate_alg(jwtcf, jwt) != 0) { ngx_log_error(NGX_LOG_ERR, r->connection->log, 0, "invalid algorithm specified"); ctx->validation_status = NGX_ERROR; } else if (validate_exp(jwtcf, jwt) != 0) { ngx_log_error(NGX_LOG_ERR, r->connection->log, 0, "the JWT has expired"); ctx->validation_status = NGX_ERROR; } else if (validate_sub(jwtcf, jwt) != 0) { ngx_log_error(NGX_LOG_ERR, r->connection->log, 0, "the JWT does not contain a subject"); ctx->validation_status = NGX_ERROR; } else { extract_request_claims(r, jwtcf, jwt); extract_response_claims(r, jwtcf, jwt); ctx->validation_status = extract_var_claims(r, jwtcf, jwt, ctx); } jwt_free(jwt); return ctx; } } } } static ngx_int_t handle_request(ngx_http_request_t *r) { auth_jwt_conf_t *jwtcf = ngx_http_get_module_loc_conf(r, ngx_http_auth_jwt_module); // Only activate JWT logic if key or keyfile_path is set if (jwtcf->key.len == 0 && jwtcf->keyfile_path.len == 0) { return NGX_DECLINED; } auth_jwt_ctx_t *ctx = get_request_jwt_ctx(r); ngx_int_t enabled = 1; if (jwtcf->enabled != NULL) { ngx_str_t cv; if (ngx_http_complex_value(r, jwtcf->enabled, &cv) == NGX_OK && cv.len > 0) { if (ngx_strncmp(cv.data, "off", 3) == 0) { enabled = 0; } } } if (!enabled) { return NGX_DECLINED; } else if (r->method == NGX_HTTP_OPTIONS) // pass through options requests without token authentication { return NGX_DECLINED; } else if (!ctx) { return NGX_ERROR; } else if (ctx->validation_status == NGX_ERROR) { return redirect(r, jwtcf); } else { return ctx->validation_status; } } static int validate_alg(auth_jwt_conf_t *jwtcf, jwt_t *jwt) { const jwt_alg_t alg = jwt_get_alg(jwt); if (alg != JWT_ALG_HS256 && alg != JWT_ALG_HS384 && alg != JWT_ALG_HS512 && alg != JWT_ALG_RS256 && alg != JWT_ALG_RS384 && alg != JWT_ALG_RS512 && alg != JWT_ALG_ES256 && alg != JWT_ALG_ES384 && alg != JWT_ALG_ES512) { return 1; } return 0; } static int validate_exp(auth_jwt_conf_t *jwtcf, jwt_t *jwt) { const time_t exp = (time_t)jwt_get_grant_int(jwt, "exp"); const time_t now = time(NULL); if (exp < now) { return 1; } return 0; } static int validate_sub(auth_jwt_conf_t *jwtcf, jwt_t *jwt) { if (jwtcf->validate_sub == 1) { const char *sub = jwt_get_grant(jwt, "sub"); if (sub == NULL) { return 1; } } return 0; } static ngx_int_t extract_var_claims(ngx_http_request_t *r, auth_jwt_conf_t *jwtcf, jwt_t *jwt, auth_jwt_ctx_t *ctx) { ngx_array_t *claims = jwtcf->extract_var_claims; if (claims == NULL || claims->nelts == 0) { return NGX_OK; } else { const ngx_str_t *claimsPtr = claims->elts; for (uint i = 0; i < claims->nelts; ++i) { const ngx_str_t claim = claimsPtr[i]; const char *claimValue = jwt_get_grant(jwt, (char *)claim.data); ngx_str_t value = ngx_string(""); if (claimValue != NULL && strlen(claimValue) > 0) { value = char_ptr_to_ngx_str_t(r->pool, claimValue); } ((ngx_str_t *)ctx->claim_values->elts)[i] = value; ngx_log_debug(NGX_LOG_DEBUG_HTTP, r->connection->log, 0, "set var %V to JWT claim value %s", &claim, value.data); } return NGX_OK; } } static void extract_claims(ngx_http_request_t *r, jwt_t *jwt, ngx_array_t *claims, ngx_int_t (*set_header)(ngx_http_request_t *r, ngx_str_t *key, ngx_str_t *value)) { if (claims != NULL && claims->nelts > 0) { const ngx_str_t *claimsPtr = claims->elts; for (uint i = 0; i < claims->nelts; ++i) { const ngx_str_t claim = claimsPtr[i]; const char *value = jwt_get_grant(jwt, (char *)claim.data); if (value != NULL && strlen(value) > 0) { ngx_uint_t claimHeaderLen = strlen(JWT_HEADER_PREFIX) + claim.len; ngx_str_t claimHeader = ngx_null_string; ngx_str_t claimValue = char_ptr_to_ngx_str_t(r->pool, value); claimHeader.data = ngx_palloc(r->pool, claimHeaderLen); claimHeader.len = claimHeaderLen; ngx_snprintf(claimHeader.data, claimHeaderLen, "%s%V", JWT_HEADER_PREFIX, &claim); set_header(r, &claimHeader, &claimValue); } } } } static void extract_request_claims(ngx_http_request_t *r, auth_jwt_conf_t *jwtcf, jwt_t *jwt) { extract_claims(r, jwt, jwtcf->extract_request_claims, set_request_header); } static void extract_response_claims(ngx_http_request_t *r, auth_jwt_conf_t *jwtcf, jwt_t *jwt) { extract_claims(r, jwt, jwtcf->extract_response_claims, set_response_header); } static ngx_int_t redirect(ngx_http_request_t *r, auth_jwt_conf_t *jwtcf) { if (jwtcf->redirect) { r->headers_out.location = ngx_list_push(&r->headers_out.headers); if (r->headers_out.location == NULL) { ngx_http_finalize_request(r, NGX_HTTP_INTERNAL_SERVER_ERROR); } r->headers_out.location->hash = 1; r->headers_out.location->key.len = strlen("Location"); r->headers_out.location->key.data = (u_char *)"Location"; if (r->method == NGX_HTTP_GET) { const int loginlen = jwtcf->loginurl.len; const char *scheme = r->connection->ssl ? "https" : "http"; ngx_str_t port_variable_name = ngx_string("server_port"); ngx_int_t port_variable_hash = ngx_hash_key(port_variable_name.data, port_variable_name.len); ngx_http_variable_value_t *port_var = ngx_http_get_variable(r, &port_variable_name, port_variable_hash); char *port_str = ""; uint port_str_len = 0; const ngx_str_t server = r->headers_in.server; ngx_str_t uri_variable_name = ngx_string("request_uri"); ngx_int_t uri_variable_hash = ngx_hash_key(uri_variable_name.data, uri_variable_name.len); ngx_http_variable_value_t *uri_var = ngx_http_get_variable(r, &uri_variable_name, uri_variable_hash); ngx_str_t uri; ngx_str_t uri_escaped; uintptr_t escaped_len; char *return_url; int return_url_idx; // get the URI if (uri_var && !uri_var->not_found && uri_var->valid) { // ideally we would like the URI with the querystring parameters uri.data = ngx_palloc(r->pool, uri_var->len); uri.len = uri_var->len; ngx_memcpy(uri.data, uri_var->data, uri_var->len); } else { // fallback to the querystring without params uri = r->uri; } if (port_var && !port_var->not_found && port_var->valid) { const ngx_uint_t port_num = ngx_atoi(port_var->data, port_var->len); const bool is_default_port_80 = !r->connection->ssl && port_num == 80; const bool is_default_port_443 = r->connection->ssl && port_num == 443; const bool is_non_default_port = !is_default_port_80 && !is_default_port_443; if (is_non_default_port) { port_str = ngx_palloc(r->pool, NGX_INT_T_LEN + 2); ngx_snprintf((u_char *)port_str, sizeof(port_str), ":%d", port_num); port_str_len = strlen(port_str); } } // escape the URI escaped_len = 2 * ngx_escape_uri(NULL, uri.data, uri.len, NGX_ESCAPE_ARGS) + uri.len; uri_escaped.data = ngx_palloc(r->pool, escaped_len); uri_escaped.len = escaped_len; ngx_escape_uri(uri_escaped.data, uri.data, uri.len, NGX_ESCAPE_ARGS); // Add up the lengths of: login URL, "?return_url=", scheme, "://", server, port, uri (path) r->headers_out.location->value.len = loginlen + 12 + strlen(scheme) + 3 + server.len + port_str_len + uri_escaped.len; return_url = ngx_palloc(r->pool, r->headers_out.location->value.len); ngx_memcpy(return_url, jwtcf->loginurl.data, jwtcf->loginurl.len); return_url_idx = jwtcf->loginurl.len; ngx_memcpy(return_url + return_url_idx, "?return_url=", 12); return_url_idx += 12; ngx_memcpy(return_url + return_url_idx, scheme, strlen(scheme)); return_url_idx += strlen(scheme); ngx_memcpy(return_url + return_url_idx, "://", 3); return_url_idx += 3; ngx_memcpy(return_url + return_url_idx, server.data, server.len); return_url_idx += server.len; if (port_str_len > 0) { ngx_memcpy(return_url + return_url_idx, port_str, port_str_len); return_url_idx += port_str_len; } if (uri_escaped.len > 0) { ngx_memcpy(return_url + return_url_idx, uri_escaped.data, uri_escaped.len); } r->headers_out.location->value.data = (u_char *)return_url; } else { // for non-get requests, redirect to the login page without a return URL r->headers_out.location->value.len = jwtcf->loginurl.len; r->headers_out.location->value.data = jwtcf->loginurl.data; } return NGX_HTTP_MOVED_TEMPORARILY; } // When no redirect is needed, no "Location" header construction is needed, and we can respond with a 401 return NGX_HTTP_UNAUTHORIZED; } // Loads the public key into the location config struct static ngx_int_t load_public_key(ngx_conf_t *cf, auth_jwt_conf_t *conf) { FILE *keyFile = fopen((const char *)conf->keyfile_path.data, "rb"); // Check if file exists or is correctly opened if (keyFile == NULL) { ngx_log_error(NGX_LOG_ERR, cf->log, 0, "failed to open public key file"); return NGX_ERROR; } else { u_long keySize; u_long keySizeRead; // Read file length fseek(keyFile, 0, SEEK_END); keySize = ftell(keyFile); fseek(keyFile, 0, SEEK_SET); if (keySize == 0) { ngx_log_error(NGX_LOG_ERR, cf->log, 0, "invalid public key file size of 0"); return NGX_ERROR; } else { conf->_keyfile.data = ngx_palloc(cf->pool, keySize); keySizeRead = fread(conf->_keyfile.data, 1, keySize, keyFile); fclose(keyFile); if (keySizeRead == keySize) { conf->_keyfile.len = (int)keySize; return NGX_OK; } else { ngx_log_error(NGX_LOG_ERR, cf->log, 0, "public key size %i does not match expected size of %i", keySizeRead, keySize); return NGX_ERROR; } } } } static char *get_jwt(ngx_http_request_t *r, ngx_str_t jwt_location) { static const char *HEADER_PREFIX = "HEADER="; static const char *COOKIE_PREFIX = "COOKIE="; char *jwtPtr = NULL; ngx_log_debug(NGX_LOG_DEBUG, r->connection->log, 0, "jwt_location.len %d", jwt_location.len); if (jwt_location.len > strlen(HEADER_PREFIX) && ngx_strncmp(jwt_location.data, HEADER_PREFIX, strlen(HEADER_PREFIX)) == 0) { ngx_table_elt_t *jwtHeaderVal; jwt_location.data += strlen(HEADER_PREFIX); jwt_location.len -= strlen(HEADER_PREFIX); jwtHeaderVal = search_headers_in(r, jwt_location.data, jwt_location.len); if (jwtHeaderVal != NULL) { static const char *BEARER_PREFIX = "Bearer "; if (ngx_strncasecmp(jwtHeaderVal->value.data, (u_char *)BEARER_PREFIX, strlen(BEARER_PREFIX)) == 0) { ngx_str_t jwtHeaderValWithoutBearer = jwtHeaderVal->value; jwtHeaderValWithoutBearer.data += strlen(BEARER_PREFIX); jwtHeaderValWithoutBearer.len -= strlen(BEARER_PREFIX); jwtPtr = ngx_str_t_to_char_ptr(r->pool, jwtHeaderValWithoutBearer); } else { jwtPtr = ngx_str_t_to_char_ptr(r->pool, jwtHeaderVal->value); } } } else if (jwt_location.len > strlen(COOKIE_PREFIX) && ngx_strncmp(jwt_location.data, COOKIE_PREFIX, strlen(COOKIE_PREFIX)) == 0) { ngx_str_t jwtCookieVal; jwt_location.data += strlen(COOKIE_PREFIX); jwt_location.len -= strlen(COOKIE_PREFIX); #if nginx_version >= 1029006 if (ngx_http_parse_cookie_lines(r, r->headers_in.cookie, &jwt_location, &jwtCookieVal) != NULL) #elif nginx_version >= 1023000 if (ngx_http_parse_multi_header_lines(r, r->headers_in.cookie, &jwt_location, &jwtCookieVal) != NULL) #else if (ngx_http_parse_multi_header_lines(&r->headers_in.cookies, &jwt_location, &jwtCookieVal) != NGX_DECLINED) #endif { jwtPtr = ngx_str_t_to_char_ptr(r->pool, jwtCookieVal); } } return jwtPtr; } TeslaGov-ngx-http-auth-jwt-module-da2d99f/src/ngx_http_auth_jwt_string.c000066400000000000000000000015731521256727700266420ustar00rootroot00000000000000/* * Copyright (C) 2018 Tesla Government * * This software may be modified and distributed under the terms * of the MIT license. See the LICENSE file for details. * * https://github.com/TeslaGov/ngx-http-auth-jwt-module */ #include #include "ngx_http_auth_jwt_string.h" /** copies an nginx string structure to a newly allocated character pointer */ char* ngx_str_t_to_char_ptr(ngx_pool_t *pool, ngx_str_t str) { char* char_ptr = ngx_palloc(pool, str.len + 1); ngx_memcpy(char_ptr, str.data, str.len); *(char_ptr + str.len) = '\0'; return char_ptr; } /** copies a character pointer string to an nginx string structure */ ngx_str_t char_ptr_to_ngx_str_t(ngx_pool_t *pool, const char* char_ptr) { const int len = strlen(char_ptr); ngx_str_t str_t; str_t.len = len; str_t.data = ngx_palloc(pool, len); ngx_memcpy(str_t.data, char_ptr, len); return str_t; }TeslaGov-ngx-http-auth-jwt-module-da2d99f/src/ngx_http_auth_jwt_string.h000066400000000000000000000007701521256727700266450ustar00rootroot00000000000000/* * Copyright (C) 2018 Tesla Government * * This software may be modified and distributed under the terms * of the MIT license. See the LICENSE file for details. * * https://github.com/TeslaGov/ngx-http-auth-jwt-module */ #ifndef _NGX_HTTP_AUTH_JWT_STRING_H #define _NGX_HTTP_AUTH_JWT_STRING_H #include char* ngx_str_t_to_char_ptr(ngx_pool_t *pool, ngx_str_t str); ngx_str_t char_ptr_to_ngx_str_t(ngx_pool_t *pool, const char* char_ptr); #endif /* _NGX_HTTP_AUTH_JWT_STRING_H */TeslaGov-ngx-http-auth-jwt-module-da2d99f/test/000077500000000000000000000000001521256727700215325ustar00rootroot00000000000000TeslaGov-ngx-http-auth-jwt-module-da2d99f/test/docker-compose-test.yml000066400000000000000000000015131521256727700261440ustar00rootroot00000000000000name: ngx-http-auth-jwt-test services: nginx: container_name: ${TEST_CONTAINER_NAME_PREFIX:?required}-nginx build: context: . dockerfile: test-nginx.dockerfile platforms: - linux/amd64 args: BASE_IMAGE: ${FULL_IMAGE_NAME}:${NGINX_VERSION:?required} platform: linux/amd64 pull_policy: always logging: driver: ${LOG_DRIVER:-json-file} healthcheck: test: ["CMD", "curl", "-f", "http://nginx:${PORT:-8000}/ping"] interval: 30s timeout: 5s retries: 3 runner: container_name: ${TEST_CONTAINER_NAME_PREFIX:?required}-runner build: context: . dockerfile: test-runner.dockerfile platforms: - linux/amd64 platform: linux/amd64 pull_policy: always depends_on: nginx: condition: service_healthy TeslaGov-ngx-http-auth-jwt-module-da2d99f/test/etc/000077500000000000000000000000001521256727700223055ustar00rootroot00000000000000TeslaGov-ngx-http-auth-jwt-module-da2d99f/test/etc/nginx/000077500000000000000000000000001521256727700234305ustar00rootroot00000000000000TeslaGov-ngx-http-auth-jwt-module-da2d99f/test/etc/nginx/conf.d/000077500000000000000000000000001521256727700245775ustar00rootroot00000000000000TeslaGov-ngx-http-auth-jwt-module-da2d99f/test/etc/nginx/conf.d/test.conf000066400000000000000000000340641521256727700264340ustar00rootroot00000000000000error_log /var/log/nginx/debug.log debug; access_log /var/log/nginx/access.log; log_format extract_test 'Log extract test sub: $jwt_claim_sub'; server { listen %{PORT}; listen %{SSL_PORT} ssl; server_name localhost; ssl_certificate /etc/nginx/test.crt; ssl_certificate_key /etc/nginx/test.key; ssl_protocols TLSv1.2 TLSv1.3; ssl_ciphers HIGH:!aNULL:!MD5; auth_jwt_key "00112233445566778899AABBCCDDEEFF00112233445566778899AABBCCDDEEFF"; auth_jwt_loginurl "https://example.com/login"; auth_jwt_enabled off; location /ping { return 200 "pong"; } location / { alias /usr/share/nginx/html/; try_files index.html =404; } location /secure/cookie/default { auth_jwt_enabled on; auth_jwt_redirect on; auth_jwt_location COOKIE=jwt; alias /usr/share/nginx/html/; try_files index.html =404; } location /secure/cookie/default/validate-sub { auth_jwt_enabled on; auth_jwt_redirect on; auth_jwt_validate_sub on; auth_jwt_location COOKIE=jwt; alias /usr/share/nginx/html/; try_files index.html =404; } location /secure/cookie/default/no-redirect { auth_jwt_enabled on; auth_jwt_redirect off; auth_jwt_location COOKIE=jwt; alias /usr/share/nginx/html/; try_files index.html =404; } location /secure/cookie/hs256 { auth_jwt_enabled on; auth_jwt_redirect on; auth_jwt_location COOKIE=jwt; auth_jwt_algorithm HS256; alias /usr/share/nginx/html/; try_files index.html =404; } location /secure/cookie/hs384 { auth_jwt_enabled on; auth_jwt_redirect on; auth_jwt_location COOKIE=jwt; auth_jwt_algorithm HS384; alias /usr/share/nginx/html/; try_files index.html =404; } location /secure/cookie/hs512 { auth_jwt_enabled on; auth_jwt_redirect on; auth_jwt_location COOKIE=jwt; auth_jwt_algorithm HS512; alias /usr/share/nginx/html/; try_files index.html =404; } location /secure/cookie/es256 { auth_jwt_enabled on; auth_jwt_redirect on; auth_jwt_location COOKIE=jwt; auth_jwt_algorithm ES256; auth_jwt_key "-----BEGIN PUBLIC KEY----- MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEz9TaQ872ZindjMOoSK7+J81Hzbuz ipk+Vbi+S4b1IM06lRrscHrwOw5RbDVUnwpYyARt4HoqN33HeJNGfUsoCw== -----END PUBLIC KEY-----"; alias /usr/share/nginx/html/; try_files index.html =404; } location /secure/cookie/es384 { auth_jwt_enabled on; auth_jwt_redirect on; auth_jwt_location COOKIE=jwt; auth_jwt_algorithm ES384; auth_jwt_key "-----BEGIN PUBLIC KEY----- MHYwEAYHKoZIzj0CAQYFK4EEACIDYgAEOPHTnyBHd9EwlRNCU+GVNA2+xbc/nse5 aIz+7qk2PTiOfcqDAdNc1DeCSt9AUV0vWL9APQbtc34C7vdxUp9JHdMyDzL7ruS1 LetDP5okrdpQNDq6hYSo8ehRXTf5TYeu -----END PUBLIC KEY-----"; alias /usr/share/nginx/html/; try_files index.html =404; } location /secure/cookie/es512 { auth_jwt_enabled on; auth_jwt_redirect on; auth_jwt_location COOKIE=jwt; auth_jwt_algorithm ES512; auth_jwt_key "-----BEGIN PUBLIC KEY----- MIGbMBAGByqGSM49AgEGBSuBBAAjA4GGAAQAaU4qUr27PH6uOLuHwPr+w0zWFYgO fLKm68Y8Xc8FmfHhIwl7B+8s0hsd2NfTUsq1/B241pq4+bsYinhQgfJhs08A3BSC gjHjJB42bP35lTrpkWtl0qY+wT+sKnDej3/lKhAorxgGRkR6Gm6P3ZucxfTN4Dvh vXjq39xtcIBRTO1c2zs= -----END PUBLIC KEY-----"; alias /usr/share/nginx/html/; try_files index.html =404; } location /secure/auth-header/default { auth_jwt_enabled on; auth_jwt_redirect on; auth_jwt_location HEADER=Authorization; alias /usr/share/nginx/html/; try_files index.html =404; } location /secure/auth-header/default/no-redirect { auth_jwt_enabled on; auth_jwt_redirect off; auth_jwt_location HEADER=Authorization; alias /usr/share/nginx/html/; try_files index.html =404; } location /secure/auth-header/default/proxy-header { auth_jwt_enabled on; auth_jwt_redirect off; auth_jwt_location HEADER=Authorization; add_header "Test-Authorization" "$http_authorization"; alias /usr/share/nginx/html/; try_files index.html =404; } location /secure/auth-header/rs256 { auth_jwt_enabled on; auth_jwt_redirect on; auth_jwt_location HEADER=Authorization; auth_jwt_key "-----BEGIN PUBLIC KEY----- MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAwtpMAM4l1H995oqlqdMh uqNuffp4+4aUCwuFE9B5s9MJr63gyf8jW0oDr7Mb1Xb8y9iGkWfhouZqNJbMFry+ iBs+z2TtJF06vbHQZzajDsdux3XVfXv9v6dDIImyU24MsGNkpNt0GISaaiqv51NM ZQX0miOXXWdkQvWTZFXhmsFCmJLE67oQFSar4hzfAaCulaMD+b3Mcsjlh0yvSq7g 6swiIasEU3qNLKaJAZEzfywroVYr3BwM1IiVbQeKgIkyPS/85M4Y6Ss/T+OWi1Oe K49NdYBvFP+hNVEoeZzJz5K/nd6C35IX0t2bN5CVXchUFmaUMYk2iPdhXdsC720t BwIDAQAB -----END PUBLIC KEY-----"; alias /usr/share/nginx/html/; try_files index.html =404; } location /secure/auth-header/es256 { auth_jwt_enabled on; auth_jwt_redirect on; auth_jwt_location HEADER=Authorization; auth_jwt_key "-----BEGIN PUBLIC KEY----- MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEz9TaQ872ZindjMOoSK7+J81Hzbuz ipk+Vbi+S4b1IM06lRrscHrwOw5RbDVUnwpYyARt4HoqN33HeJNGfUsoCw== -----END PUBLIC KEY-----"; alias /usr/share/nginx/html/; try_files index.html =404; } location /secure/auth-header/es384 { auth_jwt_enabled on; auth_jwt_redirect on; auth_jwt_location HEADER=Authorization; auth_jwt_key "-----BEGIN PUBLIC KEY----- MHYwEAYHKoZIzj0CAQYFK4EEACIDYgAEOPHTnyBHd9EwlRNCU+GVNA2+xbc/nse5 aIz+7qk2PTiOfcqDAdNc1DeCSt9AUV0vWL9APQbtc34C7vdxUp9JHdMyDzL7ruS1 LetDP5okrdpQNDq6hYSo8ehRXTf5TYeu -----END PUBLIC KEY-----"; alias /usr/share/nginx/html/; try_files index.html =404; } location /secure/auth-header/es512 { auth_jwt_enabled on; auth_jwt_redirect on; auth_jwt_location HEADER=Authorization; auth_jwt_key "-----BEGIN PUBLIC KEY----- MIGbMBAGByqGSM49AgEGBSuBBAAjA4GGAAQAaU4qUr27PH6uOLuHwPr+w0zWFYgO fLKm68Y8Xc8FmfHhIwl7B+8s0hsd2NfTUsq1/B241pq4+bsYinhQgfJhs08A3BSC gjHjJB42bP35lTrpkWtl0qY+wT+sKnDej3/lKhAorxgGRkR6Gm6P3ZucxfTN4Dvh vXjq39xtcIBRTO1c2zs= -----END PUBLIC KEY-----"; alias /usr/share/nginx/html/; try_files index.html =404; } location /secure/auth-header/rs256/file { auth_jwt_enabled on; auth_jwt_redirect on; auth_jwt_location HEADER=Authorization; auth_jwt_algorithm RS256; auth_jwt_use_keyfile on; auth_jwt_keyfile_path "/etc/nginx/rsa_key_2048-pub.pem"; alias /usr/share/nginx/html/; try_files index.html =404; } location /secure/auth-header/rs384/file { auth_jwt_enabled on; auth_jwt_redirect on; auth_jwt_location HEADER=Authorization; auth_jwt_algorithm RS384; auth_jwt_use_keyfile on; auth_jwt_keyfile_path "/etc/nginx/rsa_key_2048-pub.pem"; alias /usr/share/nginx/html/; try_files index.html =404; } location /secure/auth-header/rs512/file { auth_jwt_enabled on; auth_jwt_redirect on; auth_jwt_location HEADER=Authorization; auth_jwt_algorithm RS512; auth_jwt_use_keyfile on; auth_jwt_keyfile_path "/etc/nginx/rsa_key_2048-pub.pem"; alias /usr/share/nginx/html/; try_files index.html =404; } location /secure/auth-header/es256/file { auth_jwt_enabled on; auth_jwt_redirect on; auth_jwt_location HEADER=Authorization; auth_jwt_algorithm ES256; auth_jwt_use_keyfile on; auth_jwt_keyfile_path "/etc/nginx/ec_key_256-pub.pem"; alias /usr/share/nginx/html/; try_files index.html =404; } location /secure/auth-header/es384/file { auth_jwt_enabled on; auth_jwt_redirect on; auth_jwt_location HEADER=Authorization; auth_jwt_algorithm ES384; auth_jwt_use_keyfile on; auth_jwt_keyfile_path "/etc/nginx/ec_key_384-pub.pem"; alias /usr/share/nginx/html/; try_files index.html =404; } location /secure/auth-header/es512/file { auth_jwt_enabled on; auth_jwt_redirect on; auth_jwt_location HEADER=Authorization; auth_jwt_algorithm ES512; auth_jwt_use_keyfile on; auth_jwt_keyfile_path "/etc/nginx/ec_key_521-pub.pem"; alias /usr/share/nginx/html/; try_files index.html =404; } location /secure/custom-header/hs256 { auth_jwt_enabled on; auth_jwt_redirect on; auth_jwt_location HEADER=Auth-Token; auth_jwt_algorithm HS256; alias /usr/share/nginx/html/; try_files index.html =404; } location /secure/extract-claim/request/sub { auth_jwt_enabled on; auth_jwt_redirect off; auth_jwt_location HEADER=Authorization; auth_jwt_extract_request_claims sub; add_header "Test" "sub=$http_jwt_sub"; alias /usr/share/nginx/html/; try_files index.html =404; } location /secure/extract-claim/request/name-1 { auth_jwt_enabled on; auth_jwt_redirect off; auth_jwt_location HEADER=Authorization; auth_jwt_extract_request_claims firstName lastName; add_header "Test" "firstName=$http_jwt_firstname; lastName=$http_jwt_lastname"; alias /usr/share/nginx/html/; try_files index.html =404; } location /secure/extract-claim/request/name-2 { auth_jwt_enabled on; auth_jwt_redirect off; auth_jwt_location HEADER=Authorization; auth_jwt_extract_request_claims firstName; auth_jwt_extract_request_claims lastName; add_header "Test" "firstName=$http_jwt_firstname; lastName=$http_jwt_lastname"; alias /usr/share/nginx/html/; try_files index.html =404; } location /secure/extract-claim/request/nested { location /secure/extract-claim/request/nested { auth_jwt_enabled on; auth_jwt_redirect off; auth_jwt_location HEADER=Authorization; auth_jwt_extract_request_claims username; add_header "Test" "username=$http_jwt_username"; alias /usr/share/nginx/html/; try_files index.html =404; } } location /secure/extract-claim/response/sub { auth_jwt_enabled on; auth_jwt_redirect off; auth_jwt_location HEADER=Authorization; auth_jwt_extract_response_claims sub; add_header "Test" "sub=$sent_http_jwt_sub"; alias /usr/share/nginx/html/; try_files index.html =404; } location /secure/extract-claim/response/name-1 { auth_jwt_enabled on; auth_jwt_redirect off; auth_jwt_location HEADER=Authorization; auth_jwt_extract_response_claims firstName lastName; add_header "Test" "firstName=$sent_http_jwt_firstname; lastName=$sent_http_jwt_lastname"; alias /usr/share/nginx/html/; try_files index.html =404; } location /secure/extract-claim/response/name-2 { auth_jwt_enabled on; auth_jwt_redirect off; auth_jwt_location HEADER=Authorization; auth_jwt_extract_response_claims firstName; auth_jwt_extract_response_claims lastName; add_header "Test" "firstName=$sent_http_jwt_firstname; lastName=$sent_http_jwt_lastname"; alias /usr/share/nginx/html/; try_files index.html =404; } location /secure/extract-claim/response/nested { location /secure/extract-claim/response/nested { auth_jwt_enabled on; auth_jwt_redirect off; auth_jwt_location HEADER=Authorization; auth_jwt_extract_response_claims username; add_header "Test" "username=$sent_http_jwt_username"; alias /usr/share/nginx/html/; try_files index.html =404; } } location /secure/extract-claim/if/sub { auth_jwt_enabled on; auth_jwt_redirect off; auth_jwt_location HEADER=Authorization; auth_jwt_extract_var_claims sub; if ($jwt_claim_sub = 'some-long-uuid') { return 200; } return 401; } location /secure/extract-claim/body/sub { auth_jwt_enabled on; auth_jwt_redirect off; auth_jwt_location HEADER=Authorization; auth_jwt_extract_var_claims sub; return 200 "sub: $jwt_claim_sub"; } location /unsecure/extract-claim/body/sub { auth_jwt_enabled off; auth_jwt_redirect off; auth_jwt_location HEADER=Authorization; auth_jwt_extract_var_claims sub; return 200 "sub: $jwt_claim_sub"; } location /secure/extract-claim/body/multiple { auth_jwt_enabled on; auth_jwt_redirect off; auth_jwt_location HEADER=Authorization; auth_jwt_validate_sub on; auth_jwt_extract_var_claims firstName middleName lastName; return 200 "you are: $jwt_claim_firstName $jwt_claim_middleName $jwt_claim_lastName"; } location /profile { auth_jwt_enabled on; auth_jwt_redirect off; auth_jwt_location HEADER=Authorization; auth_jwt_validate_sub on; location /profile/me { auth_jwt_extract_var_claims sub; return 301 /profile/$jwt_claim_sub; } } location /return-url { auth_jwt_enabled on; auth_jwt_redirect on; } location /log { auth_jwt_enabled on; auth_jwt_redirect off; auth_jwt_location HEADER=Authorization; auth_jwt_validate_sub on; auth_jwt_extract_var_claims sub; access_log /var/log/nginx/test_access.log extract_test; return 200 "Log extract test sub: $jwt_claim_sub"; } location /enabled/variable { auth_jwt_enabled $jwt_enabled; alias /usr/share/nginx/html/; try_files index.html =404; } } map $http_test_auth_enabled $jwt_enabled { default on; on on; off off; } TeslaGov-ngx-http-auth-jwt-module-da2d99f/test/etc/nginx/ec_key_256-pub.pem000066400000000000000000000002621521256727700265520ustar00rootroot00000000000000-----BEGIN PUBLIC KEY----- MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEz9TaQ872ZindjMOoSK7+J81Hzbuz ipk+Vbi+S4b1IM06lRrscHrwOw5RbDVUnwpYyARt4HoqN33HeJNGfUsoCw== -----END PUBLIC KEY----- TeslaGov-ngx-http-auth-jwt-module-da2d99f/test/etc/nginx/ec_key_384-pub.pem000066400000000000000000000003271521256727700265560ustar00rootroot00000000000000-----BEGIN PUBLIC KEY----- MHYwEAYHKoZIzj0CAQYFK4EEACIDYgAEOPHTnyBHd9EwlRNCU+GVNA2+xbc/nse5 aIz+7qk2PTiOfcqDAdNc1DeCSt9AUV0vWL9APQbtc34C7vdxUp9JHdMyDzL7ruS1 LetDP5okrdpQNDq6hYSo8ehRXTf5TYeu -----END PUBLIC KEY----- TeslaGov-ngx-http-auth-jwt-module-da2d99f/test/etc/nginx/ec_key_521-pub.pem000066400000000000000000000004141521256727700265440ustar00rootroot00000000000000-----BEGIN PUBLIC KEY----- MIGbMBAGByqGSM49AgEGBSuBBAAjA4GGAAQAaU4qUr27PH6uOLuHwPr+w0zWFYgO fLKm68Y8Xc8FmfHhIwl7B+8s0hsd2NfTUsq1/B241pq4+bsYinhQgfJhs08A3BSC gjHjJB42bP35lTrpkWtl0qY+wT+sKnDej3/lKhAorxgGRkR6Gm6P3ZucxfTN4Dvh vXjq39xtcIBRTO1c2zs= -----END PUBLIC KEY----- TeslaGov-ngx-http-auth-jwt-module-da2d99f/test/etc/nginx/rsa_key_2048-pub.pem000077500000000000000000000007031521256727700270340ustar00rootroot00000000000000-----BEGIN PUBLIC KEY----- MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAwtpMAM4l1H995oqlqdMh uqNuffp4+4aUCwuFE9B5s9MJr63gyf8jW0oDr7Mb1Xb8y9iGkWfhouZqNJbMFry+ iBs+z2TtJF06vbHQZzajDsdux3XVfXv9v6dDIImyU24MsGNkpNt0GISaaiqv51NM ZQX0miOXXWdkQvWTZFXhmsFCmJLE67oQFSar4hzfAaCulaMD+b3Mcsjlh0yvSq7g 6swiIasEU3qNLKaJAZEzfywroVYr3BwM1IiVbQeKgIkyPS/85M4Y6Ss/T+OWi1Oe K49NdYBvFP+hNVEoeZzJz5K/nd6C35IX0t2bN5CVXchUFmaUMYk2iPdhXdsC720t BwIDAQAB -----END PUBLIC KEY----- TeslaGov-ngx-http-auth-jwt-module-da2d99f/test/etc/nginx/test.crt000066400000000000000000000025271521256727700251270ustar00rootroot00000000000000-----BEGIN CERTIFICATE----- MIIDwzCCAqugAwIBAgIUMG9M4Itu0cOyX0+La+7huiIoX6YwDQYJKoZIhvcNAQEL BQAwcTELMAkGA1UEBhMCVVMxETAPBgNVBAgMCFZpcmdpbmlhMRUwEwYDVQQHDAxG YWxscyBDaHVyY2gxHzAdBgNVBAoMFlRlc2xhIEdvdmVybm1lbnQsIEluYy4xFzAV BgNVBAsMDk5HSU5YIEF1dGggSldUMB4XDTI0MDMxNTE4MTM1MloXDTM0MDMxMzE4 MTM1MlowcTELMAkGA1UEBhMCVVMxETAPBgNVBAgMCFZpcmdpbmlhMRUwEwYDVQQH DAxGYWxscyBDaHVyY2gxHzAdBgNVBAoMFlRlc2xhIEdvdmVybm1lbnQsIEluYy4x FzAVBgNVBAsMDk5HSU5YIEF1dGggSldUMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8A MIIBCgKCAQEAih41Ct5XgcSTz7ZVAjBb0t0z9Qae08aseoMEKJf7AmNqKtsvzeAw /DJxOWJR5VPtUWhFAmXxPfG2B6aiSIVJVpG9yzcdQlCvyJG7Ub4QCm5GXwpU+zDC qmD5ksz9QMdOzvRLypAU1ciZiCXjwpUnW+BZyZ9Tpmsxm6/gOzkd3rxoIbc9uXxp 5o4n6k02EPSzLzUhkZnhLQrOAGUB7+q11FAU5eNMlTWC9gQUsbNaTVtKmM2eV9BA UHdX2GbkfFbN22l3Wey4oyNZWmye1ZFOPyBR+tyU3pofhb+R+hTFmeNBzrJq3i30 Qi0B8AnulKdOjnTysPYjDTrN6xcVDWNmPQIDAQABo1MwUTAdBgNVHQ4EFgQUczdy 7s64NJHNGsQTf/zwFnQe6LMwHwYDVR0jBBgwFoAUczdy7s64NJHNGsQTf/zwFnQe 6LMwDwYDVR0TAQH/BAUwAwEB/zANBgkqhkiG9w0BAQsFAAOCAQEAfcxCiz6ShHof lXiE2j+s556SM2n8oW/S1BSjFC2wF1uKVeMJA1gAaWObC3ElqffFlqTdCorhgRS/ knWa+Sqe/jWBSgwLG/e5DvxXWjD7b7kZdAZNy9evs5nhVfcLT+GyvB/z5GdAFY7s xYmLrC07ubhHIL9h7lhNKbRr++o+BcClQBZKRO4fxBwXxqx/rHudjH87Wr61Ov52 90xNjwcqvevY0skmPao5+oyxkURdKZualNxiOGMPpywkpJkfl8Az5xKAJhUMAtFR smhQduejEkcxfxtsiYgVoulI29GAsMr9zHps9zb5k0+SWIiSixjQ0CpRhLcNYu4F QPgLQLGwUQ== -----END CERTIFICATE----- TeslaGov-ngx-http-auth-jwt-module-da2d99f/test/etc/nginx/test.key000066400000000000000000000032501521256727700251210ustar00rootroot00000000000000-----BEGIN PRIVATE KEY----- MIIEvQIBADANBgkqhkiG9w0BAQEFAASCBKcwggSjAgEAAoIBAQCKHjUK3leBxJPP tlUCMFvS3TP1Bp7Txqx6gwQol/sCY2oq2y/N4DD8MnE5YlHlU+1RaEUCZfE98bYH pqJIhUlWkb3LNx1CUK/IkbtRvhAKbkZfClT7MMKqYPmSzP1Ax07O9EvKkBTVyJmI JePClSdb4FnJn1OmazGbr+A7OR3evGghtz25fGnmjifqTTYQ9LMvNSGRmeEtCs4A ZQHv6rXUUBTl40yVNYL2BBSxs1pNW0qYzZ5X0EBQd1fYZuR8Vs3baXdZ7LijI1la bJ7VkU4/IFH63JTemh+Fv5H6FMWZ40HOsmreLfRCLQHwCe6Up06OdPKw9iMNOs3r FxUNY2Y9AgMBAAECggEAAkwEggGp/xb67FCyDJ8rdimTZFPi9U7coUCN8HNI/qrf lTnfvox0oOUUqMMmIIQeS/HJ4ANvZe8GO3QkE8R5Sg7F0yjZL2tyTCNPgOMCMK8E mmHS58brHdrbm658C1ILnfmssjNmNueNbuW00Koa8imCsY2ZEW+L7vTKuMFqg6c+ BDJxC4yoCPwSTVfcajjzI6FVfphE0pd8Ho/sE8vTqdmovh23+vgfNUq1L9Smvf7R YLM+hS1ouRP2BI5AN0sm04Kxd8MKPzuwCxteoZ9Y9YHyr1JeWGTTL0T24+LwUee/ 24zXZFrzpTgmtDYeEuVWsF5bP/fMS4Fctda3pdJMsQKBgQDCANjGDwwfSCCev2kl WdrFJywhn5hWLWFwlo/FwLOsFJtejaBwIDRQCMPZ74H+KMHwUnO3vTanKJWqDRP9 CdMh94C1BqobRV6rN4HgA4Opxim1EyRWHV6ui41zokk2mJrwUzKkR8t9lt9EZKrk ZPyKER9A4hBqBmYvaYxodN8U1QKBgQC2QXUQq9j7niT7t4xMi0e9vnPLs0z1yUK9 0nzKwTHDPflk3o2sKvH7199qVkc15JQ9DQ7NuYD7ezLbE3DJuVzpNDAfNXmfWHmp 7ukdnxyn6ZCmzQY7/fTpJTEGKVQMVCgf2f5ANgxm5EmN0yWRMcEt1VXIwCisY56p o6nwv/1fyQKBgQCJBnIVyjEEszwfBBEvCX0kvVtFUGUXkSv+isl3onkFNPTcXuoP 6B8q3FYAy1MkggMhTAthnqpIfLjhCCWzFspidl8Y/WEOq/uGsUjxQWowcr+onqGO lWX3oKfDIb/WaQkeb5UYRYFr7jE6LGQrt0xL9HX/rOxtBqIMIN/EM7ARFQKBgDAJ zMtaIFUh9+mJFafPRleS7X6RggV+yOKzqkTe6zjlCuk1Z+4rW6Df43lpyFdCKnh1 CqPa805VyK/Jzf69pumo4c44EBiZ/2d1G2i9WZZAj+oHPE9vvq/9J5DSL98YB4Nt uABAvsAYB/Mj5lEA5kQoaPYDADWABH/+LXrRf/1RAoGAUvxPvmpkGMC+KdmjLam7 CPC3+y4MZOyZ11BhOxLhd1K2qcQd9K7tkjUhNxRn5GVzpzOKeFJFtiih2uN+PBNJ oylPR03uk/7D52b1OYaJhs9bQkth//Qk935nyRM26C2vG4tQLfT/cFi5F53n0ZCQ 7e8O6+QY0lZnpvsfnt8YIsM= -----END PRIVATE KEY----- TeslaGov-ngx-http-auth-jwt-module-da2d99f/test/test-nginx.dockerfile000066400000000000000000000006731521256727700256710ustar00rootroot00000000000000ARG BASE_IMAGE FROM ${BASE_IMAGE:?required} ARG PORT ARG SSL_PORT RUN <<` set -e apt-get update apt-get install -y curl apt-get clean ` COPY etc/ /etc/ COPY <<` /usr/share/nginx/html/index.html Test

NGINX Auth-JWT Module Test

` RUN sed -i "s|%{PORT}|${PORT:?required}|" /etc/nginx/conf.d/test.conf RUN sed -i "s|%{SSL_PORT}|${SSL_PORT:?required}|" /etc/nginx/conf.d/test.conf TeslaGov-ngx-http-auth-jwt-module-da2d99f/test/test-runner.dockerfile000066400000000000000000000003671521256727700260570ustar00rootroot00000000000000ARG RUNNER_BASE_IMAGE FROM ${RUNNER_BASE_IMAGE:?required} ARG PORT ARG SSL_PORT ENV PORT=${PORT:?required} ENV SSL_PORT=${SSL_PORT:?required} RUN <<` set -e apt-get update apt-get install -y curl bash ` COPY test.sh . CMD ["./test.sh"] TeslaGov-ngx-http-auth-jwt-module-da2d99f/test/test.sh000077500000000000000000000556261521256727700230660ustar00rootroot00000000000000#!/usr/bin/env bash set -u # set a test # here to execute only that test and output additional info DEBUG= RED='\e[31m' GREEN='\e[32m' GRAY='\e[90m' NC='\e[00m' NUM_TESTS=0; NUM_SKIPPED=0; NUM_FAILED=0; run_test () { NUM_TESTS=$((${NUM_TESTS} + 1)); if [ "${DEBUG}" == '' ] || [ ${DEBUG} == ${NUM_TESTS} ]; then local OPTIND; local name= local path= local expectedCode= local expectedResponseRegex= local extraCurlOpts= local scheme='http' local port=${PORT} local curlCommand= local exitCode= local response= local testNum="${GRAY}${NUM_TESTS}${NC}\t" while getopts "n:asp:r:c:x:" option; do case $option in n) name=$OPTARG;; s) scheme='https' port=${SSL_PORT};; p) path=$OPTARG;; c) expectedCode=$OPTARG;; r) expectedResponseRegex=$OPTARG;; x) extraCurlOpts=$OPTARG;; \?) # Invalid option printf "Error: Invalid option\n"; exit;; esac done printf "\n${testNum}" curlCommand="curl -skv ${scheme}://nginx:${port}${path} -H 'Cache-Control: no-cache' ${extraCurlOpts} 2>&1" trap "" EXIT response=$(eval "${curlCommand}") exitCode=$? if [ "${exitCode}" -ne "0" ]; then printf "${RED}${name} -- unexpected exit code from cURL\n\tcURL Exit Code: ${exitCode}"; NUM_FAILED=$((${NUM_FAILED} + 1)); exitCode= else local okay=1 if [[ -n "${expectedCode}" ]]; then local responseCode=$(echo "${response}" | grep -Eo 'HTTP/1.1 ([0-9]{3})' | awk '{print $2}') if [ "${expectedCode}" != "${responseCode}" ]; then printf "${RED}${name} -- unexpected status code\n\tExpected: ${expectedCode}\n\tActual: ${responseCode}\n\tPath: ${path}" NUM_FAILED=$((${NUM_FAILED} + 1)) okay=0 fi fi if [ "${okay}" == '1' ] && [ "${expectedResponseRegex}" != "" ] && ! [[ "${response}" =~ ${expectedResponseRegex} ]]; then printf "${RED}${name} -- regex not found in response\n\tPath: ${path}\n\tRegEx: ${expectedResponseRegex//%/%%}\n\tactual:${response:-""}" NUM_FAILED=$((${NUM_FAILED} + 1)) okay=0 fi if [ "${okay}" == '1' ]; then printf "${GREEN}${name}"; fi fi if [ "${DEBUG}" == "${NUM_TESTS}" ]; then printf '\n\tcURL Command: %s' "${curlCommand:---}" printf '\n\tResponse: %s' "${response:---}" fi printf "${NC}\n" else NUM_SKIPPED=$((${NUM_SKIPPED} + 1)) fi } main() { local JWT_HS256_VALID=eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJzdWIiOiJzb21lLWxvbmctdXVpZCIsImZpcnN0TmFtZSI6ImhlbGxvIiwibGFzdE5hbWUiOiJ3b3JsZCIsImVtYWlsQWRkcmVzcyI6ImhlbGxvd29ybGRAZXhhbXBsZS5jb20iLCJyb2xlcyI6WyJ0aGlzIiwidGhhdCIsInRoZW90aGVyIl0sImlzcyI6Imlzc3VlciIsInBlcnNvbklkIjoiNzViYjNjYzctYjkzMy00NGYwLTkzYzYtMTQ3YjA4MmZhZGI1IiwiZXhwIjoxOTA4ODM1MjAwLCJpYXQiOjE0ODg4MTk2MDAsInVzZXJuYW1lIjoiaGVsbG8ud29ybGQifQ.r8tG8IZheiQ-i6HqUYyJj9V6dipgcQ4ZIdxau6QCZDo local JWT_HS256_MISSING_SUB=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJmaXJzdE5hbWUiOiJoZWxsbyIsImxhc3ROYW1lIjoid29ybGQiLCJlbWFpbEFkZHJlc3MiOiJoZWxsb3dvcmxkQGV4YW1wbGUuY29tIiwicm9sZXMiOlsidGhpcyIsInRoYXQiLCJ0aGVvdGhlciJdLCJpc3MiOiJpc3N1ZXIiLCJwZXJzb25JZCI6Ijc1YmIzY2M3LWI5MzMtNDRmMC05M2M2LTE0N2IwODJmYWRiNSIsImV4cCI6MTkwODgzNTIwMCwiaWF0IjoxNDg4ODE5NjAwLCJ1c2VybmFtZSI6ImhlbGxvLndvcmxkIn0.lD6jUsazVtzeGhRTNeP_b2Zs6O798V2FQql11QOEI1Q local JWT_HS256_MISSING_EMAIL=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiJzb21lLWxvbmctdXVpZCIsImZpcnN0TmFtZSI6ImhlbGxvIiwibGFzdE5hbWUiOiJ3b3JsZCIsInJvbGVzIjpbInRoaXMiLCJ0aGF0IiwidGhlb3RoZXIiXSwiaXNzIjoiaXNzdWVyIiwicGVyc29uSWQiOiI3NWJiM2NjNy1iOTMzLTQ0ZjAtOTNjNi0xNDdiMDgyZmFkYjUiLCJleHAiOjE5MDg4MzUyMDAsImlhdCI6MTQ4ODgxOTYwMCwidXNlcm5hbWUiOiJoZWxsby53b3JsZCJ9.tJoAl_pvq95hK7GKqsp5TU462pLTbmSYZc1fAHzcqWM local JWT_HS384_VALID=eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzM4NCJ9.eyJzdWIiOiJzb21lLWxvbmctdXVpZCIsImZpcnN0TmFtZSI6ImhlbGxvIiwibGFzdE5hbWUiOiJ3b3JsZCIsImVtYWlsQWRkcmVzcyI6ImhlbGxvd29ybGRAZXhhbXBsZS5jb20iLCJyb2xlcyI6WyJ0aGlzIiwidGhhdCIsInRoZW90aGVyIl0sImlzcyI6Imlzc3VlciIsInBlcnNvbklkIjoiNzViYjNjYzctYjkzMy00NGYwLTkzYzYtMTQ3YjA4MmZhZGI1IiwiZXhwIjoxOTA4ODM1MjAwLCJpYXQiOjE0ODg4MTk2MDAsInVzZXJuYW1lIjoiaGVsbG8ud29ybGQifQ.SS57j7PEybjbsp3g5W-IhhJHBmG5K-97qvgBKL16xj9ey-uMeEenWjGbB2vVp0kq local JWT_HS512_VALID=eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzUxMiJ9.eyJzdWIiOiJzb21lLWxvbmctdXVpZCIsImZpcnN0TmFtZSI6ImhlbGxvIiwibGFzdE5hbWUiOiJ3b3JsZCIsImVtYWlsQWRkcmVzcyI6ImhlbGxvd29ybGRAZXhhbXBsZS5jb20iLCJyb2xlcyI6WyJ0aGlzIiwidGhhdCIsInRoZW90aGVyIl0sImlzcyI6Imlzc3VlciIsInBlcnNvbklkIjoiNzViYjNjYzctYjkzMy00NGYwLTkzYzYtMTQ3YjA4MmZhZGI1IiwiZXhwIjoxOTA4ODM1MjAwLCJpYXQiOjE0ODg4MTk2MDAsInVzZXJuYW1lIjoiaGVsbG8ud29ybGQifQ.xtSU6EWN2LILVsYzJFJpKnRkqjn_3qjz-J2ttNKnhZ60_5YjFeC8io4k8k1u77zlohSWvWMdugD9ZaB3vjJo-w local JWT_RS256_VALID=eyJ0eXAiOiJKV1QiLCJhbGciOiJSUzI1NiJ9.eyJzdWIiOiJzb21lLWxvbmctdXVpZCIsImZpcnN0TmFtZSI6ImhlbGxvIiwgImxhc3ROYW1lIjoid29ybGQiLCJlbWFpbEFkZHJlc3MiOiJoZWxsb3dvcmxkQGV4YW1wbGUuY29tIiwgInJvbGVzIjpbInRoaXMiLCJ0aGF0IiwidGhlb3RoZXIiXSwgImlzcyI6Imlzc3VlciIsInBlcnNvbklkIjoiNzViYjNjYzctYjkzMy00NGYwLTkzYzYtMTQ3YjA4MmZhZGI1IiwgImV4cCI6MTkwODgzNTIwMCwiaWF0IjoxNDg4ODE5NjAwLCJ1c2VybmFtZSI6ImhlbGxvLndvcmxkIn0.cn5Gb75XL-r7TMsPuqzWoKZ06ZsyF_VZIG0Ohn8uZZFeF8dFUhSrEOYe8WFN6Eon8a8LC0OCI9eNdGiD4m_e9TD1Iz2juqaeos-6yd7SWuODr4YS8KD3cqfXndnLRPzp9PC_UIpATsbqOmxGDrRKvHsQq0TuIXImU3rM_m3kFJFgtoJFHx3KmZUo_Ozkyhhc6Pukikhy6odNAtEyLHP5_tabMXtkeAuIlG8dhjAxef4mJLexYFclG-vl7No5VBU4JrMbfgyxtobcYoE-bDIpmQHywrwo6Li7X0hgHJ17sfS3G2YMHmE-Ij_W2Lf9kf5r2r12DUvg44SLIfM58pCINQ local JWT_RS256_INVALID=eyJhbGciOiJIUzI1NiJ9.eyJzdWIiOiJzb21lLWxvbmctdXVpZCIsImZpcnN0TmFtZSI6ImhlbGxvIiwibGFzdE5hbWUiOiJ3b3JsZCIsImVtYWlsQWRkcmVzcyI6ImhlbGxvd29ybGRAZXhhbXBsZS5jb20iLCJyb2xlcyI6WyJ0aGlzIiwidGhhdCIsInRoZW90aGVyIl0sImlzcyI6Imlzc3VlciIsInBlcnNvbklkIjoiNzViYjNjYzctYjkzMy00NGYwLTkzYzYtMTQ3YjA4MmZhZGI1IiwiZXhwIjoxOTA4ODM1MjAwLCJpYXQiOjE0ODg4MTk2MDAsInVzZXJuYW1lIjoiaGVsbG8ud29ybGQifQ._aQmIBL4CVBxU1fNMOHp0kkagFaaX2TvAEenizytwd0 local JWT_RS384_VALID=eyJ0eXAiOiJKV1QiLCJhbGciOiJSUzM4NCJ9.eyJzdWIiOiJzb21lLWxvbmctdXVpZCIsImZpcnN0TmFtZSI6ImhlbGxvIiwibGFzdE5hbWUiOiJ3b3JsZCIsImVtYWlsQWRkcmVzcyI6ImhlbGxvd29ybGRAZXhhbXBsZS5jb20iLCJyb2xlcyI6WyJ0aGlzIiwidGhhdCIsInRoZW90aGVyIl0sImlzcyI6Imlzc3VlciIsInBlcnNvbklkIjoiNzViYjNjYzctYjkzMy00NGYwLTkzYzYtMTQ3YjA4MmZhZGI1IiwiZXhwIjoxOTA4ODM1MjAwLCJpYXQiOjE0ODg4MTk2MDAsInVzZXJuYW1lIjoiaGVsbG8ud29ybGQifQ.H35bTcZRhepWIoa8pKCbUMRuAOkVX9K5hJjc6tPmQwWmTw8lrktsvmMzJg_rgqnJLnAkciSIQw5EDj7fngS5zX2ThyRxrkPuE2Uiyw2Ect-mo9Kg1lrWgnyZCuCgq-Up9HQRAv0160mePlm8Gs4TOY6CPr38zwTcDZsy_Keq93igDQV8WuuWAGICaGd5ZyUOPjjzGShRjTU8Szz7fnpZpTtYRCYVo0pc5yfRWYm0fdn-4AseyGvd8JJ2xfnAEe4kZOkz7X1MLKtL0slKg3m2PH1lD7HwxIawXRTPWxArhJ9dcTNiDUrqtde2juGwOuMD_zTsb2Jj0_rmRb0Q6aljNw local JWT_RS512_VALID=eyJ0eXAiOiJKV1QiLCJhbGciOiJSUzUxMiJ9.eyJzdWIiOiJzb21lLWxvbmctdXVpZCIsImZpcnN0TmFtZSI6ImhlbGxvIiwibGFzdE5hbWUiOiJ3b3JsZCIsImVtYWlsQWRkcmVzcyI6ImhlbGxvd29ybGRAZXhhbXBsZS5jb20iLCJyb2xlcyI6WyJ0aGlzIiwidGhhdCIsInRoZW90aGVyIl0sImlzcyI6Imlzc3VlciIsInBlcnNvbklkIjoiNzViYjNjYzctYjkzMy00NGYwLTkzYzYtMTQ3YjA4MmZhZGI1IiwiZXhwIjoxOTA4ODM1MjAwLCJpYXQiOjE0ODg4MTk2MDAsInVzZXJuYW1lIjoiaGVsbG8ud29ybGQifQ.iUupyKypfXJ5aZWfItSW-mOmx9a4C4X7Yr5p5Fk8W75ZhkOq0EeNfstTxx870brhkdPovBhO2LYI44_HoH9XicQNL6JnFprE0r61eJFngbuzlhRQiWpq0xYrazJWc9zB7_GgL2ZCwtw-Ts3G23Q0632wVm6-d7MKvG7RS8aEjN-MuVGdtLglH3forpItmFxw-if40EQsBL7hncN_XNcQTO4KPHkqmlpac_oKXRrLFDIIt2tB6OOpvY4QcpERoxexp4pi2f-JoINnWX_dU5JnIs3ypVJLQPfoJvxg8fsg3zYrOvMYnfsqOCYoHtZGK0O7jyfFmcGo5v2hLT-CpoF3Zw local JWT_ES256_VALID=eyJ0eXAiOiJKV1QiLCJhbGciOiJFUzI1NiJ9.eyJzdWIiOiJzb21lLWxvbmctdXVpZCIsImZpcnN0TmFtZSI6ImhlbGxvIiwibGFzdE5hbWUiOiJ3b3JsZCIsImVtYWlsQWRkcmVzcyI6ImhlbGxvd29ybGRAZXhhbXBsZS5jb20iLCJyb2xlcyI6WyJ0aGlzIiwidGhhdCIsInRoZW90aGVyIl0sImlzcyI6Imlzc3VlciIsInBlcnNvbklkIjoiNzViYjNjYzctYjkzMy00NGYwLTkzYzYtMTQ3YjA4MmZhZGI1IiwiZXhwIjoxOTA4ODM1MjAwLCJpYXQiOjE0ODg4MTk2MDAsInVzZXJuYW1lIjoiaGVsbG8ud29ybGQifQ.WFfJXGr5whKHB7arjsTXPTJ6TAsS1LoRxu7Vj2_HrLaIQphWJM6BICf-M3cv52tFzt-XTZb6GxlDgAbHo8z9Zg local JWT_ES256_INVALID=eyJhbGciOiJIUzI1NiJ9.eyJzdWIiOiJzb21lLWxvbmctdXVpZCIsImZpcnN0TmFtZSI6ImhlbGxvIiwibGFzdE5hbWUiOiJ3b3JsZCIsImVtYWlsQWRkcmVzcyI6ImhlbGxvd29ybGRAZXhhbXBsZS5jb20iLCJyb2xlcyI6WyJ0aGlzIiwidGhhdCIsInRoZW90aGVyIl0sImlzcyI6Imlzc3VlciIsInBlcnNvbklkIjoiNzViYjNjYzctYjkzMy00NGYwLTkzYzYtMTQ3YjA4MmZhZGI1IiwiZXhwIjoxOTA4ODM1MjAwLCJpYXQiOjE0ODg4MTk2MDAsInVzZXJuYW1lIjoiaGVsbG8ud29ybGQifQ._aQmIBL4CVBxU1fNMOHp0kkagFaaX2TvAEenizytwd0 local JWT_ES384_VALID=eyJ0eXAiOiJKV1QiLCJhbGciOiJFUzM4NCJ9.eyJzdWIiOiJzb21lLWxvbmctdXVpZCIsImZpcnN0TmFtZSI6ImhlbGxvIiwibGFzdE5hbWUiOiJ3b3JsZCIsImVtYWlsQWRkcmVzcyI6ImhlbGxvd29ybGRAZXhhbXBsZS5jb20iLCJyb2xlcyI6WyJ0aGlzIiwidGhhdCIsInRoZW90aGVyIl0sImlzcyI6Imlzc3VlciIsInBlcnNvbklkIjoiNzViYjNjYzctYjkzMy00NGYwLTkzYzYtMTQ3YjA4MmZhZGI1IiwiZXhwIjoxOTA4ODM1MjAwLCJpYXQiOjE0ODg4MTk2MDAsInVzZXJuYW1lIjoiaGVsbG8ud29ybGQifQ._EFxXYOTAfT3gB3xUfgGR2UyXHeRTlDWqA94oZbB0DDa7YPZTEX9T4C_0ylnOFKZ6irGHZA8vxjgXDH3DZKWwBWcZ-XaQ_Q4Ws2J-AEeLqcl7_CS6q9mFo0Y7vUNEn-W local JWT_ES512_VALID=eyJ0eXAiOiJKV1QiLCJhbGciOiJFUzUxMiJ9.eyJzdWIiOiJzb21lLWxvbmctdXVpZCIsImZpcnN0TmFtZSI6ImhlbGxvIiwibGFzdE5hbWUiOiJ3b3JsZCIsImVtYWlsQWRkcmVzcyI6ImhlbGxvd29ybGRAZXhhbXBsZS5jb20iLCJyb2xlcyI6WyJ0aGlzIiwidGhhdCIsInRoZW90aGVyIl0sImlzcyI6Imlzc3VlciIsInBlcnNvbklkIjoiNzViYjNjYzctYjkzMy00NGYwLTkzYzYtMTQ3YjA4MmZhZGI1IiwiZXhwIjoxOTA4ODM1MjAwLCJpYXQiOjE0ODg4MTk2MDAsInVzZXJuYW1lIjoiaGVsbG8ud29ybGQifQ.AFY4gNCtZNYkrTiijDkV4eKIt2UPMIuJBfZIk69jgI8FSGCQyUIMmIVg0fTvbaSiaryXzcjbG5TCm8a9Vu3KFJutAHGrgvZqcdklxx6Fbk3an3r_CH68n_ncwS3SUV58mDjf0OX8jRuNdudU1L5xYNQdodo-fxPIb1oHXfMJ0CmULDR9 run_test -n 'when auth disabled, should return 200' \ -p '/' \ -c 200 run_test -n '[SSL] when auth disabled, should return 200' \ -s \ -p '/' \ -c 200 run_test -n 'when auth enabled with default algorithm and no JWT in Authorization header, returns 302' \ -p '/secure/auth-header/default' \ -c 302 run_test -n '[SSL] when auth enabled with default algorithm and no JWT in Authorization header, returns 302' \ -s \ -p '/secure/auth-header/default' \ -c 302 run_test -n 'when auth enabled with default algorithm with no redirect and Authorization header missing Bearer, should return 200' \ -p '/secure/auth-header/default/no-redirect' \ -c 200 \ -x "--header \"Authorization: ${JWT_HS256_VALID}\"" run_test -n 'when auth enabled with default algorithm with no redirect and Authorization header with Bearer, should return 200' \ -p '/secure/auth-header/default/no-redirect' \ -c 200 \ -x "--header \"Authorization: Bearer ${JWT_HS256_VALID}\"" run_test -n 'when auth enabled with Authorization header with Bearer, should keep header intact' \ -p '/secure/auth-header/default/proxy-header' \ -c 200 \ -r "< Test-Authorization: Bearer ${JWT_HS256_VALID}" \ -x "--header \"Authorization: Bearer ${JWT_HS256_VALID}\"" run_test -n 'when auth enabled with Authorization header with Bearer, lower-case "bearer" should be accepted' \ -p '/secure/auth-header/default/proxy-header' \ -c 200 \ -r "< Test-Authorization: bearer ${JWT_HS256_VALID}" \ -x "--header \"Authorization: bearer ${JWT_HS256_VALID}\"" run_test -n 'when auth enabled with default algorithm and no JWT cookie, returns 302' \ -p '/secure/cookie/default' \ -c 302 run_test -n 'when auth enabled with default algorithm with no redirect and no JWT cookie, should return 401' \ -p '/secure/cookie/default/no-redirect' \ -c 401 run_test -n 'when auth enabled with default algorithm and valid JWT cookie, returns 200' \ -p '/secure/cookie/default' \ -c 200 \ -x "--cookie jwt=${JWT_HS256_VALID}" run_test -n 'when auth enabled with default algorithm and valid JWT cookie with no sub, returns 200' \ -p '/secure/cookie/default' \ -c 200 \ -x ' --cookie "jwt=${JWT_HS256_MISSING_SUB}"' run_test -n 'with JWT cookie before another cookie, returns 200' \ -p '/secure/cookie/default' \ -c 200 \ -x ' --cookie "jwt=${JWT_HS256_MISSING_SUB}; x=y"' run_test -n 'with JWT cookie after another cookie, returns 200' \ -p '/secure/cookie/default' \ -c 200 \ -x ' --cookie "x=y; jwt=${JWT_HS256_MISSING_SUB}"' run_test -n 'when auth enabled with default algorithm and valid JWT cookie with no sub when sub validated, returns 302' \ -p '/secure/cookie/default/validate-sub' \ -c 302 \ -x ' --cookie "jwt=${JWT_HS256_MISSING_SUB}"' run_test -n 'when auth enabled with default algorithm and valid JWT cookie with no email, returns 200' \ -p '/secure/cookie/default' \ -c 200 \ -x ' --cookie "jwt=${JWT_HS256_MISSING_EMAIL}"' run_test -n 'when auth enabled with HS256 algorithm and valid JWT cookie, returns 200' \ -p '/secure/cookie/hs256' \ -c 200 \ -x '--cookie "jwt=${JWT_HS256_VALID}"' run_test -n 'when auth enabled with HS384 algorithm and valid JWT cookie, returns 200' \ -p '/secure/cookie/hs384' \ -c 200 \ -x '--cookie "jwt=${JWT_HS384_VALID}"' run_test -n 'when auth enabled with HS512 algorithm and valid JWT cookie, returns 200' \ -p '/secure/cookie/hs512' \ -c 200 \ -x '--cookie "jwt=${JWT_HS512_VALID}"' run_test -n 'when auth enabled with RS256 algorithm and valid JWT cookie, returns 200' \ -p '/secure/cookie/rs256' \ -c 200 \ -x ' --cookie "jwt=${JWT_RS256_VALID}"' run_test -n 'when auth enabled with ES256 algorithm and valid JWT cookie, returns 200' \ -p '/secure/cookie/es256' \ -c 200 \ -x ' --cookie "jwt=${JWT_ES256_VALID}"' run_test -n 'when auth enabled with ES384 algorithm and valid JWT cookie, returns 200' \ -p '/secure/cookie/es384' \ -c 200 \ -x ' --cookie "jwt=${JWT_ES384_VALID}"' run_test -n 'when auth enabled with ES512 algorithm and valid JWT cookie, returns 200' \ -p '/secure/cookie/es512' \ -c 200 \ -x ' --cookie "jwt=${JWT_ES512_VALID}"' run_test -n 'when auth enabled with RS256 algorithm via file and valid JWT in Authorization header, returns 200' \ -p '/secure/auth-header/rs256/file' \ -c 200 \ -x '--header "Authorization: Bearer ${JWT_RS256_VALID}"' run_test -n 'when auth enabled with RS256 algorithm via file and invalid JWT in Authorization header, returns 401' \ -p '/secure/auth-header/rs256/file' \ -c 302 \ -x '--header "Authorization: Bearer ${JWT_RS256_INVALID}"' run_test -n 'when auth enabled with RS384 algorithm via file and valid JWT in Authorization header, returns 200' \ -p '/secure/auth-header/rs384/file' \ -c 200 \ -x '--header "Authorization: Bearer ${JWT_RS256_VALID}"' run_test -n 'when auth enabled with RS512 algorithm via file and valid JWT in Authorization header, returns 200' \ -p '/secure/auth-header/rs512/file' \ -c 200 \ -x '--header "Authorization: Bearer ${JWT_RS256_VALID}"' run_test -n 'when auth enabled with ES256 algorithm via file and valid JWT in Authorization header, returns 200' \ -p '/secure/auth-header/es256/file' \ -c 200 \ -x '--header "Authorization: Bearer ${JWT_ES256_VALID}"' run_test -n 'when auth enabled with ES256 algorithm via file and invalid JWT in Authorization header, returns 401' \ -p '/secure/auth-header/es256/file' \ -c 302 \ -x '--header "Authorization: Bearer ${JWT_ES256_INVALID}"' run_test -n 'when auth enabled with ES384 algorithm via file and valid JWT in Authorization header, returns 200' \ -p '/secure/auth-header/es384/file' \ -c 200 \ -x '--header "Authorization: Bearer ${JWT_ES384_VALID}"' run_test -n 'when auth enabled with ES512 algorithm via file and valid JWT in Authorization header, returns 200' \ -p '/secure/auth-header/es512/file' \ -c 200 \ -x '--header "Authorization: Bearer ${JWT_ES512_VALID}"' run_test -n 'when auth enabled with HS256 algorithm and valid JWT in custom header without bearer, returns 200' \ -p '/secure/custom-header/hs256/' \ -c 200 \ -x '--header "Auth-Token: ${JWT_HS256_VALID}"' run_test -n 'when auth enabled with HS256 algorithm and valid JWT in custom header with bearer, returns 200' \ -p '/secure/custom-header/hs256/' \ -c 200 \ -x '--header "Auth-Token: Bearer ${JWT_HS256_VALID}"' run_test -n 'extracts single claim to request variable' \ -p '/secure/extract-claim/request/sub' \ -r '< Test: sub=some-long-uuid' \ -x '--header "Authorization: Bearer ${JWT_HS256_VALID}"' run_test -n 'extracts multiple claims (single directive) to request variable' \ -p '/secure/extract-claim/request/name-1' \ -r '< Test: firstName=hello; lastName=world' \ -x '--header "Authorization: Bearer ${JWT_HS256_VALID}"' run_test -n 'extracts multiple claims (multiple directives) to request variable' \ -p '/secure/extract-claim/request/name-2' \ -r '< Test: firstName=hello; lastName=world' \ -x '--header "Authorization: Bearer ${JWT_HS256_VALID}"' run_test -n 'extracts nested claim to request variable' \ -p '/secure/extract-claim/request/nested' \ -r '< Test: username=hello\.world' \ -x '--header "Authorization: Bearer ${JWT_HS256_VALID}"' run_test -n 'extracts single claim to response variable' \ -p '/secure/extract-claim/response/sub' \ -r '< Test: sub=some-long-uuid' \ -x '--header "Authorization: Bearer ${JWT_HS256_VALID}"' run_test -n 'extracts multiple claims (single directive) to response variable' \ -p '/secure/extract-claim/response/name-1' \ -r '< Test: firstName=hello; lastName=world' \ -x '--header "Authorization: Bearer ${JWT_HS256_VALID}"' run_test -n 'extracts multiple claims (multiple directives) to response variable' \ -p '/secure/extract-claim/response/name-2' \ -r '< Test: firstName=hello; lastName=world' \ -x '--header "Authorization: Bearer ${JWT_HS256_VALID}"' run_test -n 'extracts nested claim to response variable' \ -p '/secure/extract-claim/response/nested' \ -r '< Test: username=hello.world' \ -x '--header "Authorization: Bearer ${JWT_HS256_VALID}"' run_test -n 'extracts single claim to response header' \ -p '/secure/extract-claim/response/sub' \ -r '< JWT-sub: some-long-uuid' \ -x '--header "Authorization: Bearer ${JWT_HS256_VALID}"' run_test -n 'extracts multiple claims (single directive) to response header' \ -p '/secure/extract-claim/response/name-1' \ -r '< JWT-firstName: hello' \ -x '--header "Authorization: Bearer ${JWT_HS256_VALID}"' run_test -n 'extracts multiple claims (multiple directives) to response header' \ -p '/secure/extract-claim/response/name-2' \ -r '< JWT-firstName: hello' \ -x '--header "Authorization: Bearer ${JWT_HS256_VALID}"' run_test -n 'extracts nested claim to response header' \ -p '/secure/extract-claim/response/nested' \ -r '< JWT-username: hello\.world' \ -x '--header "Authorization: Bearer ${JWT_HS256_VALID}"' run_test -n 'tests single claim with if statement' \ -p '/secure/extract-claim/if/sub' \ -c 200 \ -x '--header "Authorization: Bearer ${JWT_HS256_VALID}"' run_test -n 'tests absence of single claim with if statement' \ -p '/secure/extract-claim/if/sub' \ -c 401 \ -x '--header "Authorization: Bearer ${JWT_HS256_MISSING_SUB}"' run_test -n 'extracts single claim as var' \ -p '/secure/extract-claim/body/sub' \ -c 200 \ -r 'sub: some-long-uuid$' \ -x '--header "Authorization: Bearer ${JWT_HS256_VALID}"' run_test -n 'fails gracefully when extracting single claim as var with no JWT, auth jwt enabled' \ -p '/secure/extract-claim/body/sub' \ -c 200 \ -r 'sub: ' run_test -n 'fails gracefully when extracting single claim as var with no JWT, auth jwt disbaled' \ -p '/unsecure/extract-claim/body/sub' \ -c 200 \ -r 'sub: ' run_test -n 'extracts multiple claims as vars' \ -p '/secure/extract-claim/body/multiple' \ -c 200 \ -r 'you are: hello world$' \ -x '--header "Authorization: Bearer ${JWT_HS256_VALID}"' run_test -n 'redirect based on claim' \ -p '/profile/me' \ -c 301 \ -r '< Location: http://nginx:8000/profile/some-long-uuid' \ -x '--header "Authorization: Bearer ${JWT_HS256_VALID}"' run_test -n 'returns 302 if auth enabled and no JWT provided' \ -p '/return-url' \ -c 302 run_test -n 'redirects to login if auth enabled and no JWT provided' \ -p '/return-url' \ -r '< Location: https://example\.com/login.*' run_test -n 'adds return_url to login URL when redirected to login' \ -p '/return-url' \ -r '< Location: https://example\.com/login\?return_url=http://nginx.*' run_test -n 'return_url includes port when redirected to login' \ -p '/return-url' \ -r "< Location: https://example\.com/login\?return_url=http://nginx:${PORT}/return-url" run_test -n 'return_url includes query when redirected to login' \ -p '/return-url?test=123' \ -r '< Location: https://example\.com/login\?return_url=http://nginx.*/return-url%3Ftest=123' run_test -n 'access_log extract valid sub' \ -p '/log' \ -c 200 \ -r 'Log extract test sub: some-long-uuid$' \ -x '--header "Authorization: Bearer ${JWT_HS256_VALID}"' run_test -n 'auth_jwt_enabled supports variable (on)' \ -p '/enabled/variable' \ -c 401 \ -x '--header "Test-Auth-Enabled: on"' run_test -n 'auth_jwt_enabled supports variable (off)' \ -p '/enabled/variable' \ -c 200 \ -x '--header "Test-Auth-Enabled: off"' run_test -n 'auth_jwt_enabled supports variable (unset)' \ -p '/enabled/variable' \ -c 401 if [[ "${NUM_FAILED}" = '0' ]]; then printf "\nRan ${NUM_TESTS} tests successfully (skipped ${NUM_SKIPPED}).\n" return 0 else printf "\nRan ${NUM_TESTS} tests: ${GREEN}$((${NUM_TESTS} - ${NUM_FAILED})) passed${NC}; ${RED}${NUM_FAILED} failed${NC}; ${NUM_SKIPPED} skipped\n" return 1 fi } if [ "${DEBUG}" != '' ]; then printf "\n${RED}Some tests will be skipped since DEBUG is set.${NC}\n" fi printf "\n${GRAY}Starting tests using port ${PORT}...${NC}\n" main