blob: 8727e9f3c98e1e322502787e0c7ee2d40483272d [file] [log] [blame]
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#include "common.h"
#include "config.h"
#include "timing.h"
#include "jwt.h"
#include <cjose/cjose.h>
#include <jansson.h>
#include <string.h>
#include <search.h>
#include <errno.h>
#define JSONError(err) PluginError("json-err: %s:%d:%d: %s", (err).source, (err).line, (err).column, (err).text)
#define AUTH_DENY 0
#define AUTH_ALLOW 1
struct auth_directive {
char auth;
char *container;
};
struct config {
struct hsearch_data *issuers;
cjose_jwk_t ***jwkis;
char **issuer_names;
struct signer signer;
struct auth_directive *auth_directives;
char *id;
bool strip_token;
};
cjose_jwk_t **
find_keys(struct config *cfg, const char *issuer)
{
ENTRY *entry;
if (!hsearch_r((ENTRY){.key = (char *)issuer}, FIND, &entry, cfg->issuers) || !entry) {
PluginDebug("Unable to locate any keys at %p for issuer %s in %p->%p", entry, issuer, cfg, cfg->issuers);
return NULL;
}
int n = 0;
for (cjose_jwk_t **jwks = entry->data; *jwks; ++jwks, ++n) {
;
}
PluginDebug("Located %d keys for issuer %s in %p->%p", n, issuer, cfg, cfg->issuers);
return entry->data;
}
cjose_jwk_t *
find_key_by_kid(struct config *cfg, const char *issuer, const char *kid)
{
const char *this_kid;
cjose_jwk_t **jwkis = find_keys(cfg, issuer);
if (!jwkis) {
return NULL;
}
for (cjose_jwk_t **jwks = jwkis; *jwks; ++jwks) {
if ((this_kid = cjose_jwk_get_kid(*jwks, NULL)) && !strcmp(this_kid, kid)) {
return *jwks;
}
}
return NULL;
}
const char *
config_get_id(struct config *cfg)
{
return cfg->id;
}
bool
config_strip_token(struct config *cfg)
{
return cfg->strip_token;
}
struct config *
config_new(size_t n)
{
PluginDebug("Creating new config object with size %ld", n);
struct config *cfg = malloc(sizeof *cfg);
cfg->issuers = calloc(1, sizeof *cfg->issuers);
if (!hcreate_r(n * 2, cfg->issuers)) {
PluginError("Unable to create config table (%d)!", errno);
free(cfg);
return NULL;
}
PluginDebug("Created table with size %d", cfg->issuers->size);
cfg->jwkis = malloc((n + 1) * sizeof *cfg->jwkis);
cfg->jwkis[n] = NULL;
cfg->issuer_names = malloc((n + 1) * sizeof *cfg->issuer_names);
cfg->issuer_names[n] = NULL;
cfg->signer.issuer = NULL;
cfg->signer.jwk = NULL;
cfg->signer.alg = NULL;
cfg->auth_directives = NULL;
cfg->id = NULL;
cfg->strip_token = false;
PluginDebug("New config object created at %p", cfg);
return cfg;
}
void
config_delete(struct config *cfg)
{
if (!cfg) {
return;
}
hdestroy_r(cfg->issuers);
free(cfg->issuers);
for (cjose_jwk_t ***jwkis = cfg->jwkis; *jwkis; ++jwkis) {
for (cjose_jwk_t **jwks = *jwkis; *jwks; ++jwks) {
cjose_jwk_release(*jwks);
}
free(*jwkis);
}
free(cfg->jwkis);
if (cfg->id) {
free(cfg->id);
}
for (char **name = cfg->issuer_names; *name; ++name) {
free(*name);
}
free(cfg->issuer_names);
if (cfg->signer.alg) {
free(cfg->signer.alg);
}
if (cfg->auth_directives) {
for (struct auth_directive *ad = cfg->auth_directives; ad->container; ++ad) {
free(ad->container);
}
free(cfg->auth_directives);
}
free(cfg);
}
cjose_jwk_t *
load_jwk(json_t *obj, cjose_err *err)
{
char *s = json_dumps(obj, JSON_COMPACT);
if (!s) {
PluginError("Failed to re-serialize JSON sub-object.");
return NULL;
}
cjose_jwk_t *jwk = cjose_jwk_import(s, strlen(s), err);
free(s);
return jwk;
}
struct config *
read_config(const char *path)
{
json_error_t err = {0};
json_t *issuer_json = json_load_file(path, 0, &err);
if (!issuer_json) {
JSONError(err);
goto fail;
}
if (!json_is_object(issuer_json)) {
PluginError("Config file is not a valid JSON object");
goto issuer_fail;
}
size_t issuers_ct = json_object_size(issuer_json);
if (!issuers_ct) {
PluginError("Config file contains no issuers.");
goto issuer_fail;
}
struct config *cfg = config_new(issuers_ct);
if (!cfg) {
PluginError("Unable to allocate config.");
goto issuer_fail;
}
cjose_jwk_t ***jwkis = cfg->jwkis;
char **issuer = cfg->issuer_names;
const char *json_issuer;
json_t *jwks;
json_object_foreach(issuer_json, json_issuer, jwks)
{
*issuer = strdup(json_issuer);
json_t *ad_json = json_object_get(jwks, "auth_directives");
if (ad_json) {
PluginDebug("Loading auth_directives.");
size_t ad_ct = json_array_size(ad_json);
if (ad_ct) {
PluginDebug("Loading %d new auth_directives.", (int)ad_ct);
struct auth_directive *ad = cfg->auth_directives;
if (cfg->auth_directives) {
/* We've already got directives, so extend them. */
PluginDebug("Extending existing auth_directives.");
size_t ad_old_ct = 0;
while (ad->container) {
++ad;
++ad_old_ct;
}
cfg->auth_directives = realloc(cfg->auth_directives, (ad_ct + ad_old_ct + 1) * sizeof *cfg->auth_directives);
ad = cfg->auth_directives + ad_old_ct;
} else {
ad = cfg->auth_directives = malloc((ad_ct + 1) * sizeof *cfg->auth_directives);
}
json_t *ad_obj;
for (size_t idx = 0; (idx < ad_ct) && (ad_obj = json_array_get(ad_json, idx)); ++idx, ++ad) {
json_t *uri_json = json_object_get(ad_obj, "uri");
json_t *auth_json = json_object_get(ad_obj, "auth");
if (uri_json) {
const char *uri = json_string_value(uri_json);
ad->container = strdup(uri ? uri : "");
ad->auth = AUTH_DENY;
if (auth_json) {
const char *auth = json_string_value(auth_json);
if (!auth) {
auth = "";
}
if (!strcmp(auth, "allow")) {
ad->auth = AUTH_ALLOW;
} else if (!strcmp(auth, "deny")) {
ad->auth = AUTH_DENY;
} else {
PluginError("auth_directive has unknown auth parameter '%s', defaulting to deny: %s", auth, uri);
}
} else {
PluginError("auth_directive is missing auth parameter, defaulting to deny: %s", uri);
}
PluginDebug("Adding auth_directive %d for %s.", (int)ad->auth, ad->container);
}
}
ad->container = NULL;
}
} else {
PluginDebug("No auth_directives to load for %s.", *issuer);
}
json_t *key_ary = json_object_get(jwks, "keys");
if (!key_ary) {
PluginError("Failed to get keys member from jwk for issuer %s", *issuer);
*jwkis = NULL;
goto cfg_fail;
}
PluginDebug("Created table with size %d", cfg->issuers->size);
const char *renewal_kid = NULL;
json_t *renewal_kid_json = json_object_get(jwks, "renewal_kid");
if (renewal_kid_json) {
renewal_kid = json_string_value(renewal_kid_json);
}
json_t *id_json = json_object_get(jwks, "id");
const char *id;
if (id_json) {
id = json_string_value(id_json);
if (id) {
cfg->id = malloc(strlen(id) + 1);
strcpy(cfg->id, id);
PluginDebug("Found Id in the config: %s", cfg->id);
}
}
json_t *strip_json = json_object_get(jwks, "strip_token");
if (strip_json) {
cfg->strip_token = json_boolean_value(strip_json);
}
size_t jwks_ct = json_array_size(key_ary);
cjose_jwk_t **jwks = (*jwkis++ = malloc((jwks_ct + 1) * sizeof *jwks));
PluginDebug("Created table with size %d", cfg->issuers->size);
if (!hsearch_r(((ENTRY){*issuer, jwks}), ENTER, &(ENTRY *){0}, cfg->issuers)) {
PluginDebug("Failed to store keys for issuer %s", *issuer);
} else {
PluginDebug("Stored keys for %s at %16p", *issuer, jwks);
}
json_t *jwk_obj;
cjose_err jwk_err = {0};
for (size_t idx = 0; (idx < jwks_ct) && (jwk_obj = json_array_get(key_ary, idx)); ++idx, ++jwks) {
if ((*jwks = load_jwk(jwk_obj, &jwk_err))) {
const char *kid = cjose_jwk_get_kid(*jwks, NULL);
PluginDebug("Stored jwk %ld for issuer %s, kid %s, cfg %p->%p", idx, *issuer, kid ? kid : "<no kid>", cfg, cfg->issuers);
if (renewal_kid && kid && !strcmp(kid, renewal_kid)) {
if (cfg->signer.issuer) {
PluginError("Cannot load multiple renewal keys for a single remap. iss:\"%s\", kid:\"%s\"; iss:\"%s\", kid:\"%s\"",
cfg->signer.issuer, cjose_jwk_get_kid(cfg->signer.jwk, NULL), *issuer, kid);
goto cfg_fail;
} else {
cfg->signer.issuer = *issuer;
cfg->signer.jwk = *jwks;
const char *jwk_alg = json_string_value(json_object_get(jwk_obj, "alg"));
if (!jwk_alg) {
PluginError("Cannot load JWK algorithm for renewal key.");
goto cfg_fail;
}
cfg->signer.alg = strdup(jwk_alg);
}
}
} else {
PluginError("Failed to load jwk %ld for issuer %s: %s", idx, *issuer, jwk_err.message);
goto cfg_fail;
}
}
*jwks = NULL;
++issuer;
}
if (!cfg->signer.issuer) {
PluginError("Cannot load remap without signing key.");
goto cfg_fail;
}
json_decref(issuer_json);
PluginDebug("Loaded config file successfully.");
return cfg;
cfg_fail:
config_delete(cfg);
issuer_fail:
json_decref(issuer_json);
fail:
return NULL;
}
struct signer *
config_signer(struct config *cfg)
{
if (!cfg) {
return NULL;
}
return &cfg->signer;
}
bool
uri_matches_auth_directive(struct config *cfg, const char *uri, size_t uri_ct)
{
if (!cfg || !cfg->auth_directives || !uri) {
return false;
}
char *uri_s = malloc(uri_ct + 1);
memcpy(uri_s, uri, uri_ct);
uri_s[uri_ct] = 0;
for (const struct auth_directive *ad = cfg->auth_directives; ad->container; ++ad) {
if (jwt_check_uri(ad->container, uri_s)) {
free(uri_s);
return (ad->auth == AUTH_ALLOW);
}
}
free(uri_s);
return false;
}