Java Posts

Run Multiple Containerized Keycloak Instances Behind an Apache HTTPD Proxy

On this session im trying to create a sample condition where im having a High-Availability cluster of multiple Keycloaks instances which are located behind an HTTPD Reverse Proxy. HTTPD will do a round robin request to two Keycloaks instances behind it, showing the capability of session sharing between different Keycloak instances.

Im using a containerized Keycloak image and run them by using Docker for simulating a condition of running more than one Keycloak instances with different IP.

The concept is pretty much like below image,

The first thing needed is setuping a database for this. In here im using MySQL, despite Keycloak is able to connect to different type of databases. And for this sample, database is installed on my host laptop and not using a containerized one.

CREATE USER 'keycloak'@'%' IDENTIFIED BY 'password';
CREATE DATABASE keycloak_db;

The next step is running two Keycloak instances by executing below command, in here im putting 192.168.56.1 as the ip for my host machine.

docker run -p 8081:8080 -e PROXY_ADDRESS_FORWARDING=true  \ 
	-e DB_VENDOR="mysql" -e DB_ADDR="192.168.56.1" -e DB_USER="keycloak" \ 
	-e DB_PASSWORD="password" -e DB_PORT="3306" -e DB_DATABASE="keycloak_db" \ 
	--add-host=HOST:192.168.56.1 jboss/keycloak


docker run -p 8082:8080 -e PROXY_ADDRESS_FORWARDING=true  \ 
	-e DB_VENDOR="mysql" -e DB_ADDR="192.168.56.1" -e DB_USER="keycloak" \ 
	-e DB_PASSWORD="password" -e DB_PORT="3306" -e DB_DATABASE="keycloak_db" \ 
	--add-host=HOST:192.168.56.1 jboss/keycloak

The good thing about this keycloak image is that by default it is running a standalone-ha.xml and automatically form a cluster when being run locally at the same time. This can be seen on Keycloak’s log

06:20:53,102 INFO  [org.infinispan.CLUSTER] (non-blocking-thread--p8-t4) 
	[Context=offlineClientSessions] ISPN100010: Finished rebalance with members [a629f48aafa9, 82298394e158], topology id 11
06:20:53,102 INFO  [org.infinispan.CLUSTER] (thread-35,ejb,a629f48aafa9) 
	[Context=sessions] ISPN100010: Finished rebalance with members [a629f48aafa9, 82298394e158], topology id 11
06:20:53,103 INFO  [org.infinispan.CLUSTER] (thread-30,ejb,a629f48aafa9) 
	[Context=work] ISPN100010: Finished rebalance with members [a629f48aafa9, 82298394e158], topology id 11
06:20:53,114 INFO  [org.infinispan.CLUSTER] (thread-30,ejb,a629f48aafa9) 
	[Context=loginFailures] ISPN100010: Finished rebalance with members [a629f48aafa9, 82298394e158], topology id 11
06:20:53,121 INFO  [org.infinispan.CLUSTER] (thread-31,ejb,a629f48aafa9) 
	[Context=actionTokens] ISPN100010: Finished rebalance with members [a629f48aafa9, 82298394e158], topology id 11

The last step would be creating an HTTPD setup for creating reverse proxy with a load-balancing capability. And we can achieve that by editing httpd.conf file, for this sample we are using a round-robin mechanism of lbmethod=byrequests.

<VirtualHost *:80>
	ServerName localhost
	ProxyRequests Off
	ProxyPreserveHost On
  
	<Proxy "balancer://mycluster">
		BalancerMember http://localhost:8081
		BalancerMember http://localhost:8082
	
		ProxySet lbmethod=byrequests failontimeout=on
	 
	</Proxy>
  
	ProxyPass / balancer://mycluster/
	ProxyPassReverse / balancer://mycluster/
</VirtualHost>

In order to activate loadbalancer and http proxying feature on Apache HTTPD, there are several variables that need to be unremark on httpd.conf file such as proxy_balancer_module and proxy_http_module.

Restart httpd and open browser directly, and we can see that Keycloak is working well.

