Skip to content
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

Support multiple JWT keys #395

Draft
wants to merge 4 commits into
base: v2
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 3 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -126,17 +126,16 @@ Once application is UP, the OpenAPI specification is accessible at `http(s)://{h
The service persists out of modules with a bootstrap code to start the service. Service provides default configuration in [default-config.json](here-naksha-lib-hub/src/main/resources/config/default-config.json).

The custom (external) configuration file can be supplied by modifying environment variable or by creating the `default-config.json` file in the corresponding configuration folder.
The exact configuration folder is platform dependent, but generally follows the [XGD user configuration directory](https://specifications.freedesktop.org/basedir-spec/basedir-spec-latest.html), standard, so on Linux being by default `~/.config/naksha/v{x.x.x}/`. For Windows the files will reside in the [CSIDL_PROFILE](https://learn.microsoft.com/en-us/windows/win32/shell/csidl?redirectedfrom=MSDN) folder, by default `C:\Users\{username}\.config\naksha\v{x.x.x}\`.
Here `{x.x.x}` is the Naksha application version (for example, if version is `2.0.7`, then path will be `...\.config\naksha\v2.0.7`)
The exact configuration folder is platform dependent, but generally follows the [XGD user configuration directory](https://specifications.freedesktop.org/basedir-spec/basedir-spec-latest.html), standard, so on Linux being by default `~/.config/naksha/`. For Windows the files will reside in the [CSIDL_PROFILE](https://learn.microsoft.com/en-us/windows/win32/shell/csidl?redirectedfrom=MSDN) folder, by default `C:\Users\{username}\.config\naksha\`.

Next to this, an explicit location can be specified via the environment variable `NAKSHA_CONFIG_PATH`, this path will not be extended by the `naksha/v{x.x.x}` folder, so you can directly specify where to keep the config files. This is important when you want to start multiple versions of the service: `NAKSHA_CONFIG_PATH=~/.config/naksha/ java -jar naksha.jar {arguments}`.
Next to this, an explicit location can be specified via the environment variable `NAKSHA_CONFIG_PATH`, this path will not be extended by the `naksha/` folder, so you can directly specify where to keep the config files. This is important when you want to start multiple versions of the service: `NAKSHA_CONFIG_PATH=~/.config/naksha/ java -jar naksha.jar {arguments}`.

In the custom config file, the name of the individual properties can be set as per source code here [NakshaHubConfig](here-naksha-lib-hub/src/main/java/com/here/naksha/lib/hub/NakshaHubConfig.java).
All properties annotated with `@JsonProperty` can be set in custom config file.

Config file is loaded using `{config-id}` supplied as CLI argument, as per following precedence on file location (first match wins):
1. using env variable `NAKSHA_CONFIG_PATH` (full path will be `$NAKSHA_CONFIG_PATH/{config-id}.json`)
2. as per user's home directory `user.home` (full path will be `{user-home}/.config/naksha/v{x.x.x}/{config-id}.json` )
2. as per user's home directory `user.home` (full path will be `{user-home}/.config/naksha/{config-id}.json` )
3. as per config previously loaded in Naksha Admin Storage (PostgreSQL database)
4. default config loaded from jar (`here-naksha-lib-hub/src/main/resources/config/default-config.json`)

Expand Down
1 change: 1 addition & 0 deletions docker/Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ ENV NAKSHA_ADMIN_DB_URL 'jdbc:postgresql://host.docker.internal:5432/postgres?us
ENV NAKSHA_EXTENSION_S3_BUCKET 'naksha-pvt-releases'
ENV NAKSHA_JWT_PVT_KEY ''
ENV NAKSHA_JWT_PUB_KEY ''
ENV NAKSHA_JWT_PUB_KEY_2 ''
ENV JAVA_OPTS ''

# Execute Shell Script
Expand Down
16 changes: 9 additions & 7 deletions docker/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -30,8 +30,9 @@ To get Naksha container running, one must do the following:
use, `jdbc:postgresql://host.docker.internal:5432/postgres?user=postgres&password=password&schema=naksha&app=naksha_local&id=naksha_admin_db`
by default
- `NAKSHA_EXTENSION_S3_BUCKET`: S3 bucket name or S3 bucket access point.The default value is `naksha-pvt-releases`.
- `NAKSHA_JWT_PVT_KEY`: Naksha JWT private key. If not provided then it will load it from `here-naksha-app-service/src/main/resources/auth/jwt.key`.
- `NAKSHA_JWT_PUB_KEY`: Naksha JWT public key. If not provided then it will load it from `here-naksha-app-service/src/main/resources/auth/jwt.pub`.
- `NAKSHA_JWT_PVT_KEY`: Naksha JWT private key. If not provided then it will load from Jar bundled resources folder (i.e. indirectly from `here-naksha-app-service/src/main/resources/auth/jwt.key`).
- `NAKSHA_JWT_PUB_KEY`: Naksha JWT public key. If not provided then it will load from Jar bundled resources folder (i.e. indirectly from `here-naksha-app-service/src/main/resources/auth/jwt.pub`).
- `NAKSHA_JWT_PUB_KEY_2`: Additional Naksha JWT public key, needed to validate the JWT signed by some other application's PVT key).
- `JAVA_OPTS`: Any custom java options like `-Xms1024m -Xmx2048m`

When connecting Naksha app to database, one has to consider container networking - if your
Expand All @@ -56,11 +57,12 @@ To get Naksha container running, one must do the following:
```shell
docker run \
--name=naksha-app \
--env NAKSHA_CONFIG_ID=<your Naksha config id> \
--env NAKSHA_ADMIN_DB_URL=<your DB uri that Naksha should use> \
--env NAKSHA_EXTENSION_S3_BUCKET=<your s3 bucket name or access point> \
--env NAKSHA_JWT_PVT_KEY=<your naksha JWT private key with '\n' for new lines> \
--env NAKSHA_JWT_PUB_KEY=<your naksha JWT public key with '\n' for new lines> \
--env NAKSHA_CONFIG_ID="<your Naksha config id>" \
--env NAKSHA_ADMIN_DB_URL="<your DB uri that Naksha should use>" \
--env NAKSHA_EXTENSION_S3_BUCKET="<your s3 bucket name or access point>" \
--env NAKSHA_JWT_PVT_KEY="<your naksha JWT private key with '\n' for new lines>" \
--env NAKSHA_JWT_PUB_KEY="<your naksha JWT public key with '\n' for new lines>" \
--env NAKSHA_JWT_PUB_KEY_2="<some other application's JWT public key with '\n' for new lines>" \
--env JAVA_OPTS="-Xms1024m -Xmx2048m" \
-p 8080:8080 \
local-naksha-app
Expand Down
6 changes: 4 additions & 2 deletions docker/cloud-config.json
Original file line number Diff line number Diff line change
@@ -1,11 +1,13 @@
{
"id": "cloud-config",
"type": "Config",
"type": "Feature",
"httpPort": 7080,
"requestBodyLimit": 25,
"authMode": "JWT",
"jwtPvtKeyPath": "SOME_PVT_KEY_PATH",
"jwtPubKeyPaths": "SOME_PUB_KEY_PATHS",
"maxParallelRequestsPerCPU": 100,
"maxPctParallelRequestsPerActor": 25,
"authMode": "JWT",
"extensionConfigParams": {
"whitelistClasses": [ "java.*", "javax.*", "com.here.*", "jdk.internal.reflect.*", "com.sun.*", "org.w3c.dom.*", "sun.misc.*","org.locationtech.jts.*"],
"intervalms": 30000,
Expand Down
28 changes: 24 additions & 4 deletions docker/run-app.sh
Original file line number Diff line number Diff line change
Expand Up @@ -3,13 +3,15 @@
# Set the NAKSHA_CONFIG_PATH
export NAKSHA_CONFIG_PATH=/home/naksha/app/config/

# Replace placeholder in cloud-config.json
sed -i "s+SOME_S3_BUCKET+${NAKSHA_EXTENSION_S3_BUCKET}+g" /home/naksha/app/config/cloud-config.json
NAKSHA_PVT_KEY_PATH=""
NAKSHA_PUB_KEY_PATHS=""

# Check if NAKSHA_JWT_PVT_KEY is set and create jwt.key file if it is
if [ -n "$NAKSHA_JWT_PVT_KEY" ]; then
mkdir -p ${NAKSHA_CONFIG_PATH}auth/
echo "$NAKSHA_JWT_PVT_KEY" | sed 's/\\n/\n/g' > ${NAKSHA_CONFIG_PATH}auth/jwt.key
KEY_FILE_PATH=auth/jwt.key
echo "$NAKSHA_JWT_PVT_KEY" | sed 's/\\n/\n/g' > ${NAKSHA_CONFIG_PATH}${KEY_FILE_PATH}
NAKSHA_PVT_KEY_PATH=${KEY_FILE_PATH}
echo "Using custom JWT private key"
else
echo "No custom JWT private key supplied"
Expand All @@ -18,11 +20,29 @@ fi
# Check if NAKSHA_JWT_PUB_KEY is set and create jwt.pub file if it is
if [ -n "$NAKSHA_JWT_PUB_KEY" ]; then
mkdir -p ${NAKSHA_CONFIG_PATH}auth/
echo "$NAKSHA_JWT_PUB_KEY" | sed 's/\\n/\n/g' > ${NAKSHA_CONFIG_PATH}auth/jwt.pub
KEY_FILE_PATH=auth/jwt.pub
echo "$NAKSHA_JWT_PUB_KEY" | sed 's/\\n/\n/g' > ${NAKSHA_CONFIG_PATH}${KEY_FILE_PATH}
NAKSHA_PUB_KEY_PATHS=${KEY_FILE_PATH}
echo "Using custom JWT public key"
else
echo "No custom JWT public key supplied"
fi

# Check if NAKSHA_JWT_PUB_KEY_2 is set and create jwt_2.pub file if it is
if [ -n "$NAKSHA_JWT_PUB_KEY_2" ]; then
mkdir -p ${NAKSHA_CONFIG_PATH}auth/
KEY_FILE_PATH=auth/jwt_2.pub
echo "$NAKSHA_JWT_PUB_KEY_2" | sed 's/\\n/\n/g' > ${NAKSHA_CONFIG_PATH}${KEY_FILE_PATH}
NAKSHA_PUB_KEY_PATHS=${NAKSHA_PUB_KEY_PATHS},${KEY_FILE_PATH}
echo "Using custom JWT public key 2"
else
echo "No custom JWT public key 2 supplied"
fi

# Replace all placeholders in cloud-config.json
sed -i "s+SOME_S3_BUCKET+${NAKSHA_EXTENSION_S3_BUCKET}+g" /home/naksha/app/config/cloud-config.json
sed -i "s+SOME_PVT_KEY_PATH+${NAKSHA_PVT_KEY_PATH}+g" /home/naksha/app/config/cloud-config.json
sed -i "s+SOME_PUB_KEY_PATHS+${NAKSHA_PUB_KEY_PATHS}+g" /home/naksha/app/config/cloud-config.json

# Start the application
java $JAVA_OPTS -jar /home/naksha/app/naksha-*-all.jar $NAKSHA_CONFIG_ID $NAKSHA_ADMIN_DB_URL
5 changes: 2 additions & 3 deletions gradle.properties
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,7 @@ mavenUser=YourUserName
mavenPassword=YourPassword

# When updating the version, please as well consider:
# - here-naksha-lib-core/NakshaVersion (static property: latest)
# - here-naksha-lib-psql/resources/naksha_plpgsql.sql (method: naksha_version)
# - here-naksha-lib-core/src/main/com/here/naksha/lib/core/NakshaVersion (static property: latest)
# - here-naksha-app-service/src/main/resources/swagger/openapi.yaml (info.version property)
version=2.2.0
version=2.2.1

Original file line number Diff line number Diff line change
Expand Up @@ -220,20 +220,20 @@ public NakshaApp(
}
this.vertx = Vertx.vertx(this.vertxOptions);

final String jwtKey;
final String jwtPub;
final List<PubSecKeyOptions> keyOptions = new ArrayList<>();
// read JWT pvt key
{
final String path = "auth/" + config.jwtName + ".key";
jwtKey = readAuthKeyFile(path, NakshaHubConfig.APP_NAME);
final String keyContent = readAuthKeyFile(config.jwtPvtKeyPath, NakshaHubConfig.APP_NAME);
keyOptions.add(new PubSecKeyOptions().setAlgorithm("RS256").setBuffer(keyContent));
}
{
final String path = "auth/" + config.jwtName + ".pub";
jwtPub = readAuthKeyFile(path, NakshaHubConfig.APP_NAME);
// read JWT pub keys
for (final String keyPath : config.jwtPubKeyPaths.split(",")) {
final String keyContent = readAuthKeyFile(keyPath, NakshaHubConfig.APP_NAME);
keyOptions.add(new PubSecKeyOptions().setAlgorithm("RS256").setBuffer(keyContent));
}
this.authOptions = new JWTAuthOptions()
.setJWTOptions(new JWTOptions().setAlgorithm("RS256"))
.addPubSecKey(new PubSecKeyOptions().setAlgorithm("RS256").setBuffer(jwtKey))
.addPubSecKey(new PubSecKeyOptions().setAlgorithm("RS256").setBuffer(jwtPub));
.setPubSecKeys(keyOptions);
this.authProvider = new NakshaAuthProvider(this.vertx, this.authOptions);

final WebClientOptions webClientOptions = new WebClientOptions();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,7 @@ public class NakshaJwtAuthHandler extends JWTAuthHandlerImpl {
/**
* The master JWT used for testing.
*/
private final String MASTER_JWT = authProvider.generateToken(MASTER_JWT_PAYLOAD);
private static String MASTER_JWT = null;

public NakshaJwtAuthHandler(
@NotNull JWTAuth authProvider, @NotNull NakshaHubConfig hubConfig, @Nullable String realm) {
Expand All @@ -57,6 +57,9 @@ public void authenticate(@NotNull RoutingContext context, @NotNull Handler<@NotN
if (hubConfig.authMode == AuthorizationMode.DUMMY
&& !context.request().headers().contains(HttpHeaders.AUTHORIZATION)) {
// Use the master JWT for testing in DUMMY auth mode with no JWT provided in request
if (MASTER_JWT == null) {
MASTER_JWT = authProvider.generateToken(MASTER_JWT_PAYLOAD);
}
context.request().headers().set(HttpHeaders.AUTHORIZATION, "Bearer " + MASTER_JWT);
}
// TODO: If compressed JWTs are supported
Expand Down
13 changes: 0 additions & 13 deletions here-naksha-app-service/src/main/resources/mock-config.json

This file was deleted.

Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ servers:
info:
title: "Naksha Hub-API"
description: "Naksha Hub-API is a REST API to provide simple access to geo data."
version: "2.2.0"
version: "2.2.1"

security:
- AccessToken: [ ]
Expand Down
Original file line number Diff line number Diff line change
@@ -1,16 +1,13 @@
{
"id": "test-config",
"type": "Config",
"type": "Feature",

"httpPort": 8080,
"env": "local",
"requestBodyLimit": 25,
"authMode": "DUMMY",
"jwtName": "jwt",
"maintenanceInitialDelayInMins": 60,
"maintenanceIntervalInMins": 720,
"maintenancePoolCoreSize": 5,
"maintenancePoolMaxSize": 20,
"jwtPvtKeyPath": "auth/jwt.key",
"jwtPubKeyPaths": "auth/jwt.pub",
"maxParallelRequestsPerCPU": 30,
"maxPctParallelRequestsPerActor": 100,
"storageParams": {
Expand Down
9 changes: 3 additions & 6 deletions here-naksha-app-service/src/main/resources/test-config.json
Original file line number Diff line number Diff line change
@@ -1,16 +1,13 @@
{
"id": "test-config",
"type": "Config",
"type": "Feature",

"httpPort": 8080,
"env": "local",
"requestBodyLimit": 25,
"authMode": "DUMMY",
"jwtName": "jwt",
"maintenanceInitialDelayInMins": 60,
"maintenanceIntervalInMins": 720,
"maintenancePoolCoreSize": 5,
"maintenancePoolMaxSize": 20,
"jwtPvtKeyPath": "auth/jwt.key",
"jwtPubKeyPaths": "auth/jwt.pub",
"maxParallelRequestsPerCPU": 30,
"maxPctParallelRequestsPerActor": 100,
"storageParams": {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -57,12 +57,14 @@ public class NakshaVersion implements Comparable<NakshaVersion> {
public static final String v2_0_20 = "2.0.20";
public static final String v2_1_0 = "2.1.0";
public static final String v2_1_1 = "2.1.1";
public static final String v2_2_0 = "2.2.0";
public static final String v2_2_1 = "2.2.1";

/**
* The latest version of the naksha-extension stored in the resources.
*/
@AvailableSince(v2_0_5)
public static final NakshaVersion latest = of(v2_1_1);
public static final NakshaVersion latest = of(v2_2_1);

private final int major;
private final int minor;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -47,17 +47,14 @@
import com.here.naksha.lib.core.util.IoHelp;
import com.here.naksha.lib.core.util.json.Json;
import com.here.naksha.lib.core.util.storage.RequestHelper;
import com.here.naksha.lib.core.util.storage.ResultHelper;
import com.here.naksha.lib.core.view.ViewDeserialize;
import com.here.naksha.lib.extmanager.ExtensionManager;
import com.here.naksha.lib.extmanager.IExtensionManager;
import com.here.naksha.lib.extmanager.helpers.AmazonS3Helper;
import com.here.naksha.lib.hub.storages.NHAdminStorage;
import com.here.naksha.lib.hub.storages.NHSpaceStorage;
import com.here.naksha.lib.psql.PsqlStorage;

import java.util.*;

import org.jetbrains.annotations.ApiStatus;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
Expand Down Expand Up @@ -242,8 +239,7 @@ private static WriteXyzCollections createAdminCollectionsRequest() {
new Exception("Unable to read custom/default config from Admin DB. " + er, er.exception));
} else {
try {
List<NakshaHubConfig> nakshaHubConfigs =
readFeaturesFromResult(rdResult, NakshaHubConfig.class);
List<NakshaHubConfig> nakshaHubConfigs = readFeaturesFromResult(rdResult, NakshaHubConfig.class);
for (final NakshaHubConfig cfg : nakshaHubConfigs) {
if (cfg.getId().equals(configId)) {
customDbCfg = cfg;
Expand Down Expand Up @@ -307,15 +303,21 @@ private static WriteXyzCollections createAdminCollectionsRequest() {
try (IReadSession readSession = getAdminStorage().newReadSession(nakshaContext, false)) {
rdResult = readSession.execute(request);
} catch (Exception e) {
logger.error("Failed during reading extension handler configurations from collections {}. ", request.getCollections(), e);
logger.error(
"Failed during reading extension handler configurations from collections {}. ",
request.getCollections(),
e);
throw new RuntimeException("Failed reading extension handler configurations", e);
}
final List<EventHandler> eventHandlers;
try {
eventHandlers = readFeaturesFromResult(rdResult, EventHandler.class);
} catch (NoCursor e) {
logger.error("NoCursor exception encountered", e);
throw new RuntimeException("Failed to open cursor", e);
} catch (NoCursor | NoSuchElementException e) {
logger.info("No relevant handlers found for Extension loading", e);
return new ExtensionConfig(
System.currentTimeMillis() + nakshaHubConfig.extensionConfigParams.getIntervalMs(),
Collections.emptyList(),
null);
}

Set<String> extensionIds = new HashSet<>();
Expand Down Expand Up @@ -356,7 +358,7 @@ private List<Extension> loadExtensionConfigFromS3(String extensionRootPath, Set<
}

filePath = extensionRootPath + extensionIdWotEnv + "/" + extensionIdWotEnv + "-" + version + "."
+ env.toLowerCase() + ".json";
+ env.toLowerCase() + ".json";
String exJson;
try {
exJson = s3Helper.getFileContent(filePath);
Expand Down
Loading
Loading