Skip to content

Commit 4a07f90

Browse files
committed
SCANJLIB-236 Rework mapping of JVM SSL properties
1 parent 2bebfff commit 4a07f90

4 files changed

Lines changed: 110 additions & 79 deletions

File tree

lib/src/main/java/org/sonarsource/scanner/lib/ScannerEngineBootstrapper.java

Lines changed: 55 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -45,8 +45,13 @@
4545
import org.sonarsource.scanner.lib.internal.http.ssl.CertificateStore;
4646
import org.sonarsource.scanner.lib.internal.util.VersionUtils;
4747

48+
import static java.util.Optional.ofNullable;
4849
import static org.sonarsource.scanner.lib.ScannerProperties.SCANNER_ARCH;
4950
import static org.sonarsource.scanner.lib.ScannerProperties.SCANNER_OS;
51+
import static org.sonarsource.scanner.lib.ScannerProperties.SONAR_SCANNER_KEYSTORE_PASSWORD;
52+
import static org.sonarsource.scanner.lib.ScannerProperties.SONAR_SCANNER_KEYSTORE_PATH;
53+
import static org.sonarsource.scanner.lib.ScannerProperties.SONAR_SCANNER_TRUSTSTORE_PASSWORD;
54+
import static org.sonarsource.scanner.lib.ScannerProperties.SONAR_SCANNER_TRUSTSTORE_PATH;
5055

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

6271
private final IsolatedLauncherFactory launcherFactory;
6372
private final ScannerEngineLauncherFactory scannerEngineLauncherFactory;
@@ -106,48 +115,31 @@ public ScannerEngineFacade bootstrap() {
106115
LOG.debug("Scanner max available memory: {}", FileUtils.byteCountToDisplaySize(Runtime.getRuntime().maxMemory()));
107116
}
108117
initBootstrapDefaultValues();
109-
var properties = Map.copyOf(bootstrapProperties);
110-
var isSonarCloud = isSonarCloud(properties);
111-
var isSimulation = properties.containsKey(InternalProperties.SCANNER_DUMP_TO_FILE);
112-
var sonarUserHome = resolveSonarUserHome(properties);
118+
adaptJvmSslPropertiesToScannerProperties(bootstrapProperties, system);
119+
var immutableProperties = Map.copyOf(bootstrapProperties);
120+
var isSonarCloud = isSonarCloud(immutableProperties);
121+
var isSimulation = immutableProperties.containsKey(InternalProperties.SCANNER_DUMP_TO_FILE);
122+
var sonarUserHome = resolveSonarUserHome(immutableProperties);
113123
var fileCache = FileCache.create(sonarUserHome);
114-
var httpConfig = new HttpConfig(bootstrapProperties, sonarUserHome);
124+
var httpConfig = new HttpConfig(immutableProperties, sonarUserHome);
115125
scannerHttpClient.init(httpConfig);
116126
String serverVersion = null;
117127
if (!isSonarCloud) {
118-
serverVersion = getServerVersion(scannerHttpClient, isSimulation, properties);
128+
serverVersion = getServerVersion(scannerHttpClient, isSimulation, immutableProperties);
119129
}
120130

121131
if (isSimulation) {
122-
return new SimulationScannerEngineFacade(properties, isSonarCloud, serverVersion);
132+
return new SimulationScannerEngineFacade(immutableProperties, isSonarCloud, serverVersion);
123133
} else if (isSonarCloud || VersionUtils.isAtLeastIgnoringQualifier(serverVersion, SQ_VERSION_NEW_BOOTSTRAPPING)) {
124-
var launcher = scannerEngineLauncherFactory.createLauncher(scannerHttpClient, fileCache, properties);
125-
var adaptedProperties = adaptDeprecatedPropertiesForForkedBootstrapping(properties, httpConfig);
126-
return new NewScannerEngineFacade(adaptedProperties, launcher, isSonarCloud, serverVersion);
134+
var launcher = scannerEngineLauncherFactory.createLauncher(scannerHttpClient, fileCache, immutableProperties);
135+
return new NewScannerEngineFacade(immutableProperties, launcher, isSonarCloud, serverVersion);
127136
} else {
128137
var launcher = launcherFactory.createLauncher(scannerHttpClient, fileCache);
129-
var adaptedProperties = adaptDeprecatedPropertiesForInProcessBootstrapping(properties, httpConfig);
138+
var adaptedProperties = adaptDeprecatedPropertiesForInProcessBootstrapping(immutableProperties, httpConfig);
130139
return new InProcessScannerEngineFacade(adaptedProperties, launcher, false, serverVersion);
131140
}
132141
}
133142

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

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

