MINOR: jwt: jwt_header_query and jwt_payload_query converters

Those converters allow to extract a JSON value out of a JSON Web Token's
header part or payload part (the two first dot-separated base64url
encoded parts of a JWS in the Compact Serialization format).
They act as a json_query call on the corresponding decoded subpart when
given parameters, and they return the decoded JSON subpart when no
parameter is given.
This commit is contained in:
Remi Tricot-Le Breton 2021-10-01 15:36:57 +02:00 committed by William Lallemand
parent 864089e0a6
commit 0a72f5ee7c
3 changed files with 112 additions and 1 deletions

View File

@ -16617,6 +16617,26 @@ json_query(<json_path>,[<output_type>])
# get the value of the key 'iss' from a JWT Bearer token
http-request set-var(txn.token_payload) req.hdr(Authorization),word(2,.),ub64dec,json_query('$.iss')
jwt_header_query([<json_path>],[<output_type>])
When given a JSON Web Token (JWT) in input, either returns the decoded header
part of the token (the first base64-url encoded part of the JWT) if no
parameter is given, or performs a json_query on the decoded header part of
the token. See "json_query" converter for details about the accepted
json_path and output_type parameters.
Please note that this converter is only available when HAProxy has been
compiled with USE_OPENSSL.
jwt_payload_query([<json_path>],[<output_type>])
When given a JSON Web Token (JWT) in input, either returns the decoded
payload part of the token (the second base64-url encoded part of the JWT) if
no parameter is given, or performs a json_query on the decoded payload part
of the token. See "json_query" converter for details about the accepted
json_path and output_type parameters.
Please note that this converter is only available when HAProxy has been
compiled with USE_OPENSSL.
language(<value>[,<default>])
Returns the value with the highest q-factor from a list as extracted from the
"accept-language" header using "req.fhdr". Values with no q-factor have a

View File

@ -81,7 +81,6 @@ enum jwt_alg jwt_parse_alg(const char *alg_str, unsigned int alg_len)
return alg;
}
/*
* Split a JWT into its separate dot-separated parts.
* Since only JWS following the Compact Serialization format are managed for

View File

@ -44,6 +44,7 @@
#include <haproxy/uri_auth-t.h>
#include <haproxy/vars.h>
#include <haproxy/xxhash.h>
#include <haproxy/jwt.h>
/* sample type names */
const char *smp_to_type[SMP_TYPES] = {
@ -3493,6 +3494,91 @@ static int sample_conv_json_query(const struct arg *args, struct sample *smp, vo
return 0;
}
#ifdef USE_OPENSSL
/*
* Returns the decoded header or payload of a JWT if no parameter is given, or
* the value of the specified field of the corresponding JWT subpart if a
* parameter is given.
*/
static int sample_conv_jwt_member_query(const struct arg *args, struct sample *smp,
void *private, enum jwt_elt member)
{
struct jwt_item items[JWT_ELT_MAX] = { { 0 } };
unsigned int item_num = member + 1; /* We don't need to tokenize the full token */
struct buffer *decoded_header = get_trash_chunk();
int retval = 0;
jwt_tokenize(&smp->data.u.str, items, &item_num);
if (item_num < member + 1)
goto end;
decoded_header = alloc_trash_chunk();
if (!decoded_header)
goto end;
decoded_header->data = base64urldec(items[member].start, items[member].length,
decoded_header->area, decoded_header->size);
if (decoded_header->data == (unsigned int)-1)
goto end;
if (args[0].type != ARGT_STR) {
smp->data.u.str = *decoded_header;
smp->data.type = SMP_T_STR;
goto end;
}
/* We look for a specific field of the header or payload part of the JWT */
smp->data.u.str = *decoded_header;
retval = sample_conv_json_query(args, smp, private);
end:
return retval;
}
/* This function checks the "jwt_header_query" and "jwt_payload_query" converters' arguments.
* It is based on the "json_query" converter's check with the only difference
* being that the jwt converters can take 0 parameters as well.
*/
static int sample_conv_jwt_query_check(struct arg *arg, struct sample_conv *conv,
const char *file, int line, char **err)
{
if (arg[1].data.str.data != 0) {
if (strcmp(arg[1].data.str.area, "int") != 0) {
memprintf(err, "output_type only supports \"int\" as argument");
return 0;
} else {
arg[1].type = ARGT_SINT;
arg[1].data.sint = 0;
}
}
return 1;
}
/*
* If no parameter is given, return the decoded header part of a JWT (the first
* base64 encoded part, corresponding to the JOSE header).
* If a parameter is given, this converter acts as a "json_query" on this
* decoded JSON.
*/
static int sample_conv_jwt_header_query(const struct arg *args, struct sample *smp, void *private)
{
return sample_conv_jwt_member_query(args, smp, private, JWT_ELT_JOSE);
}
/*
* If no parameter is given, return the decoded payload part of a JWT (the
* second base64 encoded part, which contains all the claims). If a parameter
* is given, this converter acts as a "json_query" on this decoded JSON.
*/
static int sample_conv_jwt_payload_query(const struct arg *args, struct sample *smp, void *private)
{
return sample_conv_jwt_member_query(args, smp, private, JWT_ELT_CLAIMS);
}
#endif /* USE_OPENSSL */
/************************************************************************/
/* All supported sample fetch functions must be declared here */
@ -4000,6 +4086,12 @@ static struct sample_conv_kw_list sample_conv_kws = {ILH, {
{ "ltrim", sample_conv_ltrim, ARG1(1,STR), NULL, SMP_T_STR, SMP_T_STR },
{ "rtrim", sample_conv_rtrim, ARG1(1,STR), NULL, SMP_T_STR, SMP_T_STR },
{ "json_query", sample_conv_json_query, ARG2(1,STR,STR), sample_check_json_query , SMP_T_STR, SMP_T_ANY },
#ifdef USE_OPENSSL
/* JSON Web Token converters */
{ "jwt_header_query", sample_conv_jwt_header_query, ARG2(0,STR,STR), sample_conv_jwt_query_check, SMP_T_BIN, SMP_T_ANY },
{ "jwt_payload_query", sample_conv_jwt_payload_query, ARG2(0,STR,STR), sample_conv_jwt_query_check, SMP_T_BIN, SMP_T_ANY },
#endif
{ NULL, NULL, 0, 0, 0 },
}};