Skip to content

Commit

Permalink
Auto detection of proxy settings
Browse files Browse the repository at this point in the history
  • Loading branch information
breedloj committed Feb 17, 2025
1 parent fe7852e commit 6140493
Show file tree
Hide file tree
Showing 6 changed files with 286 additions and 89 deletions.
1 change: 1 addition & 0 deletions plugin/META-INF/MANIFEST.MF
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,7 @@ Bundle-Classpath: target/classes/,
target/dependency/nimbus-jose-jwt-9.41.2.jar,
target/dependency/profiles-2.28.26.jar,
target/dependency/protocol-core-2.28.26.jar,
target/dependency/proxy-vole-1.1.6.jar,
target/dependency/reactive-streams-1.0.4.jar,
target/dependency/regions-2.28.26.jar,
target/dependency/retries-2.28.26.jar,
Expand Down
29 changes: 17 additions & 12 deletions plugin/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -42,21 +42,21 @@
</dependencyManagement>

<dependencies>
<dependency>
<groupId>io.reactivex.rxjava3</groupId>
<artifactId>rxjava</artifactId>
<version>3.1.5</version>
</dependency>
<dependency>
<groupId>io.reactivex.rxjava3</groupId>
<artifactId>rxjava</artifactId>
<version>3.1.5</version>
</dependency>
<dependency>
<groupId>jakarta.inject</groupId>
<artifactId>jakarta.inject-api</artifactId>
<version>2.0.1</version>
</dependency>
<dependency>
<groupId>io.reactivex.rxjava3</groupId>
<artifactId>rxjava</artifactId>
<version>3.1.5</version>
</dependency>
<dependency>
<groupId>io.reactivex.rxjava3</groupId>
<artifactId>rxjava</artifactId>
<version>3.1.5</version>
</dependency>
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
Expand Down Expand Up @@ -106,6 +106,11 @@
<artifactId>maven-artifact</artifactId>
<version>3.9.9</version>
</dependency>
<dependency>
<groupId>org.bidib.com.github.markusbernhardt</groupId>
<artifactId>proxy-vole</artifactId>
<version>1.1.6</version>
</dependency>
<dependency>
<groupId>org.junit.jupiter</groupId>
<artifactId>junit-jupiter</artifactId>
Expand All @@ -118,7 +123,7 @@
<scope>test</scope>
</dependency>
</dependencies>

<build>
<sourceDirectory>src</sourceDirectory>
<testSourceDirectory>tst</testSourceDirectory>
Expand Down Expand Up @@ -149,7 +154,7 @@
</goals>
<configuration>
<outputDirectory>${project.build.directory}/dependency</outputDirectory>
<includeGroupIds>io.reactivex,software.amazon.awssdk,com.fasterxml.jackson,com.nimbusds,jakarta.inject,commons-codec,org.apache.httpcomponents,org.reactivestreams,org.apache.maven</includeGroupIds>
<includeGroupIds>io.reactivex,software.amazon.awssdk,com.fasterxml.jackson,com.nimbusds,jakarta.inject,commons-codec,org.apache.httpcomponents,org.reactivestreams,org.apache.maven,org.bidib.com.github.markusbernhardt</includeGroupIds>
</configuration>
</execution>
<execution>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@
import software.aws.toolkits.eclipse.amazonq.util.HttpClientFactory;
import software.aws.toolkits.eclipse.amazonq.util.ObjectMapperFactory;
import software.aws.toolkits.eclipse.amazonq.util.PluginUtils;
import software.aws.toolkits.eclipse.amazonq.util.ThreadingUtils;
import software.aws.toolkits.eclipse.amazonq.util.ToolkitNotification;
import software.aws.toolkits.telemetry.TelemetryDefinitions.ManifestLocation;
import software.aws.toolkits.telemetry.TelemetryDefinitions.Result;
Expand Down Expand Up @@ -85,11 +86,25 @@ public Optional<Manifest> fetch() {
return latestManifest;
} catch (Exception e) {
if (e.getCause() instanceof SSLHandshakeException) {
Display.getCurrent().asyncExec(() -> {
AbstractNotificationPopup notification = new ToolkitNotification(Display.getCurrent(),
Constants.IDE_SSL_HANDSHAKE_TITLE,
Constants.IDE_SSL_HANDSHAKE_BODY);
notification.open();
ThreadingUtils.executeAsyncTask(() -> {
Display display = null;
while (display == null) {
display = Display.getDefault();
if (display == null) {
try {
Thread.sleep(100);
} catch (InterruptedException interrupted) {
Thread.currentThread().interrupt();
return;
}
}
}
display.asyncExec(() -> {
AbstractNotificationPopup notification = new ToolkitNotification(Display.getCurrent(),
Constants.IDE_SSL_HANDSHAKE_TITLE,
Constants.IDE_SSL_HANDSHAKE_BODY);
notification.open();
});
});
}
Activator.getLogger().error("Error fetching manifest from remote location", e);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
import javax.net.ssl.SSLContext;

import org.apache.http.conn.ssl.SSLConnectionSocketFactory;
import org.apache.http.impl.client.SystemDefaultCredentialsProvider;

import software.amazon.awssdk.auth.credentials.AnonymousCredentialsProvider;
import software.amazon.awssdk.core.client.config.ClientOverrideConfiguration;
Expand Down Expand Up @@ -141,7 +142,7 @@ private static ToolkitTelemetryClient createDefaultTelemetryClient(final Region
null,
SSLConnectionSocketFactory.getDefaultHostnameVerifier()
);
var proxyUrl = ProxyUtil.getHttpsProxyUrl();
var proxyUrl = ProxyUtil.getHttpsProxyUrlForEndpoint(endpoint);
var httpClientBuilder = ApacheHttpClient.builder();
if (!StringUtils.isEmpty(proxyUrl)) {
httpClientBuilder.proxyConfiguration(ProxyConfiguration.builder()
Expand All @@ -151,7 +152,9 @@ private static ToolkitTelemetryClient createDefaultTelemetryClient(final Region

httpClientBuilder.socketFactory(sslSocketFactory);

SdkHttpClient sdkHttpClient = httpClientBuilder.build();
SdkHttpClient sdkHttpClient = httpClientBuilder
.credentialsProvider(new SystemDefaultCredentialsProvider())
.build();
CognitoIdentityClient cognitoClient = CognitoIdentityClient.builder()
.credentialsProvider(AnonymousCredentialsProvider.create())
.region(region)
Expand Down
193 changes: 145 additions & 48 deletions plugin/src/software/aws/toolkits/eclipse/amazonq/util/ProxyUtil.java
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,13 @@
package software.aws.toolkits.eclipse.amazonq.util;

import java.io.FileInputStream;
import java.net.InetSocketAddress;
import java.net.MalformedURLException;
import java.net.Proxy;
import java.net.ProxySelector;
import java.net.URI;
import java.net.URISyntaxException;
import java.net.URL;
import java.security.KeyStore;
import java.security.cert.CertificateFactory;
import java.security.cert.X509Certificate;
Expand All @@ -15,80 +21,171 @@
import org.eclipse.mylyn.commons.ui.dialogs.AbstractNotificationPopup;
import org.eclipse.swt.widgets.Display;

import com.github.markusbernhardt.proxy.ProxySearch;

import software.amazon.awssdk.utils.StringUtils;
import software.aws.toolkits.eclipse.amazonq.plugin.Activator;
import software.aws.toolkits.eclipse.amazonq.preferences.AmazonQPreferencePage;

public final class ProxyUtil {
private static final String DEFAULT_PROXY_ENDPOINT = "https://amazonaws.com";
private static volatile boolean hasSeenInvalidProxyNotification;

private static boolean hasSeenInvalidProxyNotification;
private static ProxySelector proxySelector;

private ProxyUtil() {
// Prevent initialization
}
private ProxyUtil() { } // Prevent initialization

public static String getHttpsProxyUrl() {
return getHttpsProxyUrl(System.getenv("HTTPS_PROXY"),
Activator.getDefault().getPreferenceStore().getString(AmazonQPreferencePage.HTTPS_PROXY));
return getHttpsProxyUrlForEndpoint(DEFAULT_PROXY_ENDPOINT);
}

protected static String getHttpsProxyUrl(final String envVarValue, final String prefValue) {
String httpsProxy = envVarValue;
if (!StringUtils.isEmpty(prefValue)) {
httpsProxy = prefValue;
}
public static String getHttpsProxyUrlForEndpoint(final String endpointUrl) {
try {
if (!StringUtils.isEmpty(httpsProxy)) {
URI.create(httpsProxy);
}
} catch (IllegalArgumentException e) {
if (!hasSeenInvalidProxyNotification) {
hasSeenInvalidProxyNotification = true;
Display.getDefault().asyncExec(() -> {
AbstractNotificationPopup notification = new ToolkitNotification(Display.getCurrent(),
Constants.INVALID_PROXY_CONFIGURATION_TITLE,
Constants.INVALID_PROXY_CONFIGURATION_BODY);
notification.open();
});
String proxyPrefUrl = getHttpsProxyPreferenceUrl();
if (!StringUtils.isEmpty(proxyPrefUrl)) {
return proxyPrefUrl;
}
} catch (MalformedURLException e) {
showInvalidProxyNotification();
return null;
}
return httpsProxy;
}

public static SSLContext getCustomSslContext() {
if (StringUtils.isEmpty(endpointUrl)) {
return null;
}

URI endpointUri;
try {
String customCertPath = System.getenv("NODE_EXTRA_CA_CERTS");
String caCertPreference = Activator.getDefault().getPreferenceStore().getString(AmazonQPreferencePage.CA_CERT);
if (!StringUtils.isEmpty(caCertPreference)) {
customCertPath = caCertPreference;
}
endpointUri = new URI(endpointUrl);
} catch (URISyntaxException e) {
Activator.getLogger().error("Could not parse endpoint for proxy configuration: " + endpointUrl, e);
return null;
}

return getProxyUrlFromSelector(getProxySelector(), endpointUri);
}

if (customCertPath != null && !customCertPath.isEmpty()) {
CertificateFactory certificateFactory = CertificateFactory.getInstance("X.509");
X509Certificate cert;
private static String getProxyUrlFromSelector(final ProxySelector proxySelector, final URI endpointUri) {
if (proxySelector == null) {
return null;
}

var proxies = proxySelector.select(endpointUri);
if (proxies == null || proxies.isEmpty()) {
return null;
}

return proxies.stream()
.filter(p -> p.type() != Proxy.Type.DIRECT)
.findFirst()
.map(proxy -> createProxyUrl(proxy, endpointUri))
.orElseGet(() -> {
Activator.getLogger().info("No non-DIRECT proxies found for endpoint: " + endpointUri);
return null;
});
}

try (FileInputStream fis = new FileInputStream(customCertPath)) {
cert = (X509Certificate) certificateFactory.generateCertificate(fis);
}
private static String createProxyUrl(final Proxy proxy, final URI endpointUri) {
if (!(proxy.address() instanceof InetSocketAddress addr)) {
return null;
}

KeyStore keyStore = KeyStore.getInstance(KeyStore.getDefaultType());
keyStore.load(null, null);
keyStore.setCertificateEntry("custom-cert", cert);
String scheme = determineProxyScheme(proxy.type(), endpointUri);
if (scheme == null) {
return null;
}

TrustManagerFactory tmf = TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm());
tmf.init(keyStore);
String proxyUrl = String.format("%s://%s:%d", scheme, addr.getHostString(), addr.getPort());
Activator.getLogger().info("Using proxy URL: " + proxyUrl + " for endpoint: " + endpointUri);
return proxyUrl;
}

SSLContext sslContext = SSLContext.getInstance("TLSv1.2");
sslContext.init(null, tmf.getTrustManagers(), null);
Activator.getLogger().info("Picked up custom CA cert.");
private static String determineProxyScheme(final Proxy.Type proxyType, final URI endpointUri) {
return switch (proxyType) {
case HTTP -> "http";
case SOCKS -> "socks";
default -> null;
};
}

return sslContext;
}
protected static String getHttpsProxyPreferenceUrl() throws MalformedURLException {
String prefValue = Activator.getDefault().getPreferenceStore()
.getString(AmazonQPreferencePage.HTTPS_PROXY);

if (StringUtils.isEmpty(prefValue)) {
return null;
}

new URL(prefValue); // Validate URL format
return prefValue;
}

private static void showInvalidProxyNotification() {
if (!hasSeenInvalidProxyNotification) {
hasSeenInvalidProxyNotification = true;
Display.getDefault().asyncExec(() -> {
AbstractNotificationPopup notification = new ToolkitNotification(
Display.getCurrent(),
Constants.INVALID_PROXY_CONFIGURATION_TITLE,
Constants.INVALID_PROXY_CONFIGURATION_BODY
);
notification.open();
});
}
}

public static SSLContext getCustomSslContext() {
String customCertPath = getCustomCertPath();
if (StringUtils.isEmpty(customCertPath)) {
return null;
}

try {
return createSslContextWithCustomCert(customCertPath);
} catch (Exception e) {
Activator.getLogger().error("Failed to set up SSL context. Additional certs will not be used.", e);
return null;
}
}

private static String getCustomCertPath() {
String caCertPreference = Activator.getDefault().getPreferenceStore().getString(AmazonQPreferencePage.CA_CERT);
return !StringUtils.isEmpty(caCertPreference) ? caCertPreference : System.getenv("NODE_EXTRA_CA_CERTS");
}

private static SSLContext createSslContextWithCustomCert(final String certPath) throws Exception {
CertificateFactory certificateFactory = CertificateFactory.getInstance("X.509");
X509Certificate cert;

try (FileInputStream fis = new FileInputStream(certPath)) {
cert = (X509Certificate) certificateFactory.generateCertificate(fis);
}
return null;

KeyStore keyStore = KeyStore.getInstance(KeyStore.getDefaultType());
keyStore.load(null, null);
keyStore.setCertificateEntry("custom-cert", cert);

TrustManagerFactory tmf = TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm());
tmf.init(keyStore);

SSLContext sslContext = SSLContext.getInstance("TLSv1.2");
sslContext.init(null, tmf.getTrustManagers(), null);
Activator.getLogger().info("Picked up custom CA cert.");

return sslContext;
}

static synchronized ProxySelector getProxySelector() {
if (proxySelector == null) {
ProxySearch proxySearch = new ProxySearch();
proxySearch.addStrategy(ProxySearch.Strategy.ENV_VAR);
proxySearch.addStrategy(ProxySearch.Strategy.JAVA);
proxySearch.addStrategy(ProxySearch.Strategy.OS_DEFAULT);
if (System.getProperty("os.name").toLowerCase().contains("windows")) {
proxySearch.addStrategy(ProxySearch.Strategy.IE);
}
proxySelector = proxySearch.getProxySelector();
}
return proxySelector;
}
}
Loading

0 comments on commit 6140493

Please sign in to comment.