From 602e7a50e2e1dac6f4918efbbc4f21ec82913f2d Mon Sep 17 00:00:00 2001 From: Dongie Agnir Date: Tue, 11 Aug 2026 15:38:00 -0700 Subject: [PATCH 1/2] 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. --- .../http/nio/netty/ProxyConfiguration.java | 33 +++++ .../internal/AwaitCloseChannelPoolMap.java | 34 +++++ .../internal/NegotiateProxyAuthGenerator.java | 18 ++- .../internal/ProxyTunnelInitHandler.java | 12 +- .../AwaitCloseChannelPoolMapTest.java | 129 +++++++++++++++--- .../Http1TunnelConnectionPoolTest.java | 7 +- .../internal/ProxyTunnelInitHandlerTest.java | 20 +++ 7 files changed, 224 insertions(+), 29 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/internal/AwaitCloseChannelPoolMapTest.java b/http-clients/netty-nio-client/src/test/java/software/amazon/awssdk/http/nio/netty/internal/AwaitCloseChannelPoolMapTest.java index a1d4b9781f35..9eda9f0d7534 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,76 @@ 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 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 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 { + mockProxy.stop(); + kdc.stop(); + } - @After + @AfterEach public void methodTeardown() { if (channelPoolMap != null) { channelPoolMap.close(); @@ -71,6 +102,50 @@ 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.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 + 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("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 +291,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 +311,7 @@ public void usingProxy_withAuth() { .protocol(Protocol.HTTP1_1) .maxStreams(100) .sslProvider(SslProvider.OPENSSL) + .negotiateAuthConfig(negotiateAuthConfig) .build(); SimpleChannelPoolAwareChannelPool simpleChannelPoolAwareChannelPool = channelPoolMap.newPool( @@ -244,9 +323,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 +390,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/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 c90bab3a9b07b45a827ec9470e3ad094b29e5c0c Mon Sep 17 00:00:00 2001 From: Dongie Agnir Date: Wed, 12 Aug 2026 14:57:33 -0700 Subject: [PATCH 2/2] Fix test --- .../nio/netty/ProxyConfigurationTest.java | 6 +- .../AwaitCloseChannelPoolMapTest.java | 77 ++++++++++-------- .../NegotiateProxyAuthGeneratorTest.java | 78 +++++++++++-------- 3 files changed, 96 insertions(+), 65 deletions(-) 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 9eda9f0d7534..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 @@ -63,6 +63,7 @@ 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 static WireMockServer mockProxy; @@ -73,6 +74,7 @@ public class AwaitCloseChannelPoolMapTest { private static int port; private static SimpleKdcServer kdc; + private static String krb5PropSave; private static Configuration negotiateAuthConfig; @@ -88,6 +90,11 @@ public static void setup() throws Exception { @AfterAll public static void teardown() throws Exception { + if (krb5PropSave != null) { + System.setProperty(KRB5_PROP, krb5PropSave); + } else { + System.clearProperty(KRB5_PROP); + } mockProxy.stop(); kdc.stop(); } @@ -111,39 +118,45 @@ private static void setupMockKerberos() throws Exception { 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 - 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("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 + 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 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); }