Directly Accessing Keycloak’s Registration Page

We can directly accessing Keycloak’s Registration Page without have to go to the Login Page first, and it is quite simple. Here is the URL required to have that condition,

http://localhost:8080/realms/PowerRanger/protocol/openid-connect/registrations?
       client_id=my-client-id&
       redirect_uri=redhat.com&
       response_type=code&
       scope=openid

This are achieve by using Keycloak version 17.

Quarkus, SmallRye, and Retry Mechanism

Quarkus provide a convenient library when connecting to an unreliable third party external system, and that is SmallRye Fault Tolerance. In this sample, we are trying to simulate a connection to an external website, which is reqres.in, while creating a random IOException.

Based on above scenario, we will try to retry the connection when we arent able to connect to our backend services, but retries will happen at most 4times and only on specific defined exceptions.

So lets start with a regular maven 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>quarkus-retry</artifactId>
    <version>1.0-SNAPSHOT</version>

    <properties>
        <maven.compiler.source>11</maven.compiler.source>
        <maven.compiler.target>11</maven.compiler.target>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>

        <skipITs>true</skipITs>
        <surefire-plugin.version>3.0.0-M7</surefire-plugin.version>
        <compiler-plugin.version>3.10.1</compiler-plugin.version>

        <!-- quarkus -->
        <quarkus.platform.artifact-id>quarkus-bom</quarkus.platform.artifact-id>
        <quarkus.platform.group-id>io.quarkus.platform</quarkus.platform.group-id>
        <quarkus.platform.version>2.16.6.Final</quarkus.platform.version>
    </properties>

    <dependencyManagement>
        <dependencies>
            <dependency>
                <groupId>${quarkus.platform.group-id}</groupId>
                <artifactId>${quarkus.platform.artifact-id}</artifactId>
                <version>${quarkus.platform.version}</version>
                <type>pom</type>
                <scope>import</scope>
            </dependency>
        </dependencies>
    </dependencyManagement>

    <dependencies>
        <dependency>
            <groupId>io.quarkus</groupId>
            <artifactId>quarkus-arc</artifactId>
        </dependency>
        <dependency>
            <groupId>io.quarkus</groupId>
            <artifactId>quarkus-resteasy-jackson</artifactId>
        </dependency>
        <dependency>
            <groupId>io.quarkus</groupId>
            <artifactId>quarkus-resteasy</artifactId>
        </dependency>

        <dependency>
            <groupId>io.quarkus</groupId>
            <artifactId>quarkus-smallrye-fault-tolerance</artifactId>
        </dependency>
        <dependency>
            <groupId>io.quarkus</groupId>
            <artifactId>quarkus-rest-client</artifactId>
        </dependency>

        <dependency>
            <groupId>io.quarkus</groupId>
            <artifactId>quarkus-junit5</artifactId>
            <scope>test</scope>
        </dependency>
        <dependency>
            <groupId>io.rest-assured</groupId>
            <artifactId>rest-assured</artifactId>
            <scope>test</scope>
        </dependency>
    </dependencies>

    <build>
        <plugins>
            <plugin>
                <groupId>${quarkus.platform.group-id}</groupId>
                <artifactId>quarkus-maven-plugin</artifactId>
                <version>${quarkus.platform.version}</version>
                <extensions>true</extensions>
                <executions>
                    <execution>
                        <goals>
                            <goal>build</goal>
                            <goal>generate-code</goal>
                            <goal>generate-code-tests</goal>
                        </goals>
                    </execution>
                </executions>
            </plugin>
            <plugin>
                <artifactId>maven-compiler-plugin</artifactId>
                <version>${compiler-plugin.version}</version>
                <configuration>
                    <compilerArgs>
                        <arg>-parameters</arg>
                    </compilerArgs>
                </configuration>
            </plugin>
            <plugin>
                <artifactId>maven-surefire-plugin</artifactId>
                <version>${surefire-plugin.version}</version>
                <configuration>
                    <systemPropertyVariables>
                        <java.util.logging.manager>org.jboss.logmanager.LogManager</java.util.logging.manager>
                        <maven.home>${maven.home}</maven.home>
                    </systemPropertyVariables>
                </configuration>
            </plugin>
            <plugin>
                <artifactId>maven-failsafe-plugin</artifactId>
                <version>${surefire-plugin.version}</version>
                <executions>
                    <execution>
                        <goals>
                            <goal>integration-test</goal>
                            <goal>verify</goal>
                        </goals>
                        <configuration>
                            <systemPropertyVariables>
                                <native.image.path>${project.build.directory}/${project.build.finalName}-runner</native.image.path>
                                <java.util.logging.manager>org.jboss.logmanager.LogManager</java.util.logging.manager>
                                <maven.home>${maven.home}</maven.home>
                            </systemPropertyVariables>
                        </configuration>
                    </execution>
                </executions>
            </plugin>
        </plugins>
    </build>
    <profiles>
        <profile>
            <id>native</id>
            <activation>
                <property>
                    <name>native</name>
                </property>
            </activation>
            <properties>
                <skipITs>false</skipITs>
                <quarkus.package.type>native</quarkus.package.type>
            </properties>
        </profile>
    </profiles>

