Skip to content

PEM key file support #56

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 13 commits into from
Aug 25, 2021
Merged

Conversation

penumbra23
Copy link
Contributor

  • added auth_jwt_use_keyfile and auth_jwt_keyfile_path for loading the PEM encoded public key from file (defaults to "/app/pub_key")

# 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 && \
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I've removed this as the Jansson build passes with cmake installed from previous step (I can return it, but it works also without it)

Dockerfile Outdated
@@ -85,7 +69,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' && \
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Here I've just added -Wno-unused-variable -Wno-error=unused-but-set-variable to suppress errors regarding unused variables (return value of fread isn't used)

@@ -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";
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is this a valid default path? All my docker images are inside /app working directory, just a habit 😄

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think there perhaps shouldn't be a default, and it would be an error case to have auth_jwt_use_keyfile on and auth_jwt_keyfile_path not supplied (e.g. force the user to supply a path).

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Makes sense, but there could also be a auth_jwt_key together with the auth_jwt_keyfile_path which adds ambiguity to what key is being used. Maybe an enum field auth_jwt_key_location (with values inline or file) would solve it also for future situations (for example having a remote keystore where the key is located).

Copy link
Contributor

@fitzyjoe fitzyjoe left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks so much @penumbra23 for the PR! Sorry it has taken me so long to get around to reviewing it. I think it would be an excellent addition to be able to refer to a PEM file instead of including the key's text in the nginx.conf. I left some comments below... I'm most concerned that your PR reads the key from the file every request. I think this would be best handled during the loading of the configuration instead. Also, I each new scenario needs a test in test.sh - maybe something like this:

test_jwt "Secure test with rs256 file" "/secure-rs256-file/" "200" "--header \"Authorization: Bearer ${VALID_RS256_JWT}\""

  test_jwt "Secure test with rs256 file and invalid JWT" "/secure-rs256-file/" "401" "--header \"Authorization: Bearer ${INVALID_RS256_JWT}\"" 

@@ -177,8 +199,45 @@ 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;
ngx_log_error(NGX_LOG_ERR, r->connection->log, 0, "failed to find a jwt");
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think you might have left this debug statement in by mistake... this message will get printed even if a JWT is present.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yup, by mistake while I was testing something. Gonna remove it 👍

}

// Read file length
fseek(file, 0, SEEK_END);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is going to read the public key from a file for every request that NGINX receives. I'm afraid that this would offer poor performance. I think we would need to read from a file when the configuration is loaded instead.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Until this I've never worked with custom Nginx plugins, it's definitely poor performance as the key won't change frequently (changing the key would require a server restart, which makes sense).

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I've tried the init_module and postconfiguration hooks, but it seems they don't load the blocks at that time (since when logging the values from the config it displays the default -1 value). I'll try to put it in the merge_location hook, since it gets called once (maybe a better idea?).

{
char err[100];
strcpy(err, "failed to open pub key file: ");
strcat(err, KEY_FILE_PATH);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This isn't necessarily the value that was specified for the location of the public key... this error outputs the default location of the file.

}

// Read pub key
pub_key = malloc(key_size + 1);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Where is this memory free'd? I think you need to use the ngx_* wrapped versions of malloc to allocate memory.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I saw this mem leak before and changed it on some internal branch, gonna merge it

@penumbra23 penumbra23 requested a review from fitzyjoe August 25, 2021 14:06
@penumbra23
Copy link
Contributor Author

@fitzyjoe I've updated the code and refactored (a bit).
First idea was to have a static keyBin variable to hold the key upon config merging. That was quite okay for one location, but as soon as I added two location with different keyfile, both locations were using the last one loaded...

Now the location config has a (kinda) private _auth_jwt_keyfile field holding the key data and keysize for each specified location block. It's loaded on config merging and reused on each request to improve the performance a bit.

Hope it's okay now, leave more comments because we're using this plugin heavily!

Copy link
Contributor

@fitzyjoe fitzyjoe left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks good @penumbra23. Thanks for the contribution!

@fitzyjoe fitzyjoe merged commit 1653ef1 into TeslaGov:master Aug 25, 2021
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

4 participants