) () -> {
+ GSSContext ctx = createGSSContext(getManager(), proxyEndpoint);
+ ctx.requestMutualAuth(true);
+ return ctx.initSecContext(new byte[0], 0, 0);
+ });
+
+ return BinaryUtils.toBase64(token);
+ } catch (PrivilegedActionException e) {
+ throw new RuntimeException("Unable to generate token", e);
+ }
+ }
+
+ private Subject getSubject() {
+ try {
+ LoginContext loginContext = new LoginContext("dummy", null, null, config);
+ loginContext.login();
+ return loginContext.getSubject();
+ } catch (LoginException e) {
+ throw new RuntimeException("Unable to perform login", e);
+ }
+ }
+
+ private GSSContext createGSSContext(GSSManager manager, URI endpoint) {
+ try {
+ String name = String.format("%s@%s", SERVICE_NAME, endpoint.getHost());
+ GSSName serverName = manager.createName(name, GSSName.NT_HOSTBASED_SERVICE);
+ Oid spnegoOid = new Oid(OID);
+ return manager.createContext(serverName, spnegoOid, null,
+ GSSContext.DEFAULT_LIFETIME);
+ } catch (GSSException e) {
+ throw new RuntimeException("Unable to create GSSContext", e);
+ }
+ }
+
+ private static GSSManager getManager() {
+ return GSSManager.getInstance();
+ }
+
+ /**
+ * Create a generic {@link Configuration} that instructs the Kerberos login module to simply look in the ticket cache, and
+ * not to prompt for passwords.
+ *
+ * See javadoc for {@link Krb5LoginModule} for additional info on the configuration options.
+ */
+ private static Configuration createDefaultConfig() {
+ return new Configuration() {
+ @Override
+ public AppConfigurationEntry[] getAppConfigurationEntry(String name) {
+ Map opts = new HashMap<>();
+ opts.put("useTicketCache", "true");
+ opts.put("doNotPrompt", "true");
+ return new AppConfigurationEntry[] {
+ new AppConfigurationEntry(
+ "com.sun.security.auth.module.Krb5LoginModule",
+ AppConfigurationEntry.LoginModuleControlFlag.REQUIRED, opts)
+ };
+ }
+ };
+ }
+}
diff --git a/http-clients/netty-nio-client/src/main/java/software/amazon/awssdk/http/nio/netty/internal/ProxyAuthGenerator.java b/http-clients/netty-nio-client/src/main/java/software/amazon/awssdk/http/nio/netty/internal/ProxyAuthGenerator.java
new file mode 100644
index 000000000000..b078500b09ac
--- /dev/null
+++ b/http-clients/netty-nio-client/src/main/java/software/amazon/awssdk/http/nio/netty/internal/ProxyAuthGenerator.java
@@ -0,0 +1,37 @@
+/*
+ * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License").
+ * You may not use this file except in compliance with the License.
+ * A copy of the License is located at
+ *
+ * http://aws.amazon.com/apache2.0
+ *
+ * or in the "license" file accompanying this file. This file is distributed
+ * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either
+ * express or implied. See the License for the specific language governing
+ * permissions and limitations under the License.
+ */
+
+package software.amazon.awssdk.http.nio.netty.internal;
+
+import io.netty.handler.codec.http.HttpRequest;
+import java.net.URI;
+import software.amazon.awssdk.annotations.SdkInternalApi;
+import software.amazon.awssdk.http.nio.netty.ProxyAuthScheme;
+
+/**
+ * Generates the auth params for an {@code Authorization} HTTP header.
+ */
+@SdkInternalApi
+public interface ProxyAuthGenerator {
+ /**
+ * The name of the auth scheme this generator supports.
+ */
+ ProxyAuthScheme scheme();
+
+ /**
+ * Generate the auth params for this request.
+ */
+ String generateAuthParams(URI proxyEndpoint);
+}
diff --git a/http-clients/netty-nio-client/src/main/java/software/amazon/awssdk/http/nio/netty/internal/ProxyTunnelInitHandler.java b/http-clients/netty-nio-client/src/main/java/software/amazon/awssdk/http/nio/netty/internal/ProxyTunnelInitHandler.java
index e6f309afbad3..277ed555dbc5 100644
--- a/http-clients/netty-nio-client/src/main/java/software/amazon/awssdk/http/nio/netty/internal/ProxyTunnelInitHandler.java
+++ b/http-clients/netty-nio-client/src/main/java/software/amazon/awssdk/http/nio/netty/internal/ProxyTunnelInitHandler.java
@@ -28,11 +28,9 @@
import io.netty.handler.codec.http.HttpRequest;
import io.netty.handler.codec.http.HttpResponse;
import io.netty.handler.codec.http.HttpVersion;
-import io.netty.util.CharsetUtil;
import io.netty.util.concurrent.Promise;
import java.io.IOException;
import java.net.URI;
-import java.util.Base64;
import java.util.function.Supplier;
import software.amazon.awssdk.annotations.SdkInternalApi;
import software.amazon.awssdk.annotations.SdkTestInternalApi;
@@ -47,32 +45,51 @@ public final class ProxyTunnelInitHandler extends ChannelDuplexHandler {
public static final NettyClientLogger log = NettyClientLogger.getLogger(ProxyTunnelInitHandler.class);
private final ChannelPool sourcePool;
- private final String username;
- private final String password;
+ private final URI proxyAddress;
+ private final ProxyAuthGenerator authGenerator;
private final URI remoteHost;
private final Promise initPromise;
private final Supplier httpCodecSupplier;
public ProxyTunnelInitHandler(ChannelPool sourcePool, String proxyUsername, String proxyPassword, URI remoteHost,
Promise initPromise) {
- this(sourcePool, proxyUsername, proxyPassword, remoteHost, initPromise, HttpClientCodec::new);
+ this(sourcePool, null, proxyUsername, proxyPassword, remoteHost, initPromise, HttpClientCodec::new);
}
public ProxyTunnelInitHandler(ChannelPool sourcePool, URI remoteHost, Promise initPromise) {
- this(sourcePool, null, null, remoteHost, initPromise, HttpClientCodec::new);
+ this(sourcePool, null, null, null, remoteHost, initPromise, HttpClientCodec::new);
}
@SdkTestInternalApi
- public ProxyTunnelInitHandler(ChannelPool sourcePool, String prosyUsername, String proxyPassword,
+ public ProxyTunnelInitHandler(ChannelPool sourcePool, URI proxyAddress, String proxyUsername, String proxyPassword,
URI remoteHost, Promise initPromise, Supplier httpCodecSupplier) {
this.sourcePool = sourcePool;
+ this.proxyAddress = proxyAddress;
this.remoteHost = remoteHost;
this.initPromise = initPromise;
- this.username = prosyUsername;
- this.password = proxyPassword;
+ if (!StringUtils.isBlank(proxyPassword) && !StringUtils.isBlank(proxyPassword)) {
+ this.authGenerator = new BasicProxyAuthGenerator(proxyUsername, proxyPassword);
+ } else {
+ this.authGenerator = null;
+ }
this.httpCodecSupplier = httpCodecSupplier;
}
+ public ProxyTunnelInitHandler(ChannelPool sourcePool, URI proxyAddress, ProxyAuthGenerator authGenerator,
+ URI remoteHost, Promise initPromise, Supplier httpCodecSupplier) {
+ this.sourcePool = sourcePool;
+ this.proxyAddress = proxyAddress;
+ this.remoteHost = remoteHost;
+ this.initPromise = initPromise;
+ this.authGenerator = authGenerator;
+ this.httpCodecSupplier = httpCodecSupplier;
+ }
+
+ public ProxyTunnelInitHandler(ChannelPool sourcePool, URI proxyAddress, ProxyAuthGenerator authGenerator,
+ URI remoteHost, Promise initPromise) {
+ this(sourcePool, proxyAddress, authGenerator, remoteHost, initPromise, HttpClientCodec::new);
+ }
+
@Override
public void handlerAdded(ChannelHandlerContext ctx) {
ChannelPipeline pipeline = ctx.pipeline();
@@ -151,10 +168,9 @@ private HttpRequest connectRequest() {
Unpooled.EMPTY_BUFFER);
request.headers().add(HttpHeaderNames.HOST, uri);
- if (!StringUtils.isEmpty(this.username) && !StringUtils.isEmpty(this.password)) {
- String authToken = String.format("%s:%s", this.username, this.password);
- String authB64 = Base64.getEncoder().encodeToString(authToken.getBytes(CharsetUtil.UTF_8));
- request.headers().add(HttpHeaderNames.PROXY_AUTHORIZATION, String.format("Basic %s", authB64));
+ if (authGenerator != null) {
+ String auth = String.format("%s %s", authGenerator.scheme().value(), authGenerator.generateAuthParams(proxyAddress));
+ request.headers().add(HttpHeaderNames.PROXY_AUTHORIZATION, auth);
}
return request;
diff --git a/http-clients/netty-nio-client/src/test/java/software/amazon/awssdk/http/nio/netty/internal/Http1TunnelConnectionPoolTest.java b/http-clients/netty-nio-client/src/test/java/software/amazon/awssdk/http/nio/netty/internal/Http1TunnelConnectionPoolTest.java
index d43b404f3f5f..9e2ed53cd1e3 100644
--- a/http-clients/netty-nio-client/src/test/java/software/amazon/awssdk/http/nio/netty/internal/Http1TunnelConnectionPoolTest.java
+++ b/http-clients/netty-nio-client/src/test/java/software/amazon/awssdk/http/nio/netty/internal/Http1TunnelConnectionPoolTest.java
@@ -73,6 +73,8 @@ public class Http1TunnelConnectionPoolTest {
private static final String PROXY_PASSWORD = "mypassword";
+ private static final ProxyAuthGenerator basicAuth = new BasicProxyAuthGenerator(PROXY_USER, PROXY_PASSWORD);
+
@Mock
private ChannelPool delegatePool;
@@ -115,7 +117,7 @@ public static void teardown() {
@Test
public void tunnelAlreadyEstablished_doesNotAddInitHandler() {
Http1TunnelConnectionPool tunnelPool = new Http1TunnelConnectionPool(GROUP.next(), delegatePool, null,
- HTTP_PROXY_ADDRESS, REMOTE_ADDRESS, mockHandler, configuration);
+ HTTP_PROXY_ADDRESS, null, REMOTE_ADDRESS, mockHandler, configuration);
when(mockAttr.get()).thenReturn(true);
@@ -127,7 +129,7 @@ public void tunnelAlreadyEstablished_doesNotAddInitHandler() {
@Test(timeout = 1000)
public void tunnelNotEstablished_addsInitHandler() throws InterruptedException {
Http1TunnelConnectionPool tunnelPool = new Http1TunnelConnectionPool(GROUP.next(), delegatePool, null,
- HTTP_PROXY_ADDRESS, REMOTE_ADDRESS, mockHandler, configuration);
+ HTTP_PROXY_ADDRESS, null, REMOTE_ADDRESS, mockHandler, configuration);
when(mockAttr.get()).thenReturn(false);
@@ -149,7 +151,7 @@ public void tunnelInitFails_acquireFutureFails() {
};
Http1TunnelConnectionPool tunnelPool = new Http1TunnelConnectionPool(GROUP.next(), delegatePool, null,
- HTTP_PROXY_ADDRESS,null, null, REMOTE_ADDRESS, mockHandler, supplier, configuration);
+ HTTP_PROXY_ADDRESS,null, REMOTE_ADDRESS, mockHandler, supplier, configuration);
Future acquireFuture = tunnelPool.acquire();
@@ -164,7 +166,7 @@ public void tunnelInitSucceeds_acquireFutureSucceeds() {
};
Http1TunnelConnectionPool tunnelPool = new Http1TunnelConnectionPool(GROUP.next(), delegatePool, null,
- HTTP_PROXY_ADDRESS, null, null, REMOTE_ADDRESS, mockHandler, supplier, configuration);
+ HTTP_PROXY_ADDRESS, null, REMOTE_ADDRESS, mockHandler, supplier, configuration);
Future acquireFuture = tunnelPool.acquire();
@@ -174,7 +176,7 @@ public void tunnelInitSucceeds_acquireFutureSucceeds() {
@Test
public void acquireFromDelegatePoolFails_failsFuture() {
Http1TunnelConnectionPool tunnelPool = new Http1TunnelConnectionPool(GROUP.next(), delegatePool, null,
- HTTP_PROXY_ADDRESS, REMOTE_ADDRESS, mockHandler, configuration);
+ HTTP_PROXY_ADDRESS, null, REMOTE_ADDRESS, mockHandler, configuration);
when(delegatePool.acquire(any(Promise.class))).thenReturn(GROUP.next().newFailedFuture(new IOException("boom")));
@@ -197,7 +199,7 @@ public void sslContextProvided_andProxyUsingHttps_addsSslHandler() {
};
Http1TunnelConnectionPool tunnelPool = new Http1TunnelConnectionPool(GROUP.next(), delegatePool, mockSslCtx,
- HTTPS_PROXY_ADDRESS, null, null, REMOTE_ADDRESS, mockHandler, supplier, configuration);
+ HTTPS_PROXY_ADDRESS, null, REMOTE_ADDRESS, mockHandler, supplier, configuration);
tunnelPool.acquire().awaitUninterruptibly();
@@ -218,7 +220,7 @@ public void sslContextProvided_andProxyNotUsingHttps_doesNotAddSslHandler() {
};
Http1TunnelConnectionPool tunnelPool = new Http1TunnelConnectionPool(GROUP.next(), delegatePool, mockSslCtx,
- HTTP_PROXY_ADDRESS, null, null, REMOTE_ADDRESS, mockHandler, supplier, configuration);
+ HTTP_PROXY_ADDRESS, null, REMOTE_ADDRESS, mockHandler, supplier, configuration);
tunnelPool.acquire().awaitUninterruptibly();
@@ -231,7 +233,7 @@ public void sslContextProvided_andProxyNotUsingHttps_doesNotAddSslHandler() {
@Test
public void release_releasedToDelegatePool() {
Http1TunnelConnectionPool tunnelPool = new Http1TunnelConnectionPool(GROUP.next(), delegatePool, null,
- HTTP_PROXY_ADDRESS, REMOTE_ADDRESS, mockHandler, configuration);
+ HTTP_PROXY_ADDRESS,null, REMOTE_ADDRESS, mockHandler, configuration);
tunnelPool.release(mockChannel);
verify(delegatePool).release(eq(mockChannel), any(Promise.class));
}
@@ -239,7 +241,7 @@ public void release_releasedToDelegatePool() {
@Test
public void release_withGivenPromise_releasedToDelegatePool() {
Http1TunnelConnectionPool tunnelPool = new Http1TunnelConnectionPool(GROUP.next(), delegatePool, null,
- HTTP_PROXY_ADDRESS, REMOTE_ADDRESS, mockHandler, configuration);
+ HTTP_PROXY_ADDRESS, null, REMOTE_ADDRESS, mockHandler, configuration);
Promise mockPromise = mock(Promise.class);
tunnelPool.release(mockChannel, mockPromise);
verify(delegatePool).release(eq(mockChannel), eq(mockPromise));
@@ -248,7 +250,7 @@ public void release_withGivenPromise_releasedToDelegatePool() {
@Test
public void close_closesDelegatePool() {
Http1TunnelConnectionPool tunnelPool = new Http1TunnelConnectionPool(GROUP.next(), delegatePool, null,
- HTTP_PROXY_ADDRESS, REMOTE_ADDRESS, mockHandler, configuration);
+ HTTP_PROXY_ADDRESS, null, REMOTE_ADDRESS, mockHandler, configuration);
tunnelPool.close();
verify(delegatePool).close();
}
@@ -257,42 +259,32 @@ public void close_closesDelegatePool() {
public void proxyAuthProvided_addInitHandler_withAuth(){
TestInitHandlerData data = new TestInitHandlerData();
- Http1TunnelConnectionPool.InitHandlerSupplier supplier = (srcPool, proxyUser, proxyPassword, remoteAddr, initFuture) -> {
+ Http1TunnelConnectionPool.InitHandlerSupplier supplier =
+ (srcPool, proxyEndpoint, proxyAuthGenerator, remoteAddr, initFuture) -> {
initFuture.setSuccess(mockChannel);
- data.proxyUser(proxyUser);
- data.proxyPassword(proxyPassword);
+ data.authHeader = proxyAuthGenerator.generateAuthParams(proxyEndpoint);
return mock(ChannelHandler.class);
};
Http1TunnelConnectionPool tunnelPool = new Http1TunnelConnectionPool(GROUP.next(), delegatePool, null,
- HTTP_PROXY_ADDRESS, PROXY_USER, PROXY_PASSWORD, REMOTE_ADDRESS, mockHandler, supplier, configuration);
+ HTTP_PROXY_ADDRESS, basicAuth, REMOTE_ADDRESS, mockHandler, supplier, configuration);
tunnelPool.acquire().awaitUninterruptibly();
- assertThat(data.proxyUser()).isEqualTo(PROXY_USER);
- assertThat(data.proxyPassword()).isEqualTo(PROXY_PASSWORD);
-
+ // assertThat(data.proxyUser()).isEqualTo(PROXY_USER);
+ // assertThat(data.proxyPassword()).isEqualTo(PROXY_PASSWORD);
}
private static class TestInitHandlerData {
- private String proxyUser;
- private String proxyPassword;
-
- public void proxyUser(String proxyUser) {
- this.proxyUser = proxyUser;
- }
-
- public String proxyUser() {
- return this.proxyUser;
- }
+ private String authHeader;
- public void proxyPassword(String proxyPassword) {
- this.proxyPassword = proxyPassword;
+ public void authHeader(String authHeader) {
+ this.authHeader = authHeader;
}
- public String proxyPassword(){
- return this.proxyPassword;
+ public String authHeader() {
+ return authHeader;
}
}
diff --git a/http-clients/netty-nio-client/src/test/java/software/amazon/awssdk/http/nio/netty/internal/NegotiateProxyAuthGeneratorTest.java b/http-clients/netty-nio-client/src/test/java/software/amazon/awssdk/http/nio/netty/internal/NegotiateProxyAuthGeneratorTest.java
new file mode 100644
index 000000000000..d7c7a3bc1506
--- /dev/null
+++ b/http-clients/netty-nio-client/src/test/java/software/amazon/awssdk/http/nio/netty/internal/NegotiateProxyAuthGeneratorTest.java
@@ -0,0 +1,110 @@
+/*
+ * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License").
+ * You may not use this file except in compliance with the License.
+ * A copy of the License is located at
+ *
+ * http://aws.amazon.com/apache2.0
+ *
+ * or in the "license" file accompanying this file. This file is distributed
+ * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either
+ * express or implied. See the License for the specific language governing
+ * permissions and limitations under the License.
+ */
+
+package software.amazon.awssdk.http.nio.netty.internal;
+
+import static org.assertj.core.api.Assertions.assertThat;
+
+import java.io.IOException;
+import java.net.InetSocketAddress;
+import java.net.Socket;
+import java.net.URI;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.util.HashMap;
+import java.util.Map;
+import javax.security.auth.login.AppConfigurationEntry;
+import javax.security.auth.login.Configuration;
+import org.apache.kerby.kerberos.kerb.KrbException;
+import org.apache.kerby.kerberos.kerb.client.KrbClient;
+import org.apache.kerby.kerberos.kerb.server.SimpleKdcServer;
+import org.apache.kerby.kerberos.kerb.type.ticket.TgtTicket;
+import org.junit.jupiter.api.AfterAll;
+import org.junit.jupiter.api.BeforeAll;
+import org.junit.jupiter.api.Test;
+import software.amazon.awssdk.testutils.FileUtils;
+
+public class NegotiateProxyAuthGeneratorTest {
+ private static Path tempDir;
+ private static Path keytabFile;
+ private static Path ccacheFile;
+ private static int port;
+
+ private static SimpleKdcServer kdc;
+
+ private static Configuration config;
+
+ @BeforeAll
+ static void setup() throws IOException, KrbException {
+ tempDir = Files.createTempDirectory(null);
+ keytabFile = tempDir.resolve("keytab");
+ ccacheFile = tempDir.resolve("ccache");
+
+ try (Socket freePort = new Socket()) {
+ freePort.setReuseAddress(true);
+ freePort.bind(new InetSocketAddress(0));
+ port = freePort.getLocalPort();
+ }
+
+ kdc = new SimpleKdcServer();
+ kdc.setKdcRealm("EXAMPLE.COM");
+ kdc.setKdcHost("localhost");
+ kdc.setWorkDir(tempDir.toFile());
+ kdc.setKdcTcpPort(port);
+ kdc.init();
+ kdc.start();
+
+ kdc.createPrincipal("alice@EXAMPLE.COM", "alicePassword");
+ kdc.createAndExportPrincipals(keytabFile.toFile(), "HTTP/localhost@EXAMPLE.COM");
+
+ // initialize the ticket cache
+ KrbClient krbClient = kdc.getKrbClient();
+ TgtTicket tgt = krbClient.requestTgt("alice@EXAMPLE.COM", "alicePassword");
+ krbClient.storeTicket(tgt, ccacheFile.toFile());
+
+ // Override config so we look at the testing cache instead of the real system cache
+ config = new Configuration() {
+ @Override
+ public AppConfigurationEntry[] getAppConfigurationEntry(String name) {
+ Map opts = new HashMap<>();
+ opts.put("useTicketCache", "true");
+ opts.put("ticketCache", ccacheFile.toAbsolutePath().toString());
+ opts.put("doNotPrompt", "true");
+ return new AppConfigurationEntry[] {
+ new AppConfigurationEntry(
+ "com.sun.security.auth.module.Krb5LoginModule",
+ AppConfigurationEntry.LoginModuleControlFlag.REQUIRED, opts)
+ };
+ }
+ };
+
+ }
+
+ @AfterAll
+ static void teardown() throws KrbException {
+ kdc.stop();
+ FileUtils.cleanUpTestDirectory(tempDir);
+ }
+
+ @Test
+ void generateAuthParams_configValid_successfullyGeneratesToken() {
+ NegotiateProxyAuthGenerator authGenerator = new NegotiateProxyAuthGenerator(config);
+
+ URI proxyEndpoint = URI.create("https://localhost:8192");
+
+ assertThat(authGenerator.generateAuthParams(proxyEndpoint)).startsWith("YII");
+ }
+
+}
diff --git a/http-clients/netty-nio-client/src/test/java/software/amazon/awssdk/http/nio/netty/internal/ProxyTunnelInitHandlerTest.java b/http-clients/netty-nio-client/src/test/java/software/amazon/awssdk/http/nio/netty/internal/ProxyTunnelInitHandlerTest.java
index 9836a953bda9..7828050bef26 100644
--- a/http-clients/netty-nio-client/src/test/java/software/amazon/awssdk/http/nio/netty/internal/ProxyTunnelInitHandlerTest.java
+++ b/http-clients/netty-nio-client/src/test/java/software/amazon/awssdk/http/nio/netty/internal/ProxyTunnelInitHandlerTest.java
@@ -95,7 +95,8 @@ public void addedToPipeline_addsCodec() {
Supplier codecSupplier = () -> codec;
when(mockCtx.name()).thenReturn("foo");
- ProxyTunnelInitHandler handler = new ProxyTunnelInitHandler(mockChannelPool, null, null, REMOTE_HOST, null, codecSupplier);
+ ProxyTunnelInitHandler handler = new ProxyTunnelInitHandler(mockChannelPool, null, null, null, REMOTE_HOST, null,
+ codecSupplier);
handler.handlerAdded(mockCtx);
verify(mockPipeline).addBefore(eq("foo"), eq(null), eq(codec));
@@ -202,7 +203,7 @@ public void handlerRemoved_removesCodec() {
}
@Test
- public void handledAdded_writesRequest_withoutAuth() {
+ public void handlerAdded_writesRequest_withoutAuth() {
Promise promise = GROUP.next().newPromise();
ProxyTunnelInitHandler handler = new ProxyTunnelInitHandler(mockChannelPool, REMOTE_HOST, promise);
handler.handlerAdded(mockCtx);
@@ -219,7 +220,7 @@ public void handledAdded_writesRequest_withoutAuth() {
}
@Test
- public void handledAdded_writesRequest_withAuth() {
+ public void handlerAdded_writesRequest_withAuth() {
Promise promise = GROUP.next().newPromise();
ProxyTunnelInitHandler handler = new ProxyTunnelInitHandler(mockChannelPool, PROXY_USER, PROXY_PASSWORD, REMOTE_HOST, promise);
handler.handlerAdded(mockCtx);
diff --git a/pom.xml b/pom.xml
index 83c359fa7ff5..cee43145ce64 100644
--- a/pom.xml
+++ b/pom.xml
@@ -152,6 +152,7 @@
1.17.5
1.3.0
1.5.4
+ 2.0.3
3.1.2
From bf5fa3411027010871c785b2b84ff28c8301854d Mon Sep 17 00:00:00 2001
From: Dongie Agnir <261310+dagnir@users.noreply.github.com>
Date: Mon, 3 Aug 2026 13:55:58 -0700
Subject: [PATCH 2/8] Revert "wip" (#7215)
This reverts commit b20454f5ec9fb0a9873fab01c805bf28c8b0224c.
---
.../internal/AwaitCloseChannelPoolMap.java | 13 +----
.../internal/BasicProxyAuthGenerator.java | 43 ---------------
.../internal/Http1TunnelConnectionPool.java | 28 ++++++----
.../internal/NegotiateProxyAuthGenerator.java | 5 +-
.../netty/internal/ProxyAuthGenerator.java | 5 +-
.../internal/ProxyTunnelInitHandler.java | 42 +++++----------
.../Http1TunnelConnectionPoolTest.java | 54 +++++++++++--------
.../NegotiateProxyAuthGeneratorTest.java | 11 ++--
.../internal/ProxyTunnelInitHandlerTest.java | 7 ++-
9 files changed, 78 insertions(+), 130 deletions(-)
delete mode 100644 http-clients/netty-nio-client/src/main/java/software/amazon/awssdk/http/nio/netty/internal/BasicProxyAuthGenerator.java
diff --git a/http-clients/netty-nio-client/src/main/java/software/amazon/awssdk/http/nio/netty/internal/AwaitCloseChannelPoolMap.java b/http-clients/netty-nio-client/src/main/java/software/amazon/awssdk/http/nio/netty/internal/AwaitCloseChannelPoolMap.java
index b9f28b6d3a59..ff5c87e57038 100644
--- a/http-clients/netty-nio-client/src/main/java/software/amazon/awssdk/http/nio/netty/internal/AwaitCloseChannelPoolMap.java
+++ b/http-clients/netty-nio-client/src/main/java/software/amazon/awssdk/http/nio/netty/internal/AwaitCloseChannelPoolMap.java
@@ -44,7 +44,6 @@
import software.amazon.awssdk.http.nio.netty.SdkEventLoopGroup;
import software.amazon.awssdk.http.nio.netty.internal.http2.HttpOrHttp2ChannelPool;
import software.amazon.awssdk.http.nio.netty.internal.utils.NettyClientLogger;
-import software.amazon.awssdk.utils.StringUtils;
/**
* Implementation of {@link SdkChannelPoolMap} that awaits channel pools to be closed upon closing.
@@ -144,7 +143,7 @@ protected SimpleChannelPoolAwareChannelPool newPool(URI key) {
if (shouldUseProxyForHost(key)) {
tcpChannelPool = new BetterSimpleChannelPool(bootstrap, NOOP_HANDLER);
baseChannelPool = new Http1TunnelConnectionPool(bootstrap.config().group().next(), tcpChannelPool, sslContext,
- proxyAddress(key), resolveProxyAuthGenerator(proxyConfiguration),
+ proxyAddress(key), proxyConfiguration.username(), proxyConfiguration.password(),
key, pipelineInitializer, configuration);
} else {
tcpChannelPool = new BetterSimpleChannelPool(bootstrap, pipelineInitializer);
@@ -157,16 +156,6 @@ protected SimpleChannelPoolAwareChannelPool newPool(URI key) {
return new SimpleChannelPoolAwareChannelPool(wrappedPool, tcpChannelPool);
}
- private ProxyAuthGenerator resolveProxyAuthGenerator(ProxyConfiguration proxyConfiguration) {
- String username = proxyConfiguration.username();
- String password = proxyConfiguration.password();
- if (!StringUtils.isBlank(username) && !StringUtils.isBlank(password)) {
- return new BasicProxyAuthGenerator(username, password);
- }
-
- return null;
- }
-
@Override
public void close() {
log.trace(null, () -> "Closing channel pools");
diff --git a/http-clients/netty-nio-client/src/main/java/software/amazon/awssdk/http/nio/netty/internal/BasicProxyAuthGenerator.java b/http-clients/netty-nio-client/src/main/java/software/amazon/awssdk/http/nio/netty/internal/BasicProxyAuthGenerator.java
deleted file mode 100644
index 4d8912994085..000000000000
--- a/http-clients/netty-nio-client/src/main/java/software/amazon/awssdk/http/nio/netty/internal/BasicProxyAuthGenerator.java
+++ /dev/null
@@ -1,43 +0,0 @@
-/*
- * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
- *
- * Licensed under the Apache License, Version 2.0 (the "License").
- * You may not use this file except in compliance with the License.
- * A copy of the License is located at
- *
- * http://aws.amazon.com/apache2.0
- *
- * or in the "license" file accompanying this file. This file is distributed
- * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either
- * express or implied. See the License for the specific language governing
- * permissions and limitations under the License.
- */
-
-package software.amazon.awssdk.http.nio.netty.internal;
-
-import io.netty.handler.codec.http.HttpRequest;
-import io.netty.util.CharsetUtil;
-import java.net.URI;
-import java.util.Base64;
-import software.amazon.awssdk.http.nio.netty.ProxyAuthScheme;
-
-public class BasicProxyAuthGenerator implements ProxyAuthGenerator {
- private final String username;
- private final String password;
-
- public BasicProxyAuthGenerator(String username, String password) {
- this.username = username;
- this.password = password;
- }
-
- @Override
- public ProxyAuthScheme scheme() {
- return ProxyAuthScheme.BASIC;
- }
-
- @Override
- public String generateAuthParams(URI proxyEndpoint) {
- String authToken = String.format("%s:%s", this.username, this.password);
- return Base64.getEncoder().encodeToString(authToken.getBytes(CharsetUtil.UTF_8));
- }
-}
diff --git a/http-clients/netty-nio-client/src/main/java/software/amazon/awssdk/http/nio/netty/internal/Http1TunnelConnectionPool.java b/http-clients/netty-nio-client/src/main/java/software/amazon/awssdk/http/nio/netty/internal/Http1TunnelConnectionPool.java
index 0dafa642d6e4..cc53ed4da46a 100644
--- a/http-clients/netty-nio-client/src/main/java/software/amazon/awssdk/http/nio/netty/internal/Http1TunnelConnectionPool.java
+++ b/http-clients/netty-nio-client/src/main/java/software/amazon/awssdk/http/nio/netty/internal/Http1TunnelConnectionPool.java
@@ -49,30 +49,41 @@ public class Http1TunnelConnectionPool implements ChannelPool {
private final ChannelPool delegate;
private final SslContext sslContext;
private final URI proxyAddress;
- private final ProxyAuthGenerator proxyAuthGenerator;
+ private final String proxyUser;
+ private final String proxyPassword;
private final URI remoteAddress;
private final ChannelPoolHandler handler;
private final InitHandlerSupplier initHandlerSupplier;
private final NettyConfiguration nettyConfiguration;
public Http1TunnelConnectionPool(EventLoop eventLoop, ChannelPool delegate, SslContext sslContext,
- URI proxyAddress, ProxyAuthGenerator proxyAuthGenerator,
+ URI proxyAddress, String proxyUsername, String proxyPassword,
URI remoteAddress, ChannelPoolHandler handler, NettyConfiguration nettyConfiguration) {
this(eventLoop, delegate, sslContext,
- proxyAddress, proxyAuthGenerator, remoteAddress, handler,
+ proxyAddress, proxyUsername, proxyPassword, remoteAddress, handler,
ProxyTunnelInitHandler::new, nettyConfiguration);
}
+ public Http1TunnelConnectionPool(EventLoop eventLoop, ChannelPool delegate, SslContext sslContext,
+ URI proxyAddress, URI remoteAddress, ChannelPoolHandler handler,
+ NettyConfiguration nettyConfiguration) {
+ this(eventLoop, delegate, sslContext,
+ proxyAddress, null, null, remoteAddress, handler,
+ ProxyTunnelInitHandler::new, nettyConfiguration);
+
+ }
+
@SdkTestInternalApi
Http1TunnelConnectionPool(EventLoop eventLoop, ChannelPool delegate, SslContext sslContext,
- URI proxyAddress, ProxyAuthGenerator proxyAuthGenerator, URI remoteAddress,
+ URI proxyAddress, String proxyUser, String proxyPassword, URI remoteAddress,
ChannelPoolHandler handler, InitHandlerSupplier initHandlerSupplier,
NettyConfiguration nettyConfiguration) {
this.eventLoop = eventLoop;
this.delegate = delegate;
this.sslContext = sslContext;
this.proxyAddress = proxyAddress;
- this.proxyAuthGenerator = proxyAuthGenerator;
+ this.proxyUser = proxyUser;
+ this.proxyPassword = proxyPassword;
this.remoteAddress = remoteAddress;
this.handler = handler;
this.initHandlerSupplier = initHandlerSupplier;
@@ -127,7 +138,7 @@ private void setupChannel(Channel ch, Promise acquirePromise) {
if (sslHandler != null) {
ch.pipeline().addLast(sslHandler);
}
- ch.pipeline().addLast(initHandlerSupplier.newInitHandler(delegate, proxyAddress, proxyAuthGenerator, remoteAddress,
+ ch.pipeline().addLast(initHandlerSupplier.newInitHandler(delegate, proxyUser, proxyPassword, remoteAddress,
tunnelEstablishedPromise));
tunnelEstablishedPromise.addListener((Future f) -> {
if (f.isSuccess()) {
@@ -169,10 +180,7 @@ private static boolean isTunnelEstablished(Channel ch) {
@SdkTestInternalApi
@FunctionalInterface
interface InitHandlerSupplier {
- ChannelHandler newInitHandler(ChannelPool sourcePool,
- URI proxyAddress,
- ProxyAuthGenerator authGenerator,
- URI remoteAddress,
+ ChannelHandler newInitHandler(ChannelPool sourcePool, String proxyUsername, String proxyPassword, URI remoteAddress,
Promise tunnelInitFuture);
}
}
diff --git a/http-clients/netty-nio-client/src/main/java/software/amazon/awssdk/http/nio/netty/internal/NegotiateProxyAuthGenerator.java b/http-clients/netty-nio-client/src/main/java/software/amazon/awssdk/http/nio/netty/internal/NegotiateProxyAuthGenerator.java
index 95d6156aa96f..c314d5c32ff3 100644
--- a/http-clients/netty-nio-client/src/main/java/software/amazon/awssdk/http/nio/netty/internal/NegotiateProxyAuthGenerator.java
+++ b/http-clients/netty-nio-client/src/main/java/software/amazon/awssdk/http/nio/netty/internal/NegotiateProxyAuthGenerator.java
@@ -16,7 +16,6 @@
package software.amazon.awssdk.http.nio.netty.internal;
import com.sun.security.auth.module.Krb5LoginModule;
-import io.netty.handler.codec.http.HttpRequest;
import java.net.URI;
import java.security.PrivilegedActionException;
import java.security.PrivilegedExceptionAction;
@@ -65,12 +64,12 @@ public ProxyAuthScheme scheme() {
}
@Override
- public String generateAuthParams(URI proxyEndpoint) {
+ public String generateAuthParams(SdkHttpRequest request) {
try {
Subject subject = getSubject();
byte[] token = Subject.doAs(subject, (PrivilegedExceptionAction) () -> {
- GSSContext ctx = createGSSContext(getManager(), proxyEndpoint);
+ GSSContext ctx = createGSSContext(getManager(), request.getUri());
ctx.requestMutualAuth(true);
return ctx.initSecContext(new byte[0], 0, 0);
});
diff --git a/http-clients/netty-nio-client/src/main/java/software/amazon/awssdk/http/nio/netty/internal/ProxyAuthGenerator.java b/http-clients/netty-nio-client/src/main/java/software/amazon/awssdk/http/nio/netty/internal/ProxyAuthGenerator.java
index b078500b09ac..3e0b2569f364 100644
--- a/http-clients/netty-nio-client/src/main/java/software/amazon/awssdk/http/nio/netty/internal/ProxyAuthGenerator.java
+++ b/http-clients/netty-nio-client/src/main/java/software/amazon/awssdk/http/nio/netty/internal/ProxyAuthGenerator.java
@@ -15,9 +15,8 @@
package software.amazon.awssdk.http.nio.netty.internal;
-import io.netty.handler.codec.http.HttpRequest;
-import java.net.URI;
import software.amazon.awssdk.annotations.SdkInternalApi;
+import software.amazon.awssdk.http.SdkHttpRequest;
import software.amazon.awssdk.http.nio.netty.ProxyAuthScheme;
/**
@@ -33,5 +32,5 @@ public interface ProxyAuthGenerator {
/**
* Generate the auth params for this request.
*/
- String generateAuthParams(URI proxyEndpoint);
+ String generateAuthParams(SdkHttpRequest request);
}
diff --git a/http-clients/netty-nio-client/src/main/java/software/amazon/awssdk/http/nio/netty/internal/ProxyTunnelInitHandler.java b/http-clients/netty-nio-client/src/main/java/software/amazon/awssdk/http/nio/netty/internal/ProxyTunnelInitHandler.java
index 277ed555dbc5..e6f309afbad3 100644
--- a/http-clients/netty-nio-client/src/main/java/software/amazon/awssdk/http/nio/netty/internal/ProxyTunnelInitHandler.java
+++ b/http-clients/netty-nio-client/src/main/java/software/amazon/awssdk/http/nio/netty/internal/ProxyTunnelInitHandler.java
@@ -28,9 +28,11 @@
import io.netty.handler.codec.http.HttpRequest;
import io.netty.handler.codec.http.HttpResponse;
import io.netty.handler.codec.http.HttpVersion;
+import io.netty.util.CharsetUtil;
import io.netty.util.concurrent.Promise;
import java.io.IOException;
import java.net.URI;
+import java.util.Base64;
import java.util.function.Supplier;
import software.amazon.awssdk.annotations.SdkInternalApi;
import software.amazon.awssdk.annotations.SdkTestInternalApi;
@@ -45,51 +47,32 @@ public final class ProxyTunnelInitHandler extends ChannelDuplexHandler {
public static final NettyClientLogger log = NettyClientLogger.getLogger(ProxyTunnelInitHandler.class);
private final ChannelPool sourcePool;
- private final URI proxyAddress;
- private final ProxyAuthGenerator authGenerator;
+ private final String username;
+ private final String password;
private final URI remoteHost;
private final Promise initPromise;
private final Supplier httpCodecSupplier;
public ProxyTunnelInitHandler(ChannelPool sourcePool, String proxyUsername, String proxyPassword, URI remoteHost,
Promise initPromise) {
- this(sourcePool, null, proxyUsername, proxyPassword, remoteHost, initPromise, HttpClientCodec::new);
+ this(sourcePool, proxyUsername, proxyPassword, remoteHost, initPromise, HttpClientCodec::new);
}
public ProxyTunnelInitHandler(ChannelPool sourcePool, URI remoteHost, Promise initPromise) {
- this(sourcePool, null, null, null, remoteHost, initPromise, HttpClientCodec::new);
+ this(sourcePool, null, null, remoteHost, initPromise, HttpClientCodec::new);
}
@SdkTestInternalApi
- public ProxyTunnelInitHandler(ChannelPool sourcePool, URI proxyAddress, String proxyUsername, String proxyPassword,
+ public ProxyTunnelInitHandler(ChannelPool sourcePool, String prosyUsername, String proxyPassword,
URI remoteHost, Promise initPromise, Supplier httpCodecSupplier) {
this.sourcePool = sourcePool;
- this.proxyAddress = proxyAddress;
this.remoteHost = remoteHost;
this.initPromise = initPromise;
- if (!StringUtils.isBlank(proxyPassword) && !StringUtils.isBlank(proxyPassword)) {
- this.authGenerator = new BasicProxyAuthGenerator(proxyUsername, proxyPassword);
- } else {
- this.authGenerator = null;
- }
+ this.username = prosyUsername;
+ this.password = proxyPassword;
this.httpCodecSupplier = httpCodecSupplier;
}
- public ProxyTunnelInitHandler(ChannelPool sourcePool, URI proxyAddress, ProxyAuthGenerator authGenerator,
- URI remoteHost, Promise initPromise, Supplier httpCodecSupplier) {
- this.sourcePool = sourcePool;
- this.proxyAddress = proxyAddress;
- this.remoteHost = remoteHost;
- this.initPromise = initPromise;
- this.authGenerator = authGenerator;
- this.httpCodecSupplier = httpCodecSupplier;
- }
-
- public ProxyTunnelInitHandler(ChannelPool sourcePool, URI proxyAddress, ProxyAuthGenerator authGenerator,
- URI remoteHost, Promise initPromise) {
- this(sourcePool, proxyAddress, authGenerator, remoteHost, initPromise, HttpClientCodec::new);
- }
-
@Override
public void handlerAdded(ChannelHandlerContext ctx) {
ChannelPipeline pipeline = ctx.pipeline();
@@ -168,9 +151,10 @@ private HttpRequest connectRequest() {
Unpooled.EMPTY_BUFFER);
request.headers().add(HttpHeaderNames.HOST, uri);
- if (authGenerator != null) {
- String auth = String.format("%s %s", authGenerator.scheme().value(), authGenerator.generateAuthParams(proxyAddress));
- request.headers().add(HttpHeaderNames.PROXY_AUTHORIZATION, auth);
+ if (!StringUtils.isEmpty(this.username) && !StringUtils.isEmpty(this.password)) {
+ String authToken = String.format("%s:%s", this.username, this.password);
+ String authB64 = Base64.getEncoder().encodeToString(authToken.getBytes(CharsetUtil.UTF_8));
+ request.headers().add(HttpHeaderNames.PROXY_AUTHORIZATION, String.format("Basic %s", authB64));
}
return request;
diff --git a/http-clients/netty-nio-client/src/test/java/software/amazon/awssdk/http/nio/netty/internal/Http1TunnelConnectionPoolTest.java b/http-clients/netty-nio-client/src/test/java/software/amazon/awssdk/http/nio/netty/internal/Http1TunnelConnectionPoolTest.java
index 9e2ed53cd1e3..d43b404f3f5f 100644
--- a/http-clients/netty-nio-client/src/test/java/software/amazon/awssdk/http/nio/netty/internal/Http1TunnelConnectionPoolTest.java
+++ b/http-clients/netty-nio-client/src/test/java/software/amazon/awssdk/http/nio/netty/internal/Http1TunnelConnectionPoolTest.java
@@ -73,8 +73,6 @@ public class Http1TunnelConnectionPoolTest {
private static final String PROXY_PASSWORD = "mypassword";
- private static final ProxyAuthGenerator basicAuth = new BasicProxyAuthGenerator(PROXY_USER, PROXY_PASSWORD);
-
@Mock
private ChannelPool delegatePool;
@@ -117,7 +115,7 @@ public static void teardown() {
@Test
public void tunnelAlreadyEstablished_doesNotAddInitHandler() {
Http1TunnelConnectionPool tunnelPool = new Http1TunnelConnectionPool(GROUP.next(), delegatePool, null,
- HTTP_PROXY_ADDRESS, null, REMOTE_ADDRESS, mockHandler, configuration);
+ HTTP_PROXY_ADDRESS, REMOTE_ADDRESS, mockHandler, configuration);
when(mockAttr.get()).thenReturn(true);
@@ -129,7 +127,7 @@ public void tunnelAlreadyEstablished_doesNotAddInitHandler() {
@Test(timeout = 1000)
public void tunnelNotEstablished_addsInitHandler() throws InterruptedException {
Http1TunnelConnectionPool tunnelPool = new Http1TunnelConnectionPool(GROUP.next(), delegatePool, null,
- HTTP_PROXY_ADDRESS, null, REMOTE_ADDRESS, mockHandler, configuration);
+ HTTP_PROXY_ADDRESS, REMOTE_ADDRESS, mockHandler, configuration);
when(mockAttr.get()).thenReturn(false);
@@ -151,7 +149,7 @@ public void tunnelInitFails_acquireFutureFails() {
};
Http1TunnelConnectionPool tunnelPool = new Http1TunnelConnectionPool(GROUP.next(), delegatePool, null,
- HTTP_PROXY_ADDRESS,null, REMOTE_ADDRESS, mockHandler, supplier, configuration);
+ HTTP_PROXY_ADDRESS,null, null, REMOTE_ADDRESS, mockHandler, supplier, configuration);
Future acquireFuture = tunnelPool.acquire();
@@ -166,7 +164,7 @@ public void tunnelInitSucceeds_acquireFutureSucceeds() {
};
Http1TunnelConnectionPool tunnelPool = new Http1TunnelConnectionPool(GROUP.next(), delegatePool, null,
- HTTP_PROXY_ADDRESS, null, REMOTE_ADDRESS, mockHandler, supplier, configuration);
+ HTTP_PROXY_ADDRESS, null, null, REMOTE_ADDRESS, mockHandler, supplier, configuration);
Future acquireFuture = tunnelPool.acquire();
@@ -176,7 +174,7 @@ public void tunnelInitSucceeds_acquireFutureSucceeds() {
@Test
public void acquireFromDelegatePoolFails_failsFuture() {
Http1TunnelConnectionPool tunnelPool = new Http1TunnelConnectionPool(GROUP.next(), delegatePool, null,
- HTTP_PROXY_ADDRESS, null, REMOTE_ADDRESS, mockHandler, configuration);
+ HTTP_PROXY_ADDRESS, REMOTE_ADDRESS, mockHandler, configuration);
when(delegatePool.acquire(any(Promise.class))).thenReturn(GROUP.next().newFailedFuture(new IOException("boom")));
@@ -199,7 +197,7 @@ public void sslContextProvided_andProxyUsingHttps_addsSslHandler() {
};
Http1TunnelConnectionPool tunnelPool = new Http1TunnelConnectionPool(GROUP.next(), delegatePool, mockSslCtx,
- HTTPS_PROXY_ADDRESS, null, REMOTE_ADDRESS, mockHandler, supplier, configuration);
+ HTTPS_PROXY_ADDRESS, null, null, REMOTE_ADDRESS, mockHandler, supplier, configuration);
tunnelPool.acquire().awaitUninterruptibly();
@@ -220,7 +218,7 @@ public void sslContextProvided_andProxyNotUsingHttps_doesNotAddSslHandler() {
};
Http1TunnelConnectionPool tunnelPool = new Http1TunnelConnectionPool(GROUP.next(), delegatePool, mockSslCtx,
- HTTP_PROXY_ADDRESS, null, REMOTE_ADDRESS, mockHandler, supplier, configuration);
+ HTTP_PROXY_ADDRESS, null, null, REMOTE_ADDRESS, mockHandler, supplier, configuration);
tunnelPool.acquire().awaitUninterruptibly();
@@ -233,7 +231,7 @@ public void sslContextProvided_andProxyNotUsingHttps_doesNotAddSslHandler() {
@Test
public void release_releasedToDelegatePool() {
Http1TunnelConnectionPool tunnelPool = new Http1TunnelConnectionPool(GROUP.next(), delegatePool, null,
- HTTP_PROXY_ADDRESS,null, REMOTE_ADDRESS, mockHandler, configuration);
+ HTTP_PROXY_ADDRESS, REMOTE_ADDRESS, mockHandler, configuration);
tunnelPool.release(mockChannel);
verify(delegatePool).release(eq(mockChannel), any(Promise.class));
}
@@ -241,7 +239,7 @@ public void release_releasedToDelegatePool() {
@Test
public void release_withGivenPromise_releasedToDelegatePool() {
Http1TunnelConnectionPool tunnelPool = new Http1TunnelConnectionPool(GROUP.next(), delegatePool, null,
- HTTP_PROXY_ADDRESS, null, REMOTE_ADDRESS, mockHandler, configuration);
+ HTTP_PROXY_ADDRESS, REMOTE_ADDRESS, mockHandler, configuration);
Promise mockPromise = mock(Promise.class);
tunnelPool.release(mockChannel, mockPromise);
verify(delegatePool).release(eq(mockChannel), eq(mockPromise));
@@ -250,7 +248,7 @@ public void release_withGivenPromise_releasedToDelegatePool() {
@Test
public void close_closesDelegatePool() {
Http1TunnelConnectionPool tunnelPool = new Http1TunnelConnectionPool(GROUP.next(), delegatePool, null,
- HTTP_PROXY_ADDRESS, null, REMOTE_ADDRESS, mockHandler, configuration);
+ HTTP_PROXY_ADDRESS, REMOTE_ADDRESS, mockHandler, configuration);
tunnelPool.close();
verify(delegatePool).close();
}
@@ -259,32 +257,42 @@ public void close_closesDelegatePool() {
public void proxyAuthProvided_addInitHandler_withAuth(){
TestInitHandlerData data = new TestInitHandlerData();
- Http1TunnelConnectionPool.InitHandlerSupplier supplier =
- (srcPool, proxyEndpoint, proxyAuthGenerator, remoteAddr, initFuture) -> {
+ Http1TunnelConnectionPool.InitHandlerSupplier supplier = (srcPool, proxyUser, proxyPassword, remoteAddr, initFuture) -> {
initFuture.setSuccess(mockChannel);
- data.authHeader = proxyAuthGenerator.generateAuthParams(proxyEndpoint);
+ data.proxyUser(proxyUser);
+ data.proxyPassword(proxyPassword);
return mock(ChannelHandler.class);
};
Http1TunnelConnectionPool tunnelPool = new Http1TunnelConnectionPool(GROUP.next(), delegatePool, null,
- HTTP_PROXY_ADDRESS, basicAuth, REMOTE_ADDRESS, mockHandler, supplier, configuration);
+ HTTP_PROXY_ADDRESS, PROXY_USER, PROXY_PASSWORD, REMOTE_ADDRESS, mockHandler, supplier, configuration);
tunnelPool.acquire().awaitUninterruptibly();
- // assertThat(data.proxyUser()).isEqualTo(PROXY_USER);
- // assertThat(data.proxyPassword()).isEqualTo(PROXY_PASSWORD);
+ assertThat(data.proxyUser()).isEqualTo(PROXY_USER);
+ assertThat(data.proxyPassword()).isEqualTo(PROXY_PASSWORD);
+
}
private static class TestInitHandlerData {
- private String authHeader;
+ private String proxyUser;
+ private String proxyPassword;
+
+ public void proxyUser(String proxyUser) {
+ this.proxyUser = proxyUser;
+ }
+
+ public String proxyUser() {
+ return this.proxyUser;
+ }
- public void authHeader(String authHeader) {
- this.authHeader = authHeader;
+ public void proxyPassword(String proxyPassword) {
+ this.proxyPassword = proxyPassword;
}
- public String authHeader() {
- return authHeader;
+ public String proxyPassword(){
+ return this.proxyPassword;
}
}
diff --git a/http-clients/netty-nio-client/src/test/java/software/amazon/awssdk/http/nio/netty/internal/NegotiateProxyAuthGeneratorTest.java b/http-clients/netty-nio-client/src/test/java/software/amazon/awssdk/http/nio/netty/internal/NegotiateProxyAuthGeneratorTest.java
index d7c7a3bc1506..839aff7c4be5 100644
--- a/http-clients/netty-nio-client/src/test/java/software/amazon/awssdk/http/nio/netty/internal/NegotiateProxyAuthGeneratorTest.java
+++ b/http-clients/netty-nio-client/src/test/java/software/amazon/awssdk/http/nio/netty/internal/NegotiateProxyAuthGeneratorTest.java
@@ -20,7 +20,6 @@
import java.io.IOException;
import java.net.InetSocketAddress;
import java.net.Socket;
-import java.net.URI;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.HashMap;
@@ -34,6 +33,8 @@
import org.junit.jupiter.api.AfterAll;
import org.junit.jupiter.api.BeforeAll;
import org.junit.jupiter.api.Test;
+import software.amazon.awssdk.http.SdkHttpMethod;
+import software.amazon.awssdk.http.SdkHttpRequest;
import software.amazon.awssdk.testutils.FileUtils;
public class NegotiateProxyAuthGeneratorTest {
@@ -102,9 +103,13 @@ static void teardown() throws KrbException {
void generateAuthParams_configValid_successfullyGeneratesToken() {
NegotiateProxyAuthGenerator authGenerator = new NegotiateProxyAuthGenerator(config);
- URI proxyEndpoint = URI.create("https://localhost:8192");
+ SdkHttpRequest request = SdkHttpRequest.builder()
+ .protocol("http")
+ .host("localhost")
+ .method(SdkHttpMethod.GET)
+ .build();
- assertThat(authGenerator.generateAuthParams(proxyEndpoint)).startsWith("YII");
+ assertThat(authGenerator.generateAuthParams(request)).startsWith("YII");
}
}
diff --git a/http-clients/netty-nio-client/src/test/java/software/amazon/awssdk/http/nio/netty/internal/ProxyTunnelInitHandlerTest.java b/http-clients/netty-nio-client/src/test/java/software/amazon/awssdk/http/nio/netty/internal/ProxyTunnelInitHandlerTest.java
index 7828050bef26..9836a953bda9 100644
--- a/http-clients/netty-nio-client/src/test/java/software/amazon/awssdk/http/nio/netty/internal/ProxyTunnelInitHandlerTest.java
+++ b/http-clients/netty-nio-client/src/test/java/software/amazon/awssdk/http/nio/netty/internal/ProxyTunnelInitHandlerTest.java
@@ -95,8 +95,7 @@ public void addedToPipeline_addsCodec() {
Supplier codecSupplier = () -> codec;
when(mockCtx.name()).thenReturn("foo");
- ProxyTunnelInitHandler handler = new ProxyTunnelInitHandler(mockChannelPool, null, null, null, REMOTE_HOST, null,
- codecSupplier);
+ ProxyTunnelInitHandler handler = new ProxyTunnelInitHandler(mockChannelPool, null, null, REMOTE_HOST, null, codecSupplier);
handler.handlerAdded(mockCtx);
verify(mockPipeline).addBefore(eq("foo"), eq(null), eq(codec));
@@ -203,7 +202,7 @@ public void handlerRemoved_removesCodec() {
}
@Test
- public void handlerAdded_writesRequest_withoutAuth() {
+ public void handledAdded_writesRequest_withoutAuth() {
Promise promise = GROUP.next().newPromise();
ProxyTunnelInitHandler handler = new ProxyTunnelInitHandler(mockChannelPool, REMOTE_HOST, promise);
handler.handlerAdded(mockCtx);
@@ -220,7 +219,7 @@ public void handlerAdded_writesRequest_withoutAuth() {
}
@Test
- public void handlerAdded_writesRequest_withAuth() {
+ public void handledAdded_writesRequest_withAuth() {
Promise promise = GROUP.next().newPromise();
ProxyTunnelInitHandler handler = new ProxyTunnelInitHandler(mockChannelPool, PROXY_USER, PROXY_PASSWORD, REMOTE_HOST, promise);
handler.handlerAdded(mockCtx);
From 81f440d127b913e43ff3277dfbc152e7b74099c4 Mon Sep 17 00:00:00 2001
From: Dongie Agnir <261310+dagnir@users.noreply.github.com>
Date: Tue, 4 Aug 2026 10:29:07 -0700
Subject: [PATCH 3/8] Add basic auth impl (#7220)
* Add basic auth impl
* Checkstyle and dependency issues
---
bom-internal/pom.xml | 14 +++-
http-clients/netty-nio-client/pom.xml | 10 +++
.../internal/BasicProxyAuthGenerator.java | 50 +++++++++++++
.../internal/BasicProxyAuthGeneratorTest.java | 72 +++++++++++++++++++
pom.xml | 2 +-
5 files changed, 146 insertions(+), 2 deletions(-)
create mode 100644 http-clients/netty-nio-client/src/main/java/software/amazon/awssdk/http/nio/netty/internal/BasicProxyAuthGenerator.java
create mode 100644 http-clients/netty-nio-client/src/test/java/software/amazon/awssdk/http/nio/netty/internal/BasicProxyAuthGeneratorTest.java
diff --git a/bom-internal/pom.xml b/bom-internal/pom.xml
index 78c85e86a17e..de6705135331 100644
--- a/bom-internal/pom.xml
+++ b/bom-internal/pom.xml
@@ -522,7 +522,19 @@
org.apache.kerby
kerb-simplekdc
- ${kerb-simplekdc.version}
+ ${kerby.version}
+ test
+
+
+ org.apache.kerby
+ kerb-client
+ ${kerby.version}
+ test
+
+
+ org.apache.kerby
+ kerb-core
+ ${kerby.version}
test
diff --git a/http-clients/netty-nio-client/pom.xml b/http-clients/netty-nio-client/pom.xml
index 9988be3c5b18..eba24127eaa6 100644
--- a/http-clients/netty-nio-client/pom.xml
+++ b/http-clients/netty-nio-client/pom.xml
@@ -238,6 +238,16 @@
kerb-simplekdc
test
+
+ org.apache.kerby
+ kerb-client
+ test
+
+
+ org.apache.kerby
+ kerb-core
+ test
+
diff --git a/http-clients/netty-nio-client/src/main/java/software/amazon/awssdk/http/nio/netty/internal/BasicProxyAuthGenerator.java b/http-clients/netty-nio-client/src/main/java/software/amazon/awssdk/http/nio/netty/internal/BasicProxyAuthGenerator.java
new file mode 100644
index 000000000000..4efc98848497
--- /dev/null
+++ b/http-clients/netty-nio-client/src/main/java/software/amazon/awssdk/http/nio/netty/internal/BasicProxyAuthGenerator.java
@@ -0,0 +1,50 @@
+/*
+ * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License").
+ * You may not use this file except in compliance with the License.
+ * A copy of the License is located at
+ *
+ * http://aws.amazon.com/apache2.0
+ *
+ * or in the "license" file accompanying this file. This file is distributed
+ * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either
+ * express or implied. See the License for the specific language governing
+ * permissions and limitations under the License.
+ */
+
+package software.amazon.awssdk.http.nio.netty.internal;
+
+import io.netty.util.CharsetUtil;
+import java.util.Base64;
+import software.amazon.awssdk.annotations.SdkInternalApi;
+import software.amazon.awssdk.http.SdkHttpRequest;
+import software.amazon.awssdk.http.nio.netty.ProxyAuthScheme;
+import software.amazon.awssdk.utils.Validate;
+
+/**
+ * Auth param generator for Basic proxy authentication.
+ *
+ * See https://datatracker.ietf.org/doc/html/rfc7617.
+ */
+@SdkInternalApi
+public class BasicProxyAuthGenerator implements ProxyAuthGenerator {
+ private final String username;
+ private final String password;
+
+ public BasicProxyAuthGenerator(String username, String password) {
+ this.username = Validate.notBlank(username, "username must not be blank");
+ this.password = Validate.notBlank(password, "password must not be blank");
+ }
+
+ @Override
+ public ProxyAuthScheme scheme() {
+ return ProxyAuthScheme.BASIC;
+ }
+
+ @Override
+ public String generateAuthParams(SdkHttpRequest request) {
+ String authToken = String.format("%s:%s", this.username, this.password);
+ return Base64.getEncoder().encodeToString(authToken.getBytes(CharsetUtil.UTF_8));
+ }
+}
diff --git a/http-clients/netty-nio-client/src/test/java/software/amazon/awssdk/http/nio/netty/internal/BasicProxyAuthGeneratorTest.java b/http-clients/netty-nio-client/src/test/java/software/amazon/awssdk/http/nio/netty/internal/BasicProxyAuthGeneratorTest.java
new file mode 100644
index 000000000000..ba7d4bf5d4f1
--- /dev/null
+++ b/http-clients/netty-nio-client/src/test/java/software/amazon/awssdk/http/nio/netty/internal/BasicProxyAuthGeneratorTest.java
@@ -0,0 +1,72 @@
+/*
+ * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License").
+ * You may not use this file except in compliance with the License.
+ * A copy of the License is located at
+ *
+ * http://aws.amazon.com/apache2.0
+ *
+ * or in the "license" file accompanying this file. This file is distributed
+ * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either
+ * express or implied. See the License for the specific language governing
+ * permissions and limitations under the License.
+ */
+
+package software.amazon.awssdk.http.nio.netty.internal;
+
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.assertj.core.api.Assertions.assertThatThrownBy;
+import static org.mockito.Mockito.mock;
+
+import java.nio.charset.StandardCharsets;
+import java.util.Base64;
+import java.util.stream.Stream;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.params.ParameterizedTest;
+import org.junit.jupiter.params.provider.Arguments;
+import org.junit.jupiter.params.provider.MethodSource;
+import software.amazon.awssdk.http.SdkHttpRequest;
+import software.amazon.awssdk.http.nio.netty.ProxyAuthScheme;
+
+public class BasicProxyAuthGeneratorTest {
+ private static final String USERNAME = "user";
+ private static final String PASSWORD = "pass";
+
+ private final BasicProxyAuthGenerator authGenerator = new BasicProxyAuthGenerator(USERNAME, PASSWORD);
+
+ @ParameterizedTest(name = "username = {0}, password = {1}, expected error = {2}")
+ @MethodSource("invalidCtorParams")
+ void ctor_paramsInvalid_throws(String username, String password, String errorMessage) {
+ assertThatThrownBy(() -> new BasicProxyAuthGenerator(username, password))
+ .hasMessageContaining(errorMessage);
+ }
+
+ @Test
+ void scheme_returnsCorrectValue() {
+ assertThat(authGenerator.scheme()).isEqualTo(ProxyAuthScheme.BASIC);
+ }
+
+ @Test
+ void generateAuthParams_generatedCorrectly() {
+ String expected = Base64.getEncoder()
+ .encodeToString(String.format("%s:%s", USERNAME, PASSWORD)
+ .getBytes(StandardCharsets.UTF_8));
+
+ assertThat(authGenerator.generateAuthParams(mock(SdkHttpRequest.class))).isEqualTo(expected);
+ }
+
+ private static Stream invalidCtorParams() {
+ return Stream.of(
+ Arguments.of(null, null, "username"),
+ Arguments.of("", "", "username"),
+ Arguments.of(" ", "", "username"),
+ Arguments.of(" ", " ", "username"),
+ Arguments.of(null, PASSWORD, "username"),
+ Arguments.of("", PASSWORD, "username"),
+ Arguments.of(USERNAME, null, "password"),
+ Arguments.of(USERNAME, "", "password")
+
+ );
+ }
+}
diff --git a/pom.xml b/pom.xml
index cee43145ce64..a6746e66b0cf 100644
--- a/pom.xml
+++ b/pom.xml
@@ -152,7 +152,7 @@
1.17.5
1.3.0
1.5.4
- 2.0.3
+ 2.0.3
3.1.2
From b2aaf34f5aa8d6872807241a567bb1321c34d334 Mon Sep 17 00:00:00 2001
From: Dongie Agnir <261310+dagnir@users.noreply.github.com>
Date: Tue, 11 Aug 2026 11:29:13 -0700
Subject: [PATCH 4/8] Switch to AuthGenerator in tunnel pool (#7252)
* Switch to AuthGenerator in tunnel pool
Use the new AuthGenerator mechanism in the `Http1TunnelConnectionPool`
and `AwaitCloseChannelPoolMap` classes. For now, supports only using
BASIC auth; Kerberos will be added in a subsequent PR.
* Allow empty username, pass
Original impl allowed empty (e.g. whitespace) in username and pass for
BASIC auth so preserve that behavior.
---
.../internal/AwaitCloseChannelPoolMap.java | 13 ++++-
.../internal/BasicProxyAuthGenerator.java | 8 +--
.../internal/Http1TunnelConnectionPool.java | 28 ++++------
.../internal/NegotiateProxyAuthGenerator.java | 7 ++-
.../netty/internal/ProxyAuthGenerator.java | 4 +-
.../internal/ProxyTunnelInitHandler.java | 42 ++++++++++-----
.../internal/BasicProxyAuthGeneratorTest.java | 7 +--
.../Http1TunnelConnectionPoolTest.java | 54 ++++++++-----------
.../NegotiateProxyAuthGeneratorTest.java | 11 ++--
.../internal/ProxyTunnelInitHandlerTest.java | 7 +--
10 files changed, 92 insertions(+), 89 deletions(-)
diff --git a/http-clients/netty-nio-client/src/main/java/software/amazon/awssdk/http/nio/netty/internal/AwaitCloseChannelPoolMap.java b/http-clients/netty-nio-client/src/main/java/software/amazon/awssdk/http/nio/netty/internal/AwaitCloseChannelPoolMap.java
index ff5c87e57038..83665f08b927 100644
--- a/http-clients/netty-nio-client/src/main/java/software/amazon/awssdk/http/nio/netty/internal/AwaitCloseChannelPoolMap.java
+++ b/http-clients/netty-nio-client/src/main/java/software/amazon/awssdk/http/nio/netty/internal/AwaitCloseChannelPoolMap.java
@@ -44,6 +44,7 @@
import software.amazon.awssdk.http.nio.netty.SdkEventLoopGroup;
import software.amazon.awssdk.http.nio.netty.internal.http2.HttpOrHttp2ChannelPool;
import software.amazon.awssdk.http.nio.netty.internal.utils.NettyClientLogger;
+import software.amazon.awssdk.utils.StringUtils;
/**
* Implementation of {@link SdkChannelPoolMap} that awaits channel pools to be closed upon closing.
@@ -143,7 +144,7 @@ protected SimpleChannelPoolAwareChannelPool newPool(URI key) {
if (shouldUseProxyForHost(key)) {
tcpChannelPool = new BetterSimpleChannelPool(bootstrap, NOOP_HANDLER);
baseChannelPool = new Http1TunnelConnectionPool(bootstrap.config().group().next(), tcpChannelPool, sslContext,
- proxyAddress(key), proxyConfiguration.username(), proxyConfiguration.password(),
+ proxyAddress(key), resolveProxyAuthGenerator(proxyConfiguration),
key, pipelineInitializer, configuration);
} else {
tcpChannelPool = new BetterSimpleChannelPool(bootstrap, pipelineInitializer);
@@ -156,6 +157,16 @@ protected SimpleChannelPoolAwareChannelPool newPool(URI key) {
return new SimpleChannelPoolAwareChannelPool(wrappedPool, tcpChannelPool);
}
+ private ProxyAuthGenerator resolveProxyAuthGenerator(ProxyConfiguration proxyConfiguration) {
+ String username = proxyConfiguration.username();
+ String password = proxyConfiguration.password();
+ if (!StringUtils.isEmpty(username) && !StringUtils.isEmpty(password)) {
+ return new BasicProxyAuthGenerator(username, password);
+ }
+
+ return null;
+ }
+
@Override
public void close() {
log.trace(null, () -> "Closing channel pools");
diff --git a/http-clients/netty-nio-client/src/main/java/software/amazon/awssdk/http/nio/netty/internal/BasicProxyAuthGenerator.java b/http-clients/netty-nio-client/src/main/java/software/amazon/awssdk/http/nio/netty/internal/BasicProxyAuthGenerator.java
index 4efc98848497..36055cf0b0fe 100644
--- a/http-clients/netty-nio-client/src/main/java/software/amazon/awssdk/http/nio/netty/internal/BasicProxyAuthGenerator.java
+++ b/http-clients/netty-nio-client/src/main/java/software/amazon/awssdk/http/nio/netty/internal/BasicProxyAuthGenerator.java
@@ -16,9 +16,9 @@
package software.amazon.awssdk.http.nio.netty.internal;
import io.netty.util.CharsetUtil;
+import java.net.URI;
import java.util.Base64;
import software.amazon.awssdk.annotations.SdkInternalApi;
-import software.amazon.awssdk.http.SdkHttpRequest;
import software.amazon.awssdk.http.nio.netty.ProxyAuthScheme;
import software.amazon.awssdk.utils.Validate;
@@ -33,8 +33,8 @@ public class BasicProxyAuthGenerator implements ProxyAuthGenerator {
private final String password;
public BasicProxyAuthGenerator(String username, String password) {
- this.username = Validate.notBlank(username, "username must not be blank");
- this.password = Validate.notBlank(password, "password must not be blank");
+ this.username = Validate.notEmpty(username, "username must not be empty");
+ this.password = Validate.notEmpty(password, "password must not be empty");
}
@Override
@@ -43,7 +43,7 @@ public ProxyAuthScheme scheme() {
}
@Override
- public String generateAuthParams(SdkHttpRequest request) {
+ public String generateAuthParams(URI proxyEndpoint) {
String authToken = String.format("%s:%s", this.username, this.password);
return Base64.getEncoder().encodeToString(authToken.getBytes(CharsetUtil.UTF_8));
}
diff --git a/http-clients/netty-nio-client/src/main/java/software/amazon/awssdk/http/nio/netty/internal/Http1TunnelConnectionPool.java b/http-clients/netty-nio-client/src/main/java/software/amazon/awssdk/http/nio/netty/internal/Http1TunnelConnectionPool.java
index cc53ed4da46a..0dafa642d6e4 100644
--- a/http-clients/netty-nio-client/src/main/java/software/amazon/awssdk/http/nio/netty/internal/Http1TunnelConnectionPool.java
+++ b/http-clients/netty-nio-client/src/main/java/software/amazon/awssdk/http/nio/netty/internal/Http1TunnelConnectionPool.java
@@ -49,41 +49,30 @@ public class Http1TunnelConnectionPool implements ChannelPool {
private final ChannelPool delegate;
private final SslContext sslContext;
private final URI proxyAddress;
- private final String proxyUser;
- private final String proxyPassword;
+ private final ProxyAuthGenerator proxyAuthGenerator;
private final URI remoteAddress;
private final ChannelPoolHandler handler;
private final InitHandlerSupplier initHandlerSupplier;
private final NettyConfiguration nettyConfiguration;
public Http1TunnelConnectionPool(EventLoop eventLoop, ChannelPool delegate, SslContext sslContext,
- URI proxyAddress, String proxyUsername, String proxyPassword,
+ URI proxyAddress, ProxyAuthGenerator proxyAuthGenerator,
URI remoteAddress, ChannelPoolHandler handler, NettyConfiguration nettyConfiguration) {
this(eventLoop, delegate, sslContext,
- proxyAddress, proxyUsername, proxyPassword, remoteAddress, handler,
+ proxyAddress, proxyAuthGenerator, remoteAddress, handler,
ProxyTunnelInitHandler::new, nettyConfiguration);
}
- public Http1TunnelConnectionPool(EventLoop eventLoop, ChannelPool delegate, SslContext sslContext,
- URI proxyAddress, URI remoteAddress, ChannelPoolHandler handler,
- NettyConfiguration nettyConfiguration) {
- this(eventLoop, delegate, sslContext,
- proxyAddress, null, null, remoteAddress, handler,
- ProxyTunnelInitHandler::new, nettyConfiguration);
-
- }
-
@SdkTestInternalApi
Http1TunnelConnectionPool(EventLoop eventLoop, ChannelPool delegate, SslContext sslContext,
- URI proxyAddress, String proxyUser, String proxyPassword, URI remoteAddress,
+ URI proxyAddress, ProxyAuthGenerator proxyAuthGenerator, URI remoteAddress,
ChannelPoolHandler handler, InitHandlerSupplier initHandlerSupplier,
NettyConfiguration nettyConfiguration) {
this.eventLoop = eventLoop;
this.delegate = delegate;
this.sslContext = sslContext;
this.proxyAddress = proxyAddress;
- this.proxyUser = proxyUser;
- this.proxyPassword = proxyPassword;
+ this.proxyAuthGenerator = proxyAuthGenerator;
this.remoteAddress = remoteAddress;
this.handler = handler;
this.initHandlerSupplier = initHandlerSupplier;
@@ -138,7 +127,7 @@ private void setupChannel(Channel ch, Promise acquirePromise) {
if (sslHandler != null) {
ch.pipeline().addLast(sslHandler);
}
- ch.pipeline().addLast(initHandlerSupplier.newInitHandler(delegate, proxyUser, proxyPassword, remoteAddress,
+ ch.pipeline().addLast(initHandlerSupplier.newInitHandler(delegate, proxyAddress, proxyAuthGenerator, remoteAddress,
tunnelEstablishedPromise));
tunnelEstablishedPromise.addListener((Future f) -> {
if (f.isSuccess()) {
@@ -180,7 +169,10 @@ private static boolean isTunnelEstablished(Channel ch) {
@SdkTestInternalApi
@FunctionalInterface
interface InitHandlerSupplier {
- ChannelHandler newInitHandler(ChannelPool sourcePool, String proxyUsername, String proxyPassword, URI remoteAddress,
+ ChannelHandler newInitHandler(ChannelPool sourcePool,
+ URI proxyAddress,
+ ProxyAuthGenerator authGenerator,
+ URI remoteAddress,
Promise tunnelInitFuture);
}
}
diff --git a/http-clients/netty-nio-client/src/main/java/software/amazon/awssdk/http/nio/netty/internal/NegotiateProxyAuthGenerator.java b/http-clients/netty-nio-client/src/main/java/software/amazon/awssdk/http/nio/netty/internal/NegotiateProxyAuthGenerator.java
index c314d5c32ff3..df76b2a8a8ba 100644
--- a/http-clients/netty-nio-client/src/main/java/software/amazon/awssdk/http/nio/netty/internal/NegotiateProxyAuthGenerator.java
+++ b/http-clients/netty-nio-client/src/main/java/software/amazon/awssdk/http/nio/netty/internal/NegotiateProxyAuthGenerator.java
@@ -33,7 +33,6 @@
import org.ietf.jgss.Oid;
import software.amazon.awssdk.annotations.SdkInternalApi;
import software.amazon.awssdk.annotations.SdkTestInternalApi;
-import software.amazon.awssdk.http.SdkHttpRequest;
import software.amazon.awssdk.http.nio.netty.ProxyAuthScheme;
import software.amazon.awssdk.utils.BinaryUtils;
@@ -64,12 +63,12 @@ public ProxyAuthScheme scheme() {
}
@Override
- public String generateAuthParams(SdkHttpRequest request) {
+ public String generateAuthParams(URI proxyEndpoint) {
try {
Subject subject = getSubject();
byte[] token = Subject.doAs(subject, (PrivilegedExceptionAction) () -> {
- GSSContext ctx = createGSSContext(getManager(), request.getUri());
+ GSSContext ctx = createGssContext(getManager(), proxyEndpoint);
ctx.requestMutualAuth(true);
return ctx.initSecContext(new byte[0], 0, 0);
});
@@ -90,7 +89,7 @@ private Subject getSubject() {
}
}
- private GSSContext createGSSContext(GSSManager manager, URI endpoint) {
+ private GSSContext createGssContext(GSSManager manager, URI endpoint) {
try {
String name = String.format("%s@%s", SERVICE_NAME, endpoint.getHost());
GSSName serverName = manager.createName(name, GSSName.NT_HOSTBASED_SERVICE);
diff --git a/http-clients/netty-nio-client/src/main/java/software/amazon/awssdk/http/nio/netty/internal/ProxyAuthGenerator.java b/http-clients/netty-nio-client/src/main/java/software/amazon/awssdk/http/nio/netty/internal/ProxyAuthGenerator.java
index 3e0b2569f364..eeb84fbdb6f5 100644
--- a/http-clients/netty-nio-client/src/main/java/software/amazon/awssdk/http/nio/netty/internal/ProxyAuthGenerator.java
+++ b/http-clients/netty-nio-client/src/main/java/software/amazon/awssdk/http/nio/netty/internal/ProxyAuthGenerator.java
@@ -15,8 +15,8 @@
package software.amazon.awssdk.http.nio.netty.internal;
+import java.net.URI;
import software.amazon.awssdk.annotations.SdkInternalApi;
-import software.amazon.awssdk.http.SdkHttpRequest;
import software.amazon.awssdk.http.nio.netty.ProxyAuthScheme;
/**
@@ -32,5 +32,5 @@ public interface ProxyAuthGenerator {
/**
* Generate the auth params for this request.
*/
- String generateAuthParams(SdkHttpRequest request);
+ String generateAuthParams(URI proxyEndpoint);
}
diff --git a/http-clients/netty-nio-client/src/main/java/software/amazon/awssdk/http/nio/netty/internal/ProxyTunnelInitHandler.java b/http-clients/netty-nio-client/src/main/java/software/amazon/awssdk/http/nio/netty/internal/ProxyTunnelInitHandler.java
index e6f309afbad3..277ed555dbc5 100644
--- a/http-clients/netty-nio-client/src/main/java/software/amazon/awssdk/http/nio/netty/internal/ProxyTunnelInitHandler.java
+++ b/http-clients/netty-nio-client/src/main/java/software/amazon/awssdk/http/nio/netty/internal/ProxyTunnelInitHandler.java
@@ -28,11 +28,9 @@
import io.netty.handler.codec.http.HttpRequest;
import io.netty.handler.codec.http.HttpResponse;
import io.netty.handler.codec.http.HttpVersion;
-import io.netty.util.CharsetUtil;
import io.netty.util.concurrent.Promise;
import java.io.IOException;
import java.net.URI;
-import java.util.Base64;
import java.util.function.Supplier;
import software.amazon.awssdk.annotations.SdkInternalApi;
import software.amazon.awssdk.annotations.SdkTestInternalApi;
@@ -47,32 +45,51 @@ public final class ProxyTunnelInitHandler extends ChannelDuplexHandler {
public static final NettyClientLogger log = NettyClientLogger.getLogger(ProxyTunnelInitHandler.class);
private final ChannelPool sourcePool;
- private final String username;
- private final String password;
+ private final URI proxyAddress;
+ private final ProxyAuthGenerator authGenerator;
private final URI remoteHost;
private final Promise initPromise;
private final Supplier httpCodecSupplier;
public ProxyTunnelInitHandler(ChannelPool sourcePool, String proxyUsername, String proxyPassword, URI remoteHost,
Promise initPromise) {
- this(sourcePool, proxyUsername, proxyPassword, remoteHost, initPromise, HttpClientCodec::new);
+ this(sourcePool, null, proxyUsername, proxyPassword, remoteHost, initPromise, HttpClientCodec::new);
}
public ProxyTunnelInitHandler(ChannelPool sourcePool, URI remoteHost, Promise initPromise) {
- this(sourcePool, null, null, remoteHost, initPromise, HttpClientCodec::new);
+ this(sourcePool, null, null, null, remoteHost, initPromise, HttpClientCodec::new);
}
@SdkTestInternalApi
- public ProxyTunnelInitHandler(ChannelPool sourcePool, String prosyUsername, String proxyPassword,
+ public ProxyTunnelInitHandler(ChannelPool sourcePool, URI proxyAddress, String proxyUsername, String proxyPassword,
URI remoteHost, Promise initPromise, Supplier httpCodecSupplier) {
this.sourcePool = sourcePool;
+ this.proxyAddress = proxyAddress;
this.remoteHost = remoteHost;
this.initPromise = initPromise;
- this.username = prosyUsername;
- this.password = proxyPassword;
+ if (!StringUtils.isBlank(proxyPassword) && !StringUtils.isBlank(proxyPassword)) {
+ this.authGenerator = new BasicProxyAuthGenerator(proxyUsername, proxyPassword);
+ } else {
+ this.authGenerator = null;
+ }
this.httpCodecSupplier = httpCodecSupplier;
}
+ public ProxyTunnelInitHandler(ChannelPool sourcePool, URI proxyAddress, ProxyAuthGenerator authGenerator,
+ URI remoteHost, Promise initPromise, Supplier httpCodecSupplier) {
+ this.sourcePool = sourcePool;
+ this.proxyAddress = proxyAddress;
+ this.remoteHost = remoteHost;
+ this.initPromise = initPromise;
+ this.authGenerator = authGenerator;
+ this.httpCodecSupplier = httpCodecSupplier;
+ }
+
+ public ProxyTunnelInitHandler(ChannelPool sourcePool, URI proxyAddress, ProxyAuthGenerator authGenerator,
+ URI remoteHost, Promise initPromise) {
+ this(sourcePool, proxyAddress, authGenerator, remoteHost, initPromise, HttpClientCodec::new);
+ }
+
@Override
public void handlerAdded(ChannelHandlerContext ctx) {
ChannelPipeline pipeline = ctx.pipeline();
@@ -151,10 +168,9 @@ private HttpRequest connectRequest() {
Unpooled.EMPTY_BUFFER);
request.headers().add(HttpHeaderNames.HOST, uri);
- if (!StringUtils.isEmpty(this.username) && !StringUtils.isEmpty(this.password)) {
- String authToken = String.format("%s:%s", this.username, this.password);
- String authB64 = Base64.getEncoder().encodeToString(authToken.getBytes(CharsetUtil.UTF_8));
- request.headers().add(HttpHeaderNames.PROXY_AUTHORIZATION, String.format("Basic %s", authB64));
+ if (authGenerator != null) {
+ String auth = String.format("%s %s", authGenerator.scheme().value(), authGenerator.generateAuthParams(proxyAddress));
+ request.headers().add(HttpHeaderNames.PROXY_AUTHORIZATION, auth);
}
return request;
diff --git a/http-clients/netty-nio-client/src/test/java/software/amazon/awssdk/http/nio/netty/internal/BasicProxyAuthGeneratorTest.java b/http-clients/netty-nio-client/src/test/java/software/amazon/awssdk/http/nio/netty/internal/BasicProxyAuthGeneratorTest.java
index ba7d4bf5d4f1..b0294ea768c3 100644
--- a/http-clients/netty-nio-client/src/test/java/software/amazon/awssdk/http/nio/netty/internal/BasicProxyAuthGeneratorTest.java
+++ b/http-clients/netty-nio-client/src/test/java/software/amazon/awssdk/http/nio/netty/internal/BasicProxyAuthGeneratorTest.java
@@ -17,8 +17,8 @@
import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.assertThatThrownBy;
-import static org.mockito.Mockito.mock;
+import java.net.URI;
import java.nio.charset.StandardCharsets;
import java.util.Base64;
import java.util.stream.Stream;
@@ -26,7 +26,6 @@
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.Arguments;
import org.junit.jupiter.params.provider.MethodSource;
-import software.amazon.awssdk.http.SdkHttpRequest;
import software.amazon.awssdk.http.nio.netty.ProxyAuthScheme;
public class BasicProxyAuthGeneratorTest {
@@ -53,15 +52,13 @@ void generateAuthParams_generatedCorrectly() {
.encodeToString(String.format("%s:%s", USERNAME, PASSWORD)
.getBytes(StandardCharsets.UTF_8));
- assertThat(authGenerator.generateAuthParams(mock(SdkHttpRequest.class))).isEqualTo(expected);
+ assertThat(authGenerator.generateAuthParams(URI.create("http://amazon.com"))).isEqualTo(expected);
}
private static Stream invalidCtorParams() {
return Stream.of(
Arguments.of(null, null, "username"),
Arguments.of("", "", "username"),
- Arguments.of(" ", "", "username"),
- Arguments.of(" ", " ", "username"),
Arguments.of(null, PASSWORD, "username"),
Arguments.of("", PASSWORD, "username"),
Arguments.of(USERNAME, null, "password"),
diff --git a/http-clients/netty-nio-client/src/test/java/software/amazon/awssdk/http/nio/netty/internal/Http1TunnelConnectionPoolTest.java b/http-clients/netty-nio-client/src/test/java/software/amazon/awssdk/http/nio/netty/internal/Http1TunnelConnectionPoolTest.java
index d43b404f3f5f..9e2ed53cd1e3 100644
--- a/http-clients/netty-nio-client/src/test/java/software/amazon/awssdk/http/nio/netty/internal/Http1TunnelConnectionPoolTest.java
+++ b/http-clients/netty-nio-client/src/test/java/software/amazon/awssdk/http/nio/netty/internal/Http1TunnelConnectionPoolTest.java
@@ -73,6 +73,8 @@ public class Http1TunnelConnectionPoolTest {
private static final String PROXY_PASSWORD = "mypassword";
+ private static final ProxyAuthGenerator basicAuth = new BasicProxyAuthGenerator(PROXY_USER, PROXY_PASSWORD);
+
@Mock
private ChannelPool delegatePool;
@@ -115,7 +117,7 @@ public static void teardown() {
@Test
public void tunnelAlreadyEstablished_doesNotAddInitHandler() {
Http1TunnelConnectionPool tunnelPool = new Http1TunnelConnectionPool(GROUP.next(), delegatePool, null,
- HTTP_PROXY_ADDRESS, REMOTE_ADDRESS, mockHandler, configuration);
+ HTTP_PROXY_ADDRESS, null, REMOTE_ADDRESS, mockHandler, configuration);
when(mockAttr.get()).thenReturn(true);
@@ -127,7 +129,7 @@ public void tunnelAlreadyEstablished_doesNotAddInitHandler() {
@Test(timeout = 1000)
public void tunnelNotEstablished_addsInitHandler() throws InterruptedException {
Http1TunnelConnectionPool tunnelPool = new Http1TunnelConnectionPool(GROUP.next(), delegatePool, null,
- HTTP_PROXY_ADDRESS, REMOTE_ADDRESS, mockHandler, configuration);
+ HTTP_PROXY_ADDRESS, null, REMOTE_ADDRESS, mockHandler, configuration);
when(mockAttr.get()).thenReturn(false);
@@ -149,7 +151,7 @@ public void tunnelInitFails_acquireFutureFails() {
};
Http1TunnelConnectionPool tunnelPool = new Http1TunnelConnectionPool(GROUP.next(), delegatePool, null,
- HTTP_PROXY_ADDRESS,null, null, REMOTE_ADDRESS, mockHandler, supplier, configuration);
+ HTTP_PROXY_ADDRESS,null, REMOTE_ADDRESS, mockHandler, supplier, configuration);
Future acquireFuture = tunnelPool.acquire();
@@ -164,7 +166,7 @@ public void tunnelInitSucceeds_acquireFutureSucceeds() {
};
Http1TunnelConnectionPool tunnelPool = new Http1TunnelConnectionPool(GROUP.next(), delegatePool, null,
- HTTP_PROXY_ADDRESS, null, null, REMOTE_ADDRESS, mockHandler, supplier, configuration);
+ HTTP_PROXY_ADDRESS, null, REMOTE_ADDRESS, mockHandler, supplier, configuration);
Future acquireFuture = tunnelPool.acquire();
@@ -174,7 +176,7 @@ public void tunnelInitSucceeds_acquireFutureSucceeds() {
@Test
public void acquireFromDelegatePoolFails_failsFuture() {
Http1TunnelConnectionPool tunnelPool = new Http1TunnelConnectionPool(GROUP.next(), delegatePool, null,
- HTTP_PROXY_ADDRESS, REMOTE_ADDRESS, mockHandler, configuration);
+ HTTP_PROXY_ADDRESS, null, REMOTE_ADDRESS, mockHandler, configuration);
when(delegatePool.acquire(any(Promise.class))).thenReturn(GROUP.next().newFailedFuture(new IOException("boom")));
@@ -197,7 +199,7 @@ public void sslContextProvided_andProxyUsingHttps_addsSslHandler() {
};
Http1TunnelConnectionPool tunnelPool = new Http1TunnelConnectionPool(GROUP.next(), delegatePool, mockSslCtx,
- HTTPS_PROXY_ADDRESS, null, null, REMOTE_ADDRESS, mockHandler, supplier, configuration);
+ HTTPS_PROXY_ADDRESS, null, REMOTE_ADDRESS, mockHandler, supplier, configuration);
tunnelPool.acquire().awaitUninterruptibly();
@@ -218,7 +220,7 @@ public void sslContextProvided_andProxyNotUsingHttps_doesNotAddSslHandler() {
};
Http1TunnelConnectionPool tunnelPool = new Http1TunnelConnectionPool(GROUP.next(), delegatePool, mockSslCtx,
- HTTP_PROXY_ADDRESS, null, null, REMOTE_ADDRESS, mockHandler, supplier, configuration);
+ HTTP_PROXY_ADDRESS, null, REMOTE_ADDRESS, mockHandler, supplier, configuration);
tunnelPool.acquire().awaitUninterruptibly();
@@ -231,7 +233,7 @@ public void sslContextProvided_andProxyNotUsingHttps_doesNotAddSslHandler() {
@Test
public void release_releasedToDelegatePool() {
Http1TunnelConnectionPool tunnelPool = new Http1TunnelConnectionPool(GROUP.next(), delegatePool, null,
- HTTP_PROXY_ADDRESS, REMOTE_ADDRESS, mockHandler, configuration);
+ HTTP_PROXY_ADDRESS,null, REMOTE_ADDRESS, mockHandler, configuration);
tunnelPool.release(mockChannel);
verify(delegatePool).release(eq(mockChannel), any(Promise.class));
}
@@ -239,7 +241,7 @@ public void release_releasedToDelegatePool() {
@Test
public void release_withGivenPromise_releasedToDelegatePool() {
Http1TunnelConnectionPool tunnelPool = new Http1TunnelConnectionPool(GROUP.next(), delegatePool, null,
- HTTP_PROXY_ADDRESS, REMOTE_ADDRESS, mockHandler, configuration);
+ HTTP_PROXY_ADDRESS, null, REMOTE_ADDRESS, mockHandler, configuration);
Promise mockPromise = mock(Promise.class);
tunnelPool.release(mockChannel, mockPromise);
verify(delegatePool).release(eq(mockChannel), eq(mockPromise));
@@ -248,7 +250,7 @@ public void release_withGivenPromise_releasedToDelegatePool() {
@Test
public void close_closesDelegatePool() {
Http1TunnelConnectionPool tunnelPool = new Http1TunnelConnectionPool(GROUP.next(), delegatePool, null,
- HTTP_PROXY_ADDRESS, REMOTE_ADDRESS, mockHandler, configuration);
+ HTTP_PROXY_ADDRESS, null, REMOTE_ADDRESS, mockHandler, configuration);
tunnelPool.close();
verify(delegatePool).close();
}
@@ -257,42 +259,32 @@ public void close_closesDelegatePool() {
public void proxyAuthProvided_addInitHandler_withAuth(){
TestInitHandlerData data = new TestInitHandlerData();
- Http1TunnelConnectionPool.InitHandlerSupplier supplier = (srcPool, proxyUser, proxyPassword, remoteAddr, initFuture) -> {
+ Http1TunnelConnectionPool.InitHandlerSupplier supplier =
+ (srcPool, proxyEndpoint, proxyAuthGenerator, remoteAddr, initFuture) -> {
initFuture.setSuccess(mockChannel);
- data.proxyUser(proxyUser);
- data.proxyPassword(proxyPassword);
+ data.authHeader = proxyAuthGenerator.generateAuthParams(proxyEndpoint);
return mock(ChannelHandler.class);
};
Http1TunnelConnectionPool tunnelPool = new Http1TunnelConnectionPool(GROUP.next(), delegatePool, null,
- HTTP_PROXY_ADDRESS, PROXY_USER, PROXY_PASSWORD, REMOTE_ADDRESS, mockHandler, supplier, configuration);
+ HTTP_PROXY_ADDRESS, basicAuth, REMOTE_ADDRESS, mockHandler, supplier, configuration);
tunnelPool.acquire().awaitUninterruptibly();
- assertThat(data.proxyUser()).isEqualTo(PROXY_USER);
- assertThat(data.proxyPassword()).isEqualTo(PROXY_PASSWORD);
-
+ // assertThat(data.proxyUser()).isEqualTo(PROXY_USER);
+ // assertThat(data.proxyPassword()).isEqualTo(PROXY_PASSWORD);
}
private static class TestInitHandlerData {
- private String proxyUser;
- private String proxyPassword;
-
- public void proxyUser(String proxyUser) {
- this.proxyUser = proxyUser;
- }
-
- public String proxyUser() {
- return this.proxyUser;
- }
+ private String authHeader;
- public void proxyPassword(String proxyPassword) {
- this.proxyPassword = proxyPassword;
+ public void authHeader(String authHeader) {
+ this.authHeader = authHeader;
}
- public String proxyPassword(){
- return this.proxyPassword;
+ public String authHeader() {
+ return authHeader;
}
}
diff --git a/http-clients/netty-nio-client/src/test/java/software/amazon/awssdk/http/nio/netty/internal/NegotiateProxyAuthGeneratorTest.java b/http-clients/netty-nio-client/src/test/java/software/amazon/awssdk/http/nio/netty/internal/NegotiateProxyAuthGeneratorTest.java
index 839aff7c4be5..d7c7a3bc1506 100644
--- a/http-clients/netty-nio-client/src/test/java/software/amazon/awssdk/http/nio/netty/internal/NegotiateProxyAuthGeneratorTest.java
+++ b/http-clients/netty-nio-client/src/test/java/software/amazon/awssdk/http/nio/netty/internal/NegotiateProxyAuthGeneratorTest.java
@@ -20,6 +20,7 @@
import java.io.IOException;
import java.net.InetSocketAddress;
import java.net.Socket;
+import java.net.URI;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.HashMap;
@@ -33,8 +34,6 @@
import org.junit.jupiter.api.AfterAll;
import org.junit.jupiter.api.BeforeAll;
import org.junit.jupiter.api.Test;
-import software.amazon.awssdk.http.SdkHttpMethod;
-import software.amazon.awssdk.http.SdkHttpRequest;
import software.amazon.awssdk.testutils.FileUtils;
public class NegotiateProxyAuthGeneratorTest {
@@ -103,13 +102,9 @@ static void teardown() throws KrbException {
void generateAuthParams_configValid_successfullyGeneratesToken() {
NegotiateProxyAuthGenerator authGenerator = new NegotiateProxyAuthGenerator(config);
- SdkHttpRequest request = SdkHttpRequest.builder()
- .protocol("http")
- .host("localhost")
- .method(SdkHttpMethod.GET)
- .build();
+ URI proxyEndpoint = URI.create("https://localhost:8192");
- assertThat(authGenerator.generateAuthParams(request)).startsWith("YII");
+ assertThat(authGenerator.generateAuthParams(proxyEndpoint)).startsWith("YII");
}
}
diff --git a/http-clients/netty-nio-client/src/test/java/software/amazon/awssdk/http/nio/netty/internal/ProxyTunnelInitHandlerTest.java b/http-clients/netty-nio-client/src/test/java/software/amazon/awssdk/http/nio/netty/internal/ProxyTunnelInitHandlerTest.java
index 9836a953bda9..7828050bef26 100644
--- a/http-clients/netty-nio-client/src/test/java/software/amazon/awssdk/http/nio/netty/internal/ProxyTunnelInitHandlerTest.java
+++ b/http-clients/netty-nio-client/src/test/java/software/amazon/awssdk/http/nio/netty/internal/ProxyTunnelInitHandlerTest.java
@@ -95,7 +95,8 @@ public void addedToPipeline_addsCodec() {
Supplier codecSupplier = () -> codec;
when(mockCtx.name()).thenReturn("foo");
- ProxyTunnelInitHandler handler = new ProxyTunnelInitHandler(mockChannelPool, null, null, REMOTE_HOST, null, codecSupplier);
+ ProxyTunnelInitHandler handler = new ProxyTunnelInitHandler(mockChannelPool, null, null, null, REMOTE_HOST, null,
+ codecSupplier);
handler.handlerAdded(mockCtx);
verify(mockPipeline).addBefore(eq("foo"), eq(null), eq(codec));
@@ -202,7 +203,7 @@ public void handlerRemoved_removesCodec() {
}
@Test
- public void handledAdded_writesRequest_withoutAuth() {
+ public void handlerAdded_writesRequest_withoutAuth() {
Promise promise = GROUP.next().newPromise();
ProxyTunnelInitHandler handler = new ProxyTunnelInitHandler(mockChannelPool, REMOTE_HOST, promise);
handler.handlerAdded(mockCtx);
@@ -219,7 +220,7 @@ public void handledAdded_writesRequest_withoutAuth() {
}
@Test
- public void handledAdded_writesRequest_withAuth() {
+ public void handlerAdded_writesRequest_withAuth() {
Promise promise = GROUP.next().newPromise();
ProxyTunnelInitHandler handler = new ProxyTunnelInitHandler(mockChannelPool, PROXY_USER, PROXY_PASSWORD, REMOTE_HOST, promise);
handler.handlerAdded(mockCtx);
From eea0b886967e4d95b189150c3d1b18d1e762b0fd Mon Sep 17 00:00:00 2001
From: Dongie Agnir <261310+dagnir@users.noreply.github.com>
Date: Thu, 13 Aug 2026 17:34:38 -0700
Subject: [PATCH 5/8] Support ProxyAuthScheme (#7260)
* Support ProxyAuthScheme
This commit Adds a `ProxyAuthScheme` configuration option in
`ProxyConfiguration` and adds support for `NEGOTIATE` auth scheme.
For backwards compatibility, if username and password are set on the
config and the proxy auth scheme is *not* set, the client assumes
`BASIC` auth scheme. If `NEGOTIATE` is configured, `username` and
`password` are ignored.
* Fix test
---
.../http/nio/netty/ProxyConfiguration.java | 33 ++++
.../internal/AwaitCloseChannelPoolMap.java | 34 +++++
.../internal/NegotiateProxyAuthGenerator.java | 18 ++-
.../internal/ProxyTunnelInitHandler.java | 12 +-
.../nio/netty/ProxyConfigurationTest.java | 6 +-
.../AwaitCloseChannelPoolMapTest.java | 142 +++++++++++++++---
.../Http1TunnelConnectionPoolTest.java | 7 +-
.../NegotiateProxyAuthGeneratorTest.java | 78 ++++++----
.../internal/ProxyTunnelInitHandlerTest.java | 20 +++
9 files changed, 288 insertions(+), 62 deletions(-)
diff --git a/http-clients/netty-nio-client/src/main/java/software/amazon/awssdk/http/nio/netty/ProxyConfiguration.java b/http-clients/netty-nio-client/src/main/java/software/amazon/awssdk/http/nio/netty/ProxyConfiguration.java
index 2c422fceb2b4..0e2592c32d6a 100644
--- a/http-clients/netty-nio-client/src/main/java/software/amazon/awssdk/http/nio/netty/ProxyConfiguration.java
+++ b/http-clients/netty-nio-client/src/main/java/software/amazon/awssdk/http/nio/netty/ProxyConfiguration.java
@@ -41,6 +41,7 @@ public final class ProxyConfiguration implements ToCopyableBuilder nonProxyHosts;
private ProxyConfiguration(BuilderImpl builder) {
@@ -56,6 +57,7 @@ private ProxyConfiguration(BuilderImpl builder) {
this.port = resolvePort(builder, proxyConfigProvider);
this.username = resolveUserName(builder, proxyConfigProvider);
this.password = resolvePassword(builder, proxyConfigProvider);
+ this.proxyAuthScheme = builder.proxyAuthScheme;
this.nonProxyHosts = resolveNonProxyHosts(builder, proxyConfigProvider);
}
@@ -151,6 +153,13 @@ public Set nonProxyHosts() {
return Collections.unmodifiableSet(nonProxyHosts != null ? nonProxyHosts : Collections.emptySet());
}
+ /**
+ * @return The auth scheme to use to authenticate with the proxy.
+ */
+ public ProxyAuthScheme proxyAuthScheme() {
+ return proxyAuthScheme;
+ }
+
@Override
public boolean equals(Object o) {
if (this == o) {
@@ -183,6 +192,10 @@ public boolean equals(Object o) {
return false;
}
+ if (proxyAuthScheme != null ? !proxyAuthScheme.equals(that.proxyAuthScheme) : that.proxyAuthScheme != null) {
+ return false;
+ }
+
return nonProxyHosts.equals(that.nonProxyHosts);
}
@@ -195,6 +208,7 @@ public int hashCode() {
result = 31 * result + nonProxyHosts.hashCode();
result = 31 * result + (username != null ? username.hashCode() : 0);
result = 31 * result + (password != null ? password.hashCode() : 0);
+ result = 31 * result + (proxyAuthScheme != null ? proxyAuthScheme.hashCode() : 0);
return result;
}
@@ -243,6 +257,17 @@ public interface Builder extends CopyableBuilder {
*/
Builder nonProxyHosts(Set nonProxyHosts);
+ /**
+ * Configure the auth scheme to use to authenticate with the proxy.
+ *
+ * If unset and {@link #username(String)} and {@link #password(String)} are set, the client will
+ * assume {@link ProxyAuthScheme#BASIC} auth.
+ *
+ * @param proxyAuthScheme The auth scheme.
+ * @return This object for method chaining.
+ */
+ Builder proxyAuthScheme(ProxyAuthScheme proxyAuthScheme);
+
/**
* Set the username used to authenticate with the proxy username.
*
@@ -293,6 +318,7 @@ private static final class BuilderImpl implements Builder {
private String scheme = "http";
private String host;
private int port = 0;
+ private ProxyAuthScheme proxyAuthScheme;
private String username;
private String password;
private Set nonProxyHosts;
@@ -310,6 +336,7 @@ private BuilderImpl(ProxyConfiguration proxyConfiguration) {
this.port = proxyConfiguration.port;
this.nonProxyHosts = proxyConfiguration.nonProxyHosts != null ?
new HashSet<>(proxyConfiguration.nonProxyHosts) : null;
+ this.proxyAuthScheme = proxyConfiguration.proxyAuthScheme;
this.username = proxyConfiguration.username;
this.password = proxyConfiguration.password;
}
@@ -342,6 +369,12 @@ public Builder nonProxyHosts(Set nonProxyHosts) {
return this;
}
+ @Override
+ public Builder proxyAuthScheme(ProxyAuthScheme proxyAuthScheme) {
+ this.proxyAuthScheme = proxyAuthScheme;
+ return this;
+ }
+
@Override
public Builder username(String username) {
this.username = username;
diff --git a/http-clients/netty-nio-client/src/main/java/software/amazon/awssdk/http/nio/netty/internal/AwaitCloseChannelPoolMap.java b/http-clients/netty-nio-client/src/main/java/software/amazon/awssdk/http/nio/netty/internal/AwaitCloseChannelPoolMap.java
index 83665f08b927..d9441a2f6ee2 100644
--- a/http-clients/netty-nio-client/src/main/java/software/amazon/awssdk/http/nio/netty/internal/AwaitCloseChannelPoolMap.java
+++ b/http-clients/netty-nio-client/src/main/java/software/amazon/awssdk/http/nio/netty/internal/AwaitCloseChannelPoolMap.java
@@ -36,10 +36,12 @@
import java.util.concurrent.TimeoutException;
import java.util.concurrent.atomic.AtomicReference;
import java.util.function.Function;
+import javax.security.auth.login.Configuration;
import software.amazon.awssdk.annotations.SdkInternalApi;
import software.amazon.awssdk.annotations.SdkTestInternalApi;
import software.amazon.awssdk.http.Protocol;
import software.amazon.awssdk.http.ProtocolNegotiation;
+import software.amazon.awssdk.http.nio.netty.ProxyAuthScheme;
import software.amazon.awssdk.http.nio.netty.ProxyConfiguration;
import software.amazon.awssdk.http.nio.netty.SdkEventLoopGroup;
import software.amazon.awssdk.http.nio.netty.internal.http2.HttpOrHttp2ChannelPool;
@@ -88,6 +90,8 @@ public void channelCreated(Channel ch) throws Exception {
private final SslContextProvider sslContextProvider;
private final Boolean useNonBlockingDnsResolver;
+ private final Configuration negotiateAuthConfig;
+
private AwaitCloseChannelPoolMap(Builder builder, Function createBootStrapProvider) {
this.configuration = builder.configuration;
this.protocol = builder.protocol;
@@ -100,6 +104,7 @@ private AwaitCloseChannelPoolMap(Builder builder, Function) () -> {
GSSContext ctx = createGssContext(getManager(), proxyEndpoint);
- ctx.requestMutualAuth(true);
- return ctx.initSecContext(new byte[0], 0, 0);
+ try {
+ ctx.requestMutualAuth(true);
+ return ctx.initSecContext(new byte[0], 0, 0);
+ } finally {
+ ctx.dispose();
+ }
});
return BinaryUtils.toBase64(token);
diff --git a/http-clients/netty-nio-client/src/main/java/software/amazon/awssdk/http/nio/netty/internal/ProxyTunnelInitHandler.java b/http-clients/netty-nio-client/src/main/java/software/amazon/awssdk/http/nio/netty/internal/ProxyTunnelInitHandler.java
index 277ed555dbc5..aeda02f02e0f 100644
--- a/http-clients/netty-nio-client/src/main/java/software/amazon/awssdk/http/nio/netty/internal/ProxyTunnelInitHandler.java
+++ b/http-clients/netty-nio-client/src/main/java/software/amazon/awssdk/http/nio/netty/internal/ProxyTunnelInitHandler.java
@@ -67,7 +67,7 @@ public ProxyTunnelInitHandler(ChannelPool sourcePool, URI proxyAddress, String p
this.proxyAddress = proxyAddress;
this.remoteHost = remoteHost;
this.initPromise = initPromise;
- if (!StringUtils.isBlank(proxyPassword) && !StringUtils.isBlank(proxyPassword)) {
+ if (!StringUtils.isBlank(proxyUsername) && !StringUtils.isBlank(proxyPassword)) {
this.authGenerator = new BasicProxyAuthGenerator(proxyUsername, proxyPassword);
} else {
this.authGenerator = null;
@@ -94,7 +94,15 @@ public ProxyTunnelInitHandler(ChannelPool sourcePool, URI proxyAddress, ProxyAut
public void handlerAdded(ChannelHandlerContext ctx) {
ChannelPipeline pipeline = ctx.pipeline();
pipeline.addBefore(ctx.name(), null, httpCodecSupplier.get());
- HttpRequest connectRequest = connectRequest();
+
+ HttpRequest connectRequest;
+ try {
+ connectRequest = connectRequest();
+ } catch (Throwable t) {
+ handleConnectRequestFailure(ctx, t);
+ return;
+ }
+
ctx.channel().writeAndFlush(connectRequest).addListener(f -> {
if (!f.isSuccess()) {
handleConnectRequestFailure(ctx, f.cause());
diff --git a/http-clients/netty-nio-client/src/test/java/software/amazon/awssdk/http/nio/netty/ProxyConfigurationTest.java b/http-clients/netty-nio-client/src/test/java/software/amazon/awssdk/http/nio/netty/ProxyConfigurationTest.java
index 06d57c1aa7d2..36cc02d57c26 100644
--- a/http-clients/netty-nio-client/src/test/java/software/amazon/awssdk/http/nio/netty/ProxyConfigurationTest.java
+++ b/http-clients/netty-nio-client/src/test/java/software/amazon/awssdk/http/nio/netty/ProxyConfigurationTest.java
@@ -185,7 +185,11 @@ private void setRandomValue(Object o, Method setter) throws InvocationTargetExce
setter.invoke(o, randomSet());
} else if (Boolean.class.equals(paramClass)) {
setter.invoke(o, RNG.nextBoolean());
- } else {
+ } else if (ProxyAuthScheme.class.equals(paramClass)) {
+ ProxyAuthScheme authScheme = ProxyAuthScheme.values()[RNG.nextInt(ProxyAuthScheme.values().length)];
+ setter.invoke(o, authScheme);
+ }
+ else {
throw new RuntimeException("Don't know how create random value for type " + paramClass);
}
}
diff --git a/http-clients/netty-nio-client/src/test/java/software/amazon/awssdk/http/nio/netty/internal/AwaitCloseChannelPoolMapTest.java b/http-clients/netty-nio-client/src/test/java/software/amazon/awssdk/http/nio/netty/internal/AwaitCloseChannelPoolMapTest.java
index a1d4b9781f35..f1b80597d3c4 100644
--- a/http-clients/netty-nio-client/src/test/java/software/amazon/awssdk/http/nio/netty/internal/AwaitCloseChannelPoolMapTest.java
+++ b/http-clients/netty-nio-client/src/test/java/software/amazon/awssdk/http/nio/netty/internal/AwaitCloseChannelPoolMapTest.java
@@ -23,45 +23,83 @@
import static software.amazon.awssdk.http.SdkHttpConfigurationOption.GLOBAL_HTTP_DEFAULTS;
import static software.amazon.awssdk.http.SdkHttpConfigurationOption.TLS_KEY_MANAGERS_PROVIDER;
-import com.github.tomakehurst.wiremock.junit.WireMockRule;
+import com.github.tomakehurst.wiremock.WireMockServer;
import io.netty.channel.Channel;
import io.netty.channel.pool.ChannelPool;
import io.netty.handler.ssl.SslProvider;
-import io.netty.util.CharsetUtil;
import io.netty.util.concurrent.Future;
+import java.net.InetSocketAddress;
+import java.net.Socket;
import java.net.URI;
+import java.nio.file.Files;
+import java.nio.file.Path;
import java.util.ArrayList;
-import java.util.Base64;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;
import java.util.stream.Stream;
+import javax.security.auth.login.AppConfigurationEntry;
+import javax.security.auth.login.Configuration;
import org.apache.commons.lang3.RandomStringUtils;
-import org.junit.After;
-import org.junit.Rule;
-import org.junit.Test;
+import org.apache.kerby.kerberos.kerb.client.KrbClient;
+import org.apache.kerby.kerberos.kerb.server.SimpleKdcServer;
+import org.apache.kerby.kerberos.kerb.type.ticket.TgtTicket;
+import org.junit.jupiter.api.AfterAll;
+import org.junit.jupiter.api.AfterEach;
+import org.junit.jupiter.api.BeforeAll;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.params.ParameterizedTest;
+import org.junit.jupiter.params.provider.Arguments;
+import org.junit.jupiter.params.provider.MethodSource;
import org.mockito.Mockito;
import software.amazon.awssdk.http.Protocol;
import software.amazon.awssdk.http.ProtocolNegotiation;
import software.amazon.awssdk.http.TlsKeyManagersProvider;
+import software.amazon.awssdk.http.nio.netty.ProxyAuthScheme;
import software.amazon.awssdk.http.nio.netty.ProxyConfiguration;
import software.amazon.awssdk.http.nio.netty.RecordingNetworkTrafficListener;
import software.amazon.awssdk.http.nio.netty.SdkEventLoopGroup;
import software.amazon.awssdk.utils.AttributeMap;
public class AwaitCloseChannelPoolMapTest {
+ private static final String KRB5_PROP = "java.security.krb5.conf";
+ private static final RecordingNetworkTrafficListener recorder = new RecordingNetworkTrafficListener();
- private final RecordingNetworkTrafficListener recorder = new RecordingNetworkTrafficListener();
+ private static WireMockServer mockProxy;
+
+ private static Path tempDir;
+ private static Path keytabFile;
+ private static Path ccacheFile;
+ private static int port;
+
+ private static SimpleKdcServer kdc;
+ private static String krb5PropSave;
+
+ private static Configuration negotiateAuthConfig;
private AwaitCloseChannelPoolMap channelPoolMap;
- @Rule
- public WireMockRule mockProxy = new WireMockRule(wireMockConfig()
- .dynamicPort()
- .networkTrafficListener(recorder));
+ @BeforeAll
+ public static void setup() throws Exception {
+ mockProxy = new WireMockServer(wireMockConfig().dynamicPort().networkTrafficListener(recorder));
+ mockProxy.start();
+
+ setupMockKerberos();
+ }
+
+ @AfterAll
+ public static void teardown() throws Exception {
+ if (krb5PropSave != null) {
+ System.setProperty(KRB5_PROP, krb5PropSave);
+ } else {
+ System.clearProperty(KRB5_PROP);
+ }
+ mockProxy.stop();
+ kdc.stop();
+ }
- @After
+ @AfterEach
public void methodTeardown() {
if (channelPoolMap != null) {
channelPoolMap.close();
@@ -71,6 +109,56 @@ public void methodTeardown() {
recorder.reset();
}
+ private static void setupMockKerberos() throws Exception {
+ tempDir = Files.createTempDirectory(null);
+ keytabFile = tempDir.resolve("keytab");
+ ccacheFile = tempDir.resolve("ccache");
+
+ try (Socket freePort = new Socket()) {
+ freePort.setReuseAddress(true);
+ freePort.bind(new InetSocketAddress(0));
+ port = freePort.getLocalPort();
+
+ kdc = new SimpleKdcServer();
+ kdc.setKdcRealm("EXAMPLE.COM");
+ kdc.setKdcHost("localhost");
+ kdc.setWorkDir(tempDir.toFile());
+ kdc.setKdcTcpPort(port);
+ kdc.setAllowUdp(false);
+ kdc.init();
+
+ krb5PropSave = System.getProperty(KRB5_PROP);
+
+ System.setProperty(KRB5_PROP, tempDir.resolve("krb5.conf").toAbsolutePath().toString());
+ kdc.start();
+
+ kdc.createPrincipal("alice@EXAMPLE.COM", "alicePassword");
+ kdc.createAndExportPrincipals(keytabFile.toFile(), "HTTP/localhost@EXAMPLE.COM");
+
+ // initialize the ticket cache
+ KrbClient krbClient = kdc.getKrbClient();
+ TgtTicket tgt = krbClient.requestTgt("alice@EXAMPLE.COM", "alicePassword");
+ krbClient.storeTicket(tgt, ccacheFile.toFile());
+
+ // Override config so we look at the testing cache instead of the real system cache
+ negotiateAuthConfig = new Configuration() {
+ @Override
+ public AppConfigurationEntry[] getAppConfigurationEntry(String name) {
+ Map opts = new HashMap<>();
+ opts.put("useTicketCache", "true");
+ opts.put("ticketCache", ccacheFile.toAbsolutePath().toString());
+ opts.put("refreshKrb5Config", "true");
+ opts.put("doNotPrompt", "true");
+ return new AppConfigurationEntry[] {
+ new AppConfigurationEntry(
+ "com.sun.security.auth.module.Krb5LoginModule",
+ AppConfigurationEntry.LoginModuleControlFlag.REQUIRED, opts)
+ };
+ }
+ };
+ }
+ }
+
@Test
public void close_underlyingPoolsShouldBeClosed() {
channelPoolMap = AwaitCloseChannelPoolMap.builder()
@@ -216,13 +304,16 @@ public void usingProxy_noSchemeGiven_defaultsToHttp() {
assertThat(requests).contains("CONNECT some-awesome-service:443");
}
- @Test
- public void usingProxy_withAuth() {
+ @ParameterizedTest
+ @MethodSource("proxyAuthTestParams")
+ public void usingProxy_authHeaderCorrect(ProxyAuthScheme authScheme, String username, String password,
+ String proxyAuthHeader) {
ProxyConfiguration proxyConfiguration = ProxyConfiguration.builder()
.host("localhost")
.port(mockProxy.port())
- .username("myuser")
- .password("mypassword")
+ .proxyAuthScheme(authScheme)
+ .username(username)
+ .password(password)
.build();
channelPoolMap = AwaitCloseChannelPoolMap.builder()
@@ -233,6 +324,7 @@ public void usingProxy_withAuth() {
.protocol(Protocol.HTTP1_1)
.maxStreams(100)
.sslProvider(SslProvider.OPENSSL)
+ .negotiateAuthConfig(negotiateAuthConfig)
.build();
SimpleChannelPoolAwareChannelPool simpleChannelPoolAwareChannelPool = channelPoolMap.newPool(
@@ -244,9 +336,11 @@ public void usingProxy_withAuth() {
assertThat(requests).contains("CONNECT some-awesome-service:443");
- String authB64 = Base64.getEncoder().encodeToString("myuser:mypassword".getBytes(CharsetUtil.UTF_8));
- String authHeaderValue = String.format("Basic %s", authB64);
- assertThat(requests).contains(String.format("proxy-authorization: %s", authHeaderValue));
+ if (proxyAuthHeader == null) {
+ assertThat(requests).doesNotContain("proxy-authorization:");
+ } else {
+ assertThat(requests).contains(String.format("proxy-authorization: %s", proxyAuthHeader));
+ }
}
@Test
@@ -309,4 +403,14 @@ public void releaseChannel_autoReadEnabled() {
assertThat(channel.config().isAutoRead()).isTrue();
}
+ private static Stream proxyAuthTestParams() {
+ return Stream.of(
+ Arguments.of(null, null, null, null),
+ Arguments.of(null, "user", "pass", "Basic dXNlcjpwYXNz"),
+ Arguments.of(ProxyAuthScheme.BASIC, "user", "pass", "Basic dXNlcjpwYXNz"),
+ Arguments.of(ProxyAuthScheme.NEGOTIATE, null, null, "Negotiate YII"),
+ Arguments.of(ProxyAuthScheme.NEGOTIATE, "user", "pass", "Negotiate YII")
+
+ );
+ }
}
diff --git a/http-clients/netty-nio-client/src/test/java/software/amazon/awssdk/http/nio/netty/internal/Http1TunnelConnectionPoolTest.java b/http-clients/netty-nio-client/src/test/java/software/amazon/awssdk/http/nio/netty/internal/Http1TunnelConnectionPoolTest.java
index 9e2ed53cd1e3..b61ac05e0b10 100644
--- a/http-clients/netty-nio-client/src/test/java/software/amazon/awssdk/http/nio/netty/internal/Http1TunnelConnectionPoolTest.java
+++ b/http-clients/netty-nio-client/src/test/java/software/amazon/awssdk/http/nio/netty/internal/Http1TunnelConnectionPoolTest.java
@@ -42,6 +42,8 @@
import io.netty.util.concurrent.Promise;
import java.io.IOException;
import java.net.URI;
+import java.nio.charset.StandardCharsets;
+import java.util.Base64;
import java.util.List;
import java.util.concurrent.CountDownLatch;
import javax.net.ssl.SSLEngine;
@@ -271,8 +273,9 @@ public void proxyAuthProvided_addInitHandler_withAuth(){
tunnelPool.acquire().awaitUninterruptibly();
- // assertThat(data.proxyUser()).isEqualTo(PROXY_USER);
- // assertThat(data.proxyPassword()).isEqualTo(PROXY_PASSWORD);
+ String expectedAuthHeader = Base64.getEncoder().encodeToString((PROXY_USER + ":" + PROXY_PASSWORD)
+ .getBytes(StandardCharsets.UTF_8));
+ assertThat(data.authHeader()).isEqualTo(expectedAuthHeader);
}
private static class TestInitHandlerData {
diff --git a/http-clients/netty-nio-client/src/test/java/software/amazon/awssdk/http/nio/netty/internal/NegotiateProxyAuthGeneratorTest.java b/http-clients/netty-nio-client/src/test/java/software/amazon/awssdk/http/nio/netty/internal/NegotiateProxyAuthGeneratorTest.java
index d7c7a3bc1506..31b02867b6e7 100644
--- a/http-clients/netty-nio-client/src/test/java/software/amazon/awssdk/http/nio/netty/internal/NegotiateProxyAuthGeneratorTest.java
+++ b/http-clients/netty-nio-client/src/test/java/software/amazon/awssdk/http/nio/netty/internal/NegotiateProxyAuthGeneratorTest.java
@@ -37,12 +37,14 @@
import software.amazon.awssdk.testutils.FileUtils;
public class NegotiateProxyAuthGeneratorTest {
+ private static final String KRB5_PROP = "java.security.krb5.conf";
private static Path tempDir;
private static Path keytabFile;
private static Path ccacheFile;
private static int port;
private static SimpleKdcServer kdc;
+ private static String krb5PropSave;
private static Configuration config;
@@ -56,44 +58,56 @@ static void setup() throws IOException, KrbException {
freePort.setReuseAddress(true);
freePort.bind(new InetSocketAddress(0));
port = freePort.getLocalPort();
- }
- kdc = new SimpleKdcServer();
- kdc.setKdcRealm("EXAMPLE.COM");
- kdc.setKdcHost("localhost");
- kdc.setWorkDir(tempDir.toFile());
- kdc.setKdcTcpPort(port);
- kdc.init();
- kdc.start();
-
- kdc.createPrincipal("alice@EXAMPLE.COM", "alicePassword");
- kdc.createAndExportPrincipals(keytabFile.toFile(), "HTTP/localhost@EXAMPLE.COM");
-
- // initialize the ticket cache
- KrbClient krbClient = kdc.getKrbClient();
- TgtTicket tgt = krbClient.requestTgt("alice@EXAMPLE.COM", "alicePassword");
- krbClient.storeTicket(tgt, ccacheFile.toFile());
-
- // Override config so we look at the testing cache instead of the real system cache
- config = new Configuration() {
- @Override
- public AppConfigurationEntry[] getAppConfigurationEntry(String name) {
- Map opts = new HashMap<>();
- opts.put("useTicketCache", "true");
- opts.put("ticketCache", ccacheFile.toAbsolutePath().toString());
- opts.put("doNotPrompt", "true");
- return new AppConfigurationEntry[] {
- new AppConfigurationEntry(
- "com.sun.security.auth.module.Krb5LoginModule",
- AppConfigurationEntry.LoginModuleControlFlag.REQUIRED, opts)
- };
- }
- };
+ kdc = new SimpleKdcServer();
+ kdc.setKdcRealm("EXAMPLE.COM");
+ kdc.setKdcHost("localhost");
+ kdc.setWorkDir(tempDir.toFile());
+ kdc.setKdcTcpPort(port);
+ kdc.setAllowUdp(false);
+ kdc.init();
+
+ krb5PropSave = System.getProperty(KRB5_PROP);
+
+ System.setProperty(KRB5_PROP, tempDir.resolve("krb5.conf").toAbsolutePath().toString());
+
+ kdc.start();
+
+ kdc.createPrincipal("alice@EXAMPLE.COM", "alicePassword");
+ kdc.createAndExportPrincipals(keytabFile.toFile(), "HTTP/localhost@EXAMPLE.COM");
+
+ // initialize the ticket cache
+ KrbClient krbClient = kdc.getKrbClient();
+ TgtTicket tgt = krbClient.requestTgt("alice@EXAMPLE.COM", "alicePassword");
+ krbClient.storeTicket(tgt, ccacheFile.toFile());
+
+ // Override config so we look at the testing cache instead of the real system cache
+ config = new Configuration() {
+ @Override
+ public AppConfigurationEntry[] getAppConfigurationEntry(String name) {
+ Map opts = new HashMap<>();
+ opts.put("useTicketCache", "true");
+ opts.put("ticketCache", ccacheFile.toAbsolutePath().toString());
+ opts.put("doNotPrompt", "true");
+ opts.put("refreshKrb5Config", "true");
+ return new AppConfigurationEntry[] {
+ new AppConfigurationEntry(
+ "com.sun.security.auth.module.Krb5LoginModule",
+ AppConfigurationEntry.LoginModuleControlFlag.REQUIRED, opts)
+ };
+ }
+ };
+ }
}
@AfterAll
static void teardown() throws KrbException {
+ if (krb5PropSave != null) {
+ System.setProperty(KRB5_PROP, krb5PropSave);
+ } else {
+ System.clearProperty(KRB5_PROP);
+ }
kdc.stop();
FileUtils.cleanUpTestDirectory(tempDir);
}
diff --git a/http-clients/netty-nio-client/src/test/java/software/amazon/awssdk/http/nio/netty/internal/ProxyTunnelInitHandlerTest.java b/http-clients/netty-nio-client/src/test/java/software/amazon/awssdk/http/nio/netty/internal/ProxyTunnelInitHandlerTest.java
index 7828050bef26..143cc174701f 100644
--- a/http-clients/netty-nio-client/src/test/java/software/amazon/awssdk/http/nio/netty/internal/ProxyTunnelInitHandlerTest.java
+++ b/http-clients/netty-nio-client/src/test/java/software/amazon/awssdk/http/nio/netty/internal/ProxyTunnelInitHandlerTest.java
@@ -16,6 +16,7 @@
package software.amazon.awssdk.http.nio.netty.internal;
import static org.assertj.core.api.Assertions.assertThat;
+import static org.assertj.core.api.Assertions.assertThatThrownBy;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.mock;
@@ -45,6 +46,7 @@
import java.io.IOException;
import java.net.URI;
import java.util.Base64;
+import java.util.concurrent.ExecutionException;
import java.util.function.Supplier;
import org.junit.AfterClass;
import org.junit.Before;
@@ -53,6 +55,7 @@
import org.mockito.ArgumentCaptor;
import org.mockito.Mock;
import org.mockito.junit.MockitoJUnitRunner;
+import software.amazon.awssdk.http.nio.netty.ProxyAuthScheme;
/**
* Unit tests for {@link ProxyTunnelInitHandler}.
@@ -239,6 +242,23 @@ public void handlerAdded_writesRequest_withAuth() {
assertThat(requestCaptor.getValue()).isEqualTo(expectedRequest);
}
+ @Test
+ public void handlerAdded_authParamsGeneratorThrows_failsFuture() {
+ ProxyAuthGenerator authGenerator = mock(ProxyAuthGenerator.class);
+ when(authGenerator.scheme()).thenReturn(ProxyAuthScheme.BASIC);
+ when(authGenerator.generateAuthParams(any(URI.class))).thenThrow(new RuntimeException("auth generator error"));
+
+ Promise promise = GROUP.next().newPromise();
+ ProxyTunnelInitHandler handler = new ProxyTunnelInitHandler(mockChannelPool, URI.create("https://amazon.com"),
+ authGenerator,
+ REMOTE_HOST,
+ promise);
+ handler.handlerAdded(mockCtx);
+
+ assertThatThrownBy(promise::get).hasMessageContaining("Unable to send CONNECT request to proxy")
+ .hasRootCauseMessage("auth generator error");
+ }
+
private void successResponse(ProxyTunnelInitHandler handler) {
DefaultHttpResponse resp = new DefaultHttpResponse(HttpVersion.HTTP_1_1, HttpResponseStatus.OK);
handler.channelRead(mockCtx, resp);
From 4675fdacd1d4d59108004bf00f90029f2babd0d2 Mon Sep 17 00:00:00 2001
From: Dongie Agnir <261310+dagnir@users.noreply.github.com>
Date: Fri, 14 Aug 2026 11:17:10 -0700
Subject: [PATCH 6/8] Validate BASIC scheme options at build time (#7277)
---
.../http/nio/netty/ProxyConfiguration.java | 12 ++++
.../nio/netty/ProxyConfigurationTest.java | 62 +++++++++++++++++++
2 files changed, 74 insertions(+)
diff --git a/http-clients/netty-nio-client/src/main/java/software/amazon/awssdk/http/nio/netty/ProxyConfiguration.java b/http-clients/netty-nio-client/src/main/java/software/amazon/awssdk/http/nio/netty/ProxyConfiguration.java
index 2d0433b2169b..79697dbf9ebe 100644
--- a/http-clients/netty-nio-client/src/main/java/software/amazon/awssdk/http/nio/netty/ProxyConfiguration.java
+++ b/http-clients/netty-nio-client/src/main/java/software/amazon/awssdk/http/nio/netty/ProxyConfiguration.java
@@ -59,6 +59,14 @@ private ProxyConfiguration(BuilderImpl builder) {
this.password = resolvePassword(builder, proxyConfigProvider);
this.proxyAuthScheme = builder.proxyAuthScheme;
this.nonProxyHosts = resolveNonProxyHosts(builder, proxyConfigProvider);
+ validateProxyAuthConfig(proxyAuthScheme, username, password);
+ }
+
+ private static void validateProxyAuthConfig(ProxyAuthScheme proxyAuthScheme, String username, String password) {
+ if (proxyAuthScheme == ProxyAuthScheme.BASIC
+ && (StringUtils.isEmpty(username) || StringUtils.isEmpty(password))) {
+ throw new IllegalArgumentException("username and password must be configured when using BASIC proxy auth");
+ }
}
private static Set resolveNonProxyHosts(BuilderImpl builder, ProxyConfigProvider proxyConfigProvider) {
@@ -266,6 +274,10 @@ public interface Builder extends CopyableBuilder {
*
* If unset and {@link #username(String)} and {@link #password(String)} are set, the client will
* assume {@link ProxyAuthScheme#BASIC} auth.
+ *
+ * If set to {@link ProxyAuthScheme#BASIC}, {@link #username(String)} and {@link #password(String)} must also be
+ * configured (directly, or resolved from system properties or environment variables), otherwise
+ * {@link Builder#build()} throws {@link IllegalArgumentException}.
*
* @param proxyAuthScheme The auth scheme.
* @return This object for method chaining.
diff --git a/http-clients/netty-nio-client/src/test/java/software/amazon/awssdk/http/nio/netty/ProxyConfigurationTest.java b/http-clients/netty-nio-client/src/test/java/software/amazon/awssdk/http/nio/netty/ProxyConfigurationTest.java
index 36cc02d57c26..b15b4c951db7 100644
--- a/http-clients/netty-nio-client/src/test/java/software/amazon/awssdk/http/nio/netty/ProxyConfigurationTest.java
+++ b/http-clients/netty-nio-client/src/test/java/software/amazon/awssdk/http/nio/netty/ProxyConfigurationTest.java
@@ -16,6 +16,7 @@
package software.amazon.awssdk.http.nio.netty;
import static org.assertj.core.api.Assertions.assertThat;
+import static org.assertj.core.api.Assertions.assertThatThrownBy;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
@@ -147,6 +148,67 @@ void setNonProxyHostsToNull_createsEmptySet() {
assertThat(cfg.nonProxyHosts()).isEmpty();
}
+ @Test
+ void build_basicAuthSchemeWithoutCredentials_throws() {
+ ProxyConfiguration.Builder builder = ProxyConfiguration.builder()
+ .host("localhost")
+ .port(8888)
+ .proxyAuthScheme(ProxyAuthScheme.BASIC);
+
+ assertThatThrownBy(builder::build)
+ .isInstanceOf(IllegalArgumentException.class)
+ .hasMessageContaining("username and password must be configured");
+ }
+
+ @Test
+ void build_basicAuthSchemeWithoutPassword_throws() {
+ ProxyConfiguration.Builder builder = ProxyConfiguration.builder()
+ .host("localhost")
+ .port(8888)
+ .proxyAuthScheme(ProxyAuthScheme.BASIC)
+ .username("user");
+
+ assertThatThrownBy(builder::build)
+ .isInstanceOf(IllegalArgumentException.class)
+ .hasMessageContaining("username and password must be configured");
+ }
+
+ @Test
+ void build_basicAuthSchemeWithCredentials_doesNotThrow() {
+ ProxyConfiguration cfg = ProxyConfiguration.builder()
+ .host("localhost")
+ .port(8888)
+ .proxyAuthScheme(ProxyAuthScheme.BASIC)
+ .username("user")
+ .password("pass")
+ .build();
+
+ assertThat(cfg.proxyAuthScheme()).isEqualTo(ProxyAuthScheme.BASIC);
+ }
+
+ @Test
+ void build_basicAuthSchemeWithSystemPropertyCredentials_doesNotThrow() {
+ setHttpProxyProperties();
+
+ ProxyConfiguration cfg = ProxyConfiguration.builder()
+ .proxyAuthScheme(ProxyAuthScheme.BASIC)
+ .build();
+
+ assertThat(cfg.username()).isEqualTo(TEST_USER);
+ assertThat(cfg.password()).isEqualTo(TEST_PASSWORD);
+ }
+
+ @Test
+ void build_negotiateAuthSchemeWithoutCredentials_doesNotThrow() {
+ ProxyConfiguration cfg = ProxyConfiguration.builder()
+ .host("localhost")
+ .port(8888)
+ .proxyAuthScheme(ProxyAuthScheme.NEGOTIATE)
+ .build();
+
+ assertThat(cfg.proxyAuthScheme()).isEqualTo(ProxyAuthScheme.NEGOTIATE);
+ }
+
@Test
void toBuilderModified_doesNotModifySource() {
ProxyConfiguration original = allPropertiesSetConfig();
From d4ebccf993e7382f577d131bb4de10088a93f60f Mon Sep 17 00:00:00 2001
From: Dongie Agnir <261310+dagnir@users.noreply.github.com>
Date: Fri, 14 Aug 2026 12:14:16 -0700
Subject: [PATCH 7/8] Improve error message when token gen fails (#7278)
---
.../internal/NegotiateProxyAuthGenerator.java | 13 +++++++--
.../NegotiateProxyAuthGeneratorTest.java | 27 +++++++++++++++++++
2 files changed, 38 insertions(+), 2 deletions(-)
diff --git a/http-clients/netty-nio-client/src/main/java/software/amazon/awssdk/http/nio/netty/internal/NegotiateProxyAuthGenerator.java b/http-clients/netty-nio-client/src/main/java/software/amazon/awssdk/http/nio/netty/internal/NegotiateProxyAuthGenerator.java
index 7432bf45e197..41cb5a3c37ba 100644
--- a/http-clients/netty-nio-client/src/main/java/software/amazon/awssdk/http/nio/netty/internal/NegotiateProxyAuthGenerator.java
+++ b/http-clients/netty-nio-client/src/main/java/software/amazon/awssdk/http/nio/netty/internal/NegotiateProxyAuthGenerator.java
@@ -81,7 +81,12 @@ public String generateAuthParams(URI proxyEndpoint) {
return BinaryUtils.toBase64(token);
} catch (PrivilegedActionException e) {
- throw new RuntimeException("Unable to generate token", e);
+ throw new RuntimeException(String.format("Unable to generate SPNEGO token for Negotiate proxy authentication "
+ + "with '%s@%s'. This can happen when a service ticket for the proxy "
+ + "cannot be obtained from the KDC, e.g. because the ticket-granting "
+ + "ticket has expired (renew with 'kinit') or the proxy host does not "
+ + "match its Kerberos service principal name.",
+ SERVICE_NAME, proxyEndpoint.getHost()), e);
}
}
@@ -91,7 +96,11 @@ private Subject getSubject() {
loginContext.login();
return loginContext.getSubject();
} catch (LoginException e) {
- throw new RuntimeException("Unable to perform login", e);
+ throw new RuntimeException("Unable to perform Kerberos login for Negotiate proxy authentication. This "
+ + "typically means the Kerberos ticket cache is missing, expired, or not readable. "
+ + "Ensure a valid ticket-granting ticket exists (e.g., by running 'kinit'), and that "
+ + "the cache is at the expected location (see the KRB5CCNAME environment variable). "
+ + "Verify with 'klist'.", e);
}
}
diff --git a/http-clients/netty-nio-client/src/test/java/software/amazon/awssdk/http/nio/netty/internal/NegotiateProxyAuthGeneratorTest.java b/http-clients/netty-nio-client/src/test/java/software/amazon/awssdk/http/nio/netty/internal/NegotiateProxyAuthGeneratorTest.java
index 31b02867b6e7..218630a46ac4 100644
--- a/http-clients/netty-nio-client/src/test/java/software/amazon/awssdk/http/nio/netty/internal/NegotiateProxyAuthGeneratorTest.java
+++ b/http-clients/netty-nio-client/src/test/java/software/amazon/awssdk/http/nio/netty/internal/NegotiateProxyAuthGeneratorTest.java
@@ -16,6 +16,7 @@
package software.amazon.awssdk.http.nio.netty.internal;
import static org.assertj.core.api.Assertions.assertThat;
+import static org.assertj.core.api.Assertions.assertThatThrownBy;
import java.io.IOException;
import java.net.InetSocketAddress;
@@ -121,4 +122,30 @@ void generateAuthParams_configValid_successfullyGeneratesToken() {
assertThat(authGenerator.generateAuthParams(proxyEndpoint)).startsWith("YII");
}
+ @Test
+ void generateAuthParams_ticketCacheMissing_failsWithActionableMessage() {
+ Configuration missingCacheConfig = new Configuration() {
+ @Override
+ public AppConfigurationEntry[] getAppConfigurationEntry(String name) {
+ Map opts = new HashMap<>();
+ opts.put("useTicketCache", "true");
+ opts.put("ticketCache", tempDir.resolve("nonexistent-cache").toAbsolutePath().toString());
+ opts.put("doNotPrompt", "true");
+ opts.put("refreshKrb5Config", "true");
+ return new AppConfigurationEntry[] {
+ new AppConfigurationEntry(
+ "com.sun.security.auth.module.Krb5LoginModule",
+ AppConfigurationEntry.LoginModuleControlFlag.REQUIRED, opts)
+ };
+ }
+ };
+
+ NegotiateProxyAuthGenerator authGenerator = new NegotiateProxyAuthGenerator(missingCacheConfig);
+
+ assertThatThrownBy(() -> authGenerator.generateAuthParams(URI.create("https://localhost:8192")))
+ .isInstanceOf(RuntimeException.class)
+ .hasMessageContaining("kinit")
+ .hasMessageContaining("ticket cache");
+ }
+
}
From bdf60d50e1c31086cfcacb6cd0f0995ba329a223 Mon Sep 17 00:00:00 2001
From: Dongie Agnir <261310+dagnir@users.noreply.github.com>
Date: Fri, 14 Aug 2026 15:18:00 -0700
Subject: [PATCH 8/8] Remove inert mutual auth request and javadoc-only import
(#7281)
requestMutualAuth(true) asked for mutual authentication that was never
established: the proxy's response token is never consumed, so there is
nothing to verify it against. Preemptive single-leg Negotiate cannot
verify it either, so the call is dropped rather than wired up.
The com.sun.security.auth.module.Krb5LoginModule import existed only to
satisfy a javadoc {@link}. Referring to the class by name in {@code}
instead keeps the documentation while dropping a compile-time reference
to a JDK-implementation-specific class.
---
.../http/nio/netty/internal/NegotiateProxyAuthGenerator.java | 4 +---
1 file changed, 1 insertion(+), 3 deletions(-)
diff --git a/http-clients/netty-nio-client/src/main/java/software/amazon/awssdk/http/nio/netty/internal/NegotiateProxyAuthGenerator.java b/http-clients/netty-nio-client/src/main/java/software/amazon/awssdk/http/nio/netty/internal/NegotiateProxyAuthGenerator.java
index 41cb5a3c37ba..0866073946c2 100644
--- a/http-clients/netty-nio-client/src/main/java/software/amazon/awssdk/http/nio/netty/internal/NegotiateProxyAuthGenerator.java
+++ b/http-clients/netty-nio-client/src/main/java/software/amazon/awssdk/http/nio/netty/internal/NegotiateProxyAuthGenerator.java
@@ -15,7 +15,6 @@
package software.amazon.awssdk.http.nio.netty.internal;
-import com.sun.security.auth.module.Krb5LoginModule;
import java.net.URI;
import java.security.PrivilegedActionException;
import java.security.PrivilegedExceptionAction;
@@ -72,7 +71,6 @@ public String generateAuthParams(URI proxyEndpoint) {
byte[] token = Subject.doAs(subject, (PrivilegedExceptionAction) () -> {
GSSContext ctx = createGssContext(getManager(), proxyEndpoint);
try {
- ctx.requestMutualAuth(true);
return ctx.initSecContext(new byte[0], 0, 0);
} finally {
ctx.dispose();
@@ -124,7 +122,7 @@ private static GSSManager getManager() {
* Create a generic {@link Configuration} that instructs the Kerberos login module to simply look in the ticket cache, and
* not to prompt for passwords.
*
- * See javadoc for {@link Krb5LoginModule} for additional info on the configuration options.
+ * See javadoc for {@code com.sun.security.auth.module.Krb5LoginModule} for additional info on the configuration options.
*/
private static Configuration createDefaultConfig() {
return new Configuration() {