Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
18 changes: 18 additions & 0 deletions bom-internal/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -519,6 +519,24 @@
<type>pom</type>
<scope>import</scope>
</dependency>
<dependency>
<groupId>org.apache.kerby</groupId>
<artifactId>kerb-simplekdc</artifactId>
<version>${kerby.version}</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.apache.kerby</groupId>
<artifactId>kerb-client</artifactId>
<version>${kerby.version}</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.apache.kerby</groupId>
<artifactId>kerb-core</artifactId>
<version>${kerby.version}</version>
<scope>test</scope>
</dependency>
</dependencies>
</dependencyManagement>

Expand Down
15 changes: 15 additions & 0 deletions http-clients/netty-nio-client/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -233,6 +233,21 @@
<artifactId>jetty-util</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.apache.kerby</groupId>
<artifactId>kerb-simplekdc</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.apache.kerby</groupId>
<artifactId>kerb-client</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.apache.kerby</groupId>
<artifactId>kerb-core</artifactId>
<scope>test</scope>
</dependency>
</dependencies>

<dependencyManagement>
Expand Down
Original file line number Diff line number Diff line change
@@ -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;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@ public final class ProxyConfiguration implements ToCopyableBuilder<ProxyConfigur
private final int port;
private final String username;
private final String password;
private final ProxyAuthScheme proxyAuthScheme;
private final Set<String> nonProxyHosts;

private ProxyConfiguration(BuilderImpl builder) {
Expand All @@ -56,7 +57,16 @@ 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);
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<String> resolveNonProxyHosts(BuilderImpl builder, ProxyConfigProvider proxyConfigProvider) {
Expand Down Expand Up @@ -151,6 +161,13 @@ public Set<String> 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) {
Expand Down Expand Up @@ -183,6 +200,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);

}
Expand All @@ -195,6 +216,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;
}

Expand Down Expand Up @@ -247,6 +269,21 @@ public interface Builder extends CopyableBuilder<Builder, ProxyConfiguration> {
*/
Builder nonProxyHosts(Set<String> nonProxyHosts);

/**
* Configure the auth scheme to use to authenticate with the proxy.
* <p>
* If unset and {@link #username(String)} and {@link #password(String)} are set, the client will
* assume {@link ProxyAuthScheme#BASIC} auth.
* <p>
* 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.
*/
Builder proxyAuthScheme(ProxyAuthScheme proxyAuthScheme);

/**
* Set the username used to authenticate with the proxy username.
*
Expand Down Expand Up @@ -305,6 +342,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<String> nonProxyHosts;
Expand All @@ -322,6 +360,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;
}
Expand Down Expand Up @@ -354,6 +393,12 @@ public Builder nonProxyHosts(Set<String> nonProxyHosts) {
return this;
}

@Override
public Builder proxyAuthScheme(ProxyAuthScheme proxyAuthScheme) {
this.proxyAuthScheme = proxyAuthScheme;
return this;
}

@Override
public Builder username(String username) {
this.username = username;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -36,14 +36,17 @@
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;
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.
Expand Down Expand Up @@ -87,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<Builder, BootstrapProvider> createBootStrapProvider) {
this.configuration = builder.configuration;
this.protocol = builder.protocol;
Expand All @@ -99,6 +104,7 @@ private AwaitCloseChannelPoolMap(Builder builder, Function<Builder, BootstrapPro
this.bootstrapProvider = createBootStrapProvider.apply(builder);
this.sslContextProvider = new SslContextProvider(configuration, protocol, protocolNegotiation, sslProvider);
this.useNonBlockingDnsResolver = builder.useNonBlockingDnsResolver;
this.negotiateAuthConfig = builder.negotiateAuthConfig;
}

private AwaitCloseChannelPoolMap(Builder builder) {
Expand Down Expand Up @@ -143,7 +149,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);
Expand All @@ -156,6 +162,36 @@ protected SimpleChannelPoolAwareChannelPool newPool(URI key) {
return new SimpleChannelPoolAwareChannelPool(wrappedPool, tcpChannelPool);
}

private ProxyAuthGenerator resolveProxyAuthGenerator(ProxyConfiguration proxyConfiguration) {
ProxyAuthScheme proxyAuthScheme = proxyConfiguration.proxyAuthScheme();

if (proxyAuthScheme != null) {
switch (proxyAuthScheme) {
case NEGOTIATE:
return new NegotiateProxyAuthGenerator(negotiateAuthConfig);
case BASIC: {
String username = proxyConfiguration.username();
String password = proxyConfiguration.password();
if (!StringUtils.isEmpty(username) && !StringUtils.isEmpty(password)) {
return new BasicProxyAuthGenerator(username, password);
}
throw new IllegalArgumentException("username and password must be configured when using BASIC proxy auth");
}
default:
throw new RuntimeException("Unknown proxy auth scheme: " + proxyAuthScheme);
}
}

// for back-compat, BASIC if scheme not configured but username password are set
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");
Expand Down Expand Up @@ -293,6 +329,9 @@ public static class Builder {
private ProxyConfiguration proxyConfiguration;
private Boolean useNonBlockingDnsResolver;

// testing only
private Configuration negotiateAuthConfig;

private Builder() {
}

Expand Down Expand Up @@ -351,6 +390,12 @@ public Builder useNonBlockingDnsResolver(Boolean useNonBlockingDnsResolver) {
return this;
}

@SdkTestInternalApi
public Builder negotiateAuthConfig(Configuration negotiateAuthConfig) {
this.negotiateAuthConfig = negotiateAuthConfig;
return this;
}

public AwaitCloseChannelPoolMap build() {
return new AwaitCloseChannelPoolMap(this);
}
Expand Down
Original file line number Diff line number Diff line change
@@ -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.net.URI;
import java.util.Base64;
import software.amazon.awssdk.annotations.SdkInternalApi;
import software.amazon.awssdk.http.nio.netty.ProxyAuthScheme;
import software.amazon.awssdk.utils.Validate;

/**
* Auth param generator for Basic proxy authentication.
* <p>
* See <a href="https://datatracker.ietf.org/doc/html/rfc7617">https://datatracker.ietf.org/doc/html/rfc7617</a>.
*/
@SdkInternalApi
public class BasicProxyAuthGenerator implements ProxyAuthGenerator {
private final String username;
private final String password;

public BasicProxyAuthGenerator(String username, String password) {
this.username = Validate.notEmpty(username, "username must not be empty");
this.password = Validate.notEmpty(password, "password must not be empty");
}

@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));
}
}
Loading
Loading