diff --git a/.changes/next-release/bugfix-NettyNIOHTTPClient-99565b4.json b/.changes/next-release/bugfix-NettyNIOHTTPClient-99565b4.json new file mode 100644 index 000000000000..1143f66271f2 --- /dev/null +++ b/.changes/next-release/bugfix-NettyNIOHTTPClient-99565b4.json @@ -0,0 +1,5 @@ +{ + "category": "Netty NIO HTTP Client", + "type": "bugfix", + "description": "Fix a bug where SNI was not enabled in Netty NIO Async Client for TLS and caused the requests to fail of handshake_failure in some services. See [#1171](https://github.com/aws/aws-sdk-java-v2/issues/1171)" +} diff --git a/http-clients/netty-nio-client/src/main/java/software/amazon/awssdk/http/nio/netty/NettyNioAsyncHttpClient.java b/http-clients/netty-nio-client/src/main/java/software/amazon/awssdk/http/nio/netty/NettyNioAsyncHttpClient.java index 01f1a70c3c05..dd2e77334f54 100644 --- a/http-clients/netty-nio-client/src/main/java/software/amazon/awssdk/http/nio/netty/NettyNioAsyncHttpClient.java +++ b/http-clients/netty-nio-client/src/main/java/software/amazon/awssdk/http/nio/netty/NettyNioAsyncHttpClient.java @@ -193,7 +193,7 @@ protected ChannelPool newPool(URI key) { AtomicReference channelPoolRef = new AtomicReference<>(); ChannelPipelineInitializer handler = - new ChannelPipelineInitializer(protocol, sslContext, maxStreams, channelPoolRef, configuration); + new ChannelPipelineInitializer(protocol, sslContext, maxStreams, channelPoolRef, configuration, key); channelPoolRef.set(createChannelPool(bootstrap, handler)); return channelPoolRef.get(); } diff --git a/http-clients/netty-nio-client/src/main/java/software/amazon/awssdk/http/nio/netty/internal/ChannelPipelineInitializer.java b/http-clients/netty-nio-client/src/main/java/software/amazon/awssdk/http/nio/netty/internal/ChannelPipelineInitializer.java index 95c7ebc320be..399471aacd04 100644 --- a/http-clients/netty-nio-client/src/main/java/software/amazon/awssdk/http/nio/netty/internal/ChannelPipelineInitializer.java +++ b/http-clients/netty-nio-client/src/main/java/software/amazon/awssdk/http/nio/netty/internal/ChannelPipelineInitializer.java @@ -30,8 +30,12 @@ import io.netty.handler.logging.LogLevel; import io.netty.handler.logging.LoggingHandler; import io.netty.handler.ssl.SslContext; +import io.netty.handler.ssl.SslHandler; +import java.net.URI; import java.util.concurrent.CompletableFuture; import java.util.concurrent.atomic.AtomicReference; +import javax.net.ssl.SSLEngine; +import javax.net.ssl.SSLParameters; import software.amazon.awssdk.annotations.SdkInternalApi; import software.amazon.awssdk.http.Protocol; import software.amazon.awssdk.http.nio.netty.internal.http2.Http2SettingsFrameHandler; @@ -46,17 +50,20 @@ public final class ChannelPipelineInitializer extends AbstractChannelPoolHandler private final long clientMaxStreams; private final AtomicReference channelPoolRef; private final NettyConfiguration configuration; + private final URI poolKey; public ChannelPipelineInitializer(Protocol protocol, SslContext sslCtx, long clientMaxStreams, AtomicReference channelPoolRef, - NettyConfiguration configuration) { + NettyConfiguration configuration, + URI poolKey) { this.protocol = protocol; this.sslCtx = sslCtx; this.clientMaxStreams = clientMaxStreams; this.channelPoolRef = channelPoolRef; this.configuration = configuration; + this.poolKey = poolKey; } @Override @@ -64,7 +71,13 @@ public void channelCreated(Channel ch) { ch.attr(PROTOCOL_FUTURE).set(new CompletableFuture<>()); ChannelPipeline pipeline = ch.pipeline(); if (sslCtx != null) { - pipeline.addLast(sslCtx.newHandler(ch.alloc())); + + // Need to provide host and port to enable SNI + // https://github.com/netty/netty/issues/3801#issuecomment-104274440 + SslHandler sslHandler = sslCtx.newHandler(ch.alloc(), poolKey.getHost(), poolKey.getPort()); + configureSslEngine(sslHandler.engine()); + + pipeline.addLast(sslHandler); pipeline.addLast(SslCloseCompletionEventHandler.getInstance()); } @@ -87,6 +100,20 @@ public void channelCreated(Channel ch) { pipeline.addLast(new LoggingHandler(LogLevel.DEBUG)); } + /** + * Enable HostName verification. + * + * See https://netty.io/4.0/api/io/netty/handler/ssl/SslContext.html#newHandler-io.netty.buffer.ByteBufAllocator-java.lang + * .String-int- + * + * @param sslEngine the sslEngine to configure + */ + private void configureSslEngine(SSLEngine sslEngine) { + SSLParameters sslParameters = sslEngine.getSSLParameters(); + sslParameters.setEndpointIdentificationAlgorithm("HTTPS"); + sslEngine.setSSLParameters(sslParameters); + } + private void configureHttp2(Channel ch, ChannelPipeline pipeline) { ForkedHttp2MultiplexCodecBuilder codecBuilder = ForkedHttp2MultiplexCodecBuilder .forClient(new NoOpChannelInitializer()) diff --git a/services/pinpoint/src/it/java/software/amazon/awssdk/services/pinpoint/PinpointIntegTest.java b/services/pinpoint/src/it/java/software/amazon/awssdk/services/pinpoint/PinpointIntegTest.java new file mode 100644 index 000000000000..d8b4b65bcad0 --- /dev/null +++ b/services/pinpoint/src/it/java/software/amazon/awssdk/services/pinpoint/PinpointIntegTest.java @@ -0,0 +1,42 @@ +/* + * Copyright 2010-2019 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.services.pinpoint; + +import static org.assertj.core.api.Assertions.assertThat; + +import org.junit.BeforeClass; +import org.junit.Test; +import software.amazon.awssdk.regions.Region; +import software.amazon.awssdk.testutils.service.AwsTestBase; +import software.amazon.awssdk.utils.builder.SdkBuilder; + +public class PinpointIntegTest extends AwsTestBase { + + protected static PinpointAsyncClient pinpointAsyncClient; + + @BeforeClass + public static void setup() { + pinpointAsyncClient = PinpointAsyncClient.builder() + .region(Region.US_WEST_2) + .credentialsProvider(CREDENTIALS_PROVIDER_CHAIN) + .build(); + } + + @Test + public void getApps() { + assertThat(pinpointAsyncClient.getApps(SdkBuilder::build).join()).isNotNull(); + } +}