Using SSL With Native Executables
We are quickly moving to an SSL-everywhere world so being able to use SSL is crucial.
In this guide, we will discuss how you can get your native executables to support SSL, as native executables don’t support it out of the box.
If you don’t plan on using native executables, you can pass your way as in JDK mode, SSL is supported without further manipulations. |
Prerequisites
To complete this guide, you need:
-
less than 20 minutes
-
an IDE
-
GraalVM installed with
JAVA_HOME
andGRAALVM_HOME
configured appropriately -
Apache Maven 3.9.9
This guide is based on the REST client guide, so you should get this Maven project first.
Clone the Git repository: git clone https://github.com/quarkusio/quarkus-quickstarts.git
, or download an archive.
The project is located in the resteasy-client-quickstart
directory.
Looks like it works out of the box?!?
If you open the application’s configuration file (src/main/resources/application.properties
), you can see the following line:
quarkus.rest-client."org.acme.rest.client.ExtensionsService".url=https://stage.code.quarkus.io/api
which configures our REST client to connect to an SSL REST service.
For the purposes of this guide, we also need to remove the configuration that starts the embedded WireMock server that stubs REST client responses so the tests actually propagate calls to the https://stage.code.quarkus.io/api. Update the test file src/test/java/org/acme/rest/client/ExtensionsResourceTest.java
and remove the line:
@QuarkusTestResource(WireMockExtensions.class)
from the ExtensionsResourceTest
class.
Now let’s build the application as a native executable and run the tests:
quarkus build --native
./mvnw install -Dnative
And we obtain the following result:
[INFO] ------------------------------------------------------------------------
[INFO] BUILD SUCCESS
[INFO] ------------------------------------------------------------------------
So, yes, it appears it works out of the box and this guide is pretty useless.
It’s not. The magic happens when building the native executable:
[INFO] [io.quarkus.creator.phase.nativeimage.NativeImagePhase] /opt/graalvm/bin/native-image -J-Dsun.nio.ch.maxUpdateArraySize=100 -J-Djava.util.logging.manager=org.jboss.logmanager.LogManager -J-Dio.netty.leakDetection.level=DISABLED -J-Dio.netty.allocator.maxOrder=3 -J-Dvertx.logger-delegate-factory-class-name=io.quarkus.vertx.core.runtime.VertxLogDelegateFactory -J-Dvertx.disableDnsResolver=true -J-Duser.language=en -J-Duser.country=IE -J-Dfile.encoding=UTF-8 --features=io.quarkus.runner.Feature,io.quarkus.runtime.graal.ResourcesFeature,io.quarkus.runtime.graal.DisableLoggingFeature -J--add-exports=java.security.jgss/sun.security.krb5=ALL-UNNAMED -J--add-opens=java.base/java.text=ALL-UNNAMED -J--add-opens=java.base/java.io=ALL-UNNAMED -J--add-opens=java.base/java.lang.invoke=ALL-UNNAMED -J--add-opens=java.base/java.util=ALL-UNNAMED -H:InitialCollectionPolicy=com.oracle.svm.core.genscavenge.CollectionPolicy\$BySpaceAndTime -H:+AllowFoldMethods -J-Djava.awt.headless=true -H:FallbackThreshold=0 --link-at-build-time -H:+ReportExceptionStackTraces -H:-AddAllCharsets -H:EnableURLProtocols=http,https -H:NativeLinkerOption=-no-pie -H:-UseServiceLoaderFeature -H:+StackTrace -J--add-exports=org.graalvm.sdk/org.graalvm.nativeimage.impl=ALL-UNNAMED -J--add-exports=org.graalvm.nativeimage.builder/com.oracle.svm.core.jdk=ALL-UNNAMED -J--add-exports=org.graalvm.nativeimage.builder/com.oracle.svm.core.configure=ALL-UNNAMED -J--add-exports=org.graalvm.nativeimage.builder/com.oracle.svm.core.jdk.proxy=ALL-UNNAMED -J--add-exports=org.graalvm.nativeimage.builder/com.oracle.svm.core.jdk.localization=ALL-UNNAMED rest-client-quickstart-1.0.0-SNAPSHOT-runner -jar rest-client-quickstart-1.0.0-SNAPSHOT-runner.jar
The important part is the following option that was automatically added by Quarkus:
-H:EnableURLProtocols=http,https
It enables the native SSL support for your native executable. But you should not set it manually, we have a nice configuration property for this purpose as described below.
As SSL is de facto the standard nowadays, we decided to enable its support automatically for some of our extensions:
-
the Agroal connection pooling extension (
quarkus-agroal
), -
the Amazon Services extension (
quarkus-amazon-*
), -
the Consul Config extension (
quarkus-config-consul
), -
the Elasticsearch client extensions (
quarkus-elasticsearch-rest-client
andquarkus-elasticsearch-java-client
) and thus the Hibernate Search Elasticsearch extension (quarkus-hibernate-search-orm-elasticsearch
), -
the Elytron Security OAuth2 extension (
quarkus-elytron-security-oauth2
), -
the gRPC extension (
quarkus-grpc
), -
the Infinispan Client extension (
quarkus-infinispan-client
). -
the Jaeger extension (
quarkus-jaeger
), -
the JGit extension (
quarkus-jgit
), -
the JSch extension (
quarkus-jsch
), -
the Kafka Client extension (
quarkus-kafka-client
), if Apicurio Registry 2.x Avro library is used -
the Keycloak Authorization extension (
quarkus-keycloak-authorization
), -
the Kubernetes client extension (
quarkus-kubernetes-client
), -
the Logging Sentry extension (
quarkus-logging-sentry
), -
the Mailer extension (
quarkus-mailer
), -
the MongoDB client extension (
quarkus-mongodb-client
), -
the Neo4j extension (
quarkus-neo4j
), -
the OIDC and OIDC client extensions (
quarkus-oidc
andquarkus-oidc-client
), -
the Reactive client for IBM DB2 extension (
quarkus-reactive-db2-client
), -
the Reactive client for PostgreSQL extension (
quarkus-reactive-pg-client
), -
the Reactive client for MySQL extension (
quarkus-reactive-mysql-client
), -
the Reactive client for Microsoft SQL Server extension (
quarkus-reactive-mssql-client
), -
the Redis client extension (
quarkus-redis-client
), -
the RESTEasy Classic REST Client extension (
quarkus-resteasy-client
), -
the REST Client extension (
quarkus-rest-client
), -
the SmallRye GraphQL Client extension (
quarkus-smallrye-graphql-client
), -
the Spring Cloud Config client extension (
quarkus-spring-cloud-config-client
), -
the Vault extension (
quarkus-vault
), -
the Cassandra client extensions (
cassandra-quarkus-client
)
As long as you have one of these extensions in your project, the SSL support will be enabled by default.
If you are not using any of them and you want to enable SSL support anyway, please add the following to your configuration:
quarkus.ssl.native=true
Now, let’s just check the size of our native executable as it will be useful later:
$ ls -lh target/resteasy-client-quickstart-1.0.0-SNAPSHOT-runner
-rwxrwxr-x. 1 gandrian gandrian 46M Jun 11 13:01 target/rest-client-quickstart-1.0.0-SNAPSHOT-runner
Let’s disable SSL and see how it goes
Quarkus has an option to disable the SSL support entirely. Why? Because it comes at a certain cost. So if you are sure you don’t need it, you can disable it entirely.
First, let’s disable it without changing the REST service URL and see how it goes.
Open src/main/resources/application.properties
and add the following line:
quarkus.ssl.native=false
And let’s try to build again:
quarkus build --native
./mvnw install -Dnative
The native executable tests will fail with the following error:
Caused by: java.lang.IllegalArgumentException: https://stage.code.quarkus.io/api requires SSL support but it is disabled. You probably have set quarkus.ssl.native to false.
This error is the one you obtain when trying to use SSL while it was not explicitly enabled in your native executable.
Now, let’s change the REST service URL to not use SSL in src/main/resources/application.properties
:
quarkus.rest-client."org.acme.rest.client.ExtensionsService".url=http://stage.code.quarkus.io/api
and since http://stage.code.quarkus.io/api responds with 302 status code we need to also skip the tests with -DskipTests
.
Now we can build again:
quarkus build --native -DskipTests
./mvnw install -Dnative -DskipTests
If you check carefully the native executable build options, you can see that the SSL related options are gone:
[INFO] [io.quarkus.creator.phase.nativeimage.NativeImagePhase] /opt/graalvm/bin/native-image -J-Djava.util.logging.manager=org.jboss.logmanager.LogManager -J-Dcom.sun.xml.internal.bind.v2.bytecode.ClassTailor.noOptimize=true -H:InitialCollectionPolicy=com.oracle.svm.core.genscavenge.CollectionPolicy$BySpaceAndTime -jar rest-client-1.0.0-SNAPSHOT-runner.jar -J-Djava.util.concurrent.ForkJoinPool.common.parallelism=1 -H:+PrintAnalysisCallTree -H:EnableURLProtocols=http -H:-SpawnIsolates -H:+JNI --no-server -H:-UseServiceLoaderFeature -H:+StackTrace
And we end up with:
[INFO] ------------------------------------------------------------------------
[INFO] BUILD SUCCESS
[INFO] ------------------------------------------------------------------------
You remember we checked the size of the native executable with SSL enabled? Let’s check again with SSL support entirely disabled:
$ ls -lh target/resteasy-client-quickstart-1.0.0-SNAPSHOT-runner
-rwxrwxr-x. 1 gandrian gandrian 35M Jun 11 13:06 target/resteasy-client-quickstart-1.0.0-SNAPSHOT-runner
Yes, it is now 35 MB whereas it used to be 46 MB. SSL comes with an 11 MB overhead in native executable size.
And there’s more to it.
Let’s start again with a clean slate
Let’s revert the changes we made to the configuration file and go back to SSL with the following command:
git checkout -- src/main/resources/application.properties
And let’s build the native executable again:
quarkus build --native
./mvnw install -Dnative
The TrustStore path
This behavior is new to GraalVM 21.3+. |
GraalVM supports both build time and runtime certificate configuration.
Build time configuration
The build time approach favors the principle of "immutable security" where the appropriate certificates are added at build time, and can never be changed afterward. This guarantees that the list of valid certificates cannot be tampered with when the application gets deployed in production.
However, this comes with a few drawbacks:
-
If you use the same executable in all environments, and a certificate expires, the application needs to be rebuilt, and redeployed into production with the new certificate, which is an inconvenience.
-
Even worse, if a certificate gets revoked because of a security breach, all applications that embed this certificate need to be rebuilt and redeployed in a timely manner.
-
This requires also to add into the application all certificates for all environments (e.g.
dev
,test
,prod
), which means that a certificate that is required for dev mode but should not be used elsewhere, will make its way anyway in production. -
Providing all certificates at build time complicates the CI, specifically in dynamic environments such as Kubernetes where valid certificates are provided by the platform in the
/var/run/secrets/kubernetes.io/serviceaccount/ca.crt
PEM file. -
Lastly, this does not play well with third party software that do not provide a dedicated build for each customer environment.
Creating a native executable using build time certificates essentially means that the root certificates are fixed at image build time, based on the certificate configuration used at build time (which for Quarkus means when you perform a build having quarkus.native.enabled=true
set).
This avoids shipping a cacerts
file or requiring a system property be set in order to set up root certificates that are provided by the OS where the binary runs.
In this situation, system properties such as javax.net.ssl.trustStore
do not have an effect at
run time, so when the defaults need to be changed, these system properties must be provided at image build time.
The easiest way to do so is by setting quarkus.native.additional-build-args
. For example:
quarkus.native.additional-build-args=-J-Djavax.net.ssl.trustStore=/tmp/mycerts,-J-Djavax.net.ssl.trustStorePassword=changeit
will ensure that the certificates of /tmp/mycerts
are baked into the native binary and used instead of the default cacerts
.
The file containing the custom TrustStore does not (and probably should not) have to be present at runtime as its content has been baked into the native binary.
Run time configuration
Using the runtime certificate configuration, supported by GraalVM since 21.3 does not require any special or additional configuration compared to regular java programs or Quarkus in jvm mode. For more information, see the Runtime Options section of the "GraalVM Certificate Management in Native Image" guide.