aboutsummaryrefslogtreecommitdiffstats
path: root/sonar-scanner-engine/src/main/java/org/sonar/scanner/http/ScannerWsClientProvider.java
diff options
context:
space:
mode:
Diffstat (limited to 'sonar-scanner-engine/src/main/java/org/sonar/scanner/http/ScannerWsClientProvider.java')
-rw-r--r--sonar-scanner-engine/src/main/java/org/sonar/scanner/http/ScannerWsClientProvider.java152
1 files changed, 152 insertions, 0 deletions
diff --git a/sonar-scanner-engine/src/main/java/org/sonar/scanner/http/ScannerWsClientProvider.java b/sonar-scanner-engine/src/main/java/org/sonar/scanner/http/ScannerWsClientProvider.java
new file mode 100644
index 00000000000..2511daf5d0f
--- /dev/null
+++ b/sonar-scanner-engine/src/main/java/org/sonar/scanner/http/ScannerWsClientProvider.java
@@ -0,0 +1,152 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2024 SonarSource SA
+ * mailto:info AT sonarsource DOT com
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+package org.sonar.scanner.http;
+
+import java.net.InetSocketAddress;
+import java.net.Proxy;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.time.Duration;
+import java.time.format.DateTimeParseException;
+import nl.altindag.ssl.SSLFactory;
+import org.sonar.api.CoreProperties;
+import org.sonar.api.notifications.AnalysisWarnings;
+import org.sonar.api.utils.System2;
+import org.sonar.batch.bootstrapper.EnvironmentInformation;
+import org.sonar.scanner.bootstrap.GlobalAnalysisMode;
+import org.sonar.scanner.bootstrap.ScannerProperties;
+import org.sonar.scanner.bootstrap.SonarUserHome;
+import org.sonar.scanner.http.ssl.CertificateStore;
+import org.sonar.scanner.http.ssl.SslConfig;
+import org.sonarqube.ws.client.HttpConnector;
+import org.sonarqube.ws.client.WsClientFactories;
+import org.springframework.context.annotation.Bean;
+
+import static java.lang.Integer.parseInt;
+import static java.lang.String.valueOf;
+import static org.apache.commons.lang3.StringUtils.defaultIfBlank;
+import static org.apache.commons.lang3.StringUtils.isNotBlank;
+import static org.sonar.core.config.ProxyProperties.HTTP_PROXY_PASSWORD;
+import static org.sonar.core.config.ProxyProperties.HTTP_PROXY_USER;
+
+public class ScannerWsClientProvider {
+ static final int DEFAULT_CONNECT_TIMEOUT = 5;
+ static final int DEFAULT_RESPONSE_TIMEOUT = 0;
+ static final String READ_TIMEOUT_SEC_PROPERTY = "sonar.ws.timeout";
+ public static final String TOKEN_PROPERTY = "sonar.token";
+ private static final String TOKEN_ENV_VARIABLE = "SONAR_TOKEN";
+ static final int DEFAULT_READ_TIMEOUT_SEC = 60;
+ public static final String SONAR_SCANNER_PROXY_PORT = "sonar.scanner.proxyPort";
+ public static final String SONAR_SCANNER_CONNECT_TIMEOUT = "sonar.scanner.connectTimeout";
+ public static final String SONAR_SCANNER_SOCKET_TIMEOUT = "sonar.scanner.socketTimeout";
+ public static final String SONAR_SCANNER_RESPONSE_TIMEOUT = "sonar.scanner.responseTimeout";
+
+ @Bean("DefaultScannerWsClient")
+ public DefaultScannerWsClient provide(ScannerProperties scannerProps, EnvironmentInformation env, GlobalAnalysisMode globalMode,
+ System2 system, AnalysisWarnings analysisWarnings, SonarUserHome sonarUserHome) {
+ String url = defaultIfBlank(scannerProps.property("sonar.host.url"), "http://localhost:9000");
+ HttpConnector.Builder connectorBuilder = HttpConnector.newBuilder().acceptGzip(true);
+
+ String oldSocketTimeout = defaultIfBlank(scannerProps.property(READ_TIMEOUT_SEC_PROPERTY), valueOf(DEFAULT_READ_TIMEOUT_SEC));
+ String socketTimeout = defaultIfBlank(scannerProps.property(SONAR_SCANNER_SOCKET_TIMEOUT), oldSocketTimeout);
+ String connectTimeout = defaultIfBlank(scannerProps.property(SONAR_SCANNER_CONNECT_TIMEOUT), valueOf(DEFAULT_CONNECT_TIMEOUT));
+ String responseTimeout = defaultIfBlank(scannerProps.property(SONAR_SCANNER_RESPONSE_TIMEOUT), valueOf(DEFAULT_RESPONSE_TIMEOUT));
+ String envVarToken = defaultIfBlank(system.envVariable(TOKEN_ENV_VARIABLE), null);
+ String token = defaultIfBlank(scannerProps.property(TOKEN_PROPERTY), envVarToken);
+ String login = defaultIfBlank(scannerProps.property(CoreProperties.LOGIN), token);
+ var sslContext = configureSsl(parseSslConfig(scannerProps, sonarUserHome), system);
+ connectorBuilder
+ .readTimeoutMilliseconds(parseDurationProperty(socketTimeout, SONAR_SCANNER_SOCKET_TIMEOUT))
+ .connectTimeoutMilliseconds(parseDurationProperty(connectTimeout, SONAR_SCANNER_CONNECT_TIMEOUT))
+ .responseTimeoutMilliseconds(parseDurationProperty(responseTimeout, SONAR_SCANNER_RESPONSE_TIMEOUT))
+ .userAgent(env.toString())
+ .url(url)
+ .credentials(login, scannerProps.property(CoreProperties.PASSWORD))
+ .setSSLSocketFactory(sslContext.getSslSocketFactory())
+ .setTrustManager(sslContext.getTrustManager().orElseThrow());
+
+ // OkHttp detects 'http.proxyHost' java property already, so just focus on sonar properties
+ String proxyHost = defaultIfBlank(scannerProps.property("sonar.scanner.proxyHost"), null);
+ if (proxyHost != null) {
+ String proxyPortStr = defaultIfBlank(scannerProps.property(SONAR_SCANNER_PROXY_PORT), url.startsWith("https") ? "443" : "80");
+ var proxyPort = parseIntProperty(proxyPortStr, SONAR_SCANNER_PROXY_PORT);
+ connectorBuilder.proxy(new Proxy(Proxy.Type.HTTP, new InetSocketAddress(proxyHost, proxyPort)));
+ }
+
+ var scannerProxyUser = scannerProps.property("sonar.scanner.proxyUser");
+ String proxyUser = scannerProxyUser != null ? scannerProxyUser : system.properties().getProperty(HTTP_PROXY_USER, "");
+ if (isNotBlank(proxyUser)) {
+ var scannerProxyPwd = scannerProps.property("sonar.scanner.proxyPassword");
+ String proxyPassword = scannerProxyPwd != null ? scannerProxyPwd : system.properties().getProperty(HTTP_PROXY_PASSWORD, "");
+ connectorBuilder.proxyCredentials(proxyUser, proxyPassword);
+ }
+
+ return new DefaultScannerWsClient(WsClientFactories.getDefault().newClient(connectorBuilder.build()), login != null, globalMode, analysisWarnings);
+ }
+
+ private static int parseIntProperty(String propValue, String propKey) {
+ try {
+ return parseInt(propValue);
+ } catch (NumberFormatException e) {
+ throw new IllegalArgumentException(propKey + " is not a valid integer: " + propValue, e);
+ }
+ }
+
+ /**
+ * For testing, we can accept timeouts that are smaller than a second, expressed using ISO-8601 format for durations.
+ * If we can't parse as ISO-8601, then fallback to the official format that is simply the number of seconds
+ */
+ private static int parseDurationProperty(String propValue, String propKey) {
+ try {
+ return (int) Duration.parse(propValue).toMillis();
+ } catch (DateTimeParseException e) {
+ return parseIntProperty(propValue, propKey) * 1_000;
+ }
+ }
+
+ private static SslConfig parseSslConfig(ScannerProperties scannerProperties, SonarUserHome sonarUserHome) {
+ var keyStorePath = defaultIfBlank(scannerProperties.property("sonar.scanner.keystorePath"), sonarUserHome.getPath().resolve("ssl/keystore.p12").toString());
+ var keyStorePassword = defaultIfBlank(scannerProperties.property("sonar.scanner.keystorePassword"), CertificateStore.DEFAULT_PASSWORD);
+ var trustStorePath = defaultIfBlank(scannerProperties.property("sonar.scanner.truststorePath"), sonarUserHome.getPath().resolve("ssl/truststore.p12").toString());
+ var trustStorePassword = defaultIfBlank(scannerProperties.property("sonar.scanner.truststorePassword"), CertificateStore.DEFAULT_PASSWORD);
+ var keyStore = new CertificateStore(Path.of(keyStorePath), keyStorePassword);
+ var trustStore = new CertificateStore(Path.of(trustStorePath), trustStorePassword);
+ return new SslConfig(keyStore, trustStore);
+ }
+
+ private static SSLFactory configureSsl(SslConfig sslConfig, System2 system2) {
+ var sslFactoryBuilder = SSLFactory.builder()
+ .withDefaultTrustMaterial()
+ .withSystemTrustMaterial();
+ if (system2.properties().containsKey("javax.net.ssl.keyStore")) {
+ sslFactoryBuilder.withSystemPropertyDerivedIdentityMaterial();
+ }
+ var keyStore = sslConfig.getKeyStore();
+ if (keyStore != null && Files.exists(keyStore.getPath())) {
+ sslFactoryBuilder.withIdentityMaterial(keyStore.getPath(), keyStore.getKeyStorePassword().toCharArray(), keyStore.getKeyStoreType());
+ }
+ var trustStore = sslConfig.getTrustStore();
+ if (trustStore != null && Files.exists(trustStore.getPath())) {
+ sslFactoryBuilder.withTrustMaterial(trustStore.getPath(), trustStore.getKeyStorePassword().toCharArray(), trustStore.getKeyStoreType());
+ }
+ return sslFactoryBuilder.build();
+ }
+
+}