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 225c7f367946e..9d1f25dc6189f 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 @@ -215,6 +215,8 @@ { "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.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" }, { "name": "camel.management.jwtKeystorePath", "required": false, "description": "Path to the keystore file used for JWT tokens validation.", "sourceType": "org.apache.camel.main.HttpManagementServerConfigurationProperties", "type": "string", "javaType": "java.lang.String", "secret": false }, { "name": "camel.management.jwtKeystoreType", "required": false, "description": "Type of the keystore used for JWT tokens validation (jks, pkcs12, etc.).", "sourceType": "org.apache.camel.main.HttpManagementServerConfigurationProperties", "type": "string", "javaType": "java.lang.String", "secret": false }, @@ -332,6 +334,8 @@ { "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.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" }, { "name": "camel.server.jwtKeystorePath", "required": false, "description": "Path to the keystore file used for JWT tokens validation.", "sourceType": "org.apache.camel.main.HttpServerConfigurationProperties", "type": "string", "javaType": "java.lang.String", "secret": false }, { "name": "camel.server.jwtKeystoreType", "required": false, "description": "Type of the keystore used for JWT tokens validation (jks, pkcs12, etc.).", "sourceType": "org.apache.camel.main.HttpServerConfigurationProperties", "type": "string", "javaType": "java.lang.String", "secret": false }, 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 2992696a0b343..141c11e4da03b 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 @@ -42,6 +42,30 @@ These features are as follows: You configure these features in the `application.properties` file using the `camel.server.xxx` and `camel.management.xxx` options. +== JWT authentication + +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. + +[source,properties] +---- +camel.server.enabled=true +camel.server.authenticationEnabled=true +camel.server.authenticationPath=/* + +camel.server.jwtKeystoreType=jks +camel.server.jwtKeystorePath=keystore.jks +camel.server.jwtKeystorePassword=changeme + +camel.server.jwtIssuer=https://issuer.example.com +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. + == 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 03380ba21611d..9b51b4befbe3a 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 @@ -17,6 +17,7 @@ package org.apache.camel.component.platform.http.main.authentication; import io.vertx.core.json.JsonObject; +import io.vertx.ext.auth.JWTOptions; import io.vertx.ext.auth.authentication.AuthenticationProvider; import io.vertx.ext.auth.jwt.JWTAuth; import io.vertx.ext.auth.jwt.JWTAuthOptions; @@ -27,6 +28,7 @@ import org.apache.camel.component.platform.http.vertx.auth.AuthenticationConfig.AuthenticationHandlerFactory; import org.apache.camel.main.HttpManagementServerConfigurationProperties; import org.apache.camel.main.HttpServerConfigurationProperties; +import org.apache.camel.util.ObjectHelper; public class JWTAuthenticationConfigurer implements MainAuthenticationConfigurer { @@ -48,13 +50,20 @@ public AuthenticationHandler createAuthentica return JWTAuthHandler.create(authProvider, realm); } }); - entry.setAuthenticationProviderFactory(vertx -> JWTAuth.create( - vertx, - new JWTAuthOptions( - new JsonObject().put("keyStore", new JsonObject() - .put("type", properties.getJwtKeystoreType()) - .put("path", properties.getJwtKeystorePath()) - .put("password", properties.getJwtKeystorePassword()))))); + + entry.setAuthenticationProviderFactory(vertx -> { + JWTAuthOptions jwtAuthOptions = new JWTAuthOptions( + new JsonObject().put("keyStore", new JsonObject() + .put("type", properties.getJwtKeystoreType()) + .put("path", properties.getJwtKeystorePath()) + .put("password", properties.getJwtKeystorePassword()))); + + JWTOptions jwtOptions = buildJwtOptions(properties.getJwtAudience(), properties.getJwtIssuer()); + if (jwtOptions != null) { + jwtAuthOptions.setJWTOptions(jwtOptions); + } + return JWTAuth.create(vertx, jwtAuthOptions); + }); authenticationConfig.getEntries().add(entry); authenticationConfig.setEnabled(true); @@ -78,15 +87,48 @@ public AuthenticationHandler createAuthentica return JWTAuthHandler.create(authProvider, realm); } }); - entry.setAuthenticationProviderFactory(vertx -> JWTAuth.create( - vertx, - new JWTAuthOptions( - new JsonObject().put("keyStore", new JsonObject() - .put("type", properties.getJwtKeystoreType()) - .put("path", properties.getJwtKeystorePath()) - .put("password", properties.getJwtKeystorePassword()))))); + + entry.setAuthenticationProviderFactory(vertx -> { + JWTAuthOptions jwtAuthOptions = new JWTAuthOptions( + new JsonObject().put("keyStore", new JsonObject() + .put("type", properties.getJwtKeystoreType()) + .put("path", properties.getJwtKeystorePath()) + .put("password", properties.getJwtKeystorePassword()))); + + JWTOptions jwtOptions = buildJwtOptions(properties.getJwtAudience(), properties.getJwtIssuer()); + if (jwtOptions != null) { + jwtAuthOptions.setJWTOptions(jwtOptions); + } + return JWTAuth.create(vertx, jwtAuthOptions); + }); authenticationConfig.getEntries().add(entry); authenticationConfig.setEnabled(true); } + + private static JWTOptions buildJwtOptions(String audience, String issuer) { + + boolean isAudienceEmpty = ObjectHelper.isEmpty(audience); + boolean isIssuerEmpty = ObjectHelper.isEmpty(issuer); + + if (isAudienceEmpty && isIssuerEmpty) { + return null; + } + + JWTOptions options = new JWTOptions(); + + if (!isAudienceEmpty) { + for (String a : audience.split(",")) { + if (ObjectHelper.isNotEmpty(a)) { + options.addAudience(a.trim()); + } + } + } + + if (!isIssuerEmpty) { + options.setIssuer(issuer); + } + + return options; + } } diff --git a/components/camel-platform-http-main/src/test/java/org/apache/camel/component/platform/http/main/authentication/JWTIssuerAudienceAuthenticationMainHttpServerTest.java b/components/camel-platform-http-main/src/test/java/org/apache/camel/component/platform/http/main/authentication/JWTIssuerAudienceAuthenticationMainHttpServerTest.java new file mode 100644 index 0000000000000..251cb3cc61588 --- /dev/null +++ b/components/camel-platform-http-main/src/test/java/org/apache/camel/component/platform/http/main/authentication/JWTIssuerAudienceAuthenticationMainHttpServerTest.java @@ -0,0 +1,164 @@ +/* + * 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.CamelContext; +import org.apache.camel.builder.RouteBuilder; +import org.apache.camel.main.Main; +import org.junit.jupiter.api.AfterAll; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.Test; + +import static io.restassured.RestAssured.given; +import static org.hamcrest.Matchers.equalTo; +import static org.junit.jupiter.api.Assertions.assertNotNull; + +public class JWTIssuerAudienceAuthenticationMainHttpServerTest { + + private static final String EXPECTED_ISSUER = "https://issuer.camel.example"; + private static final String EXPECTED_AUDIENCE = "camel-api"; + private static final String SECOND_EXPECTED_AUDIENCE = "camel-internal"; + + private static Main main; + private static JWTAuth jwtAuth; + + @BeforeAll + static void init() { + main = new Main(); + main.setPropertyPlaceholderLocations("jwt-issuer-audience-auth.properties"); + main.configure().addRoutesBuilder(new PlatformHttpRouteBuilder()); + main.enableTrace(); + main.start(); + + jwtAuth = JWTAuth.create(Vertx.vertx(), new JWTAuthOptions( + new JsonObject().put("keyStore", new JsonObject() + .put("type", "jks") + .put("path", "test-camel-main-auth-jwt.jks") + .put("password", "changeme")))); + } + + @AfterAll + static void tearDown() { + main.stop(); + } + + @Test + void testNoAuthHeaderReturns401() { + CamelContext camelContext = main.getCamelContext(); + assertNotNull(camelContext); + + given() + .when() + .get("/main-http-test") + .then() + .statusCode(401) + .body(equalTo("Unauthorized")); + } + + @Test + void testMatchingIssuerAndAudienceReturns200() { + String token = mintToken(new JWTOptions() + .setIssuer(EXPECTED_ISSUER) + .addAudience(EXPECTED_AUDIENCE)); + + given() + .header("Authorization", "Bearer " + token) + .when() + .get("/main-http-test") + .then() + .statusCode(200) + .body(equalTo("main-http-auth-jwt-test-response")); + } + + @Test + void testMatchingIssuerAndSecondAudienceReturns200() { + String token = mintToken(new JWTOptions() + .setIssuer(EXPECTED_ISSUER) + .addAudience(SECOND_EXPECTED_AUDIENCE)); + + given() + .header("Authorization", "Bearer " + token) + .when() + .get("/main-http-test") + .then() + .statusCode(200) + .body(equalTo("main-http-auth-jwt-test-response")); + } + + @Test + void testWrongIssuerReturns401() { + String token = mintToken(new JWTOptions() + .setIssuer("https://attacker.example") + .addAudience(EXPECTED_AUDIENCE)); + + given() + .header("Authorization", "Bearer " + token) + .when() + .get("/main-http-test") + .then() + .statusCode(401) + .body(equalTo("Unauthorized")); + } + + @Test + void testWrongAudienceReturns401() { + String token = mintToken(new JWTOptions() + .setIssuer(EXPECTED_ISSUER) + .addAudience("not-in-list")); + + given() + .header("Authorization", "Bearer " + token) + .when() + .get("/main-http-test") + .then() + .statusCode(401) + .body(equalTo("Unauthorized")); + } + + @Test + void testMissingAudienceReturns401() { + String token = mintToken(new JWTOptions() + .setIssuer(EXPECTED_ISSUER)); + + given() + .header("Authorization", "Bearer " + token) + .when() + .get("/main-http-test") + .then() + .statusCode(401) + .body(equalTo("Unauthorized")); + } + + private static String mintToken(JWTOptions jwtOptions) { + return jwtAuth.generateToken(new JsonObject().put("admin", "camel"), jwtOptions); + } + + private static class PlatformHttpRouteBuilder extends RouteBuilder { + + @Override + public void configure() throws Exception { + from("platform-http:/main-http-test") + .log("Received request with headers: ${headers}\nWith body: ${body}") + .setBody(simple("main-http-auth-jwt-test-response")); + } + } +} diff --git a/components/camel-platform-http-main/src/test/resources/jwt-issuer-audience-auth.properties b/components/camel-platform-http-main/src/test/resources/jwt-issuer-audience-auth.properties new file mode 100644 index 0000000000000..a35c592603786 --- /dev/null +++ b/components/camel-platform-http-main/src/test/resources/jwt-issuer-audience-auth.properties @@ -0,0 +1,25 @@ +## --------------------------------------------------------------------------- +## 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. +## --------------------------------------------------------------------------- +camel.server.enabled=true + +camel.server.authenticationEnabled=true +camel.server.authenticationPath=/* +camel.server.jwtKeystoreType=jks +camel.server.jwtKeystorePath=test-camel-main-auth-jwt.jks +camel.server.jwtKeystorePassword=changeme +camel.server.jwtIssuer=https://issuer.camel.example +camel.server.jwtAudience=camel-api,camel-internal 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 ad55f4821f9fd..6e0e3eb74b8f3 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,8 @@ 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("JwtAudience", java.lang.String.class); + map.put("JwtIssuer", java.lang.String.class); map.put("JwtKeystorePassword", java.lang.String.class); map.put("JwtKeystorePath", java.lang.String.class); map.put("JwtKeystoreType", java.lang.String.class); @@ -79,6 +81,10 @@ public boolean configure(CamelContext camelContext, Object obj, String name, Obj 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 "jwtaudience": + case "jwtAudience": target.setJwtAudience(property(camelContext, java.lang.String.class, value)); return true; + case "jwtissuer": + case "jwtIssuer": target.setJwtIssuer(property(camelContext, java.lang.String.class, value)); return true; case "jwtkeystorepassword": case "jwtKeystorePassword": target.setJwtKeystorePassword(property(camelContext, java.lang.String.class, value)); return true; case "jwtkeystorepath": @@ -135,6 +141,10 @@ public Class getOptionType(String name, boolean ignoreCase) { case "jolokiaEnabled": return boolean.class; case "jolokiapath": case "jolokiaPath": return java.lang.String.class; + case "jwtaudience": + case "jwtAudience": return java.lang.String.class; + case "jwtissuer": + case "jwtIssuer": return java.lang.String.class; case "jwtkeystorepassword": case "jwtKeystorePassword": return java.lang.String.class; case "jwtkeystorepath": @@ -187,6 +197,10 @@ public Object getOptionValue(Object obj, String name, boolean ignoreCase) { case "jolokiaEnabled": return target.isJolokiaEnabled(); case "jolokiapath": case "jolokiaPath": return target.getJolokiaPath(); + case "jwtaudience": + case "jwtAudience": return target.getJwtAudience(); + case "jwtissuer": + case "jwtIssuer": return target.getJwtIssuer(); case "jwtkeystorepassword": case "jwtKeystorePassword": return target.getJwtKeystorePassword(); case "jwtkeystorepath": 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 c4bbf04ce2df7..cec5e0d0cf157 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,8 @@ 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("JwtAudience", java.lang.String.class); + map.put("JwtIssuer", java.lang.String.class); map.put("JwtKeystorePassword", java.lang.String.class); map.put("JwtKeystorePath", java.lang.String.class); map.put("JwtKeystoreType", java.lang.String.class); @@ -61,6 +63,10 @@ public boolean configure(CamelContext camelContext, Object obj, String name, Obj 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 "jwtaudience": + case "jwtAudience": target.setJwtAudience(property(camelContext, java.lang.String.class, value)); return true; + case "jwtissuer": + case "jwtIssuer": target.setJwtIssuer(property(camelContext, java.lang.String.class, value)); return true; case "jwtkeystorepassword": case "jwtKeystorePassword": target.setJwtKeystorePassword(property(camelContext, java.lang.String.class, value)); return true; case "jwtkeystorepath": @@ -105,6 +111,10 @@ public Class getOptionType(String name, boolean ignoreCase) { case "fileuploadenabled": case "fileUploadEnabled": return boolean.class; case "host": return java.lang.String.class; + case "jwtaudience": + case "jwtAudience": return java.lang.String.class; + case "jwtissuer": + case "jwtIssuer": return java.lang.String.class; case "jwtkeystorepassword": case "jwtKeystorePassword": return java.lang.String.class; case "jwtkeystorepath": @@ -145,6 +155,10 @@ public Object getOptionValue(Object obj, String name, boolean ignoreCase) { case "fileuploadenabled": case "fileUploadEnabled": return target.isFileUploadEnabled(); case "host": return target.getHost(); + case "jwtaudience": + case "jwtAudience": return target.getJwtAudience(); + case "jwtissuer": + case "jwtIssuer": return target.getJwtIssuer(); case "jwtkeystorepassword": case "jwtKeystorePassword": return target.getJwtKeystorePassword(); case "jwtkeystorepath": 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 225c7f367946e..9d1f25dc6189f 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 @@ -215,6 +215,8 @@ { "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.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" }, { "name": "camel.management.jwtKeystorePath", "required": false, "description": "Path to the keystore file used for JWT tokens validation.", "sourceType": "org.apache.camel.main.HttpManagementServerConfigurationProperties", "type": "string", "javaType": "java.lang.String", "secret": false }, { "name": "camel.management.jwtKeystoreType", "required": false, "description": "Type of the keystore used for JWT tokens validation (jks, pkcs12, etc.).", "sourceType": "org.apache.camel.main.HttpManagementServerConfigurationProperties", "type": "string", "javaType": "java.lang.String", "secret": false }, @@ -332,6 +334,8 @@ { "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.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" }, { "name": "camel.server.jwtKeystorePath", "required": false, "description": "Path to the keystore file used for JWT tokens validation.", "sourceType": "org.apache.camel.main.HttpServerConfigurationProperties", "type": "string", "javaType": "java.lang.String", "secret": false }, { "name": "camel.server.jwtKeystoreType", "required": false, "description": "Type of the keystore used for JWT tokens validation (jks, pkcs12, etc.).", "sourceType": "org.apache.camel.main.HttpServerConfigurationProperties", "type": "string", "javaType": "java.lang.String", "secret": false }, diff --git a/core/camel-main/src/main/docs/main.adoc b/core/camel-main/src/main/docs/main.adoc index ac293f1801040..b61505434e8a3 100644 --- a/core/camel-main/src/main/docs/main.adoc +++ b/core/camel-main/src/main/docs/main.adoc @@ -196,7 +196,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 18 options, which are listed below. +The camel.server supports 20 options, which are listed below. [width="100%",cols="2,5,^1,2",options="header"] |=== @@ -209,6 +209,8 @@ The camel.server supports 18 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.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 | *camel.server.jwtKeystorePath* | Path to the keystore file used for JWT tokens validation. | | String | *camel.server.jwtKeystoreType* | Type of the keystore used for JWT tokens validation (jks, pkcs12, etc.). | | String @@ -223,7 +225,7 @@ The camel.server supports 18 options, which are listed below. === Camel Embedded HTTP management Server (only for standalone; not Spring Boot or Quarkus) configurations -The camel.management supports 24 options, which are listed below. +The camel.management supports 26 options, which are listed below. [width="100%",cols="2,5,^1,2",options="header"] |=== @@ -242,6 +244,8 @@ The camel.management supports 24 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.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 | *camel.management.jwtKeystorePath* | Path to the keystore file used for JWT tokens validation. | | String | *camel.management.jwtKeystoreType* | Type of the keystore used for JWT tokens validation (jks, pkcs12, etc.). | | 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 9329f75841441..0f8891a94766c 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 @@ -73,6 +73,10 @@ public class HttpManagementServerConfigurationProperties implements BootstrapClo private String jwtKeystorePath; @Metadata(label = "security", security = "secret") private String jwtKeystorePassword; + @Metadata(label = "security") + private String jwtIssuer; + @Metadata(label = "security") + private String jwtAudience; public HttpManagementServerConfigurationProperties(MainConfigurationProperties parent) { this.parent = parent; @@ -362,6 +366,29 @@ public void setJwtKeystorePassword(String jwtKeystorePassword) { this.jwtKeystorePassword = jwtKeystorePassword; } + public String getJwtIssuer() { + return jwtIssuer; + } + + /** + * Expected JWT issuer (iss claim) for token validation. When set, tokens whose issuer does not match are rejected. + */ + public void setJwtIssuer(String jwtIssuer) { + this.jwtIssuer = jwtIssuer; + } + + public String getJwtAudience() { + return 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. + */ + public void setJwtAudience(String jwtAudience) { + this.jwtAudience = jwtAudience; + } + /** * Whether embedded HTTP management server is enabled. By default, the server is not enabled. */ @@ -542,6 +569,23 @@ public HttpManagementServerConfigurationProperties withJwtKeystorePassword(Strin return this; } + /** + * Expected JWT issuer (iss claim) for token validation. When set, tokens whose issuer does not match are rejected. + */ + public HttpManagementServerConfigurationProperties withJwtIssuer(String jwtIssuer) { + this.jwtIssuer = jwtIssuer; + return this; + } + + /** + * 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. + */ + public HttpManagementServerConfigurationProperties withJwtAudience(String jwtAudience) { + this.jwtAudience = jwtAudience; + 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 fc025a584c288..d51cf5e9a36ab 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 @@ -64,6 +64,10 @@ public class HttpServerConfigurationProperties implements BootstrapCloseable { private String jwtKeystorePath; @Metadata(label = "security", security = "secret") private String jwtKeystorePassword; + @Metadata(label = "security") + private String jwtIssuer; + @Metadata(label = "security") + private String jwtAudience; public HttpServerConfigurationProperties(MainConfigurationProperties parent) { this.parent = parent; @@ -283,6 +287,29 @@ public void setJwtKeystorePassword(String jwtKeystorePassword) { this.jwtKeystorePassword = jwtKeystorePassword; } + public String getJwtIssuer() { + return jwtIssuer; + } + + /** + * Expected JWT issuer (iss claim) for token validation. When set, tokens whose issuer does not match are rejected. + */ + public void setJwtIssuer(String jwtIssuer) { + this.jwtIssuer = jwtIssuer; + } + + public String getJwtAudience() { + return 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. + */ + public void setJwtAudience(String jwtAudience) { + this.jwtAudience = jwtAudience; + } + /** * Whether embedded HTTP server is enabled. By default, the server is not enabled. */ @@ -434,4 +461,21 @@ public HttpServerConfigurationProperties withJwtKeystorePassword(String jwtKeyst return this; } + /** + * Expected JWT issuer (iss claim) for token validation. When set, tokens whose issuer does not match are rejected. + */ + public HttpServerConfigurationProperties withJwtIssuer(String jwtIssuer) { + this.jwtIssuer = jwtIssuer; + return this; + } + + /** + * 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. + */ + public HttpServerConfigurationProperties withJwtAudience(String jwtAudience) { + this.jwtAudience = jwtAudience; + return this; + } + } diff --git a/docs/user-manual/modules/ROOT/pages/camel-4x-upgrade-guide-4_21.adoc b/docs/user-manual/modules/ROOT/pages/camel-4x-upgrade-guide-4_21.adoc index 7327faf481881..5637226a451ec 100644 --- a/docs/user-manual/modules/ROOT/pages/camel-4x-upgrade-guide-4_21.adoc +++ b/docs/user-manual/modules/ROOT/pages/camel-4x-upgrade-guide-4_21.adoc @@ -622,6 +622,18 @@ The page titles are likewise disambiguated (`JSON Jackson 2` / `JSON Jackson 3`, variants now appear in the dataformats navigation. Update any external `xref:` links that point at the old filenames; routes referencing the dataformats by `name` are unaffected. +=== camel-platform-http-main + +Two new optional configuration properties are available on both `camel.server.*` and `camel.management.*` +for the embedded HTTP server: + +* `jwtIssuer` — when set, validates the `iss` (issuer) claim of incoming JWT tokens. +* `jwtAudience` — comma-separated list of accepted audience values; when set, validates the `aud` (audience) + claim. A token is accepted if its `aud` claim matches any configured value. + +Both default to unset. When both are unset, JWT validation behaviour is unchanged (signature plus the +default `exp` / `nbf` checks). + === Deprecation of camel-ironmq The component camel-ironmq is deprecated. The official library used has been unmaintained since 2017