Skip to content

Commit 3f0a00d

Browse files
authored
Support multiple JWT public keys (#395)
* Updated doc about existing JWT loading * added support for additional pub key * updated readme * Updated version to 2.2.1 * Updated doc for pvt and pub key * Added constants in NakshaHubConfig constructor * addressed review comments
1 parent 7128eab commit 3f0a00d

File tree

17 files changed

+152
-92
lines changed

17 files changed

+152
-92
lines changed

README.md

Lines changed: 20 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -103,9 +103,9 @@ java -jar <jar-file> <config-id> <database-url>
103103

104104
# Example 1 : Start service with test config against default Database URL (useful for local env)
105105
java -jar build/libs/naksha-2.0.6-all.jar test-config
106-
# Example 2 : Start service with given custom config and custom database URL (useful for cloud env)
106+
# Example 2 : Start service with given custom config (using NAKSHA_CONFIG_PATH) and custom database URL (useful for cloud env)
107107
java -jar build/libs/naksha-2.0.6-all.jar cloud-config 'jdbc:postgresql://localhost:5432/postgres?user=postgres&password=pswd&schema=naksha&app=naksha_local&id=naksha_admin_db'
108-
# Example 3 : Start service with given custom config and default (local) database URL
108+
# Example 3 : Start service with given custom config (using NAKSHA_CONFIG_PATH) and default (local) database URL
109109
java -jar build/libs/naksha-2.0.6-all.jar custom-config
110110

111111
```
@@ -126,17 +126,16 @@ Once application is UP, the OpenAPI specification is accessible at `http(s)://{h
126126
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).
127127

128128
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.
129-
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}\`.
130-
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`)
129+
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\`.
131130

132-
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}`.
131+
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}`.
133132

134133
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).
135134
All properties annotated with `@JsonProperty` can be set in custom config file.
136135

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

@@ -154,6 +153,21 @@ vi $NAKSHA_CONFIG_PATH/default-config.json
154153
java -jar naksha.jar default-config
155154
```
156155

156+
The config also accepts custom RSA256 Private key and multiple Public key files (in PEM format) to support JWT signing/verification operations.
157+
158+
* If custom Private key not provided, default will be loaded from Jar bundled resource [here-naksha-app-service/src/main/resources/auth/jwt.key](here-naksha-app-service/src/main/resources/auth/jwt.key).
159+
* If custom Public key not provided, default will be loaded from Jar bundled resource [here-naksha-app-service/src/main/resources/auth/jwt.pub](here-naksha-app-service/src/main/resources/auth/jwt.pub).
160+
161+
Sample commands to generate the custom key files:
162+
163+
```bash
164+
# Generate private key
165+
openssl genrsa -out ./custom_rsa256.key 2048
166+
167+
# Generate public key (using above private key)
168+
openssl rsa -in ./custom_rsa256.key -pubout -outform PEM -out ./custom_rsa256.pub
169+
```
170+
157171
# Usage
158172

159173
Start using the service by creating a _space_:

