This is an automated email from the ASF dual-hosted git repository.
oscerd pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/camel.git
The following commit(s) were added to refs/heads/main by this push:
new 4dbb9387102c CAMEL-24281: camel-platform-http-main - fail closed when
JWT authentication has no issuer or audience (#25187)
4dbb9387102c is described below
commit 4dbb9387102c102ab207e61c8307a3eaaeec7fcf
Author: Andrea Cosentino <[email protected]>
AuthorDate: Tue Jul 28 15:08:43 2026 +0200
CAMEL-24281: camel-platform-http-main - fail closed when JWT authentication
has no issuer or audience (#25187)
* CAMEL-24281: camel-platform-http-main - fail closed when JWT
authentication has no issuer or audience
The JWT authenticator was built from the configured keystore alone when
neither
jwtIssuer nor jwtAudience was set, because buildJwtOptions returned null
and the
caller then skipped setJWTOptions. Tokens were therefore only checked for
signature and expiry, and the iss and aud claims were not validated, with
nothing
signalling that to the user.
The embedded server and the management server now fail to start when a JWT
keystore is configured but neither jwtIssuer nor jwtAudience is set.
Deployments
that want signature and expiry validation only can set
jwtAllowMissingIssuerAndAudience to true, mirroring the
allow-missing-audience and
allow-missing-issuer options that camel-oauth already exposes.
The check is applied to both configureAuthentication overloads, since the
application server and the management server share this configurer.
Co-authored-by: Claude Opus 5 (1M context) <[email protected]>
Signed-off-by: Andrea Cosentino <[email protected]>
* CAMEL-24281: Address review feedback
Mark jwtAllowMissingIssuerAndAudience with security = "insecure:dev" on both
HttpServerConfigurationProperties and
HttpManagementServerConfigurationProperties,
so the security policy framework can report on it when the flag is enabled.
The
management properties class already uses that marker for devConsoleEnabled,
uploadEnabled, downloadEnabled and sendEnabled.
Share a single Vertx instance across the tests and close it afterwards,
rather
than creating one per test method and leaving it open.
Co-authored-by: Claude Opus 5 (1M context) <[email protected]>
Signed-off-by: Andrea Cosentino <[email protected]>
---------
Signed-off-by: Andrea Cosentino <[email protected]>
Co-authored-by: Claude Opus 5 (1M context) <[email protected]>
---
.../org/apache/camel/catalog/docs/main.adoc | 6 +-
.../camel/catalog/docs/platform-http-main.adoc | 17 ++-
.../main/camel-main-configuration-metadata.json | 2 +
components/camel-platform-http-main/pom.xml | 5 +
.../src/main/docs/platform-http-main.adoc | 17 ++-
.../JWTAuthenticationConfigurer.java | 24 ++++
...WTIssuerAudienceRequiredMainHttpServerTest.java | 136 +++++++++++++++++++++
...ties => jwt-auth-no-issuer-audience.properties} | 0
.../src/test/resources/jwt-auth.properties | 2 +
...entServerConfigurationPropertiesConfigurer.java | 7 ++
...ttpServerConfigurationPropertiesConfigurer.java | 7 ++
.../camel-main-configuration-metadata.json | 2 +
core/camel-main/src/main/docs/main.adoc | 6 +-
...ttpManagementServerConfigurationProperties.java | 28 +++++
.../main/HttpServerConfigurationProperties.java | 28 +++++
.../ROOT/pages/camel-4x-upgrade-guide-4_22.adoc | 15 +++
16 files changed, 294 insertions(+), 8 deletions(-)
diff --git
a/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/docs/main.adoc
b/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/docs/main.adoc
index 7a4ab4b3e9fc..9ca735c89719 100644
---
a/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/docs/main.adoc
+++
b/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/docs/main.adoc
@@ -194,7 +194,7 @@ The camel.routecontroller supports 12 options, which are
listed below.
=== Camel Embedded HTTP Server (only for standalone; not Spring Boot or
Quarkus) configurations
-The camel.server supports 20 options, which are listed below.
+The camel.server supports 21 options, which are listed below.
[width="100%",cols="2,5,^1,2",options="header"]
|===
@@ -207,6 +207,7 @@ The camel.server supports 20 options, which are listed
below.
| *camel.server.fileUploadDirectory* | Directory to temporary store file
uploads while Camel routes the incoming request. If no directory has been
explicit configured, then a temporary directory is created in the
java.io.tmpdir directory. | | String
| *camel.server.fileUploadEnabled* | Whether to enable file uploads being
supported (such as POST multipart/form-data) and stored into a temporary
directory. | true | boolean
| *camel.server.host* | Hostname to use for binding embedded HTTP server |
0.0.0.0 | String
+| *camel.server.jwtAllowMissingIssuerAndAudience* | Whether JWT authentication
is allowed to run without an expected issuer or audience. By default the
embedded server fails to start when a JWT keystore is configured but neither
jwtIssuer nor jwtAudience is set, because the tokens would only be checked for
signature and expiry. Enable this to accept that and validate signature and
expiry only. | false | boolean
| *camel.server.jwtAudience* | Expected JWT audience (aud claim) for token
validation. Multiple values can be separated by comma. When set, tokens whose
audience does not contain any of the configured values are rejected. | | String
| *camel.server.jwtIssuer* | Expected JWT issuer (iss claim) for token
validation. When set, tokens whose issuer does not match are rejected. | |
String
| *camel.server.jwtKeystorePassword* | Password from the keystore used for JWT
tokens validation. | | String
@@ -223,7 +224,7 @@ The camel.server supports 20 options, which are listed
below.
=== Camel Embedded HTTP management Server (only for standalone; not Spring
Boot or Quarkus) configurations
-The camel.management supports 26 options, which are listed below.
+The camel.management supports 27 options, which are listed below.
[width="100%",cols="2,5,^1,2",options="header"]
|===
@@ -242,6 +243,7 @@ The camel.management supports 26 options, which are listed
below.
| *camel.management.infoPath* | The path endpoint used to expose the info
status | /observe/info | String
| *camel.management.jolokiaEnabled* | Whether to enable jolokia. If enabled
then you can access jolokia api on context-path: /observe/jolokia | false |
boolean
| *camel.management.jolokiaPath* | The path endpoint used to expose the
jolokia data. | /observe/jolokia | String
+| *camel.management.jwtAllowMissingIssuerAndAudience* | Whether JWT
authentication is allowed to run without an expected issuer or audience. By
default the management server fails to start when a JWT keystore is configured
but neither jwtIssuer nor jwtAudience is set, because the tokens would only be
checked for signature and expiry. Enable this to accept that and validate
signature and expiry only. | false | boolean
| *camel.management.jwtAudience* | Expected JWT audience (aud claim) for token
validation. Multiple values can be separated by comma. When set, tokens whose
audience does not contain any of the configured values are rejected. | | String
| *camel.management.jwtIssuer* | Expected JWT issuer (iss claim) for token
validation. When set, tokens whose issuer does not match are rejected. | |
String
| *camel.management.jwtKeystorePassword* | Password from the keystore used for
JWT tokens validation. | | String
diff --git
a/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/docs/platform-http-main.adoc
b/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/docs/platform-http-main.adoc
index 00af28ba61c7..824d6fb7d15b 100644
---
a/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/docs/platform-http-main.adoc
+++
b/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/docs/platform-http-main.adoc
@@ -46,8 +46,14 @@ You configure these features in the `application.properties`
file using the `cam
The embedded HTTP server can validate JWT bearer tokens on incoming requests.
Token signatures are verified against a keystore, and the `exp` and `nbf`
claims
-are checked by default. Set `jwtIssuer` and/or `jwtAudience` to also validate
-the `iss` and `aud` claims.
+are checked.
+
+`jwtIssuer` and/or `jwtAudience` must be set so that the `iss` and `aud`
claims are
+validated as well. Without them a token is accepted on the strength of its
signature
+and expiry alone, which means any unexpired token signed by a key the keystore
trusts
+is accepted no matter which issuer minted it or which audience it was intended
for.
+The server therefore fails to start when a JWT keystore is configured but
neither
+value is set.
[source,properties]
----
@@ -66,6 +72,13 @@ camel.server.jwtAudience=api,internal-api
`jwtAudience` accepts a comma-separated list of values. A token is accepted if
its `aud` claim contains any of the configured values.
+If a deployment genuinely wants signature and expiry validation only, set
+`camel.server.jwtAllowMissingIssuerAndAudience=true` to allow the server to
start
+without an issuer or an audience.
+
+The same options are available on the management server as
`camel.management.jwtIssuer`,
+`camel.management.jwtAudience` and
`camel.management.jwtAllowMissingIssuerAndAudience`.
+
== See More
- xref:platform-http-vertx.adoc[Platform HTTP Vert.x]
diff --git
a/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/main/camel-main-configuration-metadata.json
b/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/main/camel-main-configuration-metadata.json
index 0b648bd4ea81..2a4b72613a27 100644
---
a/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/main/camel-main-configuration-metadata.json
+++
b/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/main/camel-main-configuration-metadata.json
@@ -302,6 +302,7 @@
{ "name": "camel.management.infoPath", "required": false, "description":
"The path endpoint used to expose the info status", "sourceType":
"org.apache.camel.main.HttpManagementServerConfigurationProperties", "type":
"string", "javaType": "java.lang.String", "defaultValue": "\/observe\/info",
"secret": false },
{ "name": "camel.management.jolokiaEnabled", "required": false,
"description": "Whether to enable jolokia. If enabled then you can access
jolokia api on context-path: \/observe\/jolokia", "sourceType":
"org.apache.camel.main.HttpManagementServerConfigurationProperties", "type":
"boolean", "javaType": "boolean", "defaultValue": false, "secret": false },
{ "name": "camel.management.jolokiaPath", "required": false,
"description": "The path endpoint used to expose the jolokia data.",
"sourceType":
"org.apache.camel.main.HttpManagementServerConfigurationProperties", "type":
"string", "javaType": "java.lang.String", "defaultValue": "\/observe\/jolokia",
"secret": false },
+ { "name": "camel.management.jwtAllowMissingIssuerAndAudience", "required":
false, "description": "Whether JWT authentication is allowed to run without an
expected issuer or audience. By default the management server fails to start
when a JWT keystore is configured but neither jwtIssuer nor jwtAudience is set,
because the tokens would only be checked for signature and expiry. Enable this
to accept that and validate signature and expiry only.", "sourceType":
"org.apache.camel.main.Http [...]
{ "name": "camel.management.jwtAudience", "required": false,
"description": "Expected JWT audience (aud claim) for token validation.
Multiple values can be separated by comma. When set, tokens whose audience does
not contain any of the configured values are rejected.", "sourceType":
"org.apache.camel.main.HttpManagementServerConfigurationProperties", "type":
"string", "javaType": "java.lang.String", "secret": false },
{ "name": "camel.management.jwtIssuer", "required": false, "description":
"Expected JWT issuer (iss claim) for token validation. When set, tokens whose
issuer does not match are rejected.", "sourceType":
"org.apache.camel.main.HttpManagementServerConfigurationProperties", "type":
"string", "javaType": "java.lang.String", "secret": false },
{ "name": "camel.management.jwtKeystorePassword", "required": false,
"description": "Password from the keystore used for JWT tokens validation.",
"sourceType":
"org.apache.camel.main.HttpManagementServerConfigurationProperties", "type":
"string", "javaType": "java.lang.String", "secret": true, "security": "secret"
},
@@ -426,6 +427,7 @@
{ "name": "camel.server.fileUploadDirectory", "required": false,
"description": "Directory to temporary store file uploads while Camel routes
the incoming request. If no directory has been explicit configured, then a
temporary directory is created in the java.io.tmpdir directory.", "sourceType":
"org.apache.camel.main.HttpServerConfigurationProperties", "type": "string",
"javaType": "java.lang.String", "secret": false },
{ "name": "camel.server.fileUploadEnabled", "required": false,
"description": "Whether to enable file uploads being supported (such as POST
multipart\/form-data) and stored into a temporary directory.", "sourceType":
"org.apache.camel.main.HttpServerConfigurationProperties", "type": "boolean",
"javaType": "boolean", "defaultValue": true, "secret": false },
{ "name": "camel.server.host", "required": false, "description": "Hostname
to use for binding embedded HTTP server", "sourceType":
"org.apache.camel.main.HttpServerConfigurationProperties", "type": "string",
"javaType": "java.lang.String", "defaultValue": "0.0.0.0", "secret": false },
+ { "name": "camel.server.jwtAllowMissingIssuerAndAudience", "required":
false, "description": "Whether JWT authentication is allowed to run without an
expected issuer or audience. By default the embedded server fails to start when
a JWT keystore is configured but neither jwtIssuer nor jwtAudience is set,
because the tokens would only be checked for signature and expiry. Enable this
to accept that and validate signature and expiry only.", "sourceType":
"org.apache.camel.main.HttpServer [...]
{ "name": "camel.server.jwtAudience", "required": false, "description":
"Expected JWT audience (aud claim) for token validation. Multiple values can be
separated by comma. When set, tokens whose audience does not contain any of the
configured values are rejected.", "sourceType":
"org.apache.camel.main.HttpServerConfigurationProperties", "type": "string",
"javaType": "java.lang.String", "secret": false },
{ "name": "camel.server.jwtIssuer", "required": false, "description":
"Expected JWT issuer (iss claim) for token validation. When set, tokens whose
issuer does not match are rejected.", "sourceType":
"org.apache.camel.main.HttpServerConfigurationProperties", "type": "string",
"javaType": "java.lang.String", "secret": false },
{ "name": "camel.server.jwtKeystorePassword", "required": false,
"description": "Password from the keystore used for JWT tokens validation.",
"sourceType": "org.apache.camel.main.HttpServerConfigurationProperties",
"type": "string", "javaType": "java.lang.String", "secret": true, "security":
"secret" },
diff --git a/components/camel-platform-http-main/pom.xml
b/components/camel-platform-http-main/pom.xml
index f18b9a1476b0..9b4cd83e1dbe 100644
--- a/components/camel-platform-http-main/pom.xml
+++ b/components/camel-platform-http-main/pom.xml
@@ -90,6 +90,11 @@
<version>${rest-assured-version}</version>
<scope>test</scope>
</dependency>
+ <dependency>
+ <groupId>org.assertj</groupId>
+ <artifactId>assertj-core</artifactId>
+ <scope>test</scope>
+ </dependency>
</dependencies>
</project>
diff --git
a/components/camel-platform-http-main/src/main/docs/platform-http-main.adoc
b/components/camel-platform-http-main/src/main/docs/platform-http-main.adoc
index 00af28ba61c7..824d6fb7d15b 100644
--- a/components/camel-platform-http-main/src/main/docs/platform-http-main.adoc
+++ b/components/camel-platform-http-main/src/main/docs/platform-http-main.adoc
@@ -46,8 +46,14 @@ You configure these features in the `application.properties`
file using the `cam
The embedded HTTP server can validate JWT bearer tokens on incoming requests.
Token signatures are verified against a keystore, and the `exp` and `nbf`
claims
-are checked by default. Set `jwtIssuer` and/or `jwtAudience` to also validate
-the `iss` and `aud` claims.
+are checked.
+
+`jwtIssuer` and/or `jwtAudience` must be set so that the `iss` and `aud`
claims are
+validated as well. Without them a token is accepted on the strength of its
signature
+and expiry alone, which means any unexpired token signed by a key the keystore
trusts
+is accepted no matter which issuer minted it or which audience it was intended
for.
+The server therefore fails to start when a JWT keystore is configured but
neither
+value is set.
[source,properties]
----
@@ -66,6 +72,13 @@ camel.server.jwtAudience=api,internal-api
`jwtAudience` accepts a comma-separated list of values. A token is accepted if
its `aud` claim contains any of the configured values.
+If a deployment genuinely wants signature and expiry validation only, set
+`camel.server.jwtAllowMissingIssuerAndAudience=true` to allow the server to
start
+without an issuer or an audience.
+
+The same options are available on the management server as
`camel.management.jwtIssuer`,
+`camel.management.jwtAudience` and
`camel.management.jwtAllowMissingIssuerAndAudience`.
+
== See More
- xref:platform-http-vertx.adoc[Platform HTTP Vert.x]
diff --git
a/components/camel-platform-http-main/src/main/java/org/apache/camel/component/platform/http/main/authentication/JWTAuthenticationConfigurer.java
b/components/camel-platform-http-main/src/main/java/org/apache/camel/component/platform/http/main/authentication/JWTAuthenticationConfigurer.java
index 9b51b4befbe3..982e1a27068a 100644
---
a/components/camel-platform-http-main/src/main/java/org/apache/camel/component/platform/http/main/authentication/JWTAuthenticationConfigurer.java
+++
b/components/camel-platform-http-main/src/main/java/org/apache/camel/component/platform/http/main/authentication/JWTAuthenticationConfigurer.java
@@ -40,6 +40,9 @@ public class JWTAuthenticationConfigurer implements
MainAuthenticationConfigurer
String path =
resolveAuthenticationPath(properties.getAuthenticationPath(),
properties.getPath());
String realm = properties.getAuthenticationRealm() != null ?
properties.getAuthenticationRealm() : null;
+ assertIssuerOrAudienceConfigured(properties.getJwtAudience(),
properties.getJwtIssuer(),
+ properties.isJwtAllowMissingIssuerAndAudience(),
"camel.server");
+
AuthenticationConfigEntry entry = new AuthenticationConfigEntry();
entry.setPath(path);
entry.setAuthenticationHandlerFactory(new
AuthenticationHandlerFactory() {
@@ -77,6 +80,9 @@ public class JWTAuthenticationConfigurer implements
MainAuthenticationConfigurer
String path =
resolveAuthenticationPath(properties.getAuthenticationPath(),
properties.getPath());
String realm = properties.getAuthenticationRealm() != null ?
properties.getAuthenticationRealm() : null;
+ assertIssuerOrAudienceConfigured(properties.getJwtAudience(),
properties.getJwtIssuer(),
+ properties.isJwtAllowMissingIssuerAndAudience(),
"camel.management");
+
AuthenticationConfigEntry entry = new AuthenticationConfigEntry();
entry.setPath(path);
entry.setAuthenticationHandlerFactory(new
AuthenticationHandlerFactory() {
@@ -106,6 +112,24 @@ public class JWTAuthenticationConfigurer implements
MainAuthenticationConfigurer
authenticationConfig.setEnabled(true);
}
+ /**
+ * A JWT authenticator built without an issuer or an audience only
verifies the token signature and the exp/nbf
+ * claims, which is weaker than the configuration suggests. Fail closed
unless that was asked for explicitly.
+ */
+ private static void assertIssuerOrAudienceConfigured(
+ String audience, String issuer, boolean allowMissing, String
configPrefix) {
+ if (allowMissing) {
+ return;
+ }
+ if (ObjectHelper.isEmpty(audience) && ObjectHelper.isEmpty(issuer)) {
+ throw new IllegalArgumentException(
+ "JWT authentication requires " + configPrefix +
".jwtIssuer or " + configPrefix
+ + ".jwtAudience to be
configured, otherwise tokens are only checked for"
+ + " signature and expiry. Set "
+ configPrefix
+ +
".jwtAllowMissingIssuerAndAudience=true to opt out.");
+ }
+ }
+
private static JWTOptions buildJwtOptions(String audience, String issuer) {
boolean isAudienceEmpty = ObjectHelper.isEmpty(audience);
diff --git
a/components/camel-platform-http-main/src/test/java/org/apache/camel/component/platform/http/main/authentication/JWTIssuerAudienceRequiredMainHttpServerTest.java
b/components/camel-platform-http-main/src/test/java/org/apache/camel/component/platform/http/main/authentication/JWTIssuerAudienceRequiredMainHttpServerTest.java
new file mode 100644
index 000000000000..921cb5d83f3d
--- /dev/null
+++
b/components/camel-platform-http-main/src/test/java/org/apache/camel/component/platform/http/main/authentication/JWTIssuerAudienceRequiredMainHttpServerTest.java
@@ -0,0 +1,136 @@
+/*
+ * 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.
+ */
+package org.apache.camel.component.platform.http.main.authentication;
+
+import io.vertx.core.Vertx;
+import io.vertx.core.json.JsonObject;
+import io.vertx.ext.auth.JWTOptions;
+import io.vertx.ext.auth.jwt.JWTAuth;
+import io.vertx.ext.auth.jwt.JWTAuthOptions;
+import org.apache.camel.builder.RouteBuilder;
+import org.apache.camel.main.HttpManagementServerConfigurationProperties;
+import org.apache.camel.main.HttpServerConfigurationProperties;
+import org.apache.camel.main.Main;
+import org.apache.camel.test.AvailablePortFinder;
+import org.junit.jupiter.api.AfterAll;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.extension.RegisterExtension;
+
+import static io.restassured.RestAssured.given;
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.assertj.core.api.Assertions.assertThatThrownBy;
+
+/**
+ * A JWT authenticator built without an issuer or an audience only verifies
the token signature and the exp/nbf claims,
+ * so the embedded server refuses to start in that configuration unless the
opt-out is set.
+ */
+class JWTIssuerAudienceRequiredMainHttpServerTest {
+
+ @RegisterExtension
+ static AvailablePortFinder.Port port = AvailablePortFinder.find();
+
+ private static final Vertx VERTX = Vertx.vertx();
+
+ @AfterAll
+ static void closeVertx() {
+ VERTX.close();
+ }
+
+ private static JWTAuth testKeystoreJwtAuth() {
+ return JWTAuth.create(VERTX, new JWTAuthOptions(
+ new JsonObject().put("keyStore", new JsonObject()
+ .put("type", "jks")
+ .put("path", "test-camel-main-auth-jwt.jks")
+ .put("password", "changeme"))));
+ }
+
+ @Test
+ void serverFailsToStartWithoutIssuerOrAudience() {
+ Main main = MainHttpServerAuthenticationTestSupport.createMain(
+ "jwt-auth-no-issuer-audience.properties", port, new
PlatformHttpRouteBuilder());
+ try {
+ assertThatThrownBy(main::start)
+ .isInstanceOf(IllegalArgumentException.class)
+ .hasMessageContaining("camel.server.jwtIssuer")
+ .hasMessageContaining("camel.server.jwtAudience")
+
.hasMessageContaining("camel.server.jwtAllowMissingIssuerAndAudience");
+ } finally {
+ MainHttpServerAuthenticationTestSupport.stopMain(main);
+ }
+ }
+
+ @Test
+ void optOutStartsAndValidatesSignatureAndExpiryOnly() {
+ // jwt-auth.properties sets jwtAllowMissingIssuerAndAudience=true
+ Main main = MainHttpServerAuthenticationTestSupport.createMain(
+ "jwt-auth.properties", port, new PlatformHttpRouteBuilder());
+ try {
+ main.start();
+
+ String token = testKeystoreJwtAuth().generateToken(new
JsonObject().put("admin", "camel"), new JWTOptions());
+
+ given()
+ .header("Authorization", "Bearer " + token)
+ .when()
+ .get("/main-http-test")
+ .then()
+ .statusCode(200);
+ } finally {
+ MainHttpServerAuthenticationTestSupport.stopMain(main);
+ }
+ }
+
+ @Test
+ void configuredIssuerAndAudienceRejectAForeignToken() {
+ Main main = MainHttpServerAuthenticationTestSupport.createMain(
+ "jwt-issuer-audience-auth.properties", port, new
PlatformHttpRouteBuilder());
+ try {
+ main.start();
+
+ // signed by a trusted key, but minted for a different issuer and
audience
+ String foreignToken = testKeystoreJwtAuth().generateToken(
+ new JsonObject().put("admin", "camel"),
+ new JWTOptions()
+ .setIssuer("https://another.example")
+ .addAudience("another-application"));
+
+ given()
+ .header("Authorization", "Bearer " + foreignToken)
+ .when()
+ .get("/main-http-test")
+ .then()
+ .statusCode(401);
+ } finally {
+ MainHttpServerAuthenticationTestSupport.stopMain(main);
+ }
+ }
+
+ @Test
+ void optOutIsNotSetByDefault() {
+ assertThat(new
HttpServerConfigurationProperties(null).isJwtAllowMissingIssuerAndAudience()).isFalse();
+ assertThat(new
HttpManagementServerConfigurationProperties(null).isJwtAllowMissingIssuerAndAudience()).isFalse();
+ }
+
+ private static class PlatformHttpRouteBuilder extends RouteBuilder {
+
+ @Override
+ public void configure() {
+ from("platform-http:/main-http-test")
+ .setBody(simple("main-http-auth-jwt-test-response"));
+ }
+ }
+}
diff --git
a/components/camel-platform-http-main/src/test/resources/jwt-auth.properties
b/components/camel-platform-http-main/src/test/resources/jwt-auth-no-issuer-audience.properties
similarity index 100%
copy from
components/camel-platform-http-main/src/test/resources/jwt-auth.properties
copy to
components/camel-platform-http-main/src/test/resources/jwt-auth-no-issuer-audience.properties
diff --git
a/components/camel-platform-http-main/src/test/resources/jwt-auth.properties
b/components/camel-platform-http-main/src/test/resources/jwt-auth.properties
index 24ea678556a7..4757708c0fc6 100644
--- a/components/camel-platform-http-main/src/test/resources/jwt-auth.properties
+++ b/components/camel-platform-http-main/src/test/resources/jwt-auth.properties
@@ -21,3 +21,5 @@ camel.server.authenticationPath=/*
camel.server.jwtKeystoreType=jks
camel.server.jwtKeystorePath=test-camel-main-auth-jwt.jks
camel.server.jwtKeystorePassword=changeme
+# this configuration deliberately validates signature and expiry only
+camel.server.jwtAllowMissingIssuerAndAudience=true
diff --git
a/core/camel-main/src/generated/java/org/apache/camel/main/HttpManagementServerConfigurationPropertiesConfigurer.java
b/core/camel-main/src/generated/java/org/apache/camel/main/HttpManagementServerConfigurationPropertiesConfigurer.java
index 6e0e3eb74b8f..701f3a5f97f3 100644
---
a/core/camel-main/src/generated/java/org/apache/camel/main/HttpManagementServerConfigurationPropertiesConfigurer.java
+++
b/core/camel-main/src/generated/java/org/apache/camel/main/HttpManagementServerConfigurationPropertiesConfigurer.java
@@ -36,6 +36,7 @@ public class
HttpManagementServerConfigurationPropertiesConfigurer extends org.a
map.put("InfoPath", java.lang.String.class);
map.put("JolokiaEnabled", boolean.class);
map.put("JolokiaPath", java.lang.String.class);
+ map.put("JwtAllowMissingIssuerAndAudience", boolean.class);
map.put("JwtAudience", java.lang.String.class);
map.put("JwtIssuer", java.lang.String.class);
map.put("JwtKeystorePassword", java.lang.String.class);
@@ -81,6 +82,8 @@ public class
HttpManagementServerConfigurationPropertiesConfigurer extends org.a
case "jolokiaEnabled": target.setJolokiaEnabled(property(camelContext,
boolean.class, value)); return true;
case "jolokiapath":
case "jolokiaPath": target.setJolokiaPath(property(camelContext,
java.lang.String.class, value)); return true;
+ case "jwtallowmissingissuerandaudience":
+ case "jwtAllowMissingIssuerAndAudience":
target.setJwtAllowMissingIssuerAndAudience(property(camelContext,
boolean.class, value)); return true;
case "jwtaudience":
case "jwtAudience": target.setJwtAudience(property(camelContext,
java.lang.String.class, value)); return true;
case "jwtissuer":
@@ -141,6 +144,8 @@ public class
HttpManagementServerConfigurationPropertiesConfigurer extends org.a
case "jolokiaEnabled": return boolean.class;
case "jolokiapath":
case "jolokiaPath": return java.lang.String.class;
+ case "jwtallowmissingissuerandaudience":
+ case "jwtAllowMissingIssuerAndAudience": return boolean.class;
case "jwtaudience":
case "jwtAudience": return java.lang.String.class;
case "jwtissuer":
@@ -197,6 +202,8 @@ public class
HttpManagementServerConfigurationPropertiesConfigurer extends org.a
case "jolokiaEnabled": return target.isJolokiaEnabled();
case "jolokiapath":
case "jolokiaPath": return target.getJolokiaPath();
+ case "jwtallowmissingissuerandaudience":
+ case "jwtAllowMissingIssuerAndAudience": return
target.isJwtAllowMissingIssuerAndAudience();
case "jwtaudience":
case "jwtAudience": return target.getJwtAudience();
case "jwtissuer":
diff --git
a/core/camel-main/src/generated/java/org/apache/camel/main/HttpServerConfigurationPropertiesConfigurer.java
b/core/camel-main/src/generated/java/org/apache/camel/main/HttpServerConfigurationPropertiesConfigurer.java
index cec5e0d0cf15..7c1d68afe45d 100644
---
a/core/camel-main/src/generated/java/org/apache/camel/main/HttpServerConfigurationPropertiesConfigurer.java
+++
b/core/camel-main/src/generated/java/org/apache/camel/main/HttpServerConfigurationPropertiesConfigurer.java
@@ -30,6 +30,7 @@ public class HttpServerConfigurationPropertiesConfigurer
extends org.apache.came
map.put("FileUploadDirectory", java.lang.String.class);
map.put("FileUploadEnabled", boolean.class);
map.put("Host", java.lang.String.class);
+ map.put("JwtAllowMissingIssuerAndAudience", boolean.class);
map.put("JwtAudience", java.lang.String.class);
map.put("JwtIssuer", java.lang.String.class);
map.put("JwtKeystorePassword", java.lang.String.class);
@@ -63,6 +64,8 @@ public class HttpServerConfigurationPropertiesConfigurer
extends org.apache.came
case "fileuploadenabled":
case "fileUploadEnabled":
target.setFileUploadEnabled(property(camelContext, boolean.class, value));
return true;
case "host": target.setHost(property(camelContext,
java.lang.String.class, value)); return true;
+ case "jwtallowmissingissuerandaudience":
+ case "jwtAllowMissingIssuerAndAudience":
target.setJwtAllowMissingIssuerAndAudience(property(camelContext,
boolean.class, value)); return true;
case "jwtaudience":
case "jwtAudience": target.setJwtAudience(property(camelContext,
java.lang.String.class, value)); return true;
case "jwtissuer":
@@ -111,6 +114,8 @@ public class HttpServerConfigurationPropertiesConfigurer
extends org.apache.came
case "fileuploadenabled":
case "fileUploadEnabled": return boolean.class;
case "host": return java.lang.String.class;
+ case "jwtallowmissingissuerandaudience":
+ case "jwtAllowMissingIssuerAndAudience": return boolean.class;
case "jwtaudience":
case "jwtAudience": return java.lang.String.class;
case "jwtissuer":
@@ -155,6 +160,8 @@ public class HttpServerConfigurationPropertiesConfigurer
extends org.apache.came
case "fileuploadenabled":
case "fileUploadEnabled": return target.isFileUploadEnabled();
case "host": return target.getHost();
+ case "jwtallowmissingissuerandaudience":
+ case "jwtAllowMissingIssuerAndAudience": return
target.isJwtAllowMissingIssuerAndAudience();
case "jwtaudience":
case "jwtAudience": return target.getJwtAudience();
case "jwtissuer":
diff --git
a/core/camel-main/src/generated/resources/META-INF/camel-main-configuration-metadata.json
b/core/camel-main/src/generated/resources/META-INF/camel-main-configuration-metadata.json
index 0b648bd4ea81..2a4b72613a27 100644
---
a/core/camel-main/src/generated/resources/META-INF/camel-main-configuration-metadata.json
+++
b/core/camel-main/src/generated/resources/META-INF/camel-main-configuration-metadata.json
@@ -302,6 +302,7 @@
{ "name": "camel.management.infoPath", "required": false, "description":
"The path endpoint used to expose the info status", "sourceType":
"org.apache.camel.main.HttpManagementServerConfigurationProperties", "type":
"string", "javaType": "java.lang.String", "defaultValue": "\/observe\/info",
"secret": false },
{ "name": "camel.management.jolokiaEnabled", "required": false,
"description": "Whether to enable jolokia. If enabled then you can access
jolokia api on context-path: \/observe\/jolokia", "sourceType":
"org.apache.camel.main.HttpManagementServerConfigurationProperties", "type":
"boolean", "javaType": "boolean", "defaultValue": false, "secret": false },
{ "name": "camel.management.jolokiaPath", "required": false,
"description": "The path endpoint used to expose the jolokia data.",
"sourceType":
"org.apache.camel.main.HttpManagementServerConfigurationProperties", "type":
"string", "javaType": "java.lang.String", "defaultValue": "\/observe\/jolokia",
"secret": false },
+ { "name": "camel.management.jwtAllowMissingIssuerAndAudience", "required":
false, "description": "Whether JWT authentication is allowed to run without an
expected issuer or audience. By default the management server fails to start
when a JWT keystore is configured but neither jwtIssuer nor jwtAudience is set,
because the tokens would only be checked for signature and expiry. Enable this
to accept that and validate signature and expiry only.", "sourceType":
"org.apache.camel.main.Http [...]
{ "name": "camel.management.jwtAudience", "required": false,
"description": "Expected JWT audience (aud claim) for token validation.
Multiple values can be separated by comma. When set, tokens whose audience does
not contain any of the configured values are rejected.", "sourceType":
"org.apache.camel.main.HttpManagementServerConfigurationProperties", "type":
"string", "javaType": "java.lang.String", "secret": false },
{ "name": "camel.management.jwtIssuer", "required": false, "description":
"Expected JWT issuer (iss claim) for token validation. When set, tokens whose
issuer does not match are rejected.", "sourceType":
"org.apache.camel.main.HttpManagementServerConfigurationProperties", "type":
"string", "javaType": "java.lang.String", "secret": false },
{ "name": "camel.management.jwtKeystorePassword", "required": false,
"description": "Password from the keystore used for JWT tokens validation.",
"sourceType":
"org.apache.camel.main.HttpManagementServerConfigurationProperties", "type":
"string", "javaType": "java.lang.String", "secret": true, "security": "secret"
},
@@ -426,6 +427,7 @@
{ "name": "camel.server.fileUploadDirectory", "required": false,
"description": "Directory to temporary store file uploads while Camel routes
the incoming request. If no directory has been explicit configured, then a
temporary directory is created in the java.io.tmpdir directory.", "sourceType":
"org.apache.camel.main.HttpServerConfigurationProperties", "type": "string",
"javaType": "java.lang.String", "secret": false },
{ "name": "camel.server.fileUploadEnabled", "required": false,
"description": "Whether to enable file uploads being supported (such as POST
multipart\/form-data) and stored into a temporary directory.", "sourceType":
"org.apache.camel.main.HttpServerConfigurationProperties", "type": "boolean",
"javaType": "boolean", "defaultValue": true, "secret": false },
{ "name": "camel.server.host", "required": false, "description": "Hostname
to use for binding embedded HTTP server", "sourceType":
"org.apache.camel.main.HttpServerConfigurationProperties", "type": "string",
"javaType": "java.lang.String", "defaultValue": "0.0.0.0", "secret": false },
+ { "name": "camel.server.jwtAllowMissingIssuerAndAudience", "required":
false, "description": "Whether JWT authentication is allowed to run without an
expected issuer or audience. By default the embedded server fails to start when
a JWT keystore is configured but neither jwtIssuer nor jwtAudience is set,
because the tokens would only be checked for signature and expiry. Enable this
to accept that and validate signature and expiry only.", "sourceType":
"org.apache.camel.main.HttpServer [...]
{ "name": "camel.server.jwtAudience", "required": false, "description":
"Expected JWT audience (aud claim) for token validation. Multiple values can be
separated by comma. When set, tokens whose audience does not contain any of the
configured values are rejected.", "sourceType":
"org.apache.camel.main.HttpServerConfigurationProperties", "type": "string",
"javaType": "java.lang.String", "secret": false },
{ "name": "camel.server.jwtIssuer", "required": false, "description":
"Expected JWT issuer (iss claim) for token validation. When set, tokens whose
issuer does not match are rejected.", "sourceType":
"org.apache.camel.main.HttpServerConfigurationProperties", "type": "string",
"javaType": "java.lang.String", "secret": false },
{ "name": "camel.server.jwtKeystorePassword", "required": false,
"description": "Password from the keystore used for JWT tokens validation.",
"sourceType": "org.apache.camel.main.HttpServerConfigurationProperties",
"type": "string", "javaType": "java.lang.String", "secret": true, "security":
"secret" },
diff --git a/core/camel-main/src/main/docs/main.adoc
b/core/camel-main/src/main/docs/main.adoc
index 7a4ab4b3e9fc..9ca735c89719 100644
--- a/core/camel-main/src/main/docs/main.adoc
+++ b/core/camel-main/src/main/docs/main.adoc
@@ -194,7 +194,7 @@ The camel.routecontroller supports 12 options, which are
listed below.
=== Camel Embedded HTTP Server (only for standalone; not Spring Boot or
Quarkus) configurations
-The camel.server supports 20 options, which are listed below.
+The camel.server supports 21 options, which are listed below.
[width="100%",cols="2,5,^1,2",options="header"]
|===
@@ -207,6 +207,7 @@ The camel.server supports 20 options, which are listed
below.
| *camel.server.fileUploadDirectory* | Directory to temporary store file
uploads while Camel routes the incoming request. If no directory has been
explicit configured, then a temporary directory is created in the
java.io.tmpdir directory. | | String
| *camel.server.fileUploadEnabled* | Whether to enable file uploads being
supported (such as POST multipart/form-data) and stored into a temporary
directory. | true | boolean
| *camel.server.host* | Hostname to use for binding embedded HTTP server |
0.0.0.0 | String
+| *camel.server.jwtAllowMissingIssuerAndAudience* | Whether JWT authentication
is allowed to run without an expected issuer or audience. By default the
embedded server fails to start when a JWT keystore is configured but neither
jwtIssuer nor jwtAudience is set, because the tokens would only be checked for
signature and expiry. Enable this to accept that and validate signature and
expiry only. | false | boolean
| *camel.server.jwtAudience* | Expected JWT audience (aud claim) for token
validation. Multiple values can be separated by comma. When set, tokens whose
audience does not contain any of the configured values are rejected. | | String
| *camel.server.jwtIssuer* | Expected JWT issuer (iss claim) for token
validation. When set, tokens whose issuer does not match are rejected. | |
String
| *camel.server.jwtKeystorePassword* | Password from the keystore used for JWT
tokens validation. | | String
@@ -223,7 +224,7 @@ The camel.server supports 20 options, which are listed
below.
=== Camel Embedded HTTP management Server (only for standalone; not Spring
Boot or Quarkus) configurations
-The camel.management supports 26 options, which are listed below.
+The camel.management supports 27 options, which are listed below.
[width="100%",cols="2,5,^1,2",options="header"]
|===
@@ -242,6 +243,7 @@ The camel.management supports 26 options, which are listed
below.
| *camel.management.infoPath* | The path endpoint used to expose the info
status | /observe/info | String
| *camel.management.jolokiaEnabled* | Whether to enable jolokia. If enabled
then you can access jolokia api on context-path: /observe/jolokia | false |
boolean
| *camel.management.jolokiaPath* | The path endpoint used to expose the
jolokia data. | /observe/jolokia | String
+| *camel.management.jwtAllowMissingIssuerAndAudience* | Whether JWT
authentication is allowed to run without an expected issuer or audience. By
default the management server fails to start when a JWT keystore is configured
but neither jwtIssuer nor jwtAudience is set, because the tokens would only be
checked for signature and expiry. Enable this to accept that and validate
signature and expiry only. | false | boolean
| *camel.management.jwtAudience* | Expected JWT audience (aud claim) for token
validation. Multiple values can be separated by comma. When set, tokens whose
audience does not contain any of the configured values are rejected. | | String
| *camel.management.jwtIssuer* | Expected JWT issuer (iss claim) for token
validation. When set, tokens whose issuer does not match are rejected. | |
String
| *camel.management.jwtKeystorePassword* | Password from the keystore used for
JWT tokens validation. | | String
diff --git
a/core/camel-main/src/main/java/org/apache/camel/main/HttpManagementServerConfigurationProperties.java
b/core/camel-main/src/main/java/org/apache/camel/main/HttpManagementServerConfigurationProperties.java
index 0f8891a94766..a705f287a334 100644
---
a/core/camel-main/src/main/java/org/apache/camel/main/HttpManagementServerConfigurationProperties.java
+++
b/core/camel-main/src/main/java/org/apache/camel/main/HttpManagementServerConfigurationProperties.java
@@ -77,6 +77,8 @@ public class HttpManagementServerConfigurationProperties
implements BootstrapClo
private String jwtIssuer;
@Metadata(label = "security")
private String jwtAudience;
+ @Metadata(label = "security", defaultValue = "false", security =
"insecure:dev")
+ private boolean jwtAllowMissingIssuerAndAudience;
public
HttpManagementServerConfigurationProperties(MainConfigurationProperties parent)
{
this.parent = parent;
@@ -389,6 +391,20 @@ public class HttpManagementServerConfigurationProperties
implements BootstrapClo
this.jwtAudience = jwtAudience;
}
+ public boolean isJwtAllowMissingIssuerAndAudience() {
+ return jwtAllowMissingIssuerAndAudience;
+ }
+
+ /**
+ * Whether JWT authentication is allowed to run without an expected issuer
or audience. By default the management
+ * server fails to start when a JWT keystore is configured but neither
jwtIssuer nor jwtAudience is set, because the
+ * tokens would only be checked for signature and expiry. Enable this to
accept that and validate signature and
+ * expiry only.
+ */
+ public void setJwtAllowMissingIssuerAndAudience(boolean
jwtAllowMissingIssuerAndAudience) {
+ this.jwtAllowMissingIssuerAndAudience =
jwtAllowMissingIssuerAndAudience;
+ }
+
/**
* Whether embedded HTTP management server is enabled. By default, the
server is not enabled.
*/
@@ -586,6 +602,18 @@ public class HttpManagementServerConfigurationProperties
implements BootstrapClo
return this;
}
+ /**
+ * Whether JWT authentication is allowed to run without an expected issuer
or audience. By default the management
+ * server fails to start when a JWT keystore is configured but neither
jwtIssuer nor jwtAudience is set, because the
+ * tokens would only be checked for signature and expiry. Enable this to
accept that and validate signature and
+ * expiry only.
+ */
+ public HttpManagementServerConfigurationProperties
withJwtAllowMissingIssuerAndAudience(
+ boolean jwtAllowMissingIssuerAndAudience) {
+ this.jwtAllowMissingIssuerAndAudience =
jwtAllowMissingIssuerAndAudience;
+ return this;
+ }
+
/**
* The path endpoint used to expose the info status
*/
diff --git
a/core/camel-main/src/main/java/org/apache/camel/main/HttpServerConfigurationProperties.java
b/core/camel-main/src/main/java/org/apache/camel/main/HttpServerConfigurationProperties.java
index d51cf5e9a36a..c153b60d1d3f 100644
---
a/core/camel-main/src/main/java/org/apache/camel/main/HttpServerConfigurationProperties.java
+++
b/core/camel-main/src/main/java/org/apache/camel/main/HttpServerConfigurationProperties.java
@@ -68,6 +68,8 @@ public class HttpServerConfigurationProperties implements
BootstrapCloseable {
private String jwtIssuer;
@Metadata(label = "security")
private String jwtAudience;
+ @Metadata(label = "security", defaultValue = "false", security =
"insecure:dev")
+ private boolean jwtAllowMissingIssuerAndAudience;
public HttpServerConfigurationProperties(MainConfigurationProperties
parent) {
this.parent = parent;
@@ -310,6 +312,20 @@ public class HttpServerConfigurationProperties implements
BootstrapCloseable {
this.jwtAudience = jwtAudience;
}
+ public boolean isJwtAllowMissingIssuerAndAudience() {
+ return jwtAllowMissingIssuerAndAudience;
+ }
+
+ /**
+ * Whether JWT authentication is allowed to run without an expected issuer
or audience. By default the embedded
+ * server fails to start when a JWT keystore is configured but neither
jwtIssuer nor jwtAudience is set, because the
+ * tokens would only be checked for signature and expiry. Enable this to
accept that and validate signature and
+ * expiry only.
+ */
+ public void setJwtAllowMissingIssuerAndAudience(boolean
jwtAllowMissingIssuerAndAudience) {
+ this.jwtAllowMissingIssuerAndAudience =
jwtAllowMissingIssuerAndAudience;
+ }
+
/**
* Whether embedded HTTP server is enabled. By default, the server is not
enabled.
*/
@@ -478,4 +494,16 @@ public class HttpServerConfigurationProperties implements
BootstrapCloseable {
return this;
}
+ /**
+ * Whether JWT authentication is allowed to run without an expected issuer
or audience. By default the embedded
+ * server fails to start when a JWT keystore is configured but neither
jwtIssuer nor jwtAudience is set, because the
+ * tokens would only be checked for signature and expiry. Enable this to
accept that and validate signature and
+ * expiry only.
+ */
+ public HttpServerConfigurationProperties
withJwtAllowMissingIssuerAndAudience(
+ boolean jwtAllowMissingIssuerAndAudience) {
+ this.jwtAllowMissingIssuerAndAudience =
jwtAllowMissingIssuerAndAudience;
+ return this;
+ }
+
}
diff --git
a/docs/user-manual/modules/ROOT/pages/camel-4x-upgrade-guide-4_22.adoc
b/docs/user-manual/modules/ROOT/pages/camel-4x-upgrade-guide-4_22.adoc
index b5f88e63b0eb..76a851fecad1 100644
--- a/docs/user-manual/modules/ROOT/pages/camel-4x-upgrade-guide-4_22.adoc
+++ b/docs/user-manual/modules/ROOT/pages/camel-4x-upgrade-guide-4_22.adoc
@@ -1340,3 +1340,18 @@ that resolve outside the configured directory are
rejected.
If `downloadFileName` is configured with an expression (i.e. it contains `$`),
the local
path is built by that expression as before and is not subject to this check.
+
+=== camel-main - JWT authentication requires an issuer or an audience
+
+The embedded HTTP server and the management server build their JWT
authenticator from the
+configured keystore. When neither `jwtIssuer` nor `jwtAudience` was set, no
`JWTOptions` was
+applied, so tokens were only checked for signature and expiry and the `iss`
and `aud` claims
+were not validated.
+
+The server now fails to start when a JWT keystore is configured but neither
`camel.server.jwtIssuer`
+nor `camel.server.jwtAudience` is set (and likewise for `camel.management.*`).
Configure the issuer
+and/or the audience that tokens are expected to carry.
+
+If a deployment genuinely wants signature and expiry validation only, set
+`camel.server.jwtAllowMissingIssuerAndAudience=true` (or
`camel.management.jwtAllowMissingIssuerAndAudience=true`)
+to keep the previous behaviour.