Server Configuration

Creating a Self-Signed Certificate on JBoss EAP 8.1

There are times when we want our JBoss EAP instances to be accessed via a secure connection (HTTPS) instead of plain, insecure HTTP. The fastest way to achieve this in a development or testing environment is to generate and apply a self-signed certificate.

First, let’s create the self-signed certificate. Be sure to replace your-hostname and your-ipaddress with the actual details of your JBoss EAP server:

$ keytool -genkeypair -alias server \ 
	-keyalg RSA -keysize 4096 -sigalg SHA256withRSA \ 
	-validity 3650 -storetype PKCS12 -keystore keystore.p12 \ 
	-storepass password -keypass password \ 
	-dname "CN=jboss,OU=RH,O=Edwin,C=ID" -ext SAN=dns:your-hostname,ip:your-ipaddress

This command generates a keystore.p12 file. Move this file into your JBOSS_HOME/standalone/configuration/ directory.

Next, we need to reference this new keystore in our standalone.xml. Locate the section within the elytron subsystem and update the applicationKS definition to point to your new keystore.p12 file:

<tls>
	<key-stores>
		<key-store name="applicationKS">
			<credential-reference clear-text="password"/>
			<implementation type="PKCS12"/>
			<file path="keystore.p12" relative-to="jboss.server.config.dir"/>
		</key-store>
	</key-stores>
	
	<key-managers>
		<key-manager name="applicationKM" key-store="applicationKS">
			<credential-reference clear-text="password"/>
		</key-manager>
	</key-managers>
	
	<server-ssl-contexts>
		<server-ssl-context name="applicationSSC" key-manager="applicationKM"/>
	</server-ssl-contexts>
</tls>

Start your JBoss EAP and see whether JBoss EAP is leveraging our certificate or not by using a curl command,

$ curl -Ikv https://localhost:8443
* Host localhost:8443 was resolved.
* IPv6: ::1
* IPv4: 127.0.0.1
*   Trying [::1]:8443...
* connect to ::1 port 8443 from ::1 port 40968 failed: Connection refused
*   Trying 127.0.0.1:8443...
* ALPN: curl offers h2,http/1.1
* TLSv1.3 (OUT), TLS handshake, Client hello (1):
* TLSv1.3 (IN), TLS handshake, Server hello (2):
* TLSv1.2 (IN), TLS handshake, Certificate (11):
* TLSv1.2 (IN), TLS handshake, Server key exchange (12):
* TLSv1.2 (IN), TLS handshake, Server finished (14):
* TLSv1.2 (OUT), TLS handshake, Client key exchange (16):
* TLSv1.2 (OUT), TLS change cipher, Change cipher spec (1):
* TLSv1.2 (OUT), TLS handshake, Finished (20):
* TLSv1.2 (IN), TLS handshake, Finished (20):
* SSL connection using TLSv1.2 / ECDHE-RSA-AES256-GCM-SHA384 / x25519 / RSASSA-PSS
* ALPN: server accepted h2
* Server certificate:
*  subject: C=ID; O=Edwin; OU=RH; CN=jboss
*  start date: Jul 27 12:42:48 2026 GMT
*  expire date: Jul 24 12:42:48 2036 GMT
*  issuer: C=ID; O=Edwin; OU=RH; CN=jboss
*  SSL certificate verify result: self-signed certificate (18), continuing anyway.
*   Certificate level 0: Public key type RSA (4096/152 Bits/secBits), signed using sha256WithRSAEncryption
* Connected to localhost (127.0.0.1) port 8443
* using HTTP/2
* [HTTP/2] [1] OPENED stream for https://localhost:8443/
* [HTTP/2] [1] [:method: HEAD]
* [HTTP/2] [1] [:scheme: https]
* [HTTP/2] [1] [:authority: localhost:8443]
* [HTTP/2] [1] [:path: /]
* [HTTP/2] [1] [user-agent: curl/8.15.0]
* [HTTP/2] [1] [accept: */*]
> HEAD / HTTP/2
> Host: localhost:8443
> User-Agent: curl/8.15.0
> Accept: */*
>
* Request completely sent off
< HTTP/2 200
HTTP/2 200
< last-modified: Tue, 29 Jul 2025 01:49:24 GMT
last-modified: Tue, 29 Jul 2025 01:49:24 GMT
< content-length: 1720
content-length: 1720
< content-type: text/html
content-type: text/html
< accept-ranges: bytes
accept-ranges: bytes
< date: Mon, 27 Jul 2026 12:50:15 GMT
date: Mon, 27 Jul 2026 12:50:15 GMT
<

Securely Storing Database Password on JBoss EAP 8

Typically, we store database passwords in plain text on JBoss EAP 8. However, this approach is not considered a best practice due to security concerns. Therefore, it is important to encrypt the password thru several ways of password encryption method. And on this article we’ll try to do encryption using the JBoss EAP’s credential-store.

First we need to create a credential store to be stored in JBoss EAP, with the name of “my_custom_store” and “longpassword” as its password which is located in the JBoss data directory.


$ jboss-cli.sh
You are disconnected at the moment. Type 'connect' to connect to the server or 'help' for the list of supported commands.

[disconnected /] connect

[standalone@localhost:9990 /] /subsystem=elytron/credential-store=my_custom_store:add(path="my_custom_store.jceks", relative-to=jboss.server.data.dir, credential-reference={clear-text=longpassword}, create=true)
{"outcome" => "success"}

Next is storing my database password there,

[standalone@localhost:9990 /]  /subsystem=elytron/credential-store=my_custom_store:add-alias(alias=db_password, secret-value=mysecuredatabasepassword)

And validate it,

[standalone@localhost:9990 /] /subsystem=elytron/credential-store=my_custom_store:read-aliases()
{
    "outcome" => "success",
    "result" => ["db_password"]
}

Next is injecting the value of our secure password from credential store into our database connection. This is happen in our standalone.xml file,

<datasource jndi-name="java:/my-db" pool-name="my-db">
	<connection-url>jdbc:mysql://localhost:3306/test_db</connection-url>
	<driver-class>com.mysql.cj.jdbc.Driver</driver-class>
	<driver>mysql</driver>
	<security>
		<user-name>root</user-name>
		<credential-reference store="my_custom_store" alias="db_password"/>
	</security>
</datasource>

A successful database connection can be tested thru JBoss EAP web console,

Generate an HTML Trivy Report Ordered by Severity

Trivy is an Open Source tools for scanning software artifacts, and image vulnerabilities, which is maintained by Aqua Security. We can also generate Trivy reports and displaying the list of vulnerabilities as an HTML report. We can also create our own custom HTML template that would suitable for our needs.

Below is a sample HTML report that we use for sorting vulnerabilities based on its severity level,

<!DOCTYPE html>
<html>
  <head>
    <meta http-equiv="Content-Type" content="text/html; charset=utf-8">
{{- if . }}
    <style>
      * {
        font-family: Arial, Helvetica, sans-serif;
      }
      h1 {
        text-align: center;
      }
      .group-header th {
        font-size: 200%;
      }
      .sub-header th {
        font-size: 150%;
      }
      table, th, td {
        border: 1px solid black;
        border-collapse: collapse;
        white-space: nowrap;
        padding: .3em;
      }
      table {
        margin: 0 auto;
      }
      .severity {
        text-align: center;
        font-weight: bold;
        color: #fafafa;
      }
      .severity-LOW .severity { background-color: #5fbb31; }
      .severity-MEDIUM .severity { background-color: #e9c600; }
      .severity-HIGH .severity { background-color: #ff8800; }
      .severity-CRITICAL .severity { background-color: #e40000; }
      .severity-UNKNOWN .severity { background-color: #747474; }
      .severity-LOW { background-color: #5fbb3160; }
      .severity-MEDIUM { background-color: #e9c60060; }
      .severity-HIGH { background-color: #ff880060; }
      .severity-CRITICAL { background-color: #e4000060; }
      .severity-UNKNOWN { background-color: #74747460; }
      table tr td:first-of-type {
        font-weight: bold;
      }
      .links a,
      .links[data-more-links=on] a {
        display: block;
      }
      .links[data-more-links=off] a:nth-of-type(1n+5) {
        display: none;
      }
      a.toggle-more-links { cursor: pointer; }
    </style>
    <title>{{- escapeXML ( index . 0 ).Target }} - Trivy Report - {{ now }} </title>
    <script>
      window.onload = function() {
        document.querySelectorAll('td.links').forEach(function(linkCell) {
          var links = [].concat.apply([], linkCell.querySelectorAll('a'));
          [].sort.apply(links, function(a, b) {
            return a.href > b.href ? 1 : -1;
          });
          links.forEach(function(link, idx) {
            if (links.length > 3 && 3 === idx) {
              var toggleLink = document.createElement('a');
              toggleLink.innerText = "Toggle more links";
              toggleLink.href = "#toggleMore";
              toggleLink.setAttribute("class", "toggle-more-links");
              linkCell.appendChild(toggleLink);
            }
            linkCell.appendChild(link);
          });
        });
        document.querySelectorAll('a.toggle-more-links').forEach(function(toggleLink) {
          toggleLink.onclick = function() {
            var expanded = toggleLink.parentElement.getAttribute("data-more-links");
            toggleLink.parentElement.setAttribute("data-more-links", "on" === expanded ? "off" : "on");
            return false;
          };
        });
      };
	  
	  window.addEventListener('DOMContentLoaded', () => {	  
			const severityOrder = {
			  "CRITICAL": 1,
			  "HIGH": 2,
			  "MEDIUM": 3,
			  "LOW": 4
			};

			const table = document.getElementById("myTable");
			const tbody = table.tBodies[0];
			const rows = Array.from(tbody.rows);
			
			const columnIndex = 2; 
			
			rows.sort((a, b) => {
				  const cellA = a.cells[columnIndex];
				  const cellB = b.cells[columnIndex];

				  if (!cellA || !cellB) {					
					return 0; // Skip sort if data is malformed
				  }
				  
				  if (cellA.textContent.trim().toUpperCase()=='SEVERITY' || cellB.textContent.trim().toUpperCase()=='SEVERITY') {		
					return 0; // Skip sort if data is malformed
				  }

				  const sevA = cellA.textContent.trim().toUpperCase();
				  const sevB = cellB.textContent.trim().toUpperCase();

				  const orderA = severityOrder[sevA] ?? 999;
				  const orderB = severityOrder[sevB] ?? 999;

				  return orderA - orderB;
			});

			rows.forEach(row => tbody.appendChild(row));
	  });
	
    </script>
  </head>
  <body>
    <h1>{{- escapeXML ( index . 0 ).Target }} - Trivy Report - {{ now }}</h1>
    <table id="myTable">
    {{- range . }}
      <tr class="group-header"><th colspan="6">{{ .Type | toString | escapeXML }}</th></tr>
      {{- if (eq (len .Vulnerabilities) 0) }}
      <tr><th colspan="6">No Vulnerabilities found</th></tr>
      {{- else }}
      <tr class="sub-header">
        <th>Package</th>
        <th>Vulnerability ID</th>
        <th>Severity</th>
        <th>Installed Version</th>
        <th>Fixed Version</th>
        <th>Links</th>
      </tr>
        {{- range .Vulnerabilities }}
      <tr class="severity-{{ escapeXML .Vulnerability.Severity }}">
        <td class="pkg-name">{{ escapeXML .PkgName }}</td>
        <td>{{ escapeXML .VulnerabilityID }}</td>
        <td class="severity">{{ escapeXML .Vulnerability.Severity }}</td>
        <td class="pkg-version">{{ escapeXML .InstalledVersion }}</td>
        <td>{{ escapeXML .FixedVersion }}</td>
        <td class="links" data-more-links="off">
          {{- range .Vulnerability.References }}
          <a href={{ escapeXML . | printf "%q" }}>{{ escapeXML . }}</a>
          {{- end }}
        </td>
      </tr>
        {{- end }}
      {{- end }}
      {{- if (eq (len .Misconfigurations ) 0) }}
      <tr><th colspan="6">No Misconfigurations found</th></tr>
      {{- else }}
      <tr class="sub-header">
        <th>Type</th>
        <th>Misconf ID</th>
        <th>Check</th>
        <th>Severity</th>
        <th>Message</th>
      </tr>
        {{- range .Misconfigurations }}
      <tr class="severity-{{ escapeXML .Severity }}">
        <td class="misconf-type">{{ escapeXML .Type }}</td>
        <td>{{ escapeXML .ID }}</td>
        <td class="misconf-check">{{ escapeXML .Title }}</td>
        <td class="severity">{{ escapeXML .Severity }}</td>
        <td class="link" data-more-links="off"  style="white-space:normal;">
          {{ escapeXML .Message }}
          <br>
            <a href={{ escapeXML .PrimaryURL | printf "%q" }}>{{ escapeXML .PrimaryURL }}</a>
          </br>
        </td>
      </tr>
        {{- end }}
      {{- end }}
    {{- end }}
    </table>
{{- else }}
  </head>
  <body>
    <h1>Trivy Returned Empty Report</h1>
{{- end }}
  </body>
</html>

Save it as “html.tpl”, and run the below command

$ trivy image --scanners vuln  my-image:latest --format template \
       --template "@/tmp/html.tpl" -o /tmp/my-image-vulnerabilities.html

It shall generate a report like below,

Script to Generate Series of Thread Dump

There are times when we want to see which threads are blocking our requests, and generating a thread dump is one way to find it out. The thing is sometimes we need to create a series of thread dumps, that’s why i have this script to do it for me. Create a file with the name of “jstack.sh”, with below script as its content

# number of cycles.
LOOP=3
# seconds between cycles.
INTERVAL=10

for ((i=1; i <= $LOOP; i++))
do
   _now=$(date)
   echo "\n \n ${_now}" >> cpu.out
   top -l 1 -o cpu -pid $1 >> cpu.out
   echo "\n \n ${_now}" >> tdump.out
   jstack -l $1 >> tdump.out
   echo "thread dump #" $i
   if [ $i -lt $LOOP ]; then
      echo "Sleeping..."
      sleep $INTERVAL
   fi
done

Repository for above script can be found below,

https://github.com/edwin/java-thread-dump