220 lines
7.2 KiB
Groovy
220 lines
7.2 KiB
Groovy
/*
|
|
* Copyright The OpenTelemetry Authors
|
|
* SPDX-License-Identifier: Apache-2.0
|
|
*/
|
|
|
|
import io.netty.bootstrap.Bootstrap
|
|
import io.netty.buffer.Unpooled
|
|
import io.netty.channel.Channel
|
|
import io.netty.channel.ChannelInitializer
|
|
import io.netty.channel.ChannelPipeline
|
|
import io.netty.channel.EventLoopGroup
|
|
import io.netty.channel.nio.NioEventLoopGroup
|
|
import io.netty.channel.socket.SocketChannel
|
|
import io.netty.channel.socket.nio.NioSocketChannel
|
|
import io.netty.handler.codec.http.DefaultFullHttpRequest
|
|
import io.netty.handler.codec.http.HttpClientCodec
|
|
import io.netty.handler.codec.http.HttpHeaders
|
|
import io.netty.handler.codec.http.HttpMethod
|
|
import io.netty.handler.codec.http.HttpVersion
|
|
import io.netty.handler.ssl.SslHandler
|
|
import io.opentelemetry.instrumentation.api.semconv.network.internal.NetworkAttributes
|
|
import io.opentelemetry.instrumentation.test.AgentInstrumentationSpecification
|
|
import io.opentelemetry.instrumentation.testing.junit.http.HttpClientTestServer
|
|
import io.opentelemetry.semconv.SemanticAttributes
|
|
import spock.lang.Shared
|
|
|
|
import javax.net.ssl.SSLContext
|
|
import javax.net.ssl.SSLHandshakeException
|
|
import java.util.concurrent.CompletableFuture
|
|
import java.util.concurrent.ExecutionException
|
|
import java.util.concurrent.TimeUnit
|
|
|
|
import static io.opentelemetry.api.trace.SpanKind.CLIENT
|
|
import static io.opentelemetry.api.trace.SpanKind.INTERNAL
|
|
import static io.opentelemetry.api.trace.SpanKind.SERVER
|
|
import static io.opentelemetry.api.trace.StatusCode.ERROR
|
|
|
|
class Netty40ClientSslTest extends AgentInstrumentationSpecification {
|
|
|
|
@Shared
|
|
HttpClientTestServer server
|
|
@Shared
|
|
EventLoopGroup eventLoopGroup
|
|
|
|
def setupSpec() {
|
|
server = new HttpClientTestServer(openTelemetry)
|
|
server.start()
|
|
eventLoopGroup = new NioEventLoopGroup()
|
|
}
|
|
|
|
def cleanupSpec() {
|
|
server.stop().get(10, TimeUnit.SECONDS)
|
|
eventLoopGroup.shutdownGracefully()
|
|
}
|
|
|
|
def "should fail SSL handshake"() {
|
|
given:
|
|
def bootstrap = createBootstrap(eventLoopGroup, ["SSLv3"])
|
|
|
|
def uri = server.resolveHttpsAddress("/success")
|
|
def request = new DefaultFullHttpRequest(HttpVersion.HTTP_1_1, HttpMethod.GET, uri.path, Unpooled.EMPTY_BUFFER)
|
|
HttpHeaders.setHost(request, uri.host + ":" + uri.port)
|
|
|
|
when:
|
|
Channel channel = null
|
|
runWithSpan("parent") {
|
|
channel = bootstrap.connect(uri.host, uri.port).sync().channel()
|
|
def result = new CompletableFuture<Integer>()
|
|
channel.pipeline().addLast(new ClientHandler(result))
|
|
channel.writeAndFlush(request).get(10, TimeUnit.SECONDS)
|
|
result.get(10, TimeUnit.SECONDS)
|
|
}
|
|
|
|
then:
|
|
Throwable thrownException = thrown()
|
|
if (thrownException instanceof ExecutionException) {
|
|
thrownException = thrownException.cause
|
|
}
|
|
|
|
assertTraces(1) {
|
|
trace(0, 3) {
|
|
span(0) {
|
|
name "parent"
|
|
status ERROR
|
|
errorEvent(thrownException.class, thrownException.message)
|
|
}
|
|
span(1) {
|
|
name "CONNECT"
|
|
kind INTERNAL
|
|
childOf span(0)
|
|
attributes {
|
|
"$SemanticAttributes.NETWORK_TRANSPORT" "tcp"
|
|
"$SemanticAttributes.NETWORK_TYPE" "ipv4"
|
|
"$SemanticAttributes.SERVER_ADDRESS" uri.host
|
|
"$SemanticAttributes.SERVER_PORT" uri.port
|
|
"$NetworkAttributes.NETWORK_PEER_PORT" uri.port
|
|
"$NetworkAttributes.NETWORK_PEER_ADDRESS" "127.0.0.1"
|
|
}
|
|
}
|
|
span(2) {
|
|
name "SSL handshake"
|
|
kind INTERNAL
|
|
childOf span(0)
|
|
status ERROR
|
|
// netty swallows the exception, it doesn't make any sense to hard-code the message
|
|
errorEventWithAnyMessage(SSLHandshakeException)
|
|
attributes {
|
|
"$SemanticAttributes.NETWORK_TRANSPORT" "tcp"
|
|
"$SemanticAttributes.NETWORK_TYPE" "ipv4"
|
|
"$NetworkAttributes.NETWORK_PEER_PORT" uri.port
|
|
"$NetworkAttributes.NETWORK_PEER_ADDRESS" "127.0.0.1"
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
cleanup:
|
|
channel?.close()?.sync()
|
|
}
|
|
|
|
def "should successfully establish SSL handshake"() {
|
|
given:
|
|
def bootstrap = createBootstrap(eventLoopGroup, ["TLSv1", "TLSv1.1", "TLSv1.2", "TLSv1.3"])
|
|
|
|
def uri = server.resolveHttpsAddress("/success")
|
|
def request = new DefaultFullHttpRequest(HttpVersion.HTTP_1_1, HttpMethod.GET, uri.path, Unpooled.EMPTY_BUFFER)
|
|
HttpHeaders.setHost(request, uri.host + ":" + uri.port)
|
|
|
|
when:
|
|
Channel channel = null
|
|
runWithSpan("parent") {
|
|
channel = bootstrap.connect(uri.host, uri.port).sync().channel()
|
|
def result = new CompletableFuture<Integer>()
|
|
channel.pipeline().addLast(new ClientHandler(result))
|
|
channel.writeAndFlush(request).get(10, TimeUnit.SECONDS)
|
|
result.get(10, TimeUnit.SECONDS)
|
|
}
|
|
|
|
then:
|
|
assertTraces(1) {
|
|
trace(0, 5) {
|
|
span(0) {
|
|
name "parent"
|
|
}
|
|
span(1) {
|
|
name "CONNECT"
|
|
kind INTERNAL
|
|
childOf span(0)
|
|
attributes {
|
|
"$SemanticAttributes.NETWORK_TRANSPORT" "tcp"
|
|
"$SemanticAttributes.NETWORK_TYPE" "ipv4"
|
|
"$SemanticAttributes.SERVER_ADDRESS" uri.host
|
|
"$SemanticAttributes.SERVER_PORT" uri.port
|
|
"$NetworkAttributes.NETWORK_PEER_PORT" uri.port
|
|
"$NetworkAttributes.NETWORK_PEER_ADDRESS" "127.0.0.1"
|
|
}
|
|
}
|
|
span(2) {
|
|
name "SSL handshake"
|
|
kind INTERNAL
|
|
childOf span(0)
|
|
attributes {
|
|
"$SemanticAttributes.NETWORK_TRANSPORT" "tcp"
|
|
"$SemanticAttributes.NETWORK_TYPE" "ipv4"
|
|
"$NetworkAttributes.NETWORK_PEER_PORT" uri.port
|
|
"$NetworkAttributes.NETWORK_PEER_ADDRESS" "127.0.0.1"
|
|
}
|
|
}
|
|
span(3) {
|
|
name "GET"
|
|
kind CLIENT
|
|
childOf(span(0))
|
|
}
|
|
span(4) {
|
|
name "test-http-server"
|
|
kind SERVER
|
|
childOf(span(3))
|
|
}
|
|
}
|
|
}
|
|
|
|
cleanup:
|
|
channel?.close()?.sync()
|
|
}
|
|
|
|
// list of default ciphers copied from netty's JdkSslContext
|
|
private static final String[] SUPPORTED_CIPHERS = [
|
|
"TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256",
|
|
"TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA",
|
|
"TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA",
|
|
"TLS_RSA_WITH_AES_128_GCM_SHA256",
|
|
"TLS_RSA_WITH_AES_128_CBC_SHA",
|
|
"TLS_RSA_WITH_AES_256_CBC_SHA",
|
|
"SSL_RSA_WITH_3DES_EDE_CBC_SHA"
|
|
]
|
|
|
|
private static Bootstrap createBootstrap(EventLoopGroup eventLoopGroup, List<String> enabledProtocols) {
|
|
def bootstrap = new Bootstrap()
|
|
bootstrap.group(eventLoopGroup)
|
|
.channel(NioSocketChannel)
|
|
.handler(new ChannelInitializer<SocketChannel>() {
|
|
@Override
|
|
protected void initChannel(SocketChannel socketChannel) throws Exception {
|
|
ChannelPipeline pipeline = socketChannel.pipeline()
|
|
|
|
def sslContext = SSLContext.getInstance("TLS")
|
|
sslContext.init(null, null, null)
|
|
def sslEngine = sslContext.createSSLEngine()
|
|
sslEngine.setUseClientMode(true)
|
|
sslEngine.setEnabledProtocols(enabledProtocols as String[])
|
|
sslEngine.setEnabledCipherSuites(SUPPORTED_CIPHERS)
|
|
pipeline.addLast(new SslHandler(sslEngine))
|
|
|
|
pipeline.addLast(new HttpClientCodec())
|
|
}
|
|
})
|
|
bootstrap
|
|
}
|
|
}
|