- CVE-2019-12419: Apache CXF OpenId Connect token service does not properly validate the clientId. The problem here is that the OAuth access token service didn't validate that the submitted clientId matches that of the authenticated principal, thus allowing a malicious client to obtain an access token using a code issued to another client. Of course, this requires the malicious client to actually obtain the authorization code for the other client somehow.
- CVE-2019-12406: Apache CXF does not restrict the number of message attachments. Essentially here CXF did not impose any restrictions on the number of message attachments, meaning that a malicious entity could try to attempt a denial of serice attack by generating a message with a huge number of message attachments.
Tuesday, November 5, 2019
Two new CVEs released for Apache CXF
Apache CXF 3.3.4 and 3.2.11 have been released. Along with the usual bug fixes and dependency updates, these releases contain fixes for two new CVEs:
Monday, August 26, 2019
Annotation support with Apache Shiro
Apache Shiro is a Java framework to simply authentication, authorization etc. I previously blogged about a test-case I wrote that shows how to use Shiro with Apache CXF to authenticate and authorize a username and password received as part of a web service request. This post extends the previous post by showing how to use Shiro to enable authorization via annotations on the service implementation.
The previous post defined some required roles for an endpoint in Spring, and passed them through to a ShiroUTValidator class which checks that the authenticated subject has all of the defined roles:
The problem with this approach is that it's not possible to specify individual roles for different methods in the service implementation - the user must have the role to invoke on any of the methods.
An alternative is instead to use Shiro's annotation support. Here we can add annotations to the service endpoint implementation to require that the authenticated user has the correct role (@RequiresRoles) or permissions (@RequiresPermissions). Note that these annotations are specific to Shiro, support is not yet added to support the standard javax.annotation.security annotations (see here).
So to change our test-case to use annotations, instead of defining the roles in Spring, we instead define the following annotation in the service implementation:
In the spring configuration for the service, we need to add a few additional interceptors so that the annotation gets processed:
That's all that's required to get Shiro annotations working with CXF service implementations. The full test source is available here.
The previous post defined some required roles for an endpoint in Spring, and passed them through to a ShiroUTValidator class which checks that the authenticated subject has all of the defined roles:
The problem with this approach is that it's not possible to specify individual roles for different methods in the service implementation - the user must have the role to invoke on any of the methods.
An alternative is instead to use Shiro's annotation support. Here we can add annotations to the service endpoint implementation to require that the authenticated user has the correct role (@RequiresRoles) or permissions (@RequiresPermissions). Note that these annotations are specific to Shiro, support is not yet added to support the standard javax.annotation.security annotations (see here).
So to change our test-case to use annotations, instead of defining the roles in Spring, we instead define the following annotation in the service implementation:
In the spring configuration for the service, we need to add a few additional interceptors so that the annotation gets processed:
That's all that's required to get Shiro annotations working with CXF service implementations. The full test source is available here.
Thursday, May 9, 2019
CoAP support in Apache Camel
The Constrained Application Protocol (CoAP) is standardized in RFC-7252. It offers REST-like functionality over UDP for constrained devices in the Internet of Things. Apache Camel has had support for the CoAP protocol since the 2.16 release, by using the eclipse Californium framework. It offers support for using CoAP in both producer and consumer mode, and also offers integration with the Camel REST DSL. In this post, we will cover a number of significant improvements to the Camel CoAP component for the forthcoming 3.0.0 release.
1) DTLS support
The first significant improvement is that the CoAP component has been updated to support DTLS, something that necessitated a major upgrade of the californium dependency. CoAP supports TLS / UDP using a "coaps" scheme, something it is now possible to use in Camel. To see how this all works, take a look at the following github test-case I put together:
It uses the Camel REST DSL to create a simple REST service on the "/data" path that produces an XML response when invoked with a GET request. The actual route is omitted above, it just returns a XML document read in from a file. Note the component of the REST DSL is "coap" and it uses a scheme of "coaps". When using a scheme of "coaps", we need to configure the relevant TLS configuration, something that is done by referring to a "sslContextParameters" bean, which in this case contains a reference to a keystore used to retrieve the TLS key. Note that when using a certificate for TLS with CoAP, an elliptic curve key is required - RSA is not supported.
On the client side, the test-case shows how to configure a Camel producer to invoke on the coaps REST API:
Note that a scheme of "coaps" is used, and that it refers to a sslContextParameters bean containing the truststore to use, as well as a specific CipherSuite (this is optional, I just put it in to show how to use it). As the logging output does not really show what is going on, here is the Wireshark output which shows that DTLS is in use:
2) Support for Raw Public Keys and Pre-Shared Keys
In the section above, we saw how to configure CoAP with DTLS by referring to a sslContextParameters bean, which in turn refers to keystores to extract private keys and certificates. This is one option to support DTLS. However we also have two other options instead of using certificates.
The first is called Raw Public Keys. This is when we may not have access to a certificate containing the (trusted) public key of the endpoint. In this case, we can configure TLS using a PrivateKey and/or PublicKey objects. Both are required for a service. The client needs to be configured with a trustedRpkStore parameter, which is an interface supplied by Californium, that determines trust in an identifier. If the service is configured with "clientAuthentication" of "REQUIRE", then the service must configure trustedRpkStore, and the client must also specify a privateKey parameter. Here is a sample code snippet from the Camel tests:
The second option is called Pre-Shared Keys. This is when we don't have access either to certificates or public keys, but have some symmetric keys that are shared between both the client and service. In this case, we can use these keys for TLS. Both the client and service are configured with a "pskStore" parameter, which is an interface in Californium that associates a (byte[]) key with an identity. Here is a sample code snippet from the Camel tests:
3) Support for TCP / TLS
A newer RFC (RFC-8323) extends the original RFC to add support for CoAP over TCP and Websockets. Camel 3.0.0 has added support for using CoAP over both TCP and also TLS over TCP. Websocket support is not currently available. RFC-8323 uses two new schemes for TCP, both of which are supported in Camel - "coap+tcp" for CoAP / TCP, and "coaps+tcp" for CoAP / TCP with TLS. Only the certificate method of configuring TLS is supported, which works in exactly the same way as for DTLS above. Pre-shared keys and Raw Public Keys are not supported over TCP now, only UDP.
To see how it works, simply alter the configuration in the github testcase and change "coaps" to "coaps+tcp" (in both locations). Now run the test-case again and it should work seemlessly:
1) DTLS support
The first significant improvement is that the CoAP component has been updated to support DTLS, something that necessitated a major upgrade of the californium dependency. CoAP supports TLS / UDP using a "coaps" scheme, something it is now possible to use in Camel. To see how this all works, take a look at the following github test-case I put together:
- camel-coap - A test-case for the camel-coap component. It shows how to use the coap component with the Camel REST DSL + TLS.
It uses the Camel REST DSL to create a simple REST service on the "/data" path that produces an XML response when invoked with a GET request. The actual route is omitted above, it just returns a XML document read in from a file. Note the component of the REST DSL is "coap" and it uses a scheme of "coaps". When using a scheme of "coaps", we need to configure the relevant TLS configuration, something that is done by referring to a "sslContextParameters" bean, which in this case contains a reference to a keystore used to retrieve the TLS key. Note that when using a certificate for TLS with CoAP, an elliptic curve key is required - RSA is not supported.
On the client side, the test-case shows how to configure a Camel producer to invoke on the coaps REST API:
Note that a scheme of "coaps" is used, and that it refers to a sslContextParameters bean containing the truststore to use, as well as a specific CipherSuite (this is optional, I just put it in to show how to use it). As the logging output does not really show what is going on, here is the Wireshark output which shows that DTLS is in use:
2) Support for Raw Public Keys and Pre-Shared Keys
In the section above, we saw how to configure CoAP with DTLS by referring to a sslContextParameters bean, which in turn refers to keystores to extract private keys and certificates. This is one option to support DTLS. However we also have two other options instead of using certificates.
The first is called Raw Public Keys. This is when we may not have access to a certificate containing the (trusted) public key of the endpoint. In this case, we can configure TLS using a PrivateKey and/or PublicKey objects. Both are required for a service. The client needs to be configured with a trustedRpkStore parameter, which is an interface supplied by Californium, that determines trust in an identifier. If the service is configured with "clientAuthentication" of "REQUIRE", then the service must configure trustedRpkStore, and the client must also specify a privateKey parameter. Here is a sample code snippet from the Camel tests:
The second option is called Pre-Shared Keys. This is when we don't have access either to certificates or public keys, but have some symmetric keys that are shared between both the client and service. In this case, we can use these keys for TLS. Both the client and service are configured with a "pskStore" parameter, which is an interface in Californium that associates a (byte[]) key with an identity. Here is a sample code snippet from the Camel tests:
3) Support for TCP / TLS
A newer RFC (RFC-8323) extends the original RFC to add support for CoAP over TCP and Websockets. Camel 3.0.0 has added support for using CoAP over both TCP and also TLS over TCP. Websocket support is not currently available. RFC-8323 uses two new schemes for TCP, both of which are supported in Camel - "coap+tcp" for CoAP / TCP, and "coaps+tcp" for CoAP / TCP with TLS. Only the certificate method of configuring TLS is supported, which works in exactly the same way as for DTLS above. Pre-shared keys and Raw Public Keys are not supported over TCP now, only UDP.
To see how it works, simply alter the configuration in the github testcase and change "coaps" to "coaps+tcp" (in both locations). Now run the test-case again and it should work seemlessly:
Tuesday, April 30, 2019
Securing the Apache Camel REST DSL
Recently I put together a simple test-case for Apache Camel's REST DSL and realised that it illustrated quite a few security concepts, as well as various Camel components, that might be interesting to blog about. The test-case is a simple spring-based project, which is available on github here:
1) The Apache Camel REST DSL
Apache Camel offers a REST DSL which makes it really easy to create a simple REST service.
Here we are creating a simple REST service on the "/data" path that produces an XML response when invoked with a GET request. The actual functionality is delegated to a Camel route called "direct:get":
Here we are reading in some files from a directory using the Camel File component and using "pollEnrich" to include the contents of that directory into the message that is returned to the user. Finally we need to tell Camel how to create the REST DSL. Camel supports a wide range of components, but for the purposes of this example we are using the Camel Jetty component:
Note the port is not hard-coded, but instead retrieved from a randomly generated property in the pom using the "reserve-network-port" goal of the "build-helper-maven-plugin".
2) Getting TLS to work with the Camel REST DSL
To support TLS with the Camel REST DSL, we need to set the scheme to "https" as above in the "restConfiguration". The REST configuration also refers to a property called "sslContextParameters", which is where we obtain the keys required to support TLS. See the Camel JSSE documentation for more information on this property.
The sslContextParameters bean definition allows us to define the key managers and trust managers for the TLS endpoint by referring to Java keystore files with the relevant passwords. If we are not supporting client authentication, the trustManagers portion can be omitted.
3) Using Jasypt to decrypt keystore passwords for use in TLS
Note above that we have not hard-coded the TLS keystore passwords in our Camel spring configuration, but are instead retrieving them from a property. Camel offers the ability to store the passwords in encrypted form, by using the Camel Jasypt component to decrypt them given a master password. The encrypted passwords themselves are stored in a passwords.properties file:
These encrypted passwords are obtained using the camel-jasypt jar (shipped for example in the Camel distribution):
This retrieves the master password from a system property. For the purposes of this demo, the password is set as a system property in the "maven-surefire-plugin" defined in the pom.
4) Invoking on our secured REST service using the Camel HTTP4 component
The demo also includes a client route which invokes on the secured REST service we have created. We use the Camel HTTP4 component for this:
We start the route using the Camel Timer component, before calling the HTTP4 component. As we have included a query String in the request URI, the http4 component will issue a GET request. As for the REST service, we need to configure the TLS keys using the "sslContextParameters" parameter.
On the client side we only need the trustManagers configuration, unless of course we want to support client authentication. For the purposes of this demo, we also need to configure a custom x509HostnameVerifier property. This is because the TLS certificate the service is using will not be accepted by the client by default, as the common name of the certificate does not match the domain name of the service. We can circumvent this (for testing purposes only, it is not secure!) by using the following hostname verifier:
Finally we log the service response to the console so we can see the test-case working.
- camel-jetty: A test-case for the Camel Jetty component, TLS, the REST DSL + Jasypt.
1) The Apache Camel REST DSL
Apache Camel offers a REST DSL which makes it really easy to create a simple REST service.
Here we are creating a simple REST service on the "/data" path that produces an XML response when invoked with a GET request. The actual functionality is delegated to a Camel route called "direct:get":
Here we are reading in some files from a directory using the Camel File component and using "pollEnrich" to include the contents of that directory into the message that is returned to the user. Finally we need to tell Camel how to create the REST DSL. Camel supports a wide range of components, but for the purposes of this example we are using the Camel Jetty component:
Note the port is not hard-coded, but instead retrieved from a randomly generated property in the pom using the "reserve-network-port" goal of the "build-helper-maven-plugin".
2) Getting TLS to work with the Camel REST DSL
To support TLS with the Camel REST DSL, we need to set the scheme to "https" as above in the "restConfiguration". The REST configuration also refers to a property called "sslContextParameters", which is where we obtain the keys required to support TLS. See the Camel JSSE documentation for more information on this property.
The sslContextParameters bean definition allows us to define the key managers and trust managers for the TLS endpoint by referring to Java keystore files with the relevant passwords. If we are not supporting client authentication, the trustManagers portion can be omitted.
3) Using Jasypt to decrypt keystore passwords for use in TLS
Note above that we have not hard-coded the TLS keystore passwords in our Camel spring configuration, but are instead retrieving them from a property. Camel offers the ability to store the passwords in encrypted form, by using the Camel Jasypt component to decrypt them given a master password. The encrypted passwords themselves are stored in a passwords.properties file:
These encrypted passwords are obtained using the camel-jasypt jar (shipped for example in the Camel distribution):
- java -jar camel-jasypt-2.23.1.jar -c encrypt -p master-secret -i storepass
This retrieves the master password from a system property. For the purposes of this demo, the password is set as a system property in the "maven-surefire-plugin" defined in the pom.
4) Invoking on our secured REST service using the Camel HTTP4 component
The demo also includes a client route which invokes on the secured REST service we have created. We use the Camel HTTP4 component for this:
We start the route using the Camel Timer component, before calling the HTTP4 component. As we have included a query String in the request URI, the http4 component will issue a GET request. As for the REST service, we need to configure the TLS keys using the "sslContextParameters" parameter.
On the client side we only need the trustManagers configuration, unless of course we want to support client authentication. For the purposes of this demo, we also need to configure a custom x509HostnameVerifier property. This is because the TLS certificate the service is using will not be accepted by the client by default, as the common name of the certificate does not match the domain name of the service. We can circumvent this (for testing purposes only, it is not secure!) by using the following hostname verifier:
Finally we log the service response to the console so we can see the test-case working.
Friday, April 5, 2019
Performance gain for web service requests in Apache CXF
In this post I want to talk about a recent performance gain for JAX-WS web service requests I made in Apache CXF. It was prompted by a mail to the CXF users list. The scenario was for a JAX-WS web service where certain requests are secured using WS-SecurityPolicy, and other requests are not. The problem was that the user observed that the security interceptors were always invoked in CXF, even for the requests that had no security applied to the message, and that this resulted in a noticeable performance penalty for large requests.
The reason for the performance penalty is that CXF needs to convert the request into a Document Object Model to apply WS-Security (note there is also a streaming WS-Security implementation available, but the performance is roughly similar). CXF needs to perform this conversion as it requires access to the full Document to perform XML Signature verification, etc. on the request. So even for the insecure request, it would apply CXF's SAAJInInterceptor. Then it would iterate through the security headers of the request, find that there was none present, and skip security processing.
However when thinking about this problem, I realised that before invoking the SAAJInInterceptor, we could check to see whether a security header is actually present in the request (and whether it matches the configured "actor" if one is configured). CXF makes the message headers available in DOM form, but not the SOAP Body (unless SAAJInInterceptor is called). If no matching security header is available, then we can skip security processing, and instead just perform WS-SecurityPolicy assertion using a set of empty results.
This idea is implemented in CXF for the 3.3.2 release via the task CXF-8010. To test what happens, I added a test-case to github here. This creates a war file with a service with two operations, one that is not secured, and one that has a WS-SecurityPolicy asymmetric binding applied to the operations. Both operations contain two parameters, an integer and a String description.
To test it, I added a JMeter test-case here. It uses 10 threads to call the insecure operation 30,000 times. The description String in each request contains the URL encoded version of the WS-Security specification to test what happens with a somewhat large request.
Here are the results using CXF 3.3.1:
and here are the results using the CXF 3.3.2-SNAPSHOT code with the fix for CXF-8010 applied:
Using CXF 3.3.1 the throughput is 1604.25 requests per second, whereas with CXF 3.3.2 the throughput is 1795.26 requests per second, a gain of roughly 9%. For a more complex SOAP Body I would expect the gain to be a lot greater.
The reason for the performance penalty is that CXF needs to convert the request into a Document Object Model to apply WS-Security (note there is also a streaming WS-Security implementation available, but the performance is roughly similar). CXF needs to perform this conversion as it requires access to the full Document to perform XML Signature verification, etc. on the request. So even for the insecure request, it would apply CXF's SAAJInInterceptor. Then it would iterate through the security headers of the request, find that there was none present, and skip security processing.
However when thinking about this problem, I realised that before invoking the SAAJInInterceptor, we could check to see whether a security header is actually present in the request (and whether it matches the configured "actor" if one is configured). CXF makes the message headers available in DOM form, but not the SOAP Body (unless SAAJInInterceptor is called). If no matching security header is available, then we can skip security processing, and instead just perform WS-SecurityPolicy assertion using a set of empty results.
This idea is implemented in CXF for the 3.3.2 release via the task CXF-8010. To test what happens, I added a test-case to github here. This creates a war file with a service with two operations, one that is not secured, and one that has a WS-SecurityPolicy asymmetric binding applied to the operations. Both operations contain two parameters, an integer and a String description.
To test it, I added a JMeter test-case here. It uses 10 threads to call the insecure operation 30,000 times. The description String in each request contains the URL encoded version of the WS-Security specification to test what happens with a somewhat large request.
Here are the results using CXF 3.3.1:
and here are the results using the CXF 3.3.2-SNAPSHOT code with the fix for CXF-8010 applied:
Using CXF 3.3.1 the throughput is 1604.25 requests per second, whereas with CXF 3.3.2 the throughput is 1795.26 requests per second, a gain of roughly 9%. For a more complex SOAP Body I would expect the gain to be a lot greater.
Friday, March 29, 2019
HTTP Signature support in Apache CXF
Apache CXF provides support for the HTTP Signatures draft spec since the 3.3.0 release. Up to this point, JAX-RS message payloads could be signed using either XML Security or else using JOSE. In particular, the JOSE functionality can be used to also sign HTTP headers. However it doesn't allow the possibility to sign the HTTP method and Path, something that HTTP Signature supports. In this post we'll look at how to use HTTP Signatures with Apache CXF.
I uploaded a sample project to github to see how HTTP Signature can be used with CXF:
1) Client configuration
The client configuration to both sign the outbound request and verify the service response is configured in the test code:
Two JAX-RS providers are added - CreateSignatureInterceptor creates a signature on the outbound request, and VerifySignatureClientFilter verifies a signature on the response. The keys used to sign the request and verify the response are configured in properties files, that are referenced via the "rs.security.signature.out.properties" and "rs.security.signature.in.properties" configuration tags:
Here we can see that a keystore is being used to retrieve the private key for signing the outbound request. If you wish to retrieve keys from some other source, then instead of using configuration properties it's best to configure the MessageSigner class directly on the CreateSignatureInterceptor.
By default CXF will add all HTTP headers to the signature. In addition, a client will also include the HTTP method and path using the "(request-target)" header. Also if the payload is not empty, it will be digested with the digest added to a "Digest" HTTP header, which is also signed. This provides payload integrity. By default, the signature algorithm is "rsa-sha256", of course it is possible to configure this. A secured request using HTTP signature looks like the following:
2) Service configuration
The service configuration is defined in spring. Two different JAX-RS providers are used on the service side - VerifySignatureFilter is used to verify a signature on the client request, and CreateSignatureInterceptor is used to sign the response message as per the client request.
For more information on how to use HTTP Signatures with Apache CXF, refer to the CXF documentation.
I uploaded a sample project to github to see how HTTP Signature can be used with CXF:
- cxf-jaxrs-httpsig: This project contains a test that shows how to use the HTTP Signature functionality in Apache CXF to sign a message to/from a JAX-RS service.
1) Client configuration
The client configuration to both sign the outbound request and verify the service response is configured in the test code:
Two JAX-RS providers are added - CreateSignatureInterceptor creates a signature on the outbound request, and VerifySignatureClientFilter verifies a signature on the response. The keys used to sign the request and verify the response are configured in properties files, that are referenced via the "rs.security.signature.out.properties" and "rs.security.signature.in.properties" configuration tags:
Here we can see that a keystore is being used to retrieve the private key for signing the outbound request. If you wish to retrieve keys from some other source, then instead of using configuration properties it's best to configure the MessageSigner class directly on the CreateSignatureInterceptor.
By default CXF will add all HTTP headers to the signature. In addition, a client will also include the HTTP method and path using the "(request-target)" header. Also if the payload is not empty, it will be digested with the digest added to a "Digest" HTTP header, which is also signed. This provides payload integrity. By default, the signature algorithm is "rsa-sha256", of course it is possible to configure this. A secured request using HTTP signature looks like the following:
2) Service configuration
The service configuration is defined in spring. Two different JAX-RS providers are used on the service side - VerifySignatureFilter is used to verify a signature on the client request, and CreateSignatureInterceptor is used to sign the response message as per the client request.
For more information on how to use HTTP Signatures with Apache CXF, refer to the CXF documentation.
Thursday, March 21, 2019
Using authorization with JWT tokens in Apache CXF
JSON Web Tokens (JWT) have been covered extensively on this blog (for example here). In this post we will cover how JWT tokens can be used for authorization when sent to a JAX-RS web service in Apache CXF. In particular, we will show how Apache CXF 3.3.0 supports claims based access control with JWT tokens.
1) JWT with RBAC
JWT tokens can be used for the purpose of authentication in a web service context, by verifying the signature on the token and taking the "sub" claim as the authenticated principal. This assumes no proof of possession of the token, something we will revisit in a future blog post. Once this is done we have the option of performing an authorization check on the authenticated principal. This can be done easily via RBAC by using a claim in the token to represent a role.
Apache CXF has a SimpleAuthorizingInterceptor class, which can map web service operations to role names. If the authenticated principal is not associated with the role that is required to access the operation, then an exception is thrown. Here is an example of how to configure a JAX-RS web service in CXF with the SimpleAuthorizingInterceptor for JWT:
Here the JwtAuthenticationFilter has been configured with a "roleClaim" property of "role". It then extracts the configured claim from the authenticated token and uses it for the RBAC authorization decision. To see this functionality in action, look at the corresponding test-case in my github repo.
2) JWT with CBAC
Since CXF 3.3.0, we can also use the Claims annotations in CXF (that previously only worked with SAML tokens) to perform authorization checks on requests that contain JWT tokens. This allows us to specify more fine-grained authorization requirements on the token, as opposed to the RBAC approach above. For example, we can annotate our service endpoint as follows:
Here we can see a "role" claim is required which must match either the value "boss" or "ceo". We can enable claims based authorization by adding the ClaimsAuthorizingFilter as a provider of the endpoint, with the "securedObject" parameter being the service implementation:
We can specify multiple claims annotations and combine them in different ways, please see the CXF webpage for more information. To see this functionality in action, look at the corresponding test-case in my github repo.
1) JWT with RBAC
JWT tokens can be used for the purpose of authentication in a web service context, by verifying the signature on the token and taking the "sub" claim as the authenticated principal. This assumes no proof of possession of the token, something we will revisit in a future blog post. Once this is done we have the option of performing an authorization check on the authenticated principal. This can be done easily via RBAC by using a claim in the token to represent a role.
Apache CXF has a SimpleAuthorizingInterceptor class, which can map web service operations to role names. If the authenticated principal is not associated with the role that is required to access the operation, then an exception is thrown. Here is an example of how to configure a JAX-RS web service in CXF with the SimpleAuthorizingInterceptor for JWT:
Here the JwtAuthenticationFilter has been configured with a "roleClaim" property of "role". It then extracts the configured claim from the authenticated token and uses it for the RBAC authorization decision. To see this functionality in action, look at the corresponding test-case in my github repo.
2) JWT with CBAC
Since CXF 3.3.0, we can also use the Claims annotations in CXF (that previously only worked with SAML tokens) to perform authorization checks on requests that contain JWT tokens. This allows us to specify more fine-grained authorization requirements on the token, as opposed to the RBAC approach above. For example, we can annotate our service endpoint as follows:
Here we can see a "role" claim is required which must match either the value "boss" or "ceo". We can enable claims based authorization by adding the ClaimsAuthorizingFilter as a provider of the endpoint, with the "securedObject" parameter being the service implementation:
We can specify multiple claims annotations and combine them in different ways, please see the CXF webpage for more information. To see this functionality in action, look at the corresponding test-case in my github repo.
Tuesday, February 12, 2019
Deploying an Apache Camel route to Apache Karaf
In the previous blog post, we showed how to use Apache Camel to query an Apache Kafka broker, which is secured using kerberos. In this post, we will build on the previous blog post by showing how to deploy our Camel route to Apache Karaf. Karaf is an application runtime container that makes it incredibly easy to deploy simple applications via its "hot deploy" feature. As always, there are a few slightly tricky considerations when using kerberos, which is the purpose of this post.
As a pre-requisite to this article, please follow the previous blog post to set up Apache Kafka using kerberos, and test that the Camel route can retrieve from the topic we created successfully.
1) Configuring the Kerberos JAAS Login Module in Karaf
Download and extract the latest version of the Apache Karaf runtime (4.2.3 was used in this post). Before starting Karaf, we need to pass through a system property pointing to the krb5.conf file created in our Kerby KDC. This step is not necessary if you are using the standard location in the filesystem for krb5.conf. Open 'bin/karaf' and add the following to the list of system properties:
Recall that our Camel route needs to configure a JAAS LoginModule for Kerberos. In the example given in the previous post, this was configured by setting the Java System property "java.security.auth.login.config" to point to the JAAS configuration file. We don't want to do that with Karaf, as otherwise we will end up overriding the other JAAS LoginModules that are installed.
Instead, we will take advantage of Karaf's "hot deploy" feature to add the Kerberos Login Module we need to Karaf. Drop the following blueprint XML file into Karaf's deploy directory, changing the keytab location with the correct path to the keytab file:
For Karaf to pick this up, we need to register the blueprint feature via "feature:install aries-blueprint". Now we should see our LoginModule configured via "jaas:realm-list":
2) Configuring the Camel route in Karaf
Next we will hot deploy our Camel route as a blueprint file in Karaf. Copy the following file into the deploy directory:
Then we need to install a few dependencies in Karaf. Add the Camel repo via "repo-add camel 2.23.1", and install the relevant Camel dependencies via: "feature:install camel camel-kafka". Our Camel route should then automatically start, and will retrieve the messages from the Kafka topic and write them to the filesystem, as configured in the route. The message payload and headers are logged in "data/log/karaf.log".
As a pre-requisite to this article, please follow the previous blog post to set up Apache Kafka using kerberos, and test that the Camel route can retrieve from the topic we created successfully.
1) Configuring the Kerberos JAAS Login Module in Karaf
Download and extract the latest version of the Apache Karaf runtime (4.2.3 was used in this post). Before starting Karaf, we need to pass through a system property pointing to the krb5.conf file created in our Kerby KDC. This step is not necessary if you are using the standard location in the filesystem for krb5.conf. Open 'bin/karaf' and add the following to the list of system properties:
- -Djava.security.krb5.conf=/path.to.kerby.project/target/krb5.conf \
Recall that our Camel route needs to configure a JAAS LoginModule for Kerberos. In the example given in the previous post, this was configured by setting the Java System property "java.security.auth.login.config" to point to the JAAS configuration file. We don't want to do that with Karaf, as otherwise we will end up overriding the other JAAS LoginModules that are installed.
Instead, we will take advantage of Karaf's "hot deploy" feature to add the Kerberos Login Module we need to Karaf. Drop the following blueprint XML file into Karaf's deploy directory, changing the keytab location with the correct path to the keytab file:
For Karaf to pick this up, we need to register the blueprint feature via "feature:install aries-blueprint". Now we should see our LoginModule configured via "jaas:realm-list":
2) Configuring the Camel route in Karaf
Next we will hot deploy our Camel route as a blueprint file in Karaf. Copy the following file into the deploy directory:
Then we need to install a few dependencies in Karaf. Add the Camel repo via "repo-add camel 2.23.1", and install the relevant Camel dependencies via: "feature:install camel camel-kafka". Our Camel route should then automatically start, and will retrieve the messages from the Kafka topic and write them to the filesystem, as configured in the route. The message payload and headers are logged in "data/log/karaf.log".
Thursday, February 7, 2019
Using the Apache Camel Kafka component with Kerberos
Apache Camel is a well-known integration framework available at the Apache Software Foundation. It comes with a huge number of components to integrate with pretty much anything you can think of. Naturally, it has a dedicated component to communicate with the popular Apache Kafka project. In this blog entry, we'll show first how to use Apache Camel as a consumer for a Kafka topic. Then we will show how to configure things when we are securing the Kafka broker with kerberos, something that often causes problems.
1) Setting up Apache Kafka
First let's set up Apache Kafka. Download and install it (this blog post uses Kafka 2.0.0), and then start up Zookeeper and the broker, as well as creating a "test" topic and a producer for that topic as follows:
2) Consuming from Kafka using Apache Camel
Now we'll look at how to set up Apache Camel to consume from Kafka. I put a project up on github here for this purpose. The Camel route is defined in Spring, and uses the Camel Kafka component to retrieve messages from the broker, and to write them out to the target/results folder:
Simply run "mvn clean install" and observe the logs indicating that Camel has retrieved the messages you put into the topic with the producer above. Then check "target/results" to see the files containing the message bodies.
3) Securing Apache Kafka with Kerberos
So far so good. Now let's look at securing the Kafka broker using kerberos. I wrote a previous blog post to show how to use Apache Kerby as a KDC with Kafka, so please follow the steps outlined here, skipping the parts about configuring the consumer.
4) Consuming from Kafka using Apache Camel and Kerberos
To make our Camel route work with Kafka and Kerberos, a few changes are required. Just as we did for the Kafka producer, we need to set the "java.security.auth.login.config" and "java.security.krb5.conf" system properties for Camel. You can do this in the example by editing the "pom.xml" and adding something like this under "systemPropertyVariables" of the surefire configuration:
1) Setting up Apache Kafka
First let's set up Apache Kafka. Download and install it (this blog post uses Kafka 2.0.0), and then start up Zookeeper and the broker, as well as creating a "test" topic and a producer for that topic as follows:
- bin/zookeeper-server-start.sh config/zookeeper.properties
- bin/kafka-server-start.sh config/server.properties
- bin/kafka-topics.sh --create --zookeeper localhost:2181 --replication-factor 1 --partitions 1 --topic test
- bin/kafka-console-producer.sh --broker-list localhost:9092 --topic test --producer.config config/producer.properties
2) Consuming from Kafka using Apache Camel
Now we'll look at how to set up Apache Camel to consume from Kafka. I put a project up on github here for this purpose. The Camel route is defined in Spring, and uses the Camel Kafka component to retrieve messages from the broker, and to write them out to the target/results folder:
Simply run "mvn clean install" and observe the logs indicating that Camel has retrieved the messages you put into the topic with the producer above. Then check "target/results" to see the files containing the message bodies.
3) Securing Apache Kafka with Kerberos
So far so good. Now let's look at securing the Kafka broker using kerberos. I wrote a previous blog post to show how to use Apache Kerby as a KDC with Kafka, so please follow the steps outlined here, skipping the parts about configuring the consumer.
4) Consuming from Kafka using Apache Camel and Kerberos
To make our Camel route work with Kafka and Kerberos, a few changes are required. Just as we did for the Kafka producer, we need to set the "java.security.auth.login.config" and "java.security.krb5.conf" system properties for Camel. You can do this in the example by editing the "pom.xml" and adding something like this under "systemPropertyVariables" of the surefire configuration:
- <java.security.auth.login.config>/path.to.kafka.project/config/client.jaas</java.security.auth.login.config
- <java.security.krb5.conf>/path.to.kerby.project/target/krb5.conf</java.security.krb5.conf>
- &saslKerberosServiceName=kafka&securityProtocol=SASL_PLAINTEXT
Wednesday, February 6, 2019
Validating kerberos tokens from different realms in Apache CXF
We've covered on this blog before how to configure an Apache CXF service to validate kerberos tokens. However, what if we have a use-case where we want to have multiple endpoints validate kerberos tokens that are in different realms? As Java uses system properties to configure kerberos, things can get a bit tricky if we want to co-locate the services in the same JVM. In this article we'll show how it's done.
1) The test scenario
The scenario is that we have two KDCs. The first KDC has realm "realma.apache.org", with users "alice" and "bob/service.realma.apache.org". The second KDC has realm "realmb.apache.org", with users "carol" and "dave/service.realmb.apache.org". We have a single service with two different endpoints - one which will authenticate users in "realma.apache.org", and the second that will authenticate users in "realmb.apache.org". Both endpoints have keytabs that we have exported from the KDC for "bob" and "dave".
2) Kerberos configuration
Both endpoints have to share the same Kerberos configuration, due to the fact that Java uses system properties to set up JAAS with the Krb5LoginModule. We need to set the following system properties:
Here we have two entries for "bob" and "dave", each pointing to a keytab file. Note that the principal contains the realm name. This is important as we have no default_realm in the krb5.conf file. The krb5.conf file looks like this:
Here we configure how to reach both KDCs for our different realms.
3) Service configuration
Next, we'll look at how to configure the services. We will show how it's done for a JAX-WS service, but similar configuration exists for JAX-RS. The client will pass the kerberos token in a BinarySecurityToken security header in the message, according to the WS-Security specs. We'll assume the service is using a WS-SecurityPolicy that requires a kerberos token (for more details see here). Here is a sample spring configuration for an endpoint for "dave":
We have a JAX-WS endpoint with a "ws-security.bst.validator" property which points to a KerberosTokenValidator instance. This tells CXF to process a received BinarySecurityToken with the KerberosTokenValidator.
The KerberosTokenValidator is configured with a CallbackHandler implementation, to supply a username and password (see here for a sample implementation). Note that this is not required normally when we have a keytab file, but it appears to be required when we do not define a default realm. The KerberosTokenValidator instance also defines the JAAS context name, as well as the fully qualified principal name. As this is in service name form, we have to set the property "usernameServiceNameForm" to "true" as well.
If we set up the endpoint for "bob" with similar configuration, then our krb5.conf doesn't need the "default_realm" property and we can successfully validate tickets for both realms.
1) The test scenario
The scenario is that we have two KDCs. The first KDC has realm "realma.apache.org", with users "alice" and "bob/service.realma.apache.org". The second KDC has realm "realmb.apache.org", with users "carol" and "dave/service.realmb.apache.org". We have a single service with two different endpoints - one which will authenticate users in "realma.apache.org", and the second that will authenticate users in "realmb.apache.org". Both endpoints have keytabs that we have exported from the KDC for "bob" and "dave".
2) Kerberos configuration
Both endpoints have to share the same Kerberos configuration, due to the fact that Java uses system properties to set up JAAS with the Krb5LoginModule. We need to set the following system properties:
- java.security.auth.login.config - The path to the JAAS configuration file for the Krb5LoginModule
- java.security.krb5.conf - The path to the krb5.conf kerberos configuration file
Here we have two entries for "bob" and "dave", each pointing to a keytab file. Note that the principal contains the realm name. This is important as we have no default_realm in the krb5.conf file. The krb5.conf file looks like this:
Here we configure how to reach both KDCs for our different realms.
3) Service configuration
Next, we'll look at how to configure the services. We will show how it's done for a JAX-WS service, but similar configuration exists for JAX-RS. The client will pass the kerberos token in a BinarySecurityToken security header in the message, according to the WS-Security specs. We'll assume the service is using a WS-SecurityPolicy that requires a kerberos token (for more details see here). Here is a sample spring configuration for an endpoint for "dave":
We have a JAX-WS endpoint with a "ws-security.bst.validator" property which points to a KerberosTokenValidator instance. This tells CXF to process a received BinarySecurityToken with the KerberosTokenValidator.
The KerberosTokenValidator is configured with a CallbackHandler implementation, to supply a username and password (see here for a sample implementation). Note that this is not required normally when we have a keytab file, but it appears to be required when we do not define a default realm. The KerberosTokenValidator instance also defines the JAAS context name, as well as the fully qualified principal name. As this is in service name form, we have to set the property "usernameServiceNameForm" to "true" as well.
If we set up the endpoint for "bob" with similar configuration, then our krb5.conf doesn't need the "default_realm" property and we can successfully validate tickets for both realms.
Subscribe to:
Posts (Atom)