We can also simulate a condition where one instance suddenly stopped to simulate a failover scenario by killing one Keycloak instance using docker kill command. Have fun with Keycloak πŸ˜€

Notes.
For a better performance, a sticky session approach is recommended compared to a round robin one.

https://www.keycloak.org/docs/latest/server_installation/#sticky-sessions

How to Handle CORS in Red Hat Process Automation Manager

One feature on Red Hat Process Automation Manager (RHPAM) is the ability to provide an API endpoint which can be accessed from multiple applications. But when we call them directly from javascript within a browser, sometimes it would shows a CORS error.

Workaround is quite easy, either we add a CORS header on RHPAM API response or change call method from a browser call into server-to-server call. For this article i would go with the first approach, and that is adding a CORS header on API that RHPAM provides.

Luckily our RHPAM deployment is on top of Openshift, and by adding below key-value parameters to RHPAM kie-server’s Deployment Config is good enough to solve CORS issue.

         - name: FILTERS
           value: "AC_ALLOW_ORIGIN,AC_ALLOW_METHODS,AC_ALLOW_HEADERS,AC_ALLOW_CREDENTIALS,AC_MAX_AGE"
         - name: AC_ALLOW_ORIGIN_FILTER_RESPONSE_HEADER_NAME
           value: "Access-Control-Allow-Origin"
         - name: AC_ALLOW_ORIGIN_FILTER_RESPONSE_HEADER_VALUE
           value: "*"
         - name: AC_ALLOW_METHODS_FILTER_RESPONSE_HEADER_NAME
           value: "Access-Control-Allow-Methods"
         - name: AC_ALLOW_METHODS_FILTER_RESPONSE_HEADER_VALUE
           value: "POST,GET,OPTIONS,PUT"
         - name: AC_ALLOW_HEADERS_FILTER_RESPONSE_HEADER_NAME
           value: "Access-Control-Allow-Headers"
         - name: AC_ALLOW_HEADERS_FILTER_RESPONSE_HEADER_VALUE
           value: "*"
         - name: AC_ALLOW_CREDENTIALS_FILTER_RESPONSE_HEADER_NAME
           value: "Access-Control-Allow-Credentials"
         - name: AC_ALLOW_CREDENTIALS_FILTER_RESPONSE_HEADER_VALUE
           value: "true"
         - name: AC_MAX_AGE_FILTER_RESPONSE_HEADER_NAME
           value: "Access-Control-Max-Age"
         - name: AC_MAX_AGE_FILTER_RESPONSE_HEADER_VALUE
           value: "86400"

How to Fix Error “Variable content was trimmed as it was too long” on RHPAM

Just yesterday i got a very unique error,

Variable content was trimmed as it was too long (more than 255 characters)

I can see that this error happens due to RHPAM saves the content of each object on database, but have a 255 character as limitation. And for this case, my object content length is more than 255 characters.

Workaround for this is we need to alter database generated by RHPAM and for example below, im changing it from 255 into 5000 characters.

alter table VariableInstanceLog modify column value varchar(5000);

alter table VariableInstanceLog modify column oldValue varchar(5000);

And need to put below parameter runtime,

-Dorg.jbpm.var.log.length=5000

For a RHPAM deployment on Openshift, we can embed below parameters on RHPAM DeploymentConfig,

	- name: JAVA_OPTS_APPEND
	  value: '-Dorg.jbpm.var.log.length=5000'

Restart RHPAM, and we can see now RHPAM can handle object with length up to 5000 characters.

Integrating Spring Boot Login with Keycloak or Red Hat Single Sign On

When we are managing many applications, one of the most painful part is managing its user and access right. Because usually different applications have their own user management, and sometimes each user have different credentials between multiple applications.

We can solve this problem by having a one point user management where other application can use this tools for managing their user authentication and authorization. This is where Red Hat Single Sign On (or its opensource product, Keycloak) can comes in handy. It provides an end to end user management lifecyle, from activating a new user, managing them, assigning their access right until deactivating them. On this example, we’ll start with a simple login page by using Keycloak, and how other application (in this example is a Spring Boot app) is connecting to it.

