From 758ff806949763c6fed6dbc23f9d5b19e256e59c Mon Sep 17 00:00:00 2001 From: Joseph Fitzgerald Date: Tue, 24 Aug 2021 17:16:08 -0400 Subject: [PATCH 01/75] Use nginx.org yum repo instead of epel EPEL wasn't carrying the range of versions of NGINX that the official NGINX was carrying, so I changed from EPEL to NGINX. Also, I changed the ./configure to use the "with-compat" option. At the least, it makes the configure command much simpler... and possibly we might be able to use the module without recompiling (not sure). --- Dockerfile | 19 +++++++++++++------ 1 file changed, 13 insertions(+), 6 deletions(-) diff --git a/Dockerfile b/Dockerfile index 31f1342..2097e5e 100644 --- a/Dockerfile +++ b/Dockerfile @@ -3,14 +3,21 @@ FROM centos:7 LABEL maintainer="TeslaGov" email="developers@teslagov.com" ARG NGINX_VERSION=1.16.1 -ARG JANSSON_VERSION=2.10 -ARG LIBJWT_VERSION=1.9.0 +ARG JANSSON_VERSION=2.13.1 +ARG LIBJWT_VERSION=1.12.0 ENV LD_LIBRARY_PATH=/usr/local/lib ENV PKG_CONFIG_PATH=/usr/local/lib/pkgconfig:/usr/share/pkgconfig -RUN yum -y install https://dl.fedoraproject.org/pub/epel/epel-release-latest-7.noarch.rpm && \ - yum -y update && \ +RUN yum -y install https://dl.fedoraproject.org/pub/epel/epel-release-latest-7.noarch.rpm +RUN echo "" >>/etc/yum.repos.d/nginx.repo +RUN echo "[nginx]" >>/etc/yum.repos.d/nginx.repo +RUN echo "name=nginx repo" >>/etc/yum.repos.d/nginx.repo +RUN echo "baseurl=https://nginx.org/packages/centos/7/x86_64/" >>/etc/yum.repos.d/nginx.repo +RUN echo "gpgcheck=0" >>/etc/yum.repos.d/nginx.repo +RUN echo "enabled=1" >>/etc/yum.repos.d/nginx.repo + +RUN yum -y update && \ yum -y groupinstall 'Development Tools' && \ yum -y install pcre-devel pcre zlib-devel openssl-devel wget cmake check-devel check && \ yum -y install nginx-$NGINX_VERSION @@ -85,7 +92,7 @@ RUN wget http://nginx.org/download/nginx-$NGINX_VERSION.tar.gz && \ rm nginx-$NGINX_VERSION.tar.gz && \ ln -sf nginx-$NGINX_VERSION nginx && \ cd /root/dl/nginx && \ - ./configure --add-dynamic-module=../ngx-http-auth-jwt-module --prefix=/usr/share/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 --http-client-body-temp-path=/var/lib/nginx/tmp/client_body --http-proxy-temp-path=/var/lib/nginx/tmp/proxy --http-fastcgi-temp-path=/var/lib/nginx/tmp/fastcgi --http-uwsgi-temp-path=/var/lib/nginx/tmp/uwsgi --http-scgi-temp-path=/var/lib/nginx/tmp/scgi --pid-path=/run/nginx.pid --lock-path=/run/lock/subsys/nginx --user=nginx --group=nginx --with-file-aio --with-ipv6 --with-http_ssl_module --with-http_v2_module --with-http_realip_module --with-stream_ssl_preread_module --with-http_addition_module --with-http_xslt_module=dynamic --with-http_image_filter_module=dynamic --with-http_sub_module --with-http_dav_module --with-http_flv_module --with-http_mp4_module --with-http_gunzip_module --with-http_gzip_static_module --with-http_random_index_module --with-http_secure_link_module --with-http_degradation_module --with-http_slice_module --with-http_stub_status_module --with-http_perl_module=dynamic --with-http_auth_request_module --with-mail=dynamic --with-mail_ssl_module --with-pcre --with-pcre-jit --with-stream=dynamic --with-stream_ssl_module --with-google_perftools_module --with-debug --with-cc-opt='-O2 -g -pipe -Wall -Wp,-D_FORTIFY_SOURCE=2 -fexceptions -fstack-protector-strong --param=ssp-buffer-size=4 -grecord-gcc-switches -specs=/usr/lib/rpm/redhat/redhat-hardened-cc1 -m64 -mtune=generic -std=gnu99' --with-ld-opt='-Wl,-z,relro -specs=/usr/lib/rpm/redhat/redhat-hardened-ld -Wl,-E' && \ + ./configure --with-compat --add-dynamic-module=../ngx-http-auth-jwt-module --with-cc-opt='-std=gnu99' && \ make modules && \ cp /root/dl/nginx/objs/ngx_http_auth_jwt_module.so /usr/lib64/nginx/modules/. @@ -101,4 +108,4 @@ RUN cp -r /usr/share/nginx/html /usr/share/nginx/secure-no-redirect ENTRYPOINT ["/usr/sbin/nginx"] -EXPOSE 8000 \ No newline at end of file +EXPOSE 8000 From 59ed4f97cf461401c901e49b0b951d2a39c339fd Mon Sep 17 00:00:00 2001 From: Joseph Fitzgerald Date: Tue, 24 Aug 2021 17:20:32 -0400 Subject: [PATCH 02/75] Fix possible overflow - thanks @eutychus @eutychus contributed this fix that checks to make sure that the Authorization header contains at least "Bearer: " --- src/ngx_http_auth_jwt_module.c | 14 ++++++++++---- test.sh | 4 +++- 2 files changed, 13 insertions(+), 5 deletions(-) diff --git a/src/ngx_http_auth_jwt_module.c b/src/ngx_http_auth_jwt_module.c index a44e3b9..4395d5e 100644 --- a/src/ngx_http_auth_jwt_module.c +++ b/src/ngx_http_auth_jwt_module.c @@ -413,6 +413,7 @@ static char * getJwt(ngx_http_request_t *r, ngx_str_t auth_jwt_validation_type) char* jwtCookieValChrPtr = NULL; ngx_str_t jwtCookieVal; ngx_int_t n; + ngx_int_t bearer_length; ngx_str_t authorizationHeaderStr; ngx_log_error(NGX_LOG_DEBUG, r->connection->log, 0, "auth_jwt_validation_type.len %d", auth_jwt_validation_type.len); @@ -425,12 +426,17 @@ static char * getJwt(ngx_http_request_t *r, ngx_str_t auth_jwt_validation_type) { ngx_log_error(NGX_LOG_DEBUG, r->connection->log, 0, "Found authorization header len %d", authorizationHeader->value.len); - authorizationHeaderStr.data = authorizationHeader->value.data + sizeof("Bearer ") - 1; - authorizationHeaderStr.len = authorizationHeader->value.len - (sizeof("Bearer ") - 1); + bearer_length = authorizationHeader->value.len - (sizeof("Bearer ") - 1); - jwtCookieValChrPtr = ngx_str_t_to_char_ptr(r->pool, authorizationHeaderStr); + if (bearer_length > 0) + { + authorizationHeaderStr.data = authorizationHeader->value.data + sizeof("Bearer ") - 1; + authorizationHeaderStr.len = bearer_length; + + jwtCookieValChrPtr = ngx_str_t_to_char_ptr(r->pool, authorizationHeaderStr); - ngx_log_error(NGX_LOG_DEBUG, r->connection->log, 0, "Authorization header: %s", jwtCookieValChrPtr); + ngx_log_error(NGX_LOG_DEBUG, r->connection->log, 0, "Authorization header: %s", jwtCookieValChrPtr); + } } } else if (auth_jwt_validation_type.len > sizeof("COOKIE=") && ngx_strncmp(auth_jwt_validation_type.data, "COOKIE=", sizeof("COOKIE=") - 1)==0) diff --git a/test.sh b/test.sh index 955bb13..5b9faaa 100755 --- a/test.sh +++ b/test.sh @@ -36,6 +36,8 @@ main() { test_jwt "Secure test without jwt auth header" "/secure-auth-header/" "302" + test_jwt "Secure test with jwt auth header missing Bearer" "/secure-no-redirect/" "401" "--header \"Authorization: X\"" + test_jwt "Secure test without jwt auth header" "/secure-no-redirect/" "401" test_jwt "Secure test with jwt cookie - with no sub" "/secure/" "200" " --cookie \"rampartjwt=${MISSING_SUB_JWT}\"" @@ -45,4 +47,4 @@ main() { test_jwt "Secure test with rs256 jwt cookie" "/secure-rs256/" "200" " --cookie \"rampartjwt=${VALID_RS256_JWT}\"" } -main "$@" \ No newline at end of file +main "$@" From 1653ef1c34a3c18077e137b064da85143f667438 Mon Sep 17 00:00:00 2001 From: Branimir Malesevic Date: Wed, 25 Aug 2021 18:37:40 +0200 Subject: [PATCH 03/75] PEM key file support (#56) * Fix newlines (sry win) in config * Add reading key file name, minify docker image size * Add key file path, minor name change * Fix memory leak * Fix indentation * Add RS256 key file documentation * Fix tests and config * Fix newlines * Add private keyfile fields inside location config * Add tests for rsa256 keyfile scenarios * Update readme Co-authored-by: Branimir Malesevic --- Dockerfile | 22 ++------- README.md | 11 ++++- resources/test-jwt-nginx.conf | 11 +++++ src/ngx_http_auth_jwt_module.c | 90 ++++++++++++++++++++++++++++++++-- test.sh | 5 ++ 5 files changed, 116 insertions(+), 23 deletions(-) diff --git a/Dockerfile b/Dockerfile index 2097e5e..dd5482d 100644 --- a/Dockerfile +++ b/Dockerfile @@ -19,28 +19,12 @@ RUN echo "enabled=1" >>/etc/yum.repos.d/nginx.repo RUN yum -y update && \ yum -y groupinstall 'Development Tools' && \ - yum -y install pcre-devel pcre zlib-devel openssl-devel wget cmake check-devel check && \ + yum -y install pcre-devel pcre zlib-devel openssl-devel wget cmake3 check-devel check && \ yum -y install nginx-$NGINX_VERSION -# for compiling for rh-nginx110 -# yum -y install libxml2 libxslt libxml2-devel libxslt-devel gd gd-devel perl-ExtUtils-Embed - # for compiling for epel7 RUN yum -y install libxml2 libxslt libxml2-devel libxslt-devel gd gd-devel perl-ExtUtils-Embed geoip geoip-devel google-perftools google-perftools-devel -# Jansson requires new cmake -RUN yum -y install cmake3 && \ - alternatives --install /usr/local/bin/cmake cmake /usr/bin/cmake 10 \ ---slave /usr/local/bin/ctest ctest /usr/bin/ctest \ ---slave /usr/local/bin/cpack cpack /usr/bin/cpack \ ---slave /usr/local/bin/ccmake ccmake /usr/bin/ccmake \ ---family cmake && \ - alternatives --install /usr/local/bin/cmake cmake /usr/bin/cmake3 20 \ ---slave /usr/local/bin/ctest ctest /usr/bin/ctest3 \ ---slave /usr/local/bin/cpack cpack /usr/bin/cpack3 \ ---slave /usr/local/bin/ccmake ccmake /usr/bin/ccmake3 \ ---family cmake - RUN mkdir -p /root/dl WORKDIR /root/dl @@ -50,7 +34,7 @@ RUN wget https://github.com/akheron/jansson/archive/v$JANSSON_VERSION.zip && \ rm v$JANSSON_VERSION.zip && \ ln -sf jansson-$JANSSON_VERSION jansson && \ cd /root/dl/jansson && \ - cmake . -DJANSSON_BUILD_SHARED_LIBS=1 -DJANSSON_BUILD_DOCS=OFF && \ + cmake3 . -DJANSSON_BUILD_SHARED_LIBS=1 -DJANSSON_BUILD_DOCS=OFF && \ make && \ make check && \ make install @@ -99,12 +83,14 @@ RUN wget http://nginx.org/download/nginx-$NGINX_VERSION.tar.gz && \ # Get nginx ready to run COPY resources/nginx.conf /etc/nginx/nginx.conf COPY resources/test-jwt-nginx.conf /etc/nginx/conf.d/test-jwt-nginx.conf +COPY resources/rsa_key_2048-pub.pem /etc/nginx/rsa-key.conf RUN rm -rf /usr/share/nginx/html RUN cp -r /root/dl/nginx/html /usr/share/nginx RUN cp -r /usr/share/nginx/html /usr/share/nginx/secure RUN cp -r /usr/share/nginx/html /usr/share/nginx/secure-rs256 RUN cp -r /usr/share/nginx/html /usr/share/nginx/secure-auth-header RUN cp -r /usr/share/nginx/html /usr/share/nginx/secure-no-redirect +RUN cp -r /usr/share/nginx/html /usr/share/nginx/secure-rs256-file ENTRYPOINT ["/usr/sbin/nginx"] diff --git a/README.md b/README.md index c04a8fd..97e7d82 100644 --- a/README.md +++ b/README.md @@ -45,11 +45,13 @@ auth_jwt_loginurl "https://yourdomain.com/loginpage"; auth_jwt_enabled on; auth_jwt_algorithm HS256; # or RS256 auth_jwt_validate_email on; # or off +auth_jwt_use_keyfile off; # or on +auth_jwt_keyfile_path "/app/pub_key"; ``` The default algorithm is 'HS256', for symmetric key validation. When using HS256, 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) as in the example above. 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, Section 5.3.2 The HMAC Key. -The configuration also supports the `auth_jwt_algorithm` 'RS256', for RSA 256-bit public key validation. If using "auth_jwt_algorithm RS256;", then the `auth_jwt_key` field must be set to your public key. +The configuration also supports the `auth_jwt_algorithm` 'RS256', for RSA 256-bit public key validation. If using "auth_jwt_algorithm RS256;", then the `auth_jwt_key` field must be set to your public key **OR** `auth_jwt_use_keyfile` should be set to `on` with the `auth_jwt_keyfile_path` set to the public key path (nginx won't start if the `auth_jwt_use_keyfile` is set to `on` without a keyfile). That is the public key, rather than a PEM certificate. I.e.: ``` @@ -64,6 +66,13 @@ oQIDAQAB -----END PUBLIC KEY-----"; ``` +**OR** + +``` +auth_jwt_use_keyfile on; +auth_jwt_keyfile_path "/etc/nginx/pub_key.pem"; +``` + A typical use would be to specify the key and loginurl on the main level and then only turn on the locations that you want to secure (not the login page). Unauthorized requests are given 302 "Moved Temporarily" responses with a location of the specified loginurl. diff --git a/resources/test-jwt-nginx.conf b/resources/test-jwt-nginx.conf index b39eb95..cbb356c 100644 --- a/resources/test-jwt-nginx.conf +++ b/resources/test-jwt-nginx.conf @@ -44,6 +44,17 @@ BwIDAQAB index index.html index.htm; } + location ~ ^/secure-rs256-file/ { + auth_jwt_enabled on; + auth_jwt_validation_type AUTHORIZATION; + auth_jwt_algorithm RS256; + auth_jwt_redirect off; + auth_jwt_use_keyfile on; + auth_jwt_keyfile_path "/etc/nginx/rsa-key.conf"; + root /usr/share/nginx; + index index.html index.htm; + } + location / { root /usr/share/nginx/html; index index.html index.htm; diff --git a/src/ngx_http_auth_jwt_module.c b/src/ngx_http_auth_jwt_module.c index 4395d5e..fbd07ba 100644 --- a/src/ngx_http_auth_jwt_module.c +++ b/src/ngx_http_auth_jwt_module.c @@ -18,6 +18,8 @@ #include "ngx_http_auth_jwt_binary_converters.h" #include "ngx_http_auth_jwt_string.h" +#include + typedef struct { ngx_str_t auth_jwt_loginurl; ngx_str_t auth_jwt_key; @@ -26,7 +28,10 @@ typedef struct { ngx_str_t auth_jwt_validation_type; ngx_str_t auth_jwt_algorithm; ngx_flag_t auth_jwt_validate_email; - + ngx_str_t auth_jwt_keyfile_path; + ngx_flag_t auth_jwt_use_keyfile; + // Private field for keyfile data + ngx_str_t _auth_jwt_keyfile; } ngx_http_auth_jwt_loc_conf_t; static ngx_int_t ngx_http_auth_jwt_init(ngx_conf_t *cf); @@ -86,6 +91,20 @@ static ngx_command_t ngx_http_auth_jwt_commands[] = { offsetof(ngx_http_auth_jwt_loc_conf_t, auth_jwt_validate_email), 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(ngx_http_auth_jwt_loc_conf_t, auth_jwt_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(ngx_http_auth_jwt_loc_conf_t, auth_jwt_use_keyfile), + NULL }, + ngx_null_command }; @@ -129,6 +148,7 @@ static ngx_int_t ngx_http_auth_jwt_handler(ngx_http_request_t *r) char* return_url; ngx_http_auth_jwt_loc_conf_t *jwtcf; u_char *keyBinary; + // For clearing it later on jwt_t *jwt = NULL; int jwtParseReturnCode; jwt_alg_t alg; @@ -177,8 +197,18 @@ static ngx_int_t ngx_http_auth_jwt_handler(ngx_http_request_t *r) else if ( auth_jwt_algorithm.len == sizeof("RS256") - 1 && ngx_strncmp(auth_jwt_algorithm.data, "RS256", sizeof("RS256") - 1) == 0 ) { // in this case, 'Binary' is a misnomer, as it is the public key string itself - keyBinary = jwtcf->auth_jwt_key.data; - keylen = jwtcf->auth_jwt_key.len; + if (jwtcf->auth_jwt_use_keyfile == 1) + { + // Set to global variables + // NOTE: check for keyBin == NULL skipped, unnecessary check; nginx should fail to start + keyBinary = (u_char*)jwtcf->_auth_jwt_keyfile.data; + keylen = jwtcf->_auth_jwt_keyfile.len; + } + else + { + keyBinary = jwtcf->auth_jwt_key.data; + keylen = jwtcf->auth_jwt_key.len; + } } else { @@ -239,6 +269,7 @@ static ngx_int_t ngx_http_auth_jwt_handler(ngx_http_request_t *r) jwt_free(jwt); + return NGX_OK; redirect: @@ -358,7 +389,6 @@ static ngx_int_t ngx_http_auth_jwt_init(ngx_conf_t *cf) return NGX_OK; } - static void * ngx_http_auth_jwt_create_loc_conf(ngx_conf_t *cf) { @@ -374,12 +404,43 @@ ngx_http_auth_jwt_create_loc_conf(ngx_conf_t *cf) conf->auth_jwt_enabled = (ngx_flag_t) -1; conf->auth_jwt_redirect = (ngx_flag_t) -1; conf->auth_jwt_validate_email = (ngx_flag_t) -1; + conf->auth_jwt_use_keyfile = (ngx_flag_t) -1; ngx_conf_log_error(NGX_LOG_DEBUG, cf, 0, "Created Location Configuration"); return conf; } +// Loads the RSA256 public key into the location config struct +static ngx_int_t +loadAuthKey(ngx_conf_t *cf, ngx_http_auth_jwt_loc_conf_t* conf) { + FILE *keyFile = fopen((const char*)conf->auth_jwt_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 pub key file"); + return NGX_ERROR; + } + + // Read file length + fseek(keyFile, 0, SEEK_END); + long keySize = ftell(keyFile); + fseek(keyFile, 0, SEEK_SET); + + if (keySize == 0) + { + ngx_log_error(NGX_LOG_ERR, cf->log, 0, "invalid key file size, check the key file"); + return NGX_ERROR; + } + + conf->_auth_jwt_keyfile.data = ngx_palloc(cf->pool, keySize); + fread(conf->_auth_jwt_keyfile.data, 1, keySize, keyFile); + conf->_auth_jwt_keyfile.len = (int)keySize; + + fclose(keyFile); + return NGX_OK; +} static char * ngx_http_auth_jwt_merge_loc_conf(ngx_conf_t *cf, void *parent, void *child) @@ -391,6 +452,7 @@ ngx_http_auth_jwt_merge_loc_conf(ngx_conf_t *cf, void *parent, void *child) ngx_conf_merge_str_value(conf->auth_jwt_key, prev->auth_jwt_key, ""); ngx_conf_merge_str_value(conf->auth_jwt_validation_type, prev->auth_jwt_validation_type, ""); ngx_conf_merge_str_value(conf->auth_jwt_algorithm, prev->auth_jwt_algorithm, "HS256"); + ngx_conf_merge_str_value(conf->auth_jwt_keyfile_path, prev->auth_jwt_keyfile_path, ""); ngx_conf_merge_off_value(conf->auth_jwt_validate_email, prev->auth_jwt_validate_email, 1); if (conf->auth_jwt_enabled == ((ngx_flag_t) -1)) @@ -403,6 +465,26 @@ ngx_http_auth_jwt_merge_loc_conf(ngx_conf_t *cf, void *parent, void *child) conf->auth_jwt_redirect = (prev->auth_jwt_redirect == ((ngx_flag_t) -1)) ? 0 : prev->auth_jwt_redirect; } + if (conf->auth_jwt_use_keyfile == ((ngx_flag_t) -1)) + { + conf->auth_jwt_use_keyfile = (prev->auth_jwt_use_keyfile == ((ngx_flag_t) -1)) ? 0 : prev->auth_jwt_use_keyfile; + } + + // If the usage of the keyfile is specified, check if the key_path is also configured + if (conf->auth_jwt_use_keyfile == 1) + { + if (ngx_strcmp(conf->auth_jwt_keyfile_path.data, "") != 0) + { + if (loadAuthKey(cf, conf) != NGX_OK) + return NGX_CONF_ERROR; + } + else + { + ngx_log_error(NGX_LOG_ERR, cf->log, 0, "auth_jwt_keyfile_path not specified"); + return NGX_CONF_ERROR; + } + } + return NGX_CONF_OK; } diff --git a/test.sh b/test.sh index 5b9faaa..f73a03b 100755 --- a/test.sh +++ b/test.sh @@ -25,6 +25,7 @@ main() { local MISSING_SUB_JWT=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJmaXJzdE5hbWUiOiJoZWxsbyIsImxhc3ROYW1lIjoid29ybGQiLCJlbWFpbEFkZHJlc3MiOiJoZWxsb3dvcmxkQGV4YW1wbGUuY29tIiwicm9sZXMiOlsidGhpcyIsInRoYXQiLCJ0aGVvdGhlciJdLCJpc3MiOiJpc3N1ZXIiLCJwZXJzb25JZCI6Ijc1YmIzY2M3LWI5MzMtNDRmMC05M2M2LTE0N2IwODJmYWRiNSIsImV4cCI6MTkwODgzNTIwMCwiaWF0IjoxNDg4ODE5NjAwLCJ1c2VybmFtZSI6ImhlbGxvLndvcmxkIn0.lD6jUsazVtzeGhRTNeP_b2Zs6O798V2FQql11QOEI1Q local MISSING_EMAIL_JWT=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiJzb21lLWxvbmctdXVpZCIsImZpcnN0TmFtZSI6ImhlbGxvIiwibGFzdE5hbWUiOiJ3b3JsZCIsInJvbGVzIjpbInRoaXMiLCJ0aGF0IiwidGhlb3RoZXIiXSwiaXNzIjoiaXNzdWVyIiwicGVyc29uSWQiOiI3NWJiM2NjNy1iOTMzLTQ0ZjAtOTNjNi0xNDdiMDgyZmFkYjUiLCJleHAiOjE5MDg4MzUyMDAsImlhdCI6MTQ4ODgxOTYwMCwidXNlcm5hbWUiOiJoZWxsby53b3JsZCJ9.tJoAl_pvq95hK7GKqsp5TU462pLTbmSYZc1fAHzcqWM local VALID_RS256_JWT=eyJ0eXAiOiJKV1QiLCJhbGciOiJSUzI1NiJ9.eyJzdWIiOiJzb21lLWxvbmctdXVpZCIsImZpcnN0TmFtZSI6ImhlbGxvIiwgImxhc3ROYW1lIjoid29ybGQiLCJlbWFpbEFkZHJlc3MiOiJoZWxsb3dvcmxkQGV4YW1wbGUuY29tIiwgInJvbGVzIjpbInRoaXMiLCJ0aGF0IiwidGhlb3RoZXIiXSwgImlzcyI6Imlzc3VlciIsInBlcnNvbklkIjoiNzViYjNjYzctYjkzMy00NGYwLTkzYzYtMTQ3YjA4MmZhZGI1IiwgImV4cCI6MTkwODgzNTIwMCwiaWF0IjoxNDg4ODE5NjAwLCJ1c2VybmFtZSI6ImhlbGxvLndvcmxkIn0.cn5Gb75XL-r7TMsPuqzWoKZ06ZsyF_VZIG0Ohn8uZZFeF8dFUhSrEOYe8WFN6Eon8a8LC0OCI9eNdGiD4m_e9TD1Iz2juqaeos-6yd7SWuODr4YS8KD3cqfXndnLRPzp9PC_UIpATsbqOmxGDrRKvHsQq0TuIXImU3rM_m3kFJFgtoJFHx3KmZUo_Ozkyhhc6Pukikhy6odNAtEyLHP5_tabMXtkeAuIlG8dhjAxef4mJLexYFclG-vl7No5VBU4JrMbfgyxtobcYoE-bDIpmQHywrwo6Li7X0hgHJ17sfS3G2YMHmE-Ij_W2Lf9kf5r2r12DUvg44SLIfM58pCINQ + local INVALID_RSA256_JWT=eyJhbGciOiJIUzI1NiJ9.eyJzdWIiOiJzb21lLWxvbmctdXVpZCIsImZpcnN0TmFtZSI6ImhlbGxvIiwibGFzdE5hbWUiOiJ3b3JsZCIsImVtYWlsQWRkcmVzcyI6ImhlbGxvd29ybGRAZXhhbXBsZS5jb20iLCJyb2xlcyI6WyJ0aGlzIiwidGhhdCIsInRoZW90aGVyIl0sImlzcyI6Imlzc3VlciIsInBlcnNvbklkIjoiNzViYjNjYzctYjkzMy00NGYwLTkzYzYtMTQ3YjA4MmZhZGI1IiwiZXhwIjoxOTA4ODM1MjAwLCJpYXQiOjE0ODg4MTk2MDAsInVzZXJuYW1lIjoiaGVsbG8ud29ybGQifQ._aQmIBL4CVBxU1fNMOHp0kkagFaaX2TvAEenizytwd0 test_jwt "Insecure test" "/" "200" @@ -45,6 +46,10 @@ main() { test_jwt "Secure test with jwt cookie - with no email" "/secure/" "200" " --cookie \"rampartjwt=${MISSING_EMAIL_JWT}\"" test_jwt "Secure test with rs256 jwt cookie" "/secure-rs256/" "200" " --cookie \"rampartjwt=${VALID_RS256_JWT}\"" + + test_jwt "Secure test rsa256 from file with valid jwt" "/secure-rs256-file/" "200" "--header \"Authorization: Bearer ${VALID_RS256_JWT}\"" + + test_jwt "Secure test rsa256 from file with invalid jwt" "/secure-rs256-file/" "401" "--header \"Authorization: Bearer ${INVALID_RSA256_JWT}\"" } main "$@" From 148987d81d31cf16cfb8ccc391056c6e2528ab39 Mon Sep 17 00:00:00 2001 From: Josh McCullough Date: Fri, 20 May 2022 14:30:02 -0400 Subject: [PATCH 04/75] added NGINX_VERSION to Makefile to allow for overriding --- Makefile | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Makefile b/Makefile index 8a2290b..eb7a014 100644 --- a/Makefile +++ b/Makefile @@ -18,7 +18,7 @@ all: .PHONY: build-nginx build-nginx: @echo "${BLUE} Building...${NC}" - @docker image build -t $(DOCKER_ORG_NAME)/$(DOCKER_IMAGE_NAME) . ; \ + @docker image build -t $(DOCKER_ORG_NAME)/$(DOCKER_IMAGE_NAME) . --build-arg NGINX_VERSION=${NGINX_VERSION} ; \ if [ $$? -ne 0 ] ; \ then echo "${RED} Build failed :(${NC}" ; \ else echo "${GREEN}✓ Successfully built NGINX module ${NC}" ; fi @@ -26,7 +26,7 @@ build-nginx: .PHONY: rebuild-nginx rebuild-nginx: @echo "${BLUE} Rebuilding...${NC}" - @docker image build -t $(DOCKER_ORG_NAME)/$(DOCKER_IMAGE_NAME) . --no-cache ; \ + @docker image build -t $(DOCKER_ORG_NAME)/$(DOCKER_IMAGE_NAME) . --no-cache --build-arg NGINX_VERSION=${NGINX_VERSION} ; \ if [ $$? -ne 0 ] ; \ then echo "${RED} Build failed :(${NC}" ; \ else echo "${GREEN}✓ Successfully rebuilt NGINX module ${NC}" ; fi From e959c0c39cf5cb24b9161057ed23940fd1a11346 Mon Sep 17 00:00:00 2001 From: Tim Underhay <15734900+KensingtonTech@users.noreply.github.com> Date: Tue, 7 Jun 2022 20:49:30 -0600 Subject: [PATCH 05/75] Docker / build refactor & update Nginx (#69) * Build with latest nginx version (1.21.6) Image builder now multi-stage Image builder uses debian:bullseye-slim to build module (same as official nginx image's base) Use nginx official image as final image base Now using sed to modify OOTB nginx.conf to load the module. Moved test-runner customisations into separate compose test stack Updated Makefile to use docker compose for tests. * Make org name, image name, compose project name, and nginx configurable via env vars. Clean up dangling stage images during build. * Removed copy of libjansson and libjwt from start-nginx target. Updated Readme. * Bumped default version to 1.22.0. Fix for v1.16.1. Test runner runs correct nginx version. Co-authored-by: Josh McCullough --- .env | 1 + Dockerfile | 127 +++++++++++----------------------------- Dockerfile-test | 4 -- Dockerfile-test-nginx | 10 ++++ Dockerfile-test-runner | 4 ++ Makefile | 46 ++++++++------- README.md | 32 +++++----- docker-compose-test.yml | 20 +++++++ resources/nginx.conf | 26 +++----- test.sh | 3 +- 10 files changed, 121 insertions(+), 152 deletions(-) create mode 100644 .env delete mode 100644 Dockerfile-test create mode 100644 Dockerfile-test-nginx create mode 100644 Dockerfile-test-runner create mode 100644 docker-compose-test.yml diff --git a/.env b/.env new file mode 100644 index 0000000..1bb6443 --- /dev/null +++ b/.env @@ -0,0 +1 @@ +COMPOSE_PROJECT_NAME=jwt-nginx-test \ No newline at end of file diff --git a/Dockerfile b/Dockerfile index dd5482d..648fbee 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,97 +1,40 @@ -FROM centos:7 +ARG NGINX_VERSION=1.22.0 -LABEL maintainer="TeslaGov" email="developers@teslagov.com" - -ARG NGINX_VERSION=1.16.1 -ARG JANSSON_VERSION=2.13.1 -ARG LIBJWT_VERSION=1.12.0 - -ENV LD_LIBRARY_PATH=/usr/local/lib -ENV PKG_CONFIG_PATH=/usr/local/lib/pkgconfig:/usr/share/pkgconfig - -RUN yum -y install https://dl.fedoraproject.org/pub/epel/epel-release-latest-7.noarch.rpm -RUN echo "" >>/etc/yum.repos.d/nginx.repo -RUN echo "[nginx]" >>/etc/yum.repos.d/nginx.repo -RUN echo "name=nginx repo" >>/etc/yum.repos.d/nginx.repo -RUN echo "baseurl=https://nginx.org/packages/centos/7/x86_64/" >>/etc/yum.repos.d/nginx.repo -RUN echo "gpgcheck=0" >>/etc/yum.repos.d/nginx.repo -RUN echo "enabled=1" >>/etc/yum.repos.d/nginx.repo - -RUN yum -y update && \ - yum -y groupinstall 'Development Tools' && \ - yum -y install pcre-devel pcre zlib-devel openssl-devel wget cmake3 check-devel check && \ - yum -y install nginx-$NGINX_VERSION - -# for compiling for epel7 -RUN yum -y install libxml2 libxslt libxml2-devel libxslt-devel gd gd-devel perl-ExtUtils-Embed geoip geoip-devel google-perftools google-perftools-devel - -RUN mkdir -p /root/dl -WORKDIR /root/dl -# build jansson -RUN wget https://github.com/akheron/jansson/archive/v$JANSSON_VERSION.zip && \ - unzip v$JANSSON_VERSION.zip && \ - rm v$JANSSON_VERSION.zip && \ - ln -sf jansson-$JANSSON_VERSION jansson && \ - cd /root/dl/jansson && \ - cmake3 . -DJANSSON_BUILD_SHARED_LIBS=1 -DJANSSON_BUILD_DOCS=OFF && \ - make && \ - make check && \ - make install +FROM debian:bullseye-slim as BASE_IMAGE +LABEL stage=builder +RUN apt-get update \ + && apt-get install -y curl build-essential -# build libjwt -RUN wget https://github.com/benmcollins/libjwt/archive/v$LIBJWT_VERSION.zip && \ - unzip v$LIBJWT_VERSION.zip && \ - rm v$LIBJWT_VERSION.zip && \ - ln -sf libjwt-$LIBJWT_VERSION libjwt && \ - cd /root/dl/libjwt && \ - autoreconf -i && \ - ./configure && \ - make all && \ - make install +FROM BASE_IMAGE as BUILD_IMAGE +LABEL stage=builder +ENV LD_LIBRARY_PATH=/usr/local/lib +ARG NGINX_VERSION ADD . /root/dl/ngx-http-auth-jwt-module - -# after 1.11.5 when compiling for a server that was compiled with --with-compat use this command -# ./configure --with-compat --add-dynamic-module=../ngx-http-auth-jwt-module --with-cc-opt='-std=gnu99' -# cp /root/dl/nginx/objs/ngx_http_auth_jwt_module.so /etc/nginx/modules/. -# build nginx module against nginx sources -# -# 1.10.2 from nginx by default use config flags... I had to add the -std=c99 and could not achieve "binary compatibility" -# ./configure --add-dynamic-module=../ngx-http-auth-jwt-module --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-file-aio --with-threads --with-ipv6 --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_ssl_module --with-cc-opt='-O2 -g -pipe -Wall -Wp,-D_FORTIFY_SOURCE=2 -fexceptions -fstack-protector-strong --param=ssp-buffer-size=4 -grecord-gcc-switches -m64 -mtune=generic -std=c99' -# -# rh-nginx110 uses these config flags -# ./configure --add-dynamic-module=../ngx-http-auth-jwt-module --prefix=/opt/rh/rh-nginx110/root/usr/share/nginx --sbin-path=/opt/rh/rh-nginx110/root/usr/sbin/nginx --modules-path=/opt/rh/rh-nginx110/root/usr/lib64/nginx/modules --conf-path=/etc/opt/rh/rh-nginx110/nginx/nginx.conf --error-log-path=/var/opt/rh/rh-nginx110/log/nginx/error.log --http-log-path=/var/opt/rh/rh-nginx110/log/nginx/access.log --http-client-body-temp-path=/var/opt/rh/rh-nginx110/lib/nginx/tmp/client_body --http-proxy-temp-path=/var/opt/rh/rh-nginx110/lib/nginx/tmp/proxy --http-fastcgi-temp-path=/var/opt/rh/rh-nginx110/lib/nginx/tmp/fastcgi --http-uwsgi-temp-path=/var/opt/rh/rh-nginx110/lib/nginx/tmp/uwsgi --http-scgi-temp-path=/var/opt/rh/rh-nginx110/lib/nginx/tmp/scgi --pid-path=/var/opt/rh/rh-nginx110/run/nginx/nginx.pid --lock-path=/var/opt/rh/rh-nginx110/lock/subsys/nginx --user=nginx --group=nginx --with-file-aio --with-ipv6 --with-http_ssl_module --with-http_v2_module --with-http_realip_module --with-http_addition_module --with-http_xslt_module=dynamic --with-http_image_filter_module=dynamic --with-http_sub_module --with-http_dav_module --with-http_flv_module --with-http_mp4_module --with-http_gunzip_module --with-http_gzip_static_module --with-http_random_index_module --with-http_secure_link_module --with-http_degradation_module --with-http_slice_module --with-http_stub_status_module --with-http_perl_module=dynamic --with-mail=dynamic --with-mail_ssl_module --with-pcre --with-pcre-jit --with-stream=dynamic --with-stream_ssl_module --with-debug --with-cc-opt='-O2 -g -pipe -Wall -Wp,-D_FORTIFY_SOURCE=2 -fexceptions -fstack-protector-strong --param=ssp-buffer-size=4 -grecord-gcc-switches -specs=/usr/lib/rpm/redhat/redhat-hardened-cc1 -m64 -mtune=generic -std=c99' --with-ld-opt='-Wl,-z,relro -specs=/usr/lib/rpm/redhat/redhat-hardened-ld -Wl,-E' -# -# epel7 version 1.12.1 uses these config flags -# ./configure --add-dynamic-module=../ngx-http-auth-jwt-module --prefix=/usr/share/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 --http-client-body-temp-path=/var/lib/nginx/tmp/client_body --http-proxy-temp-path=/var/lib/nginx/tmp/proxy --http-fastcgi-temp-path=/var/lib/nginx/tmp/fastcgi --http-uwsgi-temp-path=/var/lib/nginx/tmp/uwsgi --http-scgi-temp-path=/var/lib/nginx/tmp/scgi --pid-path=/run/nginx.pid --lock-path=/run/lock/subsys/nginx --user=nginx --group=nginx --with-file-aio --with-ipv6 --with-http_ssl_module --with-http_v2_module --with-http_realip_module --with-http_addition_module --with-http_xslt_module=dynamic --with-http_image_filter_module=dynamic --with-http_geoip_module=dynamic --with-http_sub_module --with-http_dav_module --with-http_flv_module --with-http_mp4_module --with-http_gunzip_module --with-http_gzip_static_module --with-http_random_index_module --with-http_secure_link_module --with-http_degradation_module --with-http_slice_module --with-http_stub_status_module --with-http_perl_module=dynamic --with-mail=dynamic --with-mail_ssl_module --with-pcre --with-pcre-jit --with-stream=dynamic --with-stream_ssl_module --with-google_perftools_module --with-debug --with-cc-opt='-O2 -g -pipe -Wall -Wp,-D_FORTIFY_SOURCE=2 -fexceptions -fstack-protector-strong --param=ssp-buffer-size=4 -grecord-gcc-switches -specs=/usr/lib/rpm/redhat/redhat-hardened-cc1 -m64 -mtune=generic -std=gnu99' --with-ld-opt='-Wl,-z,relro -specs=/usr/lib/rpm/redhat/redhat-hardened-ld -Wl,-E' -# -# epel7 version 1.16.1 uses these config flags -# ./configure --add-dynamic-module=../ngx-http-auth-jwt-module --prefix=/usr/share/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 --http-client-body-temp-path=/var/lib/nginx/tmp/client_body --http-proxy-temp-path=/var/lib/nginx/tmp/proxy --http-fastcgi-temp-path=/var/lib/nginx/tmp/fastcgi --http-uwsgi-temp-path=/var/lib/nginx/tmp/uwsgi --http-scgi-temp-path=/var/lib/nginx/tmp/scgi --pid-path=/run/nginx.pid --lock-path=/run/lock/subsys/nginx --user=nginx --group=nginx --with-file-aio --with-ipv6 --with-http_ssl_module --with-http_v2_module --with-http_realip_module --with-stream_ssl_preread_module --with-http_addition_module --with-http_xslt_module=dynamic --with-http_image_filter_module=dynamic --with-http_sub_module --with-http_dav_module --with-http_flv_module --with-http_mp4_module --with-http_gunzip_module --with-http_gzip_static_module --with-http_random_index_module --with-http_secure_link_module --with-http_degradation_module --with-http_slice_module --with-http_stub_status_module --with-http_perl_module=dynamic --with-http_auth_request_module --with-mail=dynamic --with-mail_ssl_module --with-pcre --with-pcre-jit --with-stream=dynamic --with-stream_ssl_module --with-google_perftools_module --with-debug --with-cc-opt='-O2 -g -pipe -Wall -Wp,-D_FORTIFY_SOURCE=2 -fexceptions -fstack-protector-strong --param=ssp-buffer-size=4 -grecord-gcc-switches -specs=/usr/lib/rpm/redhat/redhat-hardened-cc1 -m64 -mtune=generic' --with-ld-opt='-Wl,-z,relro -specs=/usr/lib/rpm/redhat/redhat-hardened-ld -Wl,-E' - -# ARG CACHEBUST=1 - -RUN wget http://nginx.org/download/nginx-$NGINX_VERSION.tar.gz && \ - tar -xzf nginx-$NGINX_VERSION.tar.gz && \ - rm nginx-$NGINX_VERSION.tar.gz && \ - ln -sf nginx-$NGINX_VERSION nginx && \ - cd /root/dl/nginx && \ - ./configure --with-compat --add-dynamic-module=../ngx-http-auth-jwt-module --with-cc-opt='-std=gnu99' && \ - make modules && \ - cp /root/dl/nginx/objs/ngx_http_auth_jwt_module.so /usr/lib64/nginx/modules/. - -# Get nginx ready to run -COPY resources/nginx.conf /etc/nginx/nginx.conf -COPY resources/test-jwt-nginx.conf /etc/nginx/conf.d/test-jwt-nginx.conf -COPY resources/rsa_key_2048-pub.pem /etc/nginx/rsa-key.conf -RUN rm -rf /usr/share/nginx/html -RUN cp -r /root/dl/nginx/html /usr/share/nginx -RUN cp -r /usr/share/nginx/html /usr/share/nginx/secure -RUN cp -r /usr/share/nginx/html /usr/share/nginx/secure-rs256 -RUN cp -r /usr/share/nginx/html /usr/share/nginx/secure-auth-header -RUN cp -r /usr/share/nginx/html /usr/share/nginx/secure-no-redirect -RUN cp -r /usr/share/nginx/html /usr/share/nginx/secure-rs256-file - -ENTRYPOINT ["/usr/sbin/nginx"] - -EXPOSE 8000 +RUN set -x \ + && apt-get install -y libjwt-dev libjwt0 libjansson-dev libjansson4 libpcre2-dev zlib1g-dev libpcre3-dev \ + && mkdir -p /root/dl +WORKDIR /root/dl +RUN set -x \ + && curl -O http://nginx.org/download/nginx-$NGINX_VERSION.tar.gz \ + && tar -xzf nginx-$NGINX_VERSION.tar.gz \ + && rm nginx-$NGINX_VERSION.tar.gz \ + && ln -sf nginx-$NGINX_VERSION nginx \ + && cd /root/dl/nginx \ + && ./configure --with-compat --add-dynamic-module=../ngx-http-auth-jwt-module \ + && make modules + + +FROM nginx:${NGINX_VERSION} +LABEL stage=builder +RUN apt-get update \ + && apt-get -y install libjansson4 libjwt0 \ + && cd /etc/nginx \ + && cp nginx.conf nginx.conf.orig \ + && sed -ri '/pid\s+\/var\/run\/nginx\.pid;$/a load_module \/usr\/lib64\/nginx\/modules\/ngx_http_auth_jwt_module\.so;' nginx.conf + + +LABEL stage= +LABEL maintainer="TeslaGov" email="developers@teslagov.com" +COPY --from=BUILD_IMAGE /root/dl/nginx/objs/ngx_http_auth_jwt_module.so /usr/lib64/nginx/modules/ diff --git a/Dockerfile-test b/Dockerfile-test deleted file mode 100644 index adff57a..0000000 --- a/Dockerfile-test +++ /dev/null @@ -1,4 +0,0 @@ -FROM alpine:3.7 -RUN apk add --no-cache bash curl -COPY test.sh . -CMD ["./test.sh"] \ No newline at end of file diff --git a/Dockerfile-test-nginx b/Dockerfile-test-nginx new file mode 100644 index 0000000..10a1eae --- /dev/null +++ b/Dockerfile-test-nginx @@ -0,0 +1,10 @@ +ARG BASE_IMAGE=teslagov/jwt-nginx:latest + +FROM ${BASE_IMAGE} as NGINX +COPY resources/test-jwt-nginx.conf /etc/nginx/conf.d/test-jwt-nginx.conf +COPY resources/rsa_key_2048-pub.pem /etc/nginx/rsa-key.conf +RUN cp -r /usr/share/nginx/html /usr/share/nginx/secure \ + && cp -r /usr/share/nginx/html /usr/share/nginx/secure-rs256 \ + && cp -r /usr/share/nginx/html /usr/share/nginx/secure-rs256-file \ + && cp -r /usr/share/nginx/html /usr/share/nginx/secure-auth-header \ + && cp -r /usr/share/nginx/html /usr/share/nginx/secure-no-redirect diff --git a/Dockerfile-test-runner b/Dockerfile-test-runner new file mode 100644 index 0000000..bd9fc59 --- /dev/null +++ b/Dockerfile-test-runner @@ -0,0 +1,4 @@ +FROM alpine:3.7 +COPY test.sh . +RUN apk add curl bash +CMD ["./test.sh"] diff --git a/Makefile b/Makefile index eb7a014..0661439 100644 --- a/Makefile +++ b/Makefile @@ -5,55 +5,61 @@ GREEN := \033[0;32m RED := \033[0;31m NC := \033[0m -DOCKER_ORG_NAME = teslagov -DOCKER_IMAGE_NAME = jwt-nginx +DOCKER_ORG_NAME ?= teslagov +DOCKER_IMAGE_NAME ?= jwt-nginx +COMPOSE_PROJECT_NAME ?= jwt-nginx-test +NGINX_VERSION ?= 1.22.0 .PHONY: all all: @$(MAKE) build-nginx - @$(MAKE) build-test-runner @$(MAKE) start-nginx @$(MAKE) test .PHONY: build-nginx build-nginx: @echo "${BLUE} Building...${NC}" - @docker image build -t $(DOCKER_ORG_NAME)/$(DOCKER_IMAGE_NAME) . --build-arg NGINX_VERSION=${NGINX_VERSION} ; \ - if [ $$? -ne 0 ] ; \ + @docker image pull debian:bullseye-slim + @docker image pull nginx:${NGINX_VERSION} + @docker image build -t ${DOCKER_ORG_NAME}/${DOCKER_IMAGE_NAME}:latest -t ${DOCKER_ORG_NAME}/${DOCKER_IMAGE_NAME}:${NGINX_VERSION} --build-arg NGINX_VERSION=${NGINX_VERSION} . ; \ + SUCCESS=$$? ; \ + docker rmi $$(docker images --filter=label=stage=builder --quiet); \ + if [ "$$SUCCESS" -ne 0 ] ; \ then echo "${RED} Build failed :(${NC}" ; \ else echo "${GREEN}✓ Successfully built NGINX module ${NC}" ; fi .PHONY: rebuild-nginx rebuild-nginx: @echo "${BLUE} Rebuilding...${NC}" - @docker image build -t $(DOCKER_ORG_NAME)/$(DOCKER_IMAGE_NAME) . --no-cache --build-arg NGINX_VERSION=${NGINX_VERSION} ; \ - if [ $$? -ne 0 ] ; \ + @docker image pull debian:bullseye-slim + @docker image pull nginx:${NGINX_VERSION} + @docker image build -t ${DOCKER_ORG_NAME}/${DOCKER_IMAGE_NAME}:latest -t ${DOCKER_ORG_NAME}/${DOCKER_IMAGE_NAME}:${NGINX_VERSION} --build-arg NGINX_VERSION=${NGINX_VERSION} . --no-cache ; \ + SUCCESS=$$? ; \ + docker rmi $$(docker images --filter=label=stage=builder --quiet); \ + if [ "$$SUCCESS" -ne 0 ] ; \ then echo "${RED} Build failed :(${NC}" ; \ else echo "${GREEN}✓ Successfully rebuilt NGINX module ${NC}" ; fi .PHONY: stop-nginx stop-nginx: - docker stop $(shell docker inspect --format="{{.Id}}" "$(DOCKER_IMAGE_NAME)-cont") ||: + docker stop $(shell docker inspect --format="{{.Id}}" "$(DOCKER_IMAGE_NAME)") ||: .PHONY: start-nginx start-nginx: - docker run --rm --name "$(DOCKER_IMAGE_NAME)-cont" -d -p 8000:8000 $(DOCKER_ORG_NAME)/$(DOCKER_IMAGE_NAME) - docker cp $(DOCKER_IMAGE_NAME)-cont:/usr/lib64/nginx/modules/ngx_http_auth_jwt_module.so . - docker cp $(DOCKER_IMAGE_NAME)-cont:/usr/local/lib/libjansson.so.4.13.0 . - docker cp $(DOCKER_IMAGE_NAME)-cont:/usr/local/lib/libjwt.a . - docker cp $(DOCKER_IMAGE_NAME)-cont:/usr/local/lib/libjwt.la . - docker cp $(DOCKER_IMAGE_NAME)-cont:/usr/local/lib/libjwt.so.0.7.0 . - docker cp $(DOCKER_IMAGE_NAME)-cont:/usr/local/lib/pkgconfig/jansson.pc . - docker cp $(DOCKER_IMAGE_NAME)-cont:/usr/local/lib/pkgconfig/libjwt.pc . + docker run --rm --name "${DOCKER_IMAGE_NAME}" -d -p 8000:8000 ${DOCKER_ORG_NAME}/${DOCKER_IMAGE_NAME} + docker cp ${DOCKER_IMAGE_NAME}:/usr/lib64/nginx/modules/ngx_http_auth_jwt_module.so . .PHONY: build-test-runner build-test-runner: - docker image build -f Dockerfile-test -t $(DOCKER_ORG_NAME)/jwt-nginx-test-runner . + IMAGE_VERSION=${NGINX_VERSION} docker compose -f ./docker-compose-test.yml build -.PHONY: frebuild-test-runner +.PHONY: rebuild-test-runner rebuild-test-runner: - docker image build -f Dockerfile-test -t $(DOCKER_ORG_NAME)/jwt-nginx-test-runner . --no-cache + IMAGE_VERSION=${NGINX_VERSION} docker compose -f ./docker-compose-test.yml build --no-cache .PHONY: test test: - docker run --rm $(DOCKER_ORG_NAME)/jwt-nginx-test-runner + IMAGE_VERSION=${NGINX_VERSION} docker compose -f ./docker-compose-test.yml up --no-start + docker start ${COMPOSE_PROJECT_NAME}-nginx-1 + docker start -a ${COMPOSE_PROJECT_NAME}-runner-1 + docker compose -f ./docker-compose-test.yml down \ No newline at end of file diff --git a/README.md b/README.md index 97e7d82..613ace6 100644 --- a/README.md +++ b/README.md @@ -11,23 +11,21 @@ When you make a change to the module, run `make rebuild-nginx`. When you make a change to `test.sh`, run `make rebuild-test-runner`. -| Command | Description | -| -------------------------- |:-------------------------------------------:| -| `make build-nginx` | Builds the NGINX image | -| `make rebuild-nginx` | Re-builds the NGINX image | -| `make build-test-runner` | Builds the image that will run `test.sh` | -| `make rebuild-test-runner` | Re-builds the image that will run `test.sh` | -| `make start-nginx` | Starts the NGINX container | -| `make stop-nginx` | Stops the NGINX container | -| `make test` | Runs `test.sh` against the NGINX container | - -You can re-run tests as many times as you like while NGINX is up. -When you're done running tests, make sure to stop the NGINX container. - -The Dockerfile builds all of the dependencies as well as the module, -downloads a binary version of NGINX, and runs the module as a dynamic module. - -Tests get executed in containers. This project is 100% Docker-ized. +| Command | Description | +| -------------------------- |:-----------------------------------------------------------------:| +| `make build-nginx` | Builds the NGINX image | +| `make rebuild-nginx` | Re-builds the NGINX image | +| `make build-test-runner` | Builds the images used by the test stack (uses Docker compose) | +| `make rebuild-test-runner` | Re-builds the images used by the test stack | +| `make start-nginx` | Starts the NGINX container | +| `make stop-nginx` | Stops the NGINX container | +| `make test` | Runs `test.sh` against the NGINX container (uses Docker compose) | + +The image produced with `make build-nginx` only differs from the official Nginx image in two ways: the module itself and the nginx.conf configuration entry that loads it. + +The tests use a customized Nginx image, distinct from the main image, as well as a test runner image. By running `make test`, the two test containers will be created up with Docker compose, started, and the tests run. At the end, both containers will be automatically stopped and destroyed. + +This project is 100% Docker-ized. ## Dependencies This module depends on the [JWT C Library](https://github.com/benmcollins/libjwt) diff --git a/docker-compose-test.yml b/docker-compose-test.yml new file mode 100644 index 0000000..8d406f2 --- /dev/null +++ b/docker-compose-test.yml @@ -0,0 +1,20 @@ +version: '3.3' + +services: + + nginx: + build: + context: . + dockerfile: Dockerfile-test-nginx + args: + BASE_IMAGE: ${IMAGE_NAME:-teslagov/jwt-nginx}:${IMAGE_VERSION:-latest} + + runner: + build: + context: . + dockerfile: Dockerfile-test-runner + environment: + BASE_IMAGE: ${IMAGE_NAME:-teslagov/jwt-nginx}:${IMAGE_VERSION:-latest} + + depends_on: + - nginx-test \ No newline at end of file diff --git a/resources/nginx.conf b/resources/nginx.conf index 7ea8afb..3981730 100644 --- a/resources/nginx.conf +++ b/resources/nginx.conf @@ -1,12 +1,12 @@ user nginx; -worker_processes 1; +worker_processes auto; -error_log /var/log/nginx/error.log info; +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; } @@ -16,14 +16,11 @@ http { include /etc/nginx/mime.types; default_type application/octet-stream; - log_format upstream_time '$remote_addr $sent_http_x_userid [$time_local] "$request" ' - '$status $body_bytes_sent "$http_referer" ' - '"$http_user_agent" "$http_x_forwarded_for" ' - 'rt="$request_time" uct="$upstream_connect_time" ' - 'uht="$upstream_header_time" urt="$upstream_response_time" ' - '$sent_http_x_email'; + 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 upstream_time; + access_log /var/log/nginx/access.log main; sendfile on; #tcp_nopush on; @@ -32,12 +29,5 @@ http { #gzip on; - proxy_set_header Host $host; - proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; - proxy_set_header X-Forwarded-Proto $scheme; - proxy_set_header X-Forwarded-Server $remote_addr; - include /etc/nginx/conf.d/*.conf; -} - -daemon off; +} \ No newline at end of file diff --git a/test.sh b/test.sh index f73a03b..adeb777 100755 --- a/test.sh +++ b/test.sh @@ -10,7 +10,8 @@ test_jwt () { local expect=$3 local extra=$4 - cmd="curl -X GET -o /dev/null --silent --head --write-out '%{http_code}' http://host.docker.internal:8000$path -H 'cache-control: no-cache' $extra" + cmd="curl -X GET -o /dev/null --silent --head --write-out '%{http_code}' http://nginx:8000$path -H 'cache-control: no-cache' $extra" + test=$( eval ${cmd} ) if [ "$test" -eq "$expect" ];then From 16ea0fe374176d6f5315dbd85e542fc80d38ac4f Mon Sep 17 00:00:00 2001 From: Josh McCullough Date: Tue, 7 Jun 2022 23:04:56 -0400 Subject: [PATCH 06/75] Dockerfile formatting --- Dockerfile | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/Dockerfile b/Dockerfile index 648fbee..ad36f02 100644 --- a/Dockerfile +++ b/Dockerfile @@ -30,9 +30,9 @@ FROM nginx:${NGINX_VERSION} LABEL stage=builder RUN apt-get update \ && apt-get -y install libjansson4 libjwt0 \ - && cd /etc/nginx \ - && cp nginx.conf nginx.conf.orig \ - && sed -ri '/pid\s+\/var\/run\/nginx\.pid;$/a load_module \/usr\/lib64\/nginx\/modules\/ngx_http_auth_jwt_module\.so;' nginx.conf + && cd /etc/nginx \ + && cp nginx.conf nginx.conf.orig \ + && sed -ri '/pid\s+\/var\/run\/nginx\.pid;$/a load_module \/usr\/lib64\/nginx\/modules\/ngx_http_auth_jwt_module\.so;' nginx.conf LABEL stage= From 9f8991f66a5e334f29a264fb862473556de25489 Mon Sep 17 00:00:00 2001 From: Josh McCullough Date: Tue, 7 Jun 2022 23:05:53 -0400 Subject: [PATCH 07/75] Makefile cleanup --- Makefile | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/Makefile b/Makefile index 0661439..252f799 100644 --- a/Makefile +++ b/Makefile @@ -25,24 +25,24 @@ build-nginx: SUCCESS=$$? ; \ docker rmi $$(docker images --filter=label=stage=builder --quiet); \ if [ "$$SUCCESS" -ne 0 ] ; \ - then echo "${RED} Build failed :(${NC}" ; \ - else echo "${GREEN}✓ Successfully built NGINX module ${NC}" ; fi + then echo "${RED} Build failed ${NC}"; \ + else echo "${GREEN}✓ Successfully built NGINX module ${NC}"; fi .PHONY: rebuild-nginx rebuild-nginx: @echo "${BLUE} Rebuilding...${NC}" @docker image pull debian:bullseye-slim @docker image pull nginx:${NGINX_VERSION} - @docker image build -t ${DOCKER_ORG_NAME}/${DOCKER_IMAGE_NAME}:latest -t ${DOCKER_ORG_NAME}/${DOCKER_IMAGE_NAME}:${NGINX_VERSION} --build-arg NGINX_VERSION=${NGINX_VERSION} . --no-cache ; \ + @docker image build -t ${DOCKER_ORG_NAME}/${DOCKER_IMAGE_NAME}:latest -t ${DOCKER_ORG_NAME}/${DOCKER_IMAGE_NAME}:${NGINX_VERSION} --build-arg NGINX_VERSION=${NGINX_VERSION} --no-cache .; \ SUCCESS=$$? ; \ docker rmi $$(docker images --filter=label=stage=builder --quiet); \ if [ "$$SUCCESS" -ne 0 ] ; \ - then echo "${RED} Build failed :(${NC}" ; \ - else echo "${GREEN}✓ Successfully rebuilt NGINX module ${NC}" ; fi + then echo "${RED} Build failed ${NC}"; \ + else echo "${GREEN}✓ Successfully rebuilt NGINX module ${NC}"; fi .PHONY: stop-nginx stop-nginx: - docker stop $(shell docker inspect --format="{{.Id}}" "$(DOCKER_IMAGE_NAME)") ||: + docker stop "${DOCKER_IMAGE_NAME}" .PHONY: start-nginx start-nginx: From 60b6f4bd3110a186983498b0810ba7aa8b6f421c Mon Sep 17 00:00:00 2001 From: Josh McCullough Date: Tue, 7 Jun 2022 23:06:14 -0400 Subject: [PATCH 08/75] add task to copy binaries from container --- .gitignore | 11 +---------- Makefile | 10 +++++++++- 2 files changed, 10 insertions(+), 11 deletions(-) diff --git a/.gitignore b/.gitignore index 763f224..a676215 100644 --- a/.gitignore +++ b/.gitignore @@ -1,11 +1,2 @@ .idea -ngx_http_auth_jwt_module.so -libjwt.so.0.6.0 -libjwt.la -libjwt.a -libjansson.so.4.13.0 -libjwt.so.0.7.0 -jansson.pc -libjwt.pc -libjansson.so.4.10.0 -libjwt.so.0.4.0 +bin diff --git a/Makefile b/Makefile index 252f799..4555781 100644 --- a/Makefile +++ b/Makefile @@ -47,7 +47,15 @@ stop-nginx: .PHONY: start-nginx start-nginx: docker run --rm --name "${DOCKER_IMAGE_NAME}" -d -p 8000:8000 ${DOCKER_ORG_NAME}/${DOCKER_IMAGE_NAME} - docker cp ${DOCKER_IMAGE_NAME}:/usr/lib64/nginx/modules/ngx_http_auth_jwt_module.so . + +.PHONY: cp-bin +cp-bin: start-nginx + rm -rf bin + mkdir -p bin + docker exec jwt-nginx sh -c "tar -chf - \ + /usr/lib64/nginx/modules/ngx_http_auth_jwt_module.so \ + /usr/lib/x86_64-linux-gnu/libjansson.so.* \ + /usr/lib/x86_64-linux-gnu/libjwt.*" 2>/dev/null | tar -xf - -C bin &>/dev/null .PHONY: build-test-runner build-test-runner: From aa024c58163b0639dbc95377c8c4cd95fcbf4cd0 Mon Sep 17 00:00:00 2001 From: Josh McCullough Date: Wed, 8 Jun 2022 10:26:33 -0400 Subject: [PATCH 09/75] add option to not extract sub -- fixes #66 (#70) * add option to not extract sub * move email variables into block where they are used * cleanup --- README.md | 15 ++++++++-- src/ngx_http_auth_jwt_module.c | 55 ++++++++++++++++++++-------------- test.sh | 5 ++-- 3 files changed, 46 insertions(+), 29 deletions(-) diff --git a/README.md b/README.md index 613ace6..718d7b5 100644 --- a/README.md +++ b/README.md @@ -41,9 +41,10 @@ which can be specified in on the `main` `server` or `location` level. auth_jwt_key "00112233445566778899AABBCCDDEEFF00112233445566778899AABBCCDDEEFF"; # see docs for format based on algorithm auth_jwt_loginurl "https://yourdomain.com/loginpage"; auth_jwt_enabled on; -auth_jwt_algorithm HS256; # or RS256 +auth_jwt_algorithm HS256; # or RS256 +auth_jwt_extract_sub on; # or off auth_jwt_validate_email on; # or off -auth_jwt_use_keyfile off; # or on +auth_jwt_use_keyfile off; # or on auth_jwt_keyfile_path "/app/pub_key"; ``` @@ -87,9 +88,17 @@ auth_jwt_validation_type COOKIE=rampartjwt; By default the authorization header is used to provide a JWT for validation. However, you may use the `auth_jwt_validation_type` configuration to specify the name of a cookie that provides the JWT. +``` +auth_jwt_extract_sub +``` +By default, the module will attempt to extract the `sub` claim (e.g. the user's id) from the JWT. If successful, the +value will be set in the `x-userid` HTTP header. An error will be logged if this option is enabled and the JWT does not +contain the `sub` claim. + ``` auth_jwt_validate_email off; ``` By default, the module will attempt to validate the email address field of the JWT, then set the x-email header of the session, and will log an error if it isn't found. To disable this behavior, for instance if you are using a different -user identifier property such as 'sub', set `auth_jwt_validate_email` to the value `off`. +user identifier property such as `sub`, set `auth_jwt_validate_email` to the value `off`. _Note that this flag may be +renamed to `auth_jwt_extract_email` in a future release._ diff --git a/src/ngx_http_auth_jwt_module.c b/src/ngx_http_auth_jwt_module.c index fbd07ba..f5ca357 100644 --- a/src/ngx_http_auth_jwt_module.c +++ b/src/ngx_http_auth_jwt_module.c @@ -27,6 +27,7 @@ typedef struct { ngx_flag_t auth_jwt_redirect; ngx_str_t auth_jwt_validation_type; ngx_str_t auth_jwt_algorithm; + ngx_flag_t auth_jwt_extract_sub; ngx_flag_t auth_jwt_validate_email; ngx_str_t auth_jwt_keyfile_path; ngx_flag_t auth_jwt_use_keyfile; @@ -84,6 +85,13 @@ static ngx_command_t ngx_http_auth_jwt_commands[] = { offsetof(ngx_http_auth_jwt_loc_conf_t, auth_jwt_algorithm), NULL }, + { ngx_string("auth_jwt_extract_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(ngx_http_auth_jwt_loc_conf_t, auth_jwt_extract_sub), + NULL }, + { ngx_string("auth_jwt_validate_email"), NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_FLAG, ngx_conf_set_flag_slot, @@ -152,10 +160,6 @@ static ngx_int_t ngx_http_auth_jwt_handler(ngx_http_request_t *r) jwt_t *jwt = NULL; int jwtParseReturnCode; jwt_alg_t alg; - const char* sub; - const char* email; - ngx_str_t sub_t; - ngx_str_t email_t; time_t exp; time_t now; ngx_str_t auth_jwt_algorithm; @@ -175,6 +179,7 @@ static ngx_int_t ngx_http_auth_jwt_handler(ngx_http_request_t *r) } jwtCookieValChrPtr = getJwt(r, jwtcf->auth_jwt_validation_type); + if (jwtCookieValChrPtr == NULL) { ngx_log_error(NGX_LOG_ERR, r->connection->log, 0, "failed to find a jwt"); @@ -184,6 +189,7 @@ static ngx_int_t ngx_http_auth_jwt_handler(ngx_http_request_t *r) // convert key from hex to binary, if a symmetric key auth_jwt_algorithm = jwtcf->auth_jwt_algorithm; + if (auth_jwt_algorithm.len == 0 || (auth_jwt_algorithm.len == sizeof("HS256") - 1 && ngx_strncmp(auth_jwt_algorithm.data, "HS256", sizeof("HS256") - 1)==0)) { keylen = jwtcf->auth_jwt_key.len / 2; @@ -218,6 +224,7 @@ static ngx_int_t ngx_http_auth_jwt_handler(ngx_http_request_t *r) // validate the jwt jwtParseReturnCode = jwt_decode(&jwt, jwtCookieValChrPtr, keyBinary, keylen); + if (jwtParseReturnCode != 0) { ngx_log_error(NGX_LOG_ERR, r->connection->log, 0, "failed to parse jwt"); @@ -226,6 +233,7 @@ static ngx_int_t ngx_http_auth_jwt_handler(ngx_http_request_t *r) // validate the algorithm alg = jwt_get_alg(jwt); + if (alg != JWT_ALG_HS256 && alg != JWT_ALG_RS256) { ngx_log_error(NGX_LOG_ERR, r->connection->log, 0, "invalid algorithm in jwt %d", alg); @@ -235,6 +243,7 @@ static ngx_int_t ngx_http_auth_jwt_handler(ngx_http_request_t *r) // validate the exp date of the JWT exp = (time_t)jwt_get_grant_int(jwt, "exp"); now = time(NULL); + if (exp < now) { ngx_log_error(NGX_LOG_ERR, r->connection->log, 0, "the jwt has expired"); @@ -242,38 +251,43 @@ static ngx_int_t ngx_http_auth_jwt_handler(ngx_http_request_t *r) } // extract the userid - sub = jwt_get_grant(jwt, "sub"); - if (sub == NULL) + if (jwtcf->auth_jwt_extract_sub == 1) { - ngx_log_error(NGX_LOG_ERR, r->connection->log, 0, "the jwt does not contain a subject"); - } - else - { - sub_t = ngx_char_ptr_to_str_t(r->pool, (char *)sub); - set_custom_header_in_headers_out(r, &useridHeaderName, &sub_t); + const char* sub = jwt_get_grant(jwt, "sub"); + + if (sub == NULL) + { + ngx_log_error(NGX_LOG_ERR, r->connection->log, 0, "the jwt does not contain a subject"); + } + else + { + ngx_str_t sub_t = ngx_char_ptr_to_str_t(r->pool, (char *)sub); + + set_custom_header_in_headers_out(r, &useridHeaderName, &sub_t); + } } if (jwtcf->auth_jwt_validate_email == 1) { - email = jwt_get_grant(jwt, "emailAddress"); + const char* email = jwt_get_grant(jwt, "emailAddress"); + if (email == NULL) { ngx_log_error(NGX_LOG_ERR, r->connection->log, 0, "the jwt does not contain an email address"); } else { - email_t = ngx_char_ptr_to_str_t(r->pool, (char *)email); + ngx_str_t email_t = ngx_char_ptr_to_str_t(r->pool, (char *)email); + set_custom_header_in_headers_out(r, &emailHeaderName, &email_t); } } jwt_free(jwt); - return NGX_OK; redirect: - if (jwt) { jwt_free(jwt); @@ -303,7 +317,6 @@ static ngx_int_t ngx_http_auth_jwt_handler(ngx_http_request_t *r) uintptr_t escaped_len; loginlen = jwtcf->auth_jwt_loginurl.len; - scheme = (r->connection->ssl) ? "https" : "http"; server = r->headers_in.server; @@ -318,15 +331,11 @@ static ngx_int_t ngx_http_auth_jwt_handler(ngx_http_request_t *r) uri.data = ngx_palloc(r->pool, request_uri_var->len); uri.len = request_uri_var->len; ngx_memcpy(uri.data, request_uri_var->data, request_uri_var->len); - - // ngx_log_error(NGX_LOG_ERR, r->connection->log, 0, "found uri with querystring %s", ngx_str_t_to_char_ptr(r->pool, uri)); } else { // fallback to the querystring without params uri = r->uri; - - // ngx_log_error(NGX_LOG_ERR, r->connection->log, 0, "fallback to querystring without params"); } // escape the URI @@ -350,8 +359,6 @@ static ngx_int_t ngx_http_auth_jwt_handler(ngx_http_request_t *r) ngx_memcpy(return_url+return_url_idx, uri_escaped.data, uri_escaped.len); return_url_idx += uri_escaped.len; r->headers_out.location->value.data = (u_char *)return_url; - - // ngx_log_error(NGX_LOG_ERR, r->connection->log, 0, "return_url: %s", ngx_str_t_to_char_ptr(r->pool, r->headers_out.location->value)); } else { @@ -403,6 +410,7 @@ ngx_http_auth_jwt_create_loc_conf(ngx_conf_t *cf) // set the flag to unset conf->auth_jwt_enabled = (ngx_flag_t) -1; conf->auth_jwt_redirect = (ngx_flag_t) -1; + conf->auth_jwt_extract_sub = (ngx_flag_t) -1; conf->auth_jwt_validate_email = (ngx_flag_t) -1; conf->auth_jwt_use_keyfile = (ngx_flag_t) -1; @@ -453,6 +461,7 @@ ngx_http_auth_jwt_merge_loc_conf(ngx_conf_t *cf, void *parent, void *child) ngx_conf_merge_str_value(conf->auth_jwt_validation_type, prev->auth_jwt_validation_type, ""); ngx_conf_merge_str_value(conf->auth_jwt_algorithm, prev->auth_jwt_algorithm, "HS256"); ngx_conf_merge_str_value(conf->auth_jwt_keyfile_path, prev->auth_jwt_keyfile_path, ""); + ngx_conf_merge_off_value(conf->auth_jwt_extract_sub, prev->auth_jwt_extract_sub, 1); ngx_conf_merge_off_value(conf->auth_jwt_validate_email, prev->auth_jwt_validate_email, 1); if (conf->auth_jwt_enabled == ((ngx_flag_t) -1)) diff --git a/test.sh b/test.sh index adeb777..55230b4 100755 --- a/test.sh +++ b/test.sh @@ -11,10 +11,9 @@ test_jwt () { local extra=$4 cmd="curl -X GET -o /dev/null --silent --head --write-out '%{http_code}' http://nginx:8000$path -H 'cache-control: no-cache' $extra" - - test=$( eval ${cmd} ) - if [ "$test" -eq "$expect" ];then + + if [ "$test" -eq "$expect" ]; then echo -e "${GREEN}${name}: passed (${test})${NONE}"; else echo -e "${RED}${name}: failed (${test})${NONE}"; From 8f39e48fbbeecf909c6c1b7e5dd894b9daee6c4b Mon Sep 17 00:00:00 2001 From: Harm van Tilborg Date: Mon, 15 Aug 2022 15:51:50 +0200 Subject: [PATCH 10/75] Do not respond with a "Location" header when redirects are disabled (#74) * In the default nginx config (used in the Docker container), nginx listens on port 80 * Do not respond with a "Location" header when redirects are disabled --- Makefile | 4 +- src/ngx_http_auth_jwt_module.c | 143 ++++++++++++++++----------------- 2 files changed, 73 insertions(+), 74 deletions(-) diff --git a/Makefile b/Makefile index 4555781..17c7aa2 100644 --- a/Makefile +++ b/Makefile @@ -46,7 +46,7 @@ stop-nginx: .PHONY: start-nginx start-nginx: - docker run --rm --name "${DOCKER_IMAGE_NAME}" -d -p 8000:8000 ${DOCKER_ORG_NAME}/${DOCKER_IMAGE_NAME} + docker run --rm --name "${DOCKER_IMAGE_NAME}" -d -p 8000:80 ${DOCKER_ORG_NAME}/${DOCKER_IMAGE_NAME} .PHONY: cp-bin cp-bin: start-nginx @@ -70,4 +70,4 @@ test: IMAGE_VERSION=${NGINX_VERSION} docker compose -f ./docker-compose-test.yml up --no-start docker start ${COMPOSE_PROJECT_NAME}-nginx-1 docker start -a ${COMPOSE_PROJECT_NAME}-runner-1 - docker compose -f ./docker-compose-test.yml down \ No newline at end of file + docker compose -f ./docker-compose-test.yml down diff --git a/src/ngx_http_auth_jwt_module.c b/src/ngx_http_auth_jwt_module.c index f5ca357..f91e608 100644 --- a/src/ngx_http_auth_jwt_module.c +++ b/src/ngx_http_auth_jwt_module.c @@ -227,7 +227,7 @@ static ngx_int_t ngx_http_auth_jwt_handler(ngx_http_request_t *r) if (jwtParseReturnCode != 0) { - ngx_log_error(NGX_LOG_ERR, r->connection->log, 0, "failed to parse jwt"); + ngx_log_error(NGX_LOG_ERR, r->connection->log, 0, "failed to parse jwt, error code %d", jwtParseReturnCode); goto redirect; } @@ -293,88 +293,87 @@ static ngx_int_t ngx_http_auth_jwt_handler(ngx_http_request_t *r) jwt_free(jwt); } - r->headers_out.location = ngx_list_push(&r->headers_out.headers); - - if (r->headers_out.location == NULL) + if (jwtcf->auth_jwt_redirect) { - ngx_http_finalize_request(r, NGX_HTTP_INTERNAL_SERVER_ERROR); - } + r->headers_out.location = ngx_list_push(&r->headers_out.headers); - r->headers_out.location->hash = 1; - r->headers_out.location->key.len = sizeof("Location") - 1; - r->headers_out.location->key.data = (u_char *) "Location"; + if (r->headers_out.location == NULL) + { + ngx_http_finalize_request(r, NGX_HTTP_INTERNAL_SERVER_ERROR); + } - if (r->method == NGX_HTTP_GET) - { - int loginlen; - char * scheme; - ngx_str_t server; - ngx_str_t uri_variable_name = ngx_string("request_uri"); - ngx_int_t uri_variable_hash; - ngx_http_variable_value_t * request_uri_var; - ngx_str_t uri; - ngx_str_t uri_escaped; - uintptr_t escaped_len; - - loginlen = jwtcf->auth_jwt_loginurl.len; - scheme = (r->connection->ssl) ? "https" : "http"; - server = r->headers_in.server; - - // get the URI - uri_variable_hash = ngx_hash_key(uri_variable_name.data, uri_variable_name.len); - request_uri_var = ngx_http_get_variable(r, &uri_variable_name, uri_variable_hash); - - // get the URI - if(request_uri_var && !request_uri_var->not_found && request_uri_var->valid) + r->headers_out.location->hash = 1; + r->headers_out.location->key.len = sizeof("Location") - 1; + r->headers_out.location->key.data = (u_char *) "Location"; + + if (r->method == NGX_HTTP_GET) { - // ideally we would like the uri with the querystring parameters - uri.data = ngx_palloc(r->pool, request_uri_var->len); - uri.len = request_uri_var->len; - ngx_memcpy(uri.data, request_uri_var->data, request_uri_var->len); + int loginlen; + char * scheme; + ngx_str_t server; + ngx_str_t uri_variable_name = ngx_string("request_uri"); + ngx_int_t uri_variable_hash; + ngx_http_variable_value_t * request_uri_var; + ngx_str_t uri; + ngx_str_t uri_escaped; + uintptr_t escaped_len; + + loginlen = jwtcf->auth_jwt_loginurl.len; + scheme = (r->connection->ssl) ? "https" : "http"; + server = r->headers_in.server; + + // get the URI + uri_variable_hash = ngx_hash_key(uri_variable_name.data, uri_variable_name.len); + request_uri_var = ngx_http_get_variable(r, &uri_variable_name, uri_variable_hash); + + // get the URI + if(request_uri_var && !request_uri_var->not_found && request_uri_var->valid) + { + // ideally we would like the uri with the querystring parameters + uri.data = ngx_palloc(r->pool, request_uri_var->len); + uri.len = request_uri_var->len; + ngx_memcpy(uri.data, request_uri_var->data, request_uri_var->len); + } + else + { + // fallback to the querystring without params + uri = r->uri; + } + + // 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); + + r->headers_out.location->value.len = loginlen + sizeof("?return_url=") - 1 + strlen(scheme) + sizeof("://") - 1 + server.len + uri_escaped.len; + return_url = ngx_palloc(r->pool, r->headers_out.location->value.len); + ngx_memcpy(return_url, jwtcf->auth_jwt_loginurl.data, jwtcf->auth_jwt_loginurl.len); + int return_url_idx = jwtcf->auth_jwt_loginurl.len; + ngx_memcpy(return_url+return_url_idx, "?return_url=", sizeof("?return_url=") - 1); + return_url_idx += sizeof("?return_url=") - 1; + ngx_memcpy(return_url+return_url_idx, scheme, strlen(scheme)); + return_url_idx += strlen(scheme); + ngx_memcpy(return_url+return_url_idx, "://", sizeof("://") - 1); + return_url_idx += sizeof("://") - 1; + ngx_memcpy(return_url+return_url_idx, server.data, server.len); + return_url_idx += server.len; + ngx_memcpy(return_url+return_url_idx, uri_escaped.data, uri_escaped.len); + return_url_idx += uri_escaped.len; + r->headers_out.location->value.data = (u_char *)return_url; } else { - // fallback to the querystring without params - uri = r->uri; + // for non-get requests, redirect to the login page without a return URL + r->headers_out.location->value.len = jwtcf->auth_jwt_loginurl.len; + r->headers_out.location->value.data = jwtcf->auth_jwt_loginurl.data; } - // 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); - - r->headers_out.location->value.len = loginlen + sizeof("?return_url=") - 1 + strlen(scheme) + sizeof("://") - 1 + server.len + uri_escaped.len; - return_url = ngx_palloc(r->pool, r->headers_out.location->value.len); - ngx_memcpy(return_url, jwtcf->auth_jwt_loginurl.data, jwtcf->auth_jwt_loginurl.len); - int return_url_idx = jwtcf->auth_jwt_loginurl.len; - ngx_memcpy(return_url+return_url_idx, "?return_url=", sizeof("?return_url=") - 1); - return_url_idx += sizeof("?return_url=") - 1; - ngx_memcpy(return_url+return_url_idx, scheme, strlen(scheme)); - return_url_idx += strlen(scheme); - ngx_memcpy(return_url+return_url_idx, "://", sizeof("://") - 1); - return_url_idx += sizeof("://") - 1; - ngx_memcpy(return_url+return_url_idx, server.data, server.len); - return_url_idx += server.len; - ngx_memcpy(return_url+return_url_idx, uri_escaped.data, uri_escaped.len); - return_url_idx += 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->auth_jwt_loginurl.len; - r->headers_out.location->value.data = jwtcf->auth_jwt_loginurl.data; - } - - if (jwtcf->auth_jwt_redirect) - { return NGX_HTTP_MOVED_TEMPORARILY; } - else - { - return NGX_HTTP_UNAUTHORIZED; - } + + // When no redirect is needed, no "Location" header construction is needed, and we can respond with a 401 + return NGX_HTTP_UNAUTHORIZED; } From 1cf8606dd89deb74f275d1e4b81c3d6d6913cefb Mon Sep 17 00:00:00 2001 From: Josh McCullough Date: Fri, 28 Oct 2022 14:17:34 -0400 Subject: [PATCH 11/75] update cookie name in README and test --- README.md | 2 +- resources/test-jwt-nginx.conf | 4 ++-- test.sh | 8 ++++---- 3 files changed, 7 insertions(+), 7 deletions(-) diff --git a/README.md b/README.md index 718d7b5..fe1a2e5 100644 --- a/README.md +++ b/README.md @@ -83,7 +83,7 @@ If you prefer to return 401 Unauthorized, you may turn `auth_jwt_redirect` off. ``` auth_jwt_validation_type AUTHORIZATION; -auth_jwt_validation_type COOKIE=rampartjwt; +auth_jwt_validation_type COOKIE=jwt; ``` By default the authorization header is used to provide a JWT for validation. However, you may use the `auth_jwt_validation_type` configuration to specify the name of a cookie that provides the JWT. diff --git a/resources/test-jwt-nginx.conf b/resources/test-jwt-nginx.conf index cbb356c..53f512b 100644 --- a/resources/test-jwt-nginx.conf +++ b/resources/test-jwt-nginx.conf @@ -16,7 +16,7 @@ server { location ~ ^/secure/ { auth_jwt_enabled on; - auth_jwt_validation_type COOKIE=rampartjwt; + auth_jwt_validation_type COOKIE=jwt; root /usr/share/nginx; index index.html index.htm; } @@ -29,7 +29,7 @@ server { location ~ ^/secure-rs256/ { auth_jwt_enabled on; - auth_jwt_validation_type COOKIE=rampartjwt; + auth_jwt_validation_type COOKIE=jwt; auth_jwt_algorithm RS256; auth_jwt_key "-----BEGIN PUBLIC KEY----- MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAwtpMAM4l1H995oqlqdMh diff --git a/test.sh b/test.sh index 55230b4..45d21ee 100755 --- a/test.sh +++ b/test.sh @@ -31,7 +31,7 @@ main() { test_jwt "Secure test without jwt cookie" "/secure/" "302" - test_jwt "Secure test with jwt cookie" "/secure/" "200" "--cookie \"rampartjwt=${VALIDJWT}\"" + test_jwt "Secure test with jwt cookie" "/secure/" "200" "--cookie \"jwt=${VALIDJWT}\"" test_jwt "Secure test with jwt auth header" "/secure-auth-header/" "200" "--header \"Authorization: Bearer ${VALIDJWT}\"" @@ -41,11 +41,11 @@ main() { test_jwt "Secure test without jwt auth header" "/secure-no-redirect/" "401" - test_jwt "Secure test with jwt cookie - with no sub" "/secure/" "200" " --cookie \"rampartjwt=${MISSING_SUB_JWT}\"" + test_jwt "Secure test with jwt cookie - with no sub" "/secure/" "200" " --cookie \"jwt=${MISSING_SUB_JWT}\"" - test_jwt "Secure test with jwt cookie - with no email" "/secure/" "200" " --cookie \"rampartjwt=${MISSING_EMAIL_JWT}\"" + test_jwt "Secure test with jwt cookie - with no email" "/secure/" "200" " --cookie \"jwt=${MISSING_EMAIL_JWT}\"" - test_jwt "Secure test with rs256 jwt cookie" "/secure-rs256/" "200" " --cookie \"rampartjwt=${VALID_RS256_JWT}\"" + test_jwt "Secure test with rs256 jwt cookie" "/secure-rs256/" "200" " --cookie \"jwt=${VALID_RS256_JWT}\"" test_jwt "Secure test rsa256 from file with valid jwt" "/secure-rs256-file/" "200" "--header \"Authorization: Bearer ${VALID_RS256_JWT}\"" From bd0911851838428a62537f988ffbf165e8d10e18 Mon Sep 17 00:00:00 2001 From: Josh McCullough Date: Fri, 28 Oct 2022 14:28:45 -0400 Subject: [PATCH 12/75] update README --- README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/README.md b/README.md index fe1a2e5..ab4f8cd 100644 --- a/README.md +++ b/README.md @@ -3,6 +3,7 @@ This is an NGINX module to check for a valid JWT and proxy to an upstream server ## Building and testing To build the Docker image, start NGINX, and run our Bash test against it, run + ```bash make ``` From 223a4e298c1e423efb98712ba6e887278880466a Mon Sep 17 00:00:00 2001 From: Josh McCullough Date: Fri, 28 Oct 2022 14:28:50 -0400 Subject: [PATCH 13/75] fix tests --- docker-compose-test.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docker-compose-test.yml b/docker-compose-test.yml index 8d406f2..021d191 100644 --- a/docker-compose-test.yml +++ b/docker-compose-test.yml @@ -17,4 +17,4 @@ services: BASE_IMAGE: ${IMAGE_NAME:-teslagov/jwt-nginx}:${IMAGE_VERSION:-latest} depends_on: - - nginx-test \ No newline at end of file + - nginx \ No newline at end of file From d1507058a3385421ecb31ac379c7b3e8ec07c3d1 Mon Sep 17 00:00:00 2001 From: Josh McCullough Date: Mon, 7 Nov 2022 21:09:30 -0500 Subject: [PATCH 14/75] rename variable for clarity --- src/ngx_http_auth_jwt_module.c | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/src/ngx_http_auth_jwt_module.c b/src/ngx_http_auth_jwt_module.c index f91e608..4209e3c 100644 --- a/src/ngx_http_auth_jwt_module.c +++ b/src/ngx_http_auth_jwt_module.c @@ -152,7 +152,7 @@ static ngx_int_t ngx_http_auth_jwt_handler(ngx_http_request_t *r) { ngx_str_t useridHeaderName = ngx_string("x-userid"); ngx_str_t emailHeaderName = ngx_string("x-email"); - char* jwtCookieValChrPtr; + char* jwtPtr; char* return_url; ngx_http_auth_jwt_loc_conf_t *jwtcf; u_char *keyBinary; @@ -178,9 +178,9 @@ static ngx_int_t ngx_http_auth_jwt_handler(ngx_http_request_t *r) return NGX_DECLINED; } - jwtCookieValChrPtr = getJwt(r, jwtcf->auth_jwt_validation_type); + jwtPtr = getJwt(r, jwtcf->auth_jwt_validation_type); - if (jwtCookieValChrPtr == NULL) + if (jwtPtr == NULL) { ngx_log_error(NGX_LOG_ERR, r->connection->log, 0, "failed to find a jwt"); goto redirect; @@ -223,7 +223,7 @@ static ngx_int_t ngx_http_auth_jwt_handler(ngx_http_request_t *r) } // validate the jwt - jwtParseReturnCode = jwt_decode(&jwt, jwtCookieValChrPtr, keyBinary, keylen); + jwtParseReturnCode = jwt_decode(&jwt, jwtPtr, keyBinary, keylen); if (jwtParseReturnCode != 0) { @@ -500,7 +500,7 @@ static char * getJwt(ngx_http_request_t *r, ngx_str_t auth_jwt_validation_type) { static const ngx_str_t authorizationHeaderName = ngx_string("Authorization"); ngx_table_elt_t *authorizationHeader; - char* jwtCookieValChrPtr = NULL; + char* jwtPtr = NULL; ngx_str_t jwtCookieVal; ngx_int_t n; ngx_int_t bearer_length; @@ -523,9 +523,9 @@ static char * getJwt(ngx_http_request_t *r, ngx_str_t auth_jwt_validation_type) authorizationHeaderStr.data = authorizationHeader->value.data + sizeof("Bearer ") - 1; authorizationHeaderStr.len = bearer_length; - jwtCookieValChrPtr = ngx_str_t_to_char_ptr(r->pool, authorizationHeaderStr); + jwtPtr = ngx_str_t_to_char_ptr(r->pool, authorizationHeaderStr); - ngx_log_error(NGX_LOG_DEBUG, r->connection->log, 0, "Authorization header: %s", jwtCookieValChrPtr); + ngx_log_error(NGX_LOG_DEBUG, r->connection->log, 0, "Authorization header: %s", jwtPtr); } } } @@ -539,11 +539,11 @@ static char * getJwt(ngx_http_request_t *r, ngx_str_t auth_jwt_validation_type) n = ngx_http_parse_multi_header_lines(&r->headers_in.cookies, &auth_jwt_validation_type, &jwtCookieVal); if (n != NGX_DECLINED) { - jwtCookieValChrPtr = ngx_str_t_to_char_ptr(r->pool, jwtCookieVal); + jwtPtr = ngx_str_t_to_char_ptr(r->pool, jwtCookieVal); } } - return jwtCookieValChrPtr; + return jwtPtr; } From d7c3cb48a3ec7b11f0e5eda9f36a7b11c7bc2c7d Mon Sep 17 00:00:00 2001 From: Josh McCullough Date: Wed, 9 Nov 2022 10:25:33 -0500 Subject: [PATCH 15/75] add support for higher-bit HS/RS algorithms (#80) * add support for higher-bit HS/RS algorithms fixes #77 * add Git hooks * use variable for Docker image name * cleanup --- .bin/git/hooks-wrapper | 43 ++++++ .bin/git/hooks/pre-push-build-and-test | 12 ++ .bin/git/init-hooks | 19 +++ .bin/init | 3 + Dockerfile | 35 ++--- Dockerfile-test-nginx | 10 -- Makefile | 73 --------- README.md | 142 ++++++++++++------ config | 7 +- docker-compose-test.yml | 20 --- resources/nginx.conf | 22 +-- resources/test-jwt-nginx.conf | 63 -------- scripts.sh | 89 +++++++++++ src/ngx_http_auth_jwt_module.c | 20 +-- test.sh | 55 ------- test/Dockerfile-test-nginx | 5 + .../Dockerfile-test-runner | 0 test/docker-compose-test.yml | 24 +++ {resources => test}/rsa_key_2048-pub.pem | 0 {resources => test}/rsa_key_2048.pem | 0 test/test.conf | 134 +++++++++++++++++ test/test.sh | 139 +++++++++++++++++ 22 files changed, 598 insertions(+), 317 deletions(-) create mode 100755 .bin/git/hooks-wrapper create mode 100755 .bin/git/hooks/pre-push-build-and-test create mode 100755 .bin/git/init-hooks create mode 100755 .bin/init delete mode 100644 Dockerfile-test-nginx delete mode 100644 Makefile delete mode 100644 docker-compose-test.yml delete mode 100644 resources/test-jwt-nginx.conf create mode 100755 scripts.sh delete mode 100755 test.sh create mode 100644 test/Dockerfile-test-nginx rename Dockerfile-test-runner => test/Dockerfile-test-runner (100%) create mode 100644 test/docker-compose-test.yml rename {resources => test}/rsa_key_2048-pub.pem (100%) rename {resources => test}/rsa_key_2048.pem (100%) create mode 100644 test/test.conf create mode 100755 test/test.sh diff --git a/.bin/git/hooks-wrapper b/.bin/git/hooks-wrapper new file mode 100755 index 0000000..33cea38 --- /dev/null +++ b/.bin/git/hooks-wrapper @@ -0,0 +1,43 @@ +#!/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 +done \ No newline at end of file diff --git a/.bin/git/hooks/pre-push-build-and-test b/.bin/git/hooks/pre-push-build-and-test new file mode 100755 index 0000000..9cf4682 --- /dev/null +++ b/.bin/git/hooks/pre-push-build-and-test @@ -0,0 +1,12 @@ +#!/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_runner test) +else + HOOK_NAME=$(basename $0) + + echo "Skipping hook '${HOOK_NAME}' -- no changes detected which would require tests to be run." +fi diff --git a/.bin/git/init-hooks b/.bin/git/init-hooks new file mode 100755 index 0000000..1513105 --- /dev/null +++ b/.bin/git/init-hooks @@ -0,0 +1,19 @@ +#!/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} +done \ No newline at end of file diff --git a/.bin/init b/.bin/init new file mode 100755 index 0000000..9c59222 --- /dev/null +++ b/.bin/init @@ -0,0 +1,3 @@ +#!/usr/bin/env bash + +source $(dirname $0)/git/init-hooks \ No newline at end of file diff --git a/Dockerfile b/Dockerfile index ad36f02..b0712ea 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,9 +1,9 @@ -ARG NGINX_VERSION=1.22.0 +ARG NGINX_VERSION FROM debian:bullseye-slim as BASE_IMAGE LABEL stage=builder -RUN apt-get update \ +RUN apt-get update \ && apt-get install -y curl build-essential @@ -11,30 +11,31 @@ FROM BASE_IMAGE as BUILD_IMAGE LABEL stage=builder ENV LD_LIBRARY_PATH=/usr/local/lib ARG NGINX_VERSION -ADD . /root/dl/ngx-http-auth-jwt-module -RUN set -x \ +RUN set -x \ && apt-get install -y libjwt-dev libjwt0 libjansson-dev libjansson4 libpcre2-dev zlib1g-dev libpcre3-dev \ - && mkdir -p /root/dl -WORKDIR /root/dl -RUN set -x \ - && curl -O http://nginx.org/download/nginx-$NGINX_VERSION.tar.gz \ - && tar -xzf nginx-$NGINX_VERSION.tar.gz \ - && rm nginx-$NGINX_VERSION.tar.gz \ - && ln -sf nginx-$NGINX_VERSION nginx \ - && cd /root/dl/nginx \ - && ./configure --with-compat --add-dynamic-module=../ngx-http-auth-jwt-module \ + && mkdir -p /root/build/ngx-http-auth-jwt-module +WORKDIR /root/build/ngx-http-auth-jwt-module +ADD config ./ +ADD src/*.h src/*.c ./src/ +WORKDIR /root/build +RUN set -x \ + && 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 \ + && rm nginx-${NGINX_VERSION}.tar.gz +WORKDIR /root/build/nginx +RUN ./configure --with-compat --add-dynamic-module=../ngx-http-auth-jwt-module \ && make modules FROM nginx:${NGINX_VERSION} LABEL stage=builder -RUN apt-get update \ - && apt-get -y install libjansson4 libjwt0 \ +RUN apt-get update \ + && apt-get -y install libjansson4 libjwt0 \ && cd /etc/nginx \ - && cp nginx.conf nginx.conf.orig \ && sed -ri '/pid\s+\/var\/run\/nginx\.pid;$/a load_module \/usr\/lib64\/nginx\/modules\/ngx_http_auth_jwt_module\.so;' nginx.conf LABEL stage= LABEL maintainer="TeslaGov" email="developers@teslagov.com" -COPY --from=BUILD_IMAGE /root/dl/nginx/objs/ngx_http_auth_jwt_module.so /usr/lib64/nginx/modules/ +COPY --from=BUILD_IMAGE /root/build/nginx/objs/ngx_http_auth_jwt_module.so /usr/lib64/nginx/modules/ diff --git a/Dockerfile-test-nginx b/Dockerfile-test-nginx deleted file mode 100644 index 10a1eae..0000000 --- a/Dockerfile-test-nginx +++ /dev/null @@ -1,10 +0,0 @@ -ARG BASE_IMAGE=teslagov/jwt-nginx:latest - -FROM ${BASE_IMAGE} as NGINX -COPY resources/test-jwt-nginx.conf /etc/nginx/conf.d/test-jwt-nginx.conf -COPY resources/rsa_key_2048-pub.pem /etc/nginx/rsa-key.conf -RUN cp -r /usr/share/nginx/html /usr/share/nginx/secure \ - && cp -r /usr/share/nginx/html /usr/share/nginx/secure-rs256 \ - && cp -r /usr/share/nginx/html /usr/share/nginx/secure-rs256-file \ - && cp -r /usr/share/nginx/html /usr/share/nginx/secure-auth-header \ - && cp -r /usr/share/nginx/html /usr/share/nginx/secure-no-redirect diff --git a/Makefile b/Makefile deleted file mode 100644 index 17c7aa2..0000000 --- a/Makefile +++ /dev/null @@ -1,73 +0,0 @@ -SHELL += -eu - -BLUE := \033[0;34m -GREEN := \033[0;32m -RED := \033[0;31m -NC := \033[0m - -DOCKER_ORG_NAME ?= teslagov -DOCKER_IMAGE_NAME ?= jwt-nginx -COMPOSE_PROJECT_NAME ?= jwt-nginx-test -NGINX_VERSION ?= 1.22.0 - -.PHONY: all -all: - @$(MAKE) build-nginx - @$(MAKE) start-nginx - @$(MAKE) test - -.PHONY: build-nginx -build-nginx: - @echo "${BLUE} Building...${NC}" - @docker image pull debian:bullseye-slim - @docker image pull nginx:${NGINX_VERSION} - @docker image build -t ${DOCKER_ORG_NAME}/${DOCKER_IMAGE_NAME}:latest -t ${DOCKER_ORG_NAME}/${DOCKER_IMAGE_NAME}:${NGINX_VERSION} --build-arg NGINX_VERSION=${NGINX_VERSION} . ; \ - SUCCESS=$$? ; \ - docker rmi $$(docker images --filter=label=stage=builder --quiet); \ - if [ "$$SUCCESS" -ne 0 ] ; \ - then echo "${RED} Build failed ${NC}"; \ - else echo "${GREEN}✓ Successfully built NGINX module ${NC}"; fi - -.PHONY: rebuild-nginx -rebuild-nginx: - @echo "${BLUE} Rebuilding...${NC}" - @docker image pull debian:bullseye-slim - @docker image pull nginx:${NGINX_VERSION} - @docker image build -t ${DOCKER_ORG_NAME}/${DOCKER_IMAGE_NAME}:latest -t ${DOCKER_ORG_NAME}/${DOCKER_IMAGE_NAME}:${NGINX_VERSION} --build-arg NGINX_VERSION=${NGINX_VERSION} --no-cache .; \ - SUCCESS=$$? ; \ - docker rmi $$(docker images --filter=label=stage=builder --quiet); \ - if [ "$$SUCCESS" -ne 0 ] ; \ - then echo "${RED} Build failed ${NC}"; \ - else echo "${GREEN}✓ Successfully rebuilt NGINX module ${NC}"; fi - -.PHONY: stop-nginx -stop-nginx: - docker stop "${DOCKER_IMAGE_NAME}" - -.PHONY: start-nginx -start-nginx: - docker run --rm --name "${DOCKER_IMAGE_NAME}" -d -p 8000:80 ${DOCKER_ORG_NAME}/${DOCKER_IMAGE_NAME} - -.PHONY: cp-bin -cp-bin: start-nginx - rm -rf bin - mkdir -p bin - docker exec jwt-nginx sh -c "tar -chf - \ - /usr/lib64/nginx/modules/ngx_http_auth_jwt_module.so \ - /usr/lib/x86_64-linux-gnu/libjansson.so.* \ - /usr/lib/x86_64-linux-gnu/libjwt.*" 2>/dev/null | tar -xf - -C bin &>/dev/null - -.PHONY: build-test-runner -build-test-runner: - IMAGE_VERSION=${NGINX_VERSION} docker compose -f ./docker-compose-test.yml build - -.PHONY: rebuild-test-runner -rebuild-test-runner: - IMAGE_VERSION=${NGINX_VERSION} docker compose -f ./docker-compose-test.yml build --no-cache - -.PHONY: test -test: - IMAGE_VERSION=${NGINX_VERSION} docker compose -f ./docker-compose-test.yml up --no-start - docker start ${COMPOSE_PROJECT_NAME}-nginx-1 - docker start -a ${COMPOSE_PROJECT_NAME}-runner-1 - docker compose -f ./docker-compose-test.yml down diff --git a/README.md b/README.md index ab4f8cd..84913ee 100644 --- a/README.md +++ b/README.md @@ -1,58 +1,96 @@ # Intro + This is an NGINX module to check for a valid JWT and proxy to an upstream server or redirect to a login page. ## Building and testing + To build the Docker image, start NGINX, and run our Bash test against it, run ```bash -make +./scripts.sh all ``` -When you make a change to the module, run `make rebuild-nginx`. +When you make a change to the module or the NGINX test config, run `./scripts.sh rebuild_nginx` to rebuild the NGINX Docker image. -When you make a change to `test.sh`, run `make rebuild-test-runner`. +When you make a change to `test.sh`, run `./scripts.sh rebuild_test_runner test` to rebuild the test runner image and run the tests. -| Command | Description | -| -------------------------- |:-----------------------------------------------------------------:| -| `make build-nginx` | Builds the NGINX image | -| `make rebuild-nginx` | Re-builds the NGINX image | -| `make build-test-runner` | Builds the images used by the test stack (uses Docker compose) | -| `make rebuild-test-runner` | Re-builds the images used by the test stack | -| `make start-nginx` | Starts the NGINX container | -| `make stop-nginx` | Stops the NGINX container | -| `make test` | Runs `test.sh` against the NGINX container (uses Docker compose) | +The `./scripts.sh` file contains multiple commands to make things easy: -The image produced with `make build-nginx` only differs from the official Nginx image in two ways: the module itself and the nginx.conf configuration entry that loads it. +| Command | Description | +| --------------------- | ----------------------------------------------------------------- | +| `build_nginx` | Builds the NGINX image. | +| `rebuild_nginx` | Re-builds the NGINX image. | +| `start_nginx` | Starts the NGINX container. | +| `stop_nginx` | Stops the NGINX container. | +| `cp_bin` | Copies the compiled binaries out of the NGINX container. | +| `build_test_runner` | Builds the images used by the test stack (uses Docker compose). | +| `rebuild_test_runner` | Re-builds the images used by the test stack. | +| `test` | Runs `test.sh` against the NGINX container (uses Docker compose). | -The tests use a customized Nginx image, distinct from the main image, as well as a test runner image. By running `make test`, the two test containers will be created up with Docker compose, started, and the tests run. At the end, both containers will be automatically stopped and destroyed. +You can run multiple commands in sequence by separating them with a space, e.g.: -This project is 100% Docker-ized. +```shell +./scripts.sh rebuild_nginx rebuild_test_runner test +``` -## Dependencies -This module depends on the [JWT C Library](https://github.com/benmcollins/libjwt) +The image produced with `./scripts.sh build_nginx` only differs from the official NGINX image in two ways: + - the JWT module itself, and + - the `nginx.conf` file is overwritten with our own. -Transitively, that library depends on a JSON Parser called -[Jansson](https://github.com/akheron/jansson) as well as the OpenSSL library. +The tests use a customized NGINX image, distinct from the main image, as well as a test runner image. By running `./scripts.sh 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. -## NGINX Directives -This module requires several new `nginx.conf` directives, -which can be specified in on the `main` `server` or `location` level. +### 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. + +In order to persist logs, you can configure the log driver to use. You can do this by setting the environment variable `LOG_DRIVER` before running the tests. On Linux/Unix systems, you can use the driver `journald`, as follows: + +```shell +# need to rebuild the test runner with the proper log driver +LOG_DRIVER=journald ./scripts.sh rebuild_test_runner +# run the tests +./scripts.sh test + +# check the logs +journalctl -eu docker CONTAINER_NAME=jwt-nginx-test ``` -auth_jwt_key "00112233445566778899AABBCCDDEEFF00112233445566778899AABBCCDDEEFF"; # see docs for format based on algorithm -auth_jwt_loginurl "https://yourdomain.com/loginpage"; -auth_jwt_enabled on; -auth_jwt_algorithm HS256; # or RS256 -auth_jwt_extract_sub on; # or off -auth_jwt_validate_email on; # or off -auth_jwt_use_keyfile off; # or on -auth_jwt_keyfile_path "/app/pub_key"; + +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.sh test + +# terminal 2 +journalctl -fu docker CONTAINER_NAME=jwt-nginx-test ``` -The default algorithm is 'HS256', for symmetric key validation. When using HS256, 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) as in the example above. 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, Section 5.3.2 The HMAC Key. +## 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. + +## NGINX Directives +This module requires several new `nginx.conf` directives, which can be specified at the `http`, `server`, or `location` levels. + +| Directive | Description | +| -------------------------- | ------------------------------------------------------------------------------------------------------------------ | +| `auth_jwt_key` | The key to use to decode/verify the JWT -- 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_extract_sub` | Set to "on" to extract the `sub` claim (e.g. user id) from the JWT and into the `x-userid` header on the response. | +| `auth_jwt_validate_email` | Set to "on" to extract the `emailAddress` claim from the JWT and into the `x-email` header on the response. | +| `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. | + + +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) as in the example above. 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. + +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. -The configuration also supports the `auth_jwt_algorithm` 'RS256', for RSA 256-bit public key validation. If using "auth_jwt_algorithm RS256;", then the `auth_jwt_key` field must be set to your public key **OR** `auth_jwt_use_keyfile` should be set to `on` with the `auth_jwt_keyfile_path` set to the public key path (nginx won't start if the `auth_jwt_use_keyfile` is set to `on` without a keyfile). -That is the public key, rather than a PEM certificate. I.e.: +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.: ``` auth_jwt_key "-----BEGIN PUBLIC KEY----- @@ -66,40 +104,44 @@ oQIDAQAB -----END PUBLIC KEY-----"; ``` -**OR** +When using an `RS*` algorithm with a public key file, do as follows: ``` auth_jwt_use_keyfile on; -auth_jwt_keyfile_path "/etc/nginx/pub_key.pem"; +auth_jwt_keyfile_path "/path/to/pub_key.pem"; ``` -A typical use would be to specify the key and loginurl on the main level -and then only turn on the locations that you want to secure (not the login page). -Unauthorized requests are given 302 "Moved Temporarily" responses with a location of the specified loginurl. +A typical use 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. Unauthorized requests 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: ``` -auth_jwt_redirect off; +auth_jwt_redirect off; ``` -If you prefer to return 401 Unauthorized, you may turn `auth_jwt_redirect` off. + +By default the authorization header is used to provide a JWT for validation. However, you may use the `auth_jwt_validation_type` configuration to specify the name of a cookie that provides the JWT: ``` -auth_jwt_validation_type AUTHORIZATION; auth_jwt_validation_type COOKIE=jwt; ``` -By default the authorization header is used to provide a JWT for validation. -However, you may use the `auth_jwt_validation_type` configuration to specify the name of a cookie that provides the JWT. -``` -auth_jwt_extract_sub -``` By default, the module will attempt to extract the `sub` claim (e.g. the user's id) from the JWT. If successful, the value will be set in the `x-userid` HTTP header. An error will be logged if this option is enabled and the JWT does not -contain the `sub` claim. +contain the `sub` claim. You may disable this option as follows: ``` -auth_jwt_validate_email off; +auth_jwt_extract_sub off ``` + By default, the module will attempt to validate the email address field of the JWT, then set the x-email header of the -session, and will log an error if it isn't found. To disable this behavior, for instance if you are using a different +session, and will log an error if it isn't found. To disable this behavior, for instance if you are using a different user identifier property such as `sub`, set `auth_jwt_validate_email` to the value `off`. _Note that this flag may be -renamed to `auth_jwt_extract_email` in a future release._ +renamed to `auth_jwt_extract_email` in a future release._ You may disable this option as follows: + +``` +auth_jwt_validate_email off; +``` + +## 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. diff --git a/config b/config index 317aeea..34154fb 100644 --- a/config +++ b/config @@ -1,8 +1,7 @@ -ngx_addon_name=ngx_http_auth_jwt_module - ngx_module_type=HTTP -ngx_module_name=ngx_http_auth_jwt_module -ngx_module_srcs="$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_addon_name=ngx_http_auth_jwt_module +ngx_module_name=$ngx_addon_name +ngx_module_srcs="${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" . auto/module diff --git a/docker-compose-test.yml b/docker-compose-test.yml deleted file mode 100644 index 021d191..0000000 --- a/docker-compose-test.yml +++ /dev/null @@ -1,20 +0,0 @@ -version: '3.3' - -services: - - nginx: - build: - context: . - dockerfile: Dockerfile-test-nginx - args: - BASE_IMAGE: ${IMAGE_NAME:-teslagov/jwt-nginx}:${IMAGE_VERSION:-latest} - - runner: - build: - context: . - dockerfile: Dockerfile-test-runner - environment: - BASE_IMAGE: ${IMAGE_NAME:-teslagov/jwt-nginx}:${IMAGE_VERSION:-latest} - - depends_on: - - nginx \ No newline at end of file diff --git a/resources/nginx.conf b/resources/nginx.conf index 3981730..9b8feab 100644 --- a/resources/nginx.conf +++ b/resources/nginx.conf @@ -1,4 +1,3 @@ - user nginx; worker_processes auto; @@ -6,28 +5,21 @@ 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; + include /etc/nginx/mime.types; + default_type application/octet-stream; - log_format main '$remote_addr - $remote_user [$time_local] "$request" ' + 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; - access_log /var/log/nginx/access.log main; - - sendfile on; - #tcp_nopush on; - - keepalive_timeout 65; - - #gzip on; + sendfile on; + keepalive_timeout 65; include /etc/nginx/conf.d/*.conf; -} \ No newline at end of file +} diff --git a/resources/test-jwt-nginx.conf b/resources/test-jwt-nginx.conf deleted file mode 100644 index 53f512b..0000000 --- a/resources/test-jwt-nginx.conf +++ /dev/null @@ -1,63 +0,0 @@ -server { - auth_jwt_key "00112233445566778899AABBCCDDEEFF00112233445566778899AABBCCDDEEFF"; - auth_jwt_loginurl "https://teslagov.com"; - auth_jwt_enabled off; - auth_jwt_redirect on; - - listen 8000; - server_name localhost; - - location ~ ^/secure-no-redirect/ { - auth_jwt_enabled on; - auth_jwt_redirect off; - root /usr/share/nginx; - index index.html index.htm; - } - - location ~ ^/secure/ { - auth_jwt_enabled on; - auth_jwt_validation_type COOKIE=jwt; - root /usr/share/nginx; - index index.html index.htm; - } - - location ~ ^/secure-auth-header/ { - auth_jwt_enabled on; - root /usr/share/nginx; - index index.html index.htm; - } - - location ~ ^/secure-rs256/ { - auth_jwt_enabled on; - auth_jwt_validation_type COOKIE=jwt; - auth_jwt_algorithm RS256; - auth_jwt_key "-----BEGIN PUBLIC KEY----- -MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAwtpMAM4l1H995oqlqdMh -uqNuffp4+4aUCwuFE9B5s9MJr63gyf8jW0oDr7Mb1Xb8y9iGkWfhouZqNJbMFry+ -iBs+z2TtJF06vbHQZzajDsdux3XVfXv9v6dDIImyU24MsGNkpNt0GISaaiqv51NM -ZQX0miOXXWdkQvWTZFXhmsFCmJLE67oQFSar4hzfAaCulaMD+b3Mcsjlh0yvSq7g -6swiIasEU3qNLKaJAZEzfywroVYr3BwM1IiVbQeKgIkyPS/85M4Y6Ss/T+OWi1Oe -K49NdYBvFP+hNVEoeZzJz5K/nd6C35IX0t2bN5CVXchUFmaUMYk2iPdhXdsC720t -BwIDAQAB ------END PUBLIC KEY-----"; - root /usr/share/nginx; - index index.html index.htm; - } - - location ~ ^/secure-rs256-file/ { - auth_jwt_enabled on; - auth_jwt_validation_type AUTHORIZATION; - auth_jwt_algorithm RS256; - auth_jwt_redirect off; - auth_jwt_use_keyfile on; - auth_jwt_keyfile_path "/etc/nginx/rsa-key.conf"; - root /usr/share/nginx; - index index.html index.htm; - } - - location / { - root /usr/share/nginx/html; - index index.html index.htm; - } -} - diff --git a/scripts.sh b/scripts.sh new file mode 100755 index 0000000..e41620d --- /dev/null +++ b/scripts.sh @@ -0,0 +1,89 @@ +#!/bin/bash -eu + +BLUE='\033[0;34m' +GREEN='\033[0;32m' +RED='\033[0;31m' +NC='\033[0m' + +export ORG_NAME=${ORG_NAME:-teslagov} +export IMAGE_NAME=${IMAGE_NAME:-jwt-nginx} +export FULL_IMAGE_NAME=${ORG_NAME}/${IMAGE_NAME} +export CONTAINER_NAME_PREFIX=${CONTAINER_NAME_PREFIX:-jwt-nginx-test} +export NGINX_VERSION=${NGINX_VERSION:-1.22.0} + +all() { + build_nginx + start_nginx + test +} + +fetch_headers() { + printf "${BLUE} Fetching NGINX headers...${NC}" + local files='src/core/ngx_core.h src/http/ngx_http.h' + + for f in ${files}; do + curl "https://raw.githubusercontent.com/nginx/nginx/release-${NGINX_VERSION}/${f}" -o src/lib/$(basename ${f}) + done +} + +build_nginx() { + local dockerArgs=${1:-} + + printf "${BLUE} Building...${NC}" + docker image pull debian:bullseye-slim + docker image pull nginx:${NGINX_VERSION} + docker image build -t ${FULL_IMAGE_NAME}:latest -t ${FULL_IMAGE_NAME}:${NGINX_VERSION} --build-arg NGINX_VERSION=${NGINX_VERSION} ${dockerArgs} . + + if [ "$?" -ne 0 ]; then + printf "${RED} Build failed ${NC}" + else + printf "${GREEN}✓ Successfully built NGINX module ${NC}" + fi + + docker rmi -f $(docker images --filter=label=stage=builder --quiet) +} + +rebuild_nginx() { + build_nginx --no-cache +} + +start_nginx() { + docker run --rm --name "${IMAGE_NAME}" -d -p 8000:80 ${FULL_IMAGE_NAME} +} + +stop_nginx() { + docker stop "${IMAGE_NAME}" +} + +cp_bin() { + printf "${BLUE} Copying binaries...${NC}" + rm -rf bin + mkdir bin + docker exec "${IMAGE_NAME}" sh -c "tar -chf - \ + /usr/lib64/nginx/modules/ngx_http_auth_jwt_module.so \ + /usr/lib/x86_64-linux-gnu/libjansson.so.* \ + /usr/lib/x86_64-linux-gnu/libjwt.*" 2>/dev/null | tar -xf - -C bin &>/dev/null +} + +build_test_runner() { + local dockerArgs=${1:-} + + printf "${BLUE} Building test runner...${NC}" + docker compose -f ./test/docker-compose-test.yml build ${dockerArgs} +} + +rebuild_test_runner() { + build_test_runner --no-cache +} + +test() { + printf "${BLUE} Running tests...${NC}" + docker compose -f ./test/docker-compose-test.yml up --no-start + docker start ${CONTAINER_NAME_PREFIX} + docker start -a ${CONTAINER_NAME_PREFIX}-runner + docker compose -f ./test/docker-compose-test.yml down +} + +for fn in $@; do + "$fn" +done \ No newline at end of file diff --git a/src/ngx_http_auth_jwt_module.c b/src/ngx_http_auth_jwt_module.c index 4209e3c..cb3c055 100644 --- a/src/ngx_http_auth_jwt_module.c +++ b/src/ngx_http_auth_jwt_module.c @@ -182,7 +182,7 @@ static ngx_int_t ngx_http_auth_jwt_handler(ngx_http_request_t *r) if (jwtPtr == NULL) { - ngx_log_error(NGX_LOG_ERR, r->connection->log, 0, "failed to find a jwt"); + ngx_log_error(NGX_LOG_ERR, r->connection->log, 0, "failed to find a JWT"); goto redirect; } @@ -190,7 +190,7 @@ static ngx_int_t ngx_http_auth_jwt_handler(ngx_http_request_t *r) auth_jwt_algorithm = jwtcf->auth_jwt_algorithm; - if (auth_jwt_algorithm.len == 0 || (auth_jwt_algorithm.len == sizeof("HS256") - 1 && ngx_strncmp(auth_jwt_algorithm.data, "HS256", sizeof("HS256") - 1)==0)) + if (auth_jwt_algorithm.len == 0 || (auth_jwt_algorithm.len == 5 && ngx_strncmp(auth_jwt_algorithm.data, "HS", 2) == 0)) { keylen = jwtcf->auth_jwt_key.len / 2; keyBinary = ngx_palloc(r->pool, keylen); @@ -200,7 +200,7 @@ static ngx_int_t ngx_http_auth_jwt_handler(ngx_http_request_t *r) goto redirect; } } - else if ( auth_jwt_algorithm.len == sizeof("RS256") - 1 && ngx_strncmp(auth_jwt_algorithm.data, "RS256", sizeof("RS256") - 1) == 0 ) + else if ( auth_jwt_algorithm.len == 5 && ngx_strncmp(auth_jwt_algorithm.data, "RS", 2) == 0 ) { // in this case, 'Binary' is a misnomer, as it is the public key string itself if (jwtcf->auth_jwt_use_keyfile == 1) @@ -218,7 +218,7 @@ static ngx_int_t ngx_http_auth_jwt_handler(ngx_http_request_t *r) } else { - ngx_log_error(NGX_LOG_ERR, r->connection->log, 0, "unsupported algorithm"); + ngx_log_error(NGX_LOG_ERR, r->connection->log, 0, "unsupported algorithm %s", auth_jwt_algorithm); goto redirect; } @@ -227,16 +227,16 @@ static ngx_int_t ngx_http_auth_jwt_handler(ngx_http_request_t *r) if (jwtParseReturnCode != 0) { - ngx_log_error(NGX_LOG_ERR, r->connection->log, 0, "failed to parse jwt, error code %d", jwtParseReturnCode); + ngx_log_error(NGX_LOG_ERR, r->connection->log, 0, "failed to parse JWT, error code %d", jwtParseReturnCode); goto redirect; } // validate the algorithm alg = jwt_get_alg(jwt); - if (alg != JWT_ALG_HS256 && alg != JWT_ALG_RS256) + 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) { - ngx_log_error(NGX_LOG_ERR, r->connection->log, 0, "invalid algorithm in jwt %d", alg); + ngx_log_error(NGX_LOG_ERR, r->connection->log, 0, "invalid algorithm in JWT (%d)", alg); goto redirect; } @@ -246,7 +246,7 @@ static ngx_int_t ngx_http_auth_jwt_handler(ngx_http_request_t *r) if (exp < now) { - ngx_log_error(NGX_LOG_ERR, r->connection->log, 0, "the jwt has expired"); + ngx_log_error(NGX_LOG_ERR, r->connection->log, 0, "the JWT has expired"); goto redirect; } @@ -257,7 +257,7 @@ static ngx_int_t ngx_http_auth_jwt_handler(ngx_http_request_t *r) if (sub == NULL) { - ngx_log_error(NGX_LOG_ERR, r->connection->log, 0, "the jwt does not contain a subject"); + ngx_log_error(NGX_LOG_ERR, r->connection->log, 0, "the JWT does not contain a subject"); } else { @@ -273,7 +273,7 @@ static ngx_int_t ngx_http_auth_jwt_handler(ngx_http_request_t *r) if (email == NULL) { - ngx_log_error(NGX_LOG_ERR, r->connection->log, 0, "the jwt does not contain an email address"); + ngx_log_error(NGX_LOG_ERR, r->connection->log, 0, "the JWT does not contain an email address"); } else { diff --git a/test.sh b/test.sh deleted file mode 100755 index 45d21ee..0000000 --- a/test.sh +++ /dev/null @@ -1,55 +0,0 @@ -#!/usr/bin/env bash - -RED='\033[01;31m' -GREEN='\033[01;32m' -NONE='\033[00m' - -test_jwt () { - local name=$1 - local path=$2 - local expect=$3 - local extra=$4 - - cmd="curl -X GET -o /dev/null --silent --head --write-out '%{http_code}' http://nginx:8000$path -H 'cache-control: no-cache' $extra" - test=$( eval ${cmd} ) - - if [ "$test" -eq "$expect" ]; then - echo -e "${GREEN}${name}: passed (${test})${NONE}"; - else - echo -e "${RED}${name}: failed (${test})${NONE}"; - fi -} - -main() { - local VALIDJWT=eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJzdWIiOiJzb21lLWxvbmctdXVpZCIsImZpcnN0TmFtZSI6ImhlbGxvIiwgImxhc3ROYW1lIjoid29ybGQiLCJlbWFpbEFkZHJlc3MiOiJoZWxsb3dvcmxkQGV4YW1wbGUuY29tIiwgInJvbGVzIjpbInRoaXMiLCJ0aGF0IiwidGhlb3RoZXIiXSwgImlzcyI6Imlzc3VlciIsInBlcnNvbklkIjoiNzViYjNjYzctYjkzMy00NGYwLTkzYzYtMTQ3YjA4MmZhZGI1IiwgImV4cCI6MTkwODgzNTIwMCwiaWF0IjoxNDg4ODE5NjAwLCJ1c2VybmFtZSI6ImhlbGxvLndvcmxkIn0.TvDD63ZOqFKgE-uxPDdP5aGIsbl5xPKz4fMul3Zlti4 - local MISSING_SUB_JWT=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJmaXJzdE5hbWUiOiJoZWxsbyIsImxhc3ROYW1lIjoid29ybGQiLCJlbWFpbEFkZHJlc3MiOiJoZWxsb3dvcmxkQGV4YW1wbGUuY29tIiwicm9sZXMiOlsidGhpcyIsInRoYXQiLCJ0aGVvdGhlciJdLCJpc3MiOiJpc3N1ZXIiLCJwZXJzb25JZCI6Ijc1YmIzY2M3LWI5MzMtNDRmMC05M2M2LTE0N2IwODJmYWRiNSIsImV4cCI6MTkwODgzNTIwMCwiaWF0IjoxNDg4ODE5NjAwLCJ1c2VybmFtZSI6ImhlbGxvLndvcmxkIn0.lD6jUsazVtzeGhRTNeP_b2Zs6O798V2FQql11QOEI1Q - local MISSING_EMAIL_JWT=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiJzb21lLWxvbmctdXVpZCIsImZpcnN0TmFtZSI6ImhlbGxvIiwibGFzdE5hbWUiOiJ3b3JsZCIsInJvbGVzIjpbInRoaXMiLCJ0aGF0IiwidGhlb3RoZXIiXSwiaXNzIjoiaXNzdWVyIiwicGVyc29uSWQiOiI3NWJiM2NjNy1iOTMzLTQ0ZjAtOTNjNi0xNDdiMDgyZmFkYjUiLCJleHAiOjE5MDg4MzUyMDAsImlhdCI6MTQ4ODgxOTYwMCwidXNlcm5hbWUiOiJoZWxsby53b3JsZCJ9.tJoAl_pvq95hK7GKqsp5TU462pLTbmSYZc1fAHzcqWM - local VALID_RS256_JWT=eyJ0eXAiOiJKV1QiLCJhbGciOiJSUzI1NiJ9.eyJzdWIiOiJzb21lLWxvbmctdXVpZCIsImZpcnN0TmFtZSI6ImhlbGxvIiwgImxhc3ROYW1lIjoid29ybGQiLCJlbWFpbEFkZHJlc3MiOiJoZWxsb3dvcmxkQGV4YW1wbGUuY29tIiwgInJvbGVzIjpbInRoaXMiLCJ0aGF0IiwidGhlb3RoZXIiXSwgImlzcyI6Imlzc3VlciIsInBlcnNvbklkIjoiNzViYjNjYzctYjkzMy00NGYwLTkzYzYtMTQ3YjA4MmZhZGI1IiwgImV4cCI6MTkwODgzNTIwMCwiaWF0IjoxNDg4ODE5NjAwLCJ1c2VybmFtZSI6ImhlbGxvLndvcmxkIn0.cn5Gb75XL-r7TMsPuqzWoKZ06ZsyF_VZIG0Ohn8uZZFeF8dFUhSrEOYe8WFN6Eon8a8LC0OCI9eNdGiD4m_e9TD1Iz2juqaeos-6yd7SWuODr4YS8KD3cqfXndnLRPzp9PC_UIpATsbqOmxGDrRKvHsQq0TuIXImU3rM_m3kFJFgtoJFHx3KmZUo_Ozkyhhc6Pukikhy6odNAtEyLHP5_tabMXtkeAuIlG8dhjAxef4mJLexYFclG-vl7No5VBU4JrMbfgyxtobcYoE-bDIpmQHywrwo6Li7X0hgHJ17sfS3G2YMHmE-Ij_W2Lf9kf5r2r12DUvg44SLIfM58pCINQ - local INVALID_RSA256_JWT=eyJhbGciOiJIUzI1NiJ9.eyJzdWIiOiJzb21lLWxvbmctdXVpZCIsImZpcnN0TmFtZSI6ImhlbGxvIiwibGFzdE5hbWUiOiJ3b3JsZCIsImVtYWlsQWRkcmVzcyI6ImhlbGxvd29ybGRAZXhhbXBsZS5jb20iLCJyb2xlcyI6WyJ0aGlzIiwidGhhdCIsInRoZW90aGVyIl0sImlzcyI6Imlzc3VlciIsInBlcnNvbklkIjoiNzViYjNjYzctYjkzMy00NGYwLTkzYzYtMTQ3YjA4MmZhZGI1IiwiZXhwIjoxOTA4ODM1MjAwLCJpYXQiOjE0ODg4MTk2MDAsInVzZXJuYW1lIjoiaGVsbG8ud29ybGQifQ._aQmIBL4CVBxU1fNMOHp0kkagFaaX2TvAEenizytwd0 - - test_jwt "Insecure test" "/" "200" - - test_jwt "Secure test without jwt cookie" "/secure/" "302" - - test_jwt "Secure test with jwt cookie" "/secure/" "200" "--cookie \"jwt=${VALIDJWT}\"" - - test_jwt "Secure test with jwt auth header" "/secure-auth-header/" "200" "--header \"Authorization: Bearer ${VALIDJWT}\"" - - test_jwt "Secure test without jwt auth header" "/secure-auth-header/" "302" - - test_jwt "Secure test with jwt auth header missing Bearer" "/secure-no-redirect/" "401" "--header \"Authorization: X\"" - - test_jwt "Secure test without jwt auth header" "/secure-no-redirect/" "401" - - test_jwt "Secure test with jwt cookie - with no sub" "/secure/" "200" " --cookie \"jwt=${MISSING_SUB_JWT}\"" - - test_jwt "Secure test with jwt cookie - with no email" "/secure/" "200" " --cookie \"jwt=${MISSING_EMAIL_JWT}\"" - - test_jwt "Secure test with rs256 jwt cookie" "/secure-rs256/" "200" " --cookie \"jwt=${VALID_RS256_JWT}\"" - - test_jwt "Secure test rsa256 from file with valid jwt" "/secure-rs256-file/" "200" "--header \"Authorization: Bearer ${VALID_RS256_JWT}\"" - - test_jwt "Secure test rsa256 from file with invalid jwt" "/secure-rs256-file/" "401" "--header \"Authorization: Bearer ${INVALID_RSA256_JWT}\"" -} - -main "$@" diff --git a/test/Dockerfile-test-nginx b/test/Dockerfile-test-nginx new file mode 100644 index 0000000..c1e4550 --- /dev/null +++ b/test/Dockerfile-test-nginx @@ -0,0 +1,5 @@ +ARG BASE_IMAGE + +FROM ${BASE_IMAGE} as NGINX +COPY test.conf /etc/nginx/conf.d/test.conf +COPY rsa_key_2048-pub.pem /etc/nginx/rsa-key.conf \ No newline at end of file diff --git a/Dockerfile-test-runner b/test/Dockerfile-test-runner similarity index 100% rename from Dockerfile-test-runner rename to test/Dockerfile-test-runner diff --git a/test/docker-compose-test.yml b/test/docker-compose-test.yml new file mode 100644 index 0000000..2607e42 --- /dev/null +++ b/test/docker-compose-test.yml @@ -0,0 +1,24 @@ +version: '3.3' + +services: + + nginx: + container_name: ${CONTAINER_NAME_PREFIX} + build: + context: . + dockerfile: Dockerfile-test-nginx + args: + BASE_IMAGE: ${FULL_IMAGE_NAME}:${NGINX_VERSION:-latest} + logging: + driver: ${LOG_DRIVER:-journald} + + runner: + container_name: ${CONTAINER_NAME_PREFIX}-runner + build: + context: . + dockerfile: Dockerfile-test-runner + environment: + BASE_IMAGE: ${FULL_IMAGE_NAME}:${NGINX_VERSION:-latest} + + depends_on: + - nginx \ No newline at end of file diff --git a/resources/rsa_key_2048-pub.pem b/test/rsa_key_2048-pub.pem similarity index 100% rename from resources/rsa_key_2048-pub.pem rename to test/rsa_key_2048-pub.pem diff --git a/resources/rsa_key_2048.pem b/test/rsa_key_2048.pem similarity index 100% rename from resources/rsa_key_2048.pem rename to test/rsa_key_2048.pem diff --git a/test/test.conf b/test/test.conf new file mode 100644 index 0000000..52c2829 --- /dev/null +++ b/test/test.conf @@ -0,0 +1,134 @@ +server { + listen 8000; + server_name localhost; + + auth_jwt_key "00112233445566778899AABBCCDDEEFF00112233445566778899AABBCCDDEEFF"; + auth_jwt_loginurl "https://example.com/login"; + auth_jwt_enabled off; + + 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_validation_type 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_validation_type 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_validation_type 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_validation_type 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_validation_type COOKIE=jwt; + auth_jwt_algorithm HS512; + + 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_validation_type 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_validation_type 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_validation_type 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/rs256/file { + auth_jwt_enabled on; + auth_jwt_redirect on; + auth_jwt_validation_type AUTHORIZATION; + auth_jwt_algorithm RS256; + auth_jwt_use_keyfile on; + auth_jwt_keyfile_path "/etc/nginx/rsa-key.conf"; + + 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_validation_type AUTHORIZATION; + auth_jwt_algorithm RS384; + auth_jwt_use_keyfile on; + auth_jwt_keyfile_path "/etc/nginx/rsa-key.conf"; + + 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_validation_type AUTHORIZATION; + auth_jwt_algorithm RS512; + auth_jwt_use_keyfile on; + auth_jwt_keyfile_path "/etc/nginx/rsa-key.conf"; + + alias /usr/share/nginx/html/; + try_files index.html =404; + } +} + diff --git a/test/test.sh b/test/test.sh new file mode 100755 index 0000000..b6ec12c --- /dev/null +++ b/test/test.sh @@ -0,0 +1,139 @@ +#!/bin/bash + +RED='\033[01;31m' +GREEN='\033[01;32m' +NONE='\033[00m' + +run_test () { + local name=$1 + local path=$2 + local expect=$3 + local extra=$4 + + cmd="curl -X GET -o /dev/null --silent --head --write-out '%{http_code}' http://nginx:8000${path} -H 'cache-control: no-cache' $extra" + result=$(eval ${cmd}) + + if [ "${result}" -eq "${expect}" ]; then + echo -e "${GREEN}${name}: passed (Received: ${result}; Path: ${path})${NONE}"; + return 0 + else + echo -e "${RED}${name}: failed (Expected: ${expect}; Received: ${result}; Path: ${path})${NONE}"; + return 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 num_tests=0 + local num_failed=0 + + run_test 'when auth disabled, should return 200' \ + '/' \ + '200' + num_failed=$((${num_failed} + $?)); num_tests=$((${num_tests} + 1)); + + run_test 'when auth enabled with default algorithm and no JWT in Authorization header, returns 302' \ + '/secure/auth-header/default' \ + '302' + num_failed=$((${num_failed} + $?)); num_tests=$((${num_tests} + 1)); + + run_test 'when auth enabled with default algorithm with no redirect and Authroization header missing Bearer, should return 401' \ + '/secure/auth-header/default/no-redirect' \ + '401' \ + '--header "Authorization: X"' + num_failed=$((${num_failed} + $?)); num_tests=$((${num_tests} + 1)); + + run_test 'when auth enabled with default algorithm and no JWT cookie, returns 302' \ + '/secure/cookie/default' \ + '302' + num_failed=$((${num_failed} + $?)); num_tests=$((${num_tests} + 1)); + + run_test 'when auth enabled with default algorithm with no redirect and no JWT cookie, should return 401' \ + '/secure/cookie/default/no-redirect' \ + '401' + num_failed=$((${num_failed} + $?)); num_tests=$((${num_tests} + 1)); + + run_test 'when auth enabled with default algorithm and valid JWT cookie, returns 200' \ + '/secure/cookie/default' \ + '200' \ + '--cookie "jwt=${JWT_HS256_VALID}"' + num_failed=$((${num_failed} + $?)); num_tests=$((${num_tests} + 1)); + + run_test 'when auth enabled with default algorithm and valid JWT cookie with no sub, returns 200' \ + '/secure/cookie/default' \ + '200' \ + ' --cookie "jwt=${JWT_HS256_MISSING_SUB}"' + num_failed=$((${num_failed} + $?)); num_tests=$((${num_tests} + 1)); + + run_test 'when auth enabled with default algorithm and valid JWT cookie with no email, returns 200' \ + '/secure/cookie/default' \ + '200' \ + ' --cookie "jwt=${JWT_HS256_MISSING_EMAIL}"' + num_failed=$((${num_failed} + $?)); num_tests=$((${num_tests} + 1)); + + run_test 'when auth enabled with HS256 algorithm and valid JWT cookie, returns 200' \ + '/secure/cookie/hs256/' \ + '200' \ + '--cookie "jwt=${JWT_HS256_VALID}"' + num_failed=$((${num_failed} + $?)); num_tests=$((${num_tests} + 1)); + + run_test 'when auth enabled with HS384 algorithm and valid JWT cookie, returns 200' \ + '/secure/cookie/hs384' \ + '200' \ + '--cookie "jwt=${JWT_HS384_VALID}"' + num_failed=$((${num_failed} + $?)); num_tests=$((${num_tests} + 1)); + + run_test 'when auth enabled with HS512 algorithm and valid JWT cookie, returns 200' \ + '/secure/cookie/hs512' \ + '200' \ + '--cookie "jwt=${JWT_HS512_VALID}"' + num_failed=$((${num_failed} + $?)); num_tests=$((${num_tests} + 1)); + + run_test 'when auth enabled with RS256 algorithm and valid JWT cookie, returns 200' \ + '/secure/cookie/rs256' \ + '200' \ + ' --cookie "jwt=${JWT_RS256_VALID}"' + num_failed=$((${num_failed} + $?)); num_tests=$((${num_tests} + 1)); + + run_test 'when auth enabled with RS256 algorithm via file and valid JWT in Authorization header, returns 200' \ + '/secure/auth-header/rs256/file' \ + '200' \ + '--header "Authorization: Bearer ${JWT_RS256_VALID}"' + num_failed=$((${num_failed} + $?)); num_tests=$((${num_tests} + 1)); + + run_test 'when auth enabled with RS256 algorithm via file and invalid JWT in Authorization header, returns 401' \ + '/secure/auth-header/rs256/file' \ + '302' \ + '--header "Authorization: Bearer ${JWT_RS256_INVALID}"' + num_failed=$((${num_failed} + $?)); num_tests=$((${num_tests} + 1)); + + run_test 'when auth enabled with RS384 algorithm via file and valid JWT in Authorization header, returns 200' \ + '/secure/auth-header/rs384/file' \ + '200' \ + '--header "Authorization: Bearer ${JWT_RS256_VALID}"' + num_failed=$((${num_failed} + $?)); num_tests=$((${num_tests} + 1)); + + run_test 'when auth enabled with RS512 algorithm via file and valid JWT in Authorization header, returns 200' \ + '/secure/auth-header/rs512/file' \ + '200' \ + '--header "Authorization: Bearer ${JWT_RS256_VALID}"' + num_failed=$((${num_failed} + $?)); num_tests=$((${num_tests} + 1)); + + if [[ "${num_failed}" = '0' ]]; then + printf "\nRan ${num_tests} tests successfully.\n" + return 0 + else + printf "\nRan ${num_tests} tests: ${GREEN}$((${num_tests} - ${num_failed})) passed${NONE}; ${RED}${num_failed} failed${NONE}\n" + return 1 + fi +} + +main '$@' From 69e6e53f814a83b95b1cd06bf49ffcc3b079574f Mon Sep 17 00:00:00 2001 From: Orgad Shaneh Date: Wed, 9 Nov 2022 17:36:21 +0200 Subject: [PATCH 16/75] Fix link with jansson (#63) jansson/lib/libjansson.a(value.o): In function `json_real': value.c:(.text+0x3134): undefined reference to `__isnan' value.c:(.text+0x314c): undefined reference to `__isinf' jansson/lib/libjansson.a(value.o): In function `json_real_set': value.c:(.text+0x3294): undefined reference to `__isnan' value.c:(.text+0x32ac): undefined reference to `__isinf' collect2: error: ld returned 1 exit status Co-authored-by: Josh McCullough --- config | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/config b/config index 34154fb..13c2325 100644 --- a/config +++ b/config @@ -2,6 +2,6 @@ ngx_module_type=HTTP ngx_addon_name=ngx_http_auth_jwt_module ngx_module_name=$ngx_addon_name ngx_module_srcs="${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" +ngx_module_libs="-ljansson -ljwt -lm" . auto/module From 4cf353b3d1ab90c5786269270b6bc97f62fb070a Mon Sep 17 00:00:00 2001 From: Josh McCullough Date: Wed, 9 Nov 2022 15:17:19 -0500 Subject: [PATCH 17/75] update README with build-related info --- .gitignore | 1 + README.md | 53 +++++++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 54 insertions(+) diff --git a/.gitignore b/.gitignore index a676215..fe4f0be 100644 --- a/.gitignore +++ b/.gitignore @@ -1,2 +1,3 @@ .idea +.vscode bin diff --git a/README.md b/README.md index 84913ee..6237985 100644 --- a/README.md +++ b/README.md @@ -145,3 +145,56 @@ auth_jwt_validate_email off; ## 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/third-party/nginx/objs/**", + "~/Projects/third-party/nginx/src/**", + "~/Projects/third-party/libjwt/include/**", + "~/Projects/third-party/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 lobjansson + +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. + +### Verify 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. From 829886f94a97fc3eb5f68f58db57b5fdb8dc42a4 Mon Sep 17 00:00:00 2001 From: Josh McCullough Date: Thu, 10 Nov 2022 11:46:18 -0500 Subject: [PATCH 18/75] fix for #75 (#81) --- src/ngx_http_auth_jwt_module.c | 25 ++++++++++++++++++------- 1 file changed, 18 insertions(+), 7 deletions(-) diff --git a/src/ngx_http_auth_jwt_module.c b/src/ngx_http_auth_jwt_module.c index cb3c055..508d781 100644 --- a/src/ngx_http_auth_jwt_module.c +++ b/src/ngx_http_auth_jwt_module.c @@ -422,31 +422,42 @@ ngx_http_auth_jwt_create_loc_conf(ngx_conf_t *cf) static ngx_int_t loadAuthKey(ngx_conf_t *cf, ngx_http_auth_jwt_loc_conf_t* conf) { FILE *keyFile = fopen((const char*)conf->auth_jwt_keyfile_path.data, "rb"); + unsigned long keySize; + unsigned long keySizeRead; // Check if file exists or is correctly opened if (keyFile == NULL) { - ngx_log_error(NGX_LOG_ERR, cf->log, 0, "failed to open pub key file"); + ngx_log_error(NGX_LOG_ERR, cf->log, 0, "failed to open public key file"); return NGX_ERROR; } // Read file length fseek(keyFile, 0, SEEK_END); - long keySize = ftell(keyFile); + keySize = ftell(keyFile); fseek(keyFile, 0, SEEK_SET); if (keySize == 0) { - ngx_log_error(NGX_LOG_ERR, cf->log, 0, "invalid key file size, check the key file"); + ngx_log_error(NGX_LOG_ERR, cf->log, 0, "invalid public key file size of 0"); return NGX_ERROR; } conf->_auth_jwt_keyfile.data = ngx_palloc(cf->pool, keySize); - fread(conf->_auth_jwt_keyfile.data, 1, keySize, keyFile); - conf->_auth_jwt_keyfile.len = (int)keySize; - + keySizeRead = fread(conf->_auth_jwt_keyfile.data, 1, keySize, keyFile); fclose(keyFile); - return NGX_OK; + + if (keySizeRead == keySize) + { + conf->_auth_jwt_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 * From a5656b19673bdbb6557dade0c7a445418333748a Mon Sep 17 00:00:00 2001 From: Josh McCullough Date: Wed, 15 Mar 2023 17:04:58 -0400 Subject: [PATCH 19/75] fix cp_bin function --- scripts.sh | 16 +++++++++++----- 1 file changed, 11 insertions(+), 5 deletions(-) diff --git a/scripts.sh b/scripts.sh index e41620d..01c307f 100755 --- a/scripts.sh +++ b/scripts.sh @@ -29,7 +29,7 @@ fetch_headers() { build_nginx() { local dockerArgs=${1:-} - printf "${BLUE} Building...${NC}" + printf "${BLUE} Building NGINX...${NC}" docker image pull debian:bullseye-slim docker image pull nginx:${NGINX_VERSION} docker image build -t ${FULL_IMAGE_NAME}:latest -t ${FULL_IMAGE_NAME}:${NGINX_VERSION} --build-arg NGINX_VERSION=${NGINX_VERSION} ${dockerArgs} . @@ -44,10 +44,12 @@ build_nginx() { } rebuild_nginx() { + printf "${BLUE} Rebuilding NGINX...${NC}" build_nginx --no-cache } start_nginx() { + printf "${BLUE} Starting NGINX...${NC}" docker run --rm --name "${IMAGE_NAME}" -d -p 8000:80 ${FULL_IMAGE_NAME} } @@ -56,13 +58,17 @@ stop_nginx() { } cp_bin() { + if [ "$(docker container inspect -f '{{.State.Running}}' ${IMAGE_NAME})" != "true" ]; then + start_nginx + fi + printf "${BLUE} Copying binaries...${NC}" rm -rf bin mkdir bin - docker exec "${IMAGE_NAME}" sh -c "tar -chf - \ - /usr/lib64/nginx/modules/ngx_http_auth_jwt_module.so \ - /usr/lib/x86_64-linux-gnu/libjansson.so.* \ - /usr/lib/x86_64-linux-gnu/libjwt.*" 2>/dev/null | tar -xf - -C bin &>/dev/null + docker exec "${IMAGE_NAME}" sh -c "cd /; tar -chf - \ + usr/lib64/nginx/modules/ngx_http_auth_jwt_module.so \ + usr/lib/x86_64-linux-gnu/libjansson.so.* \ + usr/lib/x86_64-linux-gnu/libjwt.*" | tar -xf - -C bin &>/dev/null } build_test_runner() { From 8e5031b5b6a5767a2ad57ac9c852eb4b4554a489 Mon Sep 17 00:00:00 2001 From: Josh McCullough Date: Wed, 19 Apr 2023 00:19:13 -0400 Subject: [PATCH 20/75] update README --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 6237985..546be26 100644 --- a/README.md +++ b/README.md @@ -75,7 +75,7 @@ This module requires several new `nginx.conf` directives, which can be specified | Directive | Description | | -------------------------- | ------------------------------------------------------------------------------------------------------------------ | -| `auth_jwt_key` | The key to use to decode/verify the JWT -- see below. | +| `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. | From 8014cdc01ac39406b75f26a61a0f6c77ea9244a8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Yoshi=20J=C3=A4ger?= Date: Wed, 19 Apr 2023 13:55:18 +0200 Subject: [PATCH 21/75] Fix: Only call `docker rmi` if there are images to prune (#85) * fix(scripts.sh): only call `docker rmi` if there are images to prune * fix(docker-rmi): always return true after calling `docker rmi` --- scripts.sh | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/scripts.sh b/scripts.sh index 01c307f..89ec2ba 100755 --- a/scripts.sh +++ b/scripts.sh @@ -40,7 +40,7 @@ build_nginx() { printf "${GREEN}✓ Successfully built NGINX module ${NC}" fi - docker rmi -f $(docker images --filter=label=stage=builder --quiet) + docker rmi -f $(docker images --filter=label=stage=builder --quiet) || true } rebuild_nginx() { @@ -92,4 +92,4 @@ test() { for fn in $@; do "$fn" -done \ No newline at end of file +done From 850833443b3f616c5d250eb9b202266c8312f88c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Yoshi=20J=C3=A4ger?= Date: Wed, 19 Apr 2023 14:04:03 +0200 Subject: [PATCH 22/75] fix(docker-buildkit): Make stage names lowercase (#84) --- Dockerfile | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/Dockerfile b/Dockerfile index b0712ea..3fc5d9e 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,13 +1,13 @@ ARG NGINX_VERSION -FROM debian:bullseye-slim as BASE_IMAGE +FROM debian:bullseye-slim as base_image LABEL stage=builder RUN apt-get update \ && apt-get install -y curl build-essential -FROM BASE_IMAGE as BUILD_IMAGE +FROM base_image as build_image LABEL stage=builder ENV LD_LIBRARY_PATH=/usr/local/lib ARG NGINX_VERSION @@ -38,4 +38,4 @@ RUN apt-get update \ LABEL stage= LABEL maintainer="TeslaGov" email="developers@teslagov.com" -COPY --from=BUILD_IMAGE /root/build/nginx/objs/ngx_http_auth_jwt_module.so /usr/lib64/nginx/modules/ +COPY --from=build_image /root/build/nginx/objs/ngx_http_auth_jwt_module.so /usr/lib64/nginx/modules/ From 583fffebe49871c2923c5e500046ba48f6ffe99c Mon Sep 17 00:00:00 2001 From: Josh McCullough Date: Thu, 20 Apr 2023 11:48:29 -0400 Subject: [PATCH 23/75] update to support extracting any claim to request/response headers + more (#87) * update to support extracting any cookie * fix tests * fix tests more * prefix log messages to find easier * try to fix array offset * fix test * extracting claims to request headers is working * add another test * refactor and cleanup * add claim extraction to response headers * rename functions and such for clarity * rename struct members for brevity _and_ clarity * rm debugging * update README * update README * update README * formatting * Update src/ngx_http_auth_jwt_header_processing.c I *think* it might be moot since the compiler will probably optimize it anyway, but might as well do it that way. Co-authored-by: Joan Marin * Update src/ngx_http_auth_jwt_module.c Co-authored-by: Joan Marin * Update src/ngx_http_auth_jwt_module.c Co-authored-by: Joan Marin --------- Co-authored-by: Joan Marin --- Dockerfile | 50 +- README.md | 242 +++-- scripts.sh | 79 +- src/ngx_http_auth_jwt_binary_converters.c | 66 +- src/ngx_http_auth_jwt_header_processing.c | 113 +-- src/ngx_http_auth_jwt_header_processing.h | 4 +- src/ngx_http_auth_jwt_module.c | 1105 +++++++++++---------- src/ngx_http_auth_jwt_string.c | 12 +- src/ngx_http_auth_jwt_string.h | 2 +- test/Dockerfile-test-nginx | 8 +- test/Dockerfile-test-runner | 10 +- test/docker-compose-test.yml | 1 + test/docker-entrypoint.d/10-nginx-test.sh | 1 + test/{ => etc/nginx/conf.d}/test.conf | 87 ++ test/{ => etc/nginx}/rsa_key_2048-pub.pem | 0 test/test.sh | 316 +++--- 16 files changed, 1227 insertions(+), 869 deletions(-) create mode 100755 test/docker-entrypoint.d/10-nginx-test.sh rename test/{ => etc/nginx/conf.d}/test.conf (59%) rename test/{ => etc/nginx}/rsa_key_2048-pub.pem (100%) diff --git a/Dockerfile b/Dockerfile index 3fc5d9e..328ea1e 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,41 +1,41 @@ ARG NGINX_VERSION +ARG SOURCE_HASH -FROM debian:bullseye-slim as base_image -LABEL stage=builder -RUN apt-get update \ - && apt-get install -y curl build-essential +FROM debian:bullseye-slim as ngx_http_auth_jwt_builder_base +LABEL stage=ngx_http_auth_jwt_builder +RUN apt-get update &&\ + apt-get install -y curl build-essential -FROM base_image as build_image -LABEL stage=builder +FROM ngx_http_auth_jwt_builder_base as ngx_http_auth_jwt_builder_module +LABEL stage=ngx_http_auth_jwt_builder ENV LD_LIBRARY_PATH=/usr/local/lib ARG NGINX_VERSION -RUN set -x \ - && apt-get install -y libjwt-dev libjwt0 libjansson-dev libjansson4 libpcre2-dev zlib1g-dev libpcre3-dev \ - && mkdir -p /root/build/ngx-http-auth-jwt-module +RUN set -x &&\ + apt-get install -y libjwt-dev libjwt0 libjansson-dev libjansson4 libpcre2-dev zlib1g-dev libpcre3-dev &&\ + mkdir -p /root/build/ngx-http-auth-jwt-module WORKDIR /root/build/ngx-http-auth-jwt-module +ARG SOURCE_HASH +RUN echo "Source Hash: ${SOURCE_HASH}" ADD config ./ ADD src/*.h src/*.c ./src/ WORKDIR /root/build -RUN set -x \ - && 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 \ - && rm nginx-${NGINX_VERSION}.tar.gz +RUN set -x &&\ + 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 --with-compat --add-dynamic-module=../ngx-http-auth-jwt-module \ - && make modules - - -FROM nginx:${NGINX_VERSION} -LABEL stage=builder -RUN apt-get update \ - && apt-get -y install libjansson4 libjwt0 \ - && cd /etc/nginx \ - && sed -ri '/pid\s+\/var\/run\/nginx\.pid;$/a load_module \/usr\/lib64\/nginx\/modules\/ngx_http_auth_jwt_module\.so;' nginx.conf +RUN ./configure --with-debug --with-compat --add-dynamic-module=../ngx-http-auth-jwt-module &&\ + make modules +FROM nginx:${NGINX_VERSION} AS ngx_http_auth_jwt_builder_nginx LABEL stage= +RUN rm /docker-entrypoint.d/10-listen-on-ipv6-by-default.sh /etc/nginx/conf.d/default.conf +RUN apt-get update &&\ + apt-get -y install libjansson4 libjwt0 &&\ + cd /etc/nginx &&\ + sed -ri '/pid\s+\/var\/run\/nginx\.pid;$/a load_module \/usr\/lib64\/nginx\/modules\/ngx_http_auth_jwt_module\.so;' nginx.conf LABEL maintainer="TeslaGov" email="developers@teslagov.com" -COPY --from=build_image /root/build/nginx/objs/ngx_http_auth_jwt_module.so /usr/lib64/nginx/modules/ +COPY --from=ngx_http_auth_jwt_builder_module /root/build/nginx/objs/ngx_http_auth_jwt_module.so /usr/lib64/nginx/modules/ diff --git a/README.md b/README.md index 546be26..3c7d0a7 100644 --- a/README.md +++ b/README.md @@ -1,98 +1,41 @@ -# Intro +# 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. - -## Building and testing - -To build the Docker image, start NGINX, and run our Bash test against it, run - -```bash -./scripts.sh all -``` - -When you make a change to the module or the NGINX test config, run `./scripts.sh rebuild_nginx` to rebuild the NGINX Docker image. - -When you make a change to `test.sh`, run `./scripts.sh rebuild_test_runner test` to rebuild the test runner image and run the tests. - -The `./scripts.sh` file contains multiple commands to make things easy: - -| Command | Description | -| --------------------- | ----------------------------------------------------------------- | -| `build_nginx` | Builds the NGINX image. | -| `rebuild_nginx` | Re-builds the NGINX image. | -| `start_nginx` | Starts the NGINX container. | -| `stop_nginx` | Stops the NGINX container. | -| `cp_bin` | Copies the compiled binaries out of the NGINX container. | -| `build_test_runner` | Builds the images used by the test stack (uses Docker compose). | -| `rebuild_test_runner` | Re-builds the images used by the test stack. | -| `test` | Runs `test.sh` against the NGINX container (uses Docker compose). | - -You can run multiple commands in sequence by separating them with a space, e.g.: - -```shell -./scripts.sh rebuild_nginx rebuild_test_runner test -``` - -The image produced with `./scripts.sh build_nginx` 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.sh 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. - -In order to persist logs, you can configure the log driver to use. You can do this by setting the environment variable `LOG_DRIVER` before running the tests. On Linux/Unix systems, you can use the driver `journald`, as follows: - -```shell -# need to rebuild the test runner with the proper log driver -LOG_DRIVER=journald ./scripts.sh rebuild_test_runner - -# run the tests -./scripts.sh test - -# check the logs -journalctl -eu docker CONTAINER_NAME=jwt-nginx-test -``` - -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.sh test - -# terminal 2 -journalctl -fu docker CONTAINER_NAME=jwt-nginx-test -``` +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. ## 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. -## NGINX Directives +## Directives + This module requires several new `nginx.conf` directives, which can be specified at the `http`, `server`, or `location` levels. -| 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_extract_sub` | Set to "on" to extract the `sub` claim (e.g. user id) from the JWT and into the `x-userid` header on the response. | -| `auth_jwt_validate_email` | Set to "on" to extract the `emailAddress` claim from the JWT and into the `x-email` header on the response. | -| `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. | +| 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_validation_type` | 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_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) as in the example above. 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. +### 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 @@ -106,42 +49,76 @@ oQIDAQAB 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 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. Unauthorized requests 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. +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_validation_type` configuration to specify the name of a cookie that provides the JWT: +By default, the authorization header is used to provide a JWT for validation. However, you may use the `auth_jwt_validation_type` configuration to specify the name of a cookie that provides the JWT: -``` +```nginx auth_jwt_validation_type COOKIE=jwt; ``` -By default, the module will attempt to extract the `sub` claim (e.g. the user's id) from the JWT. If successful, the -value will be set in the `x-userid` HTTP header. An error will be logged if this option is enabled and the JWT does not -contain the `sub` claim. You may disable this option as follows: +## `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; ``` -auth_jwt_extract_sub off + +## 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_request_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, then use `auth_jwt_extract_response_claims` instead. + +### Using Request 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_request_claims sub; + + return 301 /profile/$http_jwt_sub; +} ``` -By default, the module will attempt to validate the email address field of the JWT, then set the x-email header of the -session, and will log an error if it isn't found. To disable this behavior, for instance if you are using a different -user identifier property such as `sub`, set `auth_jwt_validate_email` to the value `off`. _Note that this flag may be -renamed to `auth_jwt_extract_email` in a future release._ You may disable this option as follows: +### 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; ``` -auth_jwt_validate_email off; + +```nginx +auth_jwt_extract_request_claims sub; +auth_jwt_extract_request_claims firstName; +auth_jwt_extract_request_claims lastName; ``` +## 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. @@ -158,10 +135,10 @@ If you'd like to contribute to this repository, please first initiate the Git ho "name": "Linux", "includePath": [ "${workspaceFolder}/**", - "~/Projects/third-party/nginx/objs/**", - "~/Projects/third-party/nginx/src/**", - "~/Projects/third-party/libjwt/include/**", - "~/Projects/third-party/jansson/src/**" + "~/Projects/nginx/objs/**", + "~/Projects/nginx/src/**", + "~/Projects/libjwt/include/**", + "~/Projects/jansson/src/**" ], "defines": [], "compilerPath": "/usr/bin/clang", @@ -183,18 +160,83 @@ Note the `includePath` additions above -- please update them as appropriate. Nex 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 +#### 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 lobjansson +#### 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. -### Verify Compliation +#### Verifing 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 + +The `./scripts.sh` file contains multiple commands to make things easy: + +| Command | Description | +| --------------------- | ----------------------------------------------------------------- | +| `build_module` | Builds the NGINX image. | +| `rebuild_module` | Re-builds the NGINX image. | +| `start_nginx` | Starts the NGINX container. | +| `stop_nginx` | Stops the NGINX container. | +| `cp_bin` | Copies the compiled binaries out of the NGINX container. | +| `build_test_runner` | Builds the images used by the test stack (uses Docker compose). | +| `rebuild_test_runner` | Re-builds the images used by the test stack. | +| `test` | Runs `test.sh` against the NGINX container (uses Docker compose). | +| `test_now` | Runs `test.sh` without rebuilding. | + +You can run multiple commands in sequence by separating them with a space, e.g.: + +```shell +./scripts.sh build_module test +``` + +To build the Docker images, module, start NGINX, and run the tests against, you can simply do: + +```shell +./scripts.sh all +``` + +When you make a change to the module run `./scripts.sh build_module test` to build a fresh module and run the tests. Note that `rebuild_module` is not often needed as `build_module` hashes the module's source files which will cause a cache miss while building the container, causing the module to be rebuilt. + +When you make a change to the test NGINX config or `test.sh`, run `./scripts.sh test` to run the tests. Similar to above, the test sources are hashed and the containers will be rebuilt as needed. + +The image produced with `./scripts.sh 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.sh 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. + +In order to persist logs, you can configure the log driver to use. You can do this by setting the environment variable `LOG_DRIVER` before running the tests. On Linux/Unix systems, you can use the driver `journald`, as follows: + +```shell +# need to rebuild the test runner with the proper log driver +LOG_DRIVER=journald ./scripts.sh rebuild_test_runner + +# run the tests +./scripts.sh test + +# check the logs +journalctl -eu docker CONTAINER_NAME=jwt-nginx-test +``` + +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.sh test + +# terminal 2 +journalctl -fu docker CONTAINER_NAME=jwt-nginx-test +``` diff --git a/scripts.sh b/scripts.sh index 89ec2ba..873d170 100755 --- a/scripts.sh +++ b/scripts.sh @@ -12,44 +12,39 @@ export CONTAINER_NAME_PREFIX=${CONTAINER_NAME_PREFIX:-jwt-nginx-test} export NGINX_VERSION=${NGINX_VERSION:-1.22.0} all() { - build_nginx - start_nginx + build_module + build_test_runner test } -fetch_headers() { - printf "${BLUE} Fetching NGINX headers...${NC}" - local files='src/core/ngx_core.h src/http/ngx_http.h' - - for f in ${files}; do - curl "https://raw.githubusercontent.com/nginx/nginx/release-${NGINX_VERSION}/${f}" -o src/lib/$(basename ${f}) - done -} - -build_nginx() { +build_module() { local dockerArgs=${1:-} + local sourceHash=$(get_hash config src/*) - printf "${BLUE} Building NGINX...${NC}" + printf "${BLUE}Pulling images...${NC}\n" docker image pull debian:bullseye-slim docker image pull nginx:${NGINX_VERSION} - docker image build -t ${FULL_IMAGE_NAME}:latest -t ${FULL_IMAGE_NAME}:${NGINX_VERSION} --build-arg NGINX_VERSION=${NGINX_VERSION} ${dockerArgs} . + + printf "${BLUE}Building module...${NC}\n" + docker image build -t ${FULL_IMAGE_NAME}:latest -t ${FULL_IMAGE_NAME}:${NGINX_VERSION} ${dockerArgs} \ + --build-arg NGINX_VERSION=${NGINX_VERSION} \ + --build-arg SOURCE_HASH=${sourceHash} \. if [ "$?" -ne 0 ]; then - printf "${RED} Build failed ${NC}" + printf "${RED}✘ Build failed ${NC}\n" else - printf "${GREEN}✓ Successfully built NGINX module ${NC}" + printf "${GREEN}✔ Successfully built NGINX module ${NC}\n" fi - docker rmi -f $(docker images --filter=label=stage=builder --quiet) || true + docker rmi -f $(docker images --filter=label=stage=ngx_http_auth_jwt_builder --quiet) 2> /dev/null || true } -rebuild_nginx() { - printf "${BLUE} Rebuilding NGINX...${NC}" - build_nginx --no-cache +rebuild_module() { + build_module --no-cache } start_nginx() { - printf "${BLUE} Starting NGINX...${NC}" + printf "${BLUE}Starting NGINX...${NC}\n" docker run --rm --name "${IMAGE_NAME}" -d -p 8000:80 ${FULL_IMAGE_NAME} } @@ -62,7 +57,7 @@ cp_bin() { start_nginx fi - printf "${BLUE} Copying binaries...${NC}" + printf "${BLUE}Copying binaries...${NC}\n" rm -rf bin mkdir bin docker exec "${IMAGE_NAME}" sh -c "cd /; tar -chf - \ @@ -73,9 +68,13 @@ cp_bin() { build_test_runner() { local dockerArgs=${1:-} + local configHash=$(get_hash $(find test -type f -not -name 'test.sh' -not -name '*.yml' -not -name 'Dockerfile*')) + local sourceHash=$(get_hash test/test.sh) - printf "${BLUE} Building test runner...${NC}" - docker compose -f ./test/docker-compose-test.yml build ${dockerArgs} + printf "${BLUE}Building test runner...${NC}\n" + docker compose -f ./test/docker-compose-test.yml build ${dockerArgs} \ + --build-arg CONFIG_HASH=${configHash}\ + --build-arg SOURCE_HASH=${sourceHash} } rebuild_test_runner() { @@ -83,13 +82,35 @@ rebuild_test_runner() { } test() { - printf "${BLUE} Running tests...${NC}" + build_test_runner + + printf "${BLUE}Running tests...${NC}\n" docker compose -f ./test/docker-compose-test.yml up --no-start docker start ${CONTAINER_NAME_PREFIX} - docker start -a ${CONTAINER_NAME_PREFIX}-runner + + if [ "$(docker container inspect -f '{{.State.Running}}' ${CONTAINER_NAME_PREFIX})" != "true" ]; then + printf "${RED}Failed to start NGINX test container. See logs below:\n" + docker logs ${CONTAINER_NAME_PREFIX} + printf "${NC}\n" + else + docker start -a ${CONTAINER_NAME_PREFIX}-runner + fi + docker compose -f ./test/docker-compose-test.yml down } -for fn in $@; do - "$fn" -done +test_now() { + docker start -a ${CONTAINER_NAME_PREFIX}-runner +} + +get_hash() { + sha1sum $@ | sed -E 's|\s+|:|' | tr '\n' ' ' | sha1sum | head -c 40 +} + +if [ $# -eq 0 ]; then + all +else + for fn in "$@"; do + ${fn} + done +fi diff --git a/src/ngx_http_auth_jwt_binary_converters.c b/src/ngx_http_auth_jwt_binary_converters.c index 8aea970..8b60560 100644 --- a/src/ngx_http_auth_jwt_binary_converters.c +++ b/src/ngx_http_auth_jwt_binary_converters.c @@ -8,42 +8,56 @@ */ #include "ngx_http_auth_jwt_binary_converters.h" - #include -int hex_char_to_binary( char ch, char* ret ) +int hex_char_to_binary(char ch, char *ret) { - ch = tolower( ch ); - if( isdigit( ch ) ) + 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 if (ch >= 'a' && ch <= 'f') + { + *ret = (ch - 'a') + 10; + } + else if (ch >= 'A' && ch <= 'F') + { + *ret = (ch - 'A') + 10; + } else - return *ret = 0; - return 1; + { + return -1; + } + + return 0; } -int hex_to_binary( const char* str, u_char* buf, int len ) +int hex_to_binary(const char *str, u_char *buf, int len) { - u_char - *cpy = buf; - char - low, - high; - int - odd = len % 2; - - if (odd) { + int odd = len % 2; + + if (odd) + { return -1; } - - for (int i = 0; i < len; i += 2) { - hex_char_to_binary( *(str + i), &high ); - hex_char_to_binary( *(str + i + 1 ), &low ); + else + { + u_char *cpy = buf; + char low; + char high; - *cpy++ = low | (high << 4); + 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; } - return 0; } \ No newline at end of file diff --git a/src/ngx_http_auth_jwt_header_processing.c b/src/ngx_http_auth_jwt_header_processing.c index e368c84..ff648b9 100644 --- a/src/ngx_http_auth_jwt_header_processing.c +++ b/src/ngx_http_auth_jwt_header_processing.c @@ -16,80 +16,73 @@ * 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_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; + 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; + // 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; - } + // 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; - } + 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; - } + // 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]; - } + /* + * 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 was found */ - return NULL; + /* No headers found */ + return NULL; } -/** - * Sample code from nginx - * https://www.nginx.com/resources/wiki/start/topics/examples/headers_management/#how-can-i-set-a-header - */ -ngx_int_t set_custom_header_in_headers_out(ngx_http_request_t *r, ngx_str_t *key, ngx_str_t *value) { - ngx_table_elt_t *h; +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); +} - /* - All we have to do is just to allocate the header... - */ - h = ngx_list_push(&r->headers_out.headers); - if (h == NULL) { - return NGX_ERROR; - } +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); +} - /* - ... setup the header key ... - */ +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; - - /* - ... and the value. - */ h->value = *value; - - /* - Mark the header as not deleted. - */ h->hash = 1; return NGX_OK; + } } \ No newline at end of file diff --git a/src/ngx_http_auth_jwt_header_processing.h b/src/ngx_http_auth_jwt_header_processing.h index 0b64133..acd1762 100644 --- a/src/ngx_http_auth_jwt_header_processing.h +++ b/src/ngx_http_auth_jwt_header_processing.h @@ -9,6 +9,8 @@ #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_custom_header_in_headers_out(ngx_http_request_t *r, ngx_str_t *key, ngx_str_t *value); +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 */ \ No newline at end of file diff --git a/src/ngx_http_auth_jwt_module.c b/src/ngx_http_auth_jwt_module.c index 508d781..3ecb0d3 100644 --- a/src/ngx_http_auth_jwt_module.c +++ b/src/ngx_http_auth_jwt_module.c @@ -20,543 +20,638 @@ #include -typedef struct { - ngx_str_t auth_jwt_loginurl; - ngx_str_t auth_jwt_key; - ngx_flag_t auth_jwt_enabled; - ngx_flag_t auth_jwt_redirect; - ngx_str_t auth_jwt_validation_type; - ngx_str_t auth_jwt_algorithm; - ngx_flag_t auth_jwt_extract_sub; - ngx_flag_t auth_jwt_validate_email; - ngx_str_t auth_jwt_keyfile_path; - ngx_flag_t auth_jwt_use_keyfile; - // Private field for keyfile data - ngx_str_t _auth_jwt_keyfile; -} ngx_http_auth_jwt_loc_conf_t; - -static ngx_int_t ngx_http_auth_jwt_init(ngx_conf_t *cf); -static ngx_int_t ngx_http_auth_jwt_handler(ngx_http_request_t *r); -static void * ngx_http_auth_jwt_create_loc_conf(ngx_conf_t *cf); -static char * ngx_http_auth_jwt_merge_loc_conf(ngx_conf_t *cf, void *parent, void *child); -static char * getJwt(ngx_http_request_t *r, ngx_str_t auth_jwt_validation_type); - -static ngx_command_t ngx_http_auth_jwt_commands[] = { - - { 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(ngx_http_auth_jwt_loc_conf_t, auth_jwt_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(ngx_http_auth_jwt_loc_conf_t, auth_jwt_key), - NULL }, - - { ngx_string("auth_jwt_enabled"), - 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(ngx_http_auth_jwt_loc_conf_t, auth_jwt_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(ngx_http_auth_jwt_loc_conf_t, auth_jwt_redirect), - NULL }, - - { ngx_string("auth_jwt_validation_type"), - 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(ngx_http_auth_jwt_loc_conf_t, auth_jwt_validation_type), - 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(ngx_http_auth_jwt_loc_conf_t, auth_jwt_algorithm), - NULL }, - - { ngx_string("auth_jwt_extract_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(ngx_http_auth_jwt_loc_conf_t, auth_jwt_extract_sub), - NULL }, - - { ngx_string("auth_jwt_validate_email"), - 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(ngx_http_auth_jwt_loc_conf_t, auth_jwt_validate_email), - 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(ngx_http_auth_jwt_loc_conf_t, auth_jwt_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(ngx_http_auth_jwt_loc_conf_t, auth_jwt_use_keyfile), - NULL }, - - ngx_null_command +typedef struct +{ + ngx_str_t loginurl; + ngx_str_t key; + ngx_flag_t enabled; + ngx_flag_t redirect; + ngx_str_t validation_type; + ngx_str_t algorithm; + ngx_flag_t validate_sub; + 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; + +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_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 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 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 free_jwt_and_redirect(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 validation_type); + +static 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_FLAG, + ngx_conf_set_flag_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_validation_type"), + 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, validation_type), + 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_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 ngx_http_module_t ngx_http_auth_jwt_module_ctx = { - NULL, /* preconfiguration */ - ngx_http_auth_jwt_init, /* postconfiguration */ +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 = NGX_CONF_UNSET; + conf->redirect = NGX_CONF_UNSET; + conf->validate_sub = NGX_CONF_UNSET; + conf->redirect = NGX_CONF_UNSET; + conf->validate_sub = NGX_CONF_UNSET; + conf->extract_request_claims = NULL; + conf->extract_response_claims = NULL; + conf->use_keyfile = NGX_CONF_UNSET; + + return conf; + } +} - NULL, /* create main configuration */ - NULL, /* init main configuration */ +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->validation_type, prev->validation_type, ""); + 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); + ngx_conf_merge_ptr_value(conf->extract_request_claims, prev->extract_request_claims, NULL); + ngx_conf_merge_ptr_value(conf->extract_request_claims, prev->extract_response_claims, NULL); + + if (conf->enabled == NGX_CONF_UNSET) + { + conf->enabled = prev->enabled == NGX_CONF_UNSET ? 0 : prev->enabled; + } + + if (conf->redirect == NGX_CONF_UNSET) + { + conf->redirect = prev->redirect == NGX_CONF_UNSET ? 0 : prev->redirect; + } + + if (conf->use_keyfile == NGX_CONF_UNSET) + { + conf->use_keyfile = prev->use_keyfile == NGX_CONF_UNSET ? 0 : prev->use_keyfile; + } + + // 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; +} - NULL, /* create server configuration */ - NULL, /* merge server configuration */ +static char *merge_extract_claims(ngx_conf_t *cf, ngx_array_t *claims) +{ + ngx_str_t *values = cf->args->elts; - ngx_http_auth_jwt_create_loc_conf, /* create location configuration */ - ngx_http_auth_jwt_merge_loc_conf /* merge location configuration */ -}; + // 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]; + } -ngx_module_t ngx_http_auth_jwt_module = { - NGX_MODULE_V1, - &ngx_http_auth_jwt_module_ctx, /* module context */ - ngx_http_auth_jwt_commands, /* 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 -}; + 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 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); + + if (!jwtcf->enabled) + { + return NGX_DECLINED; + } + else + { + // pass through options requests without token authentication + if (r->method == NGX_HTTP_OPTIONS) + { + return NGX_DECLINED; + } + else + { + char *jwtPtr = get_jwt(r, jwtcf->validation_type); + + if (jwtPtr == NULL) + { + ngx_log_error(NGX_LOG_ERR, r->connection->log, 0, "failed to find a JWT"); + return redirect(r, jwtcf); + } + 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"); + return redirect(r, jwtcf); + } + } + else if (algorithm.len == 5 && ngx_strncmp(algorithm.data, "RS", 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); + return redirect(r, jwtcf); + } + + if (jwt_decode(&jwt, jwtPtr, key, keyLength) != 0) + { + ngx_log_error(NGX_LOG_ERR, r->connection->log, 0, "failed to parse JWT"); + return redirect(r, jwtcf); + } + + if (validate_alg(jwtcf, jwt) != 0) + { + ngx_log_error(NGX_LOG_ERR, r->connection->log, 0, "invalid algorithm specified"); + return free_jwt_and_redirect(r, jwtcf, jwt); + } + else if (validate_exp(jwtcf, jwt) != 0) + { + ngx_log_error(NGX_LOG_ERR, r->connection->log, 0, "the JWT has expired"); + return free_jwt_and_redirect(r, jwtcf, jwt); + } + else if (validate_sub(jwtcf, jwt) != 0) + { + ngx_log_error(NGX_LOG_ERR, r->connection->log, 0, "the JWT does not contain a subject"); + return free_jwt_and_redirect(r, jwtcf, jwt); + } + else + { + extract_request_claims(r, jwtcf, jwt); + extract_response_claims(r, jwtcf, jwt); + jwt_free(jwt); + + return NGX_OK; + } + } + } + } +} -static ngx_int_t ngx_http_auth_jwt_handler(ngx_http_request_t *r) +static int validate_alg(auth_jwt_conf_t *jwtcf, jwt_t *jwt) { - ngx_str_t useridHeaderName = ngx_string("x-userid"); - ngx_str_t emailHeaderName = ngx_string("x-email"); - char* jwtPtr; - char* return_url; - ngx_http_auth_jwt_loc_conf_t *jwtcf; - u_char *keyBinary; - // For clearing it later on - jwt_t *jwt = NULL; - int jwtParseReturnCode; - jwt_alg_t alg; - time_t exp; - time_t now; - ngx_str_t auth_jwt_algorithm; - int keylen; - - jwtcf = ngx_http_get_module_loc_conf(r, ngx_http_auth_jwt_module); - - if (!jwtcf->auth_jwt_enabled) - { - return NGX_DECLINED; - } - - // pass through options requests without token authentication - if (r->method == NGX_HTTP_OPTIONS) - { - return NGX_DECLINED; - } - - jwtPtr = getJwt(r, jwtcf->auth_jwt_validation_type); - - if (jwtPtr == NULL) - { - ngx_log_error(NGX_LOG_ERR, r->connection->log, 0, "failed to find a JWT"); - goto redirect; - } - - // convert key from hex to binary, if a symmetric key - - auth_jwt_algorithm = jwtcf->auth_jwt_algorithm; - - if (auth_jwt_algorithm.len == 0 || (auth_jwt_algorithm.len == 5 && ngx_strncmp(auth_jwt_algorithm.data, "HS", 2) == 0)) - { - keylen = jwtcf->auth_jwt_key.len / 2; - keyBinary = ngx_palloc(r->pool, keylen); - if (0 != hex_to_binary((char *)jwtcf->auth_jwt_key.data, keyBinary, jwtcf->auth_jwt_key.len)) - { - ngx_log_error(NGX_LOG_ERR, r->connection->log, 0, "failed to turn hex key into binary"); - goto redirect; - } - } - else if ( auth_jwt_algorithm.len == 5 && ngx_strncmp(auth_jwt_algorithm.data, "RS", 2) == 0 ) - { - // in this case, 'Binary' is a misnomer, as it is the public key string itself - if (jwtcf->auth_jwt_use_keyfile == 1) - { - // Set to global variables - // NOTE: check for keyBin == NULL skipped, unnecessary check; nginx should fail to start - keyBinary = (u_char*)jwtcf->_auth_jwt_keyfile.data; - keylen = jwtcf->_auth_jwt_keyfile.len; - } - else - { - keyBinary = jwtcf->auth_jwt_key.data; - keylen = jwtcf->auth_jwt_key.len; - } - } - else - { - ngx_log_error(NGX_LOG_ERR, r->connection->log, 0, "unsupported algorithm %s", auth_jwt_algorithm); - goto redirect; - } - - // validate the jwt - jwtParseReturnCode = jwt_decode(&jwt, jwtPtr, keyBinary, keylen); - - if (jwtParseReturnCode != 0) - { - ngx_log_error(NGX_LOG_ERR, r->connection->log, 0, "failed to parse JWT, error code %d", jwtParseReturnCode); - goto redirect; - } - - // validate the algorithm - 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) - { - ngx_log_error(NGX_LOG_ERR, r->connection->log, 0, "invalid algorithm in JWT (%d)", alg); - goto redirect; - } - - // validate the exp date of the JWT - exp = (time_t)jwt_get_grant_int(jwt, "exp"); - now = time(NULL); - - if (exp < now) - { - ngx_log_error(NGX_LOG_ERR, r->connection->log, 0, "the JWT has expired"); - goto redirect; - } - - // extract the userid - if (jwtcf->auth_jwt_extract_sub == 1) - { - const char* sub = jwt_get_grant(jwt, "sub"); - - if (sub == NULL) - { - ngx_log_error(NGX_LOG_ERR, r->connection->log, 0, "the JWT does not contain a subject"); - } - else - { - ngx_str_t sub_t = ngx_char_ptr_to_str_t(r->pool, (char *)sub); - - set_custom_header_in_headers_out(r, &useridHeaderName, &sub_t); - } - } - - if (jwtcf->auth_jwt_validate_email == 1) - { - const char* email = jwt_get_grant(jwt, "emailAddress"); - - if (email == NULL) - { - ngx_log_error(NGX_LOG_ERR, r->connection->log, 0, "the JWT does not contain an email address"); - } - else - { - ngx_str_t email_t = ngx_char_ptr_to_str_t(r->pool, (char *)email); - - set_custom_header_in_headers_out(r, &emailHeaderName, &email_t); - } - } - - jwt_free(jwt); - - return NGX_OK; - - redirect: - if (jwt) - { - jwt_free(jwt); - } - - if (jwtcf->auth_jwt_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 = sizeof("Location") - 1; - r->headers_out.location->key.data = (u_char *) "Location"; - - if (r->method == NGX_HTTP_GET) - { - int loginlen; - char * scheme; - ngx_str_t server; - ngx_str_t uri_variable_name = ngx_string("request_uri"); - ngx_int_t uri_variable_hash; - ngx_http_variable_value_t * request_uri_var; - ngx_str_t uri; - ngx_str_t uri_escaped; - uintptr_t escaped_len; - - loginlen = jwtcf->auth_jwt_loginurl.len; - scheme = (r->connection->ssl) ? "https" : "http"; - server = r->headers_in.server; - - // get the URI - uri_variable_hash = ngx_hash_key(uri_variable_name.data, uri_variable_name.len); - request_uri_var = ngx_http_get_variable(r, &uri_variable_name, uri_variable_hash); - - // get the URI - if(request_uri_var && !request_uri_var->not_found && request_uri_var->valid) - { - // ideally we would like the uri with the querystring parameters - uri.data = ngx_palloc(r->pool, request_uri_var->len); - uri.len = request_uri_var->len; - ngx_memcpy(uri.data, request_uri_var->data, request_uri_var->len); - } - else - { - // fallback to the querystring without params - uri = r->uri; - } - - // 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); - - r->headers_out.location->value.len = loginlen + sizeof("?return_url=") - 1 + strlen(scheme) + sizeof("://") - 1 + server.len + uri_escaped.len; - return_url = ngx_palloc(r->pool, r->headers_out.location->value.len); - ngx_memcpy(return_url, jwtcf->auth_jwt_loginurl.data, jwtcf->auth_jwt_loginurl.len); - int return_url_idx = jwtcf->auth_jwt_loginurl.len; - ngx_memcpy(return_url+return_url_idx, "?return_url=", sizeof("?return_url=") - 1); - return_url_idx += sizeof("?return_url=") - 1; - ngx_memcpy(return_url+return_url_idx, scheme, strlen(scheme)); - return_url_idx += strlen(scheme); - ngx_memcpy(return_url+return_url_idx, "://", sizeof("://") - 1); - return_url_idx += sizeof("://") - 1; - ngx_memcpy(return_url+return_url_idx, server.data, server.len); - return_url_idx += server.len; - ngx_memcpy(return_url+return_url_idx, uri_escaped.data, uri_escaped.len); - return_url_idx += 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->auth_jwt_loginurl.len; - r->headers_out.location->value.data = jwtcf->auth_jwt_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; + 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) + { + 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 ngx_int_t ngx_http_auth_jwt_init(ngx_conf_t *cf) +static int validate_sub(auth_jwt_conf_t *jwtcf, jwt_t *jwt) { - ngx_http_handler_pt *h; - ngx_http_core_main_conf_t *cmcf; + if (jwtcf->validate_sub == 1) + { + const char *sub = jwt_get_grant(jwt, "sub"); - cmcf = ngx_http_conf_get_module_main_conf(cf, ngx_http_core_module); + if (sub == NULL) + { + return 1; + } + } - h = ngx_array_push(&cmcf->phases[NGX_HTTP_ACCESS_PHASE].handlers); - if (h == NULL) - { - return NGX_ERROR; - } + return 0; +} - *h = ngx_http_auth_jwt_handler; +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); + } + } + } +} - return NGX_OK; +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 * -ngx_http_auth_jwt_create_loc_conf(ngx_conf_t *cf) +static void extract_response_claims(ngx_http_request_t *r, auth_jwt_conf_t *jwtcf, jwt_t *jwt) { - ngx_http_auth_jwt_loc_conf_t *conf; - - conf = ngx_pcalloc(cf->pool, sizeof(ngx_http_auth_jwt_loc_conf_t)); - if (conf == NULL) - { - return NULL; - } - - // set the flag to unset - conf->auth_jwt_enabled = (ngx_flag_t) -1; - conf->auth_jwt_redirect = (ngx_flag_t) -1; - conf->auth_jwt_extract_sub = (ngx_flag_t) -1; - conf->auth_jwt_validate_email = (ngx_flag_t) -1; - conf->auth_jwt_use_keyfile = (ngx_flag_t) -1; - - ngx_conf_log_error(NGX_LOG_DEBUG, cf, 0, "Created Location Configuration"); - - return conf; + extract_claims(r, jwt, jwtcf->extract_response_claims, set_response_header); } -// Loads the RSA256 public key into the location config struct -static ngx_int_t -loadAuthKey(ngx_conf_t *cf, ngx_http_auth_jwt_loc_conf_t* conf) { - FILE *keyFile = fopen((const char*)conf->auth_jwt_keyfile_path.data, "rb"); - unsigned long keySize; - unsigned long keySizeRead; - - // 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; - } - - // 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; - } - - conf->_auth_jwt_keyfile.data = ngx_palloc(cf->pool, keySize); - keySizeRead = fread(conf->_auth_jwt_keyfile.data, 1, keySize, keyFile); - fclose(keyFile); - - if (keySizeRead == keySize) - { - conf->_auth_jwt_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 ngx_int_t free_jwt_and_redirect(ngx_http_request_t *r, auth_jwt_conf_t *jwtcf, jwt_t *jwt) +{ + if (jwt) + { + jwt_free(jwt); + } + + return redirect(r, jwtcf); } -static char * -ngx_http_auth_jwt_merge_loc_conf(ngx_conf_t *cf, void *parent, void *child) +static ngx_int_t redirect(ngx_http_request_t *r, auth_jwt_conf_t *jwtcf) { - ngx_http_auth_jwt_loc_conf_t *prev = parent; - ngx_http_auth_jwt_loc_conf_t *conf = child; - - ngx_conf_merge_str_value(conf->auth_jwt_loginurl, prev->auth_jwt_loginurl, ""); - ngx_conf_merge_str_value(conf->auth_jwt_key, prev->auth_jwt_key, ""); - ngx_conf_merge_str_value(conf->auth_jwt_validation_type, prev->auth_jwt_validation_type, ""); - ngx_conf_merge_str_value(conf->auth_jwt_algorithm, prev->auth_jwt_algorithm, "HS256"); - ngx_conf_merge_str_value(conf->auth_jwt_keyfile_path, prev->auth_jwt_keyfile_path, ""); - ngx_conf_merge_off_value(conf->auth_jwt_extract_sub, prev->auth_jwt_extract_sub, 1); - ngx_conf_merge_off_value(conf->auth_jwt_validate_email, prev->auth_jwt_validate_email, 1); - - if (conf->auth_jwt_enabled == ((ngx_flag_t) -1)) - { - conf->auth_jwt_enabled = (prev->auth_jwt_enabled == ((ngx_flag_t) -1)) ? 0 : prev->auth_jwt_enabled; - } - - if (conf->auth_jwt_redirect == ((ngx_flag_t) -1)) - { - conf->auth_jwt_redirect = (prev->auth_jwt_redirect == ((ngx_flag_t) -1)) ? 0 : prev->auth_jwt_redirect; - } - - if (conf->auth_jwt_use_keyfile == ((ngx_flag_t) -1)) - { - conf->auth_jwt_use_keyfile = (prev->auth_jwt_use_keyfile == ((ngx_flag_t) -1)) ? 0 : prev->auth_jwt_use_keyfile; - } - - // If the usage of the keyfile is specified, check if the key_path is also configured - if (conf->auth_jwt_use_keyfile == 1) - { - if (ngx_strcmp(conf->auth_jwt_keyfile_path.data, "") != 0) - { - if (loadAuthKey(cf, conf) != NGX_OK) - return NGX_CONF_ERROR; - } - else - { - ngx_log_error(NGX_LOG_ERR, cf->log, 0, "auth_jwt_keyfile_path not specified"); - return NGX_CONF_ERROR; - } - } - - return NGX_CONF_OK; + 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 = sizeof("Location") - 1; + 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"; + 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 *request_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 (request_uri_var && !request_uri_var->not_found && request_uri_var->valid) + { + // ideally we would like the URI with the querystring parameters + uri.data = ngx_palloc(r->pool, request_uri_var->len); + uri.len = request_uri_var->len; + ngx_memcpy(uri.data, request_uri_var->data, request_uri_var->len); + } + else + { + // fallback to the querystring without params + uri = r->uri; + } + + // 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); + + r->headers_out.location->value.len = loginlen + sizeof("?return_url=") - 1 + strlen(scheme) + sizeof("://") - 1 + server.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=", sizeof("?return_url=") - 1); + + return_url_idx += sizeof("?return_url=") - 1; + ngx_memcpy(return_url + return_url_idx, scheme, strlen(scheme)); + + return_url_idx += strlen(scheme); + ngx_memcpy(return_url + return_url_idx, "://", sizeof("://") - 1); + + return_url_idx += sizeof("://") - 1; + ngx_memcpy(return_url + return_url_idx, server.data, server.len); + + return_url_idx += server.len; + 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; } -static char * getJwt(ngx_http_request_t *r, ngx_str_t auth_jwt_validation_type) +// 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) { - static const ngx_str_t authorizationHeaderName = ngx_string("Authorization"); - ngx_table_elt_t *authorizationHeader; - char* jwtPtr = NULL; - ngx_str_t jwtCookieVal; - ngx_int_t n; - ngx_int_t bearer_length; - ngx_str_t authorizationHeaderStr; - - ngx_log_error(NGX_LOG_DEBUG, r->connection->log, 0, "auth_jwt_validation_type.len %d", auth_jwt_validation_type.len); - - if (auth_jwt_validation_type.len == 0 || (auth_jwt_validation_type.len == sizeof("AUTHORIZATION") - 1 && ngx_strncmp(auth_jwt_validation_type.data, "AUTHORIZATION", sizeof("AUTHORIZATION") - 1)==0)) - { - // using authorization header - authorizationHeader = search_headers_in(r, authorizationHeaderName.data, authorizationHeaderName.len); - if (authorizationHeader != NULL) - { - ngx_log_error(NGX_LOG_DEBUG, r->connection->log, 0, "Found authorization header len %d", authorizationHeader->value.len); - - bearer_length = authorizationHeader->value.len - (sizeof("Bearer ") - 1); - - if (bearer_length > 0) - { - authorizationHeaderStr.data = authorizationHeader->value.data + sizeof("Bearer ") - 1; - authorizationHeaderStr.len = bearer_length; - - jwtPtr = ngx_str_t_to_char_ptr(r->pool, authorizationHeaderStr); - - ngx_log_error(NGX_LOG_DEBUG, r->connection->log, 0, "Authorization header: %s", jwtPtr); - } - } - } - else if (auth_jwt_validation_type.len > sizeof("COOKIE=") && ngx_strncmp(auth_jwt_validation_type.data, "COOKIE=", sizeof("COOKIE=") - 1)==0) - { - auth_jwt_validation_type.data += sizeof("COOKIE=") - 1; - auth_jwt_validation_type.len -= sizeof("COOKIE=") - 1; - - // get the cookie - // TODO: the cookie name could be passed in dynamicallly - n = ngx_http_parse_multi_header_lines(&r->headers_in.cookies, &auth_jwt_validation_type, &jwtCookieVal); - if (n != NGX_DECLINED) - { - jwtPtr = ngx_str_t_to_char_ptr(r->pool, jwtCookieVal); - } - } - - return jwtPtr; + 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 validation_type) +{ + char *jwtPtr = NULL; + + ngx_log_debug(NGX_LOG_DEBUG, r->connection->log, 0, "validation_type.len %d", validation_type.len); + + if (validation_type.len == 0 || (validation_type.len == sizeof("AUTHORIZATION") - 1 && ngx_strncmp(validation_type.data, "AUTHORIZATION", sizeof("AUTHORIZATION") - 1) == 0)) + { + static const ngx_str_t authorizationHeaderName = ngx_string("Authorization"); + const ngx_table_elt_t *authorizationHeader = search_headers_in(r, authorizationHeaderName.data, authorizationHeaderName.len); + + if (authorizationHeader != NULL) + { + ngx_int_t bearer_length = authorizationHeader->value.len - (sizeof("Bearer ") - 1); + + ngx_log_debug(NGX_LOG_DEBUG, r->connection->log, 0, "Found authorization header len %d", authorizationHeader->value.len); + if (bearer_length > 0) + { + ngx_str_t authorizationHeaderStr; + authorizationHeaderStr.data = authorizationHeader->value.data + sizeof("Bearer ") - 1; + authorizationHeaderStr.len = bearer_length; + jwtPtr = ngx_str_t_to_char_ptr(r->pool, authorizationHeaderStr); + + ngx_log_debug(NGX_LOG_DEBUG, r->connection->log, 0, "Authorization header: %s", jwtPtr); + } + } + } + else if (validation_type.len > sizeof("COOKIE=") && ngx_strncmp(validation_type.data, "COOKIE=", sizeof("COOKIE=") - 1) == 0) + { + ngx_int_t n; + ngx_str_t jwtCookieVal; + + validation_type.data += sizeof("COOKIE=") - 1; + validation_type.len -= sizeof("COOKIE=") - 1; + + n = ngx_http_parse_multi_header_lines(&r->headers_in.cookies, &validation_type, &jwtCookieVal); + + if (n != NGX_DECLINED) + { + jwtPtr = ngx_str_t_to_char_ptr(r->pool, jwtCookieVal); + } + } + + return jwtPtr; +} diff --git a/src/ngx_http_auth_jwt_string.c b/src/ngx_http_auth_jwt_string.c index 186121f..f472171 100644 --- a/src/ngx_http_auth_jwt_string.c +++ b/src/ngx_http_auth_jwt_string.c @@ -15,18 +15,22 @@ 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 ngx_char_ptr_to_str_t(ngx_pool_t *pool, char* char_ptr) +ngx_str_t char_ptr_to_ngx_str_t(ngx_pool_t *pool, const char* char_ptr) { - int len = strlen(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); - str_t.len = len; + return str_t; } \ No newline at end of file diff --git a/src/ngx_http_auth_jwt_string.h b/src/ngx_http_auth_jwt_string.h index 594785b..4440d8b 100644 --- a/src/ngx_http_auth_jwt_string.h +++ b/src/ngx_http_auth_jwt_string.h @@ -13,6 +13,6 @@ #include char* ngx_str_t_to_char_ptr(ngx_pool_t *pool, ngx_str_t str); -ngx_str_t ngx_char_ptr_to_str_t(ngx_pool_t *pool, char* char_ptr); +ngx_str_t char_ptr_to_ngx_str_t(ngx_pool_t *pool, const char* char_ptr); #endif /* _NGX_HTTP_AUTH_JWT_STRING_H */ \ No newline at end of file diff --git a/test/Dockerfile-test-nginx b/test/Dockerfile-test-nginx index c1e4550..5497d07 100644 --- a/test/Dockerfile-test-nginx +++ b/test/Dockerfile-test-nginx @@ -1,5 +1,9 @@ ARG BASE_IMAGE +ARG CONFIG_HASH FROM ${BASE_IMAGE} as NGINX -COPY test.conf /etc/nginx/conf.d/test.conf -COPY rsa_key_2048-pub.pem /etc/nginx/rsa-key.conf \ No newline at end of file +ARG CONFIG_HASH +RUN echo "Config Hash: ${CONFIG_HASH}" +COPY /docker-entrypoint.d/* /docker-entrypoint.d/ +COPY /etc/nginx/conf.d/test.conf /etc/nginx/conf.d/test.conf +COPY /etc/nginx/rsa_key_2048-pub.pem /etc/nginx/rsa-key.conf diff --git a/test/Dockerfile-test-runner b/test/Dockerfile-test-runner index bd9fc59..0992d75 100644 --- a/test/Dockerfile-test-runner +++ b/test/Dockerfile-test-runner @@ -1,4 +1,10 @@ -FROM alpine:3.7 -COPY test.sh . +ARG SOURCE_HASH + +FROM alpine:3.7 AS test-base RUN apk add curl bash + +FROM test-base AS test +ARG SOURCE_HASH +RUN echo "Source Hash: ${SOURCE_HASH}" +COPY test.sh . CMD ["./test.sh"] diff --git a/test/docker-compose-test.yml b/test/docker-compose-test.yml index 2607e42..eff2460 100644 --- a/test/docker-compose-test.yml +++ b/test/docker-compose-test.yml @@ -9,6 +9,7 @@ services: dockerfile: Dockerfile-test-nginx args: BASE_IMAGE: ${FULL_IMAGE_NAME}:${NGINX_VERSION:-latest} + command: [nginx-debug, '-g', 'daemon off;'] logging: driver: ${LOG_DRIVER:-journald} diff --git a/test/docker-entrypoint.d/10-nginx-test.sh b/test/docker-entrypoint.d/10-nginx-test.sh new file mode 100755 index 0000000..0bf8791 --- /dev/null +++ b/test/docker-entrypoint.d/10-nginx-test.sh @@ -0,0 +1 @@ +nginx -t \ No newline at end of file diff --git a/test/test.conf b/test/etc/nginx/conf.d/test.conf similarity index 59% rename from test/test.conf rename to test/etc/nginx/conf.d/test.conf index 52c2829..fcc3900 100644 --- a/test/test.conf +++ b/test/etc/nginx/conf.d/test.conf @@ -1,3 +1,6 @@ +error_log /var/log/nginx/debug.log debug; +access_log /var/log/nginx/access.log; + server { listen 8000; server_name localhost; @@ -19,6 +22,16 @@ server { 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_validation_type COOKIE=jwt; + + alias /usr/share/nginx/html/; + try_files index.html =404; + } location /secure/cookie/default/no-redirect { auth_jwt_enabled on; @@ -130,5 +143,79 @@ BwIDAQAB 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_validation_type 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_validation_type AUTHORIZATION; + auth_jwt_extract_request_claims firstName lastName; + + add_header "Test" "$http_jwt_firstname $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_validation_type AUTHORIZATION; + auth_jwt_extract_request_claims firstName; + auth_jwt_extract_request_claims lastName; + + add_header "Test" "$http_jwt_firstname $http_jwt_lastname"; + + 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_validation_type 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_validation_type AUTHORIZATION; + auth_jwt_extract_response_claims firstName lastName; + + add_header "Test" "$sent_http_jwt_firstname $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_validation_type AUTHORIZATION; + auth_jwt_extract_response_claims firstName; + auth_jwt_extract_response_claims lastName; + + add_header "Test" "$sent_http_jwt_firstname $sent_http_jwt_lastname"; + + alias /usr/share/nginx/html/; + try_files index.html =404; + } } diff --git a/test/rsa_key_2048-pub.pem b/test/etc/nginx/rsa_key_2048-pub.pem similarity index 100% rename from test/rsa_key_2048-pub.pem rename to test/etc/nginx/rsa_key_2048-pub.pem diff --git a/test/test.sh b/test/test.sh index b6ec12c..6ab567b 100755 --- a/test/test.sh +++ b/test/test.sh @@ -1,24 +1,91 @@ #!/bin/bash -RED='\033[01;31m' -GREEN='\033[01;32m' -NONE='\033[00m' +# set a test # here to execute only that test and output additional info +DEBUG= -run_test () { - local name=$1 - local path=$2 - local expect=$3 - local extra=$4 +RED='\e[31m' +GREEN='\e[32m' +GRAY='\e[90m' +NC='\e[00m' - cmd="curl -X GET -o /dev/null --silent --head --write-out '%{http_code}' http://nginx:8000${path} -H 'cache-control: no-cache' $extra" - result=$(eval ${cmd}) +NUM_TESTS=0; +NUM_SKIPPED=0; +NUM_FAILED=0; - if [ "${result}" -eq "${expect}" ]; then - echo -e "${GREEN}${name}: passed (Received: ${result}; Path: ${path})${NONE}"; - return 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 curlCommand='' + local exitCode='' + local response='' + local testNum="${GRAY}${NUM_TESTS}${NC}\t" + + while getopts "n:p:r:c:x:" option; do + case $option in + n) + name=$OPTARG;; + p) + path=$OPTARG;; + c) + expectedCode=$OPTARG;; + r) + expectedResponseRegex=$OPTARG;; + x) + extraCurlOpts=$OPTARG;; + \?) # Invalid option + printf "Error: Invalid option\n"; + exit;; + esac + done + + curlCommand="curl -s -v http://nginx:8000${path} -H 'Cache-Control: no-cache' ${extraCurlOpts} 2>&1" + response=$(eval "${curlCommand}") + exitCode=$? + + printf "\n${testNum}" + + if [ "${exitCode}" -ne "0" ]; then + printf "${RED}${name} -- unexpected exit code from cURL\n\tcURL Exit Code: ${exitCode}"; + NUM_FAILED=$((${NUM_FAILED} + 1)); + else + OKAY=1 + + if [ "${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}" != "" ] && echo "${response}" | grep -Eq "${expectedResponseRegex}"; then + printf "${RED}${name} -- regex not found in response\n\tPath: ${path}\n\tRegEx: ${expectedResponseRegex}" + 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 - echo -e "${RED}${name}: failed (Expected: ${expect}; Received: ${result}; Path: ${path})${NONE}"; - return 1 + NUM_SKIPPED=$((${NUM_SKIPPED} + 1)) fi } @@ -32,108 +99,129 @@ main() { 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 num_tests=0 - local num_failed=0 - - run_test 'when auth disabled, should return 200' \ - '/' \ - '200' - num_failed=$((${num_failed} + $?)); num_tests=$((${num_tests} + 1)); - - run_test 'when auth enabled with default algorithm and no JWT in Authorization header, returns 302' \ - '/secure/auth-header/default' \ - '302' - num_failed=$((${num_failed} + $?)); num_tests=$((${num_tests} + 1)); - - run_test 'when auth enabled with default algorithm with no redirect and Authroization header missing Bearer, should return 401' \ - '/secure/auth-header/default/no-redirect' \ - '401' \ - '--header "Authorization: X"' - num_failed=$((${num_failed} + $?)); num_tests=$((${num_tests} + 1)); - - run_test 'when auth enabled with default algorithm and no JWT cookie, returns 302' \ - '/secure/cookie/default' \ - '302' - num_failed=$((${num_failed} + $?)); num_tests=$((${num_tests} + 1)); - - run_test 'when auth enabled with default algorithm with no redirect and no JWT cookie, should return 401' \ - '/secure/cookie/default/no-redirect' \ - '401' - num_failed=$((${num_failed} + $?)); num_tests=$((${num_tests} + 1)); - - run_test 'when auth enabled with default algorithm and valid JWT cookie, returns 200' \ - '/secure/cookie/default' \ - '200' \ - '--cookie "jwt=${JWT_HS256_VALID}"' - num_failed=$((${num_failed} + $?)); num_tests=$((${num_tests} + 1)); - - run_test 'when auth enabled with default algorithm and valid JWT cookie with no sub, returns 200' \ - '/secure/cookie/default' \ - '200' \ - ' --cookie "jwt=${JWT_HS256_MISSING_SUB}"' - num_failed=$((${num_failed} + $?)); num_tests=$((${num_tests} + 1)); - - run_test 'when auth enabled with default algorithm and valid JWT cookie with no email, returns 200' \ - '/secure/cookie/default' \ - '200' \ - ' --cookie "jwt=${JWT_HS256_MISSING_EMAIL}"' - num_failed=$((${num_failed} + $?)); num_tests=$((${num_tests} + 1)); - - run_test 'when auth enabled with HS256 algorithm and valid JWT cookie, returns 200' \ - '/secure/cookie/hs256/' \ - '200' \ - '--cookie "jwt=${JWT_HS256_VALID}"' - num_failed=$((${num_failed} + $?)); num_tests=$((${num_tests} + 1)); - - run_test 'when auth enabled with HS384 algorithm and valid JWT cookie, returns 200' \ - '/secure/cookie/hs384' \ - '200' \ - '--cookie "jwt=${JWT_HS384_VALID}"' - num_failed=$((${num_failed} + $?)); num_tests=$((${num_tests} + 1)); - - run_test 'when auth enabled with HS512 algorithm and valid JWT cookie, returns 200' \ - '/secure/cookie/hs512' \ - '200' \ - '--cookie "jwt=${JWT_HS512_VALID}"' - num_failed=$((${num_failed} + $?)); num_tests=$((${num_tests} + 1)); - - run_test 'when auth enabled with RS256 algorithm and valid JWT cookie, returns 200' \ - '/secure/cookie/rs256' \ - '200' \ - ' --cookie "jwt=${JWT_RS256_VALID}"' - num_failed=$((${num_failed} + $?)); num_tests=$((${num_tests} + 1)); - - run_test 'when auth enabled with RS256 algorithm via file and valid JWT in Authorization header, returns 200' \ - '/secure/auth-header/rs256/file' \ - '200' \ - '--header "Authorization: Bearer ${JWT_RS256_VALID}"' - num_failed=$((${num_failed} + $?)); num_tests=$((${num_tests} + 1)); - - run_test 'when auth enabled with RS256 algorithm via file and invalid JWT in Authorization header, returns 401' \ - '/secure/auth-header/rs256/file' \ - '302' \ - '--header "Authorization: Bearer ${JWT_RS256_INVALID}"' - num_failed=$((${num_failed} + $?)); num_tests=$((${num_tests} + 1)); - - run_test 'when auth enabled with RS384 algorithm via file and valid JWT in Authorization header, returns 200' \ - '/secure/auth-header/rs384/file' \ - '200' \ - '--header "Authorization: Bearer ${JWT_RS256_VALID}"' - num_failed=$((${num_failed} + $?)); num_tests=$((${num_tests} + 1)); - - run_test 'when auth enabled with RS512 algorithm via file and valid JWT in Authorization header, returns 200' \ - '/secure/auth-header/rs512/file' \ - '200' \ - '--header "Authorization: Bearer ${JWT_RS256_VALID}"' - num_failed=$((${num_failed} + $?)); num_tests=$((${num_tests} + 1)); - - if [[ "${num_failed}" = '0' ]]; then - printf "\nRan ${num_tests} tests successfully.\n" + + run_test -n 'when auth disabled, should return 200' \ + -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 'when auth enabled with default algorithm with no redirect and Authorization header missing Bearer, should return 401' \ + -p '/secure/auth-header/default/no-redirect' \ + -c '401' \ + -x '--header "Authorization: X"' + + 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 '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 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 'extracts single claim to request header' \ + -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 header' \ + -p '/secure/extract-claim/request/name-1' \ + -r '^Test: hello world$' \ + -x '--header "Authorization: Bearer ${JWT_HS256_VALID}"' + + run_test -n 'extracts multiple claims (multiple directives) to request header' \ + -p '/secure/extract-claim/request/name-2' \ + -r '^Test: 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 '^Test: 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 '^Test: hello world$' \ + -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 '^Test: hello world$' \ + -x '--header "Authorization: Bearer ${JWT_HS256_VALID}"' + + 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${NONE}; ${RED}${num_failed} failed${NONE}\n" + printf "\nRan ${NUM_TESTS} tests: ${GREEN}$((${NUM_TESTS} - ${NUM_FAILED})) passed${NC}; ${RED}${NUM_FAILED} failed${NC}; ${NUM_SKIPPED} skipped\n" return 1 fi } -main '$@' +if [ "${DEBUG}" != '' ]; then + printf "\n${RED}Some tests will be skipped since DEBUG is set.${NC}\n" +fi + +main From ac147ef0e7873b2becb2e68cf65b89c3c2848bcf Mon Sep 17 00:00:00 2001 From: Josh McCullough Date: Mon, 24 Apr 2023 08:05:03 -0400 Subject: [PATCH 24/75] update Dockerfile; update scripts.sh (#88) --- Dockerfile | 40 +++++++++++++++++++++++----------------- scripts.sh | 34 +++++++++++++++++++++++----------- 2 files changed, 46 insertions(+), 28 deletions(-) diff --git a/Dockerfile b/Dockerfile index 328ea1e..94d829e 100644 --- a/Dockerfile +++ b/Dockerfile @@ -4,38 +4,44 @@ ARG SOURCE_HASH FROM debian:bullseye-slim as ngx_http_auth_jwt_builder_base LABEL stage=ngx_http_auth_jwt_builder -RUN apt-get update &&\ - apt-get install -y curl build-essential - - +RUN <<` +apt-get update +apt-get install -y curl build-essential +` FROM ngx_http_auth_jwt_builder_base as ngx_http_auth_jwt_builder_module LABEL stage=ngx_http_auth_jwt_builder ENV LD_LIBRARY_PATH=/usr/local/lib ARG NGINX_VERSION -RUN set -x &&\ - apt-get install -y libjwt-dev libjwt0 libjansson-dev libjansson4 libpcre2-dev zlib1g-dev libpcre3-dev &&\ - mkdir -p /root/build/ngx-http-auth-jwt-module +RUN <<` +apt-get install -y libjwt-dev libjwt0 libjansson-dev libjansson4 libpcre2-dev zlib1g-dev libpcre3-dev +mkdir -p /root/build/ngx-http-auth-jwt-module +` WORKDIR /root/build/ngx-http-auth-jwt-module ARG SOURCE_HASH RUN echo "Source Hash: ${SOURCE_HASH}" ADD config ./ ADD src/*.h src/*.c ./src/ WORKDIR /root/build -RUN set -x &&\ - 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 +RUN <<` +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 --with-debug --with-compat --add-dynamic-module=../ngx-http-auth-jwt-module &&\ - make modules +RUN <<` +./configure --with-debug --with-compat --add-dynamic-module=../ngx-http-auth-jwt-module +make modules +` FROM nginx:${NGINX_VERSION} AS ngx_http_auth_jwt_builder_nginx LABEL stage= RUN rm /docker-entrypoint.d/10-listen-on-ipv6-by-default.sh /etc/nginx/conf.d/default.conf -RUN apt-get update &&\ - apt-get -y install libjansson4 libjwt0 &&\ - cd /etc/nginx &&\ - sed -ri '/pid\s+\/var\/run\/nginx\.pid;$/a load_module \/usr\/lib64\/nginx\/modules\/ngx_http_auth_jwt_module\.so;' nginx.conf +RUN <<` +apt-get update +apt-get -y install libjansson4 libjwt0 +cd /etc/nginx +sed -ri '/pid\s+\/var\/run\/nginx\.pid;$/a load_module \/usr\/lib64\/nginx\/modules\/ngx_http_auth_jwt_module\.so;' nginx.conf +` LABEL maintainer="TeslaGov" email="developers@teslagov.com" COPY --from=ngx_http_auth_jwt_builder_module /root/build/nginx/objs/ngx_http_auth_jwt_module.so /usr/lib64/nginx/modules/ diff --git a/scripts.sh b/scripts.sh index 873d170..e5a2380 100755 --- a/scripts.sh +++ b/scripts.sh @@ -28,42 +28,54 @@ build_module() { printf "${BLUE}Building module...${NC}\n" docker image build -t ${FULL_IMAGE_NAME}:latest -t ${FULL_IMAGE_NAME}:${NGINX_VERSION} ${dockerArgs} \ --build-arg NGINX_VERSION=${NGINX_VERSION} \ - --build-arg SOURCE_HASH=${sourceHash} \. + --build-arg SOURCE_HASH=${sourceHash} . if [ "$?" -ne 0 ]; then printf "${RED}✘ Build failed ${NC}\n" else printf "${GREEN}✔ Successfully built NGINX module ${NC}\n" fi - - docker rmi -f $(docker images --filter=label=stage=ngx_http_auth_jwt_builder --quiet) 2> /dev/null || true } rebuild_module() { + clean_module build_module --no-cache } +clean_module() { + docker rmi -f $(docker images --filter=label=stage=ngx_http_auth_jwt_builder --quiet) 2> /dev/null || true +} + start_nginx() { - printf "${BLUE}Starting NGINX...${NC}\n" - docker run --rm --name "${IMAGE_NAME}" -d -p 8000:80 ${FULL_IMAGE_NAME} + printf "${BLUE}Starting NGINX container (${IMAGE_NAME})...${NC}\n" + docker run --rm --name "${IMAGE_NAME}" -d -p 8000:80 ${FULL_IMAGE_NAME} >/dev/null } stop_nginx() { - docker stop "${IMAGE_NAME}" + docker stop "${IMAGE_NAME}" >/dev/null } cp_bin() { - if [ "$(docker container inspect -f '{{.State.Running}}' ${IMAGE_NAME})" != "true" ]; then + local destDir=bin + local stopContainer=0; + + if [ "$(docker container inspect -f '{{.State.Running}}' ${IMAGE_NAME} | true)" != "true" ]; then start_nginx + stopContainer=1 fi - printf "${BLUE}Copying binaries...${NC}\n" - rm -rf bin - mkdir bin + printf "${BLUE}Copying binaries to: ${destDir}${NC}\n" + rm -rf ${destDir}/* + mkdir -p ${destDir} docker exec "${IMAGE_NAME}" sh -c "cd /; tar -chf - \ usr/lib64/nginx/modules/ngx_http_auth_jwt_module.so \ usr/lib/x86_64-linux-gnu/libjansson.so.* \ - usr/lib/x86_64-linux-gnu/libjwt.*" | tar -xf - -C bin &>/dev/null + usr/lib/x86_64-linux-gnu/libjwt.*" | tar -xf - -C ${destDir} &>/dev/null + + if [ $stopContainer ]; then + printf "${BLUE}Stopping NGINX container (${IMAGE_NAME})...${NC}\n" + stop_nginx + fi } build_test_runner() { From bb9534e013e7811f320e3b3bbfce7864710f8bfb Mon Sep 17 00:00:00 2001 From: Josh McCullough Date: Mon, 24 Apr 2023 08:35:08 -0400 Subject: [PATCH 25/75] update README --- README.md | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index 3c7d0a7..f8cae3a 100644 --- a/README.md +++ b/README.md @@ -2,6 +2,10 @@ 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. @@ -160,19 +164,19 @@ Note the `includePath` additions above -- please update them as appropriate. Nex 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 +#### 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 +#### 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. -#### Verifing Compliation +#### 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. From ab7407180bb6188a4666e2101372c36bd781bc30 Mon Sep 17 00:00:00 2001 From: Josh McCullough Date: Mon, 24 Apr 2023 14:04:27 -0400 Subject: [PATCH 26/75] use next available port for testing --- scripts.sh | 25 +++++++++++++++++++------ test/Dockerfile-test-nginx | 3 +++ test/Dockerfile-test-runner | 5 ++++- test/etc/nginx/conf.d/test.conf | 2 +- test/test.sh | 6 ++++-- 5 files changed, 31 insertions(+), 10 deletions(-) diff --git a/scripts.sh b/scripts.sh index e5a2380..bbb6fa8 100755 --- a/scripts.sh +++ b/scripts.sh @@ -47,8 +47,10 @@ clean_module() { } start_nginx() { - printf "${BLUE}Starting NGINX container (${IMAGE_NAME})...${NC}\n" - docker run --rm --name "${IMAGE_NAME}" -d -p 8000:80 ${FULL_IMAGE_NAME} >/dev/null + local port=$(get_port) + + printf "${BLUE}Starting NGINX container (${IMAGE_NAME}) on port ${port}...${NC}\n" + docker run --rm --name "${IMAGE_NAME}" -d -p ${PORT}:80 ${FULL_IMAGE_NAME} >/dev/null } stop_nginx() { @@ -82,11 +84,13 @@ build_test_runner() { local dockerArgs=${1:-} local configHash=$(get_hash $(find test -type f -not -name 'test.sh' -not -name '*.yml' -not -name 'Dockerfile*')) local sourceHash=$(get_hash test/test.sh) - - printf "${BLUE}Building test runner...${NC}\n" + local port=$(get_port) + + printf "${BLUE}Building test runner using port ${port}...${NC}\n" docker compose -f ./test/docker-compose-test.yml build ${dockerArgs} \ --build-arg CONFIG_HASH=${configHash}\ - --build-arg SOURCE_HASH=${sourceHash} + --build-arg SOURCE_HASH=${sourceHash} \ + --build-arg PORT=${port} } rebuild_test_runner() { @@ -105,7 +109,7 @@ test() { docker logs ${CONTAINER_NAME_PREFIX} printf "${NC}\n" else - docker start -a ${CONTAINER_NAME_PREFIX}-runner + test_now fi docker compose -f ./test/docker-compose-test.yml down @@ -119,6 +123,15 @@ get_hash() { sha1sum $@ | sed -E 's|\s+|:|' | tr '\n' ' ' | sha1sum | head -c 40 } +get_port() { + for p in $(seq 8000 8100); do + if ! ss -ln | grep -q ":${p} "; then + echo ${p} + break + fi + done +} + if [ $# -eq 0 ]; then all else diff --git a/test/Dockerfile-test-nginx b/test/Dockerfile-test-nginx index 5497d07..b70ca9e 100644 --- a/test/Dockerfile-test-nginx +++ b/test/Dockerfile-test-nginx @@ -1,9 +1,12 @@ ARG BASE_IMAGE ARG CONFIG_HASH +ARG PORT FROM ${BASE_IMAGE} as NGINX ARG CONFIG_HASH +ARG PORT RUN echo "Config Hash: ${CONFIG_HASH}" COPY /docker-entrypoint.d/* /docker-entrypoint.d/ COPY /etc/nginx/conf.d/test.conf /etc/nginx/conf.d/test.conf COPY /etc/nginx/rsa_key_2048-pub.pem /etc/nginx/rsa-key.conf +RUN sed -i "s|%{PORT}|${PORT}|" /etc/nginx/conf.d/test.conf diff --git a/test/Dockerfile-test-runner b/test/Dockerfile-test-runner index 0992d75..c8cbff2 100644 --- a/test/Dockerfile-test-runner +++ b/test/Dockerfile-test-runner @@ -1,10 +1,13 @@ ARG SOURCE_HASH +ARG PORT FROM alpine:3.7 AS test-base RUN apk add curl bash FROM test-base AS test ARG SOURCE_HASH +ARG PORT +ENV PORT=${PORT} RUN echo "Source Hash: ${SOURCE_HASH}" COPY test.sh . -CMD ["./test.sh"] +CMD ./test.sh ${PORT} diff --git a/test/etc/nginx/conf.d/test.conf b/test/etc/nginx/conf.d/test.conf index fcc3900..62624df 100644 --- a/test/etc/nginx/conf.d/test.conf +++ b/test/etc/nginx/conf.d/test.conf @@ -2,7 +2,7 @@ error_log /var/log/nginx/debug.log debug; access_log /var/log/nginx/access.log; server { - listen 8000; + listen %{PORT}; server_name localhost; auth_jwt_key "00112233445566778899AABBCCDDEEFF00112233445566778899AABBCCDDEEFF"; diff --git a/test/test.sh b/test/test.sh index 6ab567b..9516087 100755 --- a/test/test.sh +++ b/test/test.sh @@ -1,6 +1,7 @@ -#!/bin/bash +#!/bin/bash -u # set a test # here to execute only that test and output additional info +PORT=${1:-8000} DEBUG= RED='\e[31m' @@ -45,7 +46,7 @@ run_test () { esac done - curlCommand="curl -s -v http://nginx:8000${path} -H 'Cache-Control: no-cache' ${extraCurlOpts} 2>&1" + curlCommand="curl -s -v http://nginx:${PORT}${path} -H 'Cache-Control: no-cache' ${extraCurlOpts} 2>&1" response=$(eval "${curlCommand}") exitCode=$? @@ -224,4 +225,5 @@ 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 From 697551d9b24508ae22aaa8f327a073e1722f2b31 Mon Sep 17 00:00:00 2001 From: Josh McCullough Date: Mon, 24 Apr 2023 14:54:35 -0400 Subject: [PATCH 27/75] fix port casing --- scripts.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scripts.sh b/scripts.sh index bbb6fa8..00a81c7 100755 --- a/scripts.sh +++ b/scripts.sh @@ -50,7 +50,7 @@ start_nginx() { local port=$(get_port) printf "${BLUE}Starting NGINX container (${IMAGE_NAME}) on port ${port}...${NC}\n" - docker run --rm --name "${IMAGE_NAME}" -d -p ${PORT}:80 ${FULL_IMAGE_NAME} >/dev/null + docker run --rm --name "${IMAGE_NAME}" -d -p ${port}:80 ${FULL_IMAGE_NAME} >/dev/null } stop_nginx() { From b888c93389a51cbe6c13c9c627d5ab2325028a47 Mon Sep 17 00:00:00 2001 From: Josh McCullough Date: Mon, 24 Apr 2023 15:47:31 -0400 Subject: [PATCH 28/75] update to support NGINX 1.23.0+ (#89) --- Dockerfile | 14 +++++++++++++- src/ngx_http_auth_jwt_module.c | 17 ++++++++++++++--- 2 files changed, 27 insertions(+), 4 deletions(-) diff --git a/Dockerfile b/Dockerfile index 94d829e..7b85a87 100644 --- a/Dockerfile +++ b/Dockerfile @@ -8,6 +8,8 @@ RUN <<` apt-get update apt-get install -y curl build-essential ` + + FROM ngx_http_auth_jwt_builder_base as ngx_http_auth_jwt_builder_module LABEL stage=ngx_http_auth_jwt_builder ENV LD_LIBRARY_PATH=/usr/local/lib @@ -29,7 +31,17 @@ tar -xzf nginx-${NGINX_VERSION}.tar.gz --strip-components 1 -C nginx ` WORKDIR /root/build/nginx RUN <<` -./configure --with-debug --with-compat --add-dynamic-module=../ngx-http-auth-jwt-module +BUILD_FLAGS='' +MAJ=$(echo ${NGINX_VERSION} | cut -f1 -d.) +MIN=$(echo ${NGINX_VERSION} | cut -f2 -d.) +REV=$(echo ${NGINX_VERSION} | cut -f3 -d.) + +# NGINX 1.23.0+ changes `cookies` to `cookie` +if [ "${MAJ}" -gt 1 ] || [ "${MAJ}" -eq 1 -a "${MIN}" -ge 23 ]; then + BUILD_FLAGS="${BUILD_FLAGS} --with-cc-opt='-DNGX_LINKED_LIST_COOKIES=1'" +fi + +./configure --with-compat --add-dynamic-module=../ngx-http-auth-jwt-module ${BUILD_FLAGS} make modules ` diff --git a/src/ngx_http_auth_jwt_module.c b/src/ngx_http_auth_jwt_module.c index 3ecb0d3..ef30437 100644 --- a/src/ngx_http_auth_jwt_module.c +++ b/src/ngx_http_auth_jwt_module.c @@ -19,6 +19,7 @@ #include "ngx_http_auth_jwt_string.h" #include +#include typedef struct { @@ -639,15 +640,25 @@ static char *get_jwt(ngx_http_request_t *r, ngx_str_t validation_type) } else if (validation_type.len > sizeof("COOKIE=") && ngx_strncmp(validation_type.data, "COOKIE=", sizeof("COOKIE=") - 1) == 0) { - ngx_int_t n; + bool has_cookie = false; ngx_str_t jwtCookieVal; validation_type.data += sizeof("COOKIE=") - 1; validation_type.len -= sizeof("COOKIE=") - 1; - n = ngx_http_parse_multi_header_lines(&r->headers_in.cookies, &validation_type, &jwtCookieVal); +#ifndef NGX_LINKED_LIST_COOKIES + if (ngx_http_parse_multi_header_lines(&r->headers_in.cookies, &validation_type, &jwtCookieVal) != NGX_DECLINED) + { + has_cookie = true; + } +#else + if (ngx_http_parse_multi_header_lines(r, r->headers_in.cookie, &validation_type, &jwtCookieVal) != NULL) + { + has_cookie = true; + } +#endif - if (n != NGX_DECLINED) + if (has_cookie == true) { jwtPtr = ngx_str_t_to_char_ptr(r->pool, jwtCookieVal); } From b2ec2bb02fbf654aa1879ab0a7d3e5da5daade40 Mon Sep 17 00:00:00 2001 From: Josh McCullough Date: Tue, 25 Apr 2023 14:02:47 -0400 Subject: [PATCH 29/75] rename `auth_jwt_authorization_type` to `auth_jwt_location` and support pulling JWT from any header (#90) --- Dockerfile | 2 +- README.md | 7 ++-- src/ngx_http_auth_jwt_module.c | 59 ++++++++++++++++----------------- test/etc/nginx/conf.d/test.conf | 46 +++++++++++++++---------- test/test.sh | 23 ++++++++++--- 5 files changed, 81 insertions(+), 56 deletions(-) diff --git a/Dockerfile b/Dockerfile index 7b85a87..4f2db13 100644 --- a/Dockerfile +++ b/Dockerfile @@ -36,7 +36,7 @@ MAJ=$(echo ${NGINX_VERSION} | cut -f1 -d.) MIN=$(echo ${NGINX_VERSION} | cut -f2 -d.) REV=$(echo ${NGINX_VERSION} | cut -f3 -d.) -# NGINX 1.23.0+ changes `cookies` to `cookie` +# NGINX 1.23.0+ changes cookies to use a linked list, and renames `cookies` to `cookie` if [ "${MAJ}" -gt 1 ] || [ "${MAJ}" -eq 1 -a "${MIN}" -ge 23 ]; then BUILD_FLAGS="${BUILD_FLAGS} --with-cc-opt='-DNGX_LINKED_LIST_COOKIES=1'" fi diff --git a/README.md b/README.md index f8cae3a..6a629d3 100644 --- a/README.md +++ b/README.md @@ -21,7 +21,7 @@ This module requires several new `nginx.conf` directives, which can be specified | `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_validation_type` | Indicates where the JWT is located in the request -- see below. | +| `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_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` | @@ -67,10 +67,11 @@ 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_validation_type` configuration to specify the name of a cookie that provides the JWT: +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_validation_type COOKIE=jwt; +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 diff --git a/src/ngx_http_auth_jwt_module.c b/src/ngx_http_auth_jwt_module.c index ef30437..0268dab 100644 --- a/src/ngx_http_auth_jwt_module.c +++ b/src/ngx_http_auth_jwt_module.c @@ -27,7 +27,7 @@ typedef struct ngx_str_t key; ngx_flag_t enabled; ngx_flag_t redirect; - ngx_str_t validation_type; + ngx_str_t jwt_location; ngx_str_t algorithm; ngx_flag_t validate_sub; ngx_array_t *extract_request_claims; @@ -51,9 +51,9 @@ static void extract_response_claims(ngx_http_request_t *r, auth_jwt_conf_t *jwtc static ngx_int_t free_jwt_and_redirect(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 validation_type); +static char *get_jwt(ngx_http_request_t *r, ngx_str_t jwt_location); -static char *JWT_HEADER_PREFIX = "JWT-"; +static const char *JWT_HEADER_PREFIX = "JWT-"; static ngx_command_t auth_jwt_directives[] = { {ngx_string("auth_jwt_loginurl"), @@ -84,11 +84,11 @@ static ngx_command_t auth_jwt_directives[] = { offsetof(auth_jwt_conf_t, redirect), NULL}, - {ngx_string("auth_jwt_validation_type"), + {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, validation_type), + offsetof(auth_jwt_conf_t, jwt_location), NULL}, {ngx_string("auth_jwt_algorithm"), @@ -208,7 +208,7 @@ static char *merge_conf(ngx_conf_t *cf, void *parent, void *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->validation_type, prev->validation_type, ""); + 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); @@ -311,7 +311,7 @@ static ngx_int_t handle_request(ngx_http_request_t *r) } else { - char *jwtPtr = get_jwt(r, jwtcf->validation_type); + char *jwtPtr = get_jwt(r, jwtcf->jwt_location); if (jwtPtr == NULL) { @@ -608,51 +608,50 @@ 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 validation_type) +static char *get_jwt(ngx_http_request_t *r, ngx_str_t jwt_location) { + static const char *HEADER_PREFIX = "HEADER="; + static const char *BEARER_PREFIX = "Bearer "; + static const char *COOKIE_PREFIX = "COOKIE="; char *jwtPtr = NULL; - ngx_log_debug(NGX_LOG_DEBUG, r->connection->log, 0, "validation_type.len %d", validation_type.len); + ngx_log_debug(NGX_LOG_DEBUG, r->connection->log, 0, "jwt_location.len %d", jwt_location.len); - if (validation_type.len == 0 || (validation_type.len == sizeof("AUTHORIZATION") - 1 && ngx_strncmp(validation_type.data, "AUTHORIZATION", sizeof("AUTHORIZATION") - 1) == 0)) + if (jwt_location.len > sizeof(HEADER_PREFIX) && ngx_strncmp(jwt_location.data, HEADER_PREFIX, sizeof(HEADER_PREFIX) - 1) == 0) { - static const ngx_str_t authorizationHeaderName = ngx_string("Authorization"); - const ngx_table_elt_t *authorizationHeader = search_headers_in(r, authorizationHeaderName.data, authorizationHeaderName.len); + ngx_table_elt_t *jwtHeaderVal; - if (authorizationHeader != NULL) - { - ngx_int_t bearer_length = authorizationHeader->value.len - (sizeof("Bearer ") - 1); + jwt_location.data += sizeof(HEADER_PREFIX) - 1; + jwt_location.len -= sizeof(HEADER_PREFIX) - 1; - ngx_log_debug(NGX_LOG_DEBUG, r->connection->log, 0, "Found authorization header len %d", authorizationHeader->value.len); + jwtHeaderVal = search_headers_in(r, jwt_location.data, jwt_location.len); - if (bearer_length > 0) + if (jwtHeaderVal != NULL) + { + if (ngx_strncmp(jwtHeaderVal->value.data, BEARER_PREFIX, sizeof(BEARER_PREFIX) - 1) == 0) { - ngx_str_t authorizationHeaderStr; - - authorizationHeaderStr.data = authorizationHeader->value.data + sizeof("Bearer ") - 1; - authorizationHeaderStr.len = bearer_length; - - jwtPtr = ngx_str_t_to_char_ptr(r->pool, authorizationHeaderStr); - - ngx_log_debug(NGX_LOG_DEBUG, r->connection->log, 0, "Authorization header: %s", jwtPtr); + jwtHeaderVal->value.data += sizeof(BEARER_PREFIX) - 1; + jwtHeaderVal->value.len -= sizeof(BEARER_PREFIX) - 1; } + + jwtPtr = ngx_str_t_to_char_ptr(r->pool, jwtHeaderVal->value); } } - else if (validation_type.len > sizeof("COOKIE=") && ngx_strncmp(validation_type.data, "COOKIE=", sizeof("COOKIE=") - 1) == 0) + else if (jwt_location.len > sizeof(COOKIE_PREFIX) && ngx_strncmp(jwt_location.data, COOKIE_PREFIX, sizeof(COOKIE_PREFIX) - 1) == 0) { bool has_cookie = false; ngx_str_t jwtCookieVal; - validation_type.data += sizeof("COOKIE=") - 1; - validation_type.len -= sizeof("COOKIE=") - 1; + jwt_location.data += sizeof(COOKIE_PREFIX) - 1; + jwt_location.len -= sizeof(COOKIE_PREFIX) - 1; #ifndef NGX_LINKED_LIST_COOKIES - if (ngx_http_parse_multi_header_lines(&r->headers_in.cookies, &validation_type, &jwtCookieVal) != NGX_DECLINED) + if (ngx_http_parse_multi_header_lines(&r->headers_in.cookies, &jwt_location, &jwtCookieVal) != NGX_DECLINED) { has_cookie = true; } #else - if (ngx_http_parse_multi_header_lines(r, r->headers_in.cookie, &validation_type, &jwtCookieVal) != NULL) + if (ngx_http_parse_multi_header_lines(r, r->headers_in.cookie, &jwt_location, &jwtCookieVal) != NULL) { has_cookie = true; } diff --git a/test/etc/nginx/conf.d/test.conf b/test/etc/nginx/conf.d/test.conf index 62624df..3d84e19 100644 --- a/test/etc/nginx/conf.d/test.conf +++ b/test/etc/nginx/conf.d/test.conf @@ -17,7 +17,7 @@ server { location /secure/cookie/default { auth_jwt_enabled on; auth_jwt_redirect on; - auth_jwt_validation_type COOKIE=jwt; + auth_jwt_location COOKIE=jwt; alias /usr/share/nginx/html/; try_files index.html =404; @@ -27,7 +27,7 @@ server { auth_jwt_enabled on; auth_jwt_redirect on; auth_jwt_validate_sub on; - auth_jwt_validation_type COOKIE=jwt; + auth_jwt_location COOKIE=jwt; alias /usr/share/nginx/html/; try_files index.html =404; @@ -36,7 +36,7 @@ server { location /secure/cookie/default/no-redirect { auth_jwt_enabled on; auth_jwt_redirect off; - auth_jwt_validation_type COOKIE=jwt; + auth_jwt_location COOKIE=jwt; alias /usr/share/nginx/html/; try_files index.html =404; @@ -45,7 +45,7 @@ server { location /secure/cookie/hs256 { auth_jwt_enabled on; auth_jwt_redirect on; - auth_jwt_validation_type COOKIE=jwt; + auth_jwt_location COOKIE=jwt; auth_jwt_algorithm HS256; alias /usr/share/nginx/html/; @@ -55,7 +55,7 @@ server { location /secure/cookie/hs384 { auth_jwt_enabled on; auth_jwt_redirect on; - auth_jwt_validation_type COOKIE=jwt; + auth_jwt_location COOKIE=jwt; auth_jwt_algorithm HS384; alias /usr/share/nginx/html/; @@ -65,7 +65,7 @@ server { location /secure/cookie/hs512 { auth_jwt_enabled on; auth_jwt_redirect on; - auth_jwt_validation_type COOKIE=jwt; + auth_jwt_location COOKIE=jwt; auth_jwt_algorithm HS512; alias /usr/share/nginx/html/; @@ -75,7 +75,7 @@ server { location /secure/auth-header/default { auth_jwt_enabled on; auth_jwt_redirect on; - auth_jwt_validation_type AUTHORIZATION; + auth_jwt_location HEADER=Authorization; alias /usr/share/nginx/html/; try_files index.html =404; @@ -84,7 +84,7 @@ server { location /secure/auth-header/default/no-redirect { auth_jwt_enabled on; auth_jwt_redirect off; - auth_jwt_validation_type AUTHORIZATION; + auth_jwt_location HEADER=Authorization; alias /usr/share/nginx/html/; try_files index.html =404; @@ -93,7 +93,7 @@ server { location /secure/auth-header/rs256 { auth_jwt_enabled on; auth_jwt_redirect on; - auth_jwt_validation_type AUTHORIZATION; + auth_jwt_location HEADER=Authorization; auth_jwt_key "-----BEGIN PUBLIC KEY----- MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAwtpMAM4l1H995oqlqdMh uqNuffp4+4aUCwuFE9B5s9MJr63gyf8jW0oDr7Mb1Xb8y9iGkWfhouZqNJbMFry+ @@ -111,7 +111,7 @@ BwIDAQAB location /secure/auth-header/rs256/file { auth_jwt_enabled on; auth_jwt_redirect on; - auth_jwt_validation_type AUTHORIZATION; + auth_jwt_location HEADER=Authorization; auth_jwt_algorithm RS256; auth_jwt_use_keyfile on; auth_jwt_keyfile_path "/etc/nginx/rsa-key.conf"; @@ -123,7 +123,7 @@ BwIDAQAB location /secure/auth-header/rs384/file { auth_jwt_enabled on; auth_jwt_redirect on; - auth_jwt_validation_type AUTHORIZATION; + auth_jwt_location HEADER=Authorization; auth_jwt_algorithm RS384; auth_jwt_use_keyfile on; auth_jwt_keyfile_path "/etc/nginx/rsa-key.conf"; @@ -135,7 +135,7 @@ BwIDAQAB location /secure/auth-header/rs512/file { auth_jwt_enabled on; auth_jwt_redirect on; - auth_jwt_validation_type AUTHORIZATION; + auth_jwt_location HEADER=Authorization; auth_jwt_algorithm RS512; auth_jwt_use_keyfile on; auth_jwt_keyfile_path "/etc/nginx/rsa-key.conf"; @@ -144,10 +144,20 @@ BwIDAQAB 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_validation_type AUTHORIZATION; + auth_jwt_location HEADER=Authorization; auth_jwt_extract_request_claims sub; add_header "Test" "sub=$http_jwt_sub"; @@ -159,7 +169,7 @@ BwIDAQAB location /secure/extract-claim/request/name-1 { auth_jwt_enabled on; auth_jwt_redirect off; - auth_jwt_validation_type AUTHORIZATION; + auth_jwt_location HEADER=Authorization; auth_jwt_extract_request_claims firstName lastName; add_header "Test" "$http_jwt_firstname $http_jwt_lastname"; @@ -171,7 +181,7 @@ BwIDAQAB location /secure/extract-claim/request/name-2 { auth_jwt_enabled on; auth_jwt_redirect off; - auth_jwt_validation_type AUTHORIZATION; + auth_jwt_location HEADER=Authorization; auth_jwt_extract_request_claims firstName; auth_jwt_extract_request_claims lastName; @@ -184,7 +194,7 @@ BwIDAQAB location /secure/extract-claim/response/sub { auth_jwt_enabled on; auth_jwt_redirect off; - auth_jwt_validation_type AUTHORIZATION; + auth_jwt_location HEADER=Authorization; auth_jwt_extract_response_claims sub; add_header "Test" "sub=$sent_http_jwt_sub"; @@ -196,7 +206,7 @@ BwIDAQAB location /secure/extract-claim/response/name-1 { auth_jwt_enabled on; auth_jwt_redirect off; - auth_jwt_validation_type AUTHORIZATION; + auth_jwt_location HEADER=Authorization; auth_jwt_extract_response_claims firstName lastName; add_header "Test" "$sent_http_jwt_firstname $sent_http_jwt_lastname"; @@ -208,7 +218,7 @@ BwIDAQAB location /secure/extract-claim/response/name-2 { auth_jwt_enabled on; auth_jwt_redirect off; - auth_jwt_validation_type AUTHORIZATION; + auth_jwt_location HEADER=Authorization; auth_jwt_extract_response_claims firstName; auth_jwt_extract_response_claims lastName; diff --git a/test/test.sh b/test/test.sh index 9516087..af03de5 100755 --- a/test/test.sh +++ b/test/test.sh @@ -109,10 +109,15 @@ main() { -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 401' \ + 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 '401' \ - -x '--header "Authorization: X"' + -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 default algorithm and no JWT cookie, returns 302' \ -p '/secure/cookie/default' \ @@ -143,7 +148,7 @@ main() { -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/' \ + -p '/secure/cookie/hs256' \ -c '200' \ -x '--cookie "jwt=${JWT_HS256_VALID}"' @@ -182,6 +187,16 @@ main() { -c '200' \ -x '--header "Authorization: Bearer ${JWT_RS256_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 header' \ -p '/secure/extract-claim/request/sub' \ -r '^Test: sub=some-long-uuid$' \ From da1c7ce1c897bd5398b6aa4e0c30b02dc015209e Mon Sep 17 00:00:00 2001 From: Josh McCullough Date: Tue, 25 Apr 2023 15:01:11 -0400 Subject: [PATCH 30/75] update scripts.sh to add release-related functions --- .gitignore | 1 + scripts.sh | 25 +++++++++++++++++++++++++ 2 files changed, 26 insertions(+) diff --git a/.gitignore b/.gitignore index fe4f0be..14c2591 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,4 @@ .idea .vscode bin +release \ No newline at end of file diff --git a/scripts.sh b/scripts.sh index 00a81c7..66b8624 100755 --- a/scripts.sh +++ b/scripts.sh @@ -80,6 +80,31 @@ cp_bin() { fi } +make_release() { + printf "${BLUE}Making release for version ${NGINX_VERSION}...${NC}\n" + + build_module + cp_bin + + mkdir -p release + tar -czvf release/ngx_http_auth_jwt_module_${NGINX_VERSION}.tgz \ + README.md \ + -C bin/usr/lib64/nginx/modules ngx_http_auth_jwt_module.so > /dev/null +} + +# Create releases for the current mainline and stable version, as well as the 2 most recent "legacy" versions. +# See: https://nginx.org/en/download.html +make_releases() { + VERSIONS=(1.20.2 1.22.1 1.24.0 1.23.4) + + rm -rf release/* + + for v in ${VERSIONS[@]}; do + NGINX_VERSION=${v} make_release + done +} + + build_test_runner() { local dockerArgs=${1:-} local configHash=$(get_hash $(find test -type f -not -name 'test.sh' -not -name '*.yml' -not -name 'Dockerfile*')) From d7a369188b1f8ebcf9ee275f5a716b655a17a2bc Mon Sep 17 00:00:00 2001 From: Josh McCullough Date: Tue, 25 Apr 2023 15:01:24 -0400 Subject: [PATCH 31/75] update default NGINX_VERSION to 1.24.0 (stable) --- scripts.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scripts.sh b/scripts.sh index 66b8624..e115b27 100755 --- a/scripts.sh +++ b/scripts.sh @@ -9,7 +9,7 @@ export ORG_NAME=${ORG_NAME:-teslagov} export IMAGE_NAME=${IMAGE_NAME:-jwt-nginx} export FULL_IMAGE_NAME=${ORG_NAME}/${IMAGE_NAME} export CONTAINER_NAME_PREFIX=${CONTAINER_NAME_PREFIX:-jwt-nginx-test} -export NGINX_VERSION=${NGINX_VERSION:-1.22.0} +export NGINX_VERSION=${NGINX_VERSION:-1.24.0} all() { build_module From 08edb040f96142a72454f02fcaea4411b8c84698 Mon Sep 17 00:00:00 2001 From: "Lewis M. Kabui" <13940255+lewisemm@users.noreply.github.com> Date: Wed, 26 Apr 2023 14:37:53 +0300 Subject: [PATCH 32/75] Add missing backticks to fix markdown format (#92) Co-authored-by: Lewis M. Kabui --- README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 6a629d3..d385f23 100644 --- a/README.md +++ b/README.md @@ -167,13 +167,13 @@ Note the `includePath` additions above -- please update them as appropriate. Nex #### Cloning `libjwt` -1. Clone this repository as follows (replace ``): `git clone git@github.com:benmcollins/libjwt.git +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 +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. From 89346e183711197eacbd7c885162176ef563b88d Mon Sep 17 00:00:00 2001 From: Josh McCullough Date: Wed, 26 Apr 2023 10:46:42 -0400 Subject: [PATCH 33/75] fix extraction of claims in nested config block (#91) --- config | 2 +- src/arrays.c | 14 ++++++++ src/arrays.h | 7 ++++ src/ngx_http_auth_jwt_module.c | 5 +-- test/etc/nginx/conf.d/test.conf | 36 +++++++++++++++++--- test/test.sh | 60 ++++++++++++++++++++++++--------- 6 files changed, 102 insertions(+), 22 deletions(-) create mode 100644 src/arrays.c create mode 100644 src/arrays.h diff --git a/config b/config index 13c2325..0271a79 100644 --- a/config +++ b/config @@ -1,7 +1,7 @@ ngx_module_type=HTTP ngx_addon_name=ngx_http_auth_jwt_module ngx_module_name=$ngx_addon_name -ngx_module_srcs="${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_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 diff --git a/src/arrays.c b/src/arrays.c new file mode 100644 index 0000000..043c24e --- /dev/null +++ b/src/arrays.c @@ -0,0 +1,14 @@ +#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; + } +} diff --git a/src/arrays.h b/src/arrays.h new file mode 100644 index 0000000..5e17158 --- /dev/null +++ b/src/arrays.h @@ -0,0 +1,7 @@ +#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); + +#endif \ No newline at end of file diff --git a/src/ngx_http_auth_jwt_module.c b/src/ngx_http_auth_jwt_module.c index 0268dab..ffc8964 100644 --- a/src/ngx_http_auth_jwt_module.c +++ b/src/ngx_http_auth_jwt_module.c @@ -14,6 +14,7 @@ #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" @@ -212,8 +213,8 @@ static char *merge_conf(ngx_conf_t *cf, void *parent, void *child) 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); - ngx_conf_merge_ptr_value(conf->extract_request_claims, prev->extract_request_claims, NULL); - ngx_conf_merge_ptr_value(conf->extract_request_claims, prev->extract_response_claims, NULL); + 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 == NGX_CONF_UNSET) { diff --git a/test/etc/nginx/conf.d/test.conf b/test/etc/nginx/conf.d/test.conf index 3d84e19..71cbd55 100644 --- a/test/etc/nginx/conf.d/test.conf +++ b/test/etc/nginx/conf.d/test.conf @@ -172,7 +172,7 @@ BwIDAQAB auth_jwt_location HEADER=Authorization; auth_jwt_extract_request_claims firstName lastName; - add_header "Test" "$http_jwt_firstname $http_jwt_lastname"; + add_header "Test" "firstName=$http_jwt_firstname; lastName=$http_jwt_lastname"; alias /usr/share/nginx/html/; try_files index.html =404; @@ -185,12 +185,26 @@ BwIDAQAB auth_jwt_extract_request_claims firstName; auth_jwt_extract_request_claims lastName; - add_header "Test" "$http_jwt_firstname $http_jwt_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; @@ -209,7 +223,7 @@ BwIDAQAB auth_jwt_location HEADER=Authorization; auth_jwt_extract_response_claims firstName lastName; - add_header "Test" "$sent_http_jwt_firstname $sent_http_jwt_lastname"; + add_header "Test" "firstName=$sent_http_jwt_firstname; lastName=$sent_http_jwt_lastname"; alias /usr/share/nginx/html/; try_files index.html =404; @@ -222,10 +236,24 @@ BwIDAQAB auth_jwt_extract_response_claims firstName; auth_jwt_extract_response_claims lastName; - add_header "Test" "$sent_http_jwt_firstname $sent_http_jwt_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; + } + } } diff --git a/test/test.sh b/test/test.sh index af03de5..471f658 100755 --- a/test/test.sh +++ b/test/test.sh @@ -56,7 +56,7 @@ run_test () { printf "${RED}${name} -- unexpected exit code from cURL\n\tcURL Exit Code: ${exitCode}"; NUM_FAILED=$((${NUM_FAILED} + 1)); else - OKAY=1 + local okay=1 if [ "${expectedCode}" != "" ]; then local responseCode=$(echo "${response}" | grep -Eo 'HTTP/1.1 ([0-9]{3})' | awk '{print $2}') @@ -64,17 +64,17 @@ run_test () { 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 + okay=0 fi fi - - if [ "${OKAY}" == "1" ] && [ "${expectedResponseRegex}" != "" ] && echo "${response}" | grep -Eq "${expectedResponseRegex}"; then + + if [ "${okay}" == '1' ] && [ "${expectedResponseRegex}" != "" ] && ! [[ "${response}" =~ "${expectedResponseRegex}" ]]; then printf "${RED}${name} -- regex not found in response\n\tPath: ${path}\n\tRegEx: ${expectedResponseRegex}" NUM_FAILED=$((${NUM_FAILED} + 1)) - OKAY=0 + okay=0 fi - if [ "${OKAY}" == "1" ]; then + if [ "${okay}" == '1' ]; then printf "${GREEN}${name}"; fi fi @@ -197,34 +197,64 @@ main() { -c '200' \ -x '--header "Auth-Token: Bearer ${JWT_HS256_VALID}"' - run_test -n 'extracts single claim to request header' \ + run_test -n 'extracts single claim to request variable' \ -p '/secure/extract-claim/request/sub' \ - -r '^Test: sub=some-long-uuid$' \ + -r '< Test: sub=some-long-uuid' \ -x '--header "Authorization: Bearer ${JWT_HS256_VALID}"' - run_test -n 'extracts multiple claims (single directive) to request header' \ + run_test -n 'extracts multiple claims (single directive) to request variable' \ -p '/secure/extract-claim/request/name-1' \ - -r '^Test: hello world$' \ + -r '< Test: firstName=hello; lastName=world' \ -x '--header "Authorization: Bearer ${JWT_HS256_VALID}"' - run_test -n 'extracts multiple claims (multiple directives) to request header' \ + run_test -n 'extracts multiple claims (multiple directives) to request variable' \ -p '/secure/extract-claim/request/name-2' \ - -r '^Test: hello world$' \ + -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 '^Test: sub=some-long-uuid$' \ + -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 '^Test: hello world$' \ + -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 '^Test: hello world$' \ + -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}"' if [[ "${NUM_FAILED}" = '0' ]]; then From f79e6603ae0ceefa545cb0eb80e5c1d05d96c77a Mon Sep 17 00:00:00 2001 From: Josh McCullough Date: Wed, 26 Apr 2023 11:11:21 -0400 Subject: [PATCH 34/75] update make_release to include module version number --- scripts.sh | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) diff --git a/scripts.sh b/scripts.sh index e115b27..fa96940 100755 --- a/scripts.sh +++ b/scripts.sh @@ -81,13 +81,16 @@ cp_bin() { } make_release() { - printf "${BLUE}Making release for version ${NGINX_VERSION}...${NC}\n" + local moduleVersion=${1} + local nginxVersion=${2} + + printf "${BLUE}Making release for version ${moduleVersion} for NGINX ${nginxVersion}...${NC}\n" build_module cp_bin mkdir -p release - tar -czvf release/ngx_http_auth_jwt_module_${NGINX_VERSION}.tgz \ + tar -czvf release/ngx_http_auth_jwt_module_${moduleVersion}_nginx_${nginxVersion}.tgz \ README.md \ -C bin/usr/lib64/nginx/modules ngx_http_auth_jwt_module.so > /dev/null } @@ -95,12 +98,13 @@ make_release() { # Create releases for the current mainline and stable version, as well as the 2 most recent "legacy" versions. # See: https://nginx.org/en/download.html make_releases() { - VERSIONS=(1.20.2 1.22.1 1.24.0 1.23.4) + local moduleVersion=$(git describe --tags --abbrev=0) + local nginxVersions=(1.20.2 1.22.1 1.24.0 1.23.4) rm -rf release/* - for v in ${VERSIONS[@]}; do - NGINX_VERSION=${v} make_release + for v in ${nginxVersions[@]}; do + make_release ${moduleVersion} ${v} done } From d4271623eade259f29fc0faab9c6166c32528e0b Mon Sep 17 00:00:00 2001 From: Josh McCullough Date: Wed, 3 May 2023 08:53:58 -0400 Subject: [PATCH 35/75] add note about string claims --- README.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/README.md b/README.md index d385f23..8c34d5c 100644 --- a/README.md +++ b/README.md @@ -88,6 +88,8 @@ You may specify claims to be extracted from the JWT and placed on the request an If you only wish to access a claim as an NGINX variable, you should use `auth_jwt_extract_request_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, then 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 Request 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`. From a23ed3c88e71b39ac0eff99086c622f347f0195e Mon Sep 17 00:00:00 2001 From: Josh McCullough Date: Thu, 4 May 2023 08:22:20 -0400 Subject: [PATCH 36/75] properly set NGINX_VERSION when making releases (#97) --- scripts.sh | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/scripts.sh b/scripts.sh index fa96940..3922a29 100755 --- a/scripts.sh +++ b/scripts.sh @@ -25,7 +25,7 @@ build_module() { docker image pull debian:bullseye-slim docker image pull nginx:${NGINX_VERSION} - printf "${BLUE}Building module...${NC}\n" + printf "${BLUE}Building module for NGINX ${NGINX_VERSION}...${NC}\n" docker image build -t ${FULL_IMAGE_NAME}:latest -t ${FULL_IMAGE_NAME}:${NGINX_VERSION} ${dockerArgs} \ --build-arg NGINX_VERSION=${NGINX_VERSION} \ --build-arg SOURCE_HASH=${sourceHash} . @@ -82,15 +82,16 @@ cp_bin() { make_release() { local moduleVersion=${1} - local nginxVersion=${2} + + NGINX_VERSION=${2} - printf "${BLUE}Making release for version ${moduleVersion} for NGINX ${nginxVersion}...${NC}\n" + printf "${BLUE}Making release for version ${moduleVersion} for NGINX ${NGINX_VERSION}...${NC}\n" build_module cp_bin mkdir -p release - tar -czvf release/ngx_http_auth_jwt_module_${moduleVersion}_nginx_${nginxVersion}.tgz \ + tar -czvf release/ngx_http_auth_jwt_module_${moduleVersion}_nginx_${NGINX_VERSION}.tgz \ README.md \ -C bin/usr/lib64/nginx/modules ngx_http_auth_jwt_module.so > /dev/null } From 2eaf11c65029bccde5a0d3da34e0e687dbcb9e8a Mon Sep 17 00:00:00 2001 From: Josh McCullough Date: Thu, 10 Aug 2023 12:21:29 -0400 Subject: [PATCH 37/75] fix issue with "Bearer" being removed from header (#106) --- src/ngx_http_auth_jwt_module.c | 17 ++++++++++++----- test/etc/nginx/conf.d/test.conf | 11 +++++++++++ test/test.sh | 6 ++++++ 3 files changed, 29 insertions(+), 5 deletions(-) diff --git a/src/ngx_http_auth_jwt_module.c b/src/ngx_http_auth_jwt_module.c index ffc8964..744b50d 100644 --- a/src/ngx_http_auth_jwt_module.c +++ b/src/ngx_http_auth_jwt_module.c @@ -612,7 +612,6 @@ 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 *HEADER_PREFIX = "HEADER="; - static const char *BEARER_PREFIX = "Bearer "; static const char *COOKIE_PREFIX = "COOKIE="; char *jwtPtr = NULL; @@ -629,13 +628,21 @@ static char *get_jwt(ngx_http_request_t *r, ngx_str_t jwt_location) if (jwtHeaderVal != NULL) { + static const char *BEARER_PREFIX = "Bearer "; + if (ngx_strncmp(jwtHeaderVal->value.data, BEARER_PREFIX, sizeof(BEARER_PREFIX) - 1) == 0) { - jwtHeaderVal->value.data += sizeof(BEARER_PREFIX) - 1; - jwtHeaderVal->value.len -= sizeof(BEARER_PREFIX) - 1; - } + ngx_str_t jwtHeaderValWithoutBearer = jwtHeaderVal->value; + + jwtHeaderValWithoutBearer.data += sizeof(BEARER_PREFIX) - 1; + jwtHeaderValWithoutBearer.len -= sizeof(BEARER_PREFIX) - 1; - jwtPtr = ngx_str_t_to_char_ptr(r->pool, jwtHeaderVal->value); + 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 > sizeof(COOKIE_PREFIX) && ngx_strncmp(jwt_location.data, COOKIE_PREFIX, sizeof(COOKIE_PREFIX) - 1) == 0) diff --git a/test/etc/nginx/conf.d/test.conf b/test/etc/nginx/conf.d/test.conf index 71cbd55..00c990b 100644 --- a/test/etc/nginx/conf.d/test.conf +++ b/test/etc/nginx/conf.d/test.conf @@ -90,6 +90,17 @@ server { 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; diff --git a/test/test.sh b/test/test.sh index 471f658..29a0bf3 100755 --- a/test/test.sh +++ b/test/test.sh @@ -119,6 +119,12 @@ main() { -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 default algorithm and no JWT cookie, returns 302' \ -p '/secure/cookie/default' \ -c '302' From 294db83fc613230958dc777edcc28cdde1a8056d Mon Sep 17 00:00:00 2001 From: Josh McCullough Date: Thu, 10 Aug 2023 12:42:30 -0400 Subject: [PATCH 38/75] update release versions; test before release --- scripts.sh | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/scripts.sh b/scripts.sh index 3922a29..253abfc 100755 --- a/scripts.sh +++ b/scripts.sh @@ -81,13 +81,17 @@ cp_bin() { } make_release() { + set -e + local moduleVersion=${1} NGINX_VERSION=${2} printf "${BLUE}Making release for version ${moduleVersion} for NGINX ${NGINX_VERSION}...${NC}\n" - build_module + rebuild_module + rebuild_test_runner + test cp_bin mkdir -p release @@ -100,7 +104,7 @@ make_release() { # See: https://nginx.org/en/download.html make_releases() { local moduleVersion=$(git describe --tags --abbrev=0) - local nginxVersions=(1.20.2 1.22.1 1.24.0 1.23.4) + local nginxVersions=('1.25.1' '1.24.0' '1.22.1' '1.20.2') rm -rf release/* From 5f9ffd20fec5fc31c90d5851c06747456a608e51 Mon Sep 17 00:00:00 2001 From: KnownEntity <42591012+KnownEntity@users.noreply.github.com> Date: Wed, 23 Aug 2023 18:08:46 -0500 Subject: [PATCH 39/75] GitHub Action to automatically build master branch on commit (#108) Co-authored-by: Josh McCullough --- .github/workflows/ci.yml | 133 +++++++++++++++++++++++++++++++++++++++ 1 file changed, 133 insertions(+) create mode 100644 .github/workflows/ci.yml diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml new file mode 100644 index 0000000..d7f4b6b --- /dev/null +++ b/.github/workflows/ci.yml @@ -0,0 +1,133 @@ +name: CI + +on: + push: + branches: + - 'master' + pull_request: + branches: + - 'master' + workflow_dispatch: + +jobs: + build: + strategy: + matrix: + # Each nginx version to build against + nginx-version: ['1.20.2', '1.22.1', '1.24.0', '1.25.1'] + # The following versions of libjwt are compatible: + # * v1.0 - v1.12.0 + # * v1.12.1 - v1.14.0 + # * v1.15.0+ + # At the time of writing this: + # * Debian and Ubuntu's repos have v1.10.2 + # * EPEL has v1.12.1 + # This compilles against each version prior to a breaking change and the latest release + libjwt-version: ['1.12.0', '1.14.0', '1.15.3'] + runs-on: ubuntu-latest + steps: + - name: Checkout code + uses: actions/checkout@v3 + with: + path: 'ngx-http-auth-jwt-module' + + - name: Download jansson + uses: actions/checkout@v3 + with: + repository: 'akheron/jansson' + ref: 'v2.14' + path: 'jansson' + + - name: Build jansson + working-directory: ./jansson + run: | + cmake . -DJANSSON_BUILD_SHARED_LIBS=1 -DJANSSON_BUILD_DOCS=OFF && \ + make && \ + make check && \ + sudo make install + + - name: Download libjwt + uses: actions/checkout@v3 + with: + repository: 'benmcollins/libjwt' + ref: 'v${{matrix.libjwt-version}}' + path: 'libjwt' + + - name: Build libjwt + working-directory: ./libjwt + run: | + autoreconf -i && \ + ./configure && \ + make all && \ + sudo make install + + - name: Download NGINX + run: | + mkdir nginx + curl -O http://nginx.org/download/nginx-${{matrix.nginx-version}}.tar.gz + tar -xzf nginx-${{matrix.nginx-version}}.tar.gz --strip-components 1 -C nginx + + - name: Run configure + working-directory: ./nginx + run: | + BUILD_FLAGS='' + MAJ=$(echo ${{matrix.nginx-version}} | cut -f1 -d.) + MIN=$(echo ${{matrix.nginx-version}} | cut -f2 -d.) + REV=$(echo ${{matrix.nginx-version}} | cut -f3 -d.) + if [ "${MAJ}" -gt 1 ] || [ "${MAJ}" -eq 1 -a "${MIN}" -ge 23 ]; then + BUILD_FLAGS="${BUILD_FLAGS} --with-cc-opt='-DNGX_LINKED_LIST_COOKIES=1'" + fi + ./configure --with-compat --add-dynamic-module=../ngx-http-auth-jwt-module ${BUILD_FLAGS} + + - name: Run make + working-directory: ./nginx + run: make modules + + - name: Create release archive + run: | + cp ./nginx/objs/ngx_http_auth_jwt_module.so ./ + tar czf ngx_http_auth_jwt_module_${{github.ref_name}}_libjwt_${{matrix.libjwt-version}}_nginx_${{matrix.nginx-version}}.tgz ngx_http_auth_jwt_module.so + + - name: Upload build artifact + uses: actions/upload-artifact@v3 + with: + if-no-files-found: error + name: ngx_http_auth_jwt_module_${{github.ref_name}}_libjwt_${{matrix.libjwt-version}}_nginx_${{matrix.nginx-version}}.tgz + path: ngx_http_auth_jwt_module_${{github.ref_name}}_libjwt_${{matrix.libjwt-version}}_nginx_${{matrix.nginx-version}}.tgz + + update_releases_page: + name: Upload builds to Releases + if: github.event_name != 'pull_request' + needs: + - 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 build artifacts from previous jobs + uses: actions/download-artifact@v3 + with: + path: artifacts + + - name: Upload builds to Releases + uses: ncipollo/release-action@v1 + with: + allowUpdates: true + artifactErrorsFailBuild: true + artifacts: artifacts/*/* + 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 to the issue tracker. + + - Build Date: `${{ steps.vars.outputs.date_now }}` + - Commit: ${{ github.sha }} + name: 'Development build: ${{ github.ref_name }}@${{ github.sha }}' + prerelease: true + removeArtifacts: true + tag: dev-build From c843ce15f5b7c162e58c15c5aa01de33bbd8df66 Mon Sep 17 00:00:00 2001 From: Josh McCullough Date: Fri, 25 Aug 2023 08:09:53 -0400 Subject: [PATCH 40/75] Update README.md --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 8c34d5c..a5b7018 100644 --- a/README.md +++ b/README.md @@ -31,7 +31,7 @@ This module requires several new `nginx.conf` directives, which can be specified ## 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) as in the example above. 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. +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. ### Additional Supported Algorithms From e5d629e30b631ea363a82cd02a01d1a483f641a0 Mon Sep 17 00:00:00 2001 From: Josh McCullough Date: Fri, 25 Aug 2023 08:22:24 -0400 Subject: [PATCH 41/75] update CI to only run if src dir is changed --- .github/workflows/ci.yml | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index d7f4b6b..9177330 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -4,9 +4,13 @@ on: push: branches: - 'master' + paths: + - src/** pull_request: branches: - 'master' + paths: + - src/** workflow_dispatch: jobs: From 0b8e193c95ed881770545058b76099e87132edba Mon Sep 17 00:00:00 2001 From: Josh McCullough Date: Fri, 25 Aug 2023 08:23:56 -0400 Subject: [PATCH 42/75] add hex generation to README --- README.md | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/README.md b/README.md index a5b7018..51c7196 100644 --- a/README.md +++ b/README.md @@ -33,6 +33,12 @@ This module requires several new `nginx.conf` directives, which can be specified 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. From 05a37988674aa3f2a63d80a3d43abb6684408cca Mon Sep 17 00:00:00 2001 From: Josh McCullough Date: Fri, 13 Oct 2023 18:51:48 -0400 Subject: [PATCH 43/75] clarify test logging --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 51c7196..ba3dde1 100644 --- a/README.md +++ b/README.md @@ -231,7 +231,7 @@ The tests use a customized NGINX image, distinct from the main image, as well as 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. -In order to persist logs, you can configure the log driver to use. You can do this by setting the environment variable `LOG_DRIVER` before running the tests. On Linux/Unix systems, you can use the driver `journald`, as follows: +If you'd like to persist logs across test runs, you can configure the log driver to use `journald` (on Linux/Unix systems for example). You can do this by setting the environment variable `LOG_DRIVER` before running the tests: ```shell # need to rebuild the test runner with the proper log driver From 2062be55dd8624ab8f4d06d27fc455bbc51a730b Mon Sep 17 00:00:00 2001 From: Josh McCullough Date: Mon, 4 Dec 2023 11:21:01 -0500 Subject: [PATCH 44/75] update scripts.sh to clarify target NGINX versions --- scripts.sh | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/scripts.sh b/scripts.sh index 253abfc..175256c 100755 --- a/scripts.sh +++ b/scripts.sh @@ -5,11 +5,17 @@ GREEN='\033[0;32m' RED='\033[0;31m' NC='\033[0m' +# supported NGINX versions -- for binary distribution +NGINX_VERSION_MAINLINE='1.25.3' +NGINX_VERSION_STABLE='1.24.0' +NGINX_VERSION_LEGACY_1='1.22.1' +NGINX_VERSION_LEGACY_2='1.20.2' + export ORG_NAME=${ORG_NAME:-teslagov} export IMAGE_NAME=${IMAGE_NAME:-jwt-nginx} export FULL_IMAGE_NAME=${ORG_NAME}/${IMAGE_NAME} export CONTAINER_NAME_PREFIX=${CONTAINER_NAME_PREFIX:-jwt-nginx-test} -export NGINX_VERSION=${NGINX_VERSION:-1.24.0} +export NGINX_VERSION=${NGINX_VERSION:-${NGINX_VERSION_STABLE}} all() { build_module @@ -104,8 +110,8 @@ make_release() { # See: https://nginx.org/en/download.html make_releases() { local moduleVersion=$(git describe --tags --abbrev=0) - local nginxVersions=('1.25.1' '1.24.0' '1.22.1' '1.20.2') - + local nginxVersions=(${NGINX_VERSION_MAINLINE} ${NGINX_VERSION_STABLE} ${NGINX_VERSION_LEGACY_1} ${NGINX_VERSION_LEGACY_2}) + rm -rf release/* for v in ${nginxVersions[@]}; do From 07f6f996975e6619a5e7a82ee7a42bc8177ef29f Mon Sep 17 00:00:00 2001 From: Josh McCullough Date: Mon, 4 Dec 2023 13:32:29 -0500 Subject: [PATCH 45/75] replace `sizeof` with `strlen` (#116) --- .github/workflows/ci.yml | 9 +++++---- src/ngx_http_auth_jwt_module.c | 30 +++++++++++++++--------------- 2 files changed, 20 insertions(+), 19 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 9177330..3c31403 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -15,10 +15,11 @@ on: jobs: build: + name: "NGINX: ${{ matrix.nginx-version }}; libjwt: ${{ matrix.libjwt-version }}" strategy: matrix: # Each nginx version to build against - nginx-version: ['1.20.2', '1.22.1', '1.24.0', '1.25.1'] + nginx-version: ['1.20.2', '1.22.1', '1.24.0', '1.25.3'] # The following versions of libjwt are compatible: # * v1.0 - v1.12.0 # * v1.12.1 - v1.14.0 @@ -90,14 +91,14 @@ jobs: - name: Create release archive run: | cp ./nginx/objs/ngx_http_auth_jwt_module.so ./ - tar czf ngx_http_auth_jwt_module_${{github.ref_name}}_libjwt_${{matrix.libjwt-version}}_nginx_${{matrix.nginx-version}}.tgz ngx_http_auth_jwt_module.so + tar czf ngx_http_auth_jwt_module_libjwt_${{matrix.libjwt-version}}_nginx_${{matrix.nginx-version}}.tgz ngx_http_auth_jwt_module.so - name: Upload build artifact uses: actions/upload-artifact@v3 with: if-no-files-found: error - name: ngx_http_auth_jwt_module_${{github.ref_name}}_libjwt_${{matrix.libjwt-version}}_nginx_${{matrix.nginx-version}}.tgz - path: ngx_http_auth_jwt_module_${{github.ref_name}}_libjwt_${{matrix.libjwt-version}}_nginx_${{matrix.nginx-version}}.tgz + name: ngx_http_auth_jwt_module_libjwt_${{matrix.libjwt-version}}_nginx_${{matrix.nginx-version}}.tgz + path: ngx_http_auth_jwt_module_libjwt_${{matrix.libjwt-version}}_nginx_${{matrix.nginx-version}}.tgz update_releases_page: name: Upload builds to Releases diff --git a/src/ngx_http_auth_jwt_module.c b/src/ngx_http_auth_jwt_module.c index 744b50d..edef14a 100644 --- a/src/ngx_http_auth_jwt_module.c +++ b/src/ngx_http_auth_jwt_module.c @@ -489,7 +489,7 @@ static ngx_int_t redirect(ngx_http_request_t *r, auth_jwt_conf_t *jwtcf) } r->headers_out.location->hash = 1; - r->headers_out.location->key.len = sizeof("Location") - 1; + r->headers_out.location->key.len = strlen("Location"); r->headers_out.location->key.data = (u_char *)"Location"; if (r->method == NGX_HTTP_GET) @@ -526,21 +526,21 @@ static ngx_int_t redirect(ngx_http_request_t *r, auth_jwt_conf_t *jwtcf) uri_escaped.len = escaped_len; ngx_escape_uri(uri_escaped.data, uri.data, uri.len, NGX_ESCAPE_ARGS); - r->headers_out.location->value.len = loginlen + sizeof("?return_url=") - 1 + strlen(scheme) + sizeof("://") - 1 + server.len + uri_escaped.len; + r->headers_out.location->value.len = loginlen + strlen("?return_url=") + strlen(scheme) + strlen("://") + server.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=", sizeof("?return_url=") - 1); + ngx_memcpy(return_url + return_url_idx, "?return_url=", strlen("?return_url=")); - return_url_idx += sizeof("?return_url=") - 1; + return_url_idx += strlen("?return_url="); ngx_memcpy(return_url + return_url_idx, scheme, strlen(scheme)); return_url_idx += strlen(scheme); - ngx_memcpy(return_url + return_url_idx, "://", sizeof("://") - 1); + ngx_memcpy(return_url + return_url_idx, "://", strlen("://")); - return_url_idx += sizeof("://") - 1; + return_url_idx += strlen("://"); ngx_memcpy(return_url + return_url_idx, server.data, server.len); return_url_idx += server.len; @@ -617,12 +617,12 @@ static char *get_jwt(ngx_http_request_t *r, ngx_str_t jwt_location) ngx_log_debug(NGX_LOG_DEBUG, r->connection->log, 0, "jwt_location.len %d", jwt_location.len); - if (jwt_location.len > sizeof(HEADER_PREFIX) && ngx_strncmp(jwt_location.data, HEADER_PREFIX, sizeof(HEADER_PREFIX) - 1) == 0) + 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 += sizeof(HEADER_PREFIX) - 1; - jwt_location.len -= sizeof(HEADER_PREFIX) - 1; + jwt_location.data += strlen(HEADER_PREFIX); + jwt_location.len -= strlen(HEADER_PREFIX); jwtHeaderVal = search_headers_in(r, jwt_location.data, jwt_location.len); @@ -630,12 +630,12 @@ static char *get_jwt(ngx_http_request_t *r, ngx_str_t jwt_location) { static const char *BEARER_PREFIX = "Bearer "; - if (ngx_strncmp(jwtHeaderVal->value.data, BEARER_PREFIX, sizeof(BEARER_PREFIX) - 1) == 0) + if (ngx_strncmp(jwtHeaderVal->value.data, BEARER_PREFIX, strlen(BEARER_PREFIX)) == 0) { ngx_str_t jwtHeaderValWithoutBearer = jwtHeaderVal->value; - jwtHeaderValWithoutBearer.data += sizeof(BEARER_PREFIX) - 1; - jwtHeaderValWithoutBearer.len -= sizeof(BEARER_PREFIX) - 1; + jwtHeaderValWithoutBearer.data += strlen(BEARER_PREFIX); + jwtHeaderValWithoutBearer.len -= strlen(BEARER_PREFIX); jwtPtr = ngx_str_t_to_char_ptr(r->pool, jwtHeaderValWithoutBearer); } @@ -645,13 +645,13 @@ static char *get_jwt(ngx_http_request_t *r, ngx_str_t jwt_location) } } } - else if (jwt_location.len > sizeof(COOKIE_PREFIX) && ngx_strncmp(jwt_location.data, COOKIE_PREFIX, sizeof(COOKIE_PREFIX) - 1) == 0) + else if (jwt_location.len > strlen(COOKIE_PREFIX) && ngx_strncmp(jwt_location.data, COOKIE_PREFIX, strlen(COOKIE_PREFIX)) == 0) { bool has_cookie = false; ngx_str_t jwtCookieVal; - jwt_location.data += sizeof(COOKIE_PREFIX) - 1; - jwt_location.len -= sizeof(COOKIE_PREFIX) - 1; + jwt_location.data += strlen(COOKIE_PREFIX); + jwt_location.len -= strlen(COOKIE_PREFIX); #ifndef NGX_LINKED_LIST_COOKIES if (ngx_http_parse_multi_header_lines(&r->headers_in.cookies, &jwt_location, &jwtCookieVal) != NGX_DECLINED) From 736a95a2dabafa31e470c3938895d514f95585d5 Mon Sep 17 00:00:00 2001 From: Josh McCullough Date: Tue, 20 Feb 2024 19:53:30 -0500 Subject: [PATCH 46/75] update NGINX mainline version --- scripts.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scripts.sh b/scripts.sh index 175256c..e3fb254 100755 --- a/scripts.sh +++ b/scripts.sh @@ -6,7 +6,7 @@ RED='\033[0;31m' NC='\033[0m' # supported NGINX versions -- for binary distribution -NGINX_VERSION_MAINLINE='1.25.3' +NGINX_VERSION_MAINLINE='1.25.4' NGINX_VERSION_STABLE='1.24.0' NGINX_VERSION_LEGACY_1='1.22.1' NGINX_VERSION_LEGACY_2='1.20.2' From 032fa5c14b93d309d88b585f084e4db7dbd2f53c Mon Sep 17 00:00:00 2001 From: Stephan Wurm Date: Mon, 18 Mar 2024 17:32:42 +0100 Subject: [PATCH 47/75] add support for ES algorithms (#118) Signed-off-by: Stephan Wurm --- src/ngx_http_auth_jwt_module.c | 6 +- test/Dockerfile-test-nginx | 3 + test/ec_key_256.pem | 5 ++ test/ec_key_384.pem | 6 ++ test/ec_key_521.pem | 8 ++ test/etc/nginx/conf.d/test.conf | 123 ++++++++++++++++++++++++++++++ test/etc/nginx/ec_key_256-pub.pem | 4 + test/etc/nginx/ec_key_384-pub.pem | 5 ++ test/etc/nginx/ec_key_521-pub.pem | 6 ++ test/test.sh | 39 ++++++++++ 10 files changed, 202 insertions(+), 3 deletions(-) create mode 100644 test/ec_key_256.pem create mode 100644 test/ec_key_384.pem create mode 100644 test/ec_key_521.pem create mode 100644 test/etc/nginx/ec_key_256-pub.pem create mode 100644 test/etc/nginx/ec_key_384-pub.pem create mode 100644 test/etc/nginx/ec_key_521-pub.pem diff --git a/src/ngx_http_auth_jwt_module.c b/src/ngx_http_auth_jwt_module.c index edef14a..85a646d 100644 --- a/src/ngx_http_auth_jwt_module.c +++ b/src/ngx_http_auth_jwt_module.c @@ -337,7 +337,7 @@ static ngx_int_t handle_request(ngx_http_request_t *r) return redirect(r, jwtcf); } } - else if (algorithm.len == 5 && ngx_strncmp(algorithm.data, "RS", 2) == 0) + else if (algorithm.len == 5 && (ngx_strncmp(algorithm.data, "RS", 2) == 0 || ngx_strncmp(algorithm.data, "ES", 2) == 0)) { if (jwtcf->use_keyfile == 1) { @@ -394,7 +394,7 @@ 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) + 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; } @@ -633,7 +633,7 @@ static char *get_jwt(ngx_http_request_t *r, ngx_str_t jwt_location) if (ngx_strncmp(jwtHeaderVal->value.data, BEARER_PREFIX, strlen(BEARER_PREFIX)) == 0) { ngx_str_t jwtHeaderValWithoutBearer = jwtHeaderVal->value; - + jwtHeaderValWithoutBearer.data += strlen(BEARER_PREFIX); jwtHeaderValWithoutBearer.len -= strlen(BEARER_PREFIX); diff --git a/test/Dockerfile-test-nginx b/test/Dockerfile-test-nginx index b70ca9e..5f01436 100644 --- a/test/Dockerfile-test-nginx +++ b/test/Dockerfile-test-nginx @@ -9,4 +9,7 @@ RUN echo "Config Hash: ${CONFIG_HASH}" COPY /docker-entrypoint.d/* /docker-entrypoint.d/ COPY /etc/nginx/conf.d/test.conf /etc/nginx/conf.d/test.conf COPY /etc/nginx/rsa_key_2048-pub.pem /etc/nginx/rsa-key.conf +COPY /etc/nginx/ec_key_256-pub.pem /etc/nginx/ec-256-key.conf +COPY /etc/nginx/ec_key_384-pub.pem /etc/nginx/ec-384-key.conf +COPY /etc/nginx/ec_key_521-pub.pem /etc/nginx/ec-521-key.conf RUN sed -i "s|%{PORT}|${PORT}|" /etc/nginx/conf.d/test.conf diff --git a/test/ec_key_256.pem b/test/ec_key_256.pem new file mode 100644 index 0000000..4206969 --- /dev/null +++ b/test/ec_key_256.pem @@ -0,0 +1,5 @@ +-----BEGIN PRIVATE KEY----- +MIGHAgEAMBMGByqGSM49AgEGCCqGSM49AwEHBG0wawIBAQQgOlEBGcZxxhv8FkN0 +YIvax6fnhJbMeotzIEBxIglkNu6hRANCAATP1NpDzvZmKd2Mw6hIrv4nzUfNu7OK +mT5VuL5LhvUgzTqVGuxwevA7DlFsNVSfCljIBG3geio3fcd4k0Z9SygL +-----END PRIVATE KEY----- diff --git a/test/ec_key_384.pem b/test/ec_key_384.pem new file mode 100644 index 0000000..2aa5780 --- /dev/null +++ b/test/ec_key_384.pem @@ -0,0 +1,6 @@ +-----BEGIN PRIVATE KEY----- +MIG2AgEAMBAGByqGSM49AgEGBSuBBAAiBIGeMIGbAgEBBDADyrL6llSQoQOZ/PF/ +l761kAbrTwn4vu30Kr34ScW6bRKVXLq3cT3QssJ1nF9B63qhZANiAAQ48dOfIEd3 +0TCVE0JT4ZU0Db7Ftz+ex7lojP7uqTY9OI59yoMB01zUN4JK30BRXS9Yv0A9Bu1z +fgLu93FSn0kd0zIPMvuu5LUt60M/miSt2lA0OrqFhKjx6FFdN/lNh64= +-----END PRIVATE KEY----- diff --git a/test/ec_key_521.pem b/test/ec_key_521.pem new file mode 100644 index 0000000..10471dc --- /dev/null +++ b/test/ec_key_521.pem @@ -0,0 +1,8 @@ +-----BEGIN PRIVATE KEY----- +MIHuAgEAMBAGByqGSM49AgEGBSuBBAAjBIHWMIHTAgEBBEIAKkag6aVn4XAbaALo +0b3pypdP5RBX7uKxHmKlkNCcpA0oVTdgjnM5NpJP8ZOM6NjVhEzsn6c/Tdn8hL8w +SI55hFWhgYkDgYYABABpTipSvbs8fq44u4fA+v7DTNYViA58sqbrxjxdzwWZ8eEj +CXsH7yzSGx3Y19NSyrX8HbjWmrj5uxiKeFCB8mGzTwDcFIKCMeMkHjZs/fmVOumR +a2XSpj7BP6wqcN6Pf+UqECivGAZGRHoabo/dm5zF9M3gO+G9eOrf3G1wgFFM7Vzb +Ow== +-----END PRIVATE KEY----- diff --git a/test/etc/nginx/conf.d/test.conf b/test/etc/nginx/conf.d/test.conf index 00c990b..3421b5b 100644 --- a/test/etc/nginx/conf.d/test.conf +++ b/test/etc/nginx/conf.d/test.conf @@ -72,6 +72,51 @@ server { 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; @@ -119,6 +164,48 @@ BwIDAQAB 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; @@ -155,6 +242,42 @@ BwIDAQAB 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-256-key.conf"; + + 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-384-key.conf"; + + 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-521-key.conf"; + + alias /usr/share/nginx/html/; + try_files index.html =404; + } + location /secure/custom-header/hs256 { auth_jwt_enabled on; auth_jwt_redirect on; diff --git a/test/etc/nginx/ec_key_256-pub.pem b/test/etc/nginx/ec_key_256-pub.pem new file mode 100644 index 0000000..3306ea0 --- /dev/null +++ b/test/etc/nginx/ec_key_256-pub.pem @@ -0,0 +1,4 @@ +-----BEGIN PUBLIC KEY----- +MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEz9TaQ872ZindjMOoSK7+J81Hzbuz +ipk+Vbi+S4b1IM06lRrscHrwOw5RbDVUnwpYyARt4HoqN33HeJNGfUsoCw== +-----END PUBLIC KEY----- diff --git a/test/etc/nginx/ec_key_384-pub.pem b/test/etc/nginx/ec_key_384-pub.pem new file mode 100644 index 0000000..e642ed1 --- /dev/null +++ b/test/etc/nginx/ec_key_384-pub.pem @@ -0,0 +1,5 @@ +-----BEGIN PUBLIC KEY----- +MHYwEAYHKoZIzj0CAQYFK4EEACIDYgAEOPHTnyBHd9EwlRNCU+GVNA2+xbc/nse5 +aIz+7qk2PTiOfcqDAdNc1DeCSt9AUV0vWL9APQbtc34C7vdxUp9JHdMyDzL7ruS1 +LetDP5okrdpQNDq6hYSo8ehRXTf5TYeu +-----END PUBLIC KEY----- diff --git a/test/etc/nginx/ec_key_521-pub.pem b/test/etc/nginx/ec_key_521-pub.pem new file mode 100644 index 0000000..0cb875c --- /dev/null +++ b/test/etc/nginx/ec_key_521-pub.pem @@ -0,0 +1,6 @@ +-----BEGIN PUBLIC KEY----- +MIGbMBAGByqGSM49AgEGBSuBBAAjA4GGAAQAaU4qUr27PH6uOLuHwPr+w0zWFYgO +fLKm68Y8Xc8FmfHhIwl7B+8s0hsd2NfTUsq1/B241pq4+bsYinhQgfJhs08A3BSC +gjHjJB42bP35lTrpkWtl0qY+wT+sKnDej3/lKhAorxgGRkR6Gm6P3ZucxfTN4Dvh +vXjq39xtcIBRTO1c2zs= +-----END PUBLIC KEY----- diff --git a/test/test.sh b/test/test.sh index 29a0bf3..5671b8f 100755 --- a/test/test.sh +++ b/test/test.sh @@ -100,6 +100,10 @@ main() { 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 '/' \ @@ -173,6 +177,21 @@ main() { -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' \ @@ -193,6 +212,26 @@ main() { -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' \ From 03d95531d14afea1a2acbd023fd1cd8ae234881a Mon Sep 17 00:00:00 2001 From: Josh McCullough Date: Tue, 19 Mar 2024 14:32:57 -0400 Subject: [PATCH 48/75] build custom SSL images; add SSL tests (#126) --- .../workflows/{ci.yml => make-releases.yml} | 27 +-- Dockerfile | 59 ------- nginx.dockerfile | 136 +++++++++++++++ openssl.dockerfile | 37 ++++ scripts.sh | 165 ++++++++++++------ test/Dockerfile-test-nginx | 15 -- test/Dockerfile-test-runner | 13 -- test/docker-compose-test.yml | 13 +- test/docker-entrypoint.d/10-nginx-test.sh | 1 - test/etc/nginx/conf.d/test.conf | 19 +- test/etc/nginx/test.crt | 23 +++ test/etc/nginx/test.key | 28 +++ test/test-nginx.dockerfile | 18 ++ test/test-runner.dockerfile | 16 ++ test/test.sh | 64 ++++--- 15 files changed, 442 insertions(+), 192 deletions(-) rename .github/workflows/{ci.yml => make-releases.yml} (89%) delete mode 100644 Dockerfile create mode 100644 nginx.dockerfile create mode 100644 openssl.dockerfile delete mode 100644 test/Dockerfile-test-nginx delete mode 100644 test/Dockerfile-test-runner delete mode 100755 test/docker-entrypoint.d/10-nginx-test.sh create mode 100644 test/etc/nginx/test.crt create mode 100644 test/etc/nginx/test.key create mode 100644 test/test-nginx.dockerfile create mode 100644 test/test-runner.dockerfile diff --git a/.github/workflows/ci.yml b/.github/workflows/make-releases.yml similarity index 89% rename from .github/workflows/ci.yml rename to .github/workflows/make-releases.yml index 3c31403..037823b 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/make-releases.yml @@ -3,12 +3,12 @@ name: CI on: push: branches: - - 'master' + - master paths: - src/** pull_request: branches: - - 'master' + - master paths: - src/** workflow_dispatch: @@ -18,8 +18,9 @@ jobs: name: "NGINX: ${{ matrix.nginx-version }}; libjwt: ${{ matrix.libjwt-version }}" strategy: matrix: - # Each nginx version to build against + # NGINX versions to build/test against nginx-version: ['1.20.2', '1.22.1', '1.24.0', '1.25.3'] + # The following versions of libjwt are compatible: # * v1.0 - v1.12.0 # * v1.12.1 - v1.14.0 @@ -27,15 +28,16 @@ jobs: # At the time of writing this: # * Debian and Ubuntu's repos have v1.10.2 # * EPEL has v1.12.1 - # This compilles against each version prior to a breaking change and the latest release + # This compiles against each version prior to a breaking change and the latest release libjwt-version: ['1.12.0', '1.14.0', '1.15.3'] runs-on: ubuntu-latest steps: - - name: Checkout code + - name: Checkout Code uses: actions/checkout@v3 with: path: 'ngx-http-auth-jwt-module' + # TODO cache the build result so we don't have to do this every time? - name: Download jansson uses: actions/checkout@v3 with: @@ -50,7 +52,8 @@ jobs: make && \ make check && \ sudo make install - + + # TODO cache the build result so we don't have to do this every time? - name: Download libjwt uses: actions/checkout@v3 with: @@ -71,20 +74,22 @@ jobs: mkdir nginx curl -O http://nginx.org/download/nginx-${{matrix.nginx-version}}.tar.gz tar -xzf nginx-${{matrix.nginx-version}}.tar.gz --strip-components 1 -C nginx - - - name: Run configure + + - name: Configure NGINX working-directory: ./nginx run: | BUILD_FLAGS='' MAJ=$(echo ${{matrix.nginx-version}} | cut -f1 -d.) MIN=$(echo ${{matrix.nginx-version}} | cut -f2 -d.) REV=$(echo ${{matrix.nginx-version}} | cut -f3 -d.) + if [ "${MAJ}" -gt 1 ] || [ "${MAJ}" -eq 1 -a "${MIN}" -ge 23 ]; then - BUILD_FLAGS="${BUILD_FLAGS} --with-cc-opt='-DNGX_LINKED_LIST_COOKIES=1'" + BUILD_FLAGS="${BUILD_FLAGS} --with-cc-opt='-DNGX_LINKED_LIST_COOKIES=1'" fi - ./configure --with-compat --add-dynamic-module=../ngx-http-auth-jwt-module ${BUILD_FLAGS} - - name: Run make + ./configure --with-compat --add-dynamic-module=../ngx-http-auth-jwt-module ${BUILD_FLAGS} + + - name: Make Modules working-directory: ./nginx run: make modules diff --git a/Dockerfile b/Dockerfile deleted file mode 100644 index 4f2db13..0000000 --- a/Dockerfile +++ /dev/null @@ -1,59 +0,0 @@ -ARG NGINX_VERSION -ARG SOURCE_HASH - - -FROM debian:bullseye-slim as ngx_http_auth_jwt_builder_base -LABEL stage=ngx_http_auth_jwt_builder -RUN <<` -apt-get update -apt-get install -y curl build-essential -` - - -FROM ngx_http_auth_jwt_builder_base as ngx_http_auth_jwt_builder_module -LABEL stage=ngx_http_auth_jwt_builder -ENV LD_LIBRARY_PATH=/usr/local/lib -ARG NGINX_VERSION -RUN <<` -apt-get install -y libjwt-dev libjwt0 libjansson-dev libjansson4 libpcre2-dev zlib1g-dev libpcre3-dev -mkdir -p /root/build/ngx-http-auth-jwt-module -` -WORKDIR /root/build/ngx-http-auth-jwt-module -ARG SOURCE_HASH -RUN echo "Source Hash: ${SOURCE_HASH}" -ADD config ./ -ADD src/*.h src/*.c ./src/ -WORKDIR /root/build -RUN <<` -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 <<` -BUILD_FLAGS='' -MAJ=$(echo ${NGINX_VERSION} | cut -f1 -d.) -MIN=$(echo ${NGINX_VERSION} | cut -f2 -d.) -REV=$(echo ${NGINX_VERSION} | cut -f3 -d.) - -# NGINX 1.23.0+ changes cookies to use a linked list, and renames `cookies` to `cookie` -if [ "${MAJ}" -gt 1 ] || [ "${MAJ}" -eq 1 -a "${MIN}" -ge 23 ]; then - BUILD_FLAGS="${BUILD_FLAGS} --with-cc-opt='-DNGX_LINKED_LIST_COOKIES=1'" -fi - -./configure --with-compat --add-dynamic-module=../ngx-http-auth-jwt-module ${BUILD_FLAGS} -make modules -` - - -FROM nginx:${NGINX_VERSION} AS ngx_http_auth_jwt_builder_nginx -LABEL stage= -RUN rm /docker-entrypoint.d/10-listen-on-ipv6-by-default.sh /etc/nginx/conf.d/default.conf -RUN <<` -apt-get update -apt-get -y install libjansson4 libjwt0 -cd /etc/nginx -sed -ri '/pid\s+\/var\/run\/nginx\.pid;$/a load_module \/usr\/lib64\/nginx\/modules\/ngx_http_auth_jwt_module\.so;' nginx.conf -` -LABEL maintainer="TeslaGov" email="developers@teslagov.com" -COPY --from=ngx_http_auth_jwt_builder_module /root/build/nginx/objs/ngx_http_auth_jwt_module.so /usr/lib64/nginx/modules/ diff --git a/nginx.dockerfile b/nginx.dockerfile new file mode 100644 index 0000000..21c7460 --- /dev/null +++ b/nginx.dockerfile @@ -0,0 +1,136 @@ +ARG BASE_IMAGE +ARG NGINX_VERSION + + +FROM ${BASE_IMAGE} as ngx_http_auth_jwt_builder_base +LABEL stage=ngx_http_auth_jwt_builder +RUN <<` +apt-get update +apt-get install -y curl build-essential +` + + +FROM ngx_http_auth_jwt_builder_base as ngx_http_auth_jwt_builder_module +LABEL stage=ngx_http_auth_jwt_builder +ENV PATH "${PATH}:/etc/nginx" +ENV LD_LIBRARY_PATH=/usr/local/lib +ARG NGINX_VERSION +RUN <<` + set -e + apt-get install -y libjwt-dev libjwt0 libjansson-dev libjansson4 libpcre2-dev zlib1g-dev libpcre3-dev + mkdir -p /root/build/ngx-http-auth-jwt-module +` +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 <<` + set -e + BUILD_FLAGS='' + MAJ=$(echo ${NGINX_VERSION} | cut -f1 -d.) + MIN=$(echo ${NGINX_VERSION} | cut -f2 -d.) + REV=$(echo ${NGINX_VERSION} | cut -f3 -d.) + + # NGINX 1.23.0+ changes cookies to use a linked list, and renames `cookies` to `cookie` + if [ "${MAJ}" -gt 1 ] || [ "${MAJ}" -eq 1 -a "${MIN}" -ge 23 ]; then + BUILD_FLAGS="${BUILD_FLAGS} --with-cc-opt='-DNGX_LINKED_LIST_COOKIES=1'" + fi + + ./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-1.25.4=. -fstack-protector-strong -Wformat -Werror=format-security -Wp,-D_FORTIFY_SOURCE=2 -fPIC' \ + --with-ld-opt='-Wl,-z,relro -Wl,-z,now -Wl,--as-needed -pie' \ + --add-dynamic-module=../ngx-http-auth-jwt-module \ + ${BUILD_FLAGS} + # --with-openssl=/usr/local \ +` +RUN make modules +RUN make install +WORKDIR /usr/lib64/nginx/modules +RUN cp /root/build/nginx/objs/ngx_http_auth_jwt_module.so . +RUN rm -rf /root/build +RUN adduser --system --no-create-home --shell /bin/false --group --disabled-login nginx +RUN mkdir -p /var/cache/nginx /var/log/nginx +WORKDIR /etc/nginx + +FROM ngx_http_auth_jwt_builder_module AS ngx_http_auth_jwt_nginx +LABEL maintainer="TeslaGov" email="developers@teslagov.com" +ARG NGINX_VERSION +RUN <<` + set -e + + apt-get update + apt-get install -y libjansson4 libjwt0 + apt-get clean +` +COPY <<` /etc/nginx/nginx.conf +user nginx; +pid /var/run/nginx.pid; + +load_module /usr/lib64/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; +} +` +ENTRYPOINT ["nginx", "-g", "daemon off;"] diff --git a/openssl.dockerfile b/openssl.dockerfile new file mode 100644 index 0000000..45140cc --- /dev/null +++ b/openssl.dockerfile @@ -0,0 +1,37 @@ +ARG BASE_IMAGE + +FROM ${BASE_IMAGE} +ARG SRC_DIR=/tmp/openssl-src +ARG OUT_DIR=/usr/local/.openssl +ARG SSL_VERSION +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 -e + curl --silent -O https://www.openssl.org/source/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/x86_64-linux-gnu/libssl.so.3 + ln -sf ${OUT_DIR}/lib64/libcrypto.so.3 /lib/x86_64-linux-gnu/libcrypto.so.3 +` +WORKDIR / +#RUN rm -rf ${SRC_DIR} \ No newline at end of file diff --git a/scripts.sh b/scripts.sh index e3fb254..421a4cb 100755 --- a/scripts.sh +++ b/scripts.sh @@ -1,40 +1,72 @@ #!/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_11='3.0.11' +SSL_VERSION_3_2_1='3.2.1' +SSL_VERSIONS=(${SSL_VERSION_3_2_1}) +SSL_VERSION=${SSL_VERSION:-$SSL_VERSION_3_0_11} + +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_11]="bookworm-slim:openssl-${SSL_VERSION_3_0_11}" +SSL_IMAGE_MAP[$SSL_VERSION_3_2_1]="bookworm-slim:openssl-${SSL_VERSION_3_2_1}" + # supported NGINX versions -- for binary distribution -NGINX_VERSION_MAINLINE='1.25.4' +NGINX_VERSION_LEGACY_1='1.20.2' +NGINX_VERSION_LEGACY_2='1.22.1' NGINX_VERSION_STABLE='1.24.0' -NGINX_VERSION_LEGACY_1='1.22.1' -NGINX_VERSION_LEGACY_2='1.20.2' +NGINX_VERSION_MAINLINE='1.25.4' +NGINX_VERSIONS=(${NGINX_VERSION_LEGACY_1} ${NGINX_VERSION_LEGACY_2} ${NGINX_VERSION_STABLE} ${NGINX_VERSION_MAINLINE}) +NGINX_VERSION=${NGINX_VERSION:-${NGINX_VERSION_STABLE}} -export ORG_NAME=${ORG_NAME:-teslagov} -export IMAGE_NAME=${IMAGE_NAME:-jwt-nginx} -export FULL_IMAGE_NAME=${ORG_NAME}/${IMAGE_NAME} -export CONTAINER_NAME_PREFIX=${CONTAINER_NAME_PREFIX:-jwt-nginx-test} -export NGINX_VERSION=${NGINX_VERSION:-${NGINX_VERSION_STABLE}} +IMAGE_NAME=${IMAGE_NAME:-nginx-auth-jwt} +FULL_IMAGE_NAME=${ORG_NAME:-teslagov}/${IMAGE_NAME} + +TEST_CONTAINER_NAME_PREFIX="${IMAGE_NAME}-test" all() { build_module - build_test_runner - test + build_test + test_all +} + +verify_and_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 base image for SSL ${SSL_VERSION}...${NC}\n" + docker image build \ + --build-arg BASE_IMAGE=debian:${baseImage} \ + --build-arg SSL_VERSION=${SSL_VERSION} \ + -f openssl.dockerfile \ + -t ${image} . + fi } build_module() { local dockerArgs=${1:-} - local sourceHash=$(get_hash config src/*) - - printf "${BLUE}Pulling images...${NC}\n" - docker image pull debian:bullseye-slim - docker image pull nginx:${NGINX_VERSION} + local baseImage=${SSL_IMAGE_MAP[$SSL_VERSION]} + + verify_and_build_base_image - printf "${BLUE}Building module for NGINX ${NGINX_VERSION}...${NC}\n" - docker image build -t ${FULL_IMAGE_NAME}:latest -t ${FULL_IMAGE_NAME}:${NGINX_VERSION} ${dockerArgs} \ + printf "${MAGENTA}Building module for NGINX ${NGINX_VERSION}...${NC}\n" + docker image build \ + -f nginx.dockerfile \ + -t ${FULL_IMAGE_NAME}:${NGINX_VERSION} \ + --build-arg BASE_IMAGE=${baseImage} \ --build-arg NGINX_VERSION=${NGINX_VERSION} \ - --build-arg SOURCE_HASH=${sourceHash} . + ${dockerArgs} . if [ "$?" -ne 0 ]; then printf "${RED}✘ Build failed ${NC}\n" @@ -55,7 +87,7 @@ clean_module() { start_nginx() { local port=$(get_port) - printf "${BLUE}Starting NGINX container (${IMAGE_NAME}) on port ${port}...${NC}\n" + 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} >/dev/null } @@ -72,7 +104,7 @@ cp_bin() { stopContainer=1 fi - printf "${BLUE}Copying binaries to: ${destDir}${NC}\n" + printf "${MAGENTA}Copying binaries to: ${destDir}${NC}\n" rm -rf ${destDir}/* mkdir -p ${destDir} docker exec "${IMAGE_NAME}" sh -c "cd /; tar -chf - \ @@ -81,7 +113,7 @@ cp_bin() { usr/lib/x86_64-linux-gnu/libjwt.*" | tar -xf - -C ${destDir} &>/dev/null if [ $stopContainer ]; then - printf "${BLUE}Stopping NGINX container (${IMAGE_NAME})...${NC}\n" + printf "${MAGENTA}Stopping NGINX container (${IMAGE_NAME})...${NC}\n" stop_nginx fi } @@ -93,7 +125,7 @@ make_release() { NGINX_VERSION=${2} - printf "${BLUE}Making release for version ${moduleVersion} for NGINX ${NGINX_VERSION}...${NC}\n" + printf "${MAGENTA}Making release for version ${moduleVersion} for NGINX ${NGINX_VERSION}...${NC}\n" rebuild_module rebuild_test_runner @@ -110,61 +142,88 @@ make_release() { # See: https://nginx.org/en/download.html make_releases() { local moduleVersion=$(git describe --tags --abbrev=0) - local nginxVersions=(${NGINX_VERSION_MAINLINE} ${NGINX_VERSION_STABLE} ${NGINX_VERSION_LEGACY_1} ${NGINX_VERSION_LEGACY_2}) rm -rf release/* - for v in ${nginxVersions[@]}; do + for v in ${NGINX_VERSIONS[@]}; do make_release ${moduleVersion} ${v} done } - -build_test_runner() { +build_test() { local dockerArgs=${1:-} - local configHash=$(get_hash $(find test -type f -not -name 'test.sh' -not -name '*.yml' -not -name 'Dockerfile*')) - local sourceHash=$(get_hash test/test.sh) local port=$(get_port) + local sslPort=$(get_port $((port + 1))) + local runnerBaseImage=${SSL_IMAGE_MAP[$SSL_VERSION]} - printf "${BLUE}Building test runner using port ${port}...${NC}\n" - docker compose -f ./test/docker-compose-test.yml build ${dockerArgs} \ - --build-arg CONFIG_HASH=${configHash}\ - --build-arg SOURCE_HASH=${sourceHash} \ - --build-arg PORT=${port} + 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/docker-compose-test.yml build \ + --build-arg RUNNER_BASE_IMAGE=${runnerBaseImage} \ + --build-arg PORT=${port} \ + --build-arg SSL_PORT=${sslPort} \ + ${dockerArgs} } -rebuild_test_runner() { - build_test_runner --no-cache +rebuild_test() { + build_test --no-cache +} + +test_all() { + for SSL_VERSION in "${SSL_VERSIONS[@]}"; do + for NGINX_VERSION in "${NGINX_VERSIONS[@]}"; do + test + done + done } test() { - build_test_runner + build_module + build_test - printf "${BLUE}Running tests...${NC}\n" - docker compose -f ./test/docker-compose-test.yml up --no-start - docker start ${CONTAINER_NAME_PREFIX} - - if [ "$(docker container inspect -f '{{.State.Running}}' ${CONTAINER_NAME_PREFIX})" != "true" ]; then - printf "${RED}Failed to start NGINX test container. See logs below:\n" - docker logs ${CONTAINER_NAME_PREFIX} - printf "${NC}\n" - else - test_now - fi + printf "${MAGENTA}Running tests...${NC}\n" + docker compose \ + -p ${TEST_CONTAINER_NAME_PREFIX} \ + -f ./test/docker-compose-test.yml up \ + --no-start + + + trap 'docker compose -f ./test/docker-compose-test.yml down' 0 - docker compose -f ./test/docker-compose-test.yml down + test_now } test_now() { - docker start -a ${CONTAINER_NAME_PREFIX}-runner -} + nginxContainerName="${TEST_CONTAINER_NAME_PREFIX}-nginx" + runnerContainerName="${TEST_CONTAINER_NAME_PREFIX}-runner" -get_hash() { - sha1sum $@ | sed -E 's|\s+|:|' | tr '\n' ' ' | sha1sum | head -c 40 + docker start ${nginxContainerName} + + if [ "$(docker container inspect -f '{{.State.Running}}' ${nginxContainerName})" != "true" ]; then + printf "${RED}Failed to start container \"${nginxContainerName}\". See logs below:\n" + docker logs ${nginxContainerName} + printf "${NC}\n" + return + fi + + docker start -a ${runnerContainerName} + + echo + echo "Tests were executed with the following options:" + echo " SSL Version: ${SSL_VERSION}" + echo " NGINX Version: ${NGINX_VERSION}" } get_port() { - for p in $(seq 8000 8100); do + startPort=${1:-8000} + endPort=$((startPort + 100)) + + for p in $(seq ${startPort} ${endPort}); do if ! ss -ln | grep -q ":${p} "; then echo ${p} break diff --git a/test/Dockerfile-test-nginx b/test/Dockerfile-test-nginx deleted file mode 100644 index 5f01436..0000000 --- a/test/Dockerfile-test-nginx +++ /dev/null @@ -1,15 +0,0 @@ -ARG BASE_IMAGE -ARG CONFIG_HASH -ARG PORT - -FROM ${BASE_IMAGE} as NGINX -ARG CONFIG_HASH -ARG PORT -RUN echo "Config Hash: ${CONFIG_HASH}" -COPY /docker-entrypoint.d/* /docker-entrypoint.d/ -COPY /etc/nginx/conf.d/test.conf /etc/nginx/conf.d/test.conf -COPY /etc/nginx/rsa_key_2048-pub.pem /etc/nginx/rsa-key.conf -COPY /etc/nginx/ec_key_256-pub.pem /etc/nginx/ec-256-key.conf -COPY /etc/nginx/ec_key_384-pub.pem /etc/nginx/ec-384-key.conf -COPY /etc/nginx/ec_key_521-pub.pem /etc/nginx/ec-521-key.conf -RUN sed -i "s|%{PORT}|${PORT}|" /etc/nginx/conf.d/test.conf diff --git a/test/Dockerfile-test-runner b/test/Dockerfile-test-runner deleted file mode 100644 index c8cbff2..0000000 --- a/test/Dockerfile-test-runner +++ /dev/null @@ -1,13 +0,0 @@ -ARG SOURCE_HASH -ARG PORT - -FROM alpine:3.7 AS test-base -RUN apk add curl bash - -FROM test-base AS test -ARG SOURCE_HASH -ARG PORT -ENV PORT=${PORT} -RUN echo "Source Hash: ${SOURCE_HASH}" -COPY test.sh . -CMD ./test.sh ${PORT} diff --git a/test/docker-compose-test.yml b/test/docker-compose-test.yml index eff2460..3c0e9be 100644 --- a/test/docker-compose-test.yml +++ b/test/docker-compose-test.yml @@ -3,23 +3,20 @@ version: '3.3' services: nginx: - container_name: ${CONTAINER_NAME_PREFIX} + container_name: ${TEST_CONTAINER_NAME_PREFIX}-nginx build: context: . - dockerfile: Dockerfile-test-nginx + dockerfile: test-nginx.dockerfile args: - BASE_IMAGE: ${FULL_IMAGE_NAME}:${NGINX_VERSION:-latest} - command: [nginx-debug, '-g', 'daemon off;'] + BASE_IMAGE: ${FULL_IMAGE_NAME}:${NGINX_VERSION} logging: driver: ${LOG_DRIVER:-journald} runner: - container_name: ${CONTAINER_NAME_PREFIX}-runner + container_name: ${TEST_CONTAINER_NAME_PREFIX}-runner build: context: . - dockerfile: Dockerfile-test-runner - environment: - BASE_IMAGE: ${FULL_IMAGE_NAME}:${NGINX_VERSION:-latest} + dockerfile: test-runner.dockerfile depends_on: - nginx \ No newline at end of file diff --git a/test/docker-entrypoint.d/10-nginx-test.sh b/test/docker-entrypoint.d/10-nginx-test.sh deleted file mode 100755 index 0bf8791..0000000 --- a/test/docker-entrypoint.d/10-nginx-test.sh +++ /dev/null @@ -1 +0,0 @@ -nginx -t \ No newline at end of file diff --git a/test/etc/nginx/conf.d/test.conf b/test/etc/nginx/conf.d/test.conf index 3421b5b..229d545 100644 --- a/test/etc/nginx/conf.d/test.conf +++ b/test/etc/nginx/conf.d/test.conf @@ -3,7 +3,13 @@ access_log /var/log/nginx/access.log; 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"; @@ -212,7 +218,7 @@ vXjq39xtcIBRTO1c2zs= auth_jwt_location HEADER=Authorization; auth_jwt_algorithm RS256; auth_jwt_use_keyfile on; - auth_jwt_keyfile_path "/etc/nginx/rsa-key.conf"; + auth_jwt_keyfile_path "/etc/nginx/rsa_key_2048-pub.pem"; alias /usr/share/nginx/html/; try_files index.html =404; @@ -224,7 +230,7 @@ vXjq39xtcIBRTO1c2zs= auth_jwt_location HEADER=Authorization; auth_jwt_algorithm RS384; auth_jwt_use_keyfile on; - auth_jwt_keyfile_path "/etc/nginx/rsa-key.conf"; + auth_jwt_keyfile_path "/etc/nginx/rsa_key_2048-pub.pem"; alias /usr/share/nginx/html/; try_files index.html =404; @@ -236,7 +242,7 @@ vXjq39xtcIBRTO1c2zs= auth_jwt_location HEADER=Authorization; auth_jwt_algorithm RS512; auth_jwt_use_keyfile on; - auth_jwt_keyfile_path "/etc/nginx/rsa-key.conf"; + auth_jwt_keyfile_path "/etc/nginx/rsa_key_2048-pub.pem"; alias /usr/share/nginx/html/; try_files index.html =404; @@ -248,7 +254,7 @@ vXjq39xtcIBRTO1c2zs= auth_jwt_location HEADER=Authorization; auth_jwt_algorithm ES256; auth_jwt_use_keyfile on; - auth_jwt_keyfile_path "/etc/nginx/ec-256-key.conf"; + auth_jwt_keyfile_path "/etc/nginx/ec_key_256-pub.pem"; alias /usr/share/nginx/html/; try_files index.html =404; @@ -260,7 +266,7 @@ vXjq39xtcIBRTO1c2zs= auth_jwt_location HEADER=Authorization; auth_jwt_algorithm ES384; auth_jwt_use_keyfile on; - auth_jwt_keyfile_path "/etc/nginx/ec-384-key.conf"; + auth_jwt_keyfile_path "/etc/nginx/ec_key_384-pub.pem"; alias /usr/share/nginx/html/; try_files index.html =404; @@ -272,7 +278,7 @@ vXjq39xtcIBRTO1c2zs= auth_jwt_location HEADER=Authorization; auth_jwt_algorithm ES512; auth_jwt_use_keyfile on; - auth_jwt_keyfile_path "/etc/nginx/ec-521-key.conf"; + auth_jwt_keyfile_path "/etc/nginx/ec_key_521-pub.pem"; alias /usr/share/nginx/html/; try_files index.html =404; @@ -390,4 +396,3 @@ vXjq39xtcIBRTO1c2zs= } } } - diff --git a/test/etc/nginx/test.crt b/test/etc/nginx/test.crt new file mode 100644 index 0000000..fb406ba --- /dev/null +++ b/test/etc/nginx/test.crt @@ -0,0 +1,23 @@ +-----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----- diff --git a/test/etc/nginx/test.key b/test/etc/nginx/test.key new file mode 100644 index 0000000..13ec754 --- /dev/null +++ b/test/etc/nginx/test.key @@ -0,0 +1,28 @@ +-----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----- diff --git a/test/test-nginx.dockerfile b/test/test-nginx.dockerfile new file mode 100644 index 0000000..c4a6104 --- /dev/null +++ b/test/test-nginx.dockerfile @@ -0,0 +1,18 @@ +ARG BASE_IMAGE +ARG PORT +ARG SSL_PORT + +FROM ${BASE_IMAGE} as NGINX +ARG PORT +ARG SSL_PORT +COPY etc/ /etc/ +RUN sed -i "s|%{PORT}|${PORT}|" /etc/nginx/conf.d/test.conf +RUN sed -i "s|%{SSL_PORT}|${SSL_PORT}|" /etc/nginx/conf.d/test.conf +COPY <<` /usr/share/nginx/html/index.html + + Test + +

NGINX Auth-JWT Module Test

+ + +` diff --git a/test/test-runner.dockerfile b/test/test-runner.dockerfile new file mode 100644 index 0000000..0aca095 --- /dev/null +++ b/test/test-runner.dockerfile @@ -0,0 +1,16 @@ +ARG RUNNER_BASE_IMAGE +ARG PORT +ARG SSL_PORT + +FROM ${RUNNER_BASE_IMAGE} +ARG PORT +ARG SSL_PORT +ENV PORT=${PORT} +ENV SSL_PORT=${SSL_PORT} +RUN <<` + set -e + apt-get update + apt-get install -y curl bash +` +COPY test.sh . +CMD ./test.sh ${PORT} ${SSL_PORT} diff --git a/test/test.sh b/test/test.sh index 5671b8f..f54e0de 100755 --- a/test/test.sh +++ b/test/test.sh @@ -1,7 +1,6 @@ -#!/bin/bash -u +#!/bin/bash -eu # set a test # here to execute only that test and output additional info -PORT=${1:-8000} DEBUG= RED='\e[31m' @@ -18,35 +17,40 @@ run_test () { if [ "${DEBUG}" == '' ] || [ ${DEBUG} == ${NUM_TESTS} ]; then local OPTIND; - local name='' - local path='' - local expectedCode='' - local expectedResponseRegex='' - local extraCurlOpts='' - local curlCommand='' - local exitCode='' - local response='' + 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:p:r:c:x:" option; do + while getopts "n:sp:r:c:x:" option; do case $option in - n) - name=$OPTARG;; - p) - path=$OPTARG;; - c) - expectedCode=$OPTARG;; - r) - expectedResponseRegex=$OPTARG;; - x) - extraCurlOpts=$OPTARG;; - \?) # Invalid option - printf "Error: Invalid option\n"; - exit;; + 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 - curlCommand="curl -s -v http://nginx:${PORT}${path} -H 'Cache-Control: no-cache' ${extraCurlOpts} 2>&1" + curlCommand="curl -skv ${scheme}://nginx:${port}${path} -H 'Cache-Control: no-cache' ${extraCurlOpts} 2>&1" response=$(eval "${curlCommand}") exitCode=$? @@ -108,10 +112,20 @@ main() { run_test -n 'when auth disabled, should return 200' \ -p '/' \ -c '200' + + run_test -s \ + -n '[SSL] when auth disabled, should return 200' \ + -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' \ From 02f4e17eb84dd2de78d5cedca8f0b4f8247470e3 Mon Sep 17 00:00:00 2001 From: Josh McCullough Date: Wed, 28 Aug 2024 12:12:07 -0400 Subject: [PATCH 49/75] case-insenitive Bearer check #134 (#135) --- openssl.dockerfile | 14 +++++++------- scripts.sh | 6 +++--- src/ngx_http_auth_jwt_module.c | 2 +- test/docker-compose-test.yml | 2 -- test/test.sh | 6 ++++++ 5 files changed, 17 insertions(+), 13 deletions(-) diff --git a/openssl.dockerfile b/openssl.dockerfile index 45140cc..d8bb293 100644 --- a/openssl.dockerfile +++ b/openssl.dockerfile @@ -1,9 +1,9 @@ -ARG BASE_IMAGE +ARG BASE_IMAGE=debian:bookworm-slim FROM ${BASE_IMAGE} -ARG SRC_DIR=/tmp/openssl-src -ARG OUT_DIR=/usr/local/.openssl -ARG SSL_VERSION +ARG SSL_VERSION=3.2.1 +ENV SRC_DIR=/tmp/openssl-src +ENV OUT_DIR=/usr/local/.openssl RUN <<` set -e apt-get update @@ -13,8 +13,8 @@ RUN <<` ` WORKDIR ${SRC_DIR} RUN <<` - set -e - curl --silent -O https://www.openssl.org/source/openssl-${SSL_VERSION}.tar.gz + set -ex + curl --silent -LO https://www.openssl.org/source/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 @@ -34,4 +34,4 @@ RUN <<` ln -sf ${OUT_DIR}/lib64/libcrypto.so.3 /lib/x86_64-linux-gnu/libcrypto.so.3 ` WORKDIR / -#RUN rm -rf ${SRC_DIR} \ No newline at end of file +RUN rm -rf ${SRC_DIR} \ No newline at end of file diff --git a/scripts.sh b/scripts.sh index 421a4cb..6f109d9 100755 --- a/scripts.sh +++ b/scripts.sh @@ -40,13 +40,13 @@ all() { verify_and_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 base image for SSL ${SSL_VERSION}...${NC}\n" - docker image build \ + printf "${MAGENTA}Building ${baseImage} base image for SSL ${SSL_VERSION}...${NC}\n" + docker buildx build \ --build-arg BASE_IMAGE=debian:${baseImage} \ --build-arg SSL_VERSION=${SSL_VERSION} \ -f openssl.dockerfile \ diff --git a/src/ngx_http_auth_jwt_module.c b/src/ngx_http_auth_jwt_module.c index 85a646d..e21560c 100644 --- a/src/ngx_http_auth_jwt_module.c +++ b/src/ngx_http_auth_jwt_module.c @@ -630,7 +630,7 @@ static char *get_jwt(ngx_http_request_t *r, ngx_str_t jwt_location) { static const char *BEARER_PREFIX = "Bearer "; - if (ngx_strncmp(jwtHeaderVal->value.data, BEARER_PREFIX, strlen(BEARER_PREFIX)) == 0) + if (ngx_strncasecmp(jwtHeaderVal->value.data, (u_char *)BEARER_PREFIX, strlen(BEARER_PREFIX)) == 0) { ngx_str_t jwtHeaderValWithoutBearer = jwtHeaderVal->value; diff --git a/test/docker-compose-test.yml b/test/docker-compose-test.yml index 3c0e9be..14c88da 100644 --- a/test/docker-compose-test.yml +++ b/test/docker-compose-test.yml @@ -1,5 +1,3 @@ -version: '3.3' - services: nginx: diff --git a/test/test.sh b/test/test.sh index f54e0de..2bf9cb3 100755 --- a/test/test.sh +++ b/test/test.sh @@ -143,6 +143,12 @@ main() { -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' From 272c02e2302208993df4f99a7f1751a793d16662 Mon Sep 17 00:00:00 2001 From: Josh McCullough Date: Wed, 28 Aug 2024 12:31:26 -0400 Subject: [PATCH 50/75] fix release script --- scripts.sh | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/scripts.sh b/scripts.sh index 6f109d9..f6863f0 100755 --- a/scripts.sh +++ b/scripts.sh @@ -61,7 +61,7 @@ build_module() { verify_and_build_base_image printf "${MAGENTA}Building module for NGINX ${NGINX_VERSION}...${NC}\n" - docker image build \ + docker buildx build \ -f nginx.dockerfile \ -t ${FULL_IMAGE_NAME}:${NGINX_VERSION} \ --build-arg BASE_IMAGE=${baseImage} \ @@ -88,7 +88,7 @@ start_nginx() { 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} >/dev/null + docker run --rm --name "${IMAGE_NAME}" -d -p ${port}:80 ${FULL_IMAGE_NAME}:${NGINX_VERSION} >/dev/null } stop_nginx() { @@ -128,7 +128,7 @@ make_release() { printf "${MAGENTA}Making release for version ${moduleVersion} for NGINX ${NGINX_VERSION}...${NC}\n" rebuild_module - rebuild_test_runner + rebuild_test test cp_bin From c5882b0c2106bc478af174bb36ea956a99038127 Mon Sep 17 00:00:00 2001 From: Josh McCullough Date: Mon, 14 Oct 2024 10:43:53 -0400 Subject: [PATCH 51/75] fix Docker warnings --- nginx.dockerfile | 6 ++---- test/test-nginx.dockerfile | 2 +- 2 files changed, 3 insertions(+), 5 deletions(-) diff --git a/nginx.dockerfile b/nginx.dockerfile index 21c7460..c0e6c02 100644 --- a/nginx.dockerfile +++ b/nginx.dockerfile @@ -1,16 +1,14 @@ ARG BASE_IMAGE ARG NGINX_VERSION - -FROM ${BASE_IMAGE} as ngx_http_auth_jwt_builder_base +FROM ${BASE_IMAGE} AS ngx_http_auth_jwt_builder_base LABEL stage=ngx_http_auth_jwt_builder RUN <<` apt-get update apt-get install -y curl build-essential ` - -FROM ngx_http_auth_jwt_builder_base as ngx_http_auth_jwt_builder_module +FROM ngx_http_auth_jwt_builder_base AS ngx_http_auth_jwt_builder_module LABEL stage=ngx_http_auth_jwt_builder ENV PATH "${PATH}:/etc/nginx" ENV LD_LIBRARY_PATH=/usr/local/lib diff --git a/test/test-nginx.dockerfile b/test/test-nginx.dockerfile index c4a6104..e12acb4 100644 --- a/test/test-nginx.dockerfile +++ b/test/test-nginx.dockerfile @@ -2,7 +2,7 @@ ARG BASE_IMAGE ARG PORT ARG SSL_PORT -FROM ${BASE_IMAGE} as NGINX +FROM ${BASE_IMAGE} AS NGINX ARG PORT ARG SSL_PORT COPY etc/ /etc/ From 867562a318abb5b6997d77300f30b942e7e964ff Mon Sep 17 00:00:00 2001 From: Josh McCullough Date: Mon, 14 Oct 2024 10:44:06 -0400 Subject: [PATCH 52/75] fix /tmp dir perms for containers --- nginx.dockerfile | 1 + openssl.dockerfile | 1 + 2 files changed, 2 insertions(+) diff --git a/nginx.dockerfile b/nginx.dockerfile index c0e6c02..360469b 100644 --- a/nginx.dockerfile +++ b/nginx.dockerfile @@ -3,6 +3,7 @@ ARG NGINX_VERSION FROM ${BASE_IMAGE} AS ngx_http_auth_jwt_builder_base LABEL stage=ngx_http_auth_jwt_builder +RUN chmod 1777 /tmp RUN <<` apt-get update apt-get install -y curl build-essential diff --git a/openssl.dockerfile b/openssl.dockerfile index d8bb293..42f824f 100644 --- a/openssl.dockerfile +++ b/openssl.dockerfile @@ -4,6 +4,7 @@ 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 From b93b816c97937e6a60620165688f937bba8e4f91 Mon Sep 17 00:00:00 2001 From: Josh McCullough Date: Mon, 14 Oct 2024 10:44:19 -0400 Subject: [PATCH 53/75] update NGINX versions to build against --- scripts.sh | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/scripts.sh b/scripts.sh index f6863f0..2ae02d4 100755 --- a/scripts.sh +++ b/scripts.sh @@ -21,9 +21,10 @@ SSL_IMAGE_MAP[$SSL_VERSION_3_2_1]="bookworm-slim:openssl-${SSL_VERSION_3_2_1}" # supported NGINX versions -- for binary distribution NGINX_VERSION_LEGACY_1='1.20.2' NGINX_VERSION_LEGACY_2='1.22.1' -NGINX_VERSION_STABLE='1.24.0' -NGINX_VERSION_MAINLINE='1.25.4' -NGINX_VERSIONS=(${NGINX_VERSION_LEGACY_1} ${NGINX_VERSION_LEGACY_2} ${NGINX_VERSION_STABLE} ${NGINX_VERSION_MAINLINE}) +NGINX_VERSION_LEGACY_3='1.24.0' +NGINX_VERSION_STABLE='1.26.2' +NGINX_VERSION_MAINLINE='1.27.2' +NGINX_VERSIONS=(${NGINX_VERSION_LEGACY_1} ${NGINX_VERSION_LEGACY_2} ${NGINX_VERSION_LEGACY_3} ${NGINX_VERSION_STABLE} ${NGINX_VERSION_MAINLINE}) NGINX_VERSION=${NGINX_VERSION:-${NGINX_VERSION_STABLE}} IMAGE_NAME=${IMAGE_NAME:-nginx-auth-jwt} From e8e60e652a14474dcef39fac95c79385813737a8 Mon Sep 17 00:00:00 2001 From: Josh McCullough Date: Mon, 14 Oct 2024 10:44:53 -0400 Subject: [PATCH 54/75] rm redundant `-e` --- scripts.sh | 2 -- 1 file changed, 2 deletions(-) diff --git a/scripts.sh b/scripts.sh index 2ae02d4..5c9b785 100755 --- a/scripts.sh +++ b/scripts.sh @@ -120,8 +120,6 @@ cp_bin() { } make_release() { - set -e - local moduleVersion=${1} NGINX_VERSION=${2} From 16b0369e8c369809de8bda8004c65b5d36fd2d56 Mon Sep 17 00:00:00 2001 From: Josh McCullough Date: Mon, 14 Oct 2024 10:45:06 -0400 Subject: [PATCH 55/75] update scripts to support arts --- scripts.sh | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/scripts.sh b/scripts.sh index 5c9b785..4b46a85 100755 --- a/scripts.sh +++ b/scripts.sh @@ -233,7 +233,8 @@ get_port() { if [ $# -eq 0 ]; then all else - for fn in "$@"; do - ${fn} - done + fn=$1 + shift + + ${fn} "$@" fi From d8974ebd93539d7961fdc552cf563701961a16a3 Mon Sep 17 00:00:00 2001 From: Josh McCullough Date: Tue, 4 Feb 2025 10:03:35 -0500 Subject: [PATCH 56/75] no releases from PRs --- .github/workflows/make-releases.yml | 5 ----- 1 file changed, 5 deletions(-) diff --git a/.github/workflows/make-releases.yml b/.github/workflows/make-releases.yml index 037823b..3a12f70 100644 --- a/.github/workflows/make-releases.yml +++ b/.github/workflows/make-releases.yml @@ -6,11 +6,6 @@ on: - master paths: - src/** - pull_request: - branches: - - master - paths: - - src/** workflow_dispatch: jobs: From 27fcd3d6fbfb561966d7e03a70000566123aee17 Mon Sep 17 00:00:00 2001 From: Josh McCullough Date: Tue, 4 Feb 2025 10:03:53 -0500 Subject: [PATCH 57/75] update NGINX versions to build against --- .github/workflows/make-releases.yml | 2 +- scripts.sh | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/make-releases.yml b/.github/workflows/make-releases.yml index 3a12f70..fdc5591 100644 --- a/.github/workflows/make-releases.yml +++ b/.github/workflows/make-releases.yml @@ -14,7 +14,7 @@ jobs: strategy: matrix: # NGINX versions to build/test against - nginx-version: ['1.20.2', '1.22.1', '1.24.0', '1.25.3'] + nginx-version: ['1.20.2', '1.22.1', '1.24.0', '1.26.2', '1.27.3'] # The following versions of libjwt are compatible: # * v1.0 - v1.12.0 diff --git a/scripts.sh b/scripts.sh index 4b46a85..af59e9b 100755 --- a/scripts.sh +++ b/scripts.sh @@ -23,7 +23,7 @@ NGINX_VERSION_LEGACY_1='1.20.2' NGINX_VERSION_LEGACY_2='1.22.1' NGINX_VERSION_LEGACY_3='1.24.0' NGINX_VERSION_STABLE='1.26.2' -NGINX_VERSION_MAINLINE='1.27.2' +NGINX_VERSION_MAINLINE='1.27.3' NGINX_VERSIONS=(${NGINX_VERSION_LEGACY_1} ${NGINX_VERSION_LEGACY_2} ${NGINX_VERSION_LEGACY_3} ${NGINX_VERSION_STABLE} ${NGINX_VERSION_MAINLINE}) NGINX_VERSION=${NGINX_VERSION:-${NGINX_VERSION_STABLE}} From 576fe71f0a693d5e380069a316ae5361fe73af30 Mon Sep 17 00:00:00 2001 From: Josh McCullough Date: Tue, 4 Feb 2025 10:04:03 -0500 Subject: [PATCH 58/75] update workflow action version --- .github/workflows/make-releases.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/make-releases.yml b/.github/workflows/make-releases.yml index fdc5591..7316e6b 100644 --- a/.github/workflows/make-releases.yml +++ b/.github/workflows/make-releases.yml @@ -94,7 +94,7 @@ jobs: tar czf ngx_http_auth_jwt_module_libjwt_${{matrix.libjwt-version}}_nginx_${{matrix.nginx-version}}.tgz ngx_http_auth_jwt_module.so - name: Upload build artifact - uses: actions/upload-artifact@v3 + uses: actions/upload-artifact@v4 with: if-no-files-found: error name: ngx_http_auth_jwt_module_libjwt_${{matrix.libjwt-version}}_nginx_${{matrix.nginx-version}}.tgz From d29adbb2ced992961d8c9c01b3dcf162b4c3f851 Mon Sep 17 00:00:00 2001 From: Josh McCullough Date: Tue, 4 Feb 2025 10:15:51 -0500 Subject: [PATCH 59/75] rename start/stop script functions --- scripts.sh | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/scripts.sh b/scripts.sh index af59e9b..edbb186 100755 --- a/scripts.sh +++ b/scripts.sh @@ -85,14 +85,14 @@ clean_module() { docker rmi -f $(docker images --filter=label=stage=ngx_http_auth_jwt_builder --quiet) 2> /dev/null || true } -start_nginx() { +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_nginx() { +stop() { docker stop "${IMAGE_NAME}" >/dev/null } From a774c4208141b77d3144ac1cd296a420fbfe4b85 Mon Sep 17 00:00:00 2001 From: Josh McCullough Date: Tue, 4 Feb 2025 10:16:09 -0500 Subject: [PATCH 60/75] rename scripts.sh --- scripts.sh => scripts | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename scripts.sh => scripts (100%) diff --git a/scripts.sh b/scripts similarity index 100% rename from scripts.sh rename to scripts From 7c9cb00f5e3bf86192c6fc7a2b6a11a706923106 Mon Sep 17 00:00:00 2001 From: Adrian Carreno Date: Tue, 4 Feb 2025 12:30:37 -0300 Subject: [PATCH 61/75] Feature: Add support for ARM64 (#139) --- openssl.dockerfile | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/openssl.dockerfile b/openssl.dockerfile index 42f824f..9839d29 100644 --- a/openssl.dockerfile +++ b/openssl.dockerfile @@ -31,8 +31,8 @@ RUN <<` ldconfig ln -sf ${OUT_DIR}/bin/openssl /usr/bin/openssl - ln -sf ${OUT_DIR}/lib64/libssl.so.3 /lib/x86_64-linux-gnu/libssl.so.3 - ln -sf ${OUT_DIR}/lib64/libcrypto.so.3 /lib/x86_64-linux-gnu/libcrypto.so.3 + 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} \ No newline at end of file From edabc23442653cc82568dcd83b034408e4708815 Mon Sep 17 00:00:00 2001 From: Josh McCullough Date: Tue, 4 Feb 2025 10:33:12 -0500 Subject: [PATCH 62/75] support ARM --- scripts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/scripts b/scripts index edbb186..9d90185 100755 --- a/scripts +++ b/scripts @@ -110,8 +110,8 @@ cp_bin() { mkdir -p ${destDir} docker exec "${IMAGE_NAME}" sh -c "cd /; tar -chf - \ usr/lib64/nginx/modules/ngx_http_auth_jwt_module.so \ - usr/lib/x86_64-linux-gnu/libjansson.so.* \ - usr/lib/x86_64-linux-gnu/libjwt.*" | tar -xf - -C ${destDir} &>/dev/null + usr/lib/$(uname -m)-linux-gnu/libjansson.so.* \ + usr/lib/$(uname -m)-linux-gnu/libjwt.*" | tar -xf - -C ${destDir} &>/dev/null if [ $stopContainer ]; then printf "${MAGENTA}Stopping NGINX container (${IMAGE_NAME})...${NC}\n" From 81a2b445d20518d264cc32fcb51350ccbf8a7214 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Wilson=20J=C3=BAnior?= Date: Tue, 4 Feb 2025 14:14:30 -0300 Subject: [PATCH 63/75] Support extracting claims to NGINX variables (#145) Co-authored-by: Matt Gilham <7717048+mgilham@users.noreply.github.com> Co-authored-by: Josh McCullough Co-authored-by: Josh McCullough --- README.md | 35 +-- src/ngx_http_auth_jwt_module.c | 387 +++++++++++++++++++++++++------- test/etc/nginx/conf.d/test.conf | 44 ++++ test/test.sh | 34 ++- 4 files changed, 393 insertions(+), 107 deletions(-) diff --git a/README.md b/README.md index ba3dde1..95c83af 100644 --- a/README.md +++ b/README.md @@ -14,19 +14,20 @@ This module depends on the [JWT C Library](https://github.com/benmcollins/libjwt This module requires several new `nginx.conf` directives, which can be specified at the `http`, `server`, or `location` levels. -| 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_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. | +| 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 @@ -92,19 +93,19 @@ auth_jwt_validate_sub on; 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_request_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, then use `auth_jwt_extract_response_claims` instead. +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 Request Claims +### 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_request_claims sub; + auth_jwt_extract_var_claims sub; - return 301 /profile/$http_jwt_sub; + return 301 /profile/$jwt_claim_sub; } ``` diff --git a/src/ngx_http_auth_jwt_module.c b/src/ngx_http_auth_jwt_module.c index e21560c..fe428b4 100644 --- a/src/ngx_http_auth_jwt_module.c +++ b/src/ngx_http_auth_jwt_module.c @@ -31,6 +31,7 @@ typedef struct 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; @@ -38,18 +39,28 @@ typedef struct 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 free_jwt_and_redirect(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); @@ -106,6 +117,13 @@ static ngx_command_t auth_jwt_directives[] = { 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, @@ -194,6 +212,7 @@ static void *create_conf(ngx_conf_t *cf) conf->validate_sub = NGX_CONF_UNSET; 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; @@ -213,6 +232,7 @@ static char *merge_conf(ngx_conf_t *cf, void *parent, void *child) 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)); @@ -252,6 +272,108 @@ static char *merge_conf(ngx_conf_t *cf, void *parent, void *child) 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_log_debug(NGX_LOG_DEBUG_HTTP, r->connection->log, 0, "getting jwt value for var index %l", *((ngx_uint_t*) data)); + auth_jwt_ctx_t *ctx = get_request_jwt_ctx(r); + + if (ctx == NULL) + { + ngx_log_debug(NGX_LOG_DEBUG_HTTP, r->connection->log, 0, "no module context found while getting jwt value"); + + return NGX_ERROR; + } + else + { + ngx_uint_t *claim_idx = (ngx_uint_t*) data; + 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; @@ -295,98 +417,169 @@ static char *merge_extract_response_claims(ngx_conf_t *cf, ngx_command_t *cmd, v return merge_extract_claims(cf, claims); } -static ngx_int_t handle_request(ngx_http_request_t *r) +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); - if (!jwtcf->enabled) + if(!jwtcf->enabled) { - return NGX_DECLINED; + return NULL; + } + + 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; + } + + 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 { - // pass through options requests without token authentication - if (r->method == NGX_HTTP_OPTIONS) + 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)) { - return NGX_DECLINED; + 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 + else if (algorithm.len == 5 && (ngx_strncmp(algorithm.data, "RS", 2) == 0 || ngx_strncmp(algorithm.data, "ES", 2) == 0)) { - char *jwtPtr = get_jwt(r, jwtcf->jwt_location); - - if (jwtPtr == NULL) + if (jwtcf->use_keyfile == 1) { - ngx_log_error(NGX_LOG_ERR, r->connection->log, 0, "failed to find a JWT"); - return redirect(r, jwtcf); + keyLength = jwtcf->_keyfile.len; + key = (u_char *)jwtcf->_keyfile.data; } 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); + 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 (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"); - return redirect(r, jwtcf); - } - } - 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); - return redirect(r, jwtcf); - } + 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); + } - if (jwt_decode(&jwt, jwtPtr, key, keyLength) != 0) - { - ngx_log_error(NGX_LOG_ERR, r->connection->log, 0, "failed to parse JWT"); - return redirect(r, jwtcf); - } + jwt_free(jwt); + return ctx; + } +} - if (validate_alg(jwtcf, jwt) != 0) - { - ngx_log_error(NGX_LOG_ERR, r->connection->log, 0, "invalid algorithm specified"); - return free_jwt_and_redirect(r, jwtcf, jwt); - } - else if (validate_exp(jwtcf, jwt) != 0) - { - ngx_log_error(NGX_LOG_ERR, r->connection->log, 0, "the JWT has expired"); - return free_jwt_and_redirect(r, jwtcf, jwt); - } - else if (validate_sub(jwtcf, jwt) != 0) - { - ngx_log_error(NGX_LOG_ERR, r->connection->log, 0, "the JWT does not contain a subject"); - return free_jwt_and_redirect(r, jwtcf, jwt); - } - else - { - extract_request_claims(r, jwtcf, jwt); - extract_response_claims(r, jwtcf, jwt); - jwt_free(jwt); +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); + auth_jwt_ctx_t *ctx = get_request_jwt_ctx(r); - return NGX_OK; - } - } - } + if (!jwtcf->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; } } @@ -394,8 +587,7 @@ 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) - { + 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; } @@ -430,6 +622,37 @@ static int validate_sub(auth_jwt_conf_t *jwtcf, jwt_t *jwt) 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) @@ -467,16 +690,6 @@ static void extract_response_claims(ngx_http_request_t *r, auth_jwt_conf_t *jwtc extract_claims(r, jwt, jwtcf->extract_response_claims, set_response_header); } -static ngx_int_t free_jwt_and_redirect(ngx_http_request_t *r, auth_jwt_conf_t *jwtcf, jwt_t *jwt) -{ - if (jwt) - { - jwt_free(jwt); - } - - return redirect(r, jwtcf); -} - static ngx_int_t redirect(ngx_http_request_t *r, auth_jwt_conf_t *jwtcf) { if (jwtcf->redirect) diff --git a/test/etc/nginx/conf.d/test.conf b/test/etc/nginx/conf.d/test.conf index 229d545..5359434 100644 --- a/test/etc/nginx/conf.d/test.conf +++ b/test/etc/nginx/conf.d/test.conf @@ -395,4 +395,48 @@ vXjq39xtcIBRTO1c2zs= 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 /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; + } + } } diff --git a/test/test.sh b/test/test.sh index 2bf9cb3..747124c 100755 --- a/test/test.sh +++ b/test/test.sh @@ -72,7 +72,7 @@ run_test () { fi fi - if [ "${okay}" == '1' ] && [ "${expectedResponseRegex}" != "" ] && ! [[ "${response}" =~ "${expectedResponseRegex}" ]]; then + if [ "${okay}" == '1' ] && [ "${expectedResponseRegex}" != "" ] && ! [[ "${response}" =~ ${expectedResponseRegex} ]]; then printf "${RED}${name} -- regex not found in response\n\tPath: ${path}\n\tRegEx: ${expectedResponseRegex}" NUM_FAILED=$((${NUM_FAILED} + 1)) okay=0 @@ -279,7 +279,7 @@ main() { run_test -n 'extracts nested claim to request variable' \ -p '/secure/extract-claim/request/nested' \ - -r '< Test: username=hello.world' \ + -r '< Test: username=hello\.world' \ -x '--header "Authorization: Bearer ${JWT_HS256_VALID}"' run_test -n 'extracts single claim to response variable' \ @@ -319,7 +319,35 @@ main() { run_test -n 'extracts nested claim to response header' \ -p '/secure/extract-claim/response/nested' \ - -r '< JWT-username: hello.world' \ + -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 to response body' \ + -p '/secure/extract-claim/body/sub' \ + -c 200 \ + -r 'sub: some-long-uuid$' \ + -x '--header "Authorization: Bearer ${JWT_HS256_VALID}"' + + run_test -n 'extracts multiple claims to response body' \ + -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}"' if [[ "${NUM_FAILED}" = '0' ]]; then From 5c3d1b9565edb5ac1243d3ebbaa869246f82f0f6 Mon Sep 17 00:00:00 2001 From: Josh McCullough Date: Tue, 4 Feb 2025 12:35:50 -0500 Subject: [PATCH 64/75] fix workflow --- .github/workflows/make-releases.yml | 56 ++++++++++++++++------------- 1 file changed, 31 insertions(+), 25 deletions(-) diff --git a/.github/workflows/make-releases.yml b/.github/workflows/make-releases.yml index 7316e6b..3e58bfa 100644 --- a/.github/workflows/make-releases.yml +++ b/.github/workflows/make-releases.yml @@ -1,11 +1,6 @@ -name: CI +name: Make Releases on: - push: - branches: - - master - paths: - - src/** workflow_dispatch: jobs: @@ -27,14 +22,26 @@ jobs: libjwt-version: ['1.12.0', '1.14.0', '1.15.3'] runs-on: ubuntu-latest steps: + - name: Checkout Code - uses: actions/checkout@v3 + uses: actions/checkout@v4 with: - path: 'ngx-http-auth-jwt-module' + fetch-depth: 0 + path: ngx-http-auth-jwt-module + + - name: Get Metadata + id: meta + run: | + set -eux + cd ngx-http-auth-jwt-module + + tag=$(git describe --tags --abbrev=0) + + echo "filename=ngx-http-auth-jwt-module-${tag}_libjwt-${{matrix.libjwt-version}}_nginx-${{matrix.nginx-version}}.tgz" >> $GITHUB_OUTPUT # TODO cache the build result so we don't have to do this every time? - name: Download jansson - uses: actions/checkout@v3 + uses: actions/checkout@v4 with: repository: 'akheron/jansson' ref: 'v2.14' @@ -50,7 +57,7 @@ jobs: # TODO cache the build result so we don't have to do this every time? - name: Download libjwt - uses: actions/checkout@v3 + uses: actions/checkout@v4 with: repository: 'benmcollins/libjwt' ref: 'v${{matrix.libjwt-version}}' @@ -82,44 +89,43 @@ jobs: BUILD_FLAGS="${BUILD_FLAGS} --with-cc-opt='-DNGX_LINKED_LIST_COOKIES=1'" fi - ./configure --with-compat --add-dynamic-module=../ngx-http-auth-jwt-module ${BUILD_FLAGS} + ./configure --with-compat --without-http_rewrite_module --add-dynamic-module=../ngx-http-auth-jwt-module ${BUILD_FLAGS} - name: Make Modules working-directory: ./nginx run: make modules - - name: Create release archive + - name: Create Release Archive run: | cp ./nginx/objs/ngx_http_auth_jwt_module.so ./ - tar czf ngx_http_auth_jwt_module_libjwt_${{matrix.libjwt-version}}_nginx_${{matrix.nginx-version}}.tgz ngx_http_auth_jwt_module.so + tar czf ${{steps.meta.outputs.filename}} ngx_http_auth_jwt_module.so - - name: Upload build artifact + - name: Upload Build Artifact uses: actions/upload-artifact@v4 with: if-no-files-found: error - name: ngx_http_auth_jwt_module_libjwt_${{matrix.libjwt-version}}_nginx_${{matrix.nginx-version}}.tgz - path: ngx_http_auth_jwt_module_libjwt_${{matrix.libjwt-version}}_nginx_${{matrix.nginx-version}}.tgz + name: ${{steps.meta.outputs.filename}} + path: ${{steps.meta.outputs.filename}} update_releases_page: - name: Upload builds to Releases - if: github.event_name != 'pull_request' - needs: - - build + name: Upload Release + needs: build runs-on: ubuntu-latest permissions: contents: write steps: - - name: Set up variables + + - name: Set-up Variables id: vars run: | echo "date_now=$(date --rfc-3339=seconds)" >> "${GITHUB_OUTPUT}" - - name: Download build artifacts from previous jobs - uses: actions/download-artifact@v3 + - name: Download Build Artifacts from Previous Jobs + uses: actions/download-artifact@v4 with: path: artifacts - - name: Upload builds to Releases + - name: Upload Builds to Release uses: ncipollo/release-action@v1 with: allowUpdates: true @@ -128,7 +134,7 @@ jobs: 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 to the issue tracker. + > Please report any bugs you find. - Build Date: `${{ steps.vars.outputs.date_now }}` - Commit: ${{ github.sha }} From c38ae696319e5106a6c171d4f6882d1ffffd58db Mon Sep 17 00:00:00 2001 From: Josh McCullough Date: Wed, 5 Feb 2025 14:23:23 -0500 Subject: [PATCH 65/75] fix release artifact upload --- .github/workflows/make-releases.yml | 15 +++++++-------- 1 file changed, 7 insertions(+), 8 deletions(-) diff --git a/.github/workflows/make-releases.yml b/.github/workflows/make-releases.yml index 3e58bfa..8d7c906 100644 --- a/.github/workflows/make-releases.yml +++ b/.github/workflows/make-releases.yml @@ -105,10 +105,9 @@ jobs: with: if-no-files-found: error name: ${{steps.meta.outputs.filename}} - path: ${{steps.meta.outputs.filename}} - update_releases_page: - name: Upload Release + release: + name: Create/Update Release needs: build runs-on: ubuntu-latest permissions: @@ -120,7 +119,7 @@ jobs: run: | echo "date_now=$(date --rfc-3339=seconds)" >> "${GITHUB_OUTPUT}" - - name: Download Build Artifacts from Previous Jobs + - name: Download Build Artifacts uses: actions/download-artifact@v4 with: path: artifacts @@ -128,9 +127,7 @@ jobs: - name: Upload Builds to Release uses: ncipollo/release-action@v1 with: - allowUpdates: true - artifactErrorsFailBuild: true - artifacts: artifacts/*/* + name: 'Development Build: ${{ github.ref_name }}@${{ github.sha }}' body: | > [!WARNING] > This is an automatically generated pre-release version of the module, which includes the latest master branch changes. @@ -138,7 +135,9 @@ jobs: - Build Date: `${{ steps.vars.outputs.date_now }}` - Commit: ${{ github.sha }} - name: 'Development build: ${{ github.ref_name }}@${{ github.sha }}' prerelease: true + allowUpdates: true removeArtifacts: true + artifactErrorsFailBuild: true + artifacts: artifacts/* tag: dev-build From acbb12e3c83d9a1d33e673407530cc7bcc8031e6 Mon Sep 17 00:00:00 2001 From: Josh McCullough Date: Wed, 5 Feb 2025 14:23:33 -0500 Subject: [PATCH 66/75] update redirect URL to include port (#146) --- README.md | 41 ++++++----- scripts | 17 +++-- src/ngx_http_auth_jwt_module.c | 119 +++++++++++++++++++++----------- test/docker-compose-test.yml | 7 +- test/etc/nginx/conf.d/test.conf | 6 ++ test/test-nginx.dockerfile | 11 +-- test/test-runner.dockerfile | 14 ++-- test/test.sh | 28 ++++++-- 8 files changed, 159 insertions(+), 84 deletions(-) diff --git a/README.md b/README.md index 95c83af..f6b09d6 100644 --- a/README.md +++ b/README.md @@ -192,41 +192,43 @@ Once you save your changes to `.vscode/c_cpp_properties.json`, you should see th ### Building and Testing -The `./scripts.sh` file contains multiple commands to make things easy: +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_nginx` | Starts the NGINX container. | -| `stop_nginx` | Stops the NGINX container. | +| `start` | Starts the NGINX container. | +| `stop` | Stops the NGINX container. | | `cp_bin` | Copies the compiled binaries out of the NGINX container. | -| `build_test_runner` | Builds the images used by the test stack (uses Docker compose). | -| `rebuild_test_runner` | Re-builds the images used by the test stack. | -| `test` | Runs `test.sh` against the NGINX container (uses Docker compose). | +| `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.sh build_module test +./scripts build_module +./scripts test ``` -To build the Docker images, module, start NGINX, and run the tests against, you can simply do: +To build the Docker images, module, start NGINX, and run the tests against it for all versions, you can simply do: ```shell -./scripts.sh all +./scripts all ``` -When you make a change to the module run `./scripts.sh build_module test` to build a fresh module and run the tests. Note that `rebuild_module` is not often needed as `build_module` hashes the module's source files which will cause a cache miss while building the container, causing the module to be rebuilt. +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.sh test` to run the tests. Similar to above, the test sources are hashed and the containers will be rebuilt as needed. +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.sh build_module` only differs from the official NGINX image in two ways: +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.sh 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. +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 @@ -236,20 +238,23 @@ If you'd like to persist logs across test runs, you can configure the log driver ```shell # need to rebuild the test runner with the proper log driver -LOG_DRIVER=journald ./scripts.sh rebuild_test_runner +export LOG_DRIVER=journald + +# rebuild the test images +./scripts rebuild_test # run the tests -./scripts.sh test +./scripts test -# check the logs -journalctl -eu docker CONTAINER_NAME=jwt-nginx-test +# check the logs -- adjust the container name as needed +journalctl -eu docker CONTAINER_NAME=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.sh test +./scripts test # terminal 2 journalctl -fu docker CONTAINER_NAME=jwt-nginx-test diff --git a/scripts b/scripts index 9d90185..59fc5c3 100755 --- a/scripts +++ b/scripts @@ -31,6 +31,7 @@ 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 @@ -162,7 +163,8 @@ build_test() { printf "${MAGENTA}Building test NGINX & runner using port ${port}...${NC}\n" docker compose \ -p ${TEST_CONTAINER_NAME_PREFIX} \ - -f ./test/docker-compose-test.yml build \ + -f ${TEST_COMPOSE_FILE} \ + build \ --build-arg RUNNER_BASE_IMAGE=${runnerBaseImage} \ --build-arg PORT=${port} \ --build-arg SSL_PORT=${sslPort} \ @@ -188,12 +190,11 @@ test() { printf "${MAGENTA}Running tests...${NC}\n" docker compose \ -p ${TEST_CONTAINER_NAME_PREFIX} \ - -f ./test/docker-compose-test.yml up \ + -f ${TEST_COMPOSE_FILE} up \ --no-start - - trap 'docker compose -f ./test/docker-compose-test.yml down' 0 - + trap test_cleanup 0 + test_now } @@ -218,6 +219,12 @@ test_now() { echo " NGINX Version: ${NGINX_VERSION}" } +test_cleanup() { + docker compose \ + -p ${TEST_CONTAINER_NAME_PREFIX} \ + -f ${TEST_COMPOSE_FILE} down +} + get_port() { startPort=${1:-8000} endPort=$((startPort + 100)) diff --git a/src/ngx_http_auth_jwt_module.c b/src/ngx_http_auth_jwt_module.c index fe428b4..59b84ac 100644 --- a/src/ngx_http_auth_jwt_module.c +++ b/src/ngx_http_auth_jwt_module.c @@ -290,13 +290,13 @@ static char *merge_extract_var_claims(ngx_conf_t *cf, ngx_command_t *cmd, void * { // 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; @@ -305,7 +305,7 @@ static char *merge_extract_var_claims(ngx_conf_t *cf, ngx_command_t *cmd, void * { 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; @@ -314,31 +314,31 @@ static char *merge_extract_var_claims(ngx_conf_t *cf, ngx_command_t *cmd, void * { 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; + return NGX_CONF_ERROR; } else { *claim_idx = claims->nelts - 1; - http_var->data = (uintptr_t) claim_idx; + http_var->data = (uintptr_t)claim_idx; } } } @@ -350,26 +350,26 @@ static char *merge_extract_var_claims(ngx_conf_t *cf, ngx_command_t *cmd, void * static ngx_int_t get_jwt_var_claim(ngx_http_request_t *r, ngx_http_variable_value_t *v, uintptr_t data) { - ngx_log_debug(NGX_LOG_DEBUG_HTTP, r->connection->log, 0, "getting jwt value for var index %l", *((ngx_uint_t*) data)); + ngx_log_debug(NGX_LOG_DEBUG_HTTP, r->connection->log, 0, "getting jwt value for var index %l", *((ngx_uint_t *)data)); auth_jwt_ctx_t *ctx = get_request_jwt_ctx(r); - + if (ctx == NULL) { ngx_log_debug(NGX_LOG_DEBUG_HTTP, r->connection->log, 0, "no module context found while getting jwt value"); - + return NGX_ERROR; } else { - ngx_uint_t *claim_idx = (ngx_uint_t*) data; - ngx_str_t claim_value = ((ngx_str_t*) ctx->claim_values->elts)[*claim_idx]; - + ngx_uint_t *claim_idx = (ngx_uint_t *)data; + 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; } } @@ -420,7 +420,7 @@ static char *merge_extract_response_claims(ngx_conf_t *cf, ngx_command_t *cmd, v 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; @@ -434,7 +434,8 @@ static auth_jwt_ctx_t *get_or_init_jwt_module_ctx(ngx_http_request_t *r, auth_jw ngx_log_error(NGX_LOG_ERR, r->connection->log, 0, "error allocating jwt module context"); return ctx; } - else { + else + { if (jwtcf->extract_var_claims != NULL) { ctx->claim_values = ngx_array_create(r->pool, jwtcf->extract_var_claims->nelts, sizeof(ngx_str_t)); @@ -460,7 +461,7 @@ 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); - if(!jwtcf->enabled) + if (!jwtcf->enabled) { return NULL; } @@ -587,7 +588,8 @@ 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) { + 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; } @@ -625,7 +627,7 @@ 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) { ngx_array_t *claims = jwtcf->extract_var_claims; - + if (claims == NULL || claims->nelts == 0) { return NGX_OK; @@ -633,22 +635,22 @@ static ngx_int_t extract_var_claims(ngx_http_request_t *r, auth_jwt_conf_t *jwtc 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_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; } } @@ -708,11 +710,16 @@ static ngx_int_t redirect(ngx_http_request_t *r, auth_jwt_conf_t *jwtcf) if (r->method == NGX_HTTP_GET) { const int loginlen = jwtcf->loginurl.len; - const char *scheme = (r->connection->ssl) ? "https" : "http"; + 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 *request_uri_var = ngx_http_get_variable(r, &uri_variable_name, uri_variable_hash); + 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; @@ -720,12 +727,12 @@ static ngx_int_t redirect(ngx_http_request_t *r, auth_jwt_conf_t *jwtcf) int return_url_idx; // get the URI - if (request_uri_var && !request_uri_var->not_found && request_uri_var->valid) + 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, request_uri_var->len); - uri.len = request_uri_var->len; - ngx_memcpy(uri.data, request_uri_var->data, request_uri_var->len); + 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 { @@ -733,31 +740,59 @@ static ngx_int_t redirect(ngx_http_request_t *r, auth_jwt_conf_t *jwtcf) 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); - r->headers_out.location->value.len = loginlen + strlen("?return_url=") + strlen(scheme) + strlen("://") + server.len + uri_escaped.len; + // 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); + 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=", strlen("?return_url=")); - return_url_idx += strlen("?return_url="); - ngx_memcpy(return_url + return_url_idx, scheme, strlen(scheme)); + 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, "://", strlen("://")); - return_url_idx += strlen("://"); - ngx_memcpy(return_url + return_url_idx, server.data, server.len); + 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; - ngx_memcpy(return_url + return_url_idx, uri_escaped.data, uri_escaped.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; } diff --git a/test/docker-compose-test.yml b/test/docker-compose-test.yml index 14c88da..72ff710 100644 --- a/test/docker-compose-test.yml +++ b/test/docker-compose-test.yml @@ -1,20 +1,19 @@ services: nginx: - container_name: ${TEST_CONTAINER_NAME_PREFIX}-nginx + container_name: ${TEST_CONTAINER_NAME_PREFIX:?required}-nginx build: context: . dockerfile: test-nginx.dockerfile args: - BASE_IMAGE: ${FULL_IMAGE_NAME}:${NGINX_VERSION} + BASE_IMAGE: ${FULL_IMAGE_NAME}:${NGINX_VERSION:?required} logging: driver: ${LOG_DRIVER:-journald} runner: - container_name: ${TEST_CONTAINER_NAME_PREFIX}-runner + container_name: ${TEST_CONTAINER_NAME_PREFIX:?required}-runner build: context: . dockerfile: test-runner.dockerfile - depends_on: - nginx \ No newline at end of file diff --git a/test/etc/nginx/conf.d/test.conf b/test/etc/nginx/conf.d/test.conf index 5359434..4e5d764 100644 --- a/test/etc/nginx/conf.d/test.conf +++ b/test/etc/nginx/conf.d/test.conf @@ -405,6 +405,7 @@ vXjq39xtcIBRTO1c2zs= if ($jwt_claim_sub = 'some-long-uuid') { return 200; } + return 401; } @@ -439,4 +440,9 @@ vXjq39xtcIBRTO1c2zs= return 301 /profile/$jwt_claim_sub; } } + + location /return-url { + auth_jwt_enabled on; + auth_jwt_redirect on; + } } diff --git a/test/test-nginx.dockerfile b/test/test-nginx.dockerfile index e12acb4..1065558 100644 --- a/test/test-nginx.dockerfile +++ b/test/test-nginx.dockerfile @@ -1,13 +1,11 @@ ARG BASE_IMAGE -ARG PORT -ARG SSL_PORT -FROM ${BASE_IMAGE} AS NGINX +FROM ${BASE_IMAGE:?required} AS NGINX ARG PORT ARG SSL_PORT + COPY etc/ /etc/ -RUN sed -i "s|%{PORT}|${PORT}|" /etc/nginx/conf.d/test.conf -RUN sed -i "s|%{SSL_PORT}|${SSL_PORT}|" /etc/nginx/conf.d/test.conf + COPY <<` /usr/share/nginx/html/index.html Test @@ -16,3 +14,6 @@ COPY <<` /usr/share/nginx/html/index.html ` + +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 diff --git a/test/test-runner.dockerfile b/test/test-runner.dockerfile index 0aca095..18fc3d3 100644 --- a/test/test-runner.dockerfile +++ b/test/test-runner.dockerfile @@ -1,16 +1,18 @@ ARG RUNNER_BASE_IMAGE -ARG PORT -ARG SSL_PORT -FROM ${RUNNER_BASE_IMAGE} +FROM ${RUNNER_BASE_IMAGE:?required} ARG PORT ARG SSL_PORT -ENV PORT=${PORT} -ENV SSL_PORT=${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 ${PORT} ${SSL_PORT} + +CMD ./test.sh diff --git a/test/test.sh b/test/test.sh index 747124c..c726a75 100755 --- a/test/test.sh +++ b/test/test.sh @@ -29,7 +29,7 @@ run_test () { local response= local testNum="${GRAY}${NUM_TESTS}${NC}\t" - while getopts "n:sp:r:c:x:" option; do + while getopts "n:asp:r:c:x:" option; do case $option in n) name=$OPTARG;; @@ -73,7 +73,7 @@ run_test () { fi if [ "${okay}" == '1' ] && [ "${expectedResponseRegex}" != "" ] && ! [[ "${response}" =~ ${expectedResponseRegex} ]]; then - printf "${RED}${name} -- regex not found in response\n\tPath: ${path}\n\tRegEx: ${expectedResponseRegex}" + printf "${RED}${name} -- regex not found in response\n\tPath: ${path}\n\tRegEx: ${expectedResponseRegex//%/%%}" NUM_FAILED=$((${NUM_FAILED} + 1)) okay=0 fi @@ -113,8 +113,8 @@ main() { -p '/' \ -c '200' - run_test -s \ - -n '[SSL] when auth disabled, should return 200' \ + run_test -n '[SSL] when auth disabled, should return 200' \ + -s \ -p '/' \ -c '200' @@ -350,6 +350,26 @@ main() { -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' + if [[ "${NUM_FAILED}" = '0' ]]; then printf "\nRan ${NUM_TESTS} tests successfully (skipped ${NUM_SKIPPED}).\n" return 0 From a2e3e914998d4b82e91d2db9ec9e482f3b562c43 Mon Sep 17 00:00:00 2001 From: Josh McCullough Date: Wed, 5 Feb 2025 14:37:41 -0500 Subject: [PATCH 67/75] fix artifact processing in workflow --- .github/workflows/make-releases.yml | 62 +++- test/docker-compose-test.yml | 19 -- test/ec_key_256.pem | 5 - test/ec_key_384.pem | 6 - test/ec_key_521.pem | 8 - test/etc/nginx/conf.d/test.conf | 448 ---------------------------- test/etc/nginx/ec_key_256-pub.pem | 4 - test/etc/nginx/ec_key_384-pub.pem | 5 - test/etc/nginx/ec_key_521-pub.pem | 6 - test/etc/nginx/rsa_key_2048-pub.pem | 9 - test/etc/nginx/test.crt | 23 -- test/etc/nginx/test.key | 28 -- test/rsa_key_2048.pem | 28 -- test/test-nginx.dockerfile | 19 -- test/test-runner.dockerfile | 18 -- test/test.sh | 387 ------------------------ 16 files changed, 49 insertions(+), 1026 deletions(-) delete mode 100644 test/docker-compose-test.yml delete mode 100644 test/ec_key_256.pem delete mode 100644 test/ec_key_384.pem delete mode 100644 test/ec_key_521.pem delete mode 100644 test/etc/nginx/conf.d/test.conf delete mode 100644 test/etc/nginx/ec_key_256-pub.pem delete mode 100644 test/etc/nginx/ec_key_384-pub.pem delete mode 100644 test/etc/nginx/ec_key_521-pub.pem delete mode 100755 test/etc/nginx/rsa_key_2048-pub.pem delete mode 100644 test/etc/nginx/test.crt delete mode 100644 test/etc/nginx/test.key delete mode 100755 test/rsa_key_2048.pem delete mode 100644 test/test-nginx.dockerfile delete mode 100644 test/test-runner.dockerfile delete mode 100755 test/test.sh diff --git a/.github/workflows/make-releases.yml b/.github/workflows/make-releases.yml index 8d7c906..487c4bf 100644 --- a/.github/workflows/make-releases.yml +++ b/.github/workflows/make-releases.yml @@ -4,8 +4,29 @@ 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@v4 + 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 strategy: matrix: # NGINX versions to build/test against @@ -26,18 +47,17 @@ jobs: - name: Checkout Code uses: actions/checkout@v4 with: - fetch-depth: 0 path: ngx-http-auth-jwt-module - name: Get Metadata id: meta run: | - set -eux - cd ngx-http-auth-jwt-module - - tag=$(git describe --tags --abbrev=0) + 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 - echo "filename=ngx-http-auth-jwt-module-${tag}_libjwt-${{matrix.libjwt-version}}_nginx-${{matrix.nginx-version}}.tgz" >> $GITHUB_OUTPUT # TODO cache the build result so we don't have to do this every time? - name: Download jansson @@ -104,11 +124,14 @@ jobs: uses: actions/upload-artifact@v4 with: if-no-files-found: error - name: ${{steps.meta.outputs.filename}} + name: ${{steps.meta.outputs.artifact}} + path: ${{steps.meta.outputs.filename}} release: name: Create/Update Release - needs: build + needs: + - meta + - build runs-on: ubuntu-latest permissions: contents: write @@ -119,25 +142,38 @@ jobs: run: | echo "date_now=$(date --rfc-3339=seconds)" >> "${GITHUB_OUTPUT}" - - name: Download Build Artifacts + - name: Download Artifacts uses: actions/download-artifact@v4 with: path: artifacts - - name: Upload Builds to Release + - name: Flatten Artifacts + run: | + set -eu + + cd artifacts + + for f in $(find . -type f); do + echo "Staging: ${f}" + mv "${f}" . + done + + find . -type d -mindepth 1 -exec rm -rf "{}" + + + - name: Create/Update Release uses: ncipollo/release-action@v1 with: - name: 'Development Build: ${{ github.ref_name }}@${{ github.sha }}' + 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 }} + - Commit: `${{ github.sha }}` prerelease: true allowUpdates: true removeArtifacts: true artifactErrorsFailBuild: true artifacts: artifacts/* - tag: dev-build diff --git a/test/docker-compose-test.yml b/test/docker-compose-test.yml deleted file mode 100644 index 72ff710..0000000 --- a/test/docker-compose-test.yml +++ /dev/null @@ -1,19 +0,0 @@ -services: - - nginx: - container_name: ${TEST_CONTAINER_NAME_PREFIX:?required}-nginx - build: - context: . - dockerfile: test-nginx.dockerfile - args: - BASE_IMAGE: ${FULL_IMAGE_NAME}:${NGINX_VERSION:?required} - logging: - driver: ${LOG_DRIVER:-journald} - - runner: - container_name: ${TEST_CONTAINER_NAME_PREFIX:?required}-runner - build: - context: . - dockerfile: test-runner.dockerfile - depends_on: - - nginx \ No newline at end of file diff --git a/test/ec_key_256.pem b/test/ec_key_256.pem deleted file mode 100644 index 4206969..0000000 --- a/test/ec_key_256.pem +++ /dev/null @@ -1,5 +0,0 @@ ------BEGIN PRIVATE KEY----- -MIGHAgEAMBMGByqGSM49AgEGCCqGSM49AwEHBG0wawIBAQQgOlEBGcZxxhv8FkN0 -YIvax6fnhJbMeotzIEBxIglkNu6hRANCAATP1NpDzvZmKd2Mw6hIrv4nzUfNu7OK -mT5VuL5LhvUgzTqVGuxwevA7DlFsNVSfCljIBG3geio3fcd4k0Z9SygL ------END PRIVATE KEY----- diff --git a/test/ec_key_384.pem b/test/ec_key_384.pem deleted file mode 100644 index 2aa5780..0000000 --- a/test/ec_key_384.pem +++ /dev/null @@ -1,6 +0,0 @@ ------BEGIN PRIVATE KEY----- -MIG2AgEAMBAGByqGSM49AgEGBSuBBAAiBIGeMIGbAgEBBDADyrL6llSQoQOZ/PF/ -l761kAbrTwn4vu30Kr34ScW6bRKVXLq3cT3QssJ1nF9B63qhZANiAAQ48dOfIEd3 -0TCVE0JT4ZU0Db7Ftz+ex7lojP7uqTY9OI59yoMB01zUN4JK30BRXS9Yv0A9Bu1z -fgLu93FSn0kd0zIPMvuu5LUt60M/miSt2lA0OrqFhKjx6FFdN/lNh64= ------END PRIVATE KEY----- diff --git a/test/ec_key_521.pem b/test/ec_key_521.pem deleted file mode 100644 index 10471dc..0000000 --- a/test/ec_key_521.pem +++ /dev/null @@ -1,8 +0,0 @@ ------BEGIN PRIVATE KEY----- -MIHuAgEAMBAGByqGSM49AgEGBSuBBAAjBIHWMIHTAgEBBEIAKkag6aVn4XAbaALo -0b3pypdP5RBX7uKxHmKlkNCcpA0oVTdgjnM5NpJP8ZOM6NjVhEzsn6c/Tdn8hL8w -SI55hFWhgYkDgYYABABpTipSvbs8fq44u4fA+v7DTNYViA58sqbrxjxdzwWZ8eEj -CXsH7yzSGx3Y19NSyrX8HbjWmrj5uxiKeFCB8mGzTwDcFIKCMeMkHjZs/fmVOumR -a2XSpj7BP6wqcN6Pf+UqECivGAZGRHoabo/dm5zF9M3gO+G9eOrf3G1wgFFM7Vzb -Ow== ------END PRIVATE KEY----- diff --git a/test/etc/nginx/conf.d/test.conf b/test/etc/nginx/conf.d/test.conf deleted file mode 100644 index 4e5d764..0000000 --- a/test/etc/nginx/conf.d/test.conf +++ /dev/null @@ -1,448 +0,0 @@ -error_log /var/log/nginx/debug.log debug; -access_log /var/log/nginx/access.log; - -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 / { - 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 /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; - } -} diff --git a/test/etc/nginx/ec_key_256-pub.pem b/test/etc/nginx/ec_key_256-pub.pem deleted file mode 100644 index 3306ea0..0000000 --- a/test/etc/nginx/ec_key_256-pub.pem +++ /dev/null @@ -1,4 +0,0 @@ ------BEGIN PUBLIC KEY----- -MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEz9TaQ872ZindjMOoSK7+J81Hzbuz -ipk+Vbi+S4b1IM06lRrscHrwOw5RbDVUnwpYyARt4HoqN33HeJNGfUsoCw== ------END PUBLIC KEY----- diff --git a/test/etc/nginx/ec_key_384-pub.pem b/test/etc/nginx/ec_key_384-pub.pem deleted file mode 100644 index e642ed1..0000000 --- a/test/etc/nginx/ec_key_384-pub.pem +++ /dev/null @@ -1,5 +0,0 @@ ------BEGIN PUBLIC KEY----- -MHYwEAYHKoZIzj0CAQYFK4EEACIDYgAEOPHTnyBHd9EwlRNCU+GVNA2+xbc/nse5 -aIz+7qk2PTiOfcqDAdNc1DeCSt9AUV0vWL9APQbtc34C7vdxUp9JHdMyDzL7ruS1 -LetDP5okrdpQNDq6hYSo8ehRXTf5TYeu ------END PUBLIC KEY----- diff --git a/test/etc/nginx/ec_key_521-pub.pem b/test/etc/nginx/ec_key_521-pub.pem deleted file mode 100644 index 0cb875c..0000000 --- a/test/etc/nginx/ec_key_521-pub.pem +++ /dev/null @@ -1,6 +0,0 @@ ------BEGIN PUBLIC KEY----- -MIGbMBAGByqGSM49AgEGBSuBBAAjA4GGAAQAaU4qUr27PH6uOLuHwPr+w0zWFYgO -fLKm68Y8Xc8FmfHhIwl7B+8s0hsd2NfTUsq1/B241pq4+bsYinhQgfJhs08A3BSC -gjHjJB42bP35lTrpkWtl0qY+wT+sKnDej3/lKhAorxgGRkR6Gm6P3ZucxfTN4Dvh -vXjq39xtcIBRTO1c2zs= ------END PUBLIC KEY----- diff --git a/test/etc/nginx/rsa_key_2048-pub.pem b/test/etc/nginx/rsa_key_2048-pub.pem deleted file mode 100755 index 01f59bf..0000000 --- a/test/etc/nginx/rsa_key_2048-pub.pem +++ /dev/null @@ -1,9 +0,0 @@ ------BEGIN PUBLIC KEY----- -MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAwtpMAM4l1H995oqlqdMh -uqNuffp4+4aUCwuFE9B5s9MJr63gyf8jW0oDr7Mb1Xb8y9iGkWfhouZqNJbMFry+ -iBs+z2TtJF06vbHQZzajDsdux3XVfXv9v6dDIImyU24MsGNkpNt0GISaaiqv51NM -ZQX0miOXXWdkQvWTZFXhmsFCmJLE67oQFSar4hzfAaCulaMD+b3Mcsjlh0yvSq7g -6swiIasEU3qNLKaJAZEzfywroVYr3BwM1IiVbQeKgIkyPS/85M4Y6Ss/T+OWi1Oe -K49NdYBvFP+hNVEoeZzJz5K/nd6C35IX0t2bN5CVXchUFmaUMYk2iPdhXdsC720t -BwIDAQAB ------END PUBLIC KEY----- diff --git a/test/etc/nginx/test.crt b/test/etc/nginx/test.crt deleted file mode 100644 index fb406ba..0000000 --- a/test/etc/nginx/test.crt +++ /dev/null @@ -1,23 +0,0 @@ ------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----- diff --git a/test/etc/nginx/test.key b/test/etc/nginx/test.key deleted file mode 100644 index 13ec754..0000000 --- a/test/etc/nginx/test.key +++ /dev/null @@ -1,28 +0,0 @@ ------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----- diff --git a/test/rsa_key_2048.pem b/test/rsa_key_2048.pem deleted file mode 100755 index 0f58120..0000000 --- a/test/rsa_key_2048.pem +++ /dev/null @@ -1,28 +0,0 @@ ------BEGIN PRIVATE KEY----- -MIIEvQIBADANBgkqhkiG9w0BAQEFAASCBKcwggSjAgEAAoIBAQDC2kwAziXUf33m -iqWp0yG6o259+nj7hpQLC4UT0Hmz0wmvreDJ/yNbSgOvsxvVdvzL2IaRZ+Gi5mo0 -lswWvL6IGz7PZO0kXTq9sdBnNqMOx27HddV9e/2/p0MgibJTbgywY2Sk23QYhJpq -Kq/nU0xlBfSaI5ddZ2RC9ZNkVeGawUKYksTruhAVJqviHN8BoK6VowP5vcxyyOWH -TK9KruDqzCIhqwRTeo0spokBkTN/LCuhVivcHAzUiJVtB4qAiTI9L/zkzhjpKz9P -45aLU54rj011gG8U/6E1USh5nMnPkr+d3oLfkhfS3Zs3kJVdyFQWZpQxiTaI92Fd -2wLvbS0HAgMBAAECggEAD8dTnkETSSjlzhRuI9loAtAXM3Zj86JLPLW7GgaoxEoT -n7lJ2bGicFMHB2ROnbOb9vnas82gtOtJsGaBslmoaCckp/C5T1eJWTEb+i+vdpPp -wZcmKZovyyRFSE4+NYlU17fEv6DRvuaGBpDcW7QgHJIl45F8QWEM+msee2KE+V4G -z/9vAQ+sOlvsb4mJP1tJIBx9Lb5loVREwCRy2Ha9tnWdDNar8EYkOn8si4snPT+E -3ZCy8mlcZyUkZeiS/HdtydxZfoiwrSRYamd1diQpPhWCeRteQ802a7ds0Y2YzgfF -UaYjNuRQm7zA//hwbXS7ELPyNMU15N00bajlG0tUOQKBgQDnLy01l20OneW6A2cI -DIDyYhy5O7uulsaEtJReUlcjEDMkin8b767q2VZHb//3ZH+ipnRYByUUyYUhdOs2 -DYRGGeAebnH8wpTT4FCYxUsIUpDfB7RwfdBONgaKewTJz/FPswy1Ye0b5H2c6vVi -m2FZ33HQcoZ3wvFFqyGVnMzpOwKBgQDXxL95yoxUGKa8vMzcE3Cn01szh0dFq0sq -cFpM+HWLVr84CItuG9H6L0KaStEEIOiJsxOVpcXfFFhsJvOGhMA4DQTwH4WuXmXp -1PoVMDlV65PYqvhzwL4+QhvZO2bsrEunITXOmU7CI6kilnAN3LuP4HbqZgoX9lqP -I31VYzLupQKBgGEYck9w0s/xxxtR9ILv5XRnepLdoJzaHHR991aKFKjYU/KD7JDK -INfoAhGs23+HCQhCCtkx3wQVA0Ii/erM0II0ueluD5fODX3TV2ZibnoHW2sgrEsW -vFcs36BnvIIaQMptc+f2QgSV+Z/fGsKYadG6Q+39O7au/HB7SHayzWkjAoGBAMgt -Fzslp9TpXd9iBWjzfCOnGUiP65Z+GWkQ/SXFqD+SRir0+m43zzGdoNvGJ23+Hd6K -TdQbDJ0uoe4MoQeepzoZEgi4JeykVUZ/uVfo+nh06yArVf8FxTm7WVzLGGzgV/uA -+wtl/cRtEyAsk1649yW/KHPEIP8kJdYAJeoO8xSlAoGAERMrkFR7KGYZG1eFNRdV -mJMq+Ibxyw8ks/CbiI+n3yUyk1U8962ol2Q0T4qjBmb26L5rrhNQhneM4e8mo9FX -LlQapYkPvkdrqW0Bp72A/UNAvcGTmN7z5OCJGMUutx2hmEAlrYmpLKS8pM/p9zpK -tEOtzsP5GMDYVlEp1jYSjzQ= ------END PRIVATE KEY----- diff --git a/test/test-nginx.dockerfile b/test/test-nginx.dockerfile deleted file mode 100644 index 1065558..0000000 --- a/test/test-nginx.dockerfile +++ /dev/null @@ -1,19 +0,0 @@ -ARG BASE_IMAGE - -FROM ${BASE_IMAGE:?required} AS NGINX -ARG PORT -ARG SSL_PORT - -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 diff --git a/test/test-runner.dockerfile b/test/test-runner.dockerfile deleted file mode 100644 index 18fc3d3..0000000 --- a/test/test-runner.dockerfile +++ /dev/null @@ -1,18 +0,0 @@ -ARG 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 diff --git a/test/test.sh b/test/test.sh deleted file mode 100755 index c726a75..0000000 --- a/test/test.sh +++ /dev/null @@ -1,387 +0,0 @@ -#!/bin/bash -eu - -# 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 - - curlCommand="curl -skv ${scheme}://nginx:${port}${path} -H 'Cache-Control: no-cache' ${extraCurlOpts} 2>&1" - response=$(eval "${curlCommand}") - exitCode=$? - - printf "\n${testNum}" - - if [ "${exitCode}" -ne "0" ]; then - printf "${RED}${name} -- unexpected exit code from cURL\n\tcURL Exit Code: ${exitCode}"; - NUM_FAILED=$((${NUM_FAILED} + 1)); - else - local okay=1 - - if [ "${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//%/%%}" - 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 '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 to response body' \ - -p '/secure/extract-claim/body/sub' \ - -c 200 \ - -r 'sub: some-long-uuid$' \ - -x '--header "Authorization: Bearer ${JWT_HS256_VALID}"' - - run_test -n 'extracts multiple claims to response body' \ - -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' - - 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 From c415b81e28b5fdacddc5716fb7e21728a4c40096 Mon Sep 17 00:00:00 2001 From: Josh McCullough Date: Wed, 5 Feb 2025 16:07:30 -0500 Subject: [PATCH 68/75] fix incorrect function name --- .bin/git/hooks/pre-push-build-and-test | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.bin/git/hooks/pre-push-build-and-test b/.bin/git/hooks/pre-push-build-and-test index 9cf4682..876ca32 100755 --- a/.bin/git/hooks/pre-push-build-and-test +++ b/.bin/git/hooks/pre-push-build-and-test @@ -4,7 +4,7 @@ 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_runner test) + (cd ${REPO_ROOT_DIR}; ./scripts.sh rebuild_nginx rebuild_test test) else HOOK_NAME=$(basename $0) From a1662419010034a8230892489b083365bed4232c Mon Sep 17 00:00:00 2001 From: Josh McCullough Date: Tue, 18 Feb 2025 11:15:25 -0500 Subject: [PATCH 69/75] restore accidentally-deleted test dir :-| --- test/docker-compose-test.yml | 19 ++ test/ec_key_256.pem | 5 + test/ec_key_384.pem | 6 + test/ec_key_521.pem | 8 + test/etc/nginx/conf.d/test.conf | 448 ++++++++++++++++++++++++++++ test/etc/nginx/ec_key_256-pub.pem | 4 + test/etc/nginx/ec_key_384-pub.pem | 5 + test/etc/nginx/ec_key_521-pub.pem | 6 + test/etc/nginx/rsa_key_2048-pub.pem | 9 + test/etc/nginx/test.crt | 23 ++ test/etc/nginx/test.key | 28 ++ test/rsa_key_2048.pem | 28 ++ test/test-nginx.dockerfile | 19 ++ test/test-runner.dockerfile | 18 ++ test/test.sh | 387 ++++++++++++++++++++++++ 15 files changed, 1013 insertions(+) create mode 100644 test/docker-compose-test.yml create mode 100644 test/ec_key_256.pem create mode 100644 test/ec_key_384.pem create mode 100644 test/ec_key_521.pem create mode 100644 test/etc/nginx/conf.d/test.conf create mode 100644 test/etc/nginx/ec_key_256-pub.pem create mode 100644 test/etc/nginx/ec_key_384-pub.pem create mode 100644 test/etc/nginx/ec_key_521-pub.pem create mode 100755 test/etc/nginx/rsa_key_2048-pub.pem create mode 100644 test/etc/nginx/test.crt create mode 100644 test/etc/nginx/test.key create mode 100755 test/rsa_key_2048.pem create mode 100644 test/test-nginx.dockerfile create mode 100644 test/test-runner.dockerfile create mode 100755 test/test.sh diff --git a/test/docker-compose-test.yml b/test/docker-compose-test.yml new file mode 100644 index 0000000..72ff710 --- /dev/null +++ b/test/docker-compose-test.yml @@ -0,0 +1,19 @@ +services: + + nginx: + container_name: ${TEST_CONTAINER_NAME_PREFIX:?required}-nginx + build: + context: . + dockerfile: test-nginx.dockerfile + args: + BASE_IMAGE: ${FULL_IMAGE_NAME}:${NGINX_VERSION:?required} + logging: + driver: ${LOG_DRIVER:-journald} + + runner: + container_name: ${TEST_CONTAINER_NAME_PREFIX:?required}-runner + build: + context: . + dockerfile: test-runner.dockerfile + depends_on: + - nginx \ No newline at end of file diff --git a/test/ec_key_256.pem b/test/ec_key_256.pem new file mode 100644 index 0000000..4206969 --- /dev/null +++ b/test/ec_key_256.pem @@ -0,0 +1,5 @@ +-----BEGIN PRIVATE KEY----- +MIGHAgEAMBMGByqGSM49AgEGCCqGSM49AwEHBG0wawIBAQQgOlEBGcZxxhv8FkN0 +YIvax6fnhJbMeotzIEBxIglkNu6hRANCAATP1NpDzvZmKd2Mw6hIrv4nzUfNu7OK +mT5VuL5LhvUgzTqVGuxwevA7DlFsNVSfCljIBG3geio3fcd4k0Z9SygL +-----END PRIVATE KEY----- diff --git a/test/ec_key_384.pem b/test/ec_key_384.pem new file mode 100644 index 0000000..2aa5780 --- /dev/null +++ b/test/ec_key_384.pem @@ -0,0 +1,6 @@ +-----BEGIN PRIVATE KEY----- +MIG2AgEAMBAGByqGSM49AgEGBSuBBAAiBIGeMIGbAgEBBDADyrL6llSQoQOZ/PF/ +l761kAbrTwn4vu30Kr34ScW6bRKVXLq3cT3QssJ1nF9B63qhZANiAAQ48dOfIEd3 +0TCVE0JT4ZU0Db7Ftz+ex7lojP7uqTY9OI59yoMB01zUN4JK30BRXS9Yv0A9Bu1z +fgLu93FSn0kd0zIPMvuu5LUt60M/miSt2lA0OrqFhKjx6FFdN/lNh64= +-----END PRIVATE KEY----- diff --git a/test/ec_key_521.pem b/test/ec_key_521.pem new file mode 100644 index 0000000..10471dc --- /dev/null +++ b/test/ec_key_521.pem @@ -0,0 +1,8 @@ +-----BEGIN PRIVATE KEY----- +MIHuAgEAMBAGByqGSM49AgEGBSuBBAAjBIHWMIHTAgEBBEIAKkag6aVn4XAbaALo +0b3pypdP5RBX7uKxHmKlkNCcpA0oVTdgjnM5NpJP8ZOM6NjVhEzsn6c/Tdn8hL8w +SI55hFWhgYkDgYYABABpTipSvbs8fq44u4fA+v7DTNYViA58sqbrxjxdzwWZ8eEj +CXsH7yzSGx3Y19NSyrX8HbjWmrj5uxiKeFCB8mGzTwDcFIKCMeMkHjZs/fmVOumR +a2XSpj7BP6wqcN6Pf+UqECivGAZGRHoabo/dm5zF9M3gO+G9eOrf3G1wgFFM7Vzb +Ow== +-----END PRIVATE KEY----- diff --git a/test/etc/nginx/conf.d/test.conf b/test/etc/nginx/conf.d/test.conf new file mode 100644 index 0000000..4e5d764 --- /dev/null +++ b/test/etc/nginx/conf.d/test.conf @@ -0,0 +1,448 @@ +error_log /var/log/nginx/debug.log debug; +access_log /var/log/nginx/access.log; + +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 / { + 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 /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; + } +} diff --git a/test/etc/nginx/ec_key_256-pub.pem b/test/etc/nginx/ec_key_256-pub.pem new file mode 100644 index 0000000..3306ea0 --- /dev/null +++ b/test/etc/nginx/ec_key_256-pub.pem @@ -0,0 +1,4 @@ +-----BEGIN PUBLIC KEY----- +MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEz9TaQ872ZindjMOoSK7+J81Hzbuz +ipk+Vbi+S4b1IM06lRrscHrwOw5RbDVUnwpYyARt4HoqN33HeJNGfUsoCw== +-----END PUBLIC KEY----- diff --git a/test/etc/nginx/ec_key_384-pub.pem b/test/etc/nginx/ec_key_384-pub.pem new file mode 100644 index 0000000..e642ed1 --- /dev/null +++ b/test/etc/nginx/ec_key_384-pub.pem @@ -0,0 +1,5 @@ +-----BEGIN PUBLIC KEY----- +MHYwEAYHKoZIzj0CAQYFK4EEACIDYgAEOPHTnyBHd9EwlRNCU+GVNA2+xbc/nse5 +aIz+7qk2PTiOfcqDAdNc1DeCSt9AUV0vWL9APQbtc34C7vdxUp9JHdMyDzL7ruS1 +LetDP5okrdpQNDq6hYSo8ehRXTf5TYeu +-----END PUBLIC KEY----- diff --git a/test/etc/nginx/ec_key_521-pub.pem b/test/etc/nginx/ec_key_521-pub.pem new file mode 100644 index 0000000..0cb875c --- /dev/null +++ b/test/etc/nginx/ec_key_521-pub.pem @@ -0,0 +1,6 @@ +-----BEGIN PUBLIC KEY----- +MIGbMBAGByqGSM49AgEGBSuBBAAjA4GGAAQAaU4qUr27PH6uOLuHwPr+w0zWFYgO +fLKm68Y8Xc8FmfHhIwl7B+8s0hsd2NfTUsq1/B241pq4+bsYinhQgfJhs08A3BSC +gjHjJB42bP35lTrpkWtl0qY+wT+sKnDej3/lKhAorxgGRkR6Gm6P3ZucxfTN4Dvh +vXjq39xtcIBRTO1c2zs= +-----END PUBLIC KEY----- diff --git a/test/etc/nginx/rsa_key_2048-pub.pem b/test/etc/nginx/rsa_key_2048-pub.pem new file mode 100755 index 0000000..01f59bf --- /dev/null +++ b/test/etc/nginx/rsa_key_2048-pub.pem @@ -0,0 +1,9 @@ +-----BEGIN PUBLIC KEY----- +MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAwtpMAM4l1H995oqlqdMh +uqNuffp4+4aUCwuFE9B5s9MJr63gyf8jW0oDr7Mb1Xb8y9iGkWfhouZqNJbMFry+ +iBs+z2TtJF06vbHQZzajDsdux3XVfXv9v6dDIImyU24MsGNkpNt0GISaaiqv51NM +ZQX0miOXXWdkQvWTZFXhmsFCmJLE67oQFSar4hzfAaCulaMD+b3Mcsjlh0yvSq7g +6swiIasEU3qNLKaJAZEzfywroVYr3BwM1IiVbQeKgIkyPS/85M4Y6Ss/T+OWi1Oe +K49NdYBvFP+hNVEoeZzJz5K/nd6C35IX0t2bN5CVXchUFmaUMYk2iPdhXdsC720t +BwIDAQAB +-----END PUBLIC KEY----- diff --git a/test/etc/nginx/test.crt b/test/etc/nginx/test.crt new file mode 100644 index 0000000..fb406ba --- /dev/null +++ b/test/etc/nginx/test.crt @@ -0,0 +1,23 @@ +-----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----- diff --git a/test/etc/nginx/test.key b/test/etc/nginx/test.key new file mode 100644 index 0000000..13ec754 --- /dev/null +++ b/test/etc/nginx/test.key @@ -0,0 +1,28 @@ +-----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----- diff --git a/test/rsa_key_2048.pem b/test/rsa_key_2048.pem new file mode 100755 index 0000000..0f58120 --- /dev/null +++ b/test/rsa_key_2048.pem @@ -0,0 +1,28 @@ +-----BEGIN PRIVATE KEY----- +MIIEvQIBADANBgkqhkiG9w0BAQEFAASCBKcwggSjAgEAAoIBAQDC2kwAziXUf33m +iqWp0yG6o259+nj7hpQLC4UT0Hmz0wmvreDJ/yNbSgOvsxvVdvzL2IaRZ+Gi5mo0 +lswWvL6IGz7PZO0kXTq9sdBnNqMOx27HddV9e/2/p0MgibJTbgywY2Sk23QYhJpq +Kq/nU0xlBfSaI5ddZ2RC9ZNkVeGawUKYksTruhAVJqviHN8BoK6VowP5vcxyyOWH +TK9KruDqzCIhqwRTeo0spokBkTN/LCuhVivcHAzUiJVtB4qAiTI9L/zkzhjpKz9P +45aLU54rj011gG8U/6E1USh5nMnPkr+d3oLfkhfS3Zs3kJVdyFQWZpQxiTaI92Fd +2wLvbS0HAgMBAAECggEAD8dTnkETSSjlzhRuI9loAtAXM3Zj86JLPLW7GgaoxEoT +n7lJ2bGicFMHB2ROnbOb9vnas82gtOtJsGaBslmoaCckp/C5T1eJWTEb+i+vdpPp +wZcmKZovyyRFSE4+NYlU17fEv6DRvuaGBpDcW7QgHJIl45F8QWEM+msee2KE+V4G +z/9vAQ+sOlvsb4mJP1tJIBx9Lb5loVREwCRy2Ha9tnWdDNar8EYkOn8si4snPT+E +3ZCy8mlcZyUkZeiS/HdtydxZfoiwrSRYamd1diQpPhWCeRteQ802a7ds0Y2YzgfF +UaYjNuRQm7zA//hwbXS7ELPyNMU15N00bajlG0tUOQKBgQDnLy01l20OneW6A2cI +DIDyYhy5O7uulsaEtJReUlcjEDMkin8b767q2VZHb//3ZH+ipnRYByUUyYUhdOs2 +DYRGGeAebnH8wpTT4FCYxUsIUpDfB7RwfdBONgaKewTJz/FPswy1Ye0b5H2c6vVi +m2FZ33HQcoZ3wvFFqyGVnMzpOwKBgQDXxL95yoxUGKa8vMzcE3Cn01szh0dFq0sq +cFpM+HWLVr84CItuG9H6L0KaStEEIOiJsxOVpcXfFFhsJvOGhMA4DQTwH4WuXmXp +1PoVMDlV65PYqvhzwL4+QhvZO2bsrEunITXOmU7CI6kilnAN3LuP4HbqZgoX9lqP +I31VYzLupQKBgGEYck9w0s/xxxtR9ILv5XRnepLdoJzaHHR991aKFKjYU/KD7JDK +INfoAhGs23+HCQhCCtkx3wQVA0Ii/erM0II0ueluD5fODX3TV2ZibnoHW2sgrEsW +vFcs36BnvIIaQMptc+f2QgSV+Z/fGsKYadG6Q+39O7au/HB7SHayzWkjAoGBAMgt +Fzslp9TpXd9iBWjzfCOnGUiP65Z+GWkQ/SXFqD+SRir0+m43zzGdoNvGJ23+Hd6K +TdQbDJ0uoe4MoQeepzoZEgi4JeykVUZ/uVfo+nh06yArVf8FxTm7WVzLGGzgV/uA ++wtl/cRtEyAsk1649yW/KHPEIP8kJdYAJeoO8xSlAoGAERMrkFR7KGYZG1eFNRdV +mJMq+Ibxyw8ks/CbiI+n3yUyk1U8962ol2Q0T4qjBmb26L5rrhNQhneM4e8mo9FX +LlQapYkPvkdrqW0Bp72A/UNAvcGTmN7z5OCJGMUutx2hmEAlrYmpLKS8pM/p9zpK +tEOtzsP5GMDYVlEp1jYSjzQ= +-----END PRIVATE KEY----- diff --git a/test/test-nginx.dockerfile b/test/test-nginx.dockerfile new file mode 100644 index 0000000..1065558 --- /dev/null +++ b/test/test-nginx.dockerfile @@ -0,0 +1,19 @@ +ARG BASE_IMAGE + +FROM ${BASE_IMAGE:?required} AS NGINX +ARG PORT +ARG SSL_PORT + +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 diff --git a/test/test-runner.dockerfile b/test/test-runner.dockerfile new file mode 100644 index 0000000..18fc3d3 --- /dev/null +++ b/test/test-runner.dockerfile @@ -0,0 +1,18 @@ +ARG 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 diff --git a/test/test.sh b/test/test.sh new file mode 100755 index 0000000..c726a75 --- /dev/null +++ b/test/test.sh @@ -0,0 +1,387 @@ +#!/bin/bash -eu + +# 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 + + curlCommand="curl -skv ${scheme}://nginx:${port}${path} -H 'Cache-Control: no-cache' ${extraCurlOpts} 2>&1" + response=$(eval "${curlCommand}") + exitCode=$? + + printf "\n${testNum}" + + if [ "${exitCode}" -ne "0" ]; then + printf "${RED}${name} -- unexpected exit code from cURL\n\tcURL Exit Code: ${exitCode}"; + NUM_FAILED=$((${NUM_FAILED} + 1)); + else + local okay=1 + + if [ "${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//%/%%}" + 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 '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 to response body' \ + -p '/secure/extract-claim/body/sub' \ + -c 200 \ + -r 'sub: some-long-uuid$' \ + -x '--header "Authorization: Bearer ${JWT_HS256_VALID}"' + + run_test -n 'extracts multiple claims to response body' \ + -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' + + 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 From 7085f5c91503a92861797a266724be5bf2f3df5f Mon Sep 17 00:00:00 2001 From: Josh McCullough Date: Tue, 18 Feb 2025 11:23:46 -0500 Subject: [PATCH 70/75] update example config --- README.md | 2 +- {resources => examples}/nginx.conf | 9 +++++++++ 2 files changed, 10 insertions(+), 1 deletion(-) rename {resources => examples}/nginx.conf (70%) diff --git a/README.md b/README.md index f6b09d6..0fb2ea8 100644 --- a/README.md +++ b/README.md @@ -12,7 +12,7 @@ This module depends on the [JWT C Library](https://github.com/benmcollins/libjwt ## Directives -This module requires several new `nginx.conf` directives, which can be specified at the `http`, `server`, or `location` levels. +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 | | ------------------------------------ | ---------------------------------------------------------------------------------------------------------------------------------------------------------- | diff --git a/resources/nginx.conf b/examples/nginx.conf similarity index 70% rename from resources/nginx.conf rename to examples/nginx.conf index 9b8feab..9795363 100644 --- a/resources/nginx.conf +++ b/examples/nginx.conf @@ -18,6 +18,15 @@ http { '"$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; From a110d0c34924d6af8b8602c37b64e41ee1bd2910 Mon Sep 17 00:00:00 2001 From: Josh McCullough Date: Tue, 18 Feb 2025 12:35:01 -0500 Subject: [PATCH 71/75] update NGINX versions to build against --- .github/workflows/make-releases.yml | 9 ++++++++- scripts | 19 ++++++++++--------- test/test-nginx.dockerfile | 2 +- 3 files changed, 19 insertions(+), 11 deletions(-) diff --git a/.github/workflows/make-releases.yml b/.github/workflows/make-releases.yml index 487c4bf..d285bd8 100644 --- a/.github/workflows/make-releases.yml +++ b/.github/workflows/make-releases.yml @@ -30,7 +30,14 @@ jobs: strategy: matrix: # NGINX versions to build/test against - nginx-version: ['1.20.2', '1.22.1', '1.24.0', '1.26.2', '1.27.3'] + nginx-version: + - '1.20.2' # legacy + - '1.22.1' # legacy + - '1.24.0' # legacy + - '1.26.2' # stable + - '1.26.3' # stable + - '1.27.3' # mainline + - '1.27.4' # mainline # The following versions of libjwt are compatible: # * v1.0 - v1.12.0 diff --git a/scripts b/scripts index 59fc5c3..0d80ed5 100755 --- a/scripts +++ b/scripts @@ -19,17 +19,18 @@ SSL_IMAGE_MAP[$SSL_VERSION_3_0_11]="bookworm-slim:openssl-${SSL_VERSION_3_0_11}" SSL_IMAGE_MAP[$SSL_VERSION_3_2_1]="bookworm-slim:openssl-${SSL_VERSION_3_2_1}" # supported NGINX versions -- for binary distribution -NGINX_VERSION_LEGACY_1='1.20.2' -NGINX_VERSION_LEGACY_2='1.22.1' -NGINX_VERSION_LEGACY_3='1.24.0' -NGINX_VERSION_STABLE='1.26.2' -NGINX_VERSION_MAINLINE='1.27.3' -NGINX_VERSIONS=(${NGINX_VERSION_LEGACY_1} ${NGINX_VERSION_LEGACY_2} ${NGINX_VERSION_LEGACY_3} ${NGINX_VERSION_STABLE} ${NGINX_VERSION_MAINLINE}) -NGINX_VERSION=${NGINX_VERSION:-${NGINX_VERSION_STABLE}} - +NGINX_VERSIONS=( + '1.20.2' # legacy + '1.22.1' # legacy + '1.24.0' # legacy + '1.26.2' # stable + '1.26.3' # stable + '1.27.3' # mainline + '1.27.4' # mainline +) +NGINX_VERSION=${NGINX_VERSION:-${NGINX_VERSIONS[-1]}} 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' diff --git a/test/test-nginx.dockerfile b/test/test-nginx.dockerfile index 1065558..f8c323f 100644 --- a/test/test-nginx.dockerfile +++ b/test/test-nginx.dockerfile @@ -1,6 +1,6 @@ ARG BASE_IMAGE -FROM ${BASE_IMAGE:?required} AS NGINX +FROM ${BASE_IMAGE:?required} ARG PORT ARG SSL_PORT From 8e4f2af67c5bfd91bb0820d63443cf8a6a33bd48 Mon Sep 17 00:00:00 2001 From: Josh McCullough Date: Tue, 18 Feb 2025 12:48:54 -0500 Subject: [PATCH 72/75] update array style --- .github/workflows/make-releases.yml | 19 +++++++++++-------- scripts | 17 ++++++++--------- 2 files changed, 19 insertions(+), 17 deletions(-) diff --git a/.github/workflows/make-releases.yml b/.github/workflows/make-releases.yml index d285bd8..ab0aa87 100644 --- a/.github/workflows/make-releases.yml +++ b/.github/workflows/make-releases.yml @@ -31,13 +31,13 @@ jobs: matrix: # NGINX versions to build/test against nginx-version: - - '1.20.2' # legacy - - '1.22.1' # legacy - - '1.24.0' # legacy - - '1.26.2' # stable - - '1.26.3' # stable - - '1.27.3' # mainline - - '1.27.4' # mainline + - 1.20.2 # legacy + - 1.22.1 # legacy + - 1.24.0 # legacy + - 1.26.2 # stable + - 1.26.3 # stable + - 1.27.3 # mainline + - 1.27.4 # mainline # The following versions of libjwt are compatible: # * v1.0 - v1.12.0 @@ -47,7 +47,10 @@ jobs: # * Debian and Ubuntu's repos have v1.10.2 # * EPEL has v1.12.1 # This compiles against each version prior to a breaking change and the latest release - libjwt-version: ['1.12.0', '1.14.0', '1.15.3'] + libjwt-version: + - 1.12.0 + - 1.14.0 + - 1.15.3 runs-on: ubuntu-latest steps: diff --git a/scripts b/scripts index 0d80ed5..7ed7557 100755 --- a/scripts +++ b/scripts @@ -20,13 +20,13 @@ SSL_IMAGE_MAP[$SSL_VERSION_3_2_1]="bookworm-slim:openssl-${SSL_VERSION_3_2_1}" # supported NGINX versions -- for binary distribution NGINX_VERSIONS=( - '1.20.2' # legacy - '1.22.1' # legacy - '1.24.0' # legacy - '1.26.2' # stable - '1.26.3' # stable - '1.27.3' # mainline - '1.27.4' # mainline + 1.20.2 # legacy + 1.22.1 # legacy + 1.24.0 # legacy + 1.26.2 # stable + 1.26.3 # stable + 1.27.3 # mainline + 1.27.4 # mainline ) NGINX_VERSION=${NGINX_VERSION:-${NGINX_VERSIONS[-1]}} IMAGE_NAME=${IMAGE_NAME:-nginx-auth-jwt} @@ -139,8 +139,7 @@ make_release() { -C bin/usr/lib64/nginx/modules ngx_http_auth_jwt_module.so > /dev/null } -# Create releases for the current mainline and stable version, as well as the 2 most recent "legacy" versions. -# See: https://nginx.org/en/download.html +# Create releases for all NGINX versions defined in `NGINX_VERSIONS`. make_releases() { local moduleVersion=$(git describe --tags --abbrev=0) From df75972a5554790aac25899e6ec654a8154bd995 Mon Sep 17 00:00:00 2001 From: Josh McCullough Date: Tue, 18 Feb 2025 13:43:23 -0500 Subject: [PATCH 73/75] fix script function calls --- scripts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/scripts b/scripts index 7ed7557..943bebc 100755 --- a/scripts +++ b/scripts @@ -103,7 +103,7 @@ cp_bin() { local stopContainer=0; if [ "$(docker container inspect -f '{{.State.Running}}' ${IMAGE_NAME} | true)" != "true" ]; then - start_nginx + start stopContainer=1 fi @@ -117,7 +117,7 @@ cp_bin() { if [ $stopContainer ]; then printf "${MAGENTA}Stopping NGINX container (${IMAGE_NAME})...${NC}\n" - stop_nginx + stop fi } From e019e2039ce9d1f6abb369ed8aa7179aaba95012 Mon Sep 17 00:00:00 2001 From: Josh McCullough Date: Tue, 18 Feb 2025 17:25:06 -0500 Subject: [PATCH 74/75] clean up & fix build process --- .github/workflows/make-releases.yml | 23 +++--- nginx.dockerfile | 96 +++++++++++++++++-------- scripts | 106 +++++++++++++++++----------- test/test-runner.dockerfile | 2 +- 4 files changed, 143 insertions(+), 84 deletions(-) diff --git a/.github/workflows/make-releases.yml b/.github/workflows/make-releases.yml index ab0aa87..c77edcb 100644 --- a/.github/workflows/make-releases.yml +++ b/.github/workflows/make-releases.yml @@ -29,7 +29,6 @@ jobs: needs: meta strategy: matrix: - # NGINX versions to build/test against nginx-version: - 1.20.2 # legacy - 1.22.1 # legacy @@ -39,14 +38,6 @@ jobs: - 1.27.3 # mainline - 1.27.4 # mainline - # The following versions of libjwt are compatible: - # * v1.0 - v1.12.0 - # * v1.12.1 - v1.14.0 - # * v1.15.0+ - # At the time of writing this: - # * Debian and Ubuntu's repos have v1.10.2 - # * EPEL has v1.12.1 - # This compiles against each version prior to a breaking change and the latest release libjwt-version: - 1.12.0 - 1.14.0 @@ -80,9 +71,10 @@ jobs: - name: Build jansson working-directory: ./jansson run: | - cmake . -DJANSSON_BUILD_SHARED_LIBS=1 -DJANSSON_BUILD_DOCS=OFF && \ - make && \ - make check && \ + set -e + cmake . -DJANSSON_BUILD_SHARED_LIBS=1 -DJANSSON_BUILD_DOCS=OFF + make + make check sudo make install # TODO cache the build result so we don't have to do this every time? @@ -96,9 +88,10 @@ jobs: - name: Build libjwt working-directory: ./libjwt run: | - autoreconf -i && \ - ./configure && \ - make all && \ + set -e + autoreconf -i + ./configure + make all sudo make install - name: Download NGINX diff --git a/nginx.dockerfile b/nginx.dockerfile index 360469b..68c67f0 100644 --- a/nginx.dockerfile +++ b/nginx.dockerfile @@ -1,24 +1,42 @@ -ARG BASE_IMAGE +ARG BASE_IMAGE=${:?required} ARG NGINX_VERSION +ARG LIBJWT_VERSION -FROM ${BASE_IMAGE} AS ngx_http_auth_jwt_builder_base +FROM ${BASE_IMAGE} AS ngx_http_auth_jwt_builder LABEL stage=ngx_http_auth_jwt_builder -RUN chmod 1777 /tmp +ENV PATH="${PATH}:/etc/nginx" +ENV LD_LIBRARY_PATH=/usr/local/lib +ARG NGINX_VERSION +ARG LIBJWT_VERSION + RUN <<` -apt-get update -apt-get install -y curl build-essential + set -e + apt-get update + apt-get upgrade -y ` -FROM ngx_http_auth_jwt_builder_base AS ngx_http_auth_jwt_builder_module -LABEL stage=ngx_http_auth_jwt_builder -ENV PATH "${PATH}:/etc/nginx" -ENV LD_LIBRARY_PATH=/usr/local/lib -ARG NGINX_VERSION +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 - apt-get install -y libjwt-dev libjwt0 libjansson-dev libjansson4 libpcre2-dev zlib1g-dev libpcre3-dev - mkdir -p /root/build/ngx-http-auth-jwt-module + 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/ @@ -29,6 +47,7 @@ RUN <<` 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 <<` set -e @@ -89,30 +108,46 @@ RUN <<` ${BUILD_FLAGS} # --with-openssl=/usr/local \ ` + RUN make modules RUN make install -WORKDIR /usr/lib64/nginx/modules -RUN cp /root/build/nginx/objs/ngx_http_auth_jwt_module.so . + +WORKDIR /usr/lib/nginx/modules +RUN mv /root/build/nginx/objs/ngx_http_auth_jwt_module.so . RUN rm -rf /root/build -RUN adduser --system --no-create-home --shell /bin/false --group --disabled-login nginx -RUN mkdir -p /var/cache/nginx /var/log/nginx -WORKDIR /etc/nginx -FROM ngx_http_auth_jwt_builder_module AS ngx_http_auth_jwt_nginx -LABEL maintainer="TeslaGov" email="developers@teslagov.com" -ARG NGINX_VERSION RUN <<` set -e - - apt-get update - apt-get install -y libjansson4 libjwt0 + 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 +` + +# RUN <<` +# set -e +# curl https://nginx.org/keys/nginx_signing.key | gpg --dearmor > /usr/share/keyrings/nginx-archive-keyring.gpg +# printf "deb [signed-by=/usr/share/keyrings/nginx-archive-keyring.gpg] http://nginx.org/packages/debian `lsb_release -cs` nginx\n" > /etc/apt/sources.list.d/nginx.list +# printf "Package: *\nPin: origin nginx.org\nPin: release o=nginx\nPin-Priority: 900\n" > /etc/apt/preferences.d/99nginx +# ` + +# RUN <<` +# set -e +# apt-get update +# apt-get install -y nginx +# ` + COPY <<` /etc/nginx/nginx.conf +daemon off; user nginx; pid /var/run/nginx.pid; -load_module /usr/lib64/nginx/modules/ngx_http_auth_jwt_module.so; +load_module /usr/lib/nginx/modules/ngx_http_auth_jwt_module.so; worker_processes 1; @@ -124,12 +159,17 @@ 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"'; + 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; } ` -ENTRYPOINT ["nginx", "-g", "daemon off;"] + +WORKDIR /var/cache/nginx +RUN chown nginx:nginx . + +WORKDIR / +CMD ["nginx"] diff --git a/scripts b/scripts index 943bebc..ca48984 100755 --- a/scripts +++ b/scripts @@ -8,14 +8,17 @@ NC='\033[0m' # supported SSL versions SSL_VERSION_1_1_1w='1.1.1w' -SSL_VERSION_3_0_11='3.0.11' +SSL_VERSION_3_0_15='3.0.15' SSL_VERSION_3_2_1='3.2.1' -SSL_VERSIONS=(${SSL_VERSION_3_2_1}) -SSL_VERSION=${SSL_VERSION:-$SSL_VERSION_3_0_11} +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_11]="bookworm-slim:openssl-${SSL_VERSION_3_0_11}" +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 @@ -28,7 +31,27 @@ NGINX_VERSIONS=( 1.27.3 # mainline 1.27.4 # mainline ) + +# The following versions of libjwt are compatible: +# * v1.0 - v1.12.0 +# * v1.12.1 - v1.14.0 +# * v1.15.0+ +# At the time of writing this: +# * Debian and Ubuntu's repos have v1.10.2 +# * EPEL has v1.12.1 +# This compiles against each version prior to a breaking change and the latest release +LIBJWT_VERSION_DEBIAN=1.12.0 +LIBJWT_VERSION_EPEL=1.14.0 +LIBJWT_VERSION_LATEST=1.15.3 +LIBJWT_VERSIONS=( + ${LIBJWT_VERSION_DEBIAN} + ${LIBJWT_VERSION_EPEL} + ${LIBJWT_VERSION_LATEST} +) + +SSL_VERSION=${SSL_VERSION:-$SSL_VERSION_3_0_15} NGINX_VERSION=${NGINX_VERSION:-${NGINX_VERSIONS[-1]}} +LIBJWT_VERSION=${LIBJWT_VERSION:-${LIBJWT_VERSION_DEBIAN}} IMAGE_NAME=${IMAGE_NAME:-nginx-auth-jwt} FULL_IMAGE_NAME=${ORG_NAME:-teslagov}/${IMAGE_NAME} TEST_CONTAINER_NAME_PREFIX="${IMAGE_NAME}-test" @@ -40,7 +63,7 @@ all() { test_all } -verify_and_build_base_image() { +build_base_image() { local image=${SSL_IMAGE_MAP[$SSL_VERSION]} local baseImage=${image%%:*} @@ -53,23 +76,24 @@ verify_and_build_base_image() { --build-arg BASE_IMAGE=debian:${baseImage} \ --build-arg SSL_VERSION=${SSL_VERSION} \ -f openssl.dockerfile \ - -t ${image} . + -t ${image} \ + . fi } build_module() { - local dockerArgs=${1:-} local baseImage=${SSL_IMAGE_MAP[$SSL_VERSION]} - - verify_and_build_base_image - printf "${MAGENTA}Building module for NGINX ${NGINX_VERSION}...${NC}\n" + build_base_image + + printf "${MAGENTA}Building module for NGINX ${NGINX_VERSION}, libjwt ${LIBJWT_VERSION}...${NC}\n" docker buildx build \ -f nginx.dockerfile \ -t ${FULL_IMAGE_NAME}:${NGINX_VERSION} \ --build-arg BASE_IMAGE=${baseImage} \ --build-arg NGINX_VERSION=${NGINX_VERSION} \ - ${dockerArgs} . + --build-arg LIBJWT_VERSION=${LIBJWT_VERSION} \ + . if [ "$?" -ne 0 ]; then printf "${RED}✘ Build failed ${NC}\n" @@ -79,12 +103,9 @@ build_module() { } rebuild_module() { - clean_module - build_module --no-cache -} - -clean_module() { docker rmi -f $(docker images --filter=label=stage=ngx_http_auth_jwt_builder --quiet) 2> /dev/null || true + + build_module } start() { @@ -111,9 +132,9 @@ cp_bin() { rm -rf ${destDir}/* mkdir -p ${destDir} docker exec "${IMAGE_NAME}" sh -c "cd /; tar -chf - \ - usr/lib64/nginx/modules/ngx_http_auth_jwt_module.so \ - usr/lib/$(uname -m)-linux-gnu/libjansson.so.* \ - usr/lib/$(uname -m)-linux-gnu/libjwt.*" | tar -xf - -C ${destDir} &>/dev/null + 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 if [ $stopContainer ]; then printf "${MAGENTA}Stopping NGINX container (${IMAGE_NAME})...${NC}\n" @@ -122,31 +143,30 @@ cp_bin() { } make_release() { - local moduleVersion=${1} - - NGINX_VERSION=${2} + local moduleVersion=$(git describe --tags --abbrev=0) printf "${MAGENTA}Making release for version ${moduleVersion} for NGINX ${NGINX_VERSION}...${NC}\n" rebuild_module rebuild_test - test + test --no-build cp_bin mkdir -p release - tar -czvf release/ngx_http_auth_jwt_module_${moduleVersion}_nginx_${NGINX_VERSION}.tgz \ + tar -czvf release/ngx-http-auth-jwt-module-${moduleVersion}_libjwt-${LIBJWT_VERSION}_nginx-${NGINX_VERSION}.tgz \ README.md \ - -C bin/usr/lib64/nginx/modules ngx_http_auth_jwt_module.so > /dev/null + -C bin/usr/lib/nginx/modules ngx_http_auth_jwt_module.so > /dev/null } # Create releases for all NGINX versions defined in `NGINX_VERSIONS`. make_releases() { - local moduleVersion=$(git describe --tags --abbrev=0) - rm -rf release/* - for v in ${NGINX_VERSIONS[@]}; do - make_release ${moduleVersion} ${v} + for NGINX_VERSION in ${NGINX_VERSIONS[@]}; do + for LIBJWT_VERSION in ${LIBJWT_VERSIONS[@]}; do + export NGINX_VERSION LIBJWT_VERSION + make_release + done done } @@ -178,14 +198,21 @@ rebuild_test() { test_all() { for SSL_VERSION in "${SSL_VERSIONS[@]}"; do for NGINX_VERSION in "${NGINX_VERSIONS[@]}"; do - test + for LIBJWT_VERSION in ${LIBJWT_VERSIONS[@]}; do + export SSL_VERSION NGINX_VERSION LIBJWT_VERSION + test + done done done } test() { - build_module - build_test + if [[ ! "$*" =~ --no-build ]]; then + build_module + build_test + fi + + trap 'test_cleanup' 0 printf "${MAGENTA}Running tests...${NC}\n" docker compose \ @@ -193,8 +220,6 @@ test() { -f ${TEST_COMPOSE_FILE} up \ --no-start - trap test_cleanup 0 - test_now } @@ -202,21 +227,22 @@ test_now() { nginxContainerName="${TEST_CONTAINER_NAME_PREFIX}-nginx" runnerContainerName="${TEST_CONTAINER_NAME_PREFIX}-runner" + echo + echo "Executing tests with the following options:" + echo " SSL Version: ${SSL_VERSION}" + echo " LIBJWT Version: ${LIBJWT_VERSION}" + echo " NGINX Version: ${NGINX_VERSION}" + docker start ${nginxContainerName} if [ "$(docker container inspect -f '{{.State.Running}}' ${nginxContainerName})" != "true" ]; then printf "${RED}Failed to start container \"${nginxContainerName}\". See logs below:\n" docker logs ${nginxContainerName} printf "${NC}\n" - return + return 1 fi docker start -a ${runnerContainerName} - - echo - echo "Tests were executed with the following options:" - echo " SSL Version: ${SSL_VERSION}" - echo " NGINX Version: ${NGINX_VERSION}" } test_cleanup() { diff --git a/test/test-runner.dockerfile b/test/test-runner.dockerfile index 18fc3d3..377c40e 100644 --- a/test/test-runner.dockerfile +++ b/test/test-runner.dockerfile @@ -15,4 +15,4 @@ RUN <<` COPY test.sh . -CMD ./test.sh +CMD ["./test.sh"] From b128954b6c3fc95d8538e75abac637405bdbd562 Mon Sep 17 00:00:00 2001 From: Josh McCullough Date: Tue, 29 Jul 2025 16:02:03 -0400 Subject: [PATCH 75/75] specify platform / fix spacing --- scripts | 336 ++++++++++++++++++----------------- test/docker-compose-test.yml | 8 +- 2 files changed, 176 insertions(+), 168 deletions(-) diff --git a/scripts b/scripts index ca48984..7ce7024 100755 --- a/scripts +++ b/scripts @@ -11,9 +11,9 @@ 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} + ${SSL_VERSION_1_1_1w} + ${SSL_VERSION_3_0_15} + ${SSL_VERSION_3_2_1} ) declare -A SSL_IMAGE_MAP @@ -23,13 +23,13 @@ SSL_IMAGE_MAP[$SSL_VERSION_3_2_1]="bookworm-slim:openssl-${SSL_VERSION_3_2_1}" # supported NGINX versions -- for binary distribution NGINX_VERSIONS=( - 1.20.2 # legacy - 1.22.1 # legacy - 1.24.0 # legacy - 1.26.2 # stable - 1.26.3 # stable - 1.27.3 # mainline - 1.27.4 # mainline + 1.20.2 # legacy + 1.22.1 # legacy + 1.24.0 # legacy + 1.26.2 # stable + 1.26.3 # stable + 1.27.3 # mainline + 1.27.4 # mainline ) # The following versions of libjwt are compatible: @@ -44,9 +44,9 @@ LIBJWT_VERSION_DEBIAN=1.12.0 LIBJWT_VERSION_EPEL=1.14.0 LIBJWT_VERSION_LATEST=1.15.3 LIBJWT_VERSIONS=( - ${LIBJWT_VERSION_DEBIAN} - ${LIBJWT_VERSION_EPEL} - ${LIBJWT_VERSION_LATEST} + ${LIBJWT_VERSION_DEBIAN} + ${LIBJWT_VERSION_EPEL} + ${LIBJWT_VERSION_LATEST} ) SSL_VERSION=${SSL_VERSION:-$SSL_VERSION_3_0_15} @@ -58,216 +58,218 @@ TEST_CONTAINER_NAME_PREFIX="${IMAGE_NAME}-test" TEST_COMPOSE_FILE='test/docker-compose-test.yml' all() { - build_module - build_test - test_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 \ - --build-arg BASE_IMAGE=debian:${baseImage} \ - --build-arg SSL_VERSION=${SSL_VERSION} \ - -f openssl.dockerfile \ - -t ${image} \ - . - fi + 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 \ - -f nginx.dockerfile \ - -t ${FULL_IMAGE_NAME}:${NGINX_VERSION} \ - --build-arg BASE_IMAGE=${baseImage} \ - --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 + 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 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 + docker rmi -f $(docker images --filter=label=stage=ngx_http_auth_jwt_builder --quiet) 2> /dev/null || true - build_module + build_module } start() { - local port=$(get_port) + 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 + 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 + docker stop "${IMAGE_NAME}" >/dev/null } cp_bin() { - local destDir=bin - local stopContainer=0; - - 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 - - if [ $stopContainer ]; then - printf "${MAGENTA}Stopping NGINX container (${IMAGE_NAME})...${NC}\n" - stop - fi + local destDir=bin + local stopContainer=0; + + 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 + + if [ $stopContainer ]; then + printf "${MAGENTA}Stopping NGINX container (${IMAGE_NAME})...${NC}\n" + stop + fi } make_release() { - local moduleVersion=$(git describe --tags --abbrev=0) + local moduleVersion=$(git describe --tags --abbrev=0) - printf "${MAGENTA}Making release for version ${moduleVersion} for NGINX ${NGINX_VERSION}...${NC}\n" + printf "${MAGENTA}Making release for version ${moduleVersion} for NGINX ${NGINX_VERSION}...${NC}\n" - rebuild_module - rebuild_test - test --no-build - cp_bin + rebuild_module + rebuild_test + test --no-build + cp_bin - mkdir -p release - tar -czvf release/ngx-http-auth-jwt-module-${moduleVersion}_libjwt-${LIBJWT_VERSION}_nginx-${NGINX_VERSION}.tgz \ - README.md \ - -C bin/usr/lib/nginx/modules ngx_http_auth_jwt_module.so > /dev/null + mkdir -p release + tar -czvf release/ngx-http-auth-jwt-module-${moduleVersion}_libjwt-${LIBJWT_VERSION}_nginx-${NGINX_VERSION}.tgz \ + README.md \ + -C bin/usr/lib/nginx/modules ngx_http_auth_jwt_module.so > /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 + 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} \ - ${dockerArgs} + 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} \ + ${dockerArgs} } rebuild_test() { - build_test --no-cache + 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 + 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 + if [[ ! "$*" =~ --no-build ]]; then + build_module + build_test + fi - trap 'test_cleanup' 0 + trap 'test_cleanup' 0 - printf "${MAGENTA}Running tests...${NC}\n" - docker compose \ - -p ${TEST_CONTAINER_NAME_PREFIX} \ - -f ${TEST_COMPOSE_FILE} up \ - --no-start + printf "${MAGENTA}Running tests...${NC}\n" + docker compose \ + -p ${TEST_CONTAINER_NAME_PREFIX} \ + -f ${TEST_COMPOSE_FILE} up \ + --no-start - test_now + test_now } test_now() { - nginxContainerName="${TEST_CONTAINER_NAME_PREFIX}-nginx" - runnerContainerName="${TEST_CONTAINER_NAME_PREFIX}-runner" - - echo - echo "Executing tests with the following options:" - echo " SSL Version: ${SSL_VERSION}" - echo " LIBJWT Version: ${LIBJWT_VERSION}" - echo " NGINX Version: ${NGINX_VERSION}" - - docker start ${nginxContainerName} - - if [ "$(docker container inspect -f '{{.State.Running}}' ${nginxContainerName})" != "true" ]; then - printf "${RED}Failed to start container \"${nginxContainerName}\". See logs below:\n" - docker logs ${nginxContainerName} - printf "${NC}\n" - return 1 - fi - - docker start -a ${runnerContainerName} + nginxContainerName="${TEST_CONTAINER_NAME_PREFIX}-nginx" + runnerContainerName="${TEST_CONTAINER_NAME_PREFIX}-runner" + + echo + echo "Executing tests with the following options:" + echo " SSL Version: ${SSL_VERSION}" + echo " LIBJWT Version: ${LIBJWT_VERSION}" + echo " NGINX Version: ${NGINX_VERSION}" + + docker start ${nginxContainerName} + + if [ "$(docker container inspect -f '{{.State.Running}}' ${nginxContainerName})" != "true" ]; then + printf "${RED}Failed to start container \"${nginxContainerName}\". See logs below:\n" + docker logs ${nginxContainerName} + printf "${NC}\n" + return 1 + fi + + docker start -a ${runnerContainerName} } test_cleanup() { - docker compose \ - -p ${TEST_CONTAINER_NAME_PREFIX} \ - -f ${TEST_COMPOSE_FILE} down + 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 ! ss -ln | grep -q ":${p} "; then - echo ${p} - break - fi - done + startPort=${1:-8000} + endPort=$((startPort + 100)) + + for p in $(seq ${startPort} ${endPort}); do + if ! ss -ln | grep -q ":${p} "; then + echo ${p} + break + fi + done } if [ $# -eq 0 ]; then - all + all else - fn=$1 - shift - - ${fn} "$@" + fn=$1 + shift + + ${fn} "$@" fi diff --git a/test/docker-compose-test.yml b/test/docker-compose-test.yml index 72ff710..cc570c5 100644 --- a/test/docker-compose-test.yml +++ b/test/docker-compose-test.yml @@ -5,8 +5,11 @@ services: build: context: . dockerfile: test-nginx.dockerfile + platforms: + - linux/amd64 args: BASE_IMAGE: ${FULL_IMAGE_NAME}:${NGINX_VERSION:?required} + platform: linux/amd64 logging: driver: ${LOG_DRIVER:-journald} @@ -15,5 +18,8 @@ services: build: context: . dockerfile: test-runner.dockerfile + platforms: + - linux/amd64 + platform: linux/amd64 depends_on: - - nginx \ No newline at end of file + - nginx