From 85159f1e87ec56d15d60c286aec9926ffee51c99 Mon Sep 17 00:00:00 2001 From: Fabio Burzigotti Date: Thu, 22 Feb 2024 22:31:29 +0100 Subject: [PATCH] [issue 140] - Adding an example that demostrates provisioning and testing a WildFly application service on Openshift, configured with the Keycloak SAML adapter layer --- examples/pom.xml | 1 + .../wildfly-keycloak-saml-adapter/README.md | 79 ++++++++ .../wildfly-keycloak-saml-adapter/pom.xml | 174 ++++++++++++++++++ .../wildfly/keycloak/saml/HelloResource.java | 17 ++ .../keycloak/saml/RestApplication.java | 8 + .../saml/ServerConfigurationResource.java | 32 ++++ ...akCapableImageBasedWildflyApplication.java | 28 +++ ...pableImageBasedWildflyApplicationTest.java | 54 ++++++ ...it.platform.launcher.TestExecutionListener | 2 + ...CapableWildflyDeploymentConfiguration.java | 20 ++ .../src/test/resources/logback.xml | 59 ++++++ .../provision/ProvisionerManagerTestCase.java | 11 +- 12 files changed, 482 insertions(+), 3 deletions(-) create mode 100644 examples/wildfly-keycloak-saml-adapter/README.md create mode 100644 examples/wildfly-keycloak-saml-adapter/pom.xml create mode 100644 examples/wildfly-keycloak-saml-adapter/src/main/java/org/jboss/intersmash/examples/wildfly/keycloak/saml/HelloResource.java create mode 100644 examples/wildfly-keycloak-saml-adapter/src/main/java/org/jboss/intersmash/examples/wildfly/keycloak/saml/RestApplication.java create mode 100644 examples/wildfly-keycloak-saml-adapter/src/main/java/org/jboss/intersmash/examples/wildfly/keycloak/saml/ServerConfigurationResource.java create mode 100644 examples/wildfly-keycloak-saml-adapter/src/test/java/org/jboss/intersmash/examples/wildfly/keycloak/saml/KeycloakCapableImageBasedWildflyApplication.java create mode 100644 examples/wildfly-keycloak-saml-adapter/src/test/java/org/jboss/intersmash/examples/wildfly/keycloak/saml/KeycloakCapableImageBasedWildflyApplicationTest.java create mode 100644 examples/wildfly-keycloak-saml-adapter/src/test/resources/META-INF/services/org.junit.platform.launcher.TestExecutionListener create mode 100644 examples/wildfly-keycloak-saml-adapter/src/test/resources/java-templates/org/jboss/intersmash/examples/wildfly/keycloak/saml/config/KeycloackCapableWildflyDeploymentConfiguration.java create mode 100644 examples/wildfly-keycloak-saml-adapter/src/test/resources/logback.xml diff --git a/examples/pom.xml b/examples/pom.xml index 5f74199f6..b860c0abb 100644 --- a/examples/pom.xml +++ b/examples/pom.xml @@ -18,6 +18,7 @@ ws-bootable-jar-example wstrust + wildfly-keycloak-saml-adapter diff --git a/examples/wildfly-keycloak-saml-adapter/README.md b/examples/wildfly-keycloak-saml-adapter/README.md new file mode 100644 index 000000000..dec8a4051 --- /dev/null +++ b/examples/wildfly-keycloak-saml-adapter/README.md @@ -0,0 +1,79 @@ +# Intersmash examples - Wildfly with Keycloak SAML Adapter Galleon Pack + +This example shows how to use Intersmash to provision and test a WildFly application on OpenShift via a s2i binary +build. + +## Overview +The application is a simple "Hello World!" Java EE application, which is executed by a WildFly instance that is +provisioned via the WildFly Maven Plugin. Such plugin allows for tailoring a WildFly server with just the +required modules and configuration enabled. + +In this example, the application project POM also demonstrates how to configure the plugin for adding the WildFly +Keycloak SAML Adapter Galleon Pack and the related `keycloak-saml` layer to the server configuration. + +There is no interoperability involved in this example, as there isn't a Keycloak server that the WildFly application +interacts with, and the test ony verifies that the server configuration contains the elements related to the +Keycloak SAML Adapter resources. + +## Test code +The test code consists of an Intersmash _application descriptor_ class, i.e. +[KeycloakCapableImageBasedWildflyApplication](../wildfly-keycloak-saml-adapter/src/test/java/org/jboss/intersmash/examples/wildfly/keycloak/saml/KeycloakCapableImageBasedWildflyApplication.java) +and an Intersmash test class, i.e. +[KeycloakCapableImageBasedWildflyApplicationTest](../wildfly-keycloak-saml-adapter/src/test/java/org/jboss/intersmash/examples/wildfly/keycloak/saml/KeycloakCapableImageBasedWildflyApplicationTest.java). + +### The _application descriptor_ +[KeycloakCapableImageBasedWildflyApplication](../wildfly-keycloak-saml-adapter/src/test/java/org/jboss/intersmash/examples/wildfly/keycloak/saml/KeycloakCapableImageBasedWildflyApplication.java) provides the information that the provisioner will use to deploy the +service on OpenShift. For instance, it can define environment variables, or secrets, and the build input. +This is the input for the s2i build that the provisioner will orchestrate on +OpenShift, in order to generate the final immutable image containing the WildFly application server and the +application deployment itself. +In our case the build input is represented by a local WildFly deployment, as generated by the WildFly Maven plugin +execution. + +#### Resolving the build input +The [templating-maven-plugin](https://www.mojohaus.org/templating-maven-plugin/) is used to have - for example - Maven +project properties injected with concrete values in Java classes at runtime. + +When built, this example uses such plugin in order to generate the +`KeycloackCapableWildflyDeploymentConfiguration` Java class from the +[KeycloackCapableWildflyDeploymentConfiguration.java](../wildfly-keycloak-saml-adapter/src/test/resources/java-templates/org/jboss/intersmash/examples/wildfly/keycloak/saml/config/KeycloackCapableWildflyDeploymentConfiguration.java) template. +This class contains the information needed to identify the built artifact properties or location, and is used by +[KeycloakCapableImageBasedWildflyApplication](../wildfly-keycloak-saml-adapter/src/test/java/org/jboss/intersmash/examples/wildfly/keycloak/saml/KeycloakCapableImageBasedWildflyApplication.java) +to initialize the build input with the `target/server` directory produced by the WildFly Maven Plugin. + +#### The _provisioner_ +The _provisioner_ is +[selected by Intersmash](../../../intersmash/provisioners/README.md#mapping-of-implemented-provisioners-), based on the +`Application` type that a given _application descriptor_ implements. + +[KeycloakCapableImageBasedWildflyApplication](../wildfly-keycloak-saml-adapter/src/test/java/org/jboss/intersmash/examples/wildfly/keycloak/saml/KeycloakCapableImageBasedWildflyApplication.java) +extends +[WildflyImageOpenShiftApplication](../../../intersmash/provisioners/src/main/java/org/jboss/intersmash/application/openshift/WildflyImageOpenShiftApplication.java) +which is provisioned by +[WildflyImageOpenShiftProvisioner](/../../../intersmash/provisioners/src/main/java/org/jboss/intersmash/provision/openshift/WildflyImageOpenShiftProvisioner.java). + +Such provisioner infers the build input runtime type in order to create the resources for either a source or binary +based s2i build to happen on OpenShift and to generate the final WildFly application image that will be fed to a +`DeploymentConfig` resource, which is eventually used for orchestrating the pods that run the actual application +service workload. + +### The test class + +[KeycloakCapableImageBasedWildflyApplicationTest](../wildfly-keycloak-saml-adapter/src/test/java/org/jboss/intersmash/examples/wildfly/keycloak/saml/KeycloakCapableImageBasedWildflyApplicationTest.java) +is a valid Intersmash test class - as per the `@Intersmash` annotation, and its lifecycle is fully managed by +Intersmash, based on the information provided by the `@Service` annotations. +The scenario consists of just one service, i.e. the one represented by the +[KeycloakCapableImageBasedWildflyApplication](../wildfly-keycloak-saml-adapter/src/test/java/org/jboss/intersmash/examples/wildfly/keycloak/saml/KeycloakCapableImageBasedWildflyApplication.java) +_application descriptor_. +It uses the `@ServiceUrl` annotation in order to let Intersmash inject the URL that exposes the application service +outside OpenShift, and which is used by one test method, which is a simple call to check that the server +configuration actually contains the resources defined by the WildFly Keycloak SAML Adapter Galleon pack. + +## Running the test + +After building the project, just issue: +```shell +mvn clean install -Pdemo -pl examples/wildfly-keycloak-saml-adapter +``` + +Or simply start the [test](../wildfly-keycloak-saml-adapter/src/test/java/org/jboss/intersmash/examples/wildfly/keycloak/saml/KeycloakCapableImageBasedWildflyApplicationTest.java) within your favourite IDE. diff --git a/examples/wildfly-keycloak-saml-adapter/pom.xml b/examples/wildfly-keycloak-saml-adapter/pom.xml new file mode 100644 index 000000000..74b2faec3 --- /dev/null +++ b/examples/wildfly-keycloak-saml-adapter/pom.xml @@ -0,0 +1,174 @@ + + + 4.0.0 + + org.jboss.intersmash.examples + intersmash-examples + 0.0.2-SNAPSHOT + ../pom.xml + + + wildfly-keycloak-saml-adapter + war + + Intersmash Demos : (Wildfly): Wildfly with Keycloak SAML Adapter + + + ${project.parent.parent.basedir}/ide-config + 3.3.2 + + 30.0.0.Final + + 4.2.2.Final + + ${version.wildfly-server} + + org.wildfly:wildfly-galleon-pack:${version.wildfly-server} + org.wildfly:wildfly-ee-galleon-pack:${version.wildfly-server} + org.wildfly.cloud:wildfly-cloud-galleon-pack:5.0.1.Final + + + org.keycloak + keycloak-saml-adapter-galleon-pack + 22.0.3 + + + + + + + org.wildfly.bom + wildfly-ee + ${bom.wildfly-ee.version} + pom + import + + + + + + + jakarta.ws.rs + jakarta.ws.rs-api + provided + + + jakarta.annotation + jakarta.annotation-api + provided + + + jakarta.enterprise + jakarta.enterprise.cdi-api + + + org.wildfly.core + wildfly-controller-client + 23.0.0.Beta4 + + + + org.jboss.intersmash + intersmash-core + test + + + org.jboss.intersmash + intersmash-provisioners + test + + + org.jboss.intersmash.test + deployments-provider + 0.0.2-SNAPSHOT + test + + + + + + + org.codehaus.mojo + templating-maven-plugin + 3.0.0 + + + filter-src + + filter-test-sources + + + + ${basedir}/src/test/resources/java-templates/org/jboss/intersmash/examples/wildfly/keycloak/saml/config/ + ${project.build.directory}/generated-sources + + + + + + + org.apache.maven.plugins + maven-war-plugin + ${version.maven-war-plugin} + + false + ROOT + + + + org.wildfly.plugins + wildfly-maven-plugin + ${wildfly-maven-plugin.version} + + ROOT.war + + true + + + ${wildfly.feature-pack.location} + + + ${wildfly.cloud-feature-pack.location} + + + ${keycloak-saml-adapter-galleon-pack.groupId} + ${keycloak-saml-adapter-galleon-pack.artifactId} + ${keycloak-saml-adapter-galleon-pack.version} + + + + cloud-default-config + keycloak-saml + + + true + + + + + + package + + + + + + + diff --git a/examples/wildfly-keycloak-saml-adapter/src/main/java/org/jboss/intersmash/examples/wildfly/keycloak/saml/HelloResource.java b/examples/wildfly-keycloak-saml-adapter/src/main/java/org/jboss/intersmash/examples/wildfly/keycloak/saml/HelloResource.java new file mode 100644 index 000000000..34ef670e1 --- /dev/null +++ b/examples/wildfly-keycloak-saml-adapter/src/main/java/org/jboss/intersmash/examples/wildfly/keycloak/saml/HelloResource.java @@ -0,0 +1,17 @@ +package org.jboss.intersmash.examples.wildfly.keycloak.saml; + +import jakarta.enterprise.context.ApplicationScoped; +import jakarta.ws.rs.GET; +import jakarta.ws.rs.Path; +import jakarta.ws.rs.Produces; +import jakarta.ws.rs.core.MediaType; + +@Path("/hello") +@ApplicationScoped +public class HelloResource { + @GET + @Produces(MediaType.TEXT_PLAIN) + public String greet() { + return "Hello world!"; + } +} diff --git a/examples/wildfly-keycloak-saml-adapter/src/main/java/org/jboss/intersmash/examples/wildfly/keycloak/saml/RestApplication.java b/examples/wildfly-keycloak-saml-adapter/src/main/java/org/jboss/intersmash/examples/wildfly/keycloak/saml/RestApplication.java new file mode 100644 index 000000000..f6f99a9eb --- /dev/null +++ b/examples/wildfly-keycloak-saml-adapter/src/main/java/org/jboss/intersmash/examples/wildfly/keycloak/saml/RestApplication.java @@ -0,0 +1,8 @@ +package org.jboss.intersmash.examples.wildfly.keycloak.saml; + +import jakarta.ws.rs.ApplicationPath; +import jakarta.ws.rs.core.Application; + +@ApplicationPath("/") +public class RestApplication extends Application { +} diff --git a/examples/wildfly-keycloak-saml-adapter/src/main/java/org/jboss/intersmash/examples/wildfly/keycloak/saml/ServerConfigurationResource.java b/examples/wildfly-keycloak-saml-adapter/src/main/java/org/jboss/intersmash/examples/wildfly/keycloak/saml/ServerConfigurationResource.java new file mode 100644 index 000000000..6308fe46c --- /dev/null +++ b/examples/wildfly-keycloak-saml-adapter/src/main/java/org/jboss/intersmash/examples/wildfly/keycloak/saml/ServerConfigurationResource.java @@ -0,0 +1,32 @@ +package org.jboss.intersmash.examples.wildfly.keycloak.saml; + +import jakarta.enterprise.context.ApplicationScoped; +import jakarta.ws.rs.GET; +import jakarta.ws.rs.Path; +import jakarta.ws.rs.Produces; +import jakarta.ws.rs.core.MediaType; + +import java.io.BufferedReader; +import java.io.FileInputStream; +import java.io.IOException; +import java.io.InputStreamReader; +import java.nio.charset.StandardCharsets; +import java.util.stream.Collectors; + +@Path("/config") +@ApplicationScoped +public class ServerConfigurationResource { + @GET + @Path("/read") + @Produces(MediaType.TEXT_PLAIN) + public String read() throws IOException { + String fileName = System.getProperty("jboss.server.config.dir") + "/standalone.xml"; + try (FileInputStream fis = new FileInputStream(fileName)) { + return new BufferedReader( + new InputStreamReader(fis, StandardCharsets.UTF_8)) + .lines() + .collect(Collectors.joining("\n")); + + } + } +} diff --git a/examples/wildfly-keycloak-saml-adapter/src/test/java/org/jboss/intersmash/examples/wildfly/keycloak/saml/KeycloakCapableImageBasedWildflyApplication.java b/examples/wildfly-keycloak-saml-adapter/src/test/java/org/jboss/intersmash/examples/wildfly/keycloak/saml/KeycloakCapableImageBasedWildflyApplication.java new file mode 100644 index 000000000..d59ac88a6 --- /dev/null +++ b/examples/wildfly-keycloak-saml-adapter/src/test/java/org/jboss/intersmash/examples/wildfly/keycloak/saml/KeycloakCapableImageBasedWildflyApplication.java @@ -0,0 +1,28 @@ +package org.jboss.intersmash.examples.wildfly.keycloak.saml; + +import java.nio.file.Path; +import java.nio.file.Paths; + +import org.jboss.intersmash.application.openshift.WildflyImageOpenShiftApplication; +import org.jboss.intersmash.application.openshift.input.BinarySource; +import org.jboss.intersmash.application.openshift.input.BuildInput; +import org.jboss.intersmash.examples.wildfly.keycloak.saml.config.KeycloackCapableWildflyDeploymentConfiguration; + +public class KeycloakCapableImageBasedWildflyApplication implements WildflyImageOpenShiftApplication { + + @Override + public String getName() { + return "wildfly-app"; + } + + @Override + public BuildInput getBuildInput() { + return (BinarySource) () -> { + Path path = Paths.get(KeycloackCapableWildflyDeploymentConfiguration.deploymentPath()); + if (path.toFile().exists() && path.toFile().isDirectory()) { + return path; + } + throw new RuntimeException("Cannot find sources root directory: " + path.toFile().getAbsolutePath()); + }; + } +} diff --git a/examples/wildfly-keycloak-saml-adapter/src/test/java/org/jboss/intersmash/examples/wildfly/keycloak/saml/KeycloakCapableImageBasedWildflyApplicationTest.java b/examples/wildfly-keycloak-saml-adapter/src/test/java/org/jboss/intersmash/examples/wildfly/keycloak/saml/KeycloakCapableImageBasedWildflyApplicationTest.java new file mode 100644 index 000000000..7245971e9 --- /dev/null +++ b/examples/wildfly-keycloak-saml-adapter/src/test/java/org/jboss/intersmash/examples/wildfly/keycloak/saml/KeycloakCapableImageBasedWildflyApplicationTest.java @@ -0,0 +1,54 @@ +package org.jboss.intersmash.examples.wildfly.keycloak.saml; + +import java.io.IOException; +import java.net.URI; +import java.net.URISyntaxException; +import java.net.http.HttpClient; +import java.net.http.HttpRequest; +import java.net.http.HttpResponse; + +import org.apache.http.HttpStatus; +import org.jboss.intersmash.annotations.Intersmash; +import org.jboss.intersmash.annotations.Service; +import org.jboss.intersmash.annotations.ServiceUrl; +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.Test; + +@Intersmash(@Service(KeycloakCapableImageBasedWildflyApplication.class)) +public class KeycloakCapableImageBasedWildflyApplicationTest { + + @ServiceUrl(KeycloakCapableImageBasedWildflyApplication.class) + private String wildflyApplicationUrl; + + @Test + public void testGreeting() throws URISyntaxException, IOException, InterruptedException { + HttpRequest request = HttpRequest.newBuilder() + .uri(new URI(wildflyApplicationUrl + "/hello")) + .GET() + .build(); + + HttpResponse response = HttpClient + .newBuilder() + .build() + .send(request, HttpResponse.BodyHandlers.ofString()); + + Assertions.assertEquals(HttpStatus.SC_OK, response.statusCode()); + Assertions.assertEquals("Hello world!", response.body()); + } + + @Test + public void testSamlSubsystemIsDefined() throws URISyntaxException, IOException, InterruptedException { + HttpRequest request = HttpRequest.newBuilder() + .uri(new URI(wildflyApplicationUrl + "/config/read")) + .GET() + .build(); + + HttpResponse response = HttpClient + .newBuilder() + .build() + .send(request, HttpResponse.BodyHandlers.ofString()); + + Assertions.assertEquals(HttpStatus.SC_OK, response.statusCode()); + Assertions.assertTrue(response.body().contains("")); + } +} diff --git a/examples/wildfly-keycloak-saml-adapter/src/test/resources/META-INF/services/org.junit.platform.launcher.TestExecutionListener b/examples/wildfly-keycloak-saml-adapter/src/test/resources/META-INF/services/org.junit.platform.launcher.TestExecutionListener new file mode 100644 index 000000000..63b7383d3 --- /dev/null +++ b/examples/wildfly-keycloak-saml-adapter/src/test/resources/META-INF/services/org.junit.platform.launcher.TestExecutionListener @@ -0,0 +1,2 @@ +cz.xtf.junit5.listeners.TestExecutionLogger +cz.xtf.junit5.listeners.ProjectCreator diff --git a/examples/wildfly-keycloak-saml-adapter/src/test/resources/java-templates/org/jboss/intersmash/examples/wildfly/keycloak/saml/config/KeycloackCapableWildflyDeploymentConfiguration.java b/examples/wildfly-keycloak-saml-adapter/src/test/resources/java-templates/org/jboss/intersmash/examples/wildfly/keycloak/saml/config/KeycloackCapableWildflyDeploymentConfiguration.java new file mode 100644 index 000000000..fe3384340 --- /dev/null +++ b/examples/wildfly-keycloak-saml-adapter/src/test/resources/java-templates/org/jboss/intersmash/examples/wildfly/keycloak/saml/config/KeycloackCapableWildflyDeploymentConfiguration.java @@ -0,0 +1,20 @@ +package org.jboss.intersmash.examples.wildfly.keycloak.saml.config; + +public class KeycloackCapableWildflyDeploymentConfiguration { + + public static String getProjectGroupId() { + return "${project.groupId}"; + } + + public static String getProjectArtifactId() { + return "${project.artifactId}"; + } + + public static String getProjectVersion() { + return "${project.version}"; + } + + public static String deploymentPath() { + return "${project.basedir}/target/server"; + } +} diff --git a/examples/wildfly-keycloak-saml-adapter/src/test/resources/logback.xml b/examples/wildfly-keycloak-saml-adapter/src/test/resources/logback.xml new file mode 100644 index 000000000..4657bc2c3 --- /dev/null +++ b/examples/wildfly-keycloak-saml-adapter/src/test/resources/logback.xml @@ -0,0 +1,59 @@ + + + + + + + ${console-log-level:-INFO} + + + [%d] %-5p- %m%n + + + + 300 + + + + + log/test.log + false + + DEBUG + + + [%d] %-5level [%thread]: %message%n + + + + 300 + + + + + log/everything.log + false + + [%d] %-5p- %m%n + + + + 300 + + + + + + + + + + + + + + + + + + diff --git a/provisioners/src/test/java/org/jboss/intersmash/provision/ProvisionerManagerTestCase.java b/provisioners/src/test/java/org/jboss/intersmash/provision/ProvisionerManagerTestCase.java index 2386eed14..5d384f95e 100644 --- a/provisioners/src/test/java/org/jboss/intersmash/provision/ProvisionerManagerTestCase.java +++ b/provisioners/src/test/java/org/jboss/intersmash/provision/ProvisionerManagerTestCase.java @@ -80,14 +80,19 @@ enum SupportedApplication { PostgreSQLImageOpenShiftProvisioner.class), PostgreSQLTemplateOpenShiftApplication( getApplicationMock(org.jboss.intersmash.application.openshift.PostgreSQLTemplateOpenShiftApplication.class, - (application) -> when(((org.jboss.intersmash.application.openshift.PostgreSQLTemplateOpenShiftApplication) application).getTemplate()) - .thenReturn(org.jboss.intersmash.application.openshift.template.PostgreSQLTemplate.POSTGRESQL_PERSISTENT)), + (application) -> when( + ((org.jboss.intersmash.application.openshift.PostgreSQLTemplateOpenShiftApplication) application) + .getTemplate()) + .thenReturn( + org.jboss.intersmash.application.openshift.template.PostgreSQLTemplate.POSTGRESQL_PERSISTENT)), PostgreSQLTemplateOpenShiftProvisioner.class), RhSsoOperatorApplication(getApplicationMock(org.jboss.intersmash.application.openshift.RhSsoOperatorApplication.class), RhSsoOperatorProvisioner.class), RhSsoTemplateOpenShiftApplication( getApplicationMock(org.jboss.intersmash.application.openshift.RhSsoTemplateOpenShiftApplication.class, - (application) -> when(((org.jboss.intersmash.application.openshift.RhSsoTemplateOpenShiftApplication) application).getTemplate()) + (application) -> when( + ((org.jboss.intersmash.application.openshift.RhSsoTemplateOpenShiftApplication) application) + .getTemplate()) .thenReturn(org.jboss.intersmash.application.openshift.template.RhSsoTemplate.X509_HTTPS)), RhSsoTemplateOpenShiftProvisioner.class), WildflyImageOpenShiftApplication(