Pages

Showing posts with label Wildfly. Show all posts
Showing posts with label Wildfly. Show all posts

Monday, April 23, 2018

Add MySQL or Oracle driver to create a datasource in Wildfly 10

In any other server one simply puts any jar into the library folder to make included in the classpath. In contrast, in Wildfly there is no such a folder - it is based on a modular classloading architecture. One needs to create an individual folder for any additional jar, which is called a module. The official documentation recommends including the required jar in the web archive instead of creating modules. However, this is impossible if one needs to create as datasource, which depends on a driver in the server classpath (A datasource can alternatively be deployed, which I find inconvenient).

  1. Create a new folder path$JBOSS_HOME/modules/system/layers/base/com/mysql/main

    Inside the created folder, create a file module.xml with contents:

    <?xml version="1.0" encoding="UTF-8"?>
    <module xmlns="urn:jboss:module:1.3" name="com.mysql">
         <resources>
            <resource-root path="mysql-connector-java-5.1.42-bin.jar"/>
         </resources>
          <dependencies>
       <module name="marian.mysqllogger"/> 
            <module name="javax.api"/> 
            <module name="javax.transaction.api"/>
        </dependencies>
    </module>

    Note, module marian.mysqllogger is obviously optional, it logs the executed SQL commands. It is described in my previous posts.

  2. Create a new folder path$JBOSS_HOME/modules/system/layers/base/com/oracle/main

    Inside the created folder, create a file module.xml with contents:

    <?xml version="1.0" encoding="UTF-8"?>
    <module xmlns="urn:jboss:module:1.3" name="com.oracle">
         <resources>
            <resource-root path="ojdbc6.jar"/>
         </resources>
    
        <dependencies>
            <module name="javax.api"/> 
            <module name="javax.transaction.api"/>
        </dependencies>
    </module>
  3. Copy a MySQL driver jar indicated in the xml file (e.g. mysql-connector-java-5.1.42-bin.jar) into folder $JBOSS_HOME/modules/system/layers/base/com/mysql/main. The oracle driver indicated in the xml file (e.g. ojdbc6.jar) should also be placed into the folder $JBOSS_HOME/modules/system/layers/base/com/oracle/main with the xml file.

  4. Finally, the datasources can be created in the Administration Console after the Wildfly is restarted.

    Alternatively, you can directly edit $JBOSS_HOME/standalone/configuration/standalone.xml. Extend the part dedicated to datasources:

            <subsystem xmlns="urn:jboss:domain:datasources:4.0">
                <datasources>
                     <datasource jta="true" jndi-name="java:/OracleDS" pool-name="OracleDS" enabled="true" use-ccm="true">
                        <connection-url>jdbc:oracle:thin:@localhost:1521:orcl2</connection-url>
                        <driver-class>oracle.jdbc.driver.OracleDriver</driver-class>
                        <driver>oracle</driver>
                        <security>
                            <user-name>username</user-name>
                            <password>password</password>
                        </security>
                        <validation>
                            <valid-connection-checker class-name="org.jboss.jca.adapters.jdbc.extensions.oracle.OracleValidConnectionChecker"/>
                            <background-validation>true</background-validation>
                            <stale-connection-checker class-name="org.jboss.jca.adapters.jdbc.extensions.oracle.OracleStaleConnectionChecker"/>
                            <exception-sorter class-name="org.jboss.jca.adapters.jdbc.extensions.oracle.OracleExceptionSorter"/>
                        </validation>
                    </datasource>
                    <datasource jta="true" jndi-name="java:/MySqlDS" pool-name="MySqlDS" enabled="true" use-ccm="true">
                        <connection-url>jdbc:mysql://localhost:3306/wildfly?useSSL=false&profileSQL=true&logger=com.mysql.jdbc.log.MySlf4JLogger</connection-url>
                        <driver-class>com.mysql.jdbc.Driver</driver-class>
                        <driver>mysql</driver>
                        <security>
                            <user-name>username</user-name>
                            <password>password</password>
                        </security>
                        <validation>
                            <valid-connection-checker class-name="org.jboss.jca.adapters.jdbc.extensions.mysql.MySQLValidConnectionChecker"/>
                            <background-validation>true</background-validation>
                            <exception-sorter class-name="org.jboss.jca.adapters.jdbc.extensions.mysql.MySQLExceptionSorter"/>
                        </validation>
                    </datasource>
                    <drivers>
                        <driver name="oracle" module="com.oracle">
                            <driver-class>oracle.jdbc.driver.OracleDriver</driver-class>
                        </driver>
                        <driver name="mysql" module="com.mysql">
                            <driver-class>com.mysql.jdbc.Driver</driver-class>
                        </driver>
                    </drivers>
                </datasources>
            </subsystem>

    com.mysql.jdbc.log.MySlf4JLogger is a handy logger of executed SQL statements. It is loaded from marian.mysqllogger module.

    Restart the Wildfly.

