From dead3d5a4821da25e1c426c777963f03cc4d8d24 Mon Sep 17 00:00:00 2001 From: Ian Smith Botsford Date: Wed, 26 Apr 2023 21:08:39 +0000 Subject: [PATCH 1/5] feat: add HTTP engine config for min TLS version --- .../1af71df0-f584-493e-85ab-2ee3e853c827.json | 8 + .../runtime/http/engine/crt/CrtHttpEngine.kt | 28 +++- .../http/engine/crt/CrtHttpEngineConfig.kt | 2 +- .../http/engine/okhttp/OkHttpEngine.kt | 24 +++ .../test-suite/build.gradle.kts | 31 ++-- .../runtime/http/test/ConnectionTest.kt | 59 ++++++++ .../kotlin/runtime/http/test/suite/Tls.kt | 17 +++ .../runtime/http/test/util/TestServer.kt | 65 -------- .../runtime/http/test/util/TestServers.kt | 139 ++++++++++++++++++ .../protocol/http-client/api/http-client.api | 3 + .../http/engine/HttpClientEngineConfig.kt | 8 + runtime/runtime-core/api/runtime-core.api | 9 ++ .../kotlin/runtime/config/TlsVersion.kt | 13 ++ .../runtime/client/config/ClientSettings.kt | 4 + 14 files changed, 323 insertions(+), 87 deletions(-) create mode 100644 .changes/1af71df0-f584-493e-85ab-2ee3e853c827.json create mode 100644 runtime/protocol/http-client-engines/test-suite/common/test/aws/smithy/kotlin/runtime/http/test/ConnectionTest.kt create mode 100644 runtime/protocol/http-client-engines/test-suite/jvm/src/aws/smithy/kotlin/runtime/http/test/suite/Tls.kt delete mode 100644 runtime/protocol/http-client-engines/test-suite/jvm/src/aws/smithy/kotlin/runtime/http/test/util/TestServer.kt create mode 100644 runtime/protocol/http-client-engines/test-suite/jvm/src/aws/smithy/kotlin/runtime/http/test/util/TestServers.kt create mode 100644 runtime/runtime-core/common/src/aws/smithy/kotlin/runtime/config/TlsVersion.kt diff --git a/.changes/1af71df0-f584-493e-85ab-2ee3e853c827.json b/.changes/1af71df0-f584-493e-85ab-2ee3e853c827.json new file mode 100644 index 000000000..bbeaa0290 --- /dev/null +++ b/.changes/1af71df0-f584-493e-85ab-2ee3e853c827.json @@ -0,0 +1,8 @@ +{ + "id": "1af71df0-f584-493e-85ab-2ee3e853c827", + "type": "feature", + "description": "Add HTTP engine configuration for minimum TLS version", + "issues": [ + "awslabs/smithy-kotlin#661" + ] +} \ No newline at end of file diff --git a/runtime/protocol/http-client-engines/http-client-engine-crt/common/src/aws/smithy/kotlin/runtime/http/engine/crt/CrtHttpEngine.kt b/runtime/protocol/http-client-engines/http-client-engine-crt/common/src/aws/smithy/kotlin/runtime/http/engine/crt/CrtHttpEngine.kt index 0e87d944e..c2f4b0e7f 100644 --- a/runtime/protocol/http-client-engines/http-client-engine-crt/common/src/aws/smithy/kotlin/runtime/http/engine/crt/CrtHttpEngine.kt +++ b/runtime/protocol/http-client-engines/http-client-engine-crt/common/src/aws/smithy/kotlin/runtime/http/engine/crt/CrtHttpEngine.kt @@ -5,8 +5,14 @@ package aws.smithy.kotlin.runtime.http.engine.crt -import aws.sdk.kotlin.crt.http.* -import aws.sdk.kotlin.crt.io.* +import aws.sdk.kotlin.crt.http.HttpClientConnectionManager +import aws.sdk.kotlin.crt.http.HttpClientConnectionManagerOptionsBuilder +import aws.sdk.kotlin.crt.http.HttpProxyAuthorizationType +import aws.sdk.kotlin.crt.http.HttpProxyOptions +import aws.sdk.kotlin.crt.io.SocketOptions +import aws.sdk.kotlin.crt.io.TlsContext +import aws.sdk.kotlin.crt.io.TlsContextOptionsBuilder +import aws.sdk.kotlin.crt.io.Uri import aws.smithy.kotlin.runtime.crt.SdkDefaultIO import aws.smithy.kotlin.runtime.http.HttpErrorCode import aws.smithy.kotlin.runtime.http.HttpException @@ -27,6 +33,8 @@ import kotlinx.coroutines.sync.Mutex import kotlinx.coroutines.sync.withLock import kotlinx.coroutines.withContext import kotlinx.coroutines.withTimeoutOrNull +import aws.sdk.kotlin.crt.io.TlsVersion as CrtTlsVersion +import aws.smithy.kotlin.runtime.config.TlsVersion as SdkTlsVersion internal const val DEFAULT_WINDOW_SIZE_BYTES: Int = 16 * 1024 internal const val CHUNK_BUFFER_SIZE: Long = 64 * 1024 @@ -44,14 +52,13 @@ public class CrtHttpEngine(public val config: CrtHttpEngineConfig) : HttpClientE } private val logger = Logger.getLogger() - private val customTlsContext: TlsContext? = if (config.alpn.isNotEmpty() && config.tlsContext == null) { + private val resolvedTlsContext: TlsContext = config.tlsContext ?: run { val options = TlsContextOptionsBuilder().apply { verifyPeer = true alpn = config.alpn.joinToString(separator = ";") { it.protocolId } + minTlsVersion = toCrtTlsVersion(config.minTlsVersion) }.build() TlsContext(options) - } else { - null } init { @@ -70,7 +77,7 @@ public class CrtHttpEngine(public val config: CrtHttpEngineConfig) : HttpClientE private val options = HttpClientConnectionManagerOptionsBuilder().apply { clientBootstrap = config.clientBootstrap ?: SdkDefaultIO.ClientBootstrap - tlsContext = customTlsContext ?: config.tlsContext ?: SdkDefaultIO.TlsContext + tlsContext = resolvedTlsContext manualWindowManagement = true socketOptions = SocketOptions( connectTimeoutMs = config.connectTimeout.inWholeMilliseconds.toInt(), @@ -129,7 +136,7 @@ public class CrtHttpEngine(public val config: CrtHttpEngineConfig) : HttpClientE // close all resources // SAFETY: shutdown is only invoked once AND only after all requests have completed and no more are coming connManagers.forEach { entry -> entry.value.close() } - customTlsContext?.close() + resolvedTlsContext.close() } private suspend fun getManagerForUri(uri: Uri, proxyConfig: ProxyConfig): HttpClientConnectionManager = mutex.withLock { @@ -151,3 +158,10 @@ public class CrtHttpEngine(public val config: CrtHttpEngineConfig) : HttpClientE } } } + +private fun toCrtTlsVersion(sdkTlsVersion: SdkTlsVersion): CrtTlsVersion = when (sdkTlsVersion) { + SdkTlsVersion.Tls1_0 -> CrtTlsVersion.TLSv1 + SdkTlsVersion.Tls1_1 -> CrtTlsVersion.TLS_V1_1 + SdkTlsVersion.Tls1_2 -> CrtTlsVersion.TLS_V1_2 + SdkTlsVersion.Tls1_3 -> CrtTlsVersion.TLS_V1_3 +} diff --git a/runtime/protocol/http-client-engines/http-client-engine-crt/common/src/aws/smithy/kotlin/runtime/http/engine/crt/CrtHttpEngineConfig.kt b/runtime/protocol/http-client-engines/http-client-engine-crt/common/src/aws/smithy/kotlin/runtime/http/engine/crt/CrtHttpEngineConfig.kt index 16e626bac..e13779b8b 100644 --- a/runtime/protocol/http-client-engines/http-client-engine-crt/common/src/aws/smithy/kotlin/runtime/http/engine/crt/CrtHttpEngineConfig.kt +++ b/runtime/protocol/http-client-engines/http-client-engine-crt/common/src/aws/smithy/kotlin/runtime/http/engine/crt/CrtHttpEngineConfig.kt @@ -53,7 +53,7 @@ public class CrtHttpEngineConfig private constructor(builder: Builder) : HttpCli public var clientBootstrap: ClientBootstrap? = null /** - * Set the TLS context to use. By default it is a shared instance. + * Set the TLS context to use. */ public var tlsContext: TlsContext? = null diff --git a/runtime/protocol/http-client-engines/http-client-engine-okhttp/jvm/src/aws/smithy/kotlin/runtime/http/engine/okhttp/OkHttpEngine.kt b/runtime/protocol/http-client-engines/http-client-engine-okhttp/jvm/src/aws/smithy/kotlin/runtime/http/engine/okhttp/OkHttpEngine.kt index 9eede1bc8..73166bf43 100644 --- a/runtime/protocol/http-client-engines/http-client-engine-okhttp/jvm/src/aws/smithy/kotlin/runtime/http/engine/okhttp/OkHttpEngine.kt +++ b/runtime/protocol/http-client-engines/http-client-engine-okhttp/jvm/src/aws/smithy/kotlin/runtime/http/engine/okhttp/OkHttpEngine.kt @@ -17,6 +17,8 @@ import kotlinx.coroutines.job import okhttp3.* import java.util.concurrent.TimeUnit import kotlin.time.toJavaDuration +import aws.smithy.kotlin.runtime.config.TlsVersion as SdkTlsVersion +import okhttp3.TlsVersion as OkHttpTlsVersion /** * [aws.smithy.kotlin.runtime.http.engine.HttpClientEngine] based on OkHttp. @@ -69,6 +71,8 @@ private fun OkHttpEngineConfig.buildClient(): OkHttpClient { followRedirects(false) followSslRedirects(false) + connectionSpecs(listOf(minTlsConnectionSpec(config.minTlsVersion), ConnectionSpec.CLEARTEXT)) + // Transient connection errors are handled by retry strategy (exceptions are wrapped and marked retryable // appropriately internally). We don't want inner retry logic that inflates the number of retries. retryOnConnectionFailure(false) @@ -115,3 +119,23 @@ private fun OkHttpEngineConfig.buildClient(): OkHttpClient { dns(OkHttpDns(config.hostResolver)) }.build() } + +private fun minTlsConnectionSpec(minVersion: SdkTlsVersion): ConnectionSpec { + val okHttpTlsVersions = SdkTlsVersion + .values() + .filter { it >= minVersion } + .sortedDescending() // Prioritize higher TLS versions first + .map(::toOkHttpTlsVersion) + .toTypedArray() + return ConnectionSpec + .Builder(ConnectionSpec.MODERN_TLS) + .tlsVersions(*okHttpTlsVersions) + .build() +} + +private fun toOkHttpTlsVersion(sdkTlsVersion: SdkTlsVersion): OkHttpTlsVersion = when (sdkTlsVersion) { + SdkTlsVersion.Tls1_0 -> OkHttpTlsVersion.TLS_1_0 + SdkTlsVersion.Tls1_1 -> OkHttpTlsVersion.TLS_1_1 + SdkTlsVersion.Tls1_2 -> OkHttpTlsVersion.TLS_1_2 + SdkTlsVersion.Tls1_3 -> OkHttpTlsVersion.TLS_1_3 +} diff --git a/runtime/protocol/http-client-engines/test-suite/build.gradle.kts b/runtime/protocol/http-client-engines/test-suite/build.gradle.kts index 52432e8f7..1500ff293 100644 --- a/runtime/protocol/http-client-engines/test-suite/build.gradle.kts +++ b/runtime/protocol/http-client-engines/test-suite/build.gradle.kts @@ -2,8 +2,8 @@ * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. * SPDX-License-Identifier: Apache-2.0 */ -import java.io.* -import java.net.* +import java.io.Closeable +import java.net.URLClassLoader description = "Common HTTP Client Engine Test Suite" extra["moduleName"] = "aws.smithy.kotlin.http.test" @@ -21,14 +21,17 @@ kotlin { dependencies { implementation(project(":runtime:protocol:http-client")) implementation(project(":runtime:protocol:http-test")) + implementation("org.jetbrains.kotlinx:kotlinx-coroutines-core:$coroutinesVersion") implementation("org.jetbrains.kotlinx:kotlinx-coroutines-test:$coroutinesVersion") implementation(project(":runtime:testing")) + + implementation("io.ktor:ktor-network-tls-certificates:$ktorVersion") } } jvmMain { dependencies { - implementation("io.ktor:ktor-server-cio:$ktorVersion") + implementation("io.ktor:ktor-server-jetty:$ktorVersion") implementation(project(":runtime:protocol:http-client-engines:http-client-engine-default")) implementation(project(":runtime:protocol:http-client-engines:http-client-engine-crt")) @@ -50,7 +53,7 @@ kotlin { } } -open class LocalTestServer : DefaultTask() { +open class LocalTestServers : DefaultTask() { @Internal var server: Closeable? = null private set @@ -64,16 +67,16 @@ open class LocalTestServer : DefaultTask() { @TaskAction fun exec() { try { - println("[TestServer] start") + println("[TestServers] start") val urlClassLoaderSource = classpath.map { file -> file.toURI().toURL() }.toTypedArray() val loader = URLClassLoader(urlClassLoaderSource, ClassLoader.getSystemClassLoader()) val mainClass = loader.loadClass(main) - val main = mainClass.getMethod("startServer") + val main = mainClass.getMethod("startServers") server = main.invoke(null) as Closeable - println("[TestServer] started") + println("[TestServers] started") } catch (cause: Throwable) { - println("[TestServer] failed: ${cause.message}") + println("[TestServers] failed: ${cause.message}") throw cause } } @@ -81,17 +84,17 @@ open class LocalTestServer : DefaultTask() { fun stop() { if (server != null) { server?.close() - println("[TestServer] stop") + println("[TestServers] stop") } } } val osName = System.getProperty("os.name") -val startTestServer = task("startTestServer") { +val startTestServers = task("startTestServers") { dependsOn(tasks["jvmJar"]) - main = "aws.smithy.kotlin.runtime.http.test.util.TestServerKt" + main = "aws.smithy.kotlin.runtime.http.test.util.TestServersKt" val kotlinCompilation = kotlin.targets.getByName("jvm").compilations["test"] classpath = (kotlinCompilation as org.jetbrains.kotlin.gradle.plugin.KotlinCompilationToRunnableFiles<*>).runtimeDependencyFiles } @@ -99,7 +102,7 @@ val startTestServer = task("startTestServer") { val testTasks = listOf("allTests", "jvmTest") .forEach { tasks.named(it) { - dependsOn(startTestServer) + dependsOn(startTestServers) } } @@ -111,5 +114,5 @@ tasks.jvmTest { } gradle.buildFinished { - startTestServer.stop() -} \ No newline at end of file + startTestServers.stop() +} diff --git a/runtime/protocol/http-client-engines/test-suite/common/test/aws/smithy/kotlin/runtime/http/test/ConnectionTest.kt b/runtime/protocol/http-client-engines/test-suite/common/test/aws/smithy/kotlin/runtime/http/test/ConnectionTest.kt new file mode 100644 index 000000000..49bdfc7b2 --- /dev/null +++ b/runtime/protocol/http-client-engines/test-suite/common/test/aws/smithy/kotlin/runtime/http/test/ConnectionTest.kt @@ -0,0 +1,59 @@ +/* + * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + * SPDX-License-Identifier: Apache-2.0 + */ +package aws.smithy.kotlin.runtime.http.test + +// TODO Finish once we have HTTP engine support for client certificates +/* +private const val TLS1_0_URL = "https://localhost:${TestServer.TlsV1.port}/" +private const val TLS1_1_URL = "https://localhost:${TestServer.TlsV1_1.port}/" +private const val TLS1_2_URL = "https://localhost:${TestServer.TlsV1_2.port}/" +private const val TLS1_3_URL = "https://localhost:${TestServer.TlsV1_3.port}/" + +class ConnectionTest : AbstractEngineTest() { + private fun testMinTlsVersion(version: TlsVersion, failUrl: String?, succeedUrl: String) { + testEngines { + engineConfig { + minTlsVersion = version + } + + failUrl?.let { + test { env, client -> + val req = HttpRequest { + testSetup(env) + url(Url.parse(failUrl)) + } + + val call = client.call(req) + call.complete() + assertEquals(HttpStatusCode.UpgradeRequired, call.response.status) + } + } + + test { env, client -> + val req = HttpRequest { + testSetup(env) + url(Url.parse(succeedUrl)) + } + + val call = client.call(req) + call.complete() + assertEquals(HttpStatusCode.OK, call.response.status) + } + } + } + + @Test + fun testMinTls1_0() = testMinTlsVersion(TlsVersion.Tls1_0, null, TLS1_0_URL) + + @Test + fun testMinTls1_1() = testMinTlsVersion(TlsVersion.Tls1_1, TLS1_0_URL, TLS1_1_URL) + + @Test + fun testMinTls1_2() = testMinTlsVersion(TlsVersion.Tls1_2, TLS1_1_URL, TLS1_2_URL) + + @Test + fun testMinTls1_3() = testMinTlsVersion(TlsVersion.Tls1_3, TLS1_2_URL, TLS1_3_URL) +} +*/ diff --git a/runtime/protocol/http-client-engines/test-suite/jvm/src/aws/smithy/kotlin/runtime/http/test/suite/Tls.kt b/runtime/protocol/http-client-engines/test-suite/jvm/src/aws/smithy/kotlin/runtime/http/test/suite/Tls.kt new file mode 100644 index 000000000..5125f434e --- /dev/null +++ b/runtime/protocol/http-client-engines/test-suite/jvm/src/aws/smithy/kotlin/runtime/http/test/suite/Tls.kt @@ -0,0 +1,17 @@ +/* + * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + * SPDX-License-Identifier: Apache-2.0 + */ +package aws.smithy.kotlin.runtime.http.test.suite + +import io.ktor.server.application.* +import io.ktor.server.response.* +import io.ktor.server.routing.* + +internal fun Application.tlsTests() { + routing { + get("/tlsVerification") { + call.respondText("OK") + } + } +} diff --git a/runtime/protocol/http-client-engines/test-suite/jvm/src/aws/smithy/kotlin/runtime/http/test/util/TestServer.kt b/runtime/protocol/http-client-engines/test-suite/jvm/src/aws/smithy/kotlin/runtime/http/test/util/TestServer.kt deleted file mode 100644 index 836ab2f88..000000000 --- a/runtime/protocol/http-client-engines/test-suite/jvm/src/aws/smithy/kotlin/runtime/http/test/util/TestServer.kt +++ /dev/null @@ -1,65 +0,0 @@ -/* - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * SPDX-License-Identifier: Apache-2.0 - */ - -package aws.smithy.kotlin.runtime.http.test.util - -import aws.smithy.kotlin.runtime.http.test.suite.concurrentTests -import aws.smithy.kotlin.runtime.http.test.suite.downloadTests -import aws.smithy.kotlin.runtime.http.test.suite.uploadTests -import io.ktor.server.application.* -import io.ktor.server.cio.* -import io.ktor.server.engine.* -import io.ktor.server.request.* -import io.ktor.server.response.* -import io.ktor.server.routing.* -import redirectTests -import java.io.Closeable -import java.util.concurrent.TimeUnit - -private const val DEFAULT_PORT = 8082 - -private class Resources : Closeable { - private val resources = mutableListOf() - - fun add(resource: Closeable) { - resources.add(resource) - } - - override fun close() { - resources.forEach(Closeable::close) - } -} - -/** - * Entry point used by Gradle to startup the shared local test server - */ -internal fun startServer(): Closeable { - val servers = Resources() - println("starting local server for HTTP client engine test suite") - - try { - val server = embeddedServer(CIO, DEFAULT_PORT) { - testRoutes() - }.start() - - servers.add(Closeable { server.stop(0L, 0L, TimeUnit.MILLISECONDS) }) - - // ensure server is up and listening before tests run - Thread.sleep(500) - } catch (ex: Exception) { - servers.close() - throw ex - } - - return servers -} - -// configure the test server routes -internal fun Application.testRoutes() { - redirectTests() - downloadTests() - uploadTests() - concurrentTests() -} diff --git a/runtime/protocol/http-client-engines/test-suite/jvm/src/aws/smithy/kotlin/runtime/http/test/util/TestServers.kt b/runtime/protocol/http-client-engines/test-suite/jvm/src/aws/smithy/kotlin/runtime/http/test/util/TestServers.kt new file mode 100644 index 000000000..400d75f01 --- /dev/null +++ b/runtime/protocol/http-client-engines/test-suite/jvm/src/aws/smithy/kotlin/runtime/http/test/util/TestServers.kt @@ -0,0 +1,139 @@ +/* + * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + * SPDX-License-Identifier: Apache-2.0 + */ + +package aws.smithy.kotlin.runtime.http.test.util + +import aws.smithy.kotlin.runtime.http.test.suite.concurrentTests +import aws.smithy.kotlin.runtime.http.test.suite.downloadTests +import aws.smithy.kotlin.runtime.http.test.suite.uploadTests +import io.ktor.server.application.* +import io.ktor.server.engine.* +import io.ktor.server.jetty.* +import redirectTests +import java.io.Closeable +import java.util.concurrent.TimeUnit + +// TODO Finish once we have HTTP engine support for client certificates +/* +public const val keyStorePath: String = "build/keystore.jks" + +public val keyStoreFile: File by lazy { File(keyStorePath) } + +public val keyStore: KeyStore by lazy { + val keyStore = buildKeyStore { + certificate("TestServerCert") { + password = "password" + domains = listOf("127.0.0.1", "0.0.0.0", "localhost") + } + } + keyStore.saveToFile(keyStoreFile, "password") + + keyStore +} + */ + +public enum class TestServer( + public val port: Int, + public val type: ConnectorType, + public val protocolName: String?, + public val initializer: Application.() -> Unit, +) { + Default(8082, ConnectorType.HTTP, null, Application::testRoutes), + + // TODO Finish once we have HTTP engine support for client certificates + /* + TlsV1(8083, ConnectorType.HTTPS, "TLSv1", Application::tlsRoutes), + TlsV1_1(8084, ConnectorType.HTTPS, "TLSv1.1", Application::tlsRoutes), + TlsV1_2(8085, ConnectorType.HTTPS, "TLSv1.2", Application::tlsRoutes), + TlsV1_3(8086, ConnectorType.HTTPS, "TLSv1.3", Application::tlsRoutes), + */ +} + +private class Resources : Closeable { + private val resources = mutableListOf() + + fun add(resource: Closeable) { + resources.add(resource) + } + + override fun close() { + resources.forEach(Closeable::close) + } + + val size: Int = resources.size +} + +/** + * Entry point used by Gradle to startup the shared local test server + */ +internal fun startServers(): Closeable { + val servers = Resources() + println("Starting local servers for HTTP client engine test suite...") + + try { + TestServer + .values() + .forEach { testServer -> + val runningInstance = tlsServer(testServer) + servers.add { runningInstance.stop(0L, 0L, TimeUnit.MILLISECONDS) } + } + + // ensure servers are up and listening before tests run + Thread.sleep(1000) + } catch (ex: Exception) { + servers.close() + throw ex + } + + println("...all ${servers.size} servers started!") + + return servers +} + +private fun tlsServer(instance: TestServer): ApplicationEngine { + val description = "${instance.type.name} server on port ${instance.port}" + println(" Starting $description...") + val environment = applicationEngineEnvironment { + when (instance.type) { + ConnectorType.HTTP -> connector { port = instance.port } + + // TODO Finish once we have HTTP engine support for client certificates + /* + ConnectorType.HTTPS -> sslConnector( + keyStore = keyStore, + keyAlias = keyStore.aliases().nextElement(), + keyStorePassword = { "password".toCharArray() }, + privateKeyPassword = { "password".toCharArray() }, + ) { + port = instance.port + keyStorePath = keyStoreFile + enabledProtocols = instance.protocolName?.let(::listOf) + } + */ + } + modules.add(instance.initializer) + } + return try { + embeddedServer(Jetty, environment).start() + } catch (e: Exception) { + println(" ...$description failed to start with exception $e") + throw e + } +} + +// configure the test server routes +internal fun Application.testRoutes() { + redirectTests() + downloadTests() + uploadTests() + concurrentTests() +} + +// TODO Finish once we have HTTP engine support for client certificates +/* +internal fun Application.tlsRoutes() { + tlsTests() +} +*/ diff --git a/runtime/protocol/http-client/api/http-client.api b/runtime/protocol/http-client/api/http-client.api index 5ce97ecd8..7b868b877 100644 --- a/runtime/protocol/http-client/api/http-client.api +++ b/runtime/protocol/http-client/api/http-client.api @@ -55,6 +55,7 @@ public class aws/smithy/kotlin/runtime/http/engine/HttpClientEngineConfig { public final fun getConnectionAcquireTimeout-UwyO8pc ()J public final fun getConnectionIdleTimeout-UwyO8pc ()J public final fun getMaxConnections-pVg5ArA ()I + public final fun getMinTlsVersion ()Laws/smithy/kotlin/runtime/config/TlsVersion; public final fun getProxySelector ()Laws/smithy/kotlin/runtime/http/engine/ProxySelector; public final fun getSocketReadTimeout-UwyO8pc ()J public final fun getSocketWriteTimeout-UwyO8pc ()J @@ -67,6 +68,7 @@ public class aws/smithy/kotlin/runtime/http/engine/HttpClientEngineConfig$Builde public final fun getConnectionAcquireTimeout-UwyO8pc ()J public final fun getConnectionIdleTimeout-UwyO8pc ()J public final fun getMaxConnections-pVg5ArA ()I + public final fun getMinTlsVersion ()Laws/smithy/kotlin/runtime/config/TlsVersion; public final fun getProxySelector ()Laws/smithy/kotlin/runtime/http/engine/ProxySelector; public final fun getSocketReadTimeout-UwyO8pc ()J public final fun getSocketWriteTimeout-UwyO8pc ()J @@ -75,6 +77,7 @@ public class aws/smithy/kotlin/runtime/http/engine/HttpClientEngineConfig$Builde public final fun setConnectionAcquireTimeout-LRDsOJo (J)V public final fun setConnectionIdleTimeout-LRDsOJo (J)V public final fun setMaxConnections-WZ4Q5Ns (I)V + public final fun setMinTlsVersion (Laws/smithy/kotlin/runtime/config/TlsVersion;)V public final fun setProxySelector (Laws/smithy/kotlin/runtime/http/engine/ProxySelector;)V public final fun setSocketReadTimeout-LRDsOJo (J)V public final fun setSocketWriteTimeout-LRDsOJo (J)V diff --git a/runtime/protocol/http-client/common/src/aws/smithy/kotlin/runtime/http/engine/HttpClientEngineConfig.kt b/runtime/protocol/http-client/common/src/aws/smithy/kotlin/runtime/http/engine/HttpClientEngineConfig.kt index 8cb71ba97..5864469df 100644 --- a/runtime/protocol/http-client/common/src/aws/smithy/kotlin/runtime/http/engine/HttpClientEngineConfig.kt +++ b/runtime/protocol/http-client/common/src/aws/smithy/kotlin/runtime/http/engine/HttpClientEngineConfig.kt @@ -5,6 +5,7 @@ package aws.smithy.kotlin.runtime.http.engine import aws.smithy.kotlin.runtime.InternalApi +import aws.smithy.kotlin.runtime.config.TlsVersion import aws.smithy.kotlin.runtime.net.HostResolver import kotlin.time.Duration import kotlin.time.Duration.Companion.seconds @@ -72,6 +73,8 @@ public open class HttpClientEngineConfig constructor(builder: Builder) { */ public val proxySelector: ProxySelector = builder.proxySelector + public val minTlsVersion: TlsVersion = builder.minTlsVersion + /** * The host name resolver (DNS) */ @@ -140,6 +143,11 @@ public open class HttpClientEngineConfig constructor(builder: Builder) { */ public var proxySelector: ProxySelector = EnvironmentProxySelector() + /** + * Set the minimum allowed TLS version for HTTP connections. + */ + public var minTlsVersion: TlsVersion = TlsVersion.Tls1_2 + /** * The host name resolver (DNS) to be used by the client */ diff --git a/runtime/runtime-core/api/runtime-core.api b/runtime/runtime-core/api/runtime-core.api index c29d1acd7..c6b24d7af 100644 --- a/runtime/runtime-core/api/runtime-core.api +++ b/runtime/runtime-core/api/runtime-core.api @@ -69,6 +69,15 @@ public final class aws/smithy/kotlin/runtime/ServiceException$ErrorType : java/l public final class aws/smithy/kotlin/runtime/config/EnvironmentSettingKt { } +public final class aws/smithy/kotlin/runtime/config/TlsVersion : java/lang/Enum { + public static final field Tls1_0 Laws/smithy/kotlin/runtime/config/TlsVersion; + public static final field Tls1_1 Laws/smithy/kotlin/runtime/config/TlsVersion; + public static final field Tls1_2 Laws/smithy/kotlin/runtime/config/TlsVersion; + public static final field Tls1_3 Laws/smithy/kotlin/runtime/config/TlsVersion; + public static fun valueOf (Ljava/lang/String;)Laws/smithy/kotlin/runtime/config/TlsVersion; + public static fun values ()[Laws/smithy/kotlin/runtime/config/TlsVersion; +} + public final class aws/smithy/kotlin/runtime/content/ByteArrayContent : aws/smithy/kotlin/runtime/content/ByteStream$Buffer { public fun ([B)V public fun bytes ()[B diff --git a/runtime/runtime-core/common/src/aws/smithy/kotlin/runtime/config/TlsVersion.kt b/runtime/runtime-core/common/src/aws/smithy/kotlin/runtime/config/TlsVersion.kt new file mode 100644 index 000000000..c7e46bbf7 --- /dev/null +++ b/runtime/runtime-core/common/src/aws/smithy/kotlin/runtime/config/TlsVersion.kt @@ -0,0 +1,13 @@ +/* + * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + * SPDX-License-Identifier: Apache-2.0 + */ +package aws.smithy.kotlin.runtime.config + +@Suppress("ktlint:enum-entry-name-case") +public enum class TlsVersion { + Tls1_0, + Tls1_1, + Tls1_2, + Tls1_3, +} diff --git a/runtime/smithy-client/common/src/aws/smithy/kotlin/runtime/client/config/ClientSettings.kt b/runtime/smithy-client/common/src/aws/smithy/kotlin/runtime/client/config/ClientSettings.kt index bd2259e4e..092383e6f 100644 --- a/runtime/smithy-client/common/src/aws/smithy/kotlin/runtime/client/config/ClientSettings.kt +++ b/runtime/smithy-client/common/src/aws/smithy/kotlin/runtime/client/config/ClientSettings.kt @@ -6,6 +6,7 @@ package aws.smithy.kotlin.runtime.client.config import aws.smithy.kotlin.runtime.InternalApi import aws.smithy.kotlin.runtime.config.EnvironmentSetting +import aws.smithy.kotlin.runtime.config.TlsVersion import aws.smithy.kotlin.runtime.config.enumEnvSetting import aws.smithy.kotlin.runtime.config.intEnvSetting @@ -17,6 +18,9 @@ public object ClientSettings { */ public val MaxAttempts: EnvironmentSetting = intEnvSetting("sdk.maxAttempts", "SDK_MAX_ATTEMPTS") + public val MinTlsVersion: EnvironmentSetting = + enumEnvSetting("SDK_MIN_TLS", "sdk.minTls").orElse(TlsVersion.Tls1_2) + /** * Which RetryMode to use for the default RetryPolicy, when one is not specified at the client level. */ From 90c905fba2d20727b4ad8b535ce9a9c0fd33a161 Mon Sep 17 00:00:00 2001 From: Ian Smith Botsford Date: Wed, 26 Apr 2023 22:21:27 +0000 Subject: [PATCH 2/5] add missing KDocs; remove unnecessary coroutines dependency --- .../test-suite/build.gradle.kts | 1 - .../smithy/kotlin/runtime/config/TlsVersion.kt | 18 ++++++++++++++++++ .../runtime/client/config/ClientSettings.kt | 3 +++ 3 files changed, 21 insertions(+), 1 deletion(-) diff --git a/runtime/protocol/http-client-engines/test-suite/build.gradle.kts b/runtime/protocol/http-client-engines/test-suite/build.gradle.kts index 1500ff293..add4cefac 100644 --- a/runtime/protocol/http-client-engines/test-suite/build.gradle.kts +++ b/runtime/protocol/http-client-engines/test-suite/build.gradle.kts @@ -21,7 +21,6 @@ kotlin { dependencies { implementation(project(":runtime:protocol:http-client")) implementation(project(":runtime:protocol:http-test")) - implementation("org.jetbrains.kotlinx:kotlinx-coroutines-core:$coroutinesVersion") implementation("org.jetbrains.kotlinx:kotlinx-coroutines-test:$coroutinesVersion") implementation(project(":runtime:testing")) diff --git a/runtime/runtime-core/common/src/aws/smithy/kotlin/runtime/config/TlsVersion.kt b/runtime/runtime-core/common/src/aws/smithy/kotlin/runtime/config/TlsVersion.kt index c7e46bbf7..ca3425807 100644 --- a/runtime/runtime-core/common/src/aws/smithy/kotlin/runtime/config/TlsVersion.kt +++ b/runtime/runtime-core/common/src/aws/smithy/kotlin/runtime/config/TlsVersion.kt @@ -4,10 +4,28 @@ */ package aws.smithy.kotlin.runtime.config +/** + * Describes a version of TLS + */ @Suppress("ktlint:enum-entry-name-case") public enum class TlsVersion { + /** + * TLS version 1 + */ Tls1_0, + + /** + * TLS version 1.1 + */ Tls1_1, + + /** + * TLS version 1.2 + */ Tls1_2, + + /** + * TLS version 1.3 + */ Tls1_3, } diff --git a/runtime/smithy-client/common/src/aws/smithy/kotlin/runtime/client/config/ClientSettings.kt b/runtime/smithy-client/common/src/aws/smithy/kotlin/runtime/client/config/ClientSettings.kt index 092383e6f..47e34335a 100644 --- a/runtime/smithy-client/common/src/aws/smithy/kotlin/runtime/client/config/ClientSettings.kt +++ b/runtime/smithy-client/common/src/aws/smithy/kotlin/runtime/client/config/ClientSettings.kt @@ -18,6 +18,9 @@ public object ClientSettings { */ public val MaxAttempts: EnvironmentSetting = intEnvSetting("sdk.maxAttempts", "SDK_MAX_ATTEMPTS") + /** + * Specifies the minimum acceptable version of TLS to use when connecting to service endpoints. + */ public val MinTlsVersion: EnvironmentSetting = enumEnvSetting("SDK_MIN_TLS", "sdk.minTls").orElse(TlsVersion.Tls1_2) From db5e0cda81c279504ad791b46f42b07a5ffca7ab Mon Sep 17 00:00:00 2001 From: Ian Smith Botsford Date: Tue, 2 May 2023 17:05:35 +0000 Subject: [PATCH 3/5] refactor TLS min version into TlsContext in generic config; remove engine-specific TlsContext from CRT engine config --- .../api/http-client-engine-crt.api | 4 - .../runtime/http/engine/crt/CrtHttpEngine.kt | 31 ++++---- .../http/engine/crt/CrtHttpEngineConfig.kt | 11 --- .../http/engine/okhttp/OkHttpEngine.kt | 19 +++-- .../protocol/http-client/api/http-client.api | 29 +++++-- .../http/engine/HttpClientEngineConfig.kt | 60 ++++----------- .../kotlin/runtime/http/engine/TlsContext.kt | 77 +++++++++++++++++++ runtime/runtime-core/api/runtime-core.api | 8 +- .../runtime/config/EnvironmentSetting.kt | 4 +- .../kotlin/runtime/config/TlsVersion.kt | 9 +-- .../runtime/client/config/ClientSettings.kt | 3 +- 11 files changed, 155 insertions(+), 100 deletions(-) create mode 100644 runtime/protocol/http-client/common/src/aws/smithy/kotlin/runtime/http/engine/TlsContext.kt diff --git a/runtime/protocol/http-client-engines/http-client-engine-crt/api/http-client-engine-crt.api b/runtime/protocol/http-client-engines/http-client-engine-crt/api/http-client-engine-crt.api index 7fea46316..c43c319dc 100644 --- a/runtime/protocol/http-client-engines/http-client-engine-crt/api/http-client-engine-crt.api +++ b/runtime/protocol/http-client-engines/http-client-engine-crt/api/http-client-engine-crt.api @@ -15,19 +15,15 @@ public final class aws/smithy/kotlin/runtime/http/engine/crt/CrtHttpEngineConfig public synthetic fun (Laws/smithy/kotlin/runtime/http/engine/crt/CrtHttpEngineConfig$Builder;Lkotlin/jvm/internal/DefaultConstructorMarker;)V public final fun getClientBootstrap ()Laws/sdk/kotlin/crt/io/ClientBootstrap; public final fun getInitialWindowSizeBytes ()I - public final fun getTlsContext ()Laws/sdk/kotlin/crt/io/TlsContext; public final fun setClientBootstrap (Laws/sdk/kotlin/crt/io/ClientBootstrap;)V - public final fun setTlsContext (Laws/sdk/kotlin/crt/io/TlsContext;)V } public final class aws/smithy/kotlin/runtime/http/engine/crt/CrtHttpEngineConfig$Builder : aws/smithy/kotlin/runtime/http/engine/HttpClientEngineConfig$Builder { public fun ()V public final fun getClientBootstrap ()Laws/sdk/kotlin/crt/io/ClientBootstrap; public final fun getInitialWindowSizeBytes ()I - public final fun getTlsContext ()Laws/sdk/kotlin/crt/io/TlsContext; public final fun setClientBootstrap (Laws/sdk/kotlin/crt/io/ClientBootstrap;)V public final fun setInitialWindowSizeBytes (I)V - public final fun setTlsContext (Laws/sdk/kotlin/crt/io/TlsContext;)V } public final class aws/smithy/kotlin/runtime/http/engine/crt/CrtHttpEngineConfig$Companion { diff --git a/runtime/protocol/http-client-engines/http-client-engine-crt/common/src/aws/smithy/kotlin/runtime/http/engine/crt/CrtHttpEngine.kt b/runtime/protocol/http-client-engines/http-client-engine-crt/common/src/aws/smithy/kotlin/runtime/http/engine/crt/CrtHttpEngine.kt index c2f4b0e7f..a4818a831 100644 --- a/runtime/protocol/http-client-engines/http-client-engine-crt/common/src/aws/smithy/kotlin/runtime/http/engine/crt/CrtHttpEngine.kt +++ b/runtime/protocol/http-client-engines/http-client-engine-crt/common/src/aws/smithy/kotlin/runtime/http/engine/crt/CrtHttpEngine.kt @@ -10,7 +10,6 @@ import aws.sdk.kotlin.crt.http.HttpClientConnectionManagerOptionsBuilder import aws.sdk.kotlin.crt.http.HttpProxyAuthorizationType import aws.sdk.kotlin.crt.http.HttpProxyOptions import aws.sdk.kotlin.crt.io.SocketOptions -import aws.sdk.kotlin.crt.io.TlsContext import aws.sdk.kotlin.crt.io.TlsContextOptionsBuilder import aws.sdk.kotlin.crt.io.Uri import aws.smithy.kotlin.runtime.crt.SdkDefaultIO @@ -33,6 +32,7 @@ import kotlinx.coroutines.sync.Mutex import kotlinx.coroutines.sync.withLock import kotlinx.coroutines.withContext import kotlinx.coroutines.withTimeoutOrNull +import aws.sdk.kotlin.crt.io.TlsContext as CrtTlsContext import aws.sdk.kotlin.crt.io.TlsVersion as CrtTlsVersion import aws.smithy.kotlin.runtime.config.TlsVersion as SdkTlsVersion @@ -52,14 +52,14 @@ public class CrtHttpEngine(public val config: CrtHttpEngineConfig) : HttpClientE } private val logger = Logger.getLogger() - private val resolvedTlsContext: TlsContext = config.tlsContext ?: run { - val options = TlsContextOptionsBuilder().apply { + private val crtTlsContext: CrtTlsContext = TlsContextOptionsBuilder() + .apply { verifyPeer = true - alpn = config.alpn.joinToString(separator = ";") { it.protocolId } - minTlsVersion = toCrtTlsVersion(config.minTlsVersion) - }.build() - TlsContext(options) - } + alpn = config.tlsContext.alpn.joinToString(separator = ";") { it.protocolId } + minTlsVersion = toCrtTlsVersion(config.tlsContext.minVersion) + } + .build() + .let(::CrtTlsContext) init { if (config.socketReadTimeout != CrtHttpEngineConfig.Default.socketReadTimeout) { @@ -77,7 +77,7 @@ public class CrtHttpEngine(public val config: CrtHttpEngineConfig) : HttpClientE private val options = HttpClientConnectionManagerOptionsBuilder().apply { clientBootstrap = config.clientBootstrap ?: SdkDefaultIO.ClientBootstrap - tlsContext = resolvedTlsContext + tlsContext = crtTlsContext manualWindowManagement = true socketOptions = SocketOptions( connectTimeoutMs = config.connectTimeout.inWholeMilliseconds.toInt(), @@ -136,7 +136,7 @@ public class CrtHttpEngine(public val config: CrtHttpEngineConfig) : HttpClientE // close all resources // SAFETY: shutdown is only invoked once AND only after all requests have completed and no more are coming connManagers.forEach { entry -> entry.value.close() } - resolvedTlsContext.close() + crtTlsContext.close() } private suspend fun getManagerForUri(uri: Uri, proxyConfig: ProxyConfig): HttpClientConnectionManager = mutex.withLock { @@ -159,9 +159,10 @@ public class CrtHttpEngine(public val config: CrtHttpEngineConfig) : HttpClientE } } -private fun toCrtTlsVersion(sdkTlsVersion: SdkTlsVersion): CrtTlsVersion = when (sdkTlsVersion) { - SdkTlsVersion.Tls1_0 -> CrtTlsVersion.TLSv1 - SdkTlsVersion.Tls1_1 -> CrtTlsVersion.TLS_V1_1 - SdkTlsVersion.Tls1_2 -> CrtTlsVersion.TLS_V1_2 - SdkTlsVersion.Tls1_3 -> CrtTlsVersion.TLS_V1_3 +private fun toCrtTlsVersion(sdkTlsVersion: SdkTlsVersion?): CrtTlsVersion = when (sdkTlsVersion) { + null -> CrtTlsVersion.SYS_DEFAULT + SdkTlsVersion.TLS_1_0 -> CrtTlsVersion.TLSv1 + SdkTlsVersion.TLS_1_1 -> CrtTlsVersion.TLS_V1_1 + SdkTlsVersion.TLS_1_2 -> CrtTlsVersion.TLS_V1_2 + SdkTlsVersion.TLS_1_3 -> CrtTlsVersion.TLS_V1_3 } diff --git a/runtime/protocol/http-client-engines/http-client-engine-crt/common/src/aws/smithy/kotlin/runtime/http/engine/crt/CrtHttpEngineConfig.kt b/runtime/protocol/http-client-engines/http-client-engine-crt/common/src/aws/smithy/kotlin/runtime/http/engine/crt/CrtHttpEngineConfig.kt index e13779b8b..00dec1734 100644 --- a/runtime/protocol/http-client-engines/http-client-engine-crt/common/src/aws/smithy/kotlin/runtime/http/engine/crt/CrtHttpEngineConfig.kt +++ b/runtime/protocol/http-client-engines/http-client-engine-crt/common/src/aws/smithy/kotlin/runtime/http/engine/crt/CrtHttpEngineConfig.kt @@ -6,7 +6,6 @@ package aws.smithy.kotlin.runtime.http.engine.crt import aws.sdk.kotlin.crt.io.ClientBootstrap -import aws.sdk.kotlin.crt.io.TlsContext import aws.smithy.kotlin.runtime.http.engine.HttpClientEngineConfig /** @@ -35,11 +34,6 @@ public class CrtHttpEngineConfig private constructor(builder: Builder) : HttpCli */ public var clientBootstrap: ClientBootstrap? = builder.clientBootstrap - /** - * The TLS context to use. By default it is a shared instance. - */ - public var tlsContext: TlsContext? = builder.tlsContext - public class Builder : HttpClientEngineConfig.Builder() { /** * Set the amount of data that can be buffered before reading from the socket will cease. Reading will @@ -52,11 +46,6 @@ public class CrtHttpEngineConfig private constructor(builder: Builder) : HttpCli */ public var clientBootstrap: ClientBootstrap? = null - /** - * Set the TLS context to use. - */ - public var tlsContext: TlsContext? = null - internal fun build(): CrtHttpEngineConfig = CrtHttpEngineConfig(this) } } diff --git a/runtime/protocol/http-client-engines/http-client-engine-okhttp/jvm/src/aws/smithy/kotlin/runtime/http/engine/okhttp/OkHttpEngine.kt b/runtime/protocol/http-client-engines/http-client-engine-okhttp/jvm/src/aws/smithy/kotlin/runtime/http/engine/okhttp/OkHttpEngine.kt index 73166bf43..2901910ba 100644 --- a/runtime/protocol/http-client-engines/http-client-engine-okhttp/jvm/src/aws/smithy/kotlin/runtime/http/engine/okhttp/OkHttpEngine.kt +++ b/runtime/protocol/http-client-engines/http-client-engine-okhttp/jvm/src/aws/smithy/kotlin/runtime/http/engine/okhttp/OkHttpEngine.kt @@ -5,8 +5,10 @@ package aws.smithy.kotlin.runtime.http.engine.okhttp +import aws.smithy.kotlin.runtime.config.TlsVersion import aws.smithy.kotlin.runtime.http.engine.AlpnId import aws.smithy.kotlin.runtime.http.engine.HttpClientEngineBase +import aws.smithy.kotlin.runtime.http.engine.TlsContext import aws.smithy.kotlin.runtime.http.engine.callContext import aws.smithy.kotlin.runtime.http.request.HttpRequest import aws.smithy.kotlin.runtime.http.response.HttpCall @@ -71,7 +73,7 @@ private fun OkHttpEngineConfig.buildClient(): OkHttpClient { followRedirects(false) followSslRedirects(false) - connectionSpecs(listOf(minTlsConnectionSpec(config.minTlsVersion), ConnectionSpec.CLEARTEXT)) + connectionSpecs(listOf(minTlsConnectionSpec(config.tlsContext), ConnectionSpec.CLEARTEXT)) // Transient connection errors are handled by retry strategy (exceptions are wrapped and marked retryable // appropriately internally). We don't want inner retry logic that inflates the number of retries. @@ -101,8 +103,8 @@ private fun OkHttpEngineConfig.buildClient(): OkHttpClient { eventListenerFactory { call -> HttpEngineEventListener(pool, config.hostResolver, call) } // map protocols - if (config.alpn.isNotEmpty()) { - val protocols = config.alpn.mapNotNull { + if (config.tlsContext.alpn.isNotEmpty()) { + val protocols = config.tlsContext.alpn.mapNotNull { when (it) { AlpnId.HTTP1_1 -> Protocol.HTTP_1_1 AlpnId.HTTP2 -> Protocol.HTTP_2 @@ -120,7 +122,8 @@ private fun OkHttpEngineConfig.buildClient(): OkHttpClient { }.build() } -private fun minTlsConnectionSpec(minVersion: SdkTlsVersion): ConnectionSpec { +private fun minTlsConnectionSpec(tlsContext: TlsContext): ConnectionSpec { + val minVersion = tlsContext.minVersion ?: TlsVersion.TLS_1_2 val okHttpTlsVersions = SdkTlsVersion .values() .filter { it >= minVersion } @@ -134,8 +137,8 @@ private fun minTlsConnectionSpec(minVersion: SdkTlsVersion): ConnectionSpec { } private fun toOkHttpTlsVersion(sdkTlsVersion: SdkTlsVersion): OkHttpTlsVersion = when (sdkTlsVersion) { - SdkTlsVersion.Tls1_0 -> OkHttpTlsVersion.TLS_1_0 - SdkTlsVersion.Tls1_1 -> OkHttpTlsVersion.TLS_1_1 - SdkTlsVersion.Tls1_2 -> OkHttpTlsVersion.TLS_1_2 - SdkTlsVersion.Tls1_3 -> OkHttpTlsVersion.TLS_1_3 + SdkTlsVersion.TLS_1_0 -> OkHttpTlsVersion.TLS_1_0 + SdkTlsVersion.TLS_1_1 -> OkHttpTlsVersion.TLS_1_1 + SdkTlsVersion.TLS_1_2 -> OkHttpTlsVersion.TLS_1_2 + SdkTlsVersion.TLS_1_3 -> OkHttpTlsVersion.TLS_1_3 } diff --git a/runtime/protocol/http-client/api/http-client.api b/runtime/protocol/http-client/api/http-client.api index 7b868b877..c7a875de0 100644 --- a/runtime/protocol/http-client/api/http-client.api +++ b/runtime/protocol/http-client/api/http-client.api @@ -50,37 +50,35 @@ public class aws/smithy/kotlin/runtime/http/engine/HttpClientEngineConfig { public static final field Companion Laws/smithy/kotlin/runtime/http/engine/HttpClientEngineConfig$Companion; public fun ()V public fun (Laws/smithy/kotlin/runtime/http/engine/HttpClientEngineConfig$Builder;)V - public final fun getAlpn ()Ljava/util/List; public final fun getConnectTimeout-UwyO8pc ()J public final fun getConnectionAcquireTimeout-UwyO8pc ()J public final fun getConnectionIdleTimeout-UwyO8pc ()J public final fun getMaxConnections-pVg5ArA ()I - public final fun getMinTlsVersion ()Laws/smithy/kotlin/runtime/config/TlsVersion; public final fun getProxySelector ()Laws/smithy/kotlin/runtime/http/engine/ProxySelector; public final fun getSocketReadTimeout-UwyO8pc ()J public final fun getSocketWriteTimeout-UwyO8pc ()J + public final fun getTlsContext ()Laws/smithy/kotlin/runtime/http/engine/TlsContext; } public class aws/smithy/kotlin/runtime/http/engine/HttpClientEngineConfig$Builder { public fun ()V - public final fun getAlpn ()Ljava/util/List; public final fun getConnectTimeout-UwyO8pc ()J public final fun getConnectionAcquireTimeout-UwyO8pc ()J public final fun getConnectionIdleTimeout-UwyO8pc ()J public final fun getMaxConnections-pVg5ArA ()I - public final fun getMinTlsVersion ()Laws/smithy/kotlin/runtime/config/TlsVersion; public final fun getProxySelector ()Laws/smithy/kotlin/runtime/http/engine/ProxySelector; public final fun getSocketReadTimeout-UwyO8pc ()J public final fun getSocketWriteTimeout-UwyO8pc ()J - public final fun setAlpn (Ljava/util/List;)V + public final fun getTlsContext ()Laws/smithy/kotlin/runtime/http/engine/TlsContext; public final fun setConnectTimeout-LRDsOJo (J)V public final fun setConnectionAcquireTimeout-LRDsOJo (J)V public final fun setConnectionIdleTimeout-LRDsOJo (J)V public final fun setMaxConnections-WZ4Q5Ns (I)V - public final fun setMinTlsVersion (Laws/smithy/kotlin/runtime/config/TlsVersion;)V public final fun setProxySelector (Laws/smithy/kotlin/runtime/http/engine/ProxySelector;)V public final fun setSocketReadTimeout-LRDsOJo (J)V public final fun setSocketWriteTimeout-LRDsOJo (J)V + public final fun setTlsContext (Laws/smithy/kotlin/runtime/http/engine/TlsContext;)V + public final fun tlsContext (Lkotlin/jvm/functions/Function1;)V } public final class aws/smithy/kotlin/runtime/http/engine/HttpClientEngineConfig$Companion { @@ -116,6 +114,25 @@ public final class aws/smithy/kotlin/runtime/http/engine/ProxySelector$Companion public final fun getNoProxy ()Laws/smithy/kotlin/runtime/http/engine/ProxySelector; } +public final class aws/smithy/kotlin/runtime/http/engine/TlsContext { + public static final field Companion Laws/smithy/kotlin/runtime/http/engine/TlsContext$Companion; + public final fun getAlpn ()Ljava/util/List; + public final fun getMinVersion ()Laws/smithy/kotlin/runtime/config/TlsVersion; +} + +public final class aws/smithy/kotlin/runtime/http/engine/TlsContext$Builder { + public fun ()V + public final fun getAlpn ()Ljava/util/List; + public final fun getMinVersion ()Laws/smithy/kotlin/runtime/config/TlsVersion; + public final fun setAlpn (Ljava/util/List;)V + public final fun setMinVersion (Laws/smithy/kotlin/runtime/config/TlsVersion;)V +} + +public final class aws/smithy/kotlin/runtime/http/engine/TlsContext$Companion { + public final fun getDefault ()Laws/smithy/kotlin/runtime/http/engine/TlsContext; + public final fun invoke (Lkotlin/jvm/functions/Function1;)Laws/smithy/kotlin/runtime/http/engine/TlsContext; +} + public final class aws/smithy/kotlin/runtime/http/engine/internal/ManagedHttpClientEngineKt { } diff --git a/runtime/protocol/http-client/common/src/aws/smithy/kotlin/runtime/http/engine/HttpClientEngineConfig.kt b/runtime/protocol/http-client/common/src/aws/smithy/kotlin/runtime/http/engine/HttpClientEngineConfig.kt index 5864469df..4e16442ca 100644 --- a/runtime/protocol/http-client/common/src/aws/smithy/kotlin/runtime/http/engine/HttpClientEngineConfig.kt +++ b/runtime/protocol/http-client/common/src/aws/smithy/kotlin/runtime/http/engine/HttpClientEngineConfig.kt @@ -5,7 +5,6 @@ package aws.smithy.kotlin.runtime.http.engine import aws.smithy.kotlin.runtime.InternalApi -import aws.smithy.kotlin.runtime.config.TlsVersion import aws.smithy.kotlin.runtime.net.HostResolver import kotlin.time.Duration import kotlin.time.Duration.Companion.seconds @@ -63,24 +62,22 @@ public open class HttpClientEngineConfig constructor(builder: Builder) { */ public val connectionIdleTimeout: Duration = builder.connectionIdleTimeout - /** - * The ALPN protocol list when a TLS connection starts - */ - public val alpn: List = builder.alpn - /** * The proxy selection policy */ public val proxySelector: ProxySelector = builder.proxySelector - public val minTlsVersion: TlsVersion = builder.minTlsVersion - /** * The host name resolver (DNS) */ @InternalApi public val hostResolver: HostResolver = builder.hostResolver + /** + * Settings related to TLS and secure connections + */ + public val tlsContext: TlsContext = builder.tlsContext + public open class Builder { /** * Timeout for each read to an underlying socket @@ -113,11 +110,6 @@ public open class HttpClientEngineConfig constructor(builder: Builder) { */ public var connectionIdleTimeout: Duration = 60.seconds - /** - * Set the ALPN protocol list when a TLS connection starts - */ - public var alpn: List = emptyList() - /** * Set the proxy selection policy to be used. * @@ -143,42 +135,22 @@ public open class HttpClientEngineConfig constructor(builder: Builder) { */ public var proxySelector: ProxySelector = EnvironmentProxySelector() - /** - * Set the minimum allowed TLS version for HTTP connections. - */ - public var minTlsVersion: TlsVersion = TlsVersion.Tls1_2 - /** * The host name resolver (DNS) to be used by the client */ @InternalApi public var hostResolver: HostResolver = HostResolver.Default - } -} - -/** - * Common ALPN identifiers - * See the [IANA registry](https://www.iana.org/assignments/tls-extensiontype-values/tls-extensiontype-values.xhtml#alpn-protocol-ids) - */ -public enum class AlpnId(public val protocolId: String) { - /** - * HTTP 1.1 - */ - HTTP1_1("http/1.1"), - /** - * HTTP 2 over TLS - */ - HTTP2("h2"), - - /** - * Cleartext HTTP/2 with no "upgrade" round trip. This option requires the client to have prior knowledge that the - * server supports cleartext HTTP/2. See also rfc_7540_34. - */ - H2_PRIOR_KNOWLEDGE("h2_prior_knowledge"), + /** + * Settings related to TLS and secure connections + */ + public var tlsContext: TlsContext = TlsContext.Default - /** - * HTTP 3 - */ - HTTP3("h3"), + /** + * Settings related to TLS and secure connections + */ + public fun tlsContext(block: TlsContext.Builder.() -> Unit) { + tlsContext = TlsContext(tlsContext.toBuilder().apply(block)) + } + } } diff --git a/runtime/protocol/http-client/common/src/aws/smithy/kotlin/runtime/http/engine/TlsContext.kt b/runtime/protocol/http-client/common/src/aws/smithy/kotlin/runtime/http/engine/TlsContext.kt new file mode 100644 index 000000000..a1e06be46 --- /dev/null +++ b/runtime/protocol/http-client/common/src/aws/smithy/kotlin/runtime/http/engine/TlsContext.kt @@ -0,0 +1,77 @@ +/* + * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + * SPDX-License-Identifier: Apache-2.0 + */ +package aws.smithy.kotlin.runtime.http.engine + +import aws.smithy.kotlin.runtime.client.config.ClientSettings +import aws.smithy.kotlin.runtime.config.TlsVersion +import aws.smithy.kotlin.runtime.config.resolve + +/** + * Defines values related to TLS and secure connections. + */ +public class TlsContext internal constructor(builder: Builder) { + public companion object { + public operator fun invoke(block: Builder.() -> Unit): TlsContext = TlsContext(Builder().apply(block)) + + public val Default: TlsContext = TlsContext(Builder()) + } + + /** + * The ALPN protocol list when a TLS connection starts + */ + public val alpn: List = builder.alpn + + /** + * The minimum allowed TLS version for HTTP connections + */ + public val minVersion: TlsVersion? = builder.minVersion ?: ClientSettings.MinTlsVersion.resolve() + + internal fun toBuilder(): Builder = Builder().apply { + alpn = this@TlsContext.alpn + minVersion = this@TlsContext.minVersion + } + + /** + * Mutable configuration for [TlsContext] + */ + public class Builder { + /** + * The ALPN protocol list when a TLS connection starts + */ + public var alpn: List = emptyList() + + /** + * The minimum allowed TLS version for HTTP connections + */ + public var minVersion: TlsVersion? = null + } +} + +/** + * Common ALPN identifiers + * See the [IANA registry](https://www.iana.org/assignments/tls-extensiontype-values/tls-extensiontype-values.xhtml#alpn-protocol-ids) + */ +public enum class AlpnId(public val protocolId: String) { + /** + * HTTP 1.1 + */ + HTTP1_1("http/1.1"), + + /** + * HTTP 2 over TLS + */ + HTTP2("h2"), + + /** + * Cleartext HTTP/2 with no "upgrade" round trip. This option requires the client to have prior knowledge that the + * server supports cleartext HTTP/2. See also rfc_7540_34. + */ + H2_PRIOR_KNOWLEDGE("h2_prior_knowledge"), + + /** + * HTTP 3 + */ + HTTP3("h3"), +} diff --git a/runtime/runtime-core/api/runtime-core.api b/runtime/runtime-core/api/runtime-core.api index c6b24d7af..4de578bc3 100644 --- a/runtime/runtime-core/api/runtime-core.api +++ b/runtime/runtime-core/api/runtime-core.api @@ -70,10 +70,10 @@ public final class aws/smithy/kotlin/runtime/config/EnvironmentSettingKt { } public final class aws/smithy/kotlin/runtime/config/TlsVersion : java/lang/Enum { - public static final field Tls1_0 Laws/smithy/kotlin/runtime/config/TlsVersion; - public static final field Tls1_1 Laws/smithy/kotlin/runtime/config/TlsVersion; - public static final field Tls1_2 Laws/smithy/kotlin/runtime/config/TlsVersion; - public static final field Tls1_3 Laws/smithy/kotlin/runtime/config/TlsVersion; + public static final field TLS_1_0 Laws/smithy/kotlin/runtime/config/TlsVersion; + public static final field TLS_1_1 Laws/smithy/kotlin/runtime/config/TlsVersion; + public static final field TLS_1_2 Laws/smithy/kotlin/runtime/config/TlsVersion; + public static final field TLS_1_3 Laws/smithy/kotlin/runtime/config/TlsVersion; public static fun valueOf (Ljava/lang/String;)Laws/smithy/kotlin/runtime/config/TlsVersion; public static fun values ()[Laws/smithy/kotlin/runtime/config/TlsVersion; } diff --git a/runtime/runtime-core/common/src/aws/smithy/kotlin/runtime/config/EnvironmentSetting.kt b/runtime/runtime-core/common/src/aws/smithy/kotlin/runtime/config/EnvironmentSetting.kt index 74aa94a0c..09ee0a7ab 100644 --- a/runtime/runtime-core/common/src/aws/smithy/kotlin/runtime/config/EnvironmentSetting.kt +++ b/runtime/runtime-core/common/src/aws/smithy/kotlin/runtime/config/EnvironmentSetting.kt @@ -7,6 +7,7 @@ package aws.smithy.kotlin.runtime.config import aws.smithy.kotlin.runtime.ClientException import aws.smithy.kotlin.runtime.InternalApi import aws.smithy.kotlin.runtime.util.PlatformEnvironProvider +import aws.smithy.kotlin.runtime.util.PlatformProvider @InternalApi public typealias EnvSettingFactory = (String, String) -> EnvironmentSetting @@ -41,9 +42,10 @@ public data class EnvironmentSetting( * property first, falling back to the environment variable if necessary. If neither is set, it returns the setting's * default value. * @param platform The [PlatformEnvironProvider] to use for reading system properties and environment variables. + * Defaults to [PlatformProvider.System]. */ @InternalApi -public fun EnvironmentSetting.resolve(platform: PlatformEnvironProvider): T? { +public fun EnvironmentSetting.resolve(platform: PlatformEnvironProvider = PlatformProvider.System): T? { val stringValue = platform.getProperty(sysProp) ?: platform.getenv(envVar) return stringValue?.let(parse) ?: defaultValue } diff --git a/runtime/runtime-core/common/src/aws/smithy/kotlin/runtime/config/TlsVersion.kt b/runtime/runtime-core/common/src/aws/smithy/kotlin/runtime/config/TlsVersion.kt index ca3425807..920438e1a 100644 --- a/runtime/runtime-core/common/src/aws/smithy/kotlin/runtime/config/TlsVersion.kt +++ b/runtime/runtime-core/common/src/aws/smithy/kotlin/runtime/config/TlsVersion.kt @@ -7,25 +7,24 @@ package aws.smithy.kotlin.runtime.config /** * Describes a version of TLS */ -@Suppress("ktlint:enum-entry-name-case") public enum class TlsVersion { /** * TLS version 1 */ - Tls1_0, + TLS_1_0, /** * TLS version 1.1 */ - Tls1_1, + TLS_1_1, /** * TLS version 1.2 */ - Tls1_2, + TLS_1_2, /** * TLS version 1.3 */ - Tls1_3, + TLS_1_3, } diff --git a/runtime/smithy-client/common/src/aws/smithy/kotlin/runtime/client/config/ClientSettings.kt b/runtime/smithy-client/common/src/aws/smithy/kotlin/runtime/client/config/ClientSettings.kt index 47e34335a..2c6dd9e49 100644 --- a/runtime/smithy-client/common/src/aws/smithy/kotlin/runtime/client/config/ClientSettings.kt +++ b/runtime/smithy-client/common/src/aws/smithy/kotlin/runtime/client/config/ClientSettings.kt @@ -21,8 +21,7 @@ public object ClientSettings { /** * Specifies the minimum acceptable version of TLS to use when connecting to service endpoints. */ - public val MinTlsVersion: EnvironmentSetting = - enumEnvSetting("SDK_MIN_TLS", "sdk.minTls").orElse(TlsVersion.Tls1_2) + public val MinTlsVersion: EnvironmentSetting = enumEnvSetting("SDK_MIN_TLS", "sdk.minTls") /** * Which RetryMode to use for the default RetryPolicy, when one is not specified at the client level. From c8085c79b6947753afcd092e811ffec8608b4e79 Mon Sep 17 00:00:00 2001 From: Ian Smith Botsford Date: Tue, 2 May 2023 17:12:22 +0000 Subject: [PATCH 4/5] clarify that change is breaking --- .changes/1af71df0-f584-493e-85ab-2ee3e853c827.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.changes/1af71df0-f584-493e-85ab-2ee3e853c827.json b/.changes/1af71df0-f584-493e-85ab-2ee3e853c827.json index bbeaa0290..f5705cc20 100644 --- a/.changes/1af71df0-f584-493e-85ab-2ee3e853c827.json +++ b/.changes/1af71df0-f584-493e-85ab-2ee3e853c827.json @@ -1,8 +1,8 @@ { "id": "1af71df0-f584-493e-85ab-2ee3e853c827", "type": "feature", - "description": "Add HTTP engine configuration for minimum TLS version", + "description": "**Breaking**: Add HTTP engine configuration for minimum TLS version", "issues": [ "awslabs/smithy-kotlin#661" ] -} \ No newline at end of file +} From 28592434fcd4cdefbc2b382d8e25e79be1272cb9 Mon Sep 17 00:00:00 2001 From: Ian Smith Botsford Date: Wed, 3 May 2023 15:58:40 +0000 Subject: [PATCH 5/5] add link to BREAKING post --- .changes/1af71df0-f584-493e-85ab-2ee3e853c827.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.changes/1af71df0-f584-493e-85ab-2ee3e853c827.json b/.changes/1af71df0-f584-493e-85ab-2ee3e853c827.json index f5705cc20..9122b809b 100644 --- a/.changes/1af71df0-f584-493e-85ab-2ee3e853c827.json +++ b/.changes/1af71df0-f584-493e-85ab-2ee3e853c827.json @@ -1,7 +1,7 @@ { "id": "1af71df0-f584-493e-85ab-2ee3e853c827", "type": "feature", - "description": "**Breaking**: Add HTTP engine configuration for minimum TLS version", + "description": "**Breaking**: Add HTTP engine configuration for minimum TLS version. See the [BREAKING: Streamlined TLS configuration](https://github.com/awslabs/aws-sdk-kotlin/discussions/909) discussion post for more details.", "issues": [ "awslabs/smithy-kotlin#661" ]