Skip to content

Commit

Permalink
SCANJLIB-236 Rework mapping of JVM SSL properties
Browse files Browse the repository at this point in the history
  • Loading branch information
henryju committed Dec 12, 2024
1 parent 2bebfff commit 4a07f90
Show file tree
Hide file tree
Showing 4 changed files with 110 additions and 79 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -45,8 +45,13 @@
import org.sonarsource.scanner.lib.internal.http.ssl.CertificateStore;
import org.sonarsource.scanner.lib.internal.util.VersionUtils;

import static java.util.Optional.ofNullable;
import static org.sonarsource.scanner.lib.ScannerProperties.SCANNER_ARCH;
import static org.sonarsource.scanner.lib.ScannerProperties.SCANNER_OS;
import static org.sonarsource.scanner.lib.ScannerProperties.SONAR_SCANNER_KEYSTORE_PASSWORD;
import static org.sonarsource.scanner.lib.ScannerProperties.SONAR_SCANNER_KEYSTORE_PATH;
import static org.sonarsource.scanner.lib.ScannerProperties.SONAR_SCANNER_TRUSTSTORE_PASSWORD;
import static org.sonarsource.scanner.lib.ScannerProperties.SONAR_SCANNER_TRUSTSTORE_PATH;

/**
* Entry point to run a Sonar analysis programmatically.
Expand All @@ -58,6 +63,10 @@ public class ScannerEngineBootstrapper {
private static final String SONARCLOUD_HOST = "https://sonarcloud.io";
private static final String SONARCLOUD_REST_API = "https://api.sonarcloud.io";
static final String SQ_VERSION_NEW_BOOTSTRAPPING = "10.6";
private static final String JAVAX_NET_SSL_TRUST_STORE = "javax.net.ssl.trustStore";
private static final String JAVAX_NET_SSL_TRUST_STORE_PASSWORD = "javax.net.ssl.trustStorePassword";
private static final String JAVAX_NET_SSL_KEY_STORE = "javax.net.ssl.keyStore";
private static final String JAVAX_NET_SSL_KEY_STORE_PASSWORD = "javax.net.ssl.keyStorePassword";

private final IsolatedLauncherFactory launcherFactory;
private final ScannerEngineLauncherFactory scannerEngineLauncherFactory;
Expand Down Expand Up @@ -106,48 +115,31 @@ public ScannerEngineFacade bootstrap() {
LOG.debug("Scanner max available memory: {}", FileUtils.byteCountToDisplaySize(Runtime.getRuntime().maxMemory()));
}
initBootstrapDefaultValues();
var properties = Map.copyOf(bootstrapProperties);
var isSonarCloud = isSonarCloud(properties);
var isSimulation = properties.containsKey(InternalProperties.SCANNER_DUMP_TO_FILE);
var sonarUserHome = resolveSonarUserHome(properties);
adaptJvmSslPropertiesToScannerProperties(bootstrapProperties, system);
var immutableProperties = Map.copyOf(bootstrapProperties);
var isSonarCloud = isSonarCloud(immutableProperties);
var isSimulation = immutableProperties.containsKey(InternalProperties.SCANNER_DUMP_TO_FILE);
var sonarUserHome = resolveSonarUserHome(immutableProperties);
var fileCache = FileCache.create(sonarUserHome);
var httpConfig = new HttpConfig(bootstrapProperties, sonarUserHome);
var httpConfig = new HttpConfig(immutableProperties, sonarUserHome);
scannerHttpClient.init(httpConfig);
String serverVersion = null;
if (!isSonarCloud) {
serverVersion = getServerVersion(scannerHttpClient, isSimulation, properties);
serverVersion = getServerVersion(scannerHttpClient, isSimulation, immutableProperties);
}

if (isSimulation) {
return new SimulationScannerEngineFacade(properties, isSonarCloud, serverVersion);
return new SimulationScannerEngineFacade(immutableProperties, isSonarCloud, serverVersion);
} else if (isSonarCloud || VersionUtils.isAtLeastIgnoringQualifier(serverVersion, SQ_VERSION_NEW_BOOTSTRAPPING)) {
var launcher = scannerEngineLauncherFactory.createLauncher(scannerHttpClient, fileCache, properties);
var adaptedProperties = adaptDeprecatedPropertiesForForkedBootstrapping(properties, httpConfig);
return new NewScannerEngineFacade(adaptedProperties, launcher, isSonarCloud, serverVersion);
var launcher = scannerEngineLauncherFactory.createLauncher(scannerHttpClient, fileCache, immutableProperties);
return new NewScannerEngineFacade(immutableProperties, launcher, isSonarCloud, serverVersion);
} else {
var launcher = launcherFactory.createLauncher(scannerHttpClient, fileCache);
var adaptedProperties = adaptDeprecatedPropertiesForInProcessBootstrapping(properties, httpConfig);
var adaptedProperties = adaptDeprecatedPropertiesForInProcessBootstrapping(immutableProperties, httpConfig);
return new InProcessScannerEngineFacade(adaptedProperties, launcher, false, serverVersion);
}
}

/**
* New versions of SonarQube/SonarCloud will run on a separate VM. For people who used to rely on configuring SSL
* by inserting the trusted certificate in the Scanner JVM truststore,
* we need to adapt the properties to read from the truststore of the scanner JVM.
*/
Map<String, String> adaptDeprecatedPropertiesForForkedBootstrapping(Map<String, String> properties, HttpConfig httpConfig) {
var adaptedProperties = new HashMap<>(properties);
if (system.getProperty("javax.net.ssl.trustStore") == null && httpConfig.getSslConfig().getTrustStore() == null) {
var defaultJvmTrustStoreLocation = Paths.get(System.getProperty("java.home"), "lib", "security", "cacerts");
if (Files.isRegularFile(defaultJvmTrustStoreLocation)) {
LOG.debug("Mapping default scanner JVM truststore location '{}' to new properties", defaultJvmTrustStoreLocation);
adaptedProperties.put("sonar.scanner.truststorePath", defaultJvmTrustStoreLocation.toString());
adaptedProperties.put("sonar.scanner.truststorePassword", System.getProperty("javax.net.ssl.trustStorePassword", "changeit"));
}
}
return Map.copyOf(adaptedProperties);
}