</project>

And create application.properties to put all apps configuration there

# default
quarkus.http.port=8080
quarkus.log.level=INFO
quarkus.log.category."com.edw".level=DEBUG

# rest client
com.edw.client.UserRestClient/mp-rest/url=https://reqres.in/

Next is to create a RestClient that will do a rest api call to an external 3rd party

@ApplicationScoped
@RegisterRestClient
@Path("/api")
public interface UserRestClient {
    @GET
    @Path("/users/{id}")
    Users get(@PathParam("id") Integer id);
}

And the create a Service class that will call the RestClient. In this class we are simulating an Exception randomly.

@ApplicationScoped
public class UserService {

    private Logger logger = LoggerFactory.getLogger(this.getClass().getName());

    @Inject
    @RestClient
    UserRestClient userRestClient;

    public Users getUser(Integer id) throws IOException {
        Random random = new Random();
        if(random.nextBoolean()) {
            logger.debug("==== simulate random exception ====");
            throw new IOException();
        }

        return userRestClient.get(id);
    }
}

And last, is our Controller class,

@Path("/")
public class HelloWorldController {

    private Logger logger = LoggerFactory.getLogger(this.getClass().getName());
    @Inject
    UserService userService;

    @GET
    @Path("/")
    @Produces(MediaType.APPLICATION_JSON)
    @Consumes(MediaType.APPLICATION_JSON)
    public Response index() {
        return Response
                .status(200)
                .entity(new Hello("world"))
                .build();
    }

    @GET
    @Path("/user/{id}")
    @Produces(MediaType.APPLICATION_JSON)
    @Retry(maxRetries = 1, retryOn= IOException.class)
    @Fallback(fallbackMethod = "getEmptyUser")
    public Response getUser(@PathParam("id") Integer id) throws IOException {
        Users users = userService.getUser(id);
        return Response
                .status(200)
                .entity(users)
                .build();
    }

    public Response getEmptyUser(Integer id) throws IOException {
        logger.debug("==== giving default response ====");
        return Response
                .status(200)
                .entity(new Users())
                .build();
    }
}

We can run our Quarkus project by using below command,

$ mvn quarkus:dev

And do some rest api call to it, a successful response would looks like this

