Skip to content

Commit f560e1e

Browse files
committed
First revision
First revision
0 parents  commit f560e1e

File tree

2 files changed

+313
-0
lines changed

2 files changed

+313
-0
lines changed

README.md

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
# Intro
2+
This is an NGINX module to check for a valid JWT and proxy to an upstream server or redirect to a login page.
3+
4+
# Build Requirements
5+
This module depends on the [JWT C Library](https://github.com/benmcollins/libjwt)
6+
7+
Transitively, that library depends on a JSON Parser called [Jansson](https://github.com/akheron/jansson) as well as OpenSSL
8+
9+
# NGINX Directives
10+
This module requires several new nginx.conf directives, which can be specified in on the `main` `server` or `___location` level.
11+
12+
```
13+
auth_jwt_key "00112233445566778899AABBCCDDEEFF00112233445566778899AABBCCDDEEFF";
14+
auth_jwt_loginurl "https://yourdomain.com/loginpage";
15+
auth_jwt_enabled on;
16+
```
17+
18+
So, 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).
19+
20+
To compile nginx with this module, use an `--add-module` option to `configure`
21+
22+
```
23+
./configure --add-module=path/to/this/module/directory
24+
```

src/ngx_http_auth_jwt_module.c

Lines changed: 289 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,289 @@
1+
/*
2+
* Tesla Government
3+
* @author joefitz
4+
*/
5+
6+
#include <ngx_config.h>
7+
#include <ngx_core.h>
8+
#include <ngx_http.h>
9+
#include <jwt.h>
10+
11+
#include <jansson.h>
12+
13+
typedef struct {
14+
ngx_str_t auth_jwt_loginurl;
15+
ngx_str_t auth_jwt_key;
16+
ngx_flag_t auth_jwt_enabled;
17+
} ngx_http_auth_jwt_loc_conf_t;
18+
19+
static ngx_int_t ngx_http_auth_jwt_init(ngx_conf_t *cf);
20+
static ngx_int_t ngx_http_auth_jwt_handler(ngx_http_request_t *r);
21+
static void * ngx_http_auth_jwt_create_loc_conf(ngx_conf_t *cf);
22+
static char * ngx_http_auth_jwt_merge_loc_conf(ngx_conf_t *cf, void *parent, void *child);
23+
static int hex_char_to_binary( char ch, char* ret );
24+
static int hex_to_binary( const char* str, u_char* buf, int len );
25+
26+
static ngx_command_t ngx_http_auth_jwt_commands[] = {
27+
28+
{ ngx_string("auth_jwt_loginurl"),
29+
NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_TAKE1,
30+
ngx_conf_set_str_slot,
31+
NGX_HTTP_LOC_CONF_OFFSET,
32+
offsetof(ngx_http_auth_jwt_loc_conf_t, auth_jwt_loginurl),
33+
NULL },
34+
35+
{ ngx_string("auth_jwt_key"),
36+
NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_TAKE1,
37+
ngx_conf_set_str_slot,
38+
NGX_HTTP_LOC_CONF_OFFSET,
39+
offsetof(ngx_http_auth_jwt_loc_conf_t, auth_jwt_key),
40+
NULL },
41+
42+
{ ngx_string("auth_jwt_enabled"),
43+
NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_FLAG,
44+
ngx_conf_set_flag_slot,
45+
NGX_HTTP_LOC_CONF_OFFSET,
46+
offsetof(ngx_http_auth_jwt_loc_conf_t, auth_jwt_enabled),
47+
NULL },
48+
49+
ngx_null_command
50+
};
51+
52+
53+
static ngx_http_module_t ngx_http_auth_jwt_module_ctx = {
54+
NULL, /* preconfiguration */
55+
ngx_http_auth_jwt_init, /* postconfiguration */
56+
57+
NULL, /* create main configuration */
58+
NULL, /* init main configuration */
59+
60+
NULL, /* create server configuration */
61+
NULL, /* merge server configuration */
62+
63+
ngx_http_auth_jwt_create_loc_conf, /* create ___location configuration */
64+
ngx_http_auth_jwt_merge_loc_conf /* merge ___location configuration */
65+
};
66+
67+
68+
ngx_module_t ngx_http_auth_jwt_module = {
69+
NGX_MODULE_V1,
70+
&ngx_http_auth_jwt_module_ctx, /* module context */
71+
ngx_http_auth_jwt_commands, /* module directives */
72+
NGX_HTTP_MODULE, /* module type */
73+
NULL, /* init master */
74+
NULL, /* init module */
75+
NULL, /* init process */
76+
NULL, /* init thread */
77+
NULL, /* exit thread */
78+
NULL, /* exit process */
79+
NULL, /* exit master */
80+
NGX_MODULE_V1_PADDING
81+
};
82+
83+
84+
static ngx_int_t ngx_http_auth_jwt_handler(ngx_http_request_t *r)
85+
{
86+
ngx_int_t n;
87+
ngx_str_t jwtCookieName = ngx_string("rampartjwt");
88+
ngx_str_t jwtCookieVal;
89+
char* jwtCookieValChrPtr;
90+
ngx_http_auth_jwt_loc_conf_t *jwtcf;
91+
u_char *keyBinary;
92+
jwt_t *jwt;
93+
int jwtParseReturnCode;
94+
jwt_alg_t alg;
95+
time_t exp;
96+
time_t now;
97+
ngx_str_t passportKeyCookieName = ngx_string("PassportKey");
98+
ngx_str_t passportKeyCookieVal;
99+
100+
101+
jwtcf = ngx_http_get_module_loc_conf(r, ngx_http_auth_jwt_module);
102+
103+
if (!jwtcf->auth_jwt_enabled)
104+
{
105+
return NGX_DECLINED;
106+
}
107+
108+
// ngx_log_error(NGX_LOG_ERR, r->connection->log, 0, "Key: %s, Enabled: %d",
109+
// jwtcf->auth_jwt_key.data,
110+
// jwtcf->auth_jwt_enabled);
111+
112+
// get the cookie
113+
// TODO: the cookie name could be passed in dynamicallly
114+
n = ngx_http_parse_multi_header_lines(&r->headers_in.cookies, &jwtCookieName, &jwtCookieVal);
115+
if (n == NGX_DECLINED)
116+
{
117+
ngx_log_error(NGX_LOG_ERR, r->connection->log, 0, "failed to obtain rampartjwt cookie");
118+
goto redirect;
119+
}
120+
121+
// the cookie data is not necessarily null terminated... we need a null terminated character pointer
122+
jwtCookieValChrPtr = ngx_alloc(jwtCookieVal.len + 1, r->connection->log);
123+
ngx_memcpy(jwtCookieValChrPtr, jwtCookieVal.data, jwtCookieVal.len);
124+
*(jwtCookieValChrPtr+jwtCookieVal.len) = '\0';
125+
126+
// ngx_log_error(NGX_LOG_ERR, r->connection->log, 0, "rampartjwt: %s %d", jwtCookieValChrPtr, jwtCookieVal.len);
127+
128+
// convert key from hex to binary
129+
keyBinary = ngx_alloc(jwtcf->auth_jwt_key.len / 2, r->connection->log);
130+
if (0 != hex_to_binary((char *)jwtcf->auth_jwt_key.data, keyBinary, jwtcf->auth_jwt_key.len))
131+
{
132+
ngx_log_error(NGX_LOG_ERR, r->connection->log, 0, "failed to turn hex key into binary");
133+
goto redirect;
134+
}
135+
136+
// validate the jwt
137+
jwtParseReturnCode = jwt_decode(&jwt, jwtCookieValChrPtr, keyBinary, jwtcf->auth_jwt_key.len / 2);
138+
if (jwtParseReturnCode != 0)
139+
{
140+
ngx_log_error(NGX_LOG_ERR, r->connection->log, 0, "failed to parse jwt");
141+
goto redirect;
142+
}
143+
144+
// ngx_log_error(NGX_LOG_ERR, r->connection->log, 0, "parsed jwt:\n%s", jwt_dump_str(jwt, 1));
145+
146+
// validate the algorithm
147+
alg = jwt_get_alg(jwt);
148+
if (alg != JWT_ALG_HS256)
149+
{
150+
ngx_log_error(NGX_LOG_ERR, r->connection->log, 0, "invalid algorithm in jwt %d", alg);
151+
goto redirect;
152+
}
153+
154+
// validate the exp date of the JWT
155+
exp = (time_t)jwt_get_grant_int(jwt, "exp");
156+
now = time(NULL);
157+
if (exp < now)
158+
{
159+
ngx_log_error(NGX_LOG_ERR, r->connection->log, 0, "the jwt has expired");
160+
goto redirect;
161+
}
162+
163+
// ensure that the user has a matching PassportKey cookie.
164+
// this can be removed once we and our partners no longer use the PassportKey cookie
165+
n = ngx_http_parse_multi_header_lines(&r->headers_in.cookies, &passportKeyCookieName, &passportKeyCookieVal);
166+
if (n == NGX_DECLINED) {
167+
ngx_log_error(NGX_LOG_ERR, r->connection->log, 0, "failed to obtain passport cookie");
168+
goto redirect;
169+
};
170+
171+
// compare both cookies
172+
if (ngx_strncmp(jwtCookieVal.data, passportKeyCookieVal.data, jwtCookieVal.len))
173+
{
174+
ngx_log_error(NGX_LOG_ERR, r->connection->log, 0, "PassportKey cookie does not match rampartjwt cookie");
175+
goto redirect;
176+
}
177+
178+
return NGX_OK;
179+
180+
redirect:
181+
r->headers_out.___location = ngx_list_push(&r->headers_out.headers);
182+
if (r->headers_out.___location == NULL) {
183+
ngx_http_finalize_request(r, NGX_HTTP_INTERNAL_SERVER_ERROR);
184+
}
185+
r->headers_out.___location->hash = 1;
186+
r->headers_out.___location->key.len = sizeof("Location") - 1;
187+
r->headers_out.___location->key.data = (u_char *) "Location";
188+
r->headers_out.___location->value.len = jwtcf->auth_jwt_loginurl.len;
189+
r->headers_out.___location->value.data = jwtcf->auth_jwt_loginurl.data;
190+
return NGX_HTTP_MOVED_PERMANENTLY;
191+
}
192+
193+
194+
static ngx_int_t ngx_http_auth_jwt_init(ngx_conf_t *cf)
195+
{
196+
ngx_http_handler_pt *h;
197+
ngx_http_core_main_conf_t *cmcf;
198+
199+
cmcf = ngx_http_conf_get_module_main_conf(cf, ngx_http_core_module);
200+
201+
h = ngx_array_push(&cmcf->phases[NGX_HTTP_ACCESS_PHASE].handlers);
202+
if (h == NULL) {
203+
return NGX_ERROR;
204+
}
205+
206+
*h = ngx_http_auth_jwt_handler;
207+
208+
return NGX_OK;
209+
}
210+
211+
212+
static void *
213+
ngx_http_auth_jwt_create_loc_conf(ngx_conf_t *cf)
214+
{
215+
ngx_http_auth_jwt_loc_conf_t *conf;
216+
217+
conf = ngx_pcalloc(cf->pool, sizeof(ngx_http_auth_jwt_loc_conf_t));
218+
if (conf == NULL) {
219+
return NULL;
220+
}
221+
222+
// set the flag to unset
223+
conf->auth_jwt_enabled = (ngx_flag_t) -1;
224+
225+
ngx_conf_log_error(NGX_LOG_DEBUG, cf, 0, "Created Location Configuration");
226+
227+
return conf;
228+
}
229+
230+
231+
static char *
232+
ngx_http_auth_jwt_merge_loc_conf(ngx_conf_t *cf, void *parent, void *child)
233+
{
234+
ngx_http_auth_jwt_loc_conf_t *prev = parent;
235+
ngx_http_auth_jwt_loc_conf_t *conf = child;
236+
237+
ngx_conf_merge_str_value(conf->auth_jwt_loginurl, prev->auth_jwt_loginurl, "");
238+
ngx_conf_merge_str_value(conf->auth_jwt_key, prev->auth_jwt_key, "");
239+
240+
241+
if (conf->auth_jwt_enabled == ((ngx_flag_t) -1)) {
242+
conf->auth_jwt_enabled = (prev->auth_jwt_enabled == ((ngx_flag_t) -1)) ? 0 : prev->auth_jwt_enabled;
243+
}
244+
245+
ngx_conf_log_error(NGX_LOG_DEBUG, cf, 0, "Merged Location Configuration");
246+
247+
// ngx_conf_log_error(NGX_LOG_ERR, cf, 0, "Key: %s, Enabled: %d",
248+
// conf->auth_jwt_key.data,
249+
// conf->auth_jwt_enabled);
250+
return NGX_CONF_OK;
251+
}
252+
253+
static int
254+
hex_char_to_binary( char ch, char* ret )
255+
{
256+
ch = tolower( ch );
257+
if( isdigit( ch ) )
258+
*ret = ch - '0';
259+
else if( ch >= 'a' && ch <= 'f' )
260+
*ret = ( ch - 'a' ) + 10;
261+
else if( ch >= 'A' && ch <= 'F' )
262+
*ret = ( ch - 'A' ) + 10;
263+
else
264+
return *ret = 0;
265+
return 1;
266+
}
267+
268+
static int
269+
hex_to_binary( const char* str, u_char* buf, int len ) {
270+
u_char
271+
*cpy = buf;
272+
char
273+
low,
274+
high;
275+
int
276+
odd = len % 2;
277+
278+
if (odd) {
279+
return -1;
280+
}
281+
282+
for (int i = 0; i < len; i += 2) {
283+
hex_char_to_binary( *(str + i), &high );
284+
hex_char_to_binary( *(str + i + 1 ), &low );
285+
286+
*cpy++ = low | (high << 4);
287+
}
288+
return 0;
289+
}

0 commit comments

Comments
 (0)