Sunday, December 27. 2020
Testing bootable JAR in wildfly
A few weeks ago wildfly added the bootable jar feature in order to offer a fat JAR packaging distribution. A big JAR file can now be generated which is directly the executable server with the application deployed inside it. The resulting file contains the jboss-modules to startup the server and all the needed layers that the specific app requires. More or less this feature pretends to achieve something similar to what I showed in the old Spring Boot series. For today's post I decided to accommodate the same application used in those entries but using the wildfly bootable JAR instead of Spring Boot.
The application is just a jaxrs hello world. It incorporates swagger to annotate the endpoint and keycloak to protect its access. Although using the bootable JAR feature, a common JavaEE server will be booted up, so the application is just the same WAR file that would be used in a standalone wildfly. It contains a restful application class and the endpoint, it is annotated with swagger, and, this time, keycloak will be added normally (just the OIDC adapter installed with a layer). The first thing in order to use the bootable JAR is adding its maven plugin, following the guide the server will be slimmed to the minimum layers: base-server, logging, jaxrs and keycloak-client-oidc.
<plugin>
<groupId>org.wildfly.plugins</groupId>
<artifactId>wildfly-jar-maven-plugin</artifactId>
<version>2.0.2.Final</version>
<configuration>
<feature-packs>
<feature-pack>
<location>wildfly@maven(org.jboss.universe:community-universe)#21.0.2.Final</location>
</feature-pack>
<feature-pack>
<groupId>org.keycloak</groupId>
<artifactId>keycloak-adapter-galleon-pack</artifactId>
<version>12.0.1</version>
</feature-pack>
</feature-packs>
<layers>
<layer>base-server</layer>
<layer>logging</layer>
<layer>jaxrs</layer>
<layer>keycloak-client-oidc</layer>
</layers>
</configuration>
<executions>
<execution>
<goals>
<goal>package</goal>
</goals>
</execution>
</executions>
</plugin>
The feature packs are the normal community-universe wildfy repository and the keycloak galleon pack, which is added as an extra. The swagger part is included inside the WAR application. So, this time, keycloak will added using a layer but swagger libraries are in the WEB-INF/lib folder. For the SSO part a public client should be defined in the usual way at server level. This entry will not cover the keycloak server configuration (it is very simple anyway). Inside the application the client is used twice, javascript is used to perform the login (public app) and the jaxrs endpoint is configured as a bearer only app. So the sample application contains code in the index.html page to perform the login which uses a keycloak.json configuration located in root folder, and the bearer java part is configured inside the web.xml file using another keycloak.json file in the WEB-INF directory. The same trick was used in the Spring Boot post.
If you remember in the old entry a jaxrs filter was developed to control the access to the endpoint via RBAC (Role-Based Access Control) . I commented that an EJB can be used instead for it if using a normal wildfly server. Using the bootable JAR now we can add EJB to the application. Besides it is a good way of complicating and testing this new feature. In order to transform the endpoint in a Stateless EJB two more layers are needed: ejb-lite and bean-validation. But the final JAR did not start. The problem is that the security domain used in the web part (jaxrs) is different to the one used in the EJB. In general each layer does not only include the needed modules and libraries to the final file, it also adds some configuration to the server. In the case of keycloak it creates all the security stuff needed for the web part and assigns it to the default other security domain. But nothing does the same for the EJB part. Hopefully the bootable JAR also lets you configure the server with CLI commands during the packaging, so we can change the default security domain for the EJB layer just using the following CLI line.
/subsystem=ejb3/application-security-domain=other:write-attribute(name=security-domain, value=KeycloakDomain)
A file with the line can also be included in the maven wildfly plugin configuration, and executing the CLI, the security domain for the EJB system will be the same one used in the web/undertow subsystem (I needed to check the files inside that keycloak layer distribution to get the correct name for the domain).
<cli-sessions>
<cli-session>
<script-files>
<script>src/scripts/ejb3.cli</script>
</script-files>
<resolve-expressions>false</resolve-expressions>
</cli-session>
</cli-sessions>
And this way it finally worked. But adding more layers means more modules, and, therefore, more size for the resulting JAR file. In this case it went from 62 to 95MB when adding the EJB stuff. It was a good exercise to play with the new wildfly plugin but I prefer to maintain the filter and save those 30GB. So I commented out the EJB part and went back to the original filter solution. This is the video that shows exactly the same steps that were presented in the Spring entry. First the keycloak server is started. Then the project is packaged (at this point the server is bundled in a JAR file) and started. Using the browser the swagger info is first requested, the hello endpoint is correctly protected and, after login into the keycloak server, the endpoint works as expected.
Today's entry is a little summary of the new bootable JAR. The same application that was previously used with Spring Boot was transformed to use this new wildfly feature. For one side there is the benefit (at least for me) of using a plain JavaEE application (no Spring at all) but the resulting JAR is not as slim as before. The first entry of the previous Spring Boot series generates a file of 31GB while the bootable JAR size is 62GB, but, considering that the full wildlfy distribution is around 200GB in size, it is not a small step. The project for the sample application can be download from here. During the entry different packs were used (wildfly packs and the keycloak one) and even the CLI integration was tested to configure keycloak and EJB at the same time (although I discarded it in the end, in the project this part is just commented out). I am quite happy with this new feature. I think that it is something useful to expand the wildfly adoption with containers.
Best regards!
Saturday, December 12. 2020
Masking passwords for the wildfly-config.xml file
Today's entry is a summary about how to mask passwords inside the wildfly-config.xml file. This file is the client side configuration currently recommended to be used for calling an Enterprise Java Bean (EJB) deployed in a wildfly server. The EJB endpoints are secured by default in wildfly (username and password) and that information needs to be present in the configuration. Masked passwords are used (directly or indirectly) to not display plain passwords inside that file. But the masking process is not as easy as expected, so I decided to create a new blog entry.
Let's start just downloading and installing the new wildfly 21. The server is just exploded and a management (for the console) and an application user (for the EJB interface) are created.
wget https://download.jboss.org/wildfly/21.0.1.Final/wildfly-21.0.1.Final.zip
unzip wildfly-21.0.1.Final.zip
cd wildfly-21.0.1.Final/bin/
./add-user.sh -u admin -p admin
./add-user.sh -a -u ejbuser -p password123
./standalone.sh
Now a very simple test application is created. The hello-worl-ejb app is just a hello world but using the EJB technology. For that an interface is needed.
public interface HelloWorldRemote {
public String sayHello(String name);
}
And the implementation which is annotated to be a stateless enterprise java bean.
@Stateless
@Remote(HelloWorldRemote.class)
public class HelloWorldBean implements HelloWorldRemote {
@Override
public String sayHello(String name) {
return "Hello " + (name == null? "World" : name) + "!";
}
}
Finally a client is developed to call the previous bean and display the resulting salute.
public class HelloWorldClient {
public static void main(String... args) throws Exception {
Properties jndiProperties = new Properties();
jndiProperties.put(Context.INITIAL_CONTEXT_FACTORY, "org.wildfly.naming.client.WildFlyInitialContextFactory");
InitialContext context = new InitialContext(jndiProperties);
final String lookupName = "ejb:/hello-world-ejb/HelloWorldBean!es.rickyepoderi.ejb.HelloWorldRemote";
HelloWorldRemote helloWorld = (HelloWorldRemote) context.lookup(lookupName);
System.out.println(helloWorld.sayHello(args.length > 0? args[0] : null));
}
}
As you see, in the client nothing is defined about the URL or the login information (although you can use other properties the idea is using the wildfly-config.xml file). My first try is just using a clear password inside a minimal configuration to connect using the new http invocation (for more information about EJB in wildfly you can check this detailed post). So, with no masking, the configuration file can be the following.
<configuration>
<authentication-client xmlns="urn:elytron:client:1.4">
<credential-stores>
<authentication-rules>
<rule use-configuration="clear"/>
</authentication-rules>
<authentication-configurations>
<configuration name="clear">
<sasl-mechanism-selector selector="DIGEST-MD5"/>
<set-user-name name="ejbuser"/>
<credentials>
<clear-password password="password123"/>
</credentials>
</configuration>
</authentication-configurations>
</authentication-client>
<http-client xmlns="urn:wildfly-http-client:1.0">
<defaults></defaults>
</http-client>
<discovery xmlns="urn:wildfly-discovery:1.0">
<discovery-provider>
<static>
<service uri="http://localhost:8080/wildfly-services" abstract-type="ejb" abstract-type-authority="jboss">
<attribute name="ejb-module" value="hello-world-ejb" />
</service>
</static>
</discovery-provider>
</discovery>
</configuration>
The file is quite easy, in the urn:elytron:client section there is only one authentication configuration clear, which specifies the username and password in plain text, and is directly assigned to all the EJB connections by the rule. The urn:wildfly-discovery part is used to configure the wildfly server URL and urn:wildfly-http-client is just added to know that it exists and it can be used to add specific details for the http client. The final project for the sample application can be downloaded at the end of the entry. Just package the application, deploy the resulting war file into the wildfly server and execute using the exec plugin.
cd hello-world-ejb
mvn clean package
# deploy target/hello-world-ejb.war
mvn exec:java
...
[INFO] --- exec-maven-plugin:3.0.0:java (default-cli) @ hello-world-ejb ---
...
Hello World!
This configuration works but the password for the ejbuser is displayed in plain inside the file. How can it be masked? The only useful information I found about the subject is this entry in Ashley Abdel-Sayed's Blog. Mainly it says that you can use a masked password but some java code is needed. Please do not try to use the elytron-tool.sh command for creating them, its mask option is for picketlink integration and it does not return a valid masked password. Maybe this changes in the future, but now this tool is not suitable for the task. Therefore I have developed another program that using Ashley's idea can be executed to create one. The project can also be downloaded at the end of the entry and it accepts different arguments.
cd masked-pasword
mvn clean package
mvn exec:java -Dexec.args="-s 12345678 -i 100 password123"
...
Masked Password: $masked-MD5-DES$somearbitrarycrazystringthatdoesnotmatter$100$MTIzNDU2Nzg=$/Nym2s/dssNzlEkNlD+ycQ==$o8IbHwo2ZNk=
Snippet: <masked-password iteration-count="100" salt="12345678" initialization-vector="o8IbHwo2ZNk=" masked-password="/Nym2s/dssNzlEkNlD+ycQ=="/>
Password Verified 'true'
...
In the previous example default algorithm is used to create a masked password for password123 with salt 12345678 and one hundred iterations. The tool has a simple usage information that can be used to specify different options (algorithm for example). So now a new authentication configuration can be added to use the previous masked password.
<authentication-client xmlns="urn:elytron:client:1.4">
<authentication-rules>
<rule use-configuration="masked"/>
</authentication-rules>
<authentication-configurations>
<configuration name="masked">
<sasl-mechanism-selector selector="DIGEST-MD5"/>
<set-user-name name="ejbuser"/>
<credentials>
<masked-password iteration-count="100" salt="12345678" initialization-vector="o8IbHwo2ZNk=" masked-password="/Nym2s/dssNzlEkNlD+ycQ=="/>
</credentials>
</configuration>
</authentication-configurations>
</authentication-client>
The clear password is substituted by the masked one reported by the utility program and the new configuration masked is used by the rule. And this works perfectly. The password is masked (reversible algorithm) but at least it is not in plain.
But let's add a final configuration. The wildfly-config.xml can also be configured to use a credential store. A store that contains secret information like passwords. It can be created using the elytron-tool.sh utility.
cd ${WILDFLY_HOME}/bin
./elytron-tool.sh credential-store --create --location "store.jceks" --password password123
./elytron-tool.sh credential-store --location "store.jceks" --password password123 --add ejbpwd --secret password123
cp store.jceks /path/to/hello-world-ejb/
The previous execution initializes a store.jceks credential store with password password123 and adds the alias ejbpwd with the password for the ejbuser inside it. The store file is moved to the project folder to be referenced by the configuration. Now the wildfly-config.xml uses the store and the alias to obtain the password for the EJB user. But the general password for the store should also be provided in the configuration... And how do we provide it not being in clear? Using a masked password of course. So in this last option the authentication section looks like this.
<authentication-client xmlns="urn:elytron:client:1.4">
<credential-stores>
<credential-store name="store">
<attributes>
<attribute name="keyStoreType" value="JCEKS" />
<attribute name="location" value="./store.jceks" />
</attributes>
<protection-parameter-credentials>
<masked-password iteration-count="100" salt="12345678" initialization-vector="o8IbHwo2ZNk=" masked-password="/Nym2s/dssNzlEkNlD+ycQ=="/>
</protection-parameter-credentials>
</credential-store>
</credential-stores>
<authentication-rules>
<rule use-configuration="store"/>
</authentication-rules>
<authentication-configurations>
<configuration name="store">
<sasl-mechanism-selector selector="DIGEST-MD5"/>
<set-user-name name="ejbuser"/>
<credentials>
<credential-store-reference store="store" alias="ejbpwd"/>
</credentials>
</configuration>
</authentication-configurations>
</authentication-client>
This last configuration is the most complete. The authentication section uses a credential store. It can handle several aliases with different secrets associated. And the general password for the store is presented using a masked password.
So the summary is that although the wildfly-config.xml can manage masked passwords to not present them in plain, the information is not very clear and you need some java code to generate them. In this entry a maven project (you can download it from here) was used to generate the masked passwords following the same idea that Ashley Abdel-Sayed presented in his blog. This way it's easier to create them. Finally the maven project for the EJB test application can also be downloaded from here. The final wildfly-config.xml in the project contains the three authentication configurations presented in the entry. You can switch from one to the other just modifying the rule (use-configuration attribute).
Best regards!
Comments