From 2c2d91e56f31b4bbf56a4266b9d1ae249035b6a1 Mon Sep 17 00:00:00 2001 From: Dongie Agnir <261310+dagnir@users.noreply.github.com> Date: Mon, 3 Aug 2026 12:54:59 -0700 Subject: [PATCH 1/8] Initial support for Kerberos auth (#7211) * Initial support for Kerberos auth This commit adds - A new enum `ProxyAuthScheme` that enumerates the proxy auth mechanisms supported by Netty - `ProxyAuthGenerator` (internal) that knows how to generate the auth params for its respective auth scheme - `NegotiateProxyAuthGenerator` for Kerberos * wip * Document OID --- bom-internal/pom.xml | 6 + http-clients/netty-nio-client/pom.xml | 5 + .../http/nio/netty/ProxyAuthScheme.java | 45 ++++++ .../internal/AwaitCloseChannelPoolMap.java | 13 +- .../internal/BasicProxyAuthGenerator.java | 43 ++++++ .../internal/Http1TunnelConnectionPool.java | 28 ++-- .../internal/NegotiateProxyAuthGenerator.java | 131 ++++++++++++++++++ .../netty/internal/ProxyAuthGenerator.java | 37 +++++ .../internal/ProxyTunnelInitHandler.java | 42 ++++-- .../Http1TunnelConnectionPoolTest.java | 54 +++----- .../NegotiateProxyAuthGeneratorTest.java | 110 +++++++++++++++ .../internal/ProxyTunnelInitHandlerTest.java | 7 +- pom.xml | 1 + 13 files changed, 456 insertions(+), 66 deletions(-) create mode 100644 http-clients/netty-nio-client/src/main/java/software/amazon/awssdk/http/nio/netty/ProxyAuthScheme.java 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/main/java/software/amazon/awssdk/http/nio/netty/internal/NegotiateProxyAuthGenerator.java create mode 100644 http-clients/netty-nio-client/src/main/java/software/amazon/awssdk/http/nio/netty/internal/ProxyAuthGenerator.java create mode 100644 http-clients/netty-nio-client/src/test/java/software/amazon/awssdk/http/nio/netty/internal/NegotiateProxyAuthGeneratorTest.java diff --git a/bom-internal/pom.xml b/bom-internal/pom.xml index e5b037c07c66..78c85e86a17e 100644 --- a/bom-internal/pom.xml +++ b/bom-internal/pom.xml @@ -519,6 +519,12 @@ pom import + + org.apache.kerby + kerb-simplekdc + ${kerb-simplekdc.version} + test + diff --git a/http-clients/netty-nio-client/pom.xml b/http-clients/netty-nio-client/pom.xml index 60139771232f..9988be3c5b18 100644 --- a/http-clients/netty-nio-client/pom.xml +++ b/http-clients/netty-nio-client/pom.xml @@ -233,6 +233,11 @@ jetty-util test + + org.apache.kerby + kerb-simplekdc + test + diff --git a/http-clients/netty-nio-client/src/main/java/software/amazon/awssdk/http/nio/netty/ProxyAuthScheme.java b/http-clients/netty-nio-client/src/main/java/software/amazon/awssdk/http/nio/netty/ProxyAuthScheme.java new file mode 100644 index 000000000000..05719c612c02 --- /dev/null +++ b/http-clients/netty-nio-client/src/main/java/software/amazon/awssdk/http/nio/netty/ProxyAuthScheme.java @@ -0,0 +1,45 @@ +/* + * 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; + +import software.amazon.awssdk.annotations.SdkPublicApi; + +/** + * Supported auth schemes for authentication with a proxy. + */ +@SdkPublicApi +public enum ProxyAuthScheme { + /** + * Basic authentication. + */ + BASIC("Basic"), + + /** + * Kerberos authentication. + */ + NEGOTIATE("Negotiate"), + ; + + private final String value; + + ProxyAuthScheme(String value) { + this.value = value; + } + + public String value() { + return value; + } +} 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..b9f28b6d3a59 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.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 new file mode 100644 index 000000000000..4d8912994085 --- /dev/null +++ b/http-clients/netty-nio-client/src/main/java/software/amazon/awssdk/http/nio/netty/internal/BasicProxyAuthGenerator.java @@ -0,0 +1,43 @@ +/* + * 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 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 new file mode 100644 index 000000000000..95d6156aa96f --- /dev/null +++ b/http-clients/netty-nio-client/src/main/java/software/amazon/awssdk/http/nio/netty/internal/NegotiateProxyAuthGenerator.java @@ -0,0 +1,131 @@ +/* + * 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 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; +import java.util.HashMap; +import java.util.Map; +import javax.security.auth.Subject; +import javax.security.auth.login.AppConfigurationEntry; +import javax.security.auth.login.Configuration; +import javax.security.auth.login.LoginContext; +import javax.security.auth.login.LoginException; +import org.ietf.jgss.GSSContext; +import org.ietf.jgss.GSSException; +import org.ietf.jgss.GSSManager; +import org.ietf.jgss.GSSName; +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; + +/** + * Auth generator for Kerberos. This does not login/authentication to Kerberos. It expects the ticket cache to be present and + * simply reads that to generate the token. + */ +@SdkInternalApi +public class NegotiateProxyAuthGenerator implements ProxyAuthGenerator { + // SPNEGO pseudo-mechanism OID. Lets the proxy negotiate Kerberos over HTTP "Negotiate". + // See https://www.ietf.org/rfc/rfc4178.txt for more info + private static final String OID = "1.3.6.1.5.5.2"; + private static final String SERVICE_NAME = "HTTP"; + private final Configuration config; + + public NegotiateProxyAuthGenerator() { + this(createDefaultConfig()); + } + + @SdkTestInternalApi + NegotiateProxyAuthGenerator(Configuration config) { + this.config = config; + } + + @Override + public ProxyAuthScheme scheme() { + return ProxyAuthScheme.NEGOTIATE; + } + + @Override + public String generateAuthParams(URI proxyEndpoint) { + try { + Subject subject = getSubject(); + + byte[] token = Subject.doAs(subject, (PrivilegedExceptionAction) () -> { + 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() {