171163
var keyStore = httpConfig.getSslConfig().getKeyStore();
172164
if (keyStore != null) {
173-
setSystemPropertyIfNotAlreadySet("javax.net.ssl.keyStore", keyStore.getPath().toString());
174-
setSystemPropertyIfNotAlreadySet("javax.net.ssl.keyStorePassword", keyStore.getKeyStorePassword().orElse(CertificateStore.DEFAULT_PASSWORD));
165+
setSystemPropertyIfNotAlreadySet(JAVAX_NET_SSL_KEY_STORE, keyStore.getPath().toString());
166+
setSystemPropertyIfNotAlreadySet(JAVAX_NET_SSL_KEY_STORE_PASSWORD, keyStore.getKeyStorePassword().orElse(CertificateStore.DEFAULT_PASSWORD));
175167
}
176168
var trustStore = httpConfig.getSslConfig().getTrustStore();
177169
if (trustStore != null) {
178-
setSystemPropertyIfNotAlreadySet("javax.net.ssl.trustStore", trustStore.getPath().toString());
179-
setSystemPropertyIfNotAlreadySet("javax.net.ssl.trustStorePassword", trustStore.getKeyStorePassword().orElse(CertificateStore.DEFAULT_PASSWORD));
170+
setSystemPropertyIfNotAlreadySet(JAVAX_NET_SSL_TRUST_STORE, trustStore.getPath().toString());
171+
setSystemPropertyIfNotAlreadySet(JAVAX_NET_SSL_TRUST_STORE_PASSWORD, trustStore.getKeyStorePassword().orElse(CertificateStore.DEFAULT_PASSWORD));
180172
}
181173

182174
return Map.copyOf(adaptedProperties);
@@ -229,6 +221,37 @@ private void initBootstrapDefaultValues() {
229221
}
230222
}
231223

224+
/**
225+
* New versions of SonarQube/SonarCloud will run on a separate VM. For people who used to rely on configuring SSL
226+
* by inserting the trusted certificate in the Scanner JVM truststore, or passing JVM SSL properties
227+
* we need to adapt the properties, at least temporarily, until we have helped most users to migrate.
228+
*/
229+
static void adaptJvmSslPropertiesToScannerProperties(Map<String, String> bootstrapProperties, System2 system) {
230+
if (!bootstrapProperties.containsKey(SONAR_SCANNER_TRUSTSTORE_PATH)) {
231+
var jvmTrustStoreProp = system.getProperty(JAVAX_NET_SSL_TRUST_STORE);
232+
if (StringUtils.isBlank(jvmTrustStoreProp)) {
233+
var defaultJvmTrustStoreLocation = Paths.get(System.getProperty("java.home"), "lib", "security", "cacerts");
234+
if (Files.isRegularFile(defaultJvmTrustStoreLocation)) {
235+
LOG.debug("Mapping default scanner JVM truststore location '{}' to new properties", defaultJvmTrustStoreLocation);
236+
bootstrapProperties.put(SONAR_SCANNER_TRUSTSTORE_PATH, defaultJvmTrustStoreLocation.toString());
237+
bootstrapProperties.putIfAbsent(SONAR_SCANNER_TRUSTSTORE_PASSWORD, System.getProperty(JAVAX_NET_SSL_TRUST_STORE_PASSWORD, "changeit"));
238+
}
239+
} else {
240+
bootstrapProperties.putIfAbsent(SONAR_SCANNER_TRUSTSTORE_PATH, jvmTrustStoreProp);
241+
ofNullable(system.getProperty(JAVAX_NET_SSL_TRUST_STORE_PASSWORD))
242+
.ifPresent(password -> bootstrapProperties.putIfAbsent(SONAR_SCANNER_TRUSTSTORE_PASSWORD, password));
243+
}
244+
}
245+
if (!bootstrapProperties.containsKey(SONAR_SCANNER_KEYSTORE_PATH)) {
246+
var keystoreProp = system.getProperty(JAVAX_NET_SSL_KEY_STORE);
247+
if (!StringUtils.isBlank(keystoreProp)) {
248+
bootstrapProperties.put(SONAR_SCANNER_KEYSTORE_PATH, keystoreProp);
249+
ofNullable(system.getProperty(JAVAX_NET_SSL_KEY_STORE_PASSWORD))
250+
.ifPresent(password -> bootstrapProperties.putIfAbsent(SONAR_SCANNER_KEYSTORE_PASSWORD, password));
251+
}
252+
}
253+
}
254+
232255
private String getSonarCloudUrl() {
233256
return bootstrapProperties.getOrDefault(ScannerProperties.SONARCLOUD_URL, SONARCLOUD_HOST);
234257
}

