From 4e96a03d44316964126c50c9acd75ce61f467a12 Mon Sep 17 00:00:00 2001 From: mcmics Date: Mon, 25 Sep 2023 22:51:57 +0200 Subject: [PATCH] add Socks support --- gradle.properties | 1 + .../security/DefaultSecurityClient.java | 20 ++- .../JenkinsConnectionSocketFactory.java | 168 ++++++++++++++++++ .../jenkins/security/JenkinsDnsResolver.java | 33 ++++ 4 files changed, 215 insertions(+), 7 deletions(-) create mode 100644 src/main/java/org/codinjutsu/tools/jenkins/security/JenkinsConnectionSocketFactory.java create mode 100644 src/main/java/org/codinjutsu/tools/jenkins/security/JenkinsDnsResolver.java diff --git a/gradle.properties b/gradle.properties index 29a8e289..5f79cf31 100644 --- a/gradle.properties +++ b/gradle.properties @@ -29,6 +29,7 @@ platformVersionToVerify=232.8660.142 # Plugin Dependencies -> https://plugins.jetbrains.com/docs/intellij/plugin-dependencies.html # Example: platformPlugins = com.intellij.java, com.jetbrains.php:203.4449.22, com.intellij.kubernetes +#platformPlugins=com.intellij.java,com.jetbrains.restClient platformPlugins=com.intellij.java # Java language level used to compile sources and to generate the files for - Java 11 is required since 2020.3 diff --git a/src/main/java/org/codinjutsu/tools/jenkins/security/DefaultSecurityClient.java b/src/main/java/org/codinjutsu/tools/jenkins/security/DefaultSecurityClient.java index 186bde3b..531ebeae 100644 --- a/src/main/java/org/codinjutsu/tools/jenkins/security/DefaultSecurityClient.java +++ b/src/main/java/org/codinjutsu/tools/jenkins/security/DefaultSecurityClient.java @@ -86,6 +86,7 @@ class DefaultSecurityClient implements SecurityClient { .setConnectionRequestTimeout(connectionTimout) .setSocketTimeout(connectionTimout); this.credentialsProvider = new BasicCredentialsProvider(); + final var dnsResolver = new JenkinsDnsResolver(); final RequestConfig defaultRequestConfig = requestConfig .setMaxRedirects(10) @@ -94,14 +95,19 @@ class DefaultSecurityClient implements SecurityClient { .setSSLContext(sslContext) .setDefaultSocketConfig(socketConfig) .setDefaultRequestConfig(defaultRequestConfig) + .setDnsResolver(dnsResolver) .setDefaultCredentialsProvider(credentialsProvider) .setRedirectStrategy(new JenkinsRedirectStrategy()); this.httpClient = httpClientBuilder.build(); if (useProxySettings) { this.configCreator = url -> { final var configForUrl = RequestConfig.copy(defaultRequestConfig); - IdeHttpClientHelpers.ApacheHttpClient4.setProxyForUrlIfEnabled(configForUrl, url); - IdeHttpClientHelpers.ApacheHttpClient4.setProxyCredentialsForUrlIfEnabled(credentialsProvider, url); + final var useSocks = JenkinsConnectionSocketFactory.INSTANCE.prepareContext(url, sslContext, + getHttpClientContext(), dnsResolver); + if (!useSocks) { + IdeHttpClientHelpers.ApacheHttpClient4.setProxyForUrlIfEnabled(configForUrl, url); + IdeHttpClientHelpers.ApacheHttpClient4.setProxyCredentialsForUrlIfEnabled(credentialsProvider, url); + } return configForUrl.build(); }; } else { @@ -180,7 +186,7 @@ protected final void addAuthenticationPreemptive(HttpHost host, UsernamePassword protected final HttpHost getLastRedirectionHost(HttpHost host) { final var httpHead = new HttpHead(host.toURI()); - LOG.trace(String.format("Sending HEAD request to: %s", host.toURI())); + LOG.trace(String.format(" %s", host.toURI())); try { final var context = getHttpClientContext(); final var response = executeHttp(httpHead); @@ -249,14 +255,14 @@ private void runMethod(String url, @NotNull Collection data, Respon } protected final HttpResponse executeHttp(HttpPost post) throws IOException { - final var postConfig = post.getConfig(); - if (postConfig == null) { - post.setConfig(configCreator.apply(post.getURI().toASCIIString())); - } return executeHttp((HttpRequestBase) post); } protected final HttpResponse executeHttp(HttpRequestBase httpRequest) throws IOException { + final var httpRequestConfig = httpRequest.getConfig(); + if (httpRequestConfig == null) { + httpRequest.setConfig(configCreator.apply(httpRequest.getURI().toASCIIString())); + } return getHttpClient().execute(httpRequest, this.httpClientContext); } diff --git a/src/main/java/org/codinjutsu/tools/jenkins/security/JenkinsConnectionSocketFactory.java b/src/main/java/org/codinjutsu/tools/jenkins/security/JenkinsConnectionSocketFactory.java new file mode 100644 index 00000000..f50e05ed --- /dev/null +++ b/src/main/java/org/codinjutsu/tools/jenkins/security/JenkinsConnectionSocketFactory.java @@ -0,0 +1,168 @@ +package org.codinjutsu.tools.jenkins.security; + +import com.intellij.openapi.vfs.VfsUtil; +import com.intellij.util.net.HttpConfigurable; +import com.intellij.util.net.IdeaWideProxySelector; +import org.apache.http.HttpHost; +import org.apache.http.client.protocol.HttpClientContext; +import org.apache.http.config.Registry; +import org.apache.http.config.RegistryBuilder; +import org.apache.http.conn.socket.ConnectionSocketFactory; +import org.apache.http.conn.socket.PlainConnectionSocketFactory; +import org.apache.http.conn.ssl.DefaultHostnameVerifier; +import org.apache.http.conn.ssl.SSLConnectionSocketFactory; +import org.apache.http.conn.util.PublicSuffixMatcherLoader; +import org.apache.http.protocol.HttpContext; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +import javax.net.ssl.HostnameVerifier; +import javax.net.ssl.SSLContext; +import java.io.IOException; +import java.net.InetSocketAddress; +import java.net.Proxy; +import java.net.Socket; +import java.net.URI; +import java.util.Collections; +import java.util.List; + +public class JenkinsConnectionSocketFactory { + @NotNull + public static final JenkinsConnectionSocketFactory INSTANCE = new JenkinsConnectionSocketFactory(); + + public @Nullable Registry getRegistry(@NotNull String url, @NotNull SSLContext sslContext) { + final Proxy proxy = getProxy(url); + if(proxy.type() == Proxy.Type.SOCKS) { + return createSocksRegistry(toUri(url), proxy, sslContext); + } + return createDefaultRegistry(sslContext); + } + + @NotNull + private static Registry createDefaultRegistry(@NotNull SSLContext sslContext) { + String[] supportedProtocols = null; + String[] supportedCipherSuites = null; + HostnameVerifier proxyAuthStrategyCopy = new DefaultHostnameVerifier(PublicSuffixMatcherLoader.getDefault()); + final SSLConnectionSocketFactory sslConnectionSocketFactory = new SSLConnectionSocketFactory(sslContext, + supportedProtocols, supportedCipherSuites, proxyAuthStrategyCopy); + + return RegistryBuilder.create() + .register("http", PlainConnectionSocketFactory.getSocketFactory()) + .register("https", sslConnectionSocketFactory) + .build(); + } + + public boolean prepareContext(@NotNull String url, + @NotNull SSLContext sslContext, + @NotNull HttpClientContext httpClientContext, + @NotNull JenkinsDnsResolver dnsResolver) { + httpClientContext.removeAttribute("http.socket-factory-registry"); + dnsResolver.clearFakes(); + final URI uri = toUri(url); + final Proxy proxy = getProxy(uri); + if(proxy.type() == Proxy.Type.SOCKS) { + // see org.apache.http.impl.conn.DefaultHttpClientConnectionOperator.SOCKET_FACTORY_REGISTRY + httpClientContext.setAttribute("http.socket-factory-registry", getRegistry(url, sslContext)); + if (uri != null) { + dnsResolver.addFakeHost(uri.getHost()); + } + return true; + } + return false; + } + + private Registry createSocksRegistry(@Nullable URI uri, + @NotNull Proxy proxy, + @NotNull SSLContext sslContext) { + return RegistryBuilder.create() + .register("http", new SocksPlainConnectionSocketFactory(proxy)) + .register("https", new SocksSSLConnectionSocketFactory(uri != null ? uri.getHost() : null, sslContext, proxy)) + .build(); + } + + @NotNull + private static HttpConfigurable getHttpConfigurable() { + return HttpConfigurable.getInstance(); + } + + private static @NotNull Proxy getProxy(String url) { + return getProxy(toUri(url)); + } + + private static @NotNull Proxy getProxy(@Nullable URI uri) { + final List proxies = uri == null ? Collections.emptyList() : + new IdeaWideProxySelector(getHttpConfigurable()).select(uri); + return proxies.isEmpty() ? Proxy.NO_PROXY : proxies.get(0); + } + + @Nullable + private static URI toUri(String url) { + return url != null ? VfsUtil.toUri(url) : null; + } + + + private static final class SocksPlainConnectionSocketFactory extends PlainConnectionSocketFactory { + @NotNull + private final Proxy proxy; + + public SocksPlainConnectionSocketFactory(@NotNull Proxy proxy) { + super(); + this.proxy = proxy; + } + + @NotNull + public Proxy getProxy() { + return this.proxy; + } + + @NotNull + public Socket createSocket(@Nullable HttpContext context) { + return new Socket(this.proxy); + } + + @NotNull + public Socket connectSocket(int connectTimeout, + @Nullable Socket socket, + @NotNull HttpHost host, + @NotNull InetSocketAddress remoteAddress, + @Nullable InetSocketAddress localAddress, + @Nullable HttpContext context) throws IOException { + InetSocketAddress unresolvedRemote = InetSocketAddress.createUnresolved(host.getHostName(), remoteAddress.getPort()); + return super.connectSocket(connectTimeout, socket, host, unresolvedRemote, localAddress, context); + } + } + + private static final class SocksSSLConnectionSocketFactory extends SSLConnectionSocketFactory { + @Nullable + private final String host; + @NotNull + private final Proxy proxy; + + public SocksSSLConnectionSocketFactory(@Nullable String host, @NotNull SSLContext sslContext, @NotNull Proxy proxy) { + super(sslContext); + this.host = host; + this.proxy = proxy; + } + + @NotNull + public Proxy getProxy() { + return this.proxy; + } + + @NotNull + public Socket createSocket(@NotNull HttpContext context) { + return new Socket(this.proxy); + } + + @NotNull + public Socket connectSocket(int connectTimeout, + @Nullable Socket socket, + @NotNull HttpHost host, + @NotNull InetSocketAddress remoteAddress, + @Nullable InetSocketAddress localAddress, + @Nullable HttpContext context) throws IOException { + final var unresolvedRemote = InetSocketAddress.createUnresolved(host.getHostName(), remoteAddress.getPort()); + return super.connectSocket(connectTimeout, socket, host, unresolvedRemote, localAddress, context); + } + } +} diff --git a/src/main/java/org/codinjutsu/tools/jenkins/security/JenkinsDnsResolver.java b/src/main/java/org/codinjutsu/tools/jenkins/security/JenkinsDnsResolver.java new file mode 100644 index 00000000..fd597e77 --- /dev/null +++ b/src/main/java/org/codinjutsu/tools/jenkins/security/JenkinsDnsResolver.java @@ -0,0 +1,33 @@ +package org.codinjutsu.tools.jenkins.security; + +import org.apache.http.conn.DnsResolver; +import org.apache.http.impl.conn.SystemDefaultDnsResolver; +import org.jetbrains.annotations.NotNull; + +import java.net.InetAddress; +import java.net.UnknownHostException; +import java.util.HashSet; +import java.util.Set; + +public class JenkinsDnsResolver implements DnsResolver { + private static final DnsResolver DNS_RESOLVER = SystemDefaultDnsResolver.INSTANCE; + private final Set hostWithFakeDns = new HashSet<>(); + + public void clearFakes() { + hostWithFakeDns.clear(); + } + + public void addFakeHost(String host) { + hostWithFakeDns.add(host); + } + + @Override + public InetAddress[] resolve(@NotNull String host) throws UnknownHostException { + return hostWithFakeDns.contains(host) ? returnFakeDns(): DNS_RESOLVER.resolve(host); + } + + private InetAddress[] returnFakeDns() throws UnknownHostException { + final var fakeIp = new byte[]{1, 1, 1, 1}; + return new InetAddress[]{InetAddress.getByAddress(fakeIp)}; + } +}