/**
* Older SonarQube versions used to rely on some different properties, or even {@link System} properties.
Expand All @@ -170,13 +162,13 @@ Map<String, String> adaptDeprecatedPropertiesForInProcessBootstrapping(Map<Strin

var keyStore = httpConfig.getSslConfig().getKeyStore();
if (keyStore != null) {
setSystemPropertyIfNotAlreadySet("javax.net.ssl.keyStore", keyStore.getPath().toString());
setSystemPropertyIfNotAlreadySet("javax.net.ssl.keyStorePassword", keyStore.getKeyStorePassword().orElse(CertificateStore.DEFAULT_PASSWORD));
setSystemPropertyIfNotAlreadySet(JAVAX_NET_SSL_KEY_STORE, keyStore.getPath().toString());
setSystemPropertyIfNotAlreadySet(JAVAX_NET_SSL_KEY_STORE_PASSWORD, keyStore.getKeyStorePassword().orElse(CertificateStore.DEFAULT_PASSWORD));
}
var trustStore = httpConfig.getSslConfig().getTrustStore();
if (trustStore != null) {
setSystemPropertyIfNotAlreadySet("javax.net.ssl.trustStore", trustStore.getPath().toString());
setSystemPropertyIfNotAlreadySet("javax.net.ssl.trustStorePassword", trustStore.getKeyStorePassword().orElse(CertificateStore.DEFAULT_PASSWORD));
setSystemPropertyIfNotAlreadySet(JAVAX_NET_SSL_TRUST_STORE, trustStore.getPath().toString());
setSystemPropertyIfNotAlreadySet(JAVAX_NET_SSL_TRUST_STORE_PASSWORD, trustStore.getKeyStorePassword().orElse(CertificateStore.DEFAULT_PASSWORD));
}

return Map.copyOf(adaptedProperties);
Expand Down Expand Up @@ -229,6 +221,37 @@ private void initBootstrapDefaultValues() {
}
}

/**
* New versions of SonarQube/SonarCloud will run on a separate VM. For people who used to rely on configuring SSL
* by inserting the trusted certificate in the Scanner JVM truststore, or passing JVM SSL properties
* we need to adapt the properties, at least temporarily, until we have helped most users to migrate.
*/
static void adaptJvmSslPropertiesToScannerProperties(Map<String, String> bootstrapProperties, System2 system) {
if (!bootstrapProperties.containsKey(SONAR_SCANNER_TRUSTSTORE_PATH)) {
var jvmTrustStoreProp = system.getProperty(JAVAX_NET_SSL_TRUST_STORE);
if (StringUtils.isBlank(jvmTrustStoreProp)) {
var defaultJvmTrustStoreLocation = Paths.get(System.getProperty("java.home"), "lib", "security", "cacerts");
if (Files.isRegularFile(defaultJvmTrustStoreLocation)) {
LOG.debug("Mapping default scanner JVM truststore location '{}' to new properties", defaultJvmTrustStoreLocation);
bootstrapProperties.put(SONAR_SCANNER_TRUSTSTORE_PATH, defaultJvmTrustStoreLocation.toString());
bootstrapProperties.putIfAbsent(SONAR_SCANNER_TRUSTSTORE_PASSWORD, System.getProperty(JAVAX_NET_SSL_TRUST_STORE_PASSWORD, "changeit"));
}
} else {
bootstrapProperties.putIfAbsent(SONAR_SCANNER_TRUSTSTORE_PATH, jvmTrustStoreProp);
ofNullable(system.getProperty(JAVAX_NET_SSL_TRUST_STORE_PASSWORD))
.ifPresent(password -> bootstrapProperties.putIfAbsent(SONAR_SCANNER_TRUSTSTORE_PASSWORD, password));
}
}
if (!bootstrapProperties.containsKey(SONAR_SCANNER_KEYSTORE_PATH)) {
var keystoreProp = system.getProperty(JAVAX_NET_SSL_KEY_STORE);
if (!StringUtils.isBlank(keystoreProp)) {
bootstrapProperties.put(SONAR_SCANNER_KEYSTORE_PATH, keystoreProp);
ofNullable(system.getProperty(JAVAX_NET_SSL_KEY_STORE_PASSWORD))
.ifPresent(password -> bootstrapProperties.putIfAbsent(SONAR_SCANNER_KEYSTORE_PASSWORD, password));
}
}
}

