Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fix trustStore handling in native-image #9906

Merged
merged 4 commits into from
Jun 15, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -2,14 +2,19 @@

import java.util.Optional;

import io.quarkus.deployment.annotations.BuildProducer;
import io.quarkus.deployment.annotations.BuildStep;
import io.quarkus.deployment.builditem.SslNativeConfigBuildItem;
import io.quarkus.deployment.builditem.nativeimage.RuntimeReinitializedClassBuildItem;
import io.quarkus.runtime.annotations.ConfigItem;
import io.quarkus.runtime.annotations.ConfigPhase;
import io.quarkus.runtime.annotations.ConfigRoot;

public class SslProcessor {

private static final String JAVA_11_PLUS_SSL_LOGGER = "sun.security.ssl.SSLLogger";
private static final String JAVA_8_PLUS_SSL_LOGGER = "sun.security.ssl.Debug";

SslConfig ssl;

@ConfigRoot(phase = ConfigPhase.BUILD_TIME)
Expand All @@ -25,4 +30,19 @@ static class SslConfig {
SslNativeConfigBuildItem setupNativeSsl() {
return new SslNativeConfigBuildItem(ssl.native_);
}

@BuildStep
void runtime(BuildProducer<RuntimeReinitializedClassBuildItem> reinitialized) {
registerIfExists(reinitialized, JAVA_11_PLUS_SSL_LOGGER);
registerIfExists(reinitialized, JAVA_8_PLUS_SSL_LOGGER);
}

private void registerIfExists(BuildProducer<RuntimeReinitializedClassBuildItem> reinitialized, String className) {
try {
Class.forName(className);
reinitialized.produce(new RuntimeReinitializedClassBuildItem(className));
} catch (ClassNotFoundException ignored) {

}
}
}

This file was deleted.

Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.UncheckedIOException;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Path;
Expand Down Expand Up @@ -61,6 +62,9 @@ public class NativeImageBuildStep {

private static final int OOM_ERROR_VALUE = 137;
private static final String QUARKUS_XMX_PROPERTY = "quarkus.native.native-image-xmx";
private static final String CONTAINER_BUILD_VOLUME_PATH = "/project";
private static final String TRUST_STORE_SYSTEM_PROPERTY_MARKER = "-Djavax.net.ssl.trustStore=";
private static final String MOVED_TRUST_STORE_NAME = "trustStore";

@BuildStep(onlyIf = NativeBuild.class)
ArtifactResultBuildItem result(NativeImageBuildItem image) {
Expand All @@ -83,7 +87,8 @@ public NativeImageBuildItem build(NativeConfig nativeConfig, NativeImageSourceJa

String noPIE = "";

if (nativeConfig.containerRuntime.isPresent() || nativeConfig.containerBuild) {
boolean isContainerBuild = nativeConfig.containerRuntime.isPresent() || nativeConfig.containerBuild;
if (isContainerBuild) {
String containerRuntime = nativeConfig.containerRuntime.orElse("docker");
// E.g. "/usr/bin/docker run -v {{PROJECT_DIR}}:/project --rm quarkus/graalvm-native-image"
nativeImage = new ArrayList<>();
Expand All @@ -92,7 +97,8 @@ public NativeImageBuildItem build(NativeConfig nativeConfig, NativeImageSourceJa
if (IS_WINDOWS) {
outputPath = FileUtil.translateToVolumePath(outputPath);
}
Collections.addAll(nativeImage, containerRuntime, "run", "-v", outputPath + ":/project:z", "--env", "LANG=C");
Collections.addAll(nativeImage, containerRuntime, "run", "-v",
outputPath + ":" + CONTAINER_BUILD_VOLUME_PATH + ":z", "--env", "LANG=C");

if (IS_LINUX) {
if ("docker".equals(containerRuntime)) {
Expand Down Expand Up @@ -227,7 +233,7 @@ public NativeImageBuildItem build(NativeConfig nativeConfig, NativeImageSourceJa
nativeConfig.enableAllSecurityServices = true;
}

nativeConfig.additionalBuildArgs.ifPresent(l -> l.stream().map(String::trim).forEach(command::add));
handleAdditionalProperties(nativeConfig, command, isContainerBuild, outputDir);
nativeConfig.resources.includes.ifPresent(l -> l.stream()
.map(GlobUtil::toRegexPattern)
.map(re -> "-H:IncludeResources=" + re.trim())
Expand Down Expand Up @@ -343,7 +349,7 @@ public NativeImageBuildItem build(NativeConfig nativeConfig, NativeImageSourceJa
if (exitCode != 0) {
throw imageGenerationFailed(exitCode, command);
}
if (IS_WINDOWS && !(nativeConfig.containerRuntime.isPresent() || nativeConfig.containerBuild)) {
if (IS_WINDOWS && !(isContainerBuild)) {
//once image is generated it gets added .exe on Windows
executableName = executableName + ".exe";
}
Expand All @@ -359,6 +365,40 @@ public NativeImageBuildItem build(NativeConfig nativeConfig, NativeImageSourceJa
}
}

private void handleAdditionalProperties(NativeConfig nativeConfig, List<String> command, boolean isContainerBuild,
Path outputDir) {
if (nativeConfig.additionalBuildArgs.isPresent()) {
List<String> strings = nativeConfig.additionalBuildArgs.get();
for (String buildArg : strings) {
String trimmedBuildArg = buildArg.trim();
if (trimmedBuildArg.contains(TRUST_STORE_SYSTEM_PROPERTY_MARKER) && isContainerBuild) {
/*
* When the native binary is being built with a docker container, because a volume is created,
* we need to copy the trustStore file into the output directory (which is the root of volume)
* and change the value of 'javax.net.ssl.trustStore' property to point to this value
*
* TODO: we might want to introduce a dedicated property in order to overcome this ugliness
*/
int index = trimmedBuildArg.indexOf(TRUST_STORE_SYSTEM_PROPERTY_MARKER);
if (trimmedBuildArg.length() > index + 2) {
String configuredTrustStorePath = trimmedBuildArg
.substring(index + TRUST_STORE_SYSTEM_PROPERTY_MARKER.length());
try {
IoUtils.copy(Paths.get(configuredTrustStorePath), outputDir.resolve(MOVED_TRUST_STORE_NAME));
command.add(trimmedBuildArg.substring(0, index) + TRUST_STORE_SYSTEM_PROPERTY_MARKER
+ CONTAINER_BUILD_VOLUME_PATH + "/" + MOVED_TRUST_STORE_NAME);
} catch (IOException e) {
throw new UncheckedIOException("Unable to copy trustStore file '" + configuredTrustStorePath
+ "' to volume root directory '" + outputDir.toAbsolutePath().toString() + "'", e);
}
}
} else {
command.add(trimmedBuildArg);
}
}
}
}

private RuntimeException imageGenerationFailed(int exitValue, List<String> command) {
if (exitValue == OOM_ERROR_VALUE) {
if (command.contains("docker") && !IS_LINUX) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,9 @@
import java.io.File;
import java.lang.reflect.Modifier;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
Expand Down Expand Up @@ -43,7 +45,6 @@
import io.quarkus.deployment.builditem.MainClassBuildItem;
import io.quarkus.deployment.builditem.ObjectSubstitutionBuildItem;
import io.quarkus.deployment.builditem.QuarkusApplicationClassBuildItem;
import io.quarkus.deployment.builditem.SslTrustStoreSystemPropertyBuildItem;
import io.quarkus.deployment.builditem.StaticBytecodeRecorderBuildItem;
import io.quarkus.deployment.builditem.SystemPropertyBuildItem;
import io.quarkus.deployment.builditem.nativeimage.ReflectiveClassBuildItem;
Expand Down Expand Up @@ -76,7 +77,15 @@ class MainClassBuildStep {
static final String STARTUP_CONTEXT = "STARTUP_CONTEXT";
static final String LOG = "LOG";
static final String JAVA_LIBRARY_PATH = "java.library.path";
static final String JAVAX_NET_SSL_TRUST_STORE = "javax.net.ssl.trustStore";

private static final String JAVAX_NET_SSL_TRUST_STORE = "javax.net.ssl.trustStore";
private static final String JAVAX_NET_SSL_TRUST_STORE_TYPE = "javax.net.ssl.trustStoreType";
private static final String JAVAX_NET_SSL_TRUST_STORE_PROVIDER = "javax.net.ssl.trustStoreProvider";
private static final String JAVAX_NET_SSL_TRUST_STORE_PASSWORD = "javax.net.ssl.trustStorePassword";
private static final List<String> BUILD_TIME_TRUST_STORE_PROPERTIES = Collections.unmodifiableList(Arrays.asList(
JAVAX_NET_SSL_TRUST_STORE,
JAVAX_NET_SSL_TRUST_STORE_TYPE, JAVAX_NET_SSL_TRUST_STORE_PROVIDER,
JAVAX_NET_SSL_TRUST_STORE_PASSWORD));

private static final FieldDescriptor STARTUP_CONTEXT_FIELD = FieldDescriptor.of(Application.APP_CLASS_NAME, STARTUP_CONTEXT,
StartupContext.class);
Expand All @@ -87,7 +96,6 @@ void build(List<StaticBytecodeRecorderBuildItem> staticInitTasks,
List<MainBytecodeRecorderBuildItem> mainMethod,
List<SystemPropertyBuildItem> properties,
List<JavaLibraryPathAdditionalPathBuildItem> javaLibraryPathAdditionalPaths,
Optional<SslTrustStoreSystemPropertyBuildItem> sslTrustStoreSystemProperty,
List<FeatureBuildItem> features,
BuildProducer<ApplicationClassNameBuildItem> appClassNameProducer,
List<BytecodeRecorderObjectLoaderBuildItem> loaders,
Expand Down Expand Up @@ -184,19 +192,24 @@ void build(List<StaticBytecodeRecorderBuildItem> staticInitTasks,
mv.invokeVirtualMethod(ofMethod(StringBuilder.class, "toString", String.class), javaLibraryPath));
}

if (sslTrustStoreSystemProperty.isPresent()) {
ResultHandle alreadySetTrustStore = mv.invokeStaticMethod(
ofMethod(System.class, "getProperty", String.class, String.class),
mv.load(JAVAX_NET_SSL_TRUST_STORE));

BytecodeCreator inGraalVMCode = mv
.ifNonZero(mv.invokeStaticMethod(ofMethod(ImageInfo.class, "inImageRuntimeCode", boolean.class)))
.trueBranch();
BytecodeCreator inGraalVMCode = mv
.ifNonZero(mv.invokeStaticMethod(ofMethod(ImageInfo.class, "inImageRuntimeCode", boolean.class)))
.trueBranch();

inGraalVMCode.ifNull(alreadySetTrustStore).trueBranch().invokeStaticMethod(
ofMethod(System.class, "setProperty", String.class, String.class, String.class),
inGraalVMCode.load(JAVAX_NET_SSL_TRUST_STORE),
inGraalVMCode.load(sslTrustStoreSystemProperty.get().getPath()));
// GraalVM uses the build-time trustStore and bakes the backing classes of the TrustStore into the the native binary,
// so we need to warn users trying to set the trust store related system properties that it won't have an effect
for (String property : BUILD_TIME_TRUST_STORE_PROPERTIES) {
ResultHandle trustStoreSystemProp = inGraalVMCode.invokeStaticMethod(
ofMethod(System.class, "getProperty", String.class, String.class),
mv.load(property));

BytecodeCreator inGraalVMCodeAndTrustStoreSet = inGraalVMCode.ifNull(trustStoreSystemProp).falseBranch();
inGraalVMCodeAndTrustStoreSet.invokeVirtualMethod(
ofMethod(Logger.class, "warn", void.class, Object.class),
inGraalVMCodeAndTrustStoreSet.readStaticField(logField.getFieldDescriptor()),
inGraalVMCodeAndTrustStoreSet.load(String.format(
"Setting the '%s' system property will not have any effect at runtime. Make sure to set this property at build time (for example by setting 'quarkus.native.additional-build-args=-J-D%s=someValue').",
property, property)));
}

mv.invokeStaticMethod(ofMethod(Timing.class, "mainStarted", void.class));
Expand Down
Original file line number Diff line number Diff line change
@@ -1,8 +1,5 @@
package io.quarkus.deployment.steps;

import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;
Expand All @@ -20,7 +17,6 @@
import io.quarkus.deployment.builditem.NativeImageEnableAllCharsetsBuildItem;
import io.quarkus.deployment.builditem.NativeImageEnableAllTimeZonesBuildItem;
import io.quarkus.deployment.builditem.SslNativeConfigBuildItem;
import io.quarkus.deployment.builditem.SslTrustStoreSystemPropertyBuildItem;
import io.quarkus.deployment.builditem.SystemPropertyBuildItem;
import io.quarkus.deployment.builditem.nativeimage.NativeImageConfigBuildItem;
import io.quarkus.deployment.builditem.nativeimage.NativeImageProxyDefinitionBuildItem;
Expand Down Expand Up @@ -51,8 +47,7 @@ void build(SslContextConfigurationRecorder sslContextConfigurationRecorder,
BuildProducer<RuntimeReinitializedClassBuildItem> runtimeReinit,
BuildProducer<NativeImageSystemPropertyBuildItem> nativeImage,
BuildProducer<SystemPropertyBuildItem> systemProperty,
BuildProducer<JavaLibraryPathAdditionalPathBuildItem> javaLibraryPathAdditionalPath,
BuildProducer<SslTrustStoreSystemPropertyBuildItem> sslTrustStoreSystemProperty) {
BuildProducer<JavaLibraryPathAdditionalPathBuildItem> javaLibraryPathAdditionalPath) {
for (NativeImageConfigBuildItem nativeImageConfigBuildItem : nativeImageConfigBuildItems) {
for (String i : nativeImageConfigBuildItem.getRuntimeInitializedClasses()) {
runtimeInit.produce(new RuntimeInitializedClassBuildItem(i));
Expand All @@ -76,21 +71,6 @@ void build(SslContextConfigurationRecorder sslContextConfigurationRecorder,
sslContextConfigurationRecorder.setSslNativeEnabled(!sslNativeConfig.isExplicitlyDisabled());

Boolean sslNativeEnabled = isSslNativeEnabled(sslNativeConfig, extensionSslNativeSupport);
if (sslNativeEnabled) {
// This makes the native image dependent on the local path used to build it.
// This is useful for testing but the user will have to override it.
String graalVmHome = System.getenv("GRAALVM_HOME");
if (graalVmHome != null) {
// JDK 8 path
Path graalVmCacertsPath = Paths.get(graalVmHome, "jre", "lib", "security", "cacerts");
if (!Files.exists(graalVmCacertsPath)) {
// Path starting with GraalVM JDK 11
graalVmCacertsPath = Paths.get(graalVmHome, "lib", "security", "cacerts");
}

sslTrustStoreSystemProperty.produce(new SslTrustStoreSystemPropertyBuildItem(graalVmCacertsPath.toString()));
}
}
nativeImage.produce(new NativeImageSystemPropertyBuildItem("quarkus.ssl.native", sslNativeEnabled.toString()));

if (!enableAllSecurityServicesBuildItems.isEmpty()) {
Expand Down
Loading