First we need to create a java project with below pom file, im using keycloak-adapter bom and keycloak-spring-boot-starter library for this.

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>

    <groupId>com.edw</groupId>
    <artifactId>spring-boot-and-rhsso-otp</artifactId>
    <version>1.0-SNAPSHOT</version>

    <packaging>war</packaging>

    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>2.2.6.RELEASE</version>
        <relativePath/>
    </parent>

    <dependencyManagement>
        <dependencies>
            <dependency>
                <groupId>org.keycloak.bom</groupId>
                <artifactId>keycloak-adapter-bom</artifactId>
                <version>9.0.2</version>
                <type>pom</type>
                <scope>import</scope>
            </dependency>
        </dependencies>
    </dependencyManagement>

    <dependencies>
        <dependency>
            <groupId>org.keycloak</groupId>
            <artifactId>keycloak-spring-boot-starter</artifactId>
            <version>9.0.2</version>
        </dependency>

        <dependency>
            <groupId>javax.servlet</groupId>
            <artifactId>jstl</artifactId>
        </dependency>

        <dependency>
            <groupId>org.apache.tomcat.embed</groupId>
            <artifactId>tomcat-embed-jasper</artifactId>
            <scope>provided</scope>
        </dependency>

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-devtools</artifactId>
            <scope>runtime</scope>
            <optional>true</optional>
        </dependency>
    </dependencies>

    <build>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
            </plugin>
        </plugins>
    </build>
</project>

And perhaps the most important part in this project is, the application.properties. In here, we are putting our Keycloak’s url, realm name, client name, and secret. Also we are defining that only user with admin role can access a URL with /admin/ pattern.

### server port
server.port=8080
spring.application.name=Spring Boot with RHSSO Login

### rhsso configuration
keycloak.auth-server-url=https://rhsso/auth/
keycloak.realm=my-realm
keycloak.resource=my-client
keycloak.public-client=false
keycloak.bearer-only=false
keycloak.principal-attribute=preferred_username
keycloak.credentials.secret=11111111-1111-1111-1111-111111111111

### spring boot ui configuration
spring.mvc.view.prefix=/WEB-INF/jsp/
spring.mvc.view.suffix=.jsp

