Skip to content

Commit

Permalink
chore: address testcontainers vulnerability by replacing with docker-…
Browse files Browse the repository at this point in the history
…java (#1088)
  • Loading branch information
ianbotsf authored May 3, 2024
1 parent 152ded4 commit 7d2012a
Show file tree
Hide file tree
Showing 11 changed files with 348 additions and 48 deletions.
3 changes: 3 additions & 0 deletions build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -83,6 +83,9 @@ allprojects {
)
}
}

// Enables running `./gradlew allDeps` to get a comprehensive list of dependencies for every subproject
tasks.register<DependencyReportTask>("allDeps") { }
}

// configure the root multimodule docs
Expand Down
6 changes: 3 additions & 3 deletions gradle/libs.versions.toml
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ kotest-version = "5.8.0"
kotlin-compile-testing-version = "1.5.0"
kotlinx-benchmark-version = "0.4.9"
kotlinx-serialization-version = "1.6.0"
testcontainers-version = "1.19.1"
docker-java-version = "3.3.6"
ktor-version = "2.3.6"
kaml-version = "0.55.0"
jsoup-version = "1.16.2"
Expand Down Expand Up @@ -81,8 +81,8 @@ kotest-assertions-core = { module = "io.kotest:kotest-assertions-core", version.
kotest-assertions-core-jvm = { module = "io.kotest:kotest-assertions-core-jvm", version.ref = "kotest-version" }
kotlinx-benchmark-runtime = { module = "org.jetbrains.kotlinx:kotlinx-benchmark-runtime", version.ref = "kotlinx-benchmark-version" }
kotlinx-serialization-json = { module = "org.jetbrains.kotlinx:kotlinx-serialization-json", version.ref = "kotlinx-serialization-version" }
testcontainers = { module = "org.testcontainers:testcontainers", version.ref = "testcontainers-version" }
testcontainers-junit-jupiter = { module = "org.testcontainers:junit-jupiter", version.ref = "testcontainers-version" }
docker-core = { module = "com.github.docker-java:docker-java-core", version.ref = "docker-java-version" }
docker-transport-zerodep = { module = "com.github.docker-java:docker-java-transport-zerodep", version.ref = "docker-java-version" }

ktor-http-cio = { module = "io.ktor:ktor-http-cio", version.ref = "ktor-version" }
ktor-utils = { module = "io.ktor:ktor-utils", version.ref = "ktor-version" }
Expand Down
10 changes: 7 additions & 3 deletions runtime/protocol/http-client-engines/test-suite/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -42,8 +42,8 @@ kotlin {

jvmTest {
dependencies {
implementation(libs.testcontainers)
implementation(libs.testcontainers.junit.jupiter)
implementation(libs.docker.core)
implementation(libs.docker.transport.zerodep)
}
}

Expand Down Expand Up @@ -114,9 +114,13 @@ tasks.jvmTest {
// set test environment for proxy tests
systemProperty("MITM_PROXY_SCRIPTS_ROOT", projectDir.resolve("proxy-scripts").absolutePath)
systemProperty("SSL_CONFIG_PATH", startTestServers.sslConfigPath)

val enableProxyTestsProp = "aws.test.http.enableProxyTests"
val runningInCodeBuild = System.getenv().containsKey("CODEBUILD_BUILD_ID")
systemProperty(enableProxyTestsProp, System.getProperties().getOrDefault(enableProxyTestsProp, !runningInCodeBuild))
val runningInLinux = System.getProperty("os.name").contains("Linux", ignoreCase = true)
val shouldRunProxyTests = !runningInCodeBuild && runningInLinux

systemProperty(enableProxyTestsProp, System.getProperties().getOrDefault(enableProxyTestsProp, shouldRunProxyTests))
}

gradle.buildFinished {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
/*
* Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
* SPDX-License-Identifier: Apache-2.0
*/
package aws.smithy.kotlin.runtime.http.test

import aws.smithy.kotlin.runtime.http.test.util.Docker
import com.github.dockerjava.api.model.AccessMode
import com.github.dockerjava.api.model.Bind
import com.github.dockerjava.api.model.ExposedPort
import com.github.dockerjava.api.model.Volume
import java.io.Closeable

private const val CONTAINER_MOUNT_POINT = "/home/mitmproxy/scripts"
private const val CONTAINER_PORT = 8080
private const val IMAGE_NAME = "mitmproxy/mitmproxy:8.1.0"
private val PROXY_SCRIPT_ROOT = System.getProperty("MITM_PROXY_SCRIPTS_ROOT") // defined by gradle script

// Port used for communication with container
private val exposedPort = ExposedPort.tcp(CONTAINER_PORT)

/**
* A Docker container which runs the **mitmproxy** image. Upon instantiating this class, a docker container will be
* created and ran with a logger attached echoing logs out to **STDOUT**. The container will be stopped and removed when
* [close] is called.
*/
class MitmContainer(vararg options: String) : Closeable {
private val delegate: Docker.Container

init {
val cmd = listOf(
"mitmdump", // https://docs.mitmproxy.org/stable/#mitmdump
"--flow-detail",
"2",
"-s",
"$CONTAINER_MOUNT_POINT/fakeupstream.py",
*options,
).also { println("Initializing container with command: $it") }

// Make proxy scripts from host filesystem available in container's filesystem
val binding = Bind(PROXY_SCRIPT_ROOT, Volume(CONTAINER_MOUNT_POINT), AccessMode.ro)

delegate = Docker.Instance.createContainer(IMAGE_NAME, cmd, binding, exposedPort)

try {
delegate.apply {
start()
waitUntilReady()
}
} catch (e: Throwable) {
close()
throw e
}
}

/**
* Gets the host port that can be used to communicate to the MITM proxy
*/
val hostPort: Int
get() = delegate.hostPort

override fun close() = delegate.close()
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@
* Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
* SPDX-License-Identifier: Apache-2.0
*/

package aws.smithy.kotlin.runtime.http.test

import aws.smithy.kotlin.runtime.http.HttpStatusCode
Expand All @@ -18,49 +17,34 @@ import aws.smithy.kotlin.runtime.http.test.util.AbstractEngineTest
import aws.smithy.kotlin.runtime.http.test.util.engineConfig
import aws.smithy.kotlin.runtime.http.test.util.test
import aws.smithy.kotlin.runtime.net.url.Url
import org.junit.jupiter.api.AfterAll
import org.junit.jupiter.api.BeforeAll
import org.junit.jupiter.api.Test
import org.junit.jupiter.api.TestInstance
import org.junit.jupiter.api.condition.EnabledIfSystemProperty
import org.testcontainers.containers.BindMode
import org.testcontainers.containers.GenericContainer
import org.testcontainers.junit.jupiter.Container
import org.testcontainers.junit.jupiter.Testcontainers
import org.testcontainers.utility.DockerImageName
import kotlin.test.assertEquals

// defined by gradle script
private val PROXY_SCRIPT_ROOT = System.getProperty("MITM_PROXY_SCRIPTS_ROOT")
private fun mitmProxyContainer(
vararg options: String,
) = GenericContainer(DockerImageName.parse("mitmproxy/mitmproxy:8.1.0"))
.withExposedPorts(8080)
.withFileSystemBind(PROXY_SCRIPT_ROOT, "/home/mitmproxy/scripts", BindMode.READ_ONLY)
.withLogConsumer {
print(it.utf8String)
}.apply {
val command = buildString {
// load the custom addon which by default does nothing without setting additional options
append("mitmdump --flow-detail 2 -s /home/mitmproxy/scripts/fakeupstream.py")
append(options.joinToString(separator = " ", prefix = " "))
}
withCommand(command)
}

@Testcontainers(disabledWithoutDocker = true)
@TestInstance(TestInstance.Lifecycle.PER_CLASS) // enables non-static @BeforeAll/@AfterAll methods
@EnabledIfSystemProperty(named = "aws.test.http.enableProxyTests", matches = "true")
class ProxyTest : AbstractEngineTest() {
private lateinit var mitmProxy: MitmContainer

@BeforeAll
fun setUp() {
mitmProxy = MitmContainer("--set", "fakeupstream=aws.amazon.com")
}

@Container
val mitmProxy = mitmProxyContainer("--set fakeupstream=aws.amazon.com")
@AfterAll
fun cleanUp() {
mitmProxy.close()
}

@Test
fun testHttpProxy() = testEngines(
// we would expect a customer to configure proxy support on the underlying engine
skipEngines = setOf("KtorEngine"),
) {
fun testHttpProxy() = testEngines {
engineConfig {
val proxyPort = mitmProxy.getMappedPort(8080)
val hostPort = mitmProxy.hostPort
proxySelector = ProxySelector {
ProxyConfig.Http("http://127.0.0.1:$proxyPort")
ProxyConfig.Http("http://127.0.0.1:$hostPort")
}
}

Expand All @@ -70,22 +54,27 @@ class ProxyTest : AbstractEngineTest() {
}
}

@Testcontainers(disabledWithoutDocker = true)
@TestInstance(TestInstance.Lifecycle.PER_CLASS) // enables non-static @BeforeAll/@AfterAll methods
@EnabledIfSystemProperty(named = "aws.test.http.enableProxyTests", matches = "true")
class ProxyAuthTest : AbstractEngineTest() {
private lateinit var mitmProxy: MitmContainer

@Container
val mitmProxy = mitmProxyContainer("--proxyauth testuser:testpass --set fakeupstream=aws.amazon.com")
@BeforeAll
fun setUp() {
mitmProxy = MitmContainer("--proxyauth", "testuser:testpass", "--set", "fakeupstream=aws.amazon.com")
}

@AfterAll
fun cleanUp() {
mitmProxy.close()
}

@Test
fun testHttpProxyAuth() = testEngines(
// we would expect a customer to configure proxy support on the underlying engine
skipEngines = setOf("KtorEngine"),
) {
fun testHttpProxyAuth() = testEngines {
engineConfig {
val proxyPort = mitmProxy.getMappedPort(8080)
val hostPort = mitmProxy.hostPort
proxySelector = ProxySelector {
ProxyConfig.Http("http://testuser:[email protected]:$proxyPort")
ProxyConfig.Http("http://testuser:[email protected]:$hostPort")
}
}

Expand Down
Loading

0 comments on commit 7d2012a

Please sign in to comment.