lib/src/main/java/org/sonarsource/scanner/lib/internal/http/OkHttpClientFactory.java

Lines changed: 2 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -121,18 +121,15 @@ private static SSLFactory configureSsl(SslConfig sslConfig, boolean skipSystemTr
121121
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);
122122
sslFactoryBuilder.withSystemTrustMaterial();
123123
}
124-
if (System.getProperties().containsKey("javax.net.ssl.keyStore")) {
125-
sslFactoryBuilder.withSystemPropertyDerivedIdentityMaterial();
126-
}
127124
var keyStoreConfig = sslConfig.getKeyStore();
128-
if (keyStoreConfig != null && Files.exists(keyStoreConfig.getPath())) {
125+
if (keyStoreConfig != null) {
129126
keyStoreConfig.getKeyStorePassword()
130127
.ifPresentOrElse(
131128
password -> sslFactoryBuilder.withIdentityMaterial(keyStoreConfig.getPath(), password.toCharArray(), keyStoreConfig.getKeyStoreType()),
132129
() -> loadIdentityMaterialWithDefaultPassword(sslFactoryBuilder, keyStoreConfig.getPath()));
133130
}
134131
var trustStoreConfig = sslConfig.getTrustStore();
135-
if (trustStoreConfig != null && Files.exists(trustStoreConfig.getPath())) {
132+
if (trustStoreConfig != null) {
136133
KeyStore trustStore;
137134
try {
138135
trustStore = loadTrustStoreWithBouncyCastle(

lib/src/test/java/org/sonarsource/scanner/lib/ScannerEngineBootstrapperTest.java

Lines changed: 49 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -312,40 +312,64 @@ void should_set_deprecated_ssl_properties() {
312312
}
313313

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

319-
var adapted = underTest.adaptDeprecatedPropertiesForForkedBootstrapping(Map.of(), httpConfig);
318+
ScannerEngineBootstrapper.adaptJvmSslPropertiesToScannerProperties(properties, system);
320319

321-
assertThat(adapted).contains(
320+
assertThat(properties).contains(
322321
entry("sonar.scanner.truststorePath", Paths.get(System.getProperty("java.home"), "lib", "security", "cacerts").toString()),
323322
entry("sonar.scanner.truststorePassword", "changeit"));
324323
}
325324

326325
@Test
327-
void should_not_set_ssl_properties_from_cacerts_if_already_set_as_scanner_props(@TempDir Path tempDir) throws IOException {
328-
var cacerts = tempDir.resolve("truststore.p12");
329-
Files.createFile(cacerts);
330-
var httpConfig = mock(HttpConfig.class);
331-
when(httpConfig.getSslConfig()).thenReturn(new SslConfig(null, new CertificateStore(cacerts, "something")));
332-
333-
var adapted = underTest.adaptDeprecatedPropertiesForForkedBootstrapping(Map.of(), httpConfig);
334-
335-
assertThat(adapted).isEmpty();
326+
void should_set_ssl_properties_from_jvm_system_properties(@TempDir Path tempDir) throws IOException {
327+
var jvmTruststore = tempDir.resolve("jvmTrust.p12");
328+
Files.createFile(jvmTruststore);
329+
var jvmKeyStore = tempDir.resolve("jvmKey.p12");
330+
Files.createFile(jvmKeyStore);
331+
when(system.getProperty("javax.net.ssl.trustStore")).thenReturn(jvmTruststore.toString());
332+
when(system.getProperty("javax.net.ssl.trustStorePassword")).thenReturn("jvmTrustPassword");
333+
when(system.getProperty("javax.net.ssl.keyStore")).thenReturn(jvmKeyStore.toString());
334+
when(system.getProperty("javax.net.ssl.keyStorePassword")).thenReturn("jvmKeyPassword");
335+
336+
Map<String, String> properties = new HashMap<>();
337+
338+
ScannerEngineBootstrapper.adaptJvmSslPropertiesToScannerProperties(properties, system);
339+
340+
assertThat(properties).containsOnly(
341+
entry("sonar.scanner.truststorePath", jvmTruststore.toString()),
342+
entry("sonar.scanner.truststorePassword", "jvmTrustPassword"),
343+
entry("sonar.scanner.keystorePath", jvmKeyStore.toString()),
344+
entry("sonar.scanner.keystorePassword", "jvmKeyPassword"));
336345
}
337346

338347
@Test
339-
void should_not_set_ssl_properties_from_cacerts_if_already_set_as_JVM_props(@TempDir Path tempDir) throws IOException {
340-
var cacerts = tempDir.resolve("truststore.p12");
341-
Files.createFile(cacerts);
342-
var httpConfig = mock(HttpConfig.class);
343-
when(httpConfig.getSslConfig()).thenReturn(new SslConfig(null, null));
344-
345-
when(system.getProperty("javax.net.ssl.trustStore")).thenReturn(cacerts.toString());
346-
347-
var adapted = underTest.adaptDeprecatedPropertiesForForkedBootstrapping(Map.of(), httpConfig);
348-
349-
assertThat(adapted).isEmpty();
348+
void should_not_change_ssl_properties_if_already_set_as_scanner_props(@TempDir Path tempDir) throws IOException {
349+
var jvmTruststore = tempDir.resolve("jvmTrust.p12");
350+
Files.createFile(jvmTruststore);
351+
var jvmKeyStore = tempDir.resolve("jvmKey.p12");
352+
Files.createFile(jvmKeyStore);
353+
when(system.getProperty("javax.net.ssl.trustStore")).thenReturn(jvmTruststore.toString());
354+
when(system.getProperty("javax.net.ssl.trustStorePassword")).thenReturn("jvmTrustPassword");
355+
when(system.getProperty("javax.net.ssl.keyStore")).thenReturn(jvmKeyStore.toString());
356+
when(system.getProperty("javax.net.ssl.keyStorePassword")).thenReturn("jvmKeyPassword");
357+
358+
var scannerTruststore = tempDir.resolve("truststore.p12");
359+
Files.createFile(scannerTruststore);
360+
var scannerKeystore = tempDir.resolve("keystore.p12");
361+
Files.createFile(scannerKeystore);
362+
363+
var properties = Map.of("sonar.scanner.truststorePath", scannerTruststore.toString(),
364+
"sonar.scanner.truststorePassword", "scannerTrustPassword",
365+
"sonar.scanner.keystorePath", scannerTruststore.toString(),
366+
"sonar.scanner.keystorePassword", "scannerKeyPassword");
367+
368+
var mutableProps = new HashMap<>(properties);
369+
370+
ScannerEngineBootstrapper.adaptJvmSslPropertiesToScannerProperties(mutableProps, system);
371+
372+
assertThat(mutableProps).containsExactlyInAnyOrderEntriesOf(properties);
350373
}
374+
351375
}

lib/src/test/java/org/sonarsource/scanner/lib/internal/http/OkHttpClientFactoryTest.java

Lines changed: 4 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -431,27 +431,14 @@ void it_should_authenticate_using_certificate_in_keystore() throws IOException {
431431
assertThat(response.body().string()).contains("Success");
432432
}
433433

434-
@RestoreSystemProperties
435-
@Test
436-
void it_should_support_jvm_system_properties() throws IOException {
437-
bootstrapProperties.put("sonar.host.url", sonarqubeMock.baseUrl());
438-
System.setProperty("javax.net.ssl.trustStore", toPath(requireNonNull(OkHttpClientFactoryTest.class.getResource("/ssl/client-truststore.p12"))).toString());
439-
System.setProperty("javax.net.ssl.trustStorePassword", "pwdClientWithServerCA");
440-
System.setProperty("javax.net.ssl.keyStore", toPath(requireNonNull(OkHttpClientFactoryTest.class.getResource("/ssl/client.p12"))).toString());
441-
System.setProperty("javax.net.ssl.keyStorePassword", "pwdClientCertP12");
442-
443-
Response response = call(sonarqubeMock.url("/batch/index"));
444-
assertThat(response.code()).isEqualTo(200);
445-
assertThat(response.body().string()).contains("Success");
446-
}
447434
}
448435

449436
private Response call(String url) throws IOException {
450437
return OkHttpClientFactory.create(new HttpConfig(bootstrapProperties, sonarUserHome)).newCall(
451-
new Request.Builder()
452-
.url(url)
453-
.get()
454-
.build())
438+
new Request.Builder()
439+
.url(url)
440+
.get()
441+
.build())
455442
.execute();
456443
}
457444

0 commit comments

Comments
 (0)