Using SSL With Native Executables

我们正在迅速发展到一个无处不在的 SSL 世界,因此能够使用 SSL 至关重要。 在本指南中,我们将讨论如何让你的本机可执行文件支持 SSL,因为本机可执行文件开箱即用时并不支持 SSL。

如果你不打算使用本机可执行文件,你可以使用 JDK 模式,SSL 在这种模式下受支持,无需进一步操作。

Prerequisites

如要完成本指南,您需要:

  • less than 20 minutes

  • an IDE

  • GraalVM 已安装,并且 JAVA_HOMEGRAALVM_HOME 已适当地配置

  • Apache Maven ${proposed-maven-version}

本指南基于 REST 客户端指南,因此你应该先获取此 Maven 项目。

克隆 Git 存储库: git clone $${quickstarts-base-url}.git,或下载 $${quickstarts-base-url}/archive/main.zip[存档]。

该项目位于 resteasy-client-quickstart directory 中。

Looks like it works out of the box?!?

如果你打开该应用程序的配置文件 (src/main/resources/application.properties),可以看到以下行:

quarkus.rest-client."org.acme.rest.client.ExtensionsService".url=https://stage.code.quarkus.io/api

它配置我们的 REST 客户端以连接到 SSL REST 服务。

出于本指南的目的,我们还需要删除启动嵌入式 WireMock 服务器(用于截取 REST 客户端响应,以便测试实际传播调用)的配置 [role="bare"][role="bare"]https://stage.code.quarkus.io/api。更新测试文件 src/test/java/org/acme/rest/client/ExtensionsResourceTest.java,并删除以下行:

@WithTestResource(WireMockExtensions.class)

来自 ExtensionsResourceTest 类。

现在,让我们将应用程序构建为本机可执行文件并运行测试:

CLI
quarkus build --native
Maven
./mvnw install -Dnative
Gradle
./gradlew build -Dquarkus.native.enabled=true

我们会获得以下结果:

[INFO] ------------------------------------------------------------------------
[INFO] BUILD SUCCESS
[INFO] ------------------------------------------------------------------------

所以是的,它似乎可以开箱即用,本指南相当无用。

但是,事实并非如此。构建本机可执行文件时会出现以下问题:

[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

重要部分是 Quarkus 自动添加的以下选项:

-H:EnableURLProtocols=http,https

它为您的本机可执行文件启用了本机 SSL 支持。但是您不应该手动设置它,我们为此目的提供了一个友好的配置属性,如以下所述。

由于 SSL 如今实际上是标准,我们决定为以下某些扩展自动启用对其的支持:

  • Agroal 连接池扩展 (quarkus-agroal)、

  • Amazon 服务扩展 (quarkus-amazon-*)、

  • Consul Config 扩展 (quarkus-config-consul)、

  • Elasticsearch 客户端扩展 (quarkus-elasticsearch-rest-client`和 `quarkus-elasticsearch-java-client),因此还有 Hibernate Search Elasticsearch 扩展 (quarkus-hibernate-search-orm-elasticsearch)、

  • Elytron Security OAuth2 扩展 (quarkus-elytron-security-oauth2)、

  • the gRPC extension (quarkus-grpc),

  • Infinispan 客户端扩展 (quarkus-infinispan-client)。

  • the Jaeger extension (quarkus-jaeger),

  • the JGit extension (quarkus-jgit),

  • the JSch extension (quarkus-jsch),

  • Kafka 客户端扩展 (quarkus-kafka-client),如果使用了 Apicurio Registry 2.x Avro 库

  • Keycloak 授权扩展 (quarkus-keycloak-authorization)、

  • Kubernetes 客户端扩展 (quarkus-kubernetes-client)、

  • 日志 Sentry 扩展 (quarkus-logging-sentry)、

  • the Mailer extension (quarkus-mailer),

  • MongoDB 客户端扩展 (quarkus-mongodb-client)、

  • the Neo4j extension (quarkus-neo4j),

  • OIDC 和 OIDC 客户端扩展 (quarkus-oidc`和 `quarkus-oidc-client)、

  • IBM DB2 扩展的 Reactive 客户端 (quarkus-reactive-db2-client)、

  • PostgreSQL 扩展的 Reactive 客户端 (quarkus-reactive-pg-client)、

  • MySQL 扩展的 Reactive 客户端 (quarkus-reactive-mysql-client)、

  • Microsoft SQL Server 扩展的 Reactive 客户端 (quarkus-reactive-mssql-client),

  • Redis 客户端扩展 (quarkus-redis-client)

  • RESTEasy Classic REST Client 扩展 (quarkus-resteasy-client)

  • REST Client 扩展 (quarkus-rest-client)

  • SmallRye GraphQL Client 扩展 (quarkus-smallrye-graphql-client)

  • Spring Cloud Config 客户端扩展 (quarkus-spring-cloud-config-client)

  • the Vault extension (quarkus-vault),

  • Cassandra 客户端扩展 (cassandra-quarkus-client)

只要在项目中有一个这些扩展,则默认情况下会启用 SSL 支持。

如果您不使用其中任何一个,而您仍希望启用 SSL 支持,请将以下内容添加到配置中:

quarkus.ssl.native=true

现在,我们只检查本机可执行文件的大小,因为这在后面会用到:

$ 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 有一个选项可以完全禁用 SSL 支持。为什么?因为它有一定的代价。因此,如果您确定不需要它,则可以完全禁用它。

首先,我们先禁用它,而不更改 REST 服务 URL,看看会怎样。

打开 `src/main/resources/application.properties`并添加以下行:

quarkus.ssl.native=false

我们再试着构建:

CLI
quarkus build --native
Maven
./mvnw install -Dnative
Gradle
./gradlew build -Dquarkus.native.enabled=true

本机可执行文件测试将会失败,并出现以下错误:

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.

当在没有在您的本机可执行文件中明确启用 SSL 而尝试使用 SSL 时,您将获得这个错误。

现在,我们更改 REST 服务 URL 为 *not*在 `src/main/resources/application.properties`中使用 SSL:

quarkus.rest-client."org.acme.rest.client.ExtensionsService".url=http://stage.code.quarkus.io/api

而且,由于 [role="bare"][role="bare"]http://stage.code.quarkus.io/api使用 302 状态码进行响应,我们需要使用 `-DskipTests`跳过测试。

现在我们可以再构建:

include::./_includes/devtools/build-native.adoc[]:!build-additional-parameters:

如果您仔细检查本机可执行文件构建选项,您会发现 SSL 相关选项已消失:

[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

结果为:

[INFO] ------------------------------------------------------------------------
[INFO] BUILD SUCCESS
[INFO] ------------------------------------------------------------------------

你是否记得我们之前检查了启用 SSL 时的本机可执行文件大小?让我们在完全禁用 SSL 支持的情况下再次检查:

$ 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

是的,它现在是 35 MB,以前是 46 MB。SSL 在本机可执行文件大小中产生 11 MB 的开销。

而且还有更多。

Let’s start again with a clean slate

让我们还原我们对配置文件所做的更改,并使用以下命令返回到 SSL:

git checkout -- src/main/resources/application.properties

再编译本机可执行文件:

CLI
quarkus build --native
Maven
./mvnw install -Dnative
Gradle
./gradlew build -Dquarkus.native.enabled=true

The TrustStore path

这种做法对 GraalVM 21.3+ 是新引入的。

GraalVM 支持构建时间和运行时证书配置。

Build time configuration

构建时间方法采用“不可变安全性” 的原则,其中适当的证书在构建时添加,之后永远不会被更改。这保证了在应用程序在生产环境中部署时,有效证书的列表不会被篡改。

然而,这也会带来一些缺点:

  • 如果你在所有环境中使用相同的可执行文件,并且证书过期,则需要重新构建应用程序,并使用新证书将其重新部署到生产环境中,这很麻烦。

  • 更糟糕的是,如果由于安全漏洞而吊销证书,则嵌入此证书的所有应用程序都需要及时地重新构建和重新部署。

  • 这也需要将所有环境的所有证书添加到应用程序中(例如 devtestprod),这意味着在开发模式下需要的证书在其他地方不应该使用,但它仍然会进入生产环境。

  • 在构建时提供所有证书会使 CI 变得复杂,特别是在 Kubernetes 等动态环境中,而平台在 `/var/run/secrets/kubernetes.io/serviceaccount/ca.crt`PEM 文件中提供了有效证书。

  • 最后,对于不为每个客户环境提供专用构建的第三方软件,这也不是很好的方法。

使用构建时间证书创建本机可执行文件本质上意味着根证书在映像构建时被修复,基于构建时使用的证书配置(对于 Quarkus 来说,这意味着当你执行具有 `quarkus.native.enabled=true`设置的构建时)。这样做无需发送一个 `cacerts`文件或要求设置系统属性以便设置 OS 在其中运行二进制文件的提供的根证书。

在这种情况下,系统属性(如 javax.net.ssl.trustStore)在运行时不起作用,因此当需要更改默认值时,这些系统属性必须在映像构建时提供。这样做的最简单方法是设置 quarkus.native.additional-build-args。例如:

quarkus.native.additional-build-args=-J-Djavax.net.ssl.trustStore=/tmp/mycerts,-J-Djavax.net.ssl.trustStorePassword=changeit

将确保 `/tmp/mycerts`的证书被烘焙到本机二进制文件中,并使用默认的 `cacerts`的 instead。包含自定义信任库的文件 not(可能也不应该)在运行时存在,因为其内容已被烘焙到本机二进制文件中。

Run time configuration

从 GraalVM 21.3 开始支持的运行时证书配置与常规 Java 程序或 JVM 模式下的 Quarkus 相比,不需要任何特殊或额外的配置。有关更多信息,请阅读“本机映像中的 GraalVM 证书管理”指南的 Runtime Options部分。

Working with containers

在容器中运行本机二进制文件时无需采取特殊操作。如果本机二进制文件通过前一节中描述的自定义信任库正确构建,它还将在容器中正常工作。

Conclusion

我们通过 SSL 对本机可执行文件进行构建变得简单,并提供了多种选择以应对不同类型的安全需求。