### authorization
keycloak.security-constraints[0].authRoles[0]=admin
keycloak.security-constraints[0].securityCollections[0].patterns[0]=/admin/*

Next is we need to create a user and role on Red Hat SSO,

After that, we need to create a client and its password,

And put those corresponding values inside application.properties.

We can test whether configuration works well or not by directly accessing to admin page (/admin/index). A successful configuration would prevent an unauthorized user from accessing admin page by showing a Keycloak login page. Admin page only accessible once a user has successfully login thru Keycloak or Red Hat Single Sign On.

Full code can be downloaded on my github page,

https://github.com/edwin/spring-boot-and-rhsso

Have fun with RHSSO and Keycloak (H)

Tracing Red Hat Fuse Transaction with Jaeger

Jaeger is open source software for tracing transactions between distributed services. It’s used for monitoring and troubleshooting complex microservices environments. And in this example, im trying trying to use it for tracing http request on Red Hat Fuse, or its opensource version which is Apache Camel. The purpose is to see how much time is needed for each api to take, and what kind of information comes with it.

First lets start by installing Jaeger on your local,

docker pull jaegertracing/all-in-one:1.19

docker run -d --name jaeger -e COLLECTOR_ZIPKIN_HTTP_PORT=9411 \
		-p 5775:5775/udp -p 6831:6831/udp -p 6832:6832/udp -p 5778:5778 \
		-p 16686:16686 -p 14268:14268 -p 14250:14250 -p 9411:9411 \
		jaegertracing/all-in-one:1.19

And create a java project with this pom file,

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>

    <groupId>com.edw</groupId>
    <artifactId>camel-with-jaeger</artifactId>
    <version>1.0-SNAPSHOT</version>

    <properties>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
        <fuse.version>7.2.0.fuse-720020-redhat-00001</fuse.version>
    </properties>

    <dependencyManagement>
        <dependencies>
            <dependency>
                <groupId>org.jboss.redhat-fuse</groupId>
                <artifactId>fuse-springboot-bom</artifactId>
                <version>${fuse.version}</version>
                <type>pom</type>
                <scope>import</scope>
            </dependency>
        </dependencies>
    </dependencyManagement>

    <dependencies>
        <dependency>
            <groupId>org.apache.camel</groupId>
            <artifactId>camel-spring-boot-starter</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-actuator</artifactId>
        </dependency>
        <dependency>
            <groupId>org.apache.camel</groupId>
            <artifactId>camel-http-starter</artifactId>
        </dependency>
        <dependency>
            <groupId>org.apache.camel</groupId>
            <artifactId>camel-servlet-starter</artifactId>
        </dependency>

        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-sleuth</artifactId>
            <version>2.1.3.RELEASE</version>
        </dependency>
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-zipkin</artifactId>
            <version>2.1.3.RELEASE</version>
        </dependency>

    </dependencies>

    <build>
        <pluginManagement>
            <plugins>
                <plugin>
                    <groupId>org.jboss.redhat-fuse</groupId>
                    <artifactId>spring-boot-maven-plugin</artifactId>
                    <version>${fuse.version}</version>
                </plugin>
            </plugins>
        </pluginManagement>
        <plugins>
            <plugin>
                <artifactId>maven-compiler-plugin</artifactId>
                <configuration>
                    <source>8</source>
                    <target>8</target>
                </configuration>
            </plugin>
        </plugins>
    </build>

</project>

Set connection to Jaeger tracing

# The Camel context name
camel.springboot.name=camel-with-jaeger

# enable all management endpoints
endpoints.enabled=true
management.security.enabled=false
camel.component.servlet.mapping.contextPath=/api/*
logging.level.root=info

# name for tracing
spring.application.name=Camel Hello World
spring.zipkin.base-url=http://localhost:9411/
spring.zipkin.enabled=true
spring.zipkin.sender.type=web
spring.sleuth.enabled=true
spring.sleuth.sampler.probability=1.0

Create a simple helo-world api,

<?xml version="1.0" encoding="UTF-8"?>
<rest path="/hello-world" xmlns="http://camel.apache.org/schema/spring">
    <get>
        <route>
            <setHeader headerName="Content-Type">
                <constant>application/json</constant>
            </setHeader>
            <setBody>
                <simple>{ "hello": "world" }</simple>
            </setBody>
        </route>
    </get>
</rest>

We test our newly created api by using curl command,

curl -kv http://localhost:8080/api/hello-world

And the result can be viewed directly on Jaeger dashboard


Now lets say we have other requirement such as adding a specific information on every trace result on Jaeger. We can achieve that by using Tag inside current Trace Span.

package com.edw.routes;

import org.apache.camel.Exchange;
import org.apache.camel.builder.RouteBuilder;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cloud.sleuth.Span;
import org.springframework.cloud.sleuth.Tracer;
import org.springframework.stereotype.Component;

@Component
public class HelloWorldRoute extends RouteBuilder {

    @Autowired
    private Tracer tracer;

    @Override
    public void configure() throws Exception {
        rest()
                .get("hello")
                .route()
                .setHeader(Exchange.HTTP_RESPONSE_CODE, simple("200"))
                .setHeader(Exchange.CONTENT_TYPE, simple("application/json"))
                .process(exchange -> {
                    Span span = tracer.getCurrentSpan();
                    span.tag("something", "whatever");
                    span.tag("pretending to do some queries", "select 1 from dual");
                })
                .setBody(constant("{\"world\":\"world\"}"))
                .endRest()
        ;
    }
}

The result would be displayed on every trace like this,

Full code for this tutorial can be accessed in below url,

https://github.com/edwin/fuse-with-jaeger

Have fun with Jaeger and Camel πŸ™‚