You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

ScannerWsClientProvider.java 8.3KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152
  1. /*
  2. * SonarQube
  3. * Copyright (C) 2009-2024 SonarSource SA
  4. * mailto:info AT sonarsource DOT com
  5. *
  6. * This program is free software; you can redistribute it and/or
  7. * modify it under the terms of the GNU Lesser General Public
  8. * License as published by the Free Software Foundation; either
  9. * version 3 of the License, or (at your option) any later version.
  10. *
  11. * This program is distributed in the hope that it will be useful,
  12. * but WITHOUT ANY WARRANTY; without even the implied warranty of
  13. * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
  14. * Lesser General Public License for more details.
  15. *
  16. * You should have received a copy of the GNU Lesser General Public License
  17. * along with this program; if not, write to the Free Software Foundation,
  18. * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
  19. */
  20. package org.sonar.scanner.http;
  21. import java.net.InetSocketAddress;
  22. import java.net.Proxy;
  23. import java.nio.file.Files;
  24. import java.nio.file.Path;
  25. import java.time.Duration;
  26. import java.time.format.DateTimeParseException;
  27. import nl.altindag.ssl.SSLFactory;
  28. import org.sonar.api.CoreProperties;
  29. import org.sonar.api.notifications.AnalysisWarnings;
  30. import org.sonar.api.utils.System2;
  31. import org.sonar.batch.bootstrapper.EnvironmentInformation;
  32. import org.sonar.scanner.bootstrap.GlobalAnalysisMode;
  33. import org.sonar.scanner.bootstrap.ScannerProperties;
  34. import org.sonar.scanner.bootstrap.SonarUserHome;
  35. import org.sonar.scanner.http.ssl.CertificateStore;
  36. import org.sonar.scanner.http.ssl.SslConfig;
  37. import org.sonarqube.ws.client.HttpConnector;
  38. import org.sonarqube.ws.client.WsClientFactories;
  39. import org.springframework.context.annotation.Bean;
  40. import static java.lang.Integer.parseInt;
  41. import static java.lang.String.valueOf;
  42. import static org.apache.commons.lang3.StringUtils.defaultIfBlank;
  43. import static org.apache.commons.lang3.StringUtils.isNotBlank;
  44. import static org.sonar.core.config.ProxyProperties.HTTP_PROXY_PASSWORD;
  45. import static org.sonar.core.config.ProxyProperties.HTTP_PROXY_USER;
  46. public class ScannerWsClientProvider {
  47. static final int DEFAULT_CONNECT_TIMEOUT = 5;
  48. static final int DEFAULT_RESPONSE_TIMEOUT = 0;
  49. static final String READ_TIMEOUT_SEC_PROPERTY = "sonar.ws.timeout";
  50. public static final String TOKEN_PROPERTY = "sonar.token";
  51. private static final String TOKEN_ENV_VARIABLE = "SONAR_TOKEN";
  52. static final int DEFAULT_READ_TIMEOUT_SEC = 60;
  53. public static final String SONAR_SCANNER_PROXY_PORT = "sonar.scanner.proxyPort";
  54. public static final String SONAR_SCANNER_CONNECT_TIMEOUT = "sonar.scanner.connectTimeout";
  55. public static final String SONAR_SCANNER_SOCKET_TIMEOUT = "sonar.scanner.socketTimeout";
  56. public static final String SONAR_SCANNER_RESPONSE_TIMEOUT = "sonar.scanner.responseTimeout";
  57. @Bean("DefaultScannerWsClient")
  58. public DefaultScannerWsClient provide(ScannerProperties scannerProps, EnvironmentInformation env, GlobalAnalysisMode globalMode,
  59. System2 system, AnalysisWarnings analysisWarnings, SonarUserHome sonarUserHome) {
  60. String url = defaultIfBlank(scannerProps.property("sonar.host.url"), "http://localhost:9000");
  61. HttpConnector.Builder connectorBuilder = HttpConnector.newBuilder().acceptGzip(true);
  62. String oldSocketTimeout = defaultIfBlank(scannerProps.property(READ_TIMEOUT_SEC_PROPERTY), valueOf(DEFAULT_READ_TIMEOUT_SEC));
  63. String socketTimeout = defaultIfBlank(scannerProps.property(SONAR_SCANNER_SOCKET_TIMEOUT), oldSocketTimeout);
  64. String connectTimeout = defaultIfBlank(scannerProps.property(SONAR_SCANNER_CONNECT_TIMEOUT), valueOf(DEFAULT_CONNECT_TIMEOUT));
  65. String responseTimeout = defaultIfBlank(scannerProps.property(SONAR_SCANNER_RESPONSE_TIMEOUT), valueOf(DEFAULT_RESPONSE_TIMEOUT));
  66. String envVarToken = defaultIfBlank(system.envVariable(TOKEN_ENV_VARIABLE), null);
  67. String token = defaultIfBlank(scannerProps.property(TOKEN_PROPERTY), envVarToken);
  68. String login = defaultIfBlank(scannerProps.property(CoreProperties.LOGIN), token);
  69. var sslContext = configureSsl(parseSslConfig(scannerProps, sonarUserHome), system);
  70. connectorBuilder
  71. .readTimeoutMilliseconds(parseDurationProperty(socketTimeout, SONAR_SCANNER_SOCKET_TIMEOUT))
  72. .connectTimeoutMilliseconds(parseDurationProperty(connectTimeout, SONAR_SCANNER_CONNECT_TIMEOUT))
  73. .responseTimeoutMilliseconds(parseDurationProperty(responseTimeout, SONAR_SCANNER_RESPONSE_TIMEOUT))
  74. .userAgent(env.toString())
  75. .url(url)
  76. .credentials(login, scannerProps.property(CoreProperties.PASSWORD))
  77. .setSSLSocketFactory(sslContext.getSslSocketFactory())
  78. .setTrustManager(sslContext.getTrustManager().orElseThrow());
  79. // OkHttp detects 'http.proxyHost' java property already, so just focus on sonar properties
  80. String proxyHost = defaultIfBlank(scannerProps.property("sonar.scanner.proxyHost"), null);
  81. if (proxyHost != null) {
  82. String proxyPortStr = defaultIfBlank(scannerProps.property(SONAR_SCANNER_PROXY_PORT), url.startsWith("https") ? "443" : "80");
  83. var proxyPort = parseIntProperty(proxyPortStr, SONAR_SCANNER_PROXY_PORT);
  84. connectorBuilder.proxy(new Proxy(Proxy.Type.HTTP, new InetSocketAddress(proxyHost, proxyPort)));
  85. }
  86. var scannerProxyUser = scannerProps.property("sonar.scanner.proxyUser");
  87. String proxyUser = scannerProxyUser != null ? scannerProxyUser : system.properties().getProperty(HTTP_PROXY_USER, "");
  88. if (isNotBlank(proxyUser)) {
  89. var scannerProxyPwd = scannerProps.property("sonar.scanner.proxyPassword");
  90. String proxyPassword = scannerProxyPwd != null ? scannerProxyPwd : system.properties().getProperty(HTTP_PROXY_PASSWORD, "");
  91. connectorBuilder.proxyCredentials(proxyUser, proxyPassword);
  92. }
  93. return new DefaultScannerWsClient(WsClientFactories.getDefault().newClient(connectorBuilder.build()), login != null, globalMode, analysisWarnings);
  94. }
  95. private static int parseIntProperty(String propValue, String propKey) {
  96. try {
  97. return parseInt(propValue);
  98. } catch (NumberFormatException e) {
  99. throw new IllegalArgumentException(propKey + " is not a valid integer: " + propValue, e);
  100. }
  101. }
  102. /**
  103. * For testing, we can accept timeouts that are smaller than a second, expressed using ISO-8601 format for durations.
  104. * If we can't parse as ISO-8601, then fallback to the official format that is simply the number of seconds
  105. */
  106. private static int parseDurationProperty(String propValue, String propKey) {
  107. try {
  108. return (int) Duration.parse(propValue).toMillis();
  109. } catch (DateTimeParseException e) {
  110. return parseIntProperty(propValue, propKey) * 1_000;
  111. }
  112. }
  113. private static SslConfig parseSslConfig(ScannerProperties scannerProperties, SonarUserHome sonarUserHome) {
  114. var keyStorePath = defaultIfBlank(scannerProperties.property("sonar.scanner.keystorePath"), sonarUserHome.getPath().resolve("ssl/keystore.p12").toString());
  115. var keyStorePassword = defaultIfBlank(scannerProperties.property("sonar.scanner.keystorePassword"), CertificateStore.DEFAULT_PASSWORD);
  116. var trustStorePath = defaultIfBlank(scannerProperties.property("sonar.scanner.truststorePath"), sonarUserHome.getPath().resolve("ssl/truststore.p12").toString());
  117. var trustStorePassword = defaultIfBlank(scannerProperties.property("sonar.scanner.truststorePassword"), CertificateStore.DEFAULT_PASSWORD);
  118. var keyStore = new CertificateStore(Path.of(keyStorePath), keyStorePassword);
  119. var trustStore = new CertificateStore(Path.of(trustStorePath), trustStorePassword);
  120. return new SslConfig(keyStore, trustStore);
  121. }
  122. private static SSLFactory configureSsl(SslConfig sslConfig, System2 system2) {
  123. var sslFactoryBuilder = SSLFactory.builder()
  124. .withDefaultTrustMaterial()
  125. .withSystemTrustMaterial();
  126. if (system2.properties().containsKey("javax.net.ssl.keyStore")) {
  127. sslFactoryBuilder.withSystemPropertyDerivedIdentityMaterial();
  128. }
  129. var keyStore = sslConfig.getKeyStore();
  130. if (keyStore != null && Files.exists(keyStore.getPath())) {
  131. sslFactoryBuilder.withIdentityMaterial(keyStore.getPath(), keyStore.getKeyStorePassword().toCharArray(), keyStore.getKeyStoreType());
  132. }
  133. var trustStore = sslConfig.getTrustStore();
  134. if (trustStore != null && Files.exists(trustStore.getPath())) {
  135. sslFactoryBuilder.withTrustMaterial(trustStore.getPath(), trustStore.getKeyStorePassword().toCharArray(), trustStore.getKeyStoreType());
  136. }
  137. return sslFactoryBuilder.build();
  138. }
  139. }