$ curl -kv http://localhost:8080/user/2
*   Trying ::1:8080...
* TCP_NODELAY set
*   Trying 127.0.0.1:8080...
* TCP_NODELAY set
* Connected to localhost (127.0.0.1) port 8080 (#0)
> GET /user/2 HTTP/1.1
> Host: localhost:8080
> User-Agent: curl/7.65.0
> Accept: */*
>
* Mark bundle as not supporting multiuse
< HTTP/1.1 200 OK
< Content-Type: application/json
< content-length: 280
<
* Connection #0 to host localhost left intact
{"data":{"id":2,"email":"janet.weaver@reqres.in","first_name":"Janet","last_name":"Weaver","avatar":"https://reqres.in/img/faces/2-image.jpg"},"support":{"url":"https://reqres.in/#support-heading","text":"To keep ReqRes free, contributions towards server costs are appreciated!"}}

while a failed one will give below response,

$ curl -kv http://localhost:8080/user/2
*   Trying ::1:8080...
* TCP_NODELAY set
*   Trying 127.0.0.1:8080...
* TCP_NODELAY set
* Connected to localhost (127.0.0.1) port 8080 (#0)
> GET /user/2 HTTP/1.1
> Host: localhost:8080
> User-Agent: curl/7.65.0
> Accept: */*
>
* Mark bundle as not supporting multiuse
< HTTP/1.1 200 OK
< Content-Type: application/json
< content-length: 28
<
* Connection #0 to host localhost left intact
{"data":null,"support":null} 

Code for this post can be found on below link,

https://github.com/edwin/quarkus-smallrye-retry

Java, SecureRandom, and Openshift

Had one unique case where generating a SecureRandom in Java is very very slow, the thing is that this never happens on a regular VM deployment, and only happens in a Pod deployment in Openshift 4.

This is original sample code that is slow,

    @GetMapping(path = "/secure-random")
    public HashMap secureRandom() {
        SecureRandom secureRandom = new SecureRandom();
        secureRandom.setSeed(secureRandom.generateSeed(24));
        return new HashMap(){{
            put("random-value", String.format("%06d", secureRandom.nextInt(1000000)));
        }};
    }

The reason why it is slow is because SecureRandom is relying on the OS random generator which is relying on noise. And it seems that for our case, we are lacking of noise to make a good entropy therefore we arent able to generate a SecureRandom at all.

Our workaround for this is by using a Pseudo Random Number Generator (PRNG) from Java, and not relying on the OS at all. For this example, im using “SHA1PRNG”.

    @GetMapping(path = "/secure-random-new")
    public HashMap secureRandomNew() throws Exception {
        SecureRandom secureRandom = SecureRandom.getInstance("SHA1PRNG");
        secureRandom.setSeed(secureRandom.generateSeed(24));
        return new HashMap(){{
            put("random-value", String.format("%06d", secureRandom.nextInt(1000000)));
        }};
    }

Hope it helps.

“No name matching” and “No subject alternative names present” when Connecting to a Secure Broker on Red Hat AMQ Broker

I had this error when using Artemis to connect to a secure broker on Openshift 4.10. AMQ Broker version is 7.10 and being installed by using Operator.

Caused by: java.security.cert.CertificateException: No name matching enterprise-rhamq-ss-0.enterprise-rhamq-hdls-svc.enterprise-rhamq.svc.cluster.local found
        at java.base/sun.security.util.HostnameChecker.matchDNS(HostnameChecker.java:234) [java.base:]
        at java.base/sun.security.util.HostnameChecker.match(HostnameChecker.java:103) [java.base:]
        at java.base/sun.security.ssl.X509TrustManagerImpl.checkIdentity(X509TrustManagerImpl.java:458) [java.base:]
        at java.base/sun.security.ssl.X509TrustManagerImpl.checkIdentity(X509TrustManagerImpl.java:418) [java.base:]
        at java.base/sun.security.ssl.X509TrustManagerImpl.checkTrusted(X509TrustManagerImpl.java:292) [java.base:]
        at java.base/sun.security.ssl.X509TrustManagerImpl.checkServerTrusted(X509TrustManagerImpl.java:144) [java.base:]
        at java.base/sun.security.ssl.CertificateMessage$T13CertificateConsumer.checkServerCerts(CertificateMessage.java:1335) [java.base:]
        ... 28 more

And when accessing with a direct IP, it would give below error

Caused by: java.security.cert.CertificateException: No subject alternative names present
        at java.base/sun.security.util.HostnameChecker.matchIP(HostnameChecker.java:142) [java.base:]
        at java.base/sun.security.util.HostnameChecker.match(HostnameChecker.java:101) [java.base:]
        at java.base/sun.security.ssl.X509TrustManagerImpl.checkIdentity(X509TrustManagerImpl.java:458) [java.base:]
        at java.base/sun.security.ssl.X509TrustManagerImpl.checkIdentity(X509TrustManagerImpl.java:432) [java.base:]
        at java.base/sun.security.ssl.X509TrustManagerImpl.checkTrusted(X509TrustManagerImpl.java:292) [java.base:]
        at java.base/sun.security.ssl.X509TrustManagerImpl.checkServerTrusted(X509TrustManagerImpl.java:144) [java.base:]
        at java.base/sun.security.ssl.CertificateMessage$T13CertificateConsumer.checkServerCerts(CertificateMessage.java:1335) [java.base:]
        ... 28 more

Apparently it is due to Java certificate validation. Workaround is quite easy, just adding below configuration on Artemis URL and it should works,

verifyHost=false

How to Create a Rule Engine with Drools and Quarkus

There are multiple ways of creating a rule engine, some prefer a DMN model and some like to use DRL (Drools) files. On this writing, we are trying to see how can we leverage DRL files to validate a user’s risk profile based on some variables, and run it on top of Quarkus.

So lets start with a Maven 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>quarkus-drl</artifactId>
    <version>1.0-SNAPSHOT</version>

    <properties>
        <surefire-plugin.version>3.0.0-M7</surefire-plugin.version>
        <maven.compiler.target>11</maven.compiler.target>
        <maven.compiler.source>11</maven.compiler.source>
        <quarkus.platform.artifact-id>quarkus-bom</quarkus.platform.artifact-id>
        <quarkus.platform.group-id>io.quarkus</quarkus.platform.group-id>
        <quarkus.platform.version>2.15.3.Final</quarkus.platform.version>
        <kogito.platform.group-id>org.kie.kogito</kogito.platform.group-id>
        <kogito.platform.artifact-id>kogito-quarkus-bom</kogito.platform.artifact-id>
        <kogito.platform.version>1.24.0.Final</kogito.platform.version>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
        <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
    </properties>

    <dependencyManagement>
        <dependencies>
            <dependency>
                <groupId>org.antlr</groupId>
                <artifactId>antlr4-runtime</artifactId>
                <version>4.9.2</version>
            </dependency>
            <dependency>
                <groupId>${quarkus.platform.group-id}</groupId>
                <artifactId>${quarkus.platform.artifact-id}</artifactId>
                <version>${quarkus.platform.version}</version>
                <type>pom</type>
                <scope>import</scope>
            </dependency>
            <dependency>
                <groupId>${kogito.platform.group-id}</groupId>
                <artifactId>${kogito.platform.artifact-id}</artifactId>
                <version>${kogito.platform.version}</version>
                <type>pom</type>
                <scope>import</scope>
            </dependency>
        </dependencies>
    </dependencyManagement>

    <dependencies>
        <dependency>
            <groupId>io.quarkus</groupId>
            <artifactId>quarkus-resteasy-reactive-jackson</artifactId>
        </dependency>
        <dependency>
            <groupId>org.kie.kogito</groupId>
            <artifactId>kogito-quarkus-rules</artifactId>
        </dependency>

        <!-- tests -->
        <dependency>
            <groupId>io.quarkus</groupId>
            <artifactId>quarkus-junit5</artifactId>
            <scope>test</scope>
        </dependency>
        <dependency>
            <groupId>io.rest-assured</groupId>
            <artifactId>rest-assured</artifactId>
            <scope>test</scope>
        </dependency>
    </dependencies>
......
</project>

Next is creating a Java model, this is going to be the base model for variables that are going to contribute on our rule engine.

package com.edw.model;

public class Loan {

    private Integer age;
    private Double salary;

    private String risk;

    public Loan() {
    }

    public Loan(Integer age, Double salary, String risk) {
        this.age = age;
        this.salary = salary;
        this.risk = risk;
    }
	
	// other setter and getter

}

After that, we need to create a Rule Unit class. The purpose of it is to bind JSON request to a Java class, which can be modified by using our Rule Engine.

package com.edw.queries;

import com.edw.model.Loan;
import org.kie.kogito.rules.DataSource;
import org.kie.kogito.rules.DataStore;
import org.kie.kogito.rules.RuleUnitData;

public class LoanUnitData  implements RuleUnitData {
    private DataStore<Loan> loan;

    public LoanUnitData() {
        this(DataSource.createStore());
    }

    public LoanUnitData(DataStore<Loan> loan) {
        this.loan = loan;
    }

    public DataStore<Loan> getLoan() {
        return loan;
    }

    public void setLoan(DataStore<Loan> loan) {
        this.loan = loan;
    }
}

Next is to create our DRL file where we can write our whole Rule Engine there, and please make sure that it needs to be on the same package as our RuleUnit class.

package com.edw.queries;

unit LoanUnitData;

import com.edw.model.Loan;

rule HighRiskCustomerEverythingBelowMinimum when
   $L: /loan[age <= 20, salary <= 1000]
then
   modify($L) { setRisk("High") };
end

rule MediumRiskCustomerAgeBelowMinimum when
   $L: /loan[age <= 20, salary > 1000]
then
   modify($L) { setRisk("Medium") };
end

rule MediumRiskCustomerSalaryBelowMinimum when
   $L: /loan[age > 20, salary <= 1000]
then
   modify($L) { setRisk("Medium") };
end

rule LowRiskCustomerSalaryEverythingAboveMinimum when
   $L: /loan[age > 20, salary > 1000]
then
   modify($L) { setRisk("Low") };
end


query GetRisk
   $L: /loan
end

To validate whether our code works well or not, we can use below Unit Test

package com.edw;

import io.quarkus.test.junit.QuarkusTest;
import io.restassured.http.ContentType;
import org.junit.jupiter.api.Test;

import static io.restassured.RestAssured.given;
import static org.hamcrest.Matchers.hasItem;
import static org.hamcrest.Matchers.hasItems;

@QuarkusTest
public class LoanTest {

    @Test
    public void testHighRisk() {
        given()
                .body("{\"loan\" : [{\"age\":17, \"salary\":900}]}")
                .contentType(ContentType.JSON)
                .log().all()
                .when()
                .post("/get-risk")
                .then()
                .statusCode(200).log().all()
                .body("risk", hasItem("High"));
    }
}

Or we can simply using a CURL to validate this,

$ curl  -X POST http://localhost:8080/get-risk  \
    -H 'content-type: application/json'  \
    -H 'accept: application/json'   \
    -d '{"loan" : [{"age":17, "salary":900}, {"age":39,"salary":1900.0}]}'

[{"age":39,"salary":1900.0,"risk":"Low"},{"age":17,"salary":900.0,"risk":"High"}]    

Code for this article can be found on below Git repository,

https://github.com/edwin/quarkus-drl

Have fun with Drools 🙂