private String getSonarCloudUrl() {
return bootstrapProperties.getOrDefault(ScannerProperties.SONARCLOUD_URL, SONARCLOUD_HOST);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -121,18 +121,15 @@ private static SSLFactory configureSsl(SslConfig sslConfig, boolean skipSystemTr
LOG.debug("This operation might be slow or even get stuck. You can skip it by passing the scanner property '{}=true'", SONAR_SCANNER_SKIP_SYSTEM_TRUSTSTORE);
sslFactoryBuilder.withSystemTrustMaterial();
}
if (System.getProperties().containsKey("javax.net.ssl.keyStore")) {
sslFactoryBuilder.withSystemPropertyDerivedIdentityMaterial();
}
var keyStoreConfig = sslConfig.getKeyStore();
if (keyStoreConfig != null && Files.exists(keyStoreConfig.getPath())) {
if (keyStoreConfig != null) {
keyStoreConfig.getKeyStorePassword()
.ifPresentOrElse(
password -> sslFactoryBuilder.withIdentityMaterial(keyStoreConfig.getPath(), password.toCharArray(), keyStoreConfig.getKeyStoreType()),
() -> loadIdentityMaterialWithDefaultPassword(sslFactoryBuilder, keyStoreConfig.getPath()));
}
var trustStoreConfig = sslConfig.getTrustStore();
if (trustStoreConfig != null && Files.exists(trustStoreConfig.getPath())) {
if (trustStoreConfig != null) {
KeyStore trustStore;
try {
trustStore = loadTrustStoreWithBouncyCastle(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -312,40 +312,64 @@ void should_set_deprecated_ssl_properties() {
}

@Test
void should_set_ssl_properties_from_cacerts() {
var httpConfig = mock(HttpConfig.class);
when(httpConfig.getSslConfig()).thenReturn(new SslConfig(null, null));
void should_set_ssl_properties_from_default_jvm_location() {
Map<String, String> properties = new HashMap<>();

var adapted = underTest.adaptDeprecatedPropertiesForForkedBootstrapping(Map.of(), httpConfig);
ScannerEngineBootstrapper.adaptJvmSslPropertiesToScannerProperties(properties, system);

assertThat(adapted).contains(
assertThat(properties).contains(
entry("sonar.scanner.truststorePath", Paths.get(System.getProperty("java.home"), "lib", "security", "cacerts").toString()),
entry("sonar.scanner.truststorePassword", "changeit"));
}

@Test
void should_not_set_ssl_properties_from_cacerts_if_already_set_as_scanner_props(@TempDir Path tempDir) throws IOException {
var cacerts = tempDir.resolve("truststore.p12");
Files.createFile(cacerts);
var httpConfig = mock(HttpConfig.class);
when(httpConfig.getSslConfig()).thenReturn(new SslConfig(null, new CertificateStore(cacerts, "something")));

var adapted = underTest.adaptDeprecatedPropertiesForForkedBootstrapping(Map.of(), httpConfig);

assertThat(adapted).isEmpty();
void should_set_ssl_properties_from_jvm_system_properties(@TempDir Path tempDir) throws IOException {
var jvmTruststore = tempDir.resolve("jvmTrust.p12");
Files.createFile(jvmTruststore);
var jvmKeyStore = tempDir.resolve("jvmKey.p12");
Files.createFile(jvmKeyStore);
when(system.getProperty("javax.net.ssl.trustStore")).thenReturn(jvmTruststore.toString());
when(system.getProperty("javax.net.ssl.trustStorePassword")).thenReturn("jvmTrustPassword");
when(system.getProperty("javax.net.ssl.keyStore")).thenReturn(jvmKeyStore.toString());
when(system.getProperty("javax.net.ssl.keyStorePassword")).thenReturn("jvmKeyPassword");

Map<String, String> properties = new HashMap<>();

ScannerEngineBootstrapper.adaptJvmSslPropertiesToScannerProperties(properties, system);

assertThat(properties).containsOnly(
entry("sonar.scanner.truststorePath", jvmTruststore.toString()),
entry("sonar.scanner.truststorePassword", "jvmTrustPassword"),
entry("sonar.scanner.keystorePath", jvmKeyStore.toString()),
entry("sonar.scanner.keystorePassword", "jvmKeyPassword"));
}

@Test
void should_not_set_ssl_properties_from_cacerts_if_already_set_as_JVM_props(@TempDir Path tempDir) throws IOException {
var cacerts = tempDir.resolve("truststore.p12");
Files.createFile(cacerts);
var httpConfig = mock(HttpConfig.class);
when(httpConfig.getSslConfig()).thenReturn(new SslConfig(null, null));

when(system.getProperty("javax.net.ssl.trustStore")).thenReturn(cacerts.toString());

var adapted = underTest.adaptDeprecatedPropertiesForForkedBootstrapping(Map.of(), httpConfig);

assertThat(adapted).isEmpty();
void should_not_change_ssl_properties_if_already_set_as_scanner_props(@TempDir Path tempDir) throws IOException {
var jvmTruststore = tempDir.resolve("jvmTrust.p12");
Files.createFile(jvmTruststore);
var jvmKeyStore = tempDir.resolve("jvmKey.p12");
Files.createFile(jvmKeyStore);
when(system.getProperty("javax.net.ssl.trustStore")).thenReturn(jvmTruststore.toString());
when(system.getProperty("javax.net.ssl.trustStorePassword")).thenReturn("jvmTrustPassword");
when(system.getProperty("javax.net.ssl.keyStore")).thenReturn(jvmKeyStore.toString());
when(system.getProperty("javax.net.ssl.keyStorePassword")).thenReturn("jvmKeyPassword");

var scannerTruststore = tempDir.resolve("truststore.p12");
Files.createFile(scannerTruststore);
var scannerKeystore = tempDir.resolve("keystore.p12");
Files.createFile(scannerKeystore);

var properties = Map.of("sonar.scanner.truststorePath", scannerTruststore.toString(),
"sonar.scanner.truststorePassword", "scannerTrustPassword",
"sonar.scanner.keystorePath", scannerTruststore.toString(),
"sonar.scanner.keystorePassword", "scannerKeyPassword");

var mutableProps = new HashMap<>(properties);

ScannerEngineBootstrapper.adaptJvmSslPropertiesToScannerProperties(mutableProps, system);

assertThat(mutableProps).containsExactlyInAnyOrderEntriesOf(properties);
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -431,27 +431,14 @@ void it_should_authenticate_using_certificate_in_keystore() throws IOException {
assertThat(response.body().string()).contains("Success");
}

@RestoreSystemProperties
@Test
void it_should_support_jvm_system_properties() throws IOException {
bootstrapProperties.put("sonar.host.url", sonarqubeMock.baseUrl());
System.setProperty("javax.net.ssl.trustStore", toPath(requireNonNull(OkHttpClientFactoryTest.class.getResource("/ssl/client-truststore.p12"))).toString());
System.setProperty("javax.net.ssl.trustStorePassword", "pwdClientWithServerCA");
System.setProperty("javax.net.ssl.keyStore", toPath(requireNonNull(OkHttpClientFactoryTest.class.getResource("/ssl/client.p12"))).toString());
System.setProperty("javax.net.ssl.keyStorePassword", "pwdClientCertP12");

Response response = call(sonarqubeMock.url("/batch/index"));
assertThat(response.code()).isEqualTo(200);
assertThat(response.body().string()).contains("Success");
}
}

private Response call(String url) throws IOException {
return OkHttpClientFactory.create(new HttpConfig(bootstrapProperties, sonarUserHome)).newCall(
new Request.Builder()
.url(url)
.get()
.build())
new Request.Builder()
.url(url)
.get()
.build())
.execute();
}

Expand Down

0 comments on commit 4a07f90

Please sign in to comment.