build.gradle.kts

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -158,7 +158,9 @@ subprojects {
158158
// excluding tests where Builder pattern gets broken by palantir
159159
targetExclude("src/test/**")
160160
encoding("UTF-8")
161-
val YEAR = LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyy"))
161+
// TODO - Disabling auto-correction to 2025 for now. Will handle via separate change.
162+
//val YEAR = LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyy"))
163+
val YEAR = 2024
162164
licenseHeader("""
163165
/*
164166
* Copyright (C) 2017-$YEAR HERE Europe B.V.

docker/Dockerfile

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@ ENV NAKSHA_ADMIN_DB_URL 'jdbc:postgresql://host.docker.internal:5432/postgres?us
2424
ENV NAKSHA_EXTENSION_S3_BUCKET 'naksha-pvt-releases'
2525
ENV NAKSHA_JWT_PVT_KEY ''
2626
ENV NAKSHA_JWT_PUB_KEY ''
27+
ENV NAKSHA_JWT_PUB_KEY_2 ''
2728
ENV JAVA_OPTS ''
2829

2930
# Execute Shell Script

docker/README.md

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -30,8 +30,9 @@ To get Naksha container running, one must do the following:
3030
use, `jdbc:postgresql://host.docker.internal:5432/postgres?user=postgres&password=password&schema=naksha&app=naksha_local&id=naksha_admin_db`
3131
by default
3232
- `NAKSHA_EXTENSION_S3_BUCKET`: S3 bucket name or S3 bucket access point.The default value is `naksha-pvt-releases`.
33-
- `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`.
34-
- `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`.
33+
- `NAKSHA_JWT_PVT_KEY`: Naksha JWT private key.
34+
- `NAKSHA_JWT_PUB_KEY`: Naksha JWT public key.
35+
- `NAKSHA_JWT_PUB_KEY_2`: Additional Naksha JWT public key, if needed to validate the JWT signed by some other application's PVT key.
3536
- `JAVA_OPTS`: Any custom java options like `-Xms1024m -Xmx2048m`
3637
3738
When connecting Naksha app to database, one has to consider container networking - if your
@@ -61,6 +62,7 @@ To get Naksha container running, one must do the following:
6162
--env NAKSHA_EXTENSION_S3_BUCKET=<your s3 bucket name or access point> \
6263
--env NAKSHA_JWT_PVT_KEY=<your naksha JWT private key with '\n' for new lines> \
6364
--env NAKSHA_JWT_PUB_KEY=<your naksha JWT public key with '\n' for new lines> \
65+
--env NAKSHA_JWT_PUB_KEY_2=<some other application's JWT public key with '\n' for new lines> \
6466
--env JAVA_OPTS="-Xms1024m -Xmx2048m" \
6567
-p 8080:8080 \
6668
local-naksha-app

docker/cloud-config.json

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,13 @@
11
{
22
"id": "cloud-config",
3-
"type": "Config",
3+
"type": "Feature",
44
"httpPort": 7080,
55
"requestBodyLimit": 25,
6+
"authMode": "JWT",
7+
"jwtPvtKeyPath": "SOME_PVT_KEY_PATH",
8+
"jwtPubKeyPaths": "SOME_PUB_KEY_PATHS",
69
"maxParallelRequestsPerCPU": 100,
710
"maxPctParallelRequestsPerActor": 25,
8-
"authMode": "JWT",
911
"extensionConfigParams": {
1012
"whitelistClasses": [ "java.*", "javax.*", "com.here.*", "jdk.internal.reflect.*", "com.sun.*", "org.w3c.dom.*", "sun.misc.*","org.locationtech.jts.*"],
1113
"intervalms": 30000,

docker/run-app.sh

Lines changed: 24 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -3,13 +3,15 @@
33
# Set the NAKSHA_CONFIG_PATH
44
export NAKSHA_CONFIG_PATH=/home/naksha/app/config/
55

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

99
# Check if NAKSHA_JWT_PVT_KEY is set and create jwt.key file if it is
1010
if [ -n "$NAKSHA_JWT_PVT_KEY" ]; then
1111
mkdir -p ${NAKSHA_CONFIG_PATH}auth/
12-
echo "$NAKSHA_JWT_PVT_KEY" | sed 's/\\n/\n/g' > ${NAKSHA_CONFIG_PATH}auth/jwt.key
12+
KEY_FILE_PATH=auth/jwt.key
13+
echo "$NAKSHA_JWT_PVT_KEY" | sed 's/\\n/\n/g' > ${NAKSHA_CONFIG_PATH}${KEY_FILE_PATH}
14+
NAKSHA_PVT_KEY_PATH=${KEY_FILE_PATH}
1315
echo "Using custom JWT private key"
1416
else
1517
echo "No custom JWT private key supplied"
@@ -18,11 +20,29 @@ fi
1820
# Check if NAKSHA_JWT_PUB_KEY is set and create jwt.pub file if it is
1921
if [ -n "$NAKSHA_JWT_PUB_KEY" ]; then
2022
mkdir -p ${NAKSHA_CONFIG_PATH}auth/
21-
echo "$NAKSHA_JWT_PUB_KEY" | sed 's/\\n/\n/g' > ${NAKSHA_CONFIG_PATH}auth/jwt.pub
23+
KEY_FILE_PATH=auth/jwt.pub
24+
echo "$NAKSHA_JWT_PUB_KEY" | sed 's/\\n/\n/g' > ${NAKSHA_CONFIG_PATH}${KEY_FILE_PATH}
25+
NAKSHA_PUB_KEY_PATHS=${KEY_FILE_PATH}
2226
echo "Using custom JWT public key"
2327
else
2428
echo "No custom JWT public key supplied"
2529
fi
2630

31+
# Check if NAKSHA_JWT_PUB_KEY_2 is set and create jwt_2.pub file if it is
32+
if [ -n "$NAKSHA_JWT_PUB_KEY_2" ]; then
33+
mkdir -p ${NAKSHA_CONFIG_PATH}auth/
34+
KEY_FILE_PATH=auth/jwt_2.pub
35+
echo "$NAKSHA_JWT_PUB_KEY_2" | sed 's/\\n/\n/g' > ${NAKSHA_CONFIG_PATH}${KEY_FILE_PATH}
36+
NAKSHA_PUB_KEY_PATHS=${NAKSHA_PUB_KEY_PATHS},${KEY_FILE_PATH}
37+
echo "Using custom JWT public key 2"
38+
else
39+
echo "No custom JWT public key 2 supplied"
40+
fi
41+
42+
# Replace all placeholders in cloud-config.json
43+
sed -i "s+SOME_S3_BUCKET+${NAKSHA_EXTENSION_S3_BUCKET}+g" /home/naksha/app/config/cloud-config.json
44+
sed -i "s+SOME_PVT_KEY_PATH+${NAKSHA_PVT_KEY_PATH}+g" /home/naksha/app/config/cloud-config.json
45+
sed -i "s+SOME_PUB_KEY_PATHS+${NAKSHA_PUB_KEY_PATHS}+g" /home/naksha/app/config/cloud-config.json
46+
2747
# Start the application
2848
java $JAVA_OPTS -jar /home/naksha/app/naksha-*-all.jar $NAKSHA_CONFIG_ID $NAKSHA_ADMIN_DB_URL

gradle.properties

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -8,8 +8,7 @@ mavenUser=YourUserName
88
mavenPassword=YourPassword
99

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

here-naksha-app-service/src/main/java/com/here/naksha/app/service/NakshaApp.java

Lines changed: 13 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -102,13 +102,13 @@ private static void printUsage() {
102102
err.println("Examples:");
103103
err.println(" ");
104104
err.println(" Example 1 : Start service with given config and default (local) database URL");
105-
err.println(" java -jar naksha.jar default-config");
105+
err.println(" java -jar naksha.jar test-config");
106106
err.println(" ");
107107
err.println(" Example 2 : Start service with given config and custom database URL");
108-
err.println(" java -jar naksha.jar default-config '" + DEFAULT_URL + "'");
108+
err.println(" java -jar naksha.jar test-config '" + DEFAULT_URL + "'");
109109
err.println(" ");
110-
err.println(" Example 3 : Start service with mock config (with in-memory hub)");
111-
err.println(" java -jar naksha.jar mock-config");
110+
err.println(" Example 3 : Start service with custom config (using custom NAKSHA_CONFIG_PATH)");
111+
err.println(" java -jar naksha.jar custom-config");
112112
err.println(" ");
113113
err.flush();
114114
}
@@ -220,20 +220,20 @@ public NakshaApp(
220220
}
221221
this.vertx = Vertx.vertx(this.vertxOptions);
222222

223-
final String jwtKey;
224-
final String jwtPub;
223+
final List<PubSecKeyOptions> keyOptions = new ArrayList<>();
224+
// read JWT pvt key
225225
{
226-
final String path = "auth/" + config.jwtName + ".key";
227-
jwtKey = readAuthKeyFile(path, NakshaHubConfig.APP_NAME);
226+
final String keyContent = readAuthKeyFile(config.jwtPvtKeyPath, NakshaHubConfig.APP_NAME);
227+
keyOptions.add(new PubSecKeyOptions().setAlgorithm("RS256").setBuffer(keyContent));
228228
}
229-
{
230-
final String path = "auth/" + config.jwtName + ".pub";
231-
jwtPub = readAuthKeyFile(path, NakshaHubConfig.APP_NAME);
229+
// read JWT pub keys
230+
for (final String keyPath : config.jwtPubKeyPaths.split(",")) {
231+
final String keyContent = readAuthKeyFile(keyPath, NakshaHubConfig.APP_NAME);
232+
keyOptions.add(new PubSecKeyOptions().setAlgorithm("RS256").setBuffer(keyContent));
232233
}
233234
this.authOptions = new JWTAuthOptions()
234235
.setJWTOptions(new JWTOptions().setAlgorithm("RS256"))
235-
.addPubSecKey(new PubSecKeyOptions().setAlgorithm("RS256").setBuffer(jwtKey))
236-
.addPubSecKey(new PubSecKeyOptions().setAlgorithm("RS256").setBuffer(jwtPub));
236+
.setPubSecKeys(keyOptions);
237237
this.authProvider = new NakshaAuthProvider(this.vertx, this.authOptions);
238238

239239
final WebClientOptions webClientOptions = new WebClientOptions();

here-naksha-app-service/src/main/java/com/here/naksha/app/service/http/auth/NakshaJwtAuthHandler.java

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -44,7 +44,7 @@ public class NakshaJwtAuthHandler extends JWTAuthHandlerImpl {
4444
/**
4545
* The master JWT used for testing.
4646
*/
47-
private final String MASTER_JWT = authProvider.generateToken(MASTER_JWT_PAYLOAD);
47+
private static String MASTER_JWT = null;
4848

4949
public NakshaJwtAuthHandler(
5050
@NotNull JWTAuth authProvider, @NotNull NakshaHubConfig hubConfig, @Nullable String realm) {
@@ -57,6 +57,9 @@ public void authenticate(@NotNull RoutingContext context, @NotNull Handler<@NotN
5757
if (hubConfig.authMode == AuthorizationMode.DUMMY
5858
&& !context.request().headers().contains(HttpHeaders.AUTHORIZATION)) {
5959
// Use the master JWT for testing in DUMMY auth mode with no JWT provided in request
60+
if (MASTER_JWT == null) {
61+
MASTER_JWT = authProvider.generateToken(MASTER_JWT_PAYLOAD);
62+
}
6063
context.request().headers().set(HttpHeaders.AUTHORIZATION, "Bearer " + MASTER_JWT);
6164
}
6265
// TODO: If compressed JWTs are supported

here-naksha-app-service/src/main/resources/mock-config.json

Lines changed: 0 additions & 13 deletions
This file was deleted.

0 commit comments

Comments
 (0)