Developing Applications with Red Hat build of Apache Camel for Quarkus
Developing Applications with Red Hat build of Apache Camel for Quarkus
Abstract
Preface
Providing feedback on Red Hat build of Apache Camel documentation
To report an error or to improve our documentation, log in to your Red Hat Jira account and submit an issue. If you do not have a Red Hat Jira account, then you will be prompted to create an account.
Procedure
- Click the following link to create ticket
- Enter a brief description of the issue in the Summary.
- Provide a detailed description of the issue or enhancement in the Description. Include a URL to where the issue occurs in the documentation.
- Clicking Submit creates and routes the issue to the appropriate documentation team.
Chapter 1. Introduction to developing applications with Red Hat build of Apache Camel for Quarkus
This guide is for developers writing Camel applications on top of Red Hat build of Apache Camel for Quarkus.
Camel components which are supported in Red Hat build of Apache Camel for Quarkus have an associated Red Hat build of Apache Camel for Quarkus extension. For more information about the Red Hat build of Apache Camel for Quarkus extensions supported in this distribution, see the Red Hat build of Apache Camel for Quarkus Extensions reference guide.
Chapter 2. Dependency management
A specific Red Hat build of Apache Camel for Quarkus release is supposed to work only with a specific Quarkus release.
2.1. Quarkus tooling for starting a new project
The easiest and most straightforward way to get the dependency versions right in a new project is to use one of the Quarkus tools:
- code.quarkus.redhat.com - an online project generator,
- Quarkus Maven plugin
These tools allow you to select extensions and scaffold a new Maven project.
The universe of available extensions spans over Quarkus Core, Camel Quarkus and several other third party participating projects, such as Hazelcast, Cassandra, Kogito and OptaPlanner.
The generated pom.xml
will look similar to the following:
<project> ... <properties> <quarkus.platform.artifact-id>quarkus-bom</quarkus.platform.artifact-id> <quarkus.platform.group-id>com.redhat.quarkus.platform</quarkus.platform.group-id> <quarkus.platform.version> <!-- The latest 3.20.x version from https://maven.repository.redhat.com/ga/com/redhat/quarkus/platform/quarkus-bom --> </quarkus.platform.version> ... </properties> <dependencyManagement> <dependencies> <!-- The BOMs managing the dependency versions --> <dependency> <groupId>${quarkus.platform.group-id}</groupId> <artifactId>quarkus-bom</artifactId> <version>${quarkus.platform.version}</version> <type>pom</type> <scope>import</scope> </dependency> <dependency> <groupId>${quarkus.platform.group-id}</groupId> <artifactId>quarkus-camel-bom</artifactId> <version>${quarkus.platform.version}</version> <type>pom</type> <scope>import</scope> </dependency> </dependencies> </dependencyManagement> <dependencies> <!-- The extensions you chose in the project generator tool --> <dependency> <groupId>org.apache.camel.quarkus</groupId> <artifactId>camel-quarkus-sql</artifactId> <!-- No explicit version required here and below --> </dependency> ... </dependencies> ... </project>
<project>
...
<properties>
<quarkus.platform.artifact-id>quarkus-bom</quarkus.platform.artifact-id>
<quarkus.platform.group-id>com.redhat.quarkus.platform</quarkus.platform.group-id>
<quarkus.platform.version>
<!-- The latest 3.20.x version from https://maven.repository.redhat.com/ga/com/redhat/quarkus/platform/quarkus-bom -->
</quarkus.platform.version>
...
</properties>
<dependencyManagement>
<dependencies>
<!-- The BOMs managing the dependency versions -->
<dependency>
<groupId>${quarkus.platform.group-id}</groupId>
<artifactId>quarkus-bom</artifactId>
<version>${quarkus.platform.version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
<dependency>
<groupId>${quarkus.platform.group-id}</groupId>
<artifactId>quarkus-camel-bom</artifactId>
<version>${quarkus.platform.version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
<dependencies>
<!-- The extensions you chose in the project generator tool -->
<dependency>
<groupId>org.apache.camel.quarkus</groupId>
<artifactId>camel-quarkus-sql</artifactId>
<!-- No explicit version required here and below -->
</dependency>
...
</dependencies>
...
</project>
BOM stands for "Bill of Materials" - it is a pom.xml
whose main purpose is to manage the versions of artifacts so that end users importing the BOM in their projects do not need to care which particular versions of the artifacts are supposed to work together. In other words, having a BOM imported in the <depependencyManagement>
section of your pom.xml
allows you to avoid specifying versions for the dependencies managed by the given BOM.
Which particular BOMs end up in the pom.xml
file depends on extensions you have selected in the generator tool. The generator tools take care to select a minimal consistent set.
If you choose to add an extension at a later point that is not managed by any of the BOMs in your pom.xml
file, you do not need to search for the appropriate BOM manually.
With the quarkus-maven-plugin
you can select the extension, and the tool adds the appropriate BOM as required. You can also use the quarkus-maven-plugin
to upgrade the BOM versions.
The com.redhat.quarkus.platform
BOMs are aligned with each other which means that if an artifact is managed in more than one BOM, it is always managed with the same version. This has the advantage that application developers do not need to care for the compatibility of the individual artifacts that may come from various independent projects.
2.2. Combining with other BOMs
When combining camel-quarkus-bom
with any other BOM, think carefully in which order you import them, because the order of imports defines the precedence.
I.e. if my-foo-bom
is imported before camel-quarkus-bom
then the versions defined in my-foo-bom
will take the precedence. This might or might not be what you want, depending on whether there are any overlaps between my-foo-bom
and camel-quarkus-bom
and depending on whether those versions with higher precedence work with the rest of the artifacts managed in camel-quarkus-bom
.
Chapter 3. Defining Camel routes
In Red Hat build of Apache Camel for Quarkus, you can define Camel routes using the following languages:
3.1. Java DSL
Extending org.apache.camel.builder.RouteBuilder
and using the fluent builder methods available there is the most common way of defining Camel Routes. Here is a simple example of a route using the timer component:
import org.apache.camel.builder.RouteBuilder; public class TimerRoute extends RouteBuilder { @Override public void configure() throws Exception { from("timer:foo?period=1000") .log("Hello World"); } }
import org.apache.camel.builder.RouteBuilder;
public class TimerRoute extends RouteBuilder {
@Override
public void configure() throws Exception {
from("timer:foo?period=1000")
.log("Hello World");
}
}
3.2. Endpoint DSL
Since Camel 3.0, you can use fluent builders also for defining Camel endpoints. The following is equivalent with the previous example:
import org.apache.camel.builder.RouteBuilder; import static org.apache.camel.builder.endpoint.StaticEndpointBuilders.timer; public class TimerRoute extends RouteBuilder { @Override public void configure() throws Exception { from(timer("foo").period(1000)) .log("Hello World"); } }
import org.apache.camel.builder.RouteBuilder;
import static org.apache.camel.builder.endpoint.StaticEndpointBuilders.timer;
public class TimerRoute extends RouteBuilder {
@Override
public void configure() throws Exception {
from(timer("foo").period(1000))
.log("Hello World");
}
}
Builder methods for all Camel components are available via camel-quarkus-core
, but you still need to add the given component’s extension as a dependency for the route to work properly. In case of the above example, it would be camel-quarkus-timer
.
3.3. XML IO DSL
In order to configure Camel routes, rests or templates in XML, you must add a Camel XML parser dependency to the classpath. Since Camel Quarkus 1.8.0, link:https://docs.redhat.com/en/documentation/developing_applications_with_red_hat_build_of_apache_camel_for_quarkus/4.10/html-single/red_hat_build_of_apache_camel_for_quarkus_reference/ #extensions-xml-io-dsl[camel-quarkus-xml-io-dsl]
is the best choice.
With Camel Main, you can set a property that points to the location of resources XML files such as routes, REST DSL and Route templates:
camel.main.routes-include-pattern = routes/routes.xml, file:src/main/routes/rests.xml, file:src/main/rests/route-template.xml
camel.main.routes-include-pattern = routes/routes.xml, file:src/main/routes/rests.xml, file:src/main/rests/route-template.xml
Path globbing like camel.main.routes-include-pattern = *./routes.xml
currently does not work in native mode.
Route
<routes xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns="http://camel.apache.org/schema/spring" xsi:schemaLocation=" http://camel.apache.org/schema/spring http://camel.apache.org/schema/spring/camel-spring.xsd"> <route id="xml-route"> <from uri="timer:from-xml?period=1000"/> <log message="Hello XML!"/> </route> </routes>
<routes xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns="http://camel.apache.org/schema/spring"
xsi:schemaLocation="
http://camel.apache.org/schema/spring
http://camel.apache.org/schema/spring/camel-spring.xsd">
<route id="xml-route">
<from uri="timer:from-xml?period=1000"/>
<log message="Hello XML!"/>
</route>
</routes>
When using XML routes with beans, it is sometime needed to refer to class name, for instance beanType=org.apache.SomeClass
. In such cases, it might be needed to register the class for reflection in native mode. Refer to the Native mode section for more information.
Spring XML with <beans>
or Blueprint XML with <blueprint>
elements are not supported.
The route XML should be in the simplified version like:
Rest DSL
<rests xmlns="http://camel.apache.org/schema/spring"> <rest id="greeting" path="/greeting"> <get path="/hello"> <to uri="direct:greet"/> </get> </rest> </rests>
<rests xmlns="http://camel.apache.org/schema/spring">
<rest id="greeting" path="/greeting">
<get path="/hello">
<to uri="direct:greet"/>
</get>
</rest>
</rests>
Route Templates
<routeTemplates xmlns="http://camel.apache.org/schema/spring"> <routeTemplate id="myTemplate"> <templateParameter name="name"/> <templateParameter name="greeting"/> <templateParameter name="myPeriod" defaultValue="3s"/> <route> <from uri="timer:{{name}}?period={{myPeriod}}"/> <setBody><simple>{{greeting}} ${body}</simple></setBody> <log message="${body}"/> </route> </routeTemplate> </routeTemplates>
<routeTemplates xmlns="http://camel.apache.org/schema/spring">
<routeTemplate id="myTemplate">
<templateParameter name="name"/>
<templateParameter name="greeting"/>
<templateParameter name="myPeriod" defaultValue="3s"/>
<route>
<from uri="timer:{{name}}?period={{myPeriod}}"/>
<setBody><simple>{{greeting}} ${body}</simple></setBody>
<log message="${body}"/>
</route>
</routeTemplate>
</routeTemplates>
3.4. YAML DSL
To configure routes with YAML, you must add the camel-quarkus-yaml-dsl
dependency to the classpath.
With Camel Main, you can set a property that points to the location of YAML files containing routes, REST DSL and Route templates definitions:
camel.main.routes-include-pattern = routes/routes.yaml, routes/rests.yaml, rests/route-template.yaml
camel.main.routes-include-pattern = routes/routes.yaml, routes/rests.yaml, rests/route-template.yaml
Route
- route: id: "my-yaml-route" from: uri: "timer:from-yaml?period=1000" steps: - set-body: constant: "Hello YAML!" - to: "log:from-yaml"
- route:
id: "my-yaml-route"
from:
uri: "timer:from-yaml?period=1000"
steps:
- set-body:
constant: "Hello YAML!"
- to: "log:from-yaml"
Rest DSL
- rest: get: - path: "/greeting" to: "direct:greet" - route: id: "rest-route" from: uri: "direct:greet" steps: - set-body: constant: "Hello YAML!"
- rest:
get:
- path: "/greeting"
to: "direct:greet"
- route:
id: "rest-route"
from:
uri: "direct:greet"
steps:
- set-body:
constant: "Hello YAML!"
Route Templates
- route-template: id: "myTemplate" parameters: - name: "name" - name: "greeting" defaultValue: "Hello" - name: "myPeriod" defaultValue: "3s" from: uri: "timer:{{name}}?period={{myPeriod}}" steps: - set-body: expression: simple: "{{greeting}} ${body}" - log: "${body}" - templated-route: route-template-ref: "myTemplate" parameters: - name: "name" value: "tick" - name: "greeting" value: "Bonjour" - name: "myPeriod" value: "5s"
- route-template:
id: "myTemplate"
parameters:
- name: "name"
- name: "greeting"
defaultValue: "Hello"
- name: "myPeriod"
defaultValue: "3s"
from:
uri: "timer:{{name}}?period={{myPeriod}}"
steps:
- set-body:
expression:
simple: "{{greeting}} ${body}"
- log: "${body}"
- templated-route:
route-template-ref: "myTemplate"
parameters:
- name: "name"
value: "tick"
- name: "greeting"
value: "Bonjour"
- name: "myPeriod"
value: "5s"
Chapter 4. Testing routes in Camel Quarkus
4.1. Testing Camel Quarkus Extensions
Testing offers a good way to ensure Camel routes behave as expected over time. If you haven’t already, read the Camel Quarkus user guide First Steps and the Quarkus documentation Testing your application section.
When it comes to testing a route in the context of Quarkus, the recommended approach is to write local integration tests. This has the advantage of covering both JVM and native mode.
In JVM mode, you can use the CamelTestSupport
style of testing.
4.1.1. Running in JVM mode
In JVM mode, use the @QuarkusTest
annotation to bootstrap Quarkus and start Camel routes before the @Test
logic executes.
For example:
import io.quarkus.test.junit.QuarkusTest; import org.junit.jupiter.api.Test; @QuarkusTest class MyTest { @Test public void test() { // Use any suitable code that sends test data to the route and then assert outcomes ... } }
import io.quarkus.test.junit.QuarkusTest;
import org.junit.jupiter.api.Test;
@QuarkusTest
class MyTest {
@Test
public void test() {
// Use any suitable code that sends test data to the route and then assert outcomes
...
}
}
You can find a sample implementation in the Camel Quarkus source:
4.1.2. Running in native mode
Always test that your application works in native mode for all supported extensions.
You can reuse the test logic defined for JVM mode by inheriting the logic from the respective JVM mode class.
Add the @QuarkusIntegrationTest
annotation to tell the Quarkus JUnit extension to compile the application under test to native image and start it before running the tests.
import io.quarkus.test.junit.QuarkusIntegrationTest; @QuarkusIntegrationTest class MyIT extends MyTest { ... }
import io.quarkus.test.junit.QuarkusIntegrationTest;
@QuarkusIntegrationTest
class MyIT extends MyTest {
...
}
You can find a sample implementation in the Camel Quarkus source:
4.1.3. Differences between @QuarkusTest
and @QuarkusIntegrationTest
A native executable does not need a JVM to run, and cannot run in a JVM, because it is native code, not bytecode.
There is no point in compiling tests to native code so they run using a traditional JVM.
This means that communication between tests and the application must go over the network (HTTP/REST, or any other protocol your application speaks), through watching filesystems (log files for example), or any other interprocess communication.
4.1.3.1. @QuarkusTest
in JVM mode
In JVM mode, tests annotated with @QuarkusTest
execute in the same JVM as the application under test.
This means you can use @Inject
to add beans from the application into the test code.
You can also define new beans or even override the beans from the application using @jakarta.enterprise.inject.Alternative
and @jakarta.annotation.Priority
.
4.1.3.2. @QuarkusIntegrationTest
in native mode
In native mode, tests annotated with @QuarkusIntegrationTest
execute in a JVM hosted in a process separate from the running native application.
An important consequence of this, is that all communication between the tests and the native application, must take one or more of the following forms:
- Network calls. Typically, HTTP or any other network protocol your application supports.
-
Watching the filesystem for changes. (For example via Camel
file
endpoints.) - Any other kind of interprocess communication.
QuarkusIntegrationTest
provides additional features that are not available through @QuarkusTest
:
- In JVM mode, you can launch and test the runnable application JAR produced by the Quarkus build.
- In native mode, you can launch and test the native application produced by the Quarkus build.
- If you add a container image to the build, a container starts, and tests execute against it.
For more information about QuarkusIntegrationTest
, see the Quarkus testing guide.
4.1.4. Testing with external services
4.1.4.1. Testcontainers
Sometimes your application needs to access some external resource, such as a messaging broker, a database, or other service.
If a container image is available for the service of interest, you can use Testcontainers to start and configure the services during testing.
4.1.4.1.1. Passing configuration data with QuarkusTestResourceLifecycleManager
For the application to work properly, it is often essential to pass the connection configuration data (host, port, user, password of the remote service) to the application before it starts.
In the Quarkus ecosystem, QuarkusTestResourceLifecycleManager
serves this purpose.
You can start one or more Testcontainers in the start()
method and return the connection configuration from the method in the form of a Map
.
The entries of this map are then passed to the application in different ways depending on the mode:
-
Native mode: a command line (
-Dkey=value
) - JVM Mode: a special MicroProfile configuration provider
Command line and MicroProfile settings have a higher precedence than the settings in the application.properties
file.
import java.util.Map; import java.util.HashMap; import io.quarkus.test.common.QuarkusTestResourceLifecycleManager; import org.testcontainers.containers.GenericContainer; import org.testcontainers.containers.wait.strategy.Wait; public class MyTestResource implements QuarkusTestResourceLifecycleManager { private GenericContainer<?> myContainer; @Override public Map<String, String> start() { // Start the needed container(s) myContainer = new GenericContainer(DockerImageName.parse("my/image:1.0.0")) .withExposedPorts(1234) .waitingFor(Wait.forListeningPort()); myContainer.start(); // Pass the configuration to the application under test // You can also pass camel component property names / values to automatically configure Camel components return new HashMap<>() {{ put("my-container.host", container.getHost()); put("my-container.port", "" + container.getMappedPort(1234)); }}; } @Override public void stop() { // Stop the needed container(s) myContainer.stop(); ... } }
import java.util.Map;
import java.util.HashMap;
import io.quarkus.test.common.QuarkusTestResourceLifecycleManager;
import org.testcontainers.containers.GenericContainer;
import org.testcontainers.containers.wait.strategy.Wait;
public class MyTestResource implements QuarkusTestResourceLifecycleManager {
private GenericContainer<?> myContainer;
@Override
public Map<String, String> start() {
// Start the needed container(s)
myContainer = new GenericContainer(DockerImageName.parse("my/image:1.0.0"))
.withExposedPorts(1234)
.waitingFor(Wait.forListeningPort());
myContainer.start();
// Pass the configuration to the application under test
// You can also pass camel component property names / values to automatically configure Camel components
return new HashMap<>() {{
put("my-container.host", container.getHost());
put("my-container.port", "" + container.getMappedPort(1234));
}};
}
@Override
public void stop() {
// Stop the needed container(s)
myContainer.stop();
...
}
}
Reference the defined test resource from the test classes with @QuarkusTestResource
:
import io.quarkus.test.common.QuarkusTestResource; import io.quarkus.test.junit.QuarkusTest; @QuarkusTest @QuarkusTestResource(MyTestResource.class) class MyTest { ... }
import io.quarkus.test.common.QuarkusTestResource;
import io.quarkus.test.junit.QuarkusTest;
@QuarkusTest
@QuarkusTestResource(MyTestResource.class)
class MyTest {
...
}
You can find a sample implementation in the Camel Quarkus source:
4.1.4.2. WireMock
Instead of having the tests connect to live endpoints, for example, if they are unavailable, unreliable, or expensive, you can stub HTTP interactions with third-party services & APIs.
You can use WireMock for mocking & recording HTTP interactions. It is used extensively throughout the Camel Quarkus test suite for various component extensions.
4.1.4.2.1. Setting up WireMock
Procedure
Set up the WireMock server.
NoteAlways configure the Camel component under test to pass any HTTP interactions through the WireMock proxy. You can achieve this by configuring a component property that determines the API endpoint URL.
import static com.github.tomakehurst.wiremock.client.WireMock.aResponse; import static com.github.tomakehurst.wiremock.client.WireMock.get; import static com.github.tomakehurst.wiremock.client.WireMock.urlEqualTo; import static com.github.tomakehurst.wiremock.core.WireMockConfiguration.wireMockConfig; import java.util.HashMap; import java.util.Map; import com.github.tomakehurst.wiremock.WireMockServer; import io.quarkus.test.common.QuarkusTestResourceLifecycleManager; public class WireMockTestResource implements QuarkusTestResourceLifecycleManager { private WireMockServer server; @Override public Map<String, String> start() { // Setup & start the server server = new WireMockServer( wireMockConfig().dynamicPort() ); server.start(); // Stub an HTTP endpoint. WireMock also supports a record and playback mode // https://wiremock.org/docs/record-playback/ server.stubFor( get(urlEqualTo("/api/greeting")) .willReturn(aResponse() .withHeader("Content-Type", "application/json") .withBody("{\"message\": \"Hello World\"}"))); // Ensure the camel component API client passes requests through the WireMock proxy Map<String, String> conf = new HashMap<>(); conf.put("camel.component.foo.server-url", server.baseUrl()); return conf; } @Override public void stop() { if (server != null) { server.stop(); } } }
import static com.github.tomakehurst.wiremock.client.WireMock.aResponse; import static com.github.tomakehurst.wiremock.client.WireMock.get; import static com.github.tomakehurst.wiremock.client.WireMock.urlEqualTo; import static com.github.tomakehurst.wiremock.core.WireMockConfiguration.wireMockConfig; import java.util.HashMap; import java.util.Map; import com.github.tomakehurst.wiremock.WireMockServer; import io.quarkus.test.common.QuarkusTestResourceLifecycleManager; public class WireMockTestResource implements QuarkusTestResourceLifecycleManager { private WireMockServer server; @Override public Map<String, String> start() { // Setup & start the server server = new WireMockServer( wireMockConfig().dynamicPort() ); server.start(); // Stub an HTTP endpoint. WireMock also supports a record and playback mode // https://wiremock.org/docs/record-playback/ server.stubFor( get(urlEqualTo("/api/greeting")) .willReturn(aResponse() .withHeader("Content-Type", "application/json") .withBody("{\"message\": \"Hello World\"}"))); // Ensure the camel component API client passes requests through the WireMock proxy Map<String, String> conf = new HashMap<>(); conf.put("camel.component.foo.server-url", server.baseUrl()); return conf; } @Override public void stop() { if (server != null) { server.stop(); } } }
Copy to Clipboard Copied! -
Ensure your test class has the
@QuarkusTestResource
annotation with the appropriate test resource class specified as the value. The WireMock server will be started before all tests are executed and will be shut down when all tests are finished.
import io.quarkus.test.common.QuarkusTestResource; import io.quarkus.test.junit.QuarkusTest; @QuarkusTest @QuarkusTestResource(WireMockTestResource.class) class MyTest { ... }
import io.quarkus.test.common.QuarkusTestResource;
import io.quarkus.test.junit.QuarkusTest;
@QuarkusTest
@QuarkusTestResource(WireMockTestResource.class)
class MyTest {
...
}
The WireMock server starts before all tests execute and shuts down when all tests finish.
You can find a sample implementation in the Camel Quarkus integration test source tree:
4.1.5. CamelTestSupport
style of testing with CamelQuarkusTestSupport
Since Camel Quarkus 2.13.0, you can use CamelQuarkusTestSupport
for testing. It is a replacement for CamelTestSupport
, which does not work well with Quarkus.
CamelQuarkusTestSupport
only works in JVM mode. If you need to test in native mode, then use one of the alternate test strategies described above.
4.1.5.1. Testing with CamelQuarkusTestSupport
in JVM mode
Add the following dependency into your module (preferably in the test
scope):
<dependency> <groupId>org.apache.camel.quarkus</groupId> <artifactId>camel-quarkus-junit5</artifactId> <scope>test</scope> </dependency>
<dependency>
<groupId>org.apache.camel.quarkus</groupId>
<artifactId>camel-quarkus-junit5</artifactId>
<scope>test</scope>
</dependency>
You can use CamelQuarkusTestSupport
in your test like this:
@QuarkusTest @TestProfile(SimpleTest.class) //necessary only if "newly created" context is required for the test (worse performance) public class SimpleTest extends CamelQuarkusTestSupport { ... }
@QuarkusTest
@TestProfile(SimpleTest.class) //necessary only if "newly created" context is required for the test (worse performance)
public class SimpleTest extends CamelQuarkusTestSupport {
...
}
4.1.5.2. Customizing the CamelContext
for testing
You can customize the CamelContext
for testing with configuration profiles, CDI beans, observers, mocks etc. You can also override the createCamelContext
method and interact directly with the CamelContext
.
When using createCamelContext
you MUST NOT instantiate and return a new CamelContext
. Instead, invoke super.createCamelContext()
and modify the returned CamelContext
as needed. Failing to follow this rule will result in an exception being thrown.
@QuarkusTest class SimpleTest extends CamelQuarkusTestSupport { @Override protected CamelContext createCamelContext() throws Exception { // Must call super to get a handle on the application scoped CamelContext CamelContext context = super.createCamelContext(); // Apply customizations context.setTracing(true); // Return the modified CamelContext return context; } }
@QuarkusTest
class SimpleTest extends CamelQuarkusTestSupport {
@Override
protected CamelContext createCamelContext() throws Exception {
// Must call super to get a handle on the application scoped CamelContext
CamelContext context = super.createCamelContext();
// Apply customizations
context.setTracing(true);
// Return the modified CamelContext
return context;
}
}
4.1.5.3. Configuring routes for testing
Any classes that extend RouteBuilder
in your application will have their routes automatically added to the CamelContext
. Similarly, any XML or YAML routes configured from camel.main.routes-include-pattern
will also be loaded.
This may not always be desirable for your tests. You control which routes get loaded at test time with configuration properties:
-
quarkus.camel.routes-discovery.include-patterns
-
quarkus.camel.routes-discovery.exclude-patterns
, -
camel.main.routes-include-pattern
-
camel.main.routes-exclude-pattern
.
You can also define test specific routes per test class by overriding createRouteBuilder
:
@QuarkusTest class SimpleTest extends CamelQuarkusTestSupport { @Test void testGreeting() { MockEndpoint mockEndpoint = getMockEndpoint("mock:result"); mockEndpoint.expectedBodiesReceived("Hello World"); template.sendBody("direct:start", "World"); mockEndpoint.assertIsSatisified(); } @Override protected RoutesBuilder createRouteBuilder() throws Exception { return new RouteBuilder() { @Override public void configure() throws Exception { from("direct:start") .transform().simple("Hello ${body}") .to("mock:result"); } }; } }
@QuarkusTest
class SimpleTest extends CamelQuarkusTestSupport {
@Test
void testGreeting() {
MockEndpoint mockEndpoint = getMockEndpoint("mock:result");
mockEndpoint.expectedBodiesReceived("Hello World");
template.sendBody("direct:start", "World");
mockEndpoint.assertIsSatisified();
}
@Override
protected RoutesBuilder createRouteBuilder() throws Exception {
return new RouteBuilder() {
@Override
public void configure() throws Exception {
from("direct:start")
.transform().simple("Hello ${body}")
.to("mock:result");
}
};
}
}
4.1.5.4. CamelContext test lifecycle
One of the main differences in CamelQuarkusTestSupport
compared to CamelTestSupport
is how the CamelContext
lifecycle is managed.
On Camel Quarkus, a single CamelContext
is created for you automatically by the runtime. By default, this CamelContext
is shared among all tests and remains started for the duration of the entire test suite execution.
This can potentially have some unintended side effects for your tests. If you need to have the CamelContext
restarted between tests, then you can create a custom test profile, which will force the application under test to be restarted.
For example, to define a test profile:
@QuarkusTest class MyTestProfile implements QuarkusTestProfile { ... }
@QuarkusTest
class MyTestProfile implements QuarkusTestProfile {
...
}
Then reference it on the test class with @TestProfile
:
// @TestProfile will trigger the application to be restarted @TestProfile(MyTestProfile.class) @QuarkusTest class SimpleTest extends CamelQuarkusTestSupport { ... }
// @TestProfile will trigger the application to be restarted
@TestProfile(MyTestProfile.class)
@QuarkusTest
class SimpleTest extends CamelQuarkusTestSupport {
...
}
You cannot manually restart the CamelContext
by invoking its stop()
and start()
methods. This will result in an exception.
4.1.5.5. Examples
4.1.5.5.1. Simple RouteBuilder
and test class
Simple RouteBuilder
:
public class MyRoutes extends RouteBuilder { @Override public void configure() { from("direct:start") .transform().simple("Hello ${body}") .to("mock:result"); } }
public class MyRoutes extends RouteBuilder {
@Override
public void configure() {
from("direct:start")
.transform().simple("Hello ${body}")
.to("mock:result");
}
}
Test sending a message payload to the direct:start
endpoint:
@QuarkusTest class SimpleTest extends CamelQuarkusTestSupport { @Test void testGreeting() { MockEndpoint mockEndpoint = getMockEndpoint("mock:result"); mockEndpoint.expectedBodiesReceived("Hello World"); template.sendBody("direct:start", "World"); mockEndpoint.assertIsSatisified(); } }
@QuarkusTest
class SimpleTest extends CamelQuarkusTestSupport {
@Test
void testGreeting() {
MockEndpoint mockEndpoint = getMockEndpoint("mock:result");
mockEndpoint.expectedBodiesReceived("Hello World");
template.sendBody("direct:start", "World");
mockEndpoint.assertIsSatisified();
}
}
4.1.5.5.2. Using AdviceWith
@QuarkusTest class SimpleTest extends CamelQuarkusTestSupport { @BeforeEach public void beforeEach() throws Exception { AdviceWith.adviceWith(this.context, "advisedRoute", route -> { route.replaceFromWith("direct:replaced"); }); } @Override protected RoutesBuilder createRouteBuilder() throws Exception { return new RouteBuilder() { @Override public void configure() throws Exception { from("direct:start").routeId("advisedRoute") .transform().simple("Hello ${body}") .to("mock:result"); } }; } @Test void testAdvisedRoute() throws Exception { MockEndpoint mockEndpoint = getMockEndpoint("mock:result"); mockEndpoint.expectedBodiesReceived("Hello World"); template.sendBody("direct:replaced", "World"); mockEndpoint.assertIsSatisfied(); } }
@QuarkusTest
class SimpleTest extends CamelQuarkusTestSupport {
@BeforeEach
public void beforeEach() throws Exception {
AdviceWith.adviceWith(this.context, "advisedRoute", route -> {
route.replaceFromWith("direct:replaced");
});
}
@Override
protected RoutesBuilder createRouteBuilder() throws Exception {
return new RouteBuilder() {
@Override
public void configure() throws Exception {
from("direct:start").routeId("advisedRoute")
.transform().simple("Hello ${body}")
.to("mock:result");
}
};
}
@Test
void testAdvisedRoute() throws Exception {
MockEndpoint mockEndpoint = getMockEndpoint("mock:result");
mockEndpoint.expectedBodiesReceived("Hello World");
template.sendBody("direct:replaced", "World");
mockEndpoint.assertIsSatisfied();
}
}
4.1.5.5.3. Explicitly enabling advice
When explicitly enabling advice you must invoke startRouteDefinitions
when completing your AdviceWith
setup.
Invoking startRouteDefinitions
is only required if you have routes configured that are NOT being advised.
4.1.5.6. Limitations
4.1.5.6.1. Test lifecycle methods inherited from CamelTestSupport
CamelQuarkusTestSupport
inherits some test lifecycle methods from CamelTestSupport
. However, they should not be used and instead are replaced with equivalent methods in CamelQuarkusTestSupport
.
CamelTestSupport lifecycle methods | CamelQuarkusTestSupport equivalent |
---|---|
|
|
|
|
|
|
|
|
4.1.5.6.2. Creating a custom Camel registry is not supported
The CamelQuarkusTestSupport
implementation of createCamelRegistry
will throw UnsupportedOperationException
.
If you need to bind or unbind objects to the Camel registry, then you can do it by one of the following methods.
Produce named CDI beans
public class MyBeanProducers { @Produces @Named("myBean") public MyBean createMyBean() { return new MyBean(); } }
public class MyBeanProducers { @Produces @Named("myBean") public MyBean createMyBean() { return new MyBean(); } }
Copy to Clipboard Copied! -
Override
createCamelContext
(see example above) and invokecamelContext.getRegistry().bind("foo", fooBean)
Use the
@BindToRegistry
annotation@QuarkusTest class SimpleTest extends CamelQuarkusTestSupport { @BindToRegistry("myBean") MyBean myBean = new MyBean(); }
@QuarkusTest class SimpleTest extends CamelQuarkusTestSupport { @BindToRegistry("myBean") MyBean myBean = new MyBean(); }
Copy to Clipboard Copied! NoteBeans bound to the Camel registry from individual test classes, will persist for the duration of the test suite execution. This could have unintended consequences, depending on your test expectations. You can use test profiles to restart the
CamelContext
to avoid this.
Chapter 5. Configuration
Camel Quarkus automatically configures and deploys a Camel Context bean which by default is started/stopped according to the Quarkus Application lifecycle. The configuration step happens at build time during Quarkus' augmentation phase, and it is driven by the Camel Quarkus extensions which can be tuned using Camel Quarkus specific quarkus.camel.*
properties.
quarkus.camel.*
configuration properties are documented on the individual extension pages - for example see Camel Quarkus Core.
After the configuration is done, a minimal Camel Runtime is assembled and started in the RUNTIME_INIT phase.
5.1. Configuring Camel components
5.1.1. application.properties
To configure components and other aspects of Apache Camel through properties, make sure that your application depends on camel-quarkus-core
directly or transitively. Because most Camel Quarkus extensions depend on camel-quarkus-core
, you typically do not need to add it explicitly.
camel-quarkus-core
brings functionalities from Camel Main to Camel Quarkus.
In the example below, you set a specific ExchangeFormatter
configuration on the LogComponent
via application.properties
:
camel.component.log.exchange-formatter = #class:org.apache.camel.support.processor.DefaultExchangeFormatter camel.component.log.exchange-formatter.show-exchange-pattern = false camel.component.log.exchange-formatter.show-body-type = false
camel.component.log.exchange-formatter = #class:org.apache.camel.support.processor.DefaultExchangeFormatter
camel.component.log.exchange-formatter.show-exchange-pattern = false
camel.component.log.exchange-formatter.show-body-type = false
5.1.2. CDI
You can also configure a component programmatically using CDI.
The recommended method is to observe the ComponentAddEvent
and configure the component before the routes and the CamelContext
are started:
import jakarta.enterprise.context.ApplicationScoped; import jakarta.enterprise.event.Observes; import org.apache.camel.quarkus.core.events.ComponentAddEvent; import org.apache.camel.component.log.LogComponent; import org.apache.camel.support.processor.DefaultExchangeFormatter; @ApplicationScoped public static class EventHandler { public void onComponentAdd(@Observes ComponentAddEvent event) { if (event.getComponent() instanceof LogComponent) { /* Perform some custom configuration of the component */ LogComponent logComponent = ((LogComponent) event.getComponent()); DefaultExchangeFormatter formatter = new DefaultExchangeFormatter(); formatter.setShowExchangePattern(false); formatter.setShowBodyType(false); logComponent.setExchangeFormatter(formatter); } } }
import jakarta.enterprise.context.ApplicationScoped;
import jakarta.enterprise.event.Observes;
import org.apache.camel.quarkus.core.events.ComponentAddEvent;
import org.apache.camel.component.log.LogComponent;
import org.apache.camel.support.processor.DefaultExchangeFormatter;
@ApplicationScoped
public static class EventHandler {
public void onComponentAdd(@Observes ComponentAddEvent event) {
if (event.getComponent() instanceof LogComponent) {
/* Perform some custom configuration of the component */
LogComponent logComponent = ((LogComponent) event.getComponent());
DefaultExchangeFormatter formatter = new DefaultExchangeFormatter();
formatter.setShowExchangePattern(false);
formatter.setShowBodyType(false);
logComponent.setExchangeFormatter(formatter);
}
}
}
5.1.2.1. Producing a @Named
component instance
Alternatively, you can create and configure the component yourself in a @Named
producer method. This works as Camel uses the component URI scheme to look-up components from its registry. For example, in the case of a LogComponent
Camel looks for a log
named bean.
While producing a @Named
component bean will usually work, it may cause subtle issues with some components.
Camel Quarkus extensions may do one or more of the following:
- Pass custom subtype of the default Camel component type. See the Vert.x WebSocket extension example.
- Perform some Quarkus specific customization of the component. See the JPA extension example.
These actions are not performed when you produce your own component instance, therefore, configuring components in an observer method is the recommended method.
import jakarta.enterprise.context.ApplicationScoped; import jakarta.inject.Named; import org.apache.camel.component.log.LogComponent; import org.apache.camel.support.processor.DefaultExchangeFormatter; @ApplicationScoped public class Configurations { /** * Produces a {@link LogComponent} instance with a custom exchange formatter set-up. */ @Named("log") LogComponent log() { DefaultExchangeFormatter formatter = new DefaultExchangeFormatter(); formatter.setShowExchangePattern(false); formatter.setShowBodyType(false); LogComponent component = new LogComponent(); component.setExchangeFormatter(formatter); return component; } }
import jakarta.enterprise.context.ApplicationScoped;
import jakarta.inject.Named;
import org.apache.camel.component.log.LogComponent;
import org.apache.camel.support.processor.DefaultExchangeFormatter;
@ApplicationScoped
public class Configurations {
/**
* Produces a {@link LogComponent} instance with a custom exchange formatter set-up.
*/
@Named("log")
LogComponent log() {
DefaultExchangeFormatter formatter = new DefaultExchangeFormatter();
formatter.setShowExchangePattern(false);
formatter.setShowBodyType(false);
LogComponent component = new LogComponent();
component.setExchangeFormatter(formatter);
return component;
}
}
- 1
- The
"log"
argument of the@Named
annotation can be omitted if the name of the method is the same.
5.2. Configuration by convention
In addition to support configuring Camel through properties, camel-quarkus-core
allows you to use conventions to configure the Camel behavior. For example, if there is a single ExchangeFormatter
instance in the CDI container, then it will automatically wire that bean to the LogComponent
.
Chapter 6. Contexts and Dependency Injection (CDI) in Camel Quarkus
CDI plays a central role in Quarkus and Camel Quarkus offers a first class support for it too.
You may use @Inject
, @ConfigProperty
and similar annotations e.g. to inject beans and configuration values to your Camel RouteBuilder
, for example:
import jakarta.enterprise.context.ApplicationScoped; import jakarta.inject.Inject; import org.apache.camel.builder.RouteBuilder; import org.eclipse.microprofile.config.inject.ConfigProperty; @ApplicationScoped public class TimerRoute extends RouteBuilder { @ConfigProperty(name = "timer.period", defaultValue = "1000") String period; @Inject Counter counter; @Override public void configure() throws Exception { fromF("timer:foo?period=%s", period) .setBody(exchange -> "Incremented the counter: " + counter.increment()) .to("log:cdi-example?showExchangePattern=false&showBodyType=false"); } }
import jakarta.enterprise.context.ApplicationScoped;
import jakarta.inject.Inject;
import org.apache.camel.builder.RouteBuilder;
import org.eclipse.microprofile.config.inject.ConfigProperty;
@ApplicationScoped
public class TimerRoute extends RouteBuilder {
@ConfigProperty(name = "timer.period", defaultValue = "1000")
String period;
@Inject
Counter counter;
@Override
public void configure() throws Exception {
fromF("timer:foo?period=%s", period)
.setBody(exchange -> "Incremented the counter: " + counter.increment())
.to("log:cdi-example?showExchangePattern=false&showBodyType=false");
}
}
- 1
- The
@ApplicationScoped
annotation is required for@Inject
and@ConfigProperty
to work in aRouteBuilder
. Note that the@ApplicationScoped
beans are managed by the CDI container and their life cycle is thus a bit more complex than the one of the plainRouteBuilder
. In other words, using@ApplicationScoped
inRouteBuilder
comes with some boot time penalty and you should therefore only annotate yourRouteBuilder
with@ApplicationScoped
when you really need it. - 2
- The value for the
timer.period
property is defined insrc/main/resources/application.properties
of the example project.
Refer to the Quarkus Dependency Injection guide for more details.
6.1. Accessing CamelContext
To access CamelContext
just inject it into your bean:
import jakarta.inject.Inject; import jakarta.enterprise.context.ApplicationScoped; import java.util.stream.Collectors; import java.util.List; import org.apache.camel.CamelContext; @ApplicationScoped public class MyBean { @Inject CamelContext context; public List<String> listRouteIds() { return context.getRoutes().stream().map(Route::getId).sorted().collect(Collectors.toList()); } }
import jakarta.inject.Inject;
import jakarta.enterprise.context.ApplicationScoped;
import java.util.stream.Collectors;
import java.util.List;
import org.apache.camel.CamelContext;
@ApplicationScoped
public class MyBean {
@Inject
CamelContext context;
public List<String> listRouteIds() {
return context.getRoutes().stream().map(Route::getId).sorted().collect(Collectors.toList());
}
}
6.2. @EndpointInject
and @Produce
If you are used to @org.apache.camel.EndpointInject
and @org.apache.camel.Produce
from plain Camel or from Camel on SpringBoot, you can continue using them on Quarkus too.
The following use cases are supported by org.apache.camel.quarkus:camel-quarkus-core
:
import jakarta.enterprise.context.ApplicationScoped; import org.apache.camel.EndpointInject; import org.apache.camel.FluentProducerTemplate; import org.apache.camel.Produce; import org.apache.camel.ProducerTemplate; @ApplicationScoped class MyBean { @EndpointInject("direct:myDirect1") ProducerTemplate producerTemplate; @EndpointInject("direct:myDirect2") FluentProducerTemplate fluentProducerTemplate; @EndpointInject("direct:myDirect3") DirectEndpoint directEndpoint; @Produce("direct:myDirect4") ProducerTemplate produceProducer; @Produce("direct:myDirect5") FluentProducerTemplate produceProducerFluent; }
import jakarta.enterprise.context.ApplicationScoped;
import org.apache.camel.EndpointInject;
import org.apache.camel.FluentProducerTemplate;
import org.apache.camel.Produce;
import org.apache.camel.ProducerTemplate;
@ApplicationScoped
class MyBean {
@EndpointInject("direct:myDirect1")
ProducerTemplate producerTemplate;
@EndpointInject("direct:myDirect2")
FluentProducerTemplate fluentProducerTemplate;
@EndpointInject("direct:myDirect3")
DirectEndpoint directEndpoint;
@Produce("direct:myDirect4")
ProducerTemplate produceProducer;
@Produce("direct:myDirect5")
FluentProducerTemplate produceProducerFluent;
}
You can use any other Camel producer endpoint URI instead of direct:myDirect*
.
@EndpointInject
and @Produce
are not supported on setter methods - see #2579
The following use case is supported by org.apache.camel.quarkus:camel-quarkus-bean
:
import jakarta.enterprise.context.ApplicationScoped; import org.apache.camel.Produce; @ApplicationScoped class MyProduceBean { public interface ProduceInterface { String sayHello(String name); } @Produce("direct:myDirect6") ProduceInterface produceInterface; void doSomething() { produceInterface.sayHello("Kermit") } }
import jakarta.enterprise.context.ApplicationScoped;
import org.apache.camel.Produce;
@ApplicationScoped
class MyProduceBean {
public interface ProduceInterface {
String sayHello(String name);
}
@Produce("direct:myDirect6")
ProduceInterface produceInterface;
void doSomething() {
produceInterface.sayHello("Kermit")
}
}
6.3. CDI and the Camel Bean component
6.3.1. Refer to a bean by name
To refer to a bean in a route definition by name, just annotate the bean with @Named("myNamedBean")
and @ApplicationScoped
(or some other supported scope). The @RegisterForReflection
annotation is important for the native mode.
import jakarta.enterprise.context.ApplicationScoped; import jakarta.inject.Named; import io.quarkus.runtime.annotations.RegisterForReflection; @ApplicationScoped @Named("myNamedBean") @RegisterForReflection public class NamedBean { public String hello(String name) { return "Hello " + name + " from the NamedBean"; } }
import jakarta.enterprise.context.ApplicationScoped;
import jakarta.inject.Named;
import io.quarkus.runtime.annotations.RegisterForReflection;
@ApplicationScoped
@Named("myNamedBean")
@RegisterForReflection
public class NamedBean {
public String hello(String name) {
return "Hello " + name + " from the NamedBean";
}
}
Then you can use the myNamedBean
name in a route definition:
import org.apache.camel.builder.RouteBuilder; public class CamelRoute extends RouteBuilder { @Override public void configure() { from("direct:named") .bean("myNamedBean", "hello"); /* ... which is an equivalent of the following: */ from("direct:named") .to("bean:myNamedBean?method=hello"); } }
import org.apache.camel.builder.RouteBuilder;
public class CamelRoute extends RouteBuilder {
@Override
public void configure() {
from("direct:named")
.bean("myNamedBean", "hello");
/* ... which is an equivalent of the following: */
from("direct:named")
.to("bean:myNamedBean?method=hello");
}
}
As an alternative to @Named
, you may also use io.smallrye.common.annotation.Identifier
to name and identify a bean.
import jakarta.enterprise.context.ApplicationScoped; import io.quarkus.runtime.annotations.RegisterForReflection; import io.smallrye.common.annotation.Identifier; @ApplicationScoped @Identifier("myBeanIdentifier") @RegisterForReflection public class MyBean { public String hello(String name) { return "Hello " + name + " from MyBean"; } }
import jakarta.enterprise.context.ApplicationScoped;
import io.quarkus.runtime.annotations.RegisterForReflection;
import io.smallrye.common.annotation.Identifier;
@ApplicationScoped
@Identifier("myBeanIdentifier")
@RegisterForReflection
public class MyBean {
public String hello(String name) {
return "Hello " + name + " from MyBean";
}
}
Then refer to the identifier value within the Camel route:
import org.apache.camel.builder.RouteBuilder; public class CamelRoute extends RouteBuilder { @Override public void configure() { from("direct:start") .bean("myBeanIdentifier", "Camel"); } }
import org.apache.camel.builder.RouteBuilder;
public class CamelRoute extends RouteBuilder {
@Override
public void configure() {
from("direct:start")
.bean("myBeanIdentifier", "Camel");
}
}
We aim at supporting all use cases listed in Bean binding section of Camel documentation. Do not hesitate to file an issue if some bean binding scenario does not work for you.
6.3.2. @Consume
Since Camel Quarkus 2.0.0, the camel-quarkus-bean
artifact brings support for @org.apache.camel.Consume
- see the Pojo consuming section of Camel documentation.
Declaring a class like the following
import org.apache.camel.Consume; public class Foo { @Consume("activemq:cheese") public void onCheese(String name) { ... } }
import org.apache.camel.Consume;
public class Foo {
@Consume("activemq:cheese")
public void onCheese(String name) {
...
}
}
will automatically create the following Camel route
from("activemq:cheese").bean("foo1234", "onCheese")
from("activemq:cheese").bean("foo1234", "onCheese")
for you. Note that Camel Quarkus will implicitly add @jakarta.inject.Singleton
and jakarta.inject.Named("foo1234")
to the bean class, where 1234
is a hash code obtained from the fully qualified class name. If your bean has some CDI scope (such as @ApplicationScoped
) or @Named("someName")
set already, those will be honored in the auto-created route.
Chapter 7. Observability
7.1. Health & liveness checks
Health & liveness checks are supported via the MicroProfile Health extension. They can be configured via the Camel Health API or via Quarkus MicroProfile Health.
All configured checks are available on the standard MicroProfile Health endpoint URLs:
7.1.1. Health endpoint
Camel provides some out of the box liveness and readiness checks. To see this working, interrogate the /q/health/live
and /q/health/ready
endpoints on port 9000
:
curl -s localhost:9000/q/health/live
$ curl -s localhost:9000/q/health/live
curl -s localhost:9000/q/health/ready
$ curl -s localhost:9000/q/health/ready
The JSON output will contain a checks for verifying whether the CamelContext
and each individual route is in the 'Started' state.
This example project contains a custom liveness check class CustomLivenessCheck
and custom readiness check class CustomReadinessCheck
which leverage the Camel health API. You’ll see these listed in the health JSON as 'custom-liveness-check' and 'custom-readiness-check'. On every 5th invocation of these checks, the health status of custom-liveness-check
will be reported as DOWN.
You can also directly leverage MicroProfile Health APIs to create checks. Class CamelUptimeHealthCheck
demonstrates how to register a readiness check.
7.2. Metrics
We provide MicroProfile Metrics for exposing metrics.
Some basic Camel metrics are provided for you out of the box, and these can be supplemented by configuring additional metrics in your routes.
Metrics are available on the standard Quarkus metrics endpoint:
7.3. Monitoring a Camel application
With monitoring of your applications, you can collect information about how your application behaves, such as metrics, health checks and distributed tracing.
This section uses the Observability
example listed in the Red Hat build of Quarkus examples, adding observability with micrometer
.
Check the Camel Quarkus User guide for prerequisites and other general information.
7.3.1. Creating a project
- Start in the Development mode
Run the maven
compile
command:mvn clean compile quarkus:dev
$ mvn clean compile quarkus:dev
Copy to Clipboard Copied! This compiles the project, starts the application and lets the Quarkus tooling watch for changes in your workspace.
Any modifications in your project automatically take effect in the running application.
TipRefer to the Development mode section of Camel Quarkus User guide for more details.
7.3.2. Enabling metrics
To enable observability features in Camel Quarkus, you must add additional dependencies to the project’s pom.xml file. The most important ones are camel-quarkus-opentelemetry
and quarkus-micrometer-registry-prometheus
.
Add the dependencies to your project
pom.xml
:<dependencies> ... <dependency> <groupId>org.apache.camel.quarkus</groupId> <artifactId>camel-quarkus-opentelemetry</artifactId> </dependency> <dependency> <groupId>io.quarkiverse.micrometer.registry</groupId> <artifactId>quarkus-micrometer-registry-prometheus</artifactId> </dependency> ... </dependencies>
<dependencies> ... <dependency> <groupId>org.apache.camel.quarkus</groupId> <artifactId>camel-quarkus-opentelemetry</artifactId> </dependency> <dependency> <groupId>io.quarkiverse.micrometer.registry</groupId> <artifactId>quarkus-micrometer-registry-prometheus</artifactId> </dependency> ... </dependencies>
Copy to Clipboard Copied! With these dependencies you benefit from both Camel Micrometer and Quarkus Micrometer.
7.3.3. Creating meters
You can create meters for custom metrics in multiple ways:
7.3.3.1. Using Camel micrometer component
With this method you use Routes.java.
.to("micrometer:counter:org.acme.observability.greeting-provider?tags=type=events,purpose=example")
.to("micrometer:counter:org.acme.observability.greeting-provider?tags=type=events,purpose=example")
Which will count each call to the platform-http:/greeting-provider
endpoint.
7.3.3.2. Using CDI dependency injection
With this method you use CDI dependency injection of the MeterRegistry
:
@Inject MeterRegistry registry;
@Inject
MeterRegistry registry;
Then using it directly in a Camel Processor
method to publish metrics:
void countGreeting(Exchange exchange) { registry.counter("org.acme.observability.greeting", "type", "events", "purpose", "example").increment(); }
void countGreeting(Exchange exchange) {
registry.counter("org.acme.observability.greeting", "type", "events", "purpose", "example").increment();
}
from("platform-http:/greeting") .removeHeaders("*") .process(this::countGreeting)
from("platform-http:/greeting")
.removeHeaders("*")
.process(this::countGreeting)
This counts each call to the platform-http:/greeting
endpoint.
7.3.3.3. Using Micrometer annotations
With this method you use Micrometer annotations, by defining a bean TimerCounter.java
as follows:
@ApplicationScoped @Named("timerCounter") public class TimerCounter { @Counted(value = "org.acme.observability.timer-counter", extraTags = { "purpose", "example" }) public void count() { } }
@ApplicationScoped
@Named("timerCounter")
public class TimerCounter {
@Counted(value = "org.acme.observability.timer-counter", extraTags = { "purpose", "example" })
public void count() {
}
}
It can then be invoked from Camel via the bean EIP (see TimerRoute.java
):
.bean("timerCounter", "count")
.bean("timerCounter", "count")
It will increment the counter metric each time the Camel timer is fired.
7.3.3.4. Browsing metrics
Metrics are exposed on an HTTP endpoint at /q/metrics
on port 9000
.
Note we are using a different port (9000) for the management endpoint then our application (8080) is listening on. This is configured in application.properties
via quarkus.management.enabled = true
. See the Quarkus management interface guide for more information.
To view all Camel metrics do:
curl -s localhost:9000/q/metrics
$ curl -s localhost:9000/q/metrics
To view only our previously created metrics, use:
curl -s localhost:9000/q/metrics | grep -i 'purpose="example"'
$ curl -s localhost:9000/q/metrics | grep -i 'purpose="example"'
and you should see 3 lines of different metrics (with the same value, as they are all triggered by the timer).
Maybe you’ve noticed the Prometheus output format. If you would rather use the JSON format, please follow the Quarkus Micrometer management interface configuration guide.
7.3.4. Tracing
To be able to diagnose problems in Camel Quarkus applications, you can start tracing messages. We will use OpenTelemetry standard suited for cloud environments.
All you need is to add the dependencies camel-quarkus-opentelemetry
and quarkus-micrometer-registry-prometheus
to your project pom.xml
:
<dependencies> ... <dependency> <groupId>org.apache.camel.quarkus</groupId> <artifactId>camel-quarkus-opentelemetry</artifactId> </dependency> <dependency> <groupId>io.quarkiverse.micrometer.registry</groupId> <artifactId>quarkus-micrometer-registry-prometheus</artifactId> </dependency> ... </dependencies>
<dependencies>
...
<dependency>
<groupId>org.apache.camel.quarkus</groupId>
<artifactId>camel-quarkus-opentelemetry</artifactId>
</dependency>
<dependency>
<groupId>io.quarkiverse.micrometer.registry</groupId>
<artifactId>quarkus-micrometer-registry-prometheus</artifactId>
</dependency>
...
</dependencies>
Then configure the OpenTelemetry exporter in application.properties
:
We are using a property placeholder to be able to test this example in convenient way in a cloud environment
# We are using a property placeholder to be able to test this example in convenient way in a cloud environment
quarkus.otel.exporter.otlp.traces.endpoint = http://${TELEMETRY_COLLECTOR_COLLECTOR_SERVICE_HOST:localhost}:4317
For information about other OpenTelemetry exporters, refer to the Camel Quarkus OpenTelemetry extension documentation.
To view tracing events, start a tracing server. A simple way of doing this is with Docker Compose:
docker-compose up -d
$ docker-compose up -d
With the server running, browse to http://localhost:16686. Then choose 'camel-quarkus-observability' from the 'Service' drop down and click the 'Find Traces' button.
The platform-http
consumer route introduces a random delay to simulate latency, hence the overall time of each trace should be different. When viewing a trace, you should see a hierarchy of 6 spans showing the progression of the message exchange through each endpoint.
7.3.5. Packaging and running the application
Once you are done with developing you can package and run the application.
For more details about the JVM mode and Native mode, see the "Package and run" section of the Camel Quarkus User guide
7.3.5.1. JVM mode
mvn clean package java -jar target/quarkus-app/quarkus-run.jar
$ mvn clean package
$ java -jar target/quarkus-app/quarkus-run.jar
...
[io.quarkus] (main) camel-quarkus-examples-... started in 1.163s. Listening on: http://0.0.0.0:8080
7.3.5.2. Native mode
Native mode requires having GraalVM and other tools installed. Please check the Prerequisites section of Camel Quarkus User guide.
To prepare a native executable using GraalVM, run the following command:
mvn clean package -Pnative ./target/*-runner
$ mvn clean package -Pnative
$ ./target/*-runner
...
[io.quarkus] (main) camel-quarkus-examples-... started in 0.013s. Listening on: http://0.0.0.0:8080
...
Chapter 8. Native mode
For additional information about compiling and testing application in native mode, see Producing a native executable in the Compiling your Quarkus applications to native executables guide.
8.1. Character encodings
By default, not all Charsets
are available in native mode.
Charset.defaultCharset(), US-ASCII, ISO-8859-1, UTF-8, UTF-16BE, UTF-16LE, UTF-16
Charset.defaultCharset(), US-ASCII, ISO-8859-1, UTF-8, UTF-16BE, UTF-16LE, UTF-16
If you expect your application to need any encoding not included in this set or if you see an UnsupportedCharsetException
thrown in the native mode, please add the following entry to your application.properties
:
quarkus.native.add-all-charsets = true
quarkus.native.add-all-charsets = true
See also quarkus.native.add-all-charsets in Quarkus documentation.
8.2. Locale
By default, only the building JVM default locale is included in the native image. Quarkus provides a way to set the locale via application.properties
, so that you do not need to rely on LANG
and LC_*
environement variables:
quarkus.native.user-country=US quarkus.native.user-language=en
quarkus.native.user-country=US
quarkus.native.user-language=en
There is also support for embedding multiple locales into the native image and for selecting the default locale via Mandrel command line options -H:IncludeLocales=fr,en
, H:+IncludeAllLocales
and -H:DefaultLocale=de
. You can set those via the Quarkus quarkus.native.additional-build-args
property.
8.3. Embedding resources in the native executable
Resources accessed via Class.getResource()
, Class.getResourceAsStream()
, ClassLoader.getResource()
, ClassLoader.getResourceAsStream()
, etc. at runtime need to be explicitly listed for including in the native executable.
This can be done using Quarkus quarkus.native.resources.includes
and quarkus.native.resources.excludes
properties in application.properties
file as demonstrated below:
quarkus.native.resources.includes = docs/*,images/* quarkus.native.resources.excludes = docs/ignored.adoc,images/ignored.png
quarkus.native.resources.includes = docs/*,images/*
quarkus.native.resources.excludes = docs/ignored.adoc,images/ignored.png
In the example above, resources named docs/included.adoc
and images/included.png
would be embedded in the native executable while docs/ignored.adoc
and images/ignored.png
would not.
resources.includes
and resources.excludes
are both lists of comma separated Ant-path style glob patterns.
Refer to Red Hat build of Apache Camel for Quarkus Extensions Reference for more details.
8.4. Using the onException clause in native mode
When using Camel onException
handling in native mode, it is your responsibility to register the exception classes for reflection.
For instance, having a camel context with onException
handling:
onException(MyException.class).handled(true); from("direct:route-that-could-produce-my-exception").throw(MyException.class);
onException(MyException.class).handled(true);
from("direct:route-that-could-produce-my-exception").throw(MyException.class);
The class mypackage.MyException
should be registered for reflection. For more information, see Registering classes for reflection.
8.5. Registering classes for reflection
By default, dynamic reflection is not available in native mode. Classes for which reflective access is needed, have to be registered for reflection at compile time.
In many cases, application developers do not need to care because Quarkus extensions are able to detect the classes that require the reflection and register them automatically.
However, in some situations, Quarkus extensions may miss some classes and it is up to the application developer to register them. There are two ways to do that:
The
@io.quarkus.runtime.annotations.RegisterForReflection
annotation can be used to register classes on which it is used, or it can also register third party classes via itstargets
attribute.import io.quarkus.runtime.annotations.RegisterForReflection; @RegisterForReflection class MyClassAccessedReflectively { } @RegisterForReflection( targets = { org.third-party.Class1.class, org.third-party.Class2.class } ) class ReflectionRegistrations { }
import io.quarkus.runtime.annotations.RegisterForReflection; @RegisterForReflection class MyClassAccessedReflectively { } @RegisterForReflection( targets = { org.third-party.Class1.class, org.third-party.Class2.class } ) class ReflectionRegistrations { }
Copy to Clipboard Copied! The
quarkus.camel.native.reflection
options inapplication.properties
:quarkus.camel.native.reflection.include-patterns = org.apache.commons.lang3.tuple.* quarkus.camel.native.reflection.exclude-patterns = org.apache.commons.lang3.tuple.*Triple
quarkus.camel.native.reflection.include-patterns = org.apache.commons.lang3.tuple.* quarkus.camel.native.reflection.exclude-patterns = org.apache.commons.lang3.tuple.*Triple
Copy to Clipboard Copied! For these options to work properly, the artifacts containing the selected classes must either contain a Jandex index ('META-INF/jandex.idx') or they must be registered for indexing using the 'quarkus.index-dependency.*' options in 'application.properties' - for example:
quarkus.index-dependency.commons-lang3.group-id = org.apache.commons quarkus.index-dependency.commons-lang3.artifact-id = commons-lang3
quarkus.index-dependency.commons-lang3.group-id = org.apache.commons quarkus.index-dependency.commons-lang3.artifact-id = commons-lang3
Copy to Clipboard Copied!
8.6. Registering classes for serialization
If serialization support is requested via quarkus.camel.native.reflection.serialization-enabled
, the classes listed in CamelSerializationProcessor.BASE_SERIALIZATION_CLASSES are automatically registered for serialization.
You can register more classes using @RegisterForReflection(serialization = true)
.
Chapter 9. Kubernetes
This guide describes different ways to configure and deploy a Camel Quarkus application on kubernetes. It also describes some specific use cases for Knative and Service Binding.
9.1. Kubernetes
Quarkus supports generating resources for vanilla Kubernetes, OpenShift and Knative. Furthermore, Quarkus can deploy the application to a target Kubernetes cluster by applying the generated manifests to the target cluster’s API Server. For more information, see the Quarkus Kubernetes guide
.
9.2. Knative
The Camel Quarkus extensions whose consumers support Knative deployment are:
9.3. Service binding
Quarkus also supports the Service Binding Specification for Kubernetes to bind services to applications.
The following Camel Quarkus extensions can be used with Service Binding:
Chapter 10. Quarkus CXF security guide
This chapter provides information about security when working with Quarkus CXF extensions.
10.1. Security guide
The security guide documents various security related aspects of Quarkus CXF:
10.1.1. SSL, TLS and HTTPS
This section documents various use cases related to SSL, TLS and HTTPS.
The sample code snippets used in this section come from the WS-SecurityPolicy integration test in the source tree of Quarkus CXF
10.1.1.1. Client SSL configuration
If your client is going to communicate with a server whose SSL certificate is not trusted by the client’s operating system, then you need to set up a custom trust store for your client.
Tools like openssl
or Java keytool
are commonly used for creating and maintaining truststores.
We have examples for both tools in the Quarkus CXF source tree:
Once you have prepared the trust store, you need to configure your client to use it.
10.1.1.1.1. Set the client trust store in application.properties
This is the easiest way to set the client trust store. The key role is played by the following properties:
Here is an example:
application.properties
Client side SSL
# Client side SSL
quarkus.cxf.client.hello.client-endpoint-url = https://localhost:${quarkus.http.test-ssl-port}/services/hello
quarkus.cxf.client.hello.service-interface = io.quarkiverse.cxf.it.security.policy.HelloService
quarkus.cxf.client.hello.trust-store-type = pkcs12
quarkus.cxf.client.hello.trust-store = client-truststore.pkcs12
quarkus.cxf.client.hello.trust-store-password = client-truststore-password
- 1
pkcs12
andjks
are two commonly used keystore formats. PKCS12 is the default Java keystore format since Java 9. We recommend using PKCS12 rather than JKS, because it offers stronger cryptographic algorithms, it is extensible, standardized, language-neutral and widely supported.- 2
- The referenced
client-truststore.pkcs12
file has to be available either in the classpath or in the file system.
10.1.1.2. Server SSL configuration
To make your services available over the HTTPS protocol, you need to set up server keystore in the first place. The server SSL configuration is driven by Vert.x, the HTTP layer of Quarkus. Quarkus HTTP guide provides the information about the configuration options.
Here is a basic example:
application.properties
Server side SSL
# Server side SSL
quarkus.tls.key-store.p12.path = localhost-keystore.pkcs12
quarkus.tls.key-store.p12.password = localhost-keystore-password
quarkus.tls.key-store.p12.alias = localhost
quarkus.tls.key-store.p12.alias-password = localhost-keystore-password
10.1.1.3. Mutual TLS (mTLS) authentication
So far, we have explained the simple or single-sided case where only the server proves its identity through an SSL certificate and the client has to be set up to trust that certificate. Mutual TLS authentication goes by letting also the client prove its identity using the same means of public key cryptography.
Hence, for the Mutual TLS (mTLS) authentication, in addition to setting up the server keystore and client truststore as described above, you need to set up the keystore on the client side and the truststore on the server side.
The tools for creating and maintaining the stores are the same and the configuration properties to use are pretty much analogous to the ones used in the Simple TLS case.
The mTLS integration test in the Quarkus CXF source tree can serve as a good starting point.
The keystores and truststores are created with openssl
(or alternatively with Java Java keytool
)
Here is the application.properties
file:
application.properties
Server keystore for Simple TLS Server truststore for Mutual TLS Select localhost-pkcs12 as the TLS configuration for the HTTP server Do not allow any clients which do not prove their indentity through an SSL certificate CXF service CXF client with a properly set certificate for mTLS Include the keystores in the native executable
# Server keystore for Simple TLS
quarkus.tls.localhost-pkcs12.key-store.p12.path = localhost-keystore.pkcs12
quarkus.tls.localhost-pkcs12.key-store.p12.password = localhost-keystore-password
quarkus.tls.localhost-pkcs12.key-store.p12.alias = localhost
quarkus.tls.localhost-pkcs12.key-store.p12.alias-password = localhost-keystore-password
# Server truststore for Mutual TLS
quarkus.tls.localhost-pkcs12.trust-store.p12.path = localhost-truststore.pkcs12
quarkus.tls.localhost-pkcs12.trust-store.p12.password = localhost-truststore-password
# Select localhost-pkcs12 as the TLS configuration for the HTTP server
quarkus.http.tls-configuration-name = localhost-pkcs12
# Do not allow any clients which do not prove their indentity through an SSL certificate
quarkus.http.ssl.client-auth = required
# CXF service
quarkus.cxf.endpoint."/mTls".implementor = io.quarkiverse.cxf.it.auth.mtls.MTlsHelloServiceImpl
# CXF client with a properly set certificate for mTLS
quarkus.cxf.client.mTls.client-endpoint-url = https://localhost:${quarkus.http.test-ssl-port}/services/mTls
quarkus.cxf.client.mTls.service-interface = io.quarkiverse.cxf.it.security.policy.HelloService
quarkus.cxf.client.mTls.key-store = target/classes/client-keystore.pkcs12
quarkus.cxf.client.mTls.key-store-type = pkcs12
quarkus.cxf.client.mTls.key-store-password = client-keystore-password
quarkus.cxf.client.mTls.key-password = client-keystore-password
quarkus.cxf.client.mTls.trust-store = target/classes/client-truststore.pkcs12
quarkus.cxf.client.mTls.trust-store-type = pkcs12
quarkus.cxf.client.mTls.trust-store-password = client-truststore-password
# Include the keystores in the native executable
quarkus.native.resources.includes = *.pkcs12,*.jks
10.1.1.4. Enforce SSL through WS-SecurityPolicy
The requirement for the clients to connect through HTTPS can be defined in a policy.
The functionality is provided by quarkus-cxf-rt-ws-security
extension.
Here is an example of a policy file:
https-policy.xml
<?xml version="1.0" encoding="UTF-8"?> <wsp:Policy wsp:Id="HttpsSecurityServicePolicy" xmlns:wsp="http://schemas.xmlsoap.org/ws/2004/09/policy" xmlns:sp="http://docs.oasis-open.org/ws-sx/ws-securitypolicy/200702" xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/"> <wsp:ExactlyOne> <wsp:All> <sp:TransportBinding> <wsp:Policy> <sp:TransportToken> <wsp:Policy> <sp:HttpsToken RequireClientCertificate="false" /> </wsp:Policy> </sp:TransportToken> <sp:IncludeTimestamp /> <sp:AlgorithmSuite> <wsp:Policy> <sp:Basic128 /> </wsp:Policy> </sp:AlgorithmSuite> </wsp:Policy> </sp:TransportBinding> </wsp:All> </wsp:ExactlyOne> </wsp:Policy>
<?xml version="1.0" encoding="UTF-8"?>
<wsp:Policy wsp:Id="HttpsSecurityServicePolicy"
xmlns:wsp="http://schemas.xmlsoap.org/ws/2004/09/policy"
xmlns:sp="http://docs.oasis-open.org/ws-sx/ws-securitypolicy/200702"
xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/">
<wsp:ExactlyOne>
<wsp:All>
<sp:TransportBinding>
<wsp:Policy>
<sp:TransportToken>
<wsp:Policy>
<sp:HttpsToken RequireClientCertificate="false" />
</wsp:Policy>
</sp:TransportToken>
<sp:IncludeTimestamp />
<sp:AlgorithmSuite>
<wsp:Policy>
<sp:Basic128 />
</wsp:Policy>
</sp:AlgorithmSuite>
</wsp:Policy>
</sp:TransportBinding>
</wsp:All>
</wsp:ExactlyOne>
</wsp:Policy>
The policy has to be referenced from a service endpoint interface (SEI):
HttpsPolicyHelloService.java
package io.quarkiverse.cxf.it.security.policy; import jakarta.jws.WebMethod; import jakarta.jws.WebService; import org.apache.cxf.annotations.Policy; /** * A service implementation with a transport policy set */ @WebService(serviceName = "HttpsPolicyHelloService") @Policy(placement = Policy.Placement.BINDING, uri = "https-policy.xml") public interface HttpsPolicyHelloService extends AbstractHelloService { @WebMethod @Override public String hello(String text); }
package io.quarkiverse.cxf.it.security.policy;
import jakarta.jws.WebMethod;
import jakarta.jws.WebService;
import org.apache.cxf.annotations.Policy;
/**
* A service implementation with a transport policy set
*/
@WebService(serviceName = "HttpsPolicyHelloService")
@Policy(placement = Policy.Placement.BINDING, uri = "https-policy.xml")
public interface HttpsPolicyHelloService extends AbstractHelloService {
@WebMethod
@Override
public String hello(String text);
}
With this setup in place, any request delivered over HTTP will be rejected by the PolicyVerificationInInterceptor
:
ERROR [org.apa.cxf.ws.pol.PolicyVerificationInInterceptor] Inbound policy verification failed: These policy alternatives can not be satisfied: {http://docs.oasis-open.org/ws-sx/ws-securitypolicy/200702}TransportBinding: TLS is not enabled ...
ERROR [org.apa.cxf.ws.pol.PolicyVerificationInInterceptor] Inbound policy verification failed: These policy alternatives can not be satisfied:
{http://docs.oasis-open.org/ws-sx/ws-securitypolicy/200702}TransportBinding: TLS is not enabled
...
10.1.2. Authentication and authorization
The sample code snippets shown in this section come from the Client and server integration test in the source tree of Quarkus CXF. You may want to use it as a runnable example.
10.1.2.1. Client HTTP basic authentication
Use the following client configuration options provided by quarkus-cxf
extension to pass the username and password for HTTP basic authentication:
Here is an example:
application.properties
quarkus.cxf.client.basicAuth.wsdl = http://localhost:${quarkus.http.test-port}/soap/basicAuth?wsdl quarkus.cxf.client.basicAuth.client-endpoint-url = http://localhost:${quarkus.http.test-port}/soap/basicAuth quarkus.cxf.client.basicAuth.username = bob quarkus.cxf.client.basicAuth.password = bob234
quarkus.cxf.client.basicAuth.wsdl = http://localhost:${quarkus.http.test-port}/soap/basicAuth?wsdl
quarkus.cxf.client.basicAuth.client-endpoint-url = http://localhost:${quarkus.http.test-port}/soap/basicAuth
quarkus.cxf.client.basicAuth.username = bob
quarkus.cxf.client.basicAuth.password = bob234
10.1.2.1.1. Accessing WSDL protected by basic authentication
By default, the clients created by Quarkus CXF do not send the Authorization
header, unless you set the quarkus.cxf.client."client-name".secure-wsdl-access
to true
:
application.properties
quarkus.cxf.client.basicAuthSecureWsdl.wsdl = http://localhost:${quarkus.http.test-port}/soap/basicAuth?wsdl quarkus.cxf.client.basicAuthSecureWsdl.client-endpoint-url = http://localhost:${quarkus.http.test-port}/soap/basicAuthSecureWsdl quarkus.cxf.client.basicAuthSecureWsdl.username = bob quarkus.cxf.client.basicAuthSecureWsdl.password = ${client-server.bob.password} quarkus.cxf.client.basicAuthSecureWsdl.secure-wsdl-access = true
quarkus.cxf.client.basicAuthSecureWsdl.wsdl = http://localhost:${quarkus.http.test-port}/soap/basicAuth?wsdl
quarkus.cxf.client.basicAuthSecureWsdl.client-endpoint-url = http://localhost:${quarkus.http.test-port}/soap/basicAuthSecureWsdl
quarkus.cxf.client.basicAuthSecureWsdl.username = bob
quarkus.cxf.client.basicAuthSecureWsdl.password = ${client-server.bob.password}
quarkus.cxf.client.basicAuthSecureWsdl.secure-wsdl-access = true
10.1.2.2. Mutual TLS (mTLS) authentication
See the Mutual TLS (mTLS) authentication section in SSL, TLS and HTTPS guide.
10.1.2.3. Securing service endpoints
The server-side authentication and authorization is driven by Quarkus Security, especially when it comes to
There is a basic example in our Client and server integration test. Its key parts are:
-
io.quarkus:quarkus-elytron-security-properties-file
dependency as an Identity provider Basic authentication enabled and users with their roles configured in
application.properties
:application.properties
quarkus.http.auth.basic = true quarkus.security.users.embedded.enabled = true quarkus.security.users.embedded.plain-text = true quarkus.security.users.embedded.users.alice = alice123 quarkus.security.users.embedded.roles.alice = admin quarkus.security.users.embedded.users.bob = bob234 quarkus.security.users.embedded.roles.bob = app-user
quarkus.http.auth.basic = true quarkus.security.users.embedded.enabled = true quarkus.security.users.embedded.plain-text = true quarkus.security.users.embedded.users.alice = alice123 quarkus.security.users.embedded.roles.alice = admin quarkus.security.users.embedded.users.bob = bob234 quarkus.security.users.embedded.roles.bob = app-user
Copy to Clipboard Copied! -
Role-based access control enfoced via
@RolesAllowed
annotation:
BasicAuthHelloServiceImpl.java
package io.quarkiverse.cxf.it.auth.basic; import jakarta.annotation.security.RolesAllowed; import jakarta.jws.WebService; import io.quarkiverse.cxf.it.HelloService; @WebService(serviceName = "HelloService", targetNamespace = HelloService.NS) @RolesAllowed("app-user") public class BasicAuthHelloServiceImpl implements HelloService { @Override public String hello(String person) { return "Hello " + person + "!"; } }
package io.quarkiverse.cxf.it.auth.basic;
import jakarta.annotation.security.RolesAllowed;
import jakarta.jws.WebService;
import io.quarkiverse.cxf.it.HelloService;
@WebService(serviceName = "HelloService", targetNamespace = HelloService.NS)
@RolesAllowed("app-user")
public class BasicAuthHelloServiceImpl implements HelloService {
@Override
public String hello(String person) {
return "Hello " + person + "!";
}
}
10.1.3. Authentication enforced by WS-SecurityPolicy
You can enforce authentication through WS-SecurityPolicy, instead of Mutual TLS and Basic HTTP authentication for clients and services.
To enforce authentication through WS-SecurityPolicy, follow these steps:
- Add a supporting tokens policy to an endpoint in the WSDL contract.
-
On the server side, implement an authentication callback handler and associate it with the endpoint in
application.properties
or via environment variables. Credentials received from clients are authenticated by the callback handler. -
On the client side, provide credentials through either configuration in
application.properties
or environment variables. Alternatively, you can implement an authentication callback handler to pass the credentials.
10.1.3.1. Specifying an Authentication Policy
If you want to enforce authentication on a service endpoint, associate a supporting tokens policy assertion with the relevant endpoint binding and specify one or more token assertions under it.
There are several different kinds of supporting tokens policy assertions, whose XML element names all end with SupportingTokens
(for example, SupportingTokens
, SignedSupportingTokens
, and so on). For a complete list, see the Supporting Tokens section of the WS-SecurityPolicy specification.
10.1.3.2. UsernameToken
policy assertion example
The sample code snippets used in this section come from the WS-SecurityPolicy integration test in the source tree of Quarkus CXF. You may want to use it as a runnable example.
The following listing shows an example of a policy that requires a WS-Security UsernameToken
(which contains username/password credentials) to be included in the security header.
username-token-policy.xml
<?xml version="1.0" encoding="UTF-8"?> <wsp:Policy wsp:Id="UsernameTokenSecurityServicePolicy" xmlns:wsp="http://schemas.xmlsoap.org/ws/2004/09/policy" xmlns:sp="http://docs.oasis-open.org/ws-sx/ws-securitypolicy/200702" xmlns:sp13="http://docs.oasis-open.org/ws-sx/ws-securitypolicy/200802" xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/"> <wsp:ExactlyOne> <wsp:All> <sp:SupportingTokens> <wsp:Policy> <sp:UsernameToken sp:IncludeToken="http://docs.oasis-open.org/ws-sx/ws-securitypolicy/200702/IncludeToken/AlwaysToRecipient"> <wsp:Policy> <sp:WssUsernameToken11 /> <sp13:Created /> <sp13:Nonce /> </wsp:Policy> </sp:UsernameToken> </wsp:Policy> </sp:SupportingTokens> </wsp:All> </wsp:ExactlyOne> </wsp:Policy>
<?xml version="1.0" encoding="UTF-8"?>
<wsp:Policy
wsp:Id="UsernameTokenSecurityServicePolicy"
xmlns:wsp="http://schemas.xmlsoap.org/ws/2004/09/policy"
xmlns:sp="http://docs.oasis-open.org/ws-sx/ws-securitypolicy/200702"
xmlns:sp13="http://docs.oasis-open.org/ws-sx/ws-securitypolicy/200802"
xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/">
<wsp:ExactlyOne>
<wsp:All>
<sp:SupportingTokens>
<wsp:Policy>
<sp:UsernameToken
sp:IncludeToken="http://docs.oasis-open.org/ws-sx/ws-securitypolicy/200702/IncludeToken/AlwaysToRecipient">
<wsp:Policy>
<sp:WssUsernameToken11 />
<sp13:Created />
<sp13:Nonce />
</wsp:Policy>
</sp:UsernameToken>
</wsp:Policy>
</sp:SupportingTokens>
</wsp:All>
</wsp:ExactlyOne>
</wsp:Policy>
There are two ways how you can associate this policy file with a service endpoint:
Reference the policy on the Service Endpoint Interface (SEI) like this:
UsernameTokenPolicyHelloService.java
@WebService(serviceName = "UsernameTokenPolicyHelloService") @Policy(placement = Policy.Placement.BINDING, uri = "username-token-policy.xml") public interface UsernameTokenPolicyHelloService extends AbstractHelloService { ... }
@WebService(serviceName = "UsernameTokenPolicyHelloService") @Policy(placement = Policy.Placement.BINDING, uri = "username-token-policy.xml") public interface UsernameTokenPolicyHelloService extends AbstractHelloService { ... }
Copy to Clipboard Copied! -
Include the policy in your WSDL contract and reference it via
PolicyReference
element.
When you have the policy in place, configure the credentials on the service endpoint and the client:
application.properties
A service with a UsernameToken policy assertion These properties are used in UsernameTokenPasswordCallback and in the configuration of the helloUsernameToken below A client with a UsernameToken policy assertion
# A service with a UsernameToken policy assertion
quarkus.cxf.endpoint."/helloUsernameToken".implementor = io.quarkiverse.cxf.it.security.policy.UsernameTokenPolicyHelloServiceImpl
quarkus.cxf.endpoint."/helloUsernameToken".security.callback-handler = #usernameTokenPasswordCallback
# These properties are used in UsernameTokenPasswordCallback
# and in the configuration of the helloUsernameToken below
wss.user = cxf-user
wss.password = secret
# A client with a UsernameToken policy assertion
quarkus.cxf.client.helloUsernameToken.client-endpoint-url = https://localhost:${quarkus.http.test-ssl-port}/services/helloUsernameToken
quarkus.cxf.client.helloUsernameToken.service-interface = io.quarkiverse.cxf.it.security.policy.UsernameTokenPolicyHelloService
quarkus.cxf.client.helloUsernameToken.security.username = ${wss.user}
quarkus.cxf.client.helloUsernameToken.security.password = ${wss.password}
In the above listing, usernameTokenPasswordCallback
is a name of a @jakarta.inject.Named
bean implementing javax.security.auth.callback.CallbackHandler
. Quarkus CXF will lookup a bean with this name in the CDI container.
Here is an example implementation of the bean:
UsernameTokenPasswordCallback.java
package io.quarkiverse.cxf.it.security.policy; import java.io.IOException; import javax.security.auth.callback.Callback; import javax.security.auth.callback.CallbackHandler; import javax.security.auth.callback.UnsupportedCallbackException; import jakarta.enterprise.context.ApplicationScoped; import jakarta.inject.Named; import org.apache.wss4j.common.ext.WSPasswordCallback; import org.eclipse.microprofile.config.inject.ConfigProperty; @ApplicationScoped @Named("usernameTokenPasswordCallback") /* We refer to this bean by this name from application.properties */ public class UsernameTokenPasswordCallback implements CallbackHandler { /* These two configuration properties are set in application.properties */ @ConfigProperty(name = "wss.password") String password; @ConfigProperty(name = "wss.user") String user; @Override public void handle(Callback[] callbacks) throws IOException, UnsupportedCallbackException { if (callbacks.length < 1) { throw new IllegalStateException("Expected a " + WSPasswordCallback.class.getName() + " at possition 0 of callbacks. Got array of length " + callbacks.length); } if (!(callbacks[0] instanceof WSPasswordCallback)) { throw new IllegalStateException( "Expected a " + WSPasswordCallback.class.getName() + " at possition 0 of callbacks. Got an instance of " + callbacks[0].getClass().getName() + " at possition 0"); } final WSPasswordCallback pc = (WSPasswordCallback) callbacks[0]; if (user.equals(pc.getIdentifier())) { pc.setPassword(password); } else { throw new IllegalStateException("Unexpected user " + user); } } }
package io.quarkiverse.cxf.it.security.policy;
import java.io.IOException;
import javax.security.auth.callback.Callback;
import javax.security.auth.callback.CallbackHandler;
import javax.security.auth.callback.UnsupportedCallbackException;
import jakarta.enterprise.context.ApplicationScoped;
import jakarta.inject.Named;
import org.apache.wss4j.common.ext.WSPasswordCallback;
import org.eclipse.microprofile.config.inject.ConfigProperty;
@ApplicationScoped
@Named("usernameTokenPasswordCallback") /* We refer to this bean by this name from application.properties */
public class UsernameTokenPasswordCallback implements CallbackHandler {
/* These two configuration properties are set in application.properties */
@ConfigProperty(name = "wss.password")
String password;
@ConfigProperty(name = "wss.user")
String user;
@Override
public void handle(Callback[] callbacks) throws IOException, UnsupportedCallbackException {
if (callbacks.length < 1) {
throw new IllegalStateException("Expected a " + WSPasswordCallback.class.getName()
+ " at possition 0 of callbacks. Got array of length " + callbacks.length);
}
if (!(callbacks[0] instanceof WSPasswordCallback)) {
throw new IllegalStateException(
"Expected a " + WSPasswordCallback.class.getName() + " at possition 0 of callbacks. Got an instance of "
+ callbacks[0].getClass().getName() + " at possition 0");
}
final WSPasswordCallback pc = (WSPasswordCallback) callbacks[0];
if (user.equals(pc.getIdentifier())) {
pc.setPassword(password);
} else {
throw new IllegalStateException("Unexpected user " + user);
}
}
}
To test the whole setup, you can create a simple @QuarkusTest
:
UsernameTokenTest.java
package io.quarkiverse.cxf.it.security.policy; import org.assertj.core.api.Assertions; import org.junit.jupiter.api.Test; import io.quarkiverse.cxf.annotation.CXFClient; import io.quarkus.test.junit.QuarkusTest; @QuarkusTest public class UsernameTokenTest { @CXFClient("helloUsernameToken") UsernameTokenPolicyHelloService helloUsernameToken; @Test void helloUsernameToken() { Assertions.assertThat(helloUsernameToken.hello("CXF")).isEqualTo("Hello CXF from UsernameToken!"); } }
package io.quarkiverse.cxf.it.security.policy;
import org.assertj.core.api.Assertions;
import org.junit.jupiter.api.Test;
import io.quarkiverse.cxf.annotation.CXFClient;
import io.quarkus.test.junit.QuarkusTest;
@QuarkusTest
public class UsernameTokenTest {
@CXFClient("helloUsernameToken")
UsernameTokenPolicyHelloService helloUsernameToken;
@Test
void helloUsernameToken() {
Assertions.assertThat(helloUsernameToken.hello("CXF")).isEqualTo("Hello CXF from UsernameToken!");
}
}
When running the test via mvn test -Dtest=UsernameTokenTest
, you should see a SOAP message being logged with a Security
header containing Username
and Password
:
Log output of the UsernameTokenTest
<soap:Envelope xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/"> <soap:Header> <wsse:Security xmlns:wsse="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-secext-1.0.xsd" soap:mustUnderstand="1"> <wsse:UsernameToken xmlns:wsu="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-utility-1.0.xsd" wsu:Id="UsernameToken-bac4f255-147e-42a4-aeec-e0a3f5cd3587"> <wsse:Username>cxf-user</wsse:Username> <wsse:Password Type="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-username-token-profile-1.0#PasswordText">secret</wsse:Password> <wsse:Nonce EncodingType="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-soap-message-security-1.0#Base64Binary">3uX15dZT08jRWFWxyWmfhg==</wsse:Nonce> <wsu:Created>2024-10-02T17:32:10.497Z</wsu:Created> </wsse:UsernameToken> </wsse:Security> </soap:Header> <soap:Body> <ns2:hello xmlns:ns2="http://policy.security.it.cxf.quarkiverse.io/"> <arg0>CXF</arg0> </ns2:hello> </soap:Body> </soap:Envelope>
<soap:Envelope xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/">
<soap:Header>
<wsse:Security xmlns:wsse="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-secext-1.0.xsd" soap:mustUnderstand="1">
<wsse:UsernameToken xmlns:wsu="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-utility-1.0.xsd" wsu:Id="UsernameToken-bac4f255-147e-42a4-aeec-e0a3f5cd3587">
<wsse:Username>cxf-user</wsse:Username>
<wsse:Password Type="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-username-token-profile-1.0#PasswordText">secret</wsse:Password>
<wsse:Nonce EncodingType="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-soap-message-security-1.0#Base64Binary">3uX15dZT08jRWFWxyWmfhg==</wsse:Nonce>
<wsu:Created>2024-10-02T17:32:10.497Z</wsu:Created>
</wsse:UsernameToken>
</wsse:Security>
</soap:Header>
<soap:Body>
<ns2:hello xmlns:ns2="http://policy.security.it.cxf.quarkiverse.io/">
<arg0>CXF</arg0>
</ns2:hello>
</soap:Body>
</soap:Envelope>
10.1.3.3. SAML v1 and v2 policy assertion examples
The WS-SecurityPolicy integration test contains also analogous examples with SAML v1 and SAML v2 assertions.
Chapter 11. Camel Security
This chapter provides information about Camel route security options.
11.1. Camel security overview
Camel offers several forms & levels of security capabilities that can be utilized on Camel routes. These various forms of security may be used in conjunction with each other or separately.
The broad categories offered are:
- Route Security - Authentication and Authorization services to proceed on a route or route segment
- Payload Security - Data Formats that offer encryption/decryption services at the payload level
- Endpoint Security - Security offered by components that can be utilized by endpointUri associated with the component
- Configuration Security - Security offered by encrypting sensitive information from configuration files or external Secured Vault systems.
Camel offers the JSSE Utility for configuring SSL/TLS related aspects of a number of Camel components.
11.2. Route Security
Authentication and Authorization Services
Camel offers Route Policy driven security capabilities that may be wired into routes or route segments. A route policy in Camel utilizes a strategy pattern for applying interceptors on Camel Processors. It’s offering the ability to apply cross-cutting concerns (for example. security, transactions etc) of a Camel route.
11.3. Payload Security
Camel offers encryption/decryption services to secure payloads or selectively apply encryption/decryption capabilities on portions/sections of a payload.
The dataformats offering encryption/decryption of payloads utilizing Marshal are:
11.4. Endpoint Security
Some components in Camel offer an ability to secure their endpoints (using interceptors etc) and therefore ensure that they offer the ability to secure payloads as well as provide authentication/authorization capabilities at endpoints created using the components.
11.5. Configuration Security
Camel offers the Properties component to externalize configuration values to properties files. Those values could contain sensitive information such as usernames and passwords.
Those values can be encrypted and automatic decrypted by Camel using:
Camel also support accessing the secured configuration from an external vault systems.
11.5.1. Configuration Security using Vaults
The following Vaults are supported by Camel:
11.5.1.1. Using AWS Vault
To use AWS Secrets Manager you need to provide accessKey, secretKey and the region. This can be done using environmental variables before starting the application:
export $CAMEL_VAULT_AWS_ACCESS_KEY=accessKey export $CAMEL_VAULT_AWS_SECRET_KEY=secretKey export $CAMEL_VAULT_AWS_REGION=region
export $CAMEL_VAULT_AWS_ACCESS_KEY=accessKey
export $CAMEL_VAULT_AWS_SECRET_KEY=secretKey
export $CAMEL_VAULT_AWS_REGION=region
You can also configure the credentials in the application.properties
file such as:
camel.vault.aws.accessKey = accessKey camel.vault.aws.secretKey = secretKey camel.vault.aws.region = region
camel.vault.aws.accessKey = accessKey
camel.vault.aws.secretKey = secretKey
camel.vault.aws.region = region
If you want instead to use the AWS default credentials provider, you’ll need to provide the following env variables:
export $CAMEL_VAULT_AWS_USE_DEFAULT_CREDENTIALS_PROVIDER=true export $CAMEL_VAULT_AWS_REGION=region
export $CAMEL_VAULT_AWS_USE_DEFAULT_CREDENTIALS_PROVIDER=true
export $CAMEL_VAULT_AWS_REGION=region
You can also configure the credentials in the application.properties
file such as:
camel.vault.aws.defaultCredentialsProvider = true camel.vault.aws.region = region
camel.vault.aws.defaultCredentialsProvider = true
camel.vault.aws.region = region
It is also possible to specify a particular profile name for accessing AWS Secrets Manager
export $CAMEL_VAULT_AWS_USE_PROFILE_CREDENTIALS_PROVIDER=true export $CAMEL_VAULT_AWS_PROFILE_NAME=test-account export $CAMEL_VAULT_AWS_REGION=region
export $CAMEL_VAULT_AWS_USE_PROFILE_CREDENTIALS_PROVIDER=true
export $CAMEL_VAULT_AWS_PROFILE_NAME=test-account
export $CAMEL_VAULT_AWS_REGION=region
You can also configure the credentials in the application.properties
file such as:
camel.vault.aws.profileCredentialsProvider = true camel.vault.aws.profileName = test-account camel.vault.aws.region = region
camel.vault.aws.profileCredentialsProvider = true
camel.vault.aws.profileName = test-account
camel.vault.aws.region = region
At this point you’ll be able to reference a property in the following way by using aws:
as prefix in the {{ }}
syntax:
<camelContext> <route> <from uri="direct:start"/> <to uri="{{aws:route}}"/> </route> </camelContext>
<camelContext>
<route>
<from uri="direct:start"/>
<to uri="{{aws:route}}"/>
</route>
</camelContext>
Where route
will be the name of the secret stored in the AWS Secrets Manager Service.
You could specify a default value in case the secret is not present on AWS Secret Manager:
<camelContext> <route> <from uri="direct:start"/> <to uri="{{aws:route:default}}"/> </route> </camelContext>
<camelContext>
<route>
<from uri="direct:start"/>
<to uri="{{aws:route:default}}"/>
</route>
</camelContext>
In this case if the secret doesn’t exist, the property will fallback to "default" as value.
Also, you are able to get particular field of the secret, if you have for example a secret named database of this form:
{ "username": "admin", "password": "password123", "engine": "postgres", "host": "127.0.0.1", "port": "3128", "dbname": "db" }
{
"username": "admin",
"password": "password123",
"engine": "postgres",
"host": "127.0.0.1",
"port": "3128",
"dbname": "db"
}
You’re able to do get single secret value in your route, like for example:
<camelContext> <route> <from uri="direct:start"/> <log message="Username is {{aws:database/username}}"/> </route> </camelContext>
<camelContext>
<route>
<from uri="direct:start"/>
<log message="Username is {{aws:database/username}}"/>
</route>
</camelContext>
Or re-use the property as part of an endpoint.
You could specify a default value in case the particular field of secret is not present on AWS Secret Manager:
<camelContext> <route> <from uri="direct:start"/> <log message="Username is {{aws:database/username:admin}}"/> </route> </camelContext>
<camelContext>
<route>
<from uri="direct:start"/>
<log message="Username is {{aws:database/username:admin}}"/>
</route>
</camelContext>
In this case if the secret doesn’t exist or the secret exists, but the username field is not part of the secret, the property will fallback to "admin" as value.
For the moment we are not considering the rotation function, if any will be applied, but it is in the work to be done.
The only requirement is adding camel-aws-secrets-manager
JAR to your Camel application.
11.5.1.2. Using Google Secret Manager GCP Vault
To use GCP Secret Manager you need to provide serviceAccountKey file and GCP projectId. This can be done using environmental variables before starting the application:
export $CAMEL_VAULT_GCP_SERVICE_ACCOUNT_KEY=file:////path/to/service.accountkey export $CAMEL_VAULT_GCP_PROJECT_ID=projectId
export $CAMEL_VAULT_GCP_SERVICE_ACCOUNT_KEY=file:////path/to/service.accountkey
export $CAMEL_VAULT_GCP_PROJECT_ID=projectId
You can also configure the credentials in the application.properties
file such as:
camel.vault.gcp.serviceAccountKey = accessKey camel.vault.gcp.projectId = secretKey
camel.vault.gcp.serviceAccountKey = accessKey
camel.vault.gcp.projectId = secretKey
If you want instead to use the GCP default client instance, you’ll need to provide the following env variables:
export $CAMEL_VAULT_GCP_USE_DEFAULT_INSTANCE=true export $CAMEL_VAULT_GCP_PROJECT_ID=projectId
export $CAMEL_VAULT_GCP_USE_DEFAULT_INSTANCE=true
export $CAMEL_VAULT_GCP_PROJECT_ID=projectId
You can also configure the credentials in the application.properties
file such as:
camel.vault.gcp.useDefaultInstance = true camel.vault.aws.projectId = region
camel.vault.gcp.useDefaultInstance = true
camel.vault.aws.projectId = region
At this point you’ll be able to reference a property in the following way by using gcp:
as prefix in the {{ }}
syntax:
<camelContext> <route> <from uri="direct:start"/> <to uri="{{gcp:route}}"/> </route> </camelContext>
<camelContext>
<route>
<from uri="direct:start"/>
<to uri="{{gcp:route}}"/>
</route>
</camelContext>
Where route
will be the name of the secret stored in the GCP Secret Manager Service.
You could specify a default value in case the secret is not present on GCP Secret Manager:
<camelContext> <route> <from uri="direct:start"/> <to uri="{{gcp:route:default}}"/> </route> </camelContext>
<camelContext>
<route>
<from uri="direct:start"/>
<to uri="{{gcp:route:default}}"/>
</route>
</camelContext>
In this case if the secret doesn’t exist, the property will fallback to "default" as value.
Also, you are able to get particular field of the secret, if you have for example a secret named database of this form:
{ "username": "admin", "password": "password123", "engine": "postgres", "host": "127.0.0.1", "port": "3128", "dbname": "db" }
{
"username": "admin",
"password": "password123",
"engine": "postgres",
"host": "127.0.0.1",
"port": "3128",
"dbname": "db"
}
You’re able to do get single secret value in your route, like for example:
<camelContext> <route> <from uri="direct:start"/> <log message="Username is {{gcp:database/username}}"/> </route> </camelContext>
<camelContext>
<route>
<from uri="direct:start"/>
<log message="Username is {{gcp:database/username}}"/>
</route>
</camelContext>
Or re-use the property as part of an endpoint.
You could specify a default value in case the particular field of secret is not present on GCP Secret Manager:
<camelContext> <route> <from uri="direct:start"/> <log message="Username is {{gcp:database/username:admin}}"/> </route> </camelContext>
<camelContext>
<route>
<from uri="direct:start"/>
<log message="Username is {{gcp:database/username:admin}}"/>
</route>
</camelContext>
In this case if the secret doesn’t exist or the secret exists, but the username field is not part of the secret, the property will fallback to "admin" as value.
For the moment we are not considering the rotation function, if any will be applied, but it is in the work to be done.
There are only two requirements: - Adding camel-google-secret-manager
JAR to your Camel application. - Give the service account used permissions to do operation at secret management level (for example accessing the secret payload, or being admin of secret manager service)
11.5.1.3. Using Azure Key Vault
To use this function you’ll need to provide credentials to Azure Key Vault Service as environment variables:
export $CAMEL_VAULT_AZURE_TENANT_ID=tenantId export $CAMEL_VAULT_AZURE_CLIENT_ID=clientId export $CAMEL_VAULT_AZURE_CLIENT_SECRET=clientSecret export $CAMEL_VAULT_AZURE_VAULT_NAME=vaultName
export $CAMEL_VAULT_AZURE_TENANT_ID=tenantId
export $CAMEL_VAULT_AZURE_CLIENT_ID=clientId
export $CAMEL_VAULT_AZURE_CLIENT_SECRET=clientSecret
export $CAMEL_VAULT_AZURE_VAULT_NAME=vaultName
You can also configure the credentials in the application.properties
file such as:
camel.vault.azure.tenantId = accessKey camel.vault.azure.clientId = clientId camel.vault.azure.clientSecret = clientSecret camel.vault.azure.vaultName = vaultName
camel.vault.azure.tenantId = accessKey
camel.vault.azure.clientId = clientId
camel.vault.azure.clientSecret = clientSecret
camel.vault.azure.vaultName = vaultName
Or you can enable the usage of Azure Identity in the following way:
export $CAMEL_VAULT_AZURE_IDENTITY_ENABLED=true export $CAMEL_VAULT_AZURE_VAULT_NAME=vaultName
export $CAMEL_VAULT_AZURE_IDENTITY_ENABLED=true
export $CAMEL_VAULT_AZURE_VAULT_NAME=vaultName
You can also enable the usage of Azure Identity in the application.properties
file such as:
camel.vault.azure.azureIdentityEnabled = true camel.vault.azure.vaultName = vaultName
camel.vault.azure.azureIdentityEnabled = true
camel.vault.azure.vaultName = vaultName
At this point you’ll be able to reference a property in the following way:
<camelContext> <route> <from uri="direct:start"/> <to uri="{{azure:route}}"/> </route> </camelContext>
<camelContext>
<route>
<from uri="direct:start"/>
<to uri="{{azure:route}}"/>
</route>
</camelContext>
Where route will be the name of the secret stored in the Azure Key Vault Service.
You could specify a default value in case the secret is not present on Azure Key Vault Service:
<camelContext> <route> <from uri="direct:start"/> <to uri="{{azure:route:default}}"/> </route> </camelContext>
<camelContext>
<route>
<from uri="direct:start"/>
<to uri="{{azure:route:default}}"/>
</route>
</camelContext>
In this case if the secret doesn’t exist, the property will fallback to "default" as value.
Also you are able to get particular field of the secret, if you have for example a secret named database of this form:
{ "username": "admin", "password": "password123", "engine": "postgres", "host": "127.0.0.1", "port": "3128", "dbname": "db" }
{
"username": "admin",
"password": "password123",
"engine": "postgres",
"host": "127.0.0.1",
"port": "3128",
"dbname": "db"
}
You’re able to do get single secret value in your route, like for example:
<camelContext> <route> <from uri="direct:start"/> <log message="Username is {{azure:database/username}}"/> </route> </camelContext>
<camelContext>
<route>
<from uri="direct:start"/>
<log message="Username is {{azure:database/username}}"/>
</route>
</camelContext>
Or re-use the property as part of an endpoint.
You could specify a default value in case the particular field of secret is not present on Azure Key Vault:
<camelContext> <route> <from uri="direct:start"/> <log message="Username is {{azure:database/username:admin}}"/> </route> </camelContext>
<camelContext>
<route>
<from uri="direct:start"/>
<log message="Username is {{azure:database/username:admin}}"/>
</route>
</camelContext>
In this case if the secret doesn’t exist or the secret exists, but the username field is not part of the secret, the property will fallback to "admin" as value.
For the moment we are not considering the rotation function, if any will be applied, but it is in the work to be done.
The only requirement is adding the camel-azure-key-vault jar to your Camel application.
11.5.1.4. Using Hashicorp Vault
To use this function, you’ll need to provide credentials for Hashicorp vault as environment variables:
export $CAMEL_VAULT_HASHICORP_TOKEN=token export $CAMEL_VAULT_HASHICORP_HOST=host export $CAMEL_VAULT_HASHICORP_PORT=port export $CAMEL_VAULT_HASHICORP_SCHEME=http/https
export $CAMEL_VAULT_HASHICORP_TOKEN=token
export $CAMEL_VAULT_HASHICORP_HOST=host
export $CAMEL_VAULT_HASHICORP_PORT=port
export $CAMEL_VAULT_HASHICORP_SCHEME=http/https
You can also configure the credentials in the application.properties
file such as:
camel.vault.hashicorp.token = token camel.vault.hashicorp.host = host camel.vault.hashicorp.port = port camel.vault.hashicorp.scheme = scheme
camel.vault.hashicorp.token = token
camel.vault.hashicorp.host = host
camel.vault.hashicorp.port = port
camel.vault.hashicorp.scheme = scheme
At this point, you’ll be able to reference a property in the following way:
<camelContext> <route> <from uri="direct:start"/> <to uri="{{hashicorp:secret:route}}"/> </route> </camelContext>
<camelContext>
<route>
<from uri="direct:start"/>
<to uri="{{hashicorp:secret:route}}"/>
</route>
</camelContext>
Where route will be the name of the secret stored in the Hashicorp Vault instance, in the 'secret' engine.
You could specify a default value in case the secret is not present on Hashicorp Vault instance:
<camelContext> <route> <from uri="direct:start"/> <to uri="{{hashicorp:secret:route:default}}"/> </route> </camelContext>
<camelContext>
<route>
<from uri="direct:start"/>
<to uri="{{hashicorp:secret:route:default}}"/>
</route>
</camelContext>
In this case, if the secret doesn’t exist in the 'secret' engine, the property will fall back to "default" as value.
Also, you are able to get a particular field of the secret, if you have, for example, a secret named database of this form:
{ "username": "admin", "password": "password123", "engine": "postgres", "host": "127.0.0.1", "port": "3128", "dbname": "db" }
{
"username": "admin",
"password": "password123",
"engine": "postgres",
"host": "127.0.0.1",
"port": "3128",
"dbname": "db"
}
You’re able to do get single secret value in your route, in the 'secret' engine, like for example:
<camelContext> <route> <from uri="direct:start"/> <log message="Username is {{hashicorp:secret:database/username}}"/> </route> </camelContext>
<camelContext>
<route>
<from uri="direct:start"/>
<log message="Username is {{hashicorp:secret:database/username}}"/>
</route>
</camelContext>
Or re-use the property as part of an endpoint.
You could specify a default value in case the particular field of secret is not present on Hashicorp Vault instance, in the 'secret' engine:
<camelContext> <route> <from uri="direct:start"/> <log message="Username is {{hashicorp:secret:database/username:admin}}"/> </route> </camelContext>
<camelContext>
<route>
<from uri="direct:start"/>
<log message="Username is {{hashicorp:secret:database/username:admin}}"/>
</route>
</camelContext>
In this case, if the secret doesn’t exist or the secret exists (in the 'secret' engine) but the username field is not part of the secret, the property will fall back to "admin" as value.
There is also the syntax to get a particular version of the secret for both the approach, with field/default value specified or only with secret:
<camelContext> <route> <from uri="direct:start"/> <to uri="{{hashicorp:secret:route@2}}"/> </route> </camelContext>
<camelContext>
<route>
<from uri="direct:start"/>
<to uri="{{hashicorp:secret:route@2}}"/>
</route>
</camelContext>
This approach will return the RAW route secret with version '2', in the 'secret' engine.
<camelContext> <route> <from uri="direct:start"/> <to uri="{{hashicorp:route:default@2}}"/> </route> </camelContext>
<camelContext>
<route>
<from uri="direct:start"/>
<to uri="{{hashicorp:route:default@2}}"/>
</route>
</camelContext>
This approach will return the route secret value with version '2' or default value in case the secret doesn’t exist or the version doesn’t exist (in the 'secret' engine).
<camelContext> <route> <from uri="direct:start"/> <log message="Username is {{hashicorp:secret:database/username:admin@2}}"/> </route> </camelContext>
<camelContext>
<route>
<from uri="direct:start"/>
<log message="Username is {{hashicorp:secret:database/username:admin@2}}"/>
</route>
</camelContext>
This approach will return the username field of the database secret with version '2' or admin in case the secret doesn’t exist or the version doesn’t exist (in the 'secret' engine).
11.5.1.5. Automatic Camel context reloading on Secret Refresh while using AWS Secrets Manager
Being able to reload Camel context on a Secret Refresh, could be done by specifying the usual credentials (the same used for AWS Secret Manager Property Function).
With Environment variables:
export $CAMEL_VAULT_AWS_USE_DEFAULT_CREDENTIALS_PROVIDER=accessKey export $CAMEL_VAULT_AWS_REGION=region
export $CAMEL_VAULT_AWS_USE_DEFAULT_CREDENTIALS_PROVIDER=accessKey
export $CAMEL_VAULT_AWS_REGION=region
or as plain Camel main properties:
camel.vault.aws.useDefaultCredentialProvider = true camel.vault.aws.region = region
camel.vault.aws.useDefaultCredentialProvider = true
camel.vault.aws.region = region
Or by specifying accessKey/SecretKey and region, instead of using the default credentials provider chain.
To enable the automatic refresh you’ll need additional properties to set:
camel.vault.aws.refreshEnabled=true camel.vault.aws.refreshPeriod=60000 camel.vault.aws.secrets=Secret camel.main.context-reload-enabled = true
camel.vault.aws.refreshEnabled=true
camel.vault.aws.refreshPeriod=60000
camel.vault.aws.secrets=Secret
camel.main.context-reload-enabled = true
where camel.vault.aws.refreshEnabled
will enable the automatic context reload, camel.vault.aws.refreshPeriod
is the interval of time between two different checks for update events and camel.vault.aws.secrets
is a regex representing the secrets we want to track for updates.
Note that camel.vault.aws.secrets
is not mandatory: if not specified the task responsible for checking updates events will take into accounts or the properties with an aws:
prefix.
The only requirement is adding the camel-aws-secrets-manager jar to your Camel application.
11.5.1.6. Automatic Camel context reloading on Secret Refresh while using AWS Secrets Manager with Eventbridge and AWS SQS Services
Another option is to use AWS EventBridge in conjunction with the AWS SQS service.
On the AWS side, the following resources need to be created:
- an AWS Couldtrail trail
- an AWS SQS Queue
- an Eventbridge rule of the following kind
{ "source": ["aws.secretsmanager"], "detail-type": ["AWS API Call via CloudTrail"], "detail": { "eventSource": ["secretsmanager.amazonaws.com"] } }
{
"source": ["aws.secretsmanager"],
"detail-type": ["AWS API Call via CloudTrail"],
"detail": {
"eventSource": ["secretsmanager.amazonaws.com"]
}
}
This rule will make the event related to AWS Secrets Manager filtered
- You need to set the a Rule target to the AWS SQS Queue for Eventbridge rule
- You need to give permission to the Eventbrige rule, to write on the above SQS Queue. For doing this you’ll need to define a json file like this:
{ "Policy": "{\"Version\":\"2012-10-17\",\"Id\":\"<queue_arn>/SQSDefaultPolicy\",\"Statement\":[{\"Sid\": \"EventsToMyQueue\", \"Effect\": \"Allow\", \"Principal\": {\"Service\": \"events.amazonaws.com\"}, \"Action\": \"sqs:SendMessage\", \"Resource\": \"<queue_arn>\", \"Condition\": {\"ArnEquals\": {\"aws:SourceArn\": \"<eventbridge_rule_arn>\"}}}]}" }
{
"Policy": "{\"Version\":\"2012-10-17\",\"Id\":\"<queue_arn>/SQSDefaultPolicy\",\"Statement\":[{\"Sid\": \"EventsToMyQueue\", \"Effect\": \"Allow\", \"Principal\": {\"Service\": \"events.amazonaws.com\"}, \"Action\": \"sqs:SendMessage\", \"Resource\": \"<queue_arn>\", \"Condition\": {\"ArnEquals\": {\"aws:SourceArn\": \"<eventbridge_rule_arn>\"}}}]}"
}
Change the values for queue_arn and eventbridge_rule_arn, save the file with policy.json name and run the following command with AWS CLI
aws sqs set-queue-attributes --queue-url <queue_url> --attributes file://policy.json
aws sqs set-queue-attributes --queue-url <queue_url> --attributes file://policy.json
where queue_url is the AWS SQS Queue URL of the just created Queue.
Now you should be able to set up the configuration on the Camel side. To enable the SQS notification add the following properties:
camel.vault.aws.refreshEnabled=true camel.vault.aws.refreshPeriod=60000 camel.vault.aws.secrets=Secret camel.main.context-reload-enabled = true camel.vault.aws.useSqsNotification=true camel.vault.aws.sqsQueueUrl=<queue_url>
camel.vault.aws.refreshEnabled=true
camel.vault.aws.refreshPeriod=60000
camel.vault.aws.secrets=Secret
camel.main.context-reload-enabled = true
camel.vault.aws.useSqsNotification=true
camel.vault.aws.sqsQueueUrl=<queue_url>
where queue_url is the AWS SQS Queue URL of the just created Queue.
Whenever an event of PutSecretValue for the Secret named 'Secret' will happen, a message will be enqueued in the AWS SQS Queue and consumed on the Camel side and a context reload will be triggered.
11.5.1.7. Automatic Camel context reloading on Secret Refresh while using Google Secret Manager
Being able to reload Camel context on a Secret Refresh, could be done by specifying the usual credentials (the same used for Google Secret Manager Property Function).
With Environment variables:
export $CAMEL_VAULT_GCP_USE_DEFAULT_INSTANCE=true export $CAMEL_VAULT_GCP_PROJECT_ID=projectId
export $CAMEL_VAULT_GCP_USE_DEFAULT_INSTANCE=true
export $CAMEL_VAULT_GCP_PROJECT_ID=projectId
or as plain Camel main properties:
camel.vault.gcp.useDefaultInstance = true camel.vault.aws.projectId = projectId
camel.vault.gcp.useDefaultInstance = true
camel.vault.aws.projectId = projectId
Or by specifying a path to a service account key file, instead of using the default instance.
To enable the automatic refresh you’ll need additional properties to set:
camel.vault.gcp.projectId= projectId camel.vault.gcp.refreshEnabled=true camel.vault.gcp.refreshPeriod=60000 camel.vault.gcp.secrets=hello* camel.vault.gcp.subscriptionName=subscriptionName camel.main.context-reload-enabled = true
camel.vault.gcp.projectId= projectId
camel.vault.gcp.refreshEnabled=true
camel.vault.gcp.refreshPeriod=60000
camel.vault.gcp.secrets=hello*
camel.vault.gcp.subscriptionName=subscriptionName
camel.main.context-reload-enabled = true
where camel.vault.gcp.refreshEnabled
will enable the automatic context reload, camel.vault.gcp.refreshPeriod
is the interval of time between two different checks for update events and camel.vault.gcp.secrets
is a regex representing the secrets we want to track for updates.
Note that camel.vault.gcp.secrets
is not mandatory: if not specified the task responsible for checking updates events will take into accounts or the properties with an gcp:
prefix.
The camel.vault.gcp.subscriptionName
is the subscription name created in relation to the Google PubSub topic associated with the tracked secrets.
This mechanism while make use of the notification system related to Google Secret Manager: through this feature, every secret could be associated to one up to ten Google Pubsub Topics. These topics will receive events related to life cycle of the secret.
There are only two requirements: - Adding camel-google-secret-manager
JAR to your Camel application. - Give the service account used permissions to do operation at secret management level (for example accessing the secret payload, or being admin of secret manager service and also have permission over the Pubsub service)
11.5.1.8. Automatic Camel context reloading on Secret Refresh while using Azure Key Vault
Being able to reload Camel context on a Secret Refresh, could be done by specifying the usual credentials (the same used for Azure Key Vault Property Function).
With Environment variables:
export $CAMEL_VAULT_AZURE_TENANT_ID=tenantId export $CAMEL_VAULT_AZURE_CLIENT_ID=clientId export $CAMEL_VAULT_AZURE_CLIENT_SECRET=clientSecret export $CAMEL_VAULT_AZURE_VAULT_NAME=vaultName
export $CAMEL_VAULT_AZURE_TENANT_ID=tenantId
export $CAMEL_VAULT_AZURE_CLIENT_ID=clientId
export $CAMEL_VAULT_AZURE_CLIENT_SECRET=clientSecret
export $CAMEL_VAULT_AZURE_VAULT_NAME=vaultName
or as plain Camel main properties:
camel.vault.azure.tenantId = accessKey camel.vault.azure.clientId = clientId camel.vault.azure.clientSecret = clientSecret camel.vault.azure.vaultName = vaultName
camel.vault.azure.tenantId = accessKey
camel.vault.azure.clientId = clientId
camel.vault.azure.clientSecret = clientSecret
camel.vault.azure.vaultName = vaultName
If you want to use Azure Identity with environment variables, you can do in the following way:
export $CAMEL_VAULT_AZURE_IDENTITY_ENABLED=true export $CAMEL_VAULT_AZURE_VAULT_NAME=vaultName
export $CAMEL_VAULT_AZURE_IDENTITY_ENABLED=true
export $CAMEL_VAULT_AZURE_VAULT_NAME=vaultName
You can also enable the usage of Azure Identity in the application.properties
file such as:
camel.vault.azure.azureIdentityEnabled = true camel.vault.azure.vaultName = vaultName
camel.vault.azure.azureIdentityEnabled = true
camel.vault.azure.vaultName = vaultName
To enable the automatic refresh you’ll need additional properties to set:
camel.vault.azure.refreshEnabled=true camel.vault.azure.refreshPeriod=60000 camel.vault.azure.secrets=Secret camel.vault.azure.eventhubConnectionString=eventhub_conn_string camel.vault.azure.blobAccountName=blob_account_name camel.vault.azure.blobContainerName=blob_container_name camel.vault.azure.blobAccessKey=blob_access_key camel.main.context-reload-enabled = true
camel.vault.azure.refreshEnabled=true
camel.vault.azure.refreshPeriod=60000
camel.vault.azure.secrets=Secret
camel.vault.azure.eventhubConnectionString=eventhub_conn_string
camel.vault.azure.blobAccountName=blob_account_name
camel.vault.azure.blobContainerName=blob_container_name
camel.vault.azure.blobAccessKey=blob_access_key
camel.main.context-reload-enabled = true
where camel.vault.azure.refreshEnabled
will enable the automatic context reload, camel.vault.azure.refreshPeriod
is the interval of time between two different checks for update events and camel.vault.azure.secrets
is a regex representing the secrets we want to track for updates.
where camel.vault.azure.eventhubConnectionString
is the eventhub connection string to get notification from, camel.vault.azure.blobAccountName
, camel.vault.azure.blobContainerName
and camel.vault.azure.blobAccessKey
are the Azure Storage Blob parameters for the checkpoint store needed by Azure Eventhub.
Note that camel.vault.azure.secrets
is not mandatory: if not specified the task responsible for checking updates events will take into accounts or the properties with an azure:
prefix.
The only requirement is adding the camel-azure-key-vault jar to your Camel application.