Saturday, January 27, 2018

Enabling SSL in Wildfly using a free certificate from Let's Encrypt

Let’s Encrypt is a free Certificate Authority. To enable HTTPS on a website, one needs to get a certificate from a Certificate Authority. Let’s Encrypt recommends to use Certbot - a tool that validates your ownership over the target domain and fetches the certificates.

Intalling Certbot on CentOS7
sudo yum install epel-release
sudo yum install certbot
Validating a domain to fetch a certificate

Fetching certificates is described in detail in thethe official documentation, which needs to be consulted to understand the command below. Briefly, Certbot attempts to validate that you own the domain for which you ask a certificate. When a user opts for http-based verification, Certbot asks to create two files with tiny contents specified by Certbot so that they are accessible at some urls in your domain also specified by Certbot.

sudo certbot certonly --manual --preferred-challenges http -d food-diary-online.com -d www.food-diary-online.com --manual-auth-hook /opt/SSLCertificates/authenticator.sh --non-interactive --manual-public-ip-logging-ok

--manual-auth-hook option points on the script authenticator.sh creating files in a web application folder .well-known/acme-challenge that is accessible by CertBot via web:

#!/bin/bash

TARGET_DIR=/opt/wildfly-10.1.0.Final/standalone/deployments/FoodApp.war/.well-known/acme-challenge
mkdir -p $TARGET_DIR
echo $CERTBOT_VALIDATION > $TARGET_DIR/$CERTBOT_TOKEN

As the result the certificates are downloaded

Strangely, a normal user could not access the certificates because of the permissions on the created by Certbot parent folders. So I adjusted the permissions:

sudo chmod 755 /etc/letsencrypt/archive
sudo chmod 755 /etc/letsencrypt/live
cat /etc/letsencrypt/live/food-diary-online.com/fullchain.pem

The indicated directory /etc/letsencrypt/live/food-diary-online.com/ contains symbolic links privkey.pem and fullchain.pem to the most recently downloaded certificate files. For example, when I downloaded the certificates second time, the directory contents were:

What happens without manual-auth-hook

Certbot asks to create two files so that they are accessible at the specified urls. Without the authenticator script, the output is like:

Create a file containing just this data:

9-gBwqnje4DmxbrxaXX7E3-Rua2_-rY54JB6wsdCWqo.m1NBHzDLwknVhXjqDceEqOyC2Na8q0e1QJws4FCqErs

And make it available on your web server at this URL:

http://food-diary-online.com/.well-known/acme-challenge/9-gBwqnje4DmxbrxaXX7E3-Rua2_-rY54JB6wsdCWqo

--Press Enter to Continue--

Create a file containing just this data:

VNDE0iHhEQccJuFcDFF3X-FwsaxItyFlfE0GGy_6ixI.m1NBHzDLwknVhXjqDceEqOyC2Na8q0e1QJws4FCqErs

And make it available on your web server at this URL:

http://www.food-diary-online.com/.well-known/acme-challenge/VNDE0iHhEQccJuFcDFF3X-FwsaxItyFlfE0GGy_6ixI

--Press Enter to Continue--


IMPORTANT NOTES:
 - Congratulations! Your certificate and chain have been saved at:
   /etc/letsencrypt/live/food-diary-online.com/fullchain.pem
   Your key file has been saved at:
   /etc/letsencrypt/live/food-diary-online.com/privkey.pem
   Your cert will expire on 2018-04-26. To obtain a new or tweaked
   version of this certificate in the future, simply run certbot
   again. To non-interactively renew *all* of your certificates, run
   "certbot renew"

During this process I just created the requested files in a war deployed into the Wildfly:

Importing the private key with the fetches certificate into a Java key store (jks)

The following files has been created:

  • privkey.pem - Private key for the certificate.
  • fullchain.pem - The server certificate followed by intermediate certificates that web browsers use to validate the server certificate.

However, Wildfly 10 accepts only jks. So the fullchain.pem has to be imported into jks. However, keytool can import a certificate or an entire keystore, but does not import a private key separated from the paired public key with the certificate. Therefore, a private key has to be combined with the certificate in a acceptable PKCS12 keystore with openssl command. Then the keystore can be imported into jks. The keystore will be created in /opt/SSLCertificates/

cd /opt/SSLCertificates/
openssl pkcs12 -export -in /etc/letsencrypt/live/food-diary-online.com/fullchain.pem -inkey /etc/letsencrypt/live/food-diary-online.com/privkey.pem -out keystore.p12 -name wildfly -passout pass:changeit

changeit is the password for the keystrore to be created. File keystore.p12 is created.

keytool -importkeystore -deststorepass changeit -destkeypass changeit -destkeystore keystore.jks -srckeystore keystore.p12 -srcstoretype PKCS12 -srcstorepass changeit -v -noprompt

Jks keystore.jks is created when the command is executed for the first time. During the second import the existing alias will be overwritten:

Configuring SSL in Wildfly

Stop the server and edit standalone.xml so that it contains:

<security-realm name="ApplicationRealm">
    <server-identities>
     <ssl>
            <keystore path="/opt/SSLCertificates/keystore.jks" keystore-password="changeit" alias="wildfly" key-password="changeit"/>
        </ssl>
    </server-identities>
    <authentication>
        <local default-user="$local" allowed-users="*" skip-group-loading="true"/>
        <properties path="application-users.properties" relative-to="jboss.server.config.dir"/>
    </authentication>
    <authorization>
        <properties path="application-roles.properties" relative-to="jboss.server.config.dir"/>
    </authorization>
</security-realm>

Start the server. Well, that's it, we're done.

Automatic certificate renewal Incomplete yet

The only problem with the Let's Encrypt certificates is that they last for 90 days, so they have to be regularly renewed. This can be achieved with a script scheduled in crontab.

cerbot renew command attempts to renew any previously-obtained certificates that expire in less than 30 days. The same plugin and options that were used at the time the certificate was originally issued will be used for the renewal attempt, unless you specify other plugins or options. renew can be run as frequently as you want since it will usually take no action.

I created deployhook.sh script in /opt/SSLCertificates/. The script merely executes the commands used above to import the certificates into the java keystore and restart wildfly.

#!/bin/bash

service wildfly stop

cd /opt/SSLCertificates/
openssl pkcs12 -export -in /etc/letsencrypt/live/food-diary-online.com/fullchain.pem -inkey /etc/letsencrypt/live/food-diary-online.com/privkey.pem -out keystore.p12 -name wildfly -passout pass:changeit

keytool -importkeystore -deststorepass changeit -destkeypass changeit -destkeystore keystore.jks -srckeystore keystore.p12 -srcstoretype PKCS12 -srcstorepass changeit -v -noprompt

service wildfly start

Now, to renew certificates one single-line command is sufficient. The script indicated by --deploy-hook is executed only after a successful certificate renewal.

sudo certbot renew --deploy-hook deployhook.sh

The output of the command:

[centos@ip-172-31-42-159 SSLCertificates]$ sudo certbot renew --deploy-hook ./deployhook.sh
Saving debug log to /var/log/letsencrypt/letsencrypt.log

-------------------------------------------------------------------------------
Processing /etc/letsencrypt/renewal/food-diary-online.com.conf
-------------------------------------------------------------------------------
Cert is due for renewal, auto-renewing...
Plugins selected: Authenticator manual, Installer None
Starting new HTTPS connection (1): acme-v01.api.letsencrypt.org
Renewing an existing certificate
Performing the following challenges:
http-01 challenge for food-diary-online.com
http-01 challenge for www.food-diary-online.com
Waiting for verification...
Cleaning up challenges
Running deploy-hook command: ./deployhook.sh
Output from deployhook.sh:
Stopping wildfly (via systemctl):  [  OK  ]
Starting wildfly (via systemctl):  [  OK  ]

Error output from deployhook.sh:
Warning: Overwriting existing alias wildfly in destination keystore
Entry for alias wildfly successfully imported.
Import command completed:  1 entries successfully imported, 0 entries failed or cancelled
[Storing keystore.jks]


-------------------------------------------------------------------------------
new certificate deployed without reload, fullchain is
/etc/letsencrypt/live/food-diary-online.com/fullchain.pem
-------------------------------------------------------------------------------

-------------------------------------------------------------------------------

Congratulations, all renewals succeeded. The following certs have been renewed:
  /etc/letsencrypt/live/food-diary-online.com/fullchain.pem (success)
-------------------------------------------------------------------------------
[centos@ip-172-31-42-159 SSLCertificates]$

To automate the renewals, the command has to be scheduled in the crontab.

Monday, January 22, 2018

How to activate gzip compression of selected content types in Tomcat or Wildfly

Another note for myself. To enable gzip compression in tomcat add an additional attribute to Connector tag in CATALINA_HOME/conf/server.xml:

<Connector port="8080" protocol="HTTP/1.1" connectionTimeout="20000" redirectPort="443" compressibleMimeType="application/javascript,text/css,application/json" compression="on"/>

Wildfly is not so well documented as Tomcat. So this note assembled from pieces of information saves time. Essentially one needs to enable and configure gzipFilter using Undertow predicates. Edit the default configuration file standalone.xml:

<subsystem xmlns="urn:jboss:domain:undertow:3.1">
    <buffer-cache name="default"/>
    <server name="default-server">
        <http-listener name="default" socket-binding="http" redirect-socket="https" enable-http2="true"/>
        <https-listener name="https" socket-binding="https" security-realm="ApplicationRealm" enable-http2="true"/>
        <host name="default-host" alias="localhost">
            <location name="/" handler="welcome-content"/>
            <access-log pattern="%h %t "%r" %s "%{i,User-Agent}"" prefix="myaccess."/>
            <filter-ref name="gzipfilter" predicate="regex[pattern='text/html|text/css|application/javascript|application/json',value=%{o,Content-Type}] and max-content-size[value=1024]"/>
        </host>
    </server>
    <servlet-container name="default">
        <jsp-config/>
        <persistent-sessions path="sessions" relative-to="jboss.server.temp.dir"/>
        <websockets/>
    </servlet-container>
    <handlers>
        <file name="welcome-content" path="${jboss.home.dir}/welcome-content"/>
    </handlers>
    <filters>
        <gzip name="gzipfilter"/>
    </filters>
</subsystem>

All the possible predicates are listed in Undertow documentation. Some people use url-based predicates like:

<filter-ref name="gzipFilter" predicate="path-suffix['.css'] or path-suffix['.js']" />

Alternatively, one can use a custom gzip compression servlet filter that can be more easily configured to target some specific output. A working example is in GitHub. I keed this sample only because it works well and its GIPOutputStream potentially could be replaced by some other stream to for example encrypt the output or produce hashes.