Skip to content
Merged
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
6 changes: 6 additions & 0 deletions bom-internal/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -519,6 +519,12 @@
<type>pom</type>
<scope>import</scope>
</dependency>
<dependency>
<groupId>org.apache.kerby</groupId>
<artifactId>kerb-simplekdc</artifactId>
<version>${kerb-simplekdc.version}</version>
<scope>test</scope>
</dependency>
</dependencies>
</dependencyManagement>

Expand Down
5 changes: 5 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,11 @@
<artifactId>jetty-util</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.apache.kerby</groupId>
<artifactId>kerb-simplekdc</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 {
Comment thread
dagnir marked this conversation as resolved.
/**
* 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 @@ -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.
Expand Down Expand Up @@ -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);
Expand All @@ -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");
Expand Down
Original file line number Diff line number Diff line change
@@ -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));
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -138,7 +127,7 @@ private void setupChannel(Channel ch, Promise<Channel> 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<Channel> f) -> {
if (f.isSuccess()) {
Expand Down Expand Up @@ -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<Channel> tunnelInitFuture);
}
}
Original file line number Diff line number Diff line change
@@ -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.
Comment thread
dagnir marked this conversation as resolved.
*/
@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";
Comment thread
dagnir marked this conversation as resolved.
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<byte[]>) () -> {
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.
* <p>
* 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<String, String> 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)
};
}
};
}
}
Original file line number Diff line number Diff line change
@@ -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);
}
Loading
Loading