From da45c5997f37311a105d6c5dd9d9276aebb55e0e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?O=C4=9Fuzhan=20Soykan?= Date: Wed, 16 Aug 2023 09:59:30 +0200 Subject: [PATCH] Improved elastic search captured certificate (#191) --- gradle/libs.versions.toml | 1 + .../build.gradle.kts | 1 + .../e2e/elasticsearch/ElasticsearchSystem.kt | 19 +++--- .../testing/e2e/elasticsearch/Extensions.kt | 3 + .../testing/e2e/elasticsearch/Options.kt | 64 ++++++++++++++++--- .../ElasticsearchExposedCertificateTest.kt | 38 +++++++++++ .../com/trendyol/stove/functional/Reflect.kt | 2 + .../e2e/containers/ExposedCertificate.kt | 11 ---- 8 files changed, 107 insertions(+), 32 deletions(-) create mode 100644 lib/stove-testing-e2e-elasticsearch/src/test/kotlin/com/trendyol/stove/testing/e2e/elasticsearch/ElasticsearchExposedCertificateTest.kt delete mode 100644 lib/stove-testing-e2e/src/main/kotlin/com/trendyol/stove/testing/e2e/containers/ExposedCertificate.kt diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 659b80b8..b8f57f87 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -50,6 +50,7 @@ couchbase-client = { module = "com.couchbase.client:java-client", version.ref = couchbase-client-metrics = { module = "com.couchbase.client:metrics-micrometer", version.ref = "couchbase-client-metrics" } jackson-kotlin = { module = "com.fasterxml.jackson.module:jackson-module-kotlin", version.ref = "jackson" } jackson-databind = { module = "com.fasterxml.jackson.core:jackson-databind", version.ref = "jackson" } +jackson-arrow = { module = "io.arrow-kt:arrow-integrations-jackson-module", version = "0.14.1" } slf4j-api = { module = "org.slf4j:slf4j-api", version.ref = "slf4j" } slf4j-simple = { module = "org.slf4j:slf4j-simple", version.ref = "slf4j" } ktor-server-host-common = { module = "io.ktor:ktor-server-host-common", version.ref = "ktor" } diff --git a/lib/stove-testing-e2e-elasticsearch/build.gradle.kts b/lib/stove-testing-e2e-elasticsearch/build.gradle.kts index 88399ff9..b03caae3 100644 --- a/lib/stove-testing-e2e-elasticsearch/build.gradle.kts +++ b/lib/stove-testing-e2e-elasticsearch/build.gradle.kts @@ -4,6 +4,7 @@ dependencies { api(libs.elastic) implementation(testLibs.testcontainers.elasticsearch) implementation(libs.jackson.databind) + implementation(libs.jackson.arrow) } dependencies { diff --git a/lib/stove-testing-e2e-elasticsearch/src/main/kotlin/com/trendyol/stove/testing/e2e/elasticsearch/ElasticsearchSystem.kt b/lib/stove-testing-e2e-elasticsearch/src/main/kotlin/com/trendyol/stove/testing/e2e/elasticsearch/ElasticsearchSystem.kt index d054eb38..6eb5e3b9 100644 --- a/lib/stove-testing-e2e-elasticsearch/src/main/kotlin/com/trendyol/stove/testing/e2e/elasticsearch/ElasticsearchSystem.kt +++ b/lib/stove-testing-e2e-elasticsearch/src/main/kotlin/com/trendyol/stove/testing/e2e/elasticsearch/ElasticsearchSystem.kt @@ -1,7 +1,6 @@ package com.trendyol.stove.testing.e2e.elasticsearch -import arrow.core.getOrElse -import arrow.core.toOption +import arrow.core.* import co.elastic.clients.elasticsearch.ElasticsearchClient import co.elastic.clients.elasticsearch._types.Refresh import co.elastic.clients.elasticsearch._types.query_dsl.Query @@ -11,8 +10,6 @@ import co.elastic.clients.json.jackson.JacksonJsonpMapper import co.elastic.clients.transport.rest_client.RestClientTransport import com.trendyol.stove.functional.Try import com.trendyol.stove.functional.recover -import com.trendyol.stove.testing.e2e.containers.ExposedCertificate -import com.trendyol.stove.testing.e2e.containers.NoCertificate import com.trendyol.stove.testing.e2e.system.TestSystem import com.trendyol.stove.testing.e2e.system.abstractions.* import kotlinx.coroutines.Dispatchers @@ -53,13 +50,13 @@ class ElasticsearchSystem internal constructor( } } - private fun determineCertificate(): ExposedCertificate = when (context.options.containerOptions.disableSecurity) { - true -> NoCertificate - false -> ElasticsearchExposedCertificate( - context.container.caCertAsBytes().getOrElse { ByteArray(0) }, - context.container.createSslContextFromCa() - ) - } + private fun determineCertificate(): Option = + when (context.options.containerOptions.disableSecurity) { + true -> None + false -> ElasticsearchExposedCertificate( + context.container.caCertAsBytes().getOrElse { ByteArray(0) } + ).apply { sslContext = context.container.createSslContextFromCa() }.some() + } override suspend fun afterRun() { esClient = createEsClient(exposedConfiguration) diff --git a/lib/stove-testing-e2e-elasticsearch/src/main/kotlin/com/trendyol/stove/testing/e2e/elasticsearch/Extensions.kt b/lib/stove-testing-e2e-elasticsearch/src/main/kotlin/com/trendyol/stove/testing/e2e/elasticsearch/Extensions.kt index c6a5e2ea..bbee7065 100644 --- a/lib/stove-testing-e2e-elasticsearch/src/main/kotlin/com/trendyol/stove/testing/e2e/elasticsearch/Extensions.kt +++ b/lib/stove-testing-e2e-elasticsearch/src/main/kotlin/com/trendyol/stove/testing/e2e/elasticsearch/Extensions.kt @@ -1,6 +1,7 @@ package com.trendyol.stove.testing.e2e.elasticsearch import arrow.core.getOrElse +import arrow.integrations.jackson.module.registerArrowModule import com.trendyol.stove.testing.e2e.containers.withProvidedRegistry import com.trendyol.stove.testing.e2e.system.TestSystem import com.trendyol.stove.testing.e2e.system.ValidationDsl @@ -21,6 +22,8 @@ fun TestSystem.withElasticsearch( register { options.defaultIndex.migrator } } + options.objectMapper.registerArrowModule() + return withProvidedRegistry( imageName = "elasticsearch/elasticsearch:${options.containerOptions.imageVersion}", registry = options.containerOptions.registry, diff --git a/lib/stove-testing-e2e-elasticsearch/src/main/kotlin/com/trendyol/stove/testing/e2e/elasticsearch/Options.kt b/lib/stove-testing-e2e-elasticsearch/src/main/kotlin/com/trendyol/stove/testing/e2e/elasticsearch/Options.kt index 53aa2cbc..e0cec165 100644 --- a/lib/stove-testing-e2e-elasticsearch/src/main/kotlin/com/trendyol/stove/testing/e2e/elasticsearch/Options.kt +++ b/lib/stove-testing-e2e-elasticsearch/src/main/kotlin/com/trendyol/stove/testing/e2e/elasticsearch/Options.kt @@ -4,8 +4,11 @@ import arrow.core.None import arrow.core.Option import arrow.core.none import co.elastic.clients.elasticsearch.ElasticsearchClient +import com.fasterxml.jackson.annotation.JsonCreator +import com.fasterxml.jackson.annotation.JsonIgnore +import com.fasterxml.jackson.annotation.JsonProperty import com.fasterxml.jackson.databind.ObjectMapper -import com.trendyol.stove.testing.e2e.containers.ExposedCertificate +import com.trendyol.stove.functional.Reflect import com.trendyol.stove.testing.e2e.database.migrations.DatabaseMigration import com.trendyol.stove.testing.e2e.database.migrations.MigrationCollection import com.trendyol.stove.testing.e2e.serialization.StoveObjectMapper @@ -16,6 +19,7 @@ import org.apache.http.client.config.RequestConfig import org.apache.http.impl.nio.client.HttpAsyncClientBuilder import org.elasticsearch.client.RestClient import org.testcontainers.elasticsearch.ElasticsearchContainer +import java.util.* import javax.net.ssl.SSLContext import kotlin.time.Duration.Companion.minutes @@ -34,23 +38,63 @@ data class ElasticsearchSystemOptions( * @see MigrationCollection * @see DatabaseMigration */ - fun migrations(migration: MigrationCollection.() -> Unit): ElasticsearchSystemOptions = migration( - migrationCollection - ).let { - this - } + fun migrations(migration: MigrationCollection.() -> Unit): ElasticsearchSystemOptions = + migration( + migrationCollection + ).let { + this + } } data class ElasticsearchExposedCertificate( - val bytes: ByteArray, - val sslContext: SSLContext -) : ExposedCertificate + val bytes: ByteArray +) { + + @get:JsonIgnore + @set:JsonIgnore + var sslContext: SSLContext = SSLContext.getDefault() + internal set + + override fun equals(other: Any?): Boolean { + if (this === other) return true + if (javaClass != other?.javaClass) return false + + other as ElasticsearchExposedCertificate + + if (!bytes.contentEquals(other.bytes)) return false + if (sslContext != other.sslContext) return false + + return true + } + + override fun hashCode(): Int { + var result = bytes.contentHashCode() + result = 31 * result + sslContext.hashCode() + return result + } + + companion object { + @JsonCreator + @JvmStatic + fun create( + @JsonProperty bytes: ByteArray + ): ElasticsearchExposedCertificate { + val container = ElasticsearchContainer("docker.elastic.co/elasticsearch/elasticsearch:latest") + Reflect(container) { + on>("caCertAsBytes").then(Optional.of(bytes)) + } + return ElasticsearchExposedCertificate(bytes).apply { + sslContext = container.createSslContextFromCa() + } + } + } +} data class ElasticSearchExposedConfiguration( val host: String, val port: Int, val password: String, - val certificate: ExposedCertificate + val certificate: Option ) : ExposedConfiguration data class ElasticsearchContext( diff --git a/lib/stove-testing-e2e-elasticsearch/src/test/kotlin/com/trendyol/stove/testing/e2e/elasticsearch/ElasticsearchExposedCertificateTest.kt b/lib/stove-testing-e2e-elasticsearch/src/test/kotlin/com/trendyol/stove/testing/e2e/elasticsearch/ElasticsearchExposedCertificateTest.kt new file mode 100644 index 00000000..82d2743b --- /dev/null +++ b/lib/stove-testing-e2e-elasticsearch/src/test/kotlin/com/trendyol/stove/testing/e2e/elasticsearch/ElasticsearchExposedCertificateTest.kt @@ -0,0 +1,38 @@ +package com.trendyol.stove.testing.e2e.elasticsearch + +import arrow.integrations.jackson.module.registerArrowModule +import com.fasterxml.jackson.module.kotlin.readValue +import com.trendyol.stove.functional.get +import com.trendyol.stove.testing.e2e.serialization.StoveObjectMapper +import com.trendyol.stove.testing.e2e.system.abstractions.StateWithProcess +import io.kotest.core.spec.style.FunSpec +import io.kotest.matchers.ints.shouldBeGreaterThan +import io.kotest.matchers.shouldBe +import io.kotest.matchers.shouldNotBe + +class ElasticsearchExposedCertificateTest : FunSpec({ + test("ser/de") { + val state = """ + { + "state": { + "host": "localhost", + "port": 50543, + "password": "password", + "certificate": { + "bytes": "LS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0tLS0tCk1JSUZXakNDQTBLZ0F3SUJBZ0lWQU4wclloSXpaMS90Rmg5NmR3WGI1b1lWWnl4UU1BMEdDU3FHU0liM0RRRUIKQ3dVQU1Ed3hPakE0QmdOVkJBTVRNVVZzWVhOMGFXTnpaV0Z5WTJnZ2MyVmpkWEpwZEhrZ1lYVjBieTFqYjI1bQphV2QxY21GMGFXOXVJRWhVVkZBZ1EwRXdIaGNOTWpNd09ERTFNVGd5TWpJNFdoY05Nall3T0RFME1UZ3lNakk0CldqQThNVG93T0FZRFZRUURFekZGYkdGemRHbGpjMlZoY21Ob0lITmxZM1Z5YVhSNUlHRjFkRzh0WTI5dVptbG4KZFhKaGRHbHZiaUJJVkZSUUlFTkJNSUlDSWpBTkJna3Foa2lHOXcwQkFRRUZBQU9DQWc4QU1JSUNDZ0tDQWdFQQppcDVGaWwySUMyRGhYbHkyV3RYTFliTnJUbHNkQklZQ3JvK0N1QU40djJHM3RuMkNQTmVoMnM2V0ovRUNrRitVCndJUVVKWEN0Mm43aEJDWkY4M1BlQ1JabWZyWkE0VXNtdzBYWS9OWWpTcnJRQXVtODYvamFZM0lMVGYzU1Jnei8KaWNFVEJCRVM1eTdmSFZlU0xmTjl1ME9hSC9tTnN5Q3FoMERMRFZrWXR5MHNJZXorb1paNmtxN2UrRE1OeHB5Sgp1cjRvUGJ5ekdmY1dnZDdnMll5T2RxNEd1TmFOck8ySjFVZG5BV3B5TVdnME5TSzd1TGlEZ0w5a25ZZlBnMmhQCisrWVpnVkJNNXJBMXJhY2x3c0U0NWJNVlErKzRyWEhhbDUwdS83VmN6a3M5QTREVDc3ZHVyOC9aM1hONWtpMm0KaWNTemJDTHlLdTZwdUhLQWhCdFMyWnlMMXBYN09RSVA2aWZLaVpaU1ZYUGZnMGk5cjVQL3ZFZTJoVXpTTDRVLwpsSWQvaWJUQWtIcmZGbEErN2FreFNzcFJoalMra1ZTQndyR05KQ3BDbWsraitxSnB5Sis5aTNWb0pkanVvemprClk2bS9EZG9kc21LdUZFYUdZNytBT1RVMjAwN0ZjZWdXUEJWejgzU051WmpwbURCczMzS25oeG5WM0RBb0QzUm4KbkNoV2ZQTGo4TUl1OG9tMll3RUpZMUtLR1hzUzU5TWtyclpuQnNjdzl0S0prMFQyRHlLM2dWckY5UnJpMk1mTwpKY3FCWUhBSHRQTHRQZU5STHJLUmxtYkh4NXJFMkNCWEJWQWJ1bU1EaGRIbE1lTWtwT1p3WnoyQWljWUV1anhlClU0TUl5LzczU1RHakhtcGpVT3dKcjNMdVdqVlBMNDlZeTZZWmNPbENsTThDQXdFQUFhTlRNRkV3SFFZRFZSME8KQkJZRUZNUTlobHVXN3VzdGQwZkZDNU5zSkNyTDhaTStNQjhHQTFVZEl3UVlNQmFBRk1ROWhsdVc3dXN0ZDBmRgpDNU5zSkNyTDhaTStNQThHQTFVZEV3RUIvd1FGTUFNQkFmOHdEUVlKS29aSWh2Y05BUUVMQlFBRGdnSUJBSEdOCjQ1Nm1iYXdKUHNLTVgvQlowanB2LytTbCtMTTB6U2gxeEF1YXlmbDk3WnBlbS80QkhGcU5vTUxGOEVqczhXcHoKUHU1Y3Y5VVFaZXNaWVVsNHE4ODY5TW03QnQ5UHVRcUJBR25VbTU3alhMRkRsdDRvVTFvZmVpalF1YkZ3M0wwMwpGa2NsQ3psZ2JhV21vb2ZKRTdKK2FEMGo5bHNOWllKem9tQlN6QnZGTC9uK0ptS0poQVk4SDNwTkNqdExtbXZjClZPbmluQWFScGxLQndSS1RRYm1ZVE53QXVTcEhvSUk4empqK3pGWm54MzVqSitJY0YwblQ1Q3FQT0tCcllxTmwKN21kTnU0OGs0eUpiY0JtYXNoa3BRdkQra2Q1RFJBWmZXZ2tjZzVZUk1RUnE3RnVpWkhxcmFVdWV2WmZ3dnB2UApqMmV5M0QwMG5aSUVIN3I0alVpVnl0SGNGejVQU29zRmIwZDlmWkRJYmJGanRQblpSTEVxbS8wd3N1V25VSVdRCnlSWTNvclNiMUZIYjdYQUlUdHlnZlZQZnlUV0lnemdtbjFCR3Z2eE5sYjIyVnB4TXcvaEpLWTU0WDRjc2s1RzkKbHZMNUVzT3BvYnZvWVJRNU9taHlJT1ZGSHUwcjRKZWkzcGJ3dTczWmlnLzNFanJLY0lRS0ttYzdhQUFkbGREeQpid0dRWDdvYzRLS1lra2JPNFNNQTRTZzUxQjJFZFEzVGYrSHJlUjFTcHN1TlB1U2p0aGY5MGY2eWYrU1d0NU04CnY2RmpVRy9sR0NGTndJTTd2N1o3SHhMVnIvbVg4MTRKVzBGREdLUmhHRHd3SDUzcTJYSmRaaEl5RlNaeWtuc1UKdmJLeW51Vm43czZrU1pYbnh2NnYyTTNsL09ZMjdpNHdUVnB6bzhXbgotLS0tLUVORCBDRVJUSUZJQ0FURS0tLS0tCg==" + } + }, + "processId": 10496 + } + """.trimIndent() + val j = StoveObjectMapper.byConfiguring { this.registerArrowModule() } + val stateWithProcess = j.readValue>(state) + val serialize = j.writeValueAsString(stateWithProcess) + val stateWithProcess2 = j.readValue>(serialize) + + val cert = stateWithProcess2.state.certificate.get() + cert.bytes.size shouldBeGreaterThan 0 + cert.sslContext shouldNotBe null + cert.sslContext.protocol shouldBe "TLSv1.3" + } +}) diff --git a/lib/stove-testing-e2e/src/main/kotlin/com/trendyol/stove/functional/Reflect.kt b/lib/stove-testing-e2e/src/main/kotlin/com/trendyol/stove/functional/Reflect.kt index 265062d7..3e5cc0e9 100644 --- a/lib/stove-testing-e2e/src/main/kotlin/com/trendyol/stove/functional/Reflect.kt +++ b/lib/stove-testing-e2e/src/main/kotlin/com/trendyol/stove/functional/Reflect.kt @@ -18,6 +18,8 @@ class Reflect(val instance: T) { propertySelector: T.() -> KProperty ): OnGoingReflect = OnGoingReflect(instance, propertySelector(instance).name) + inline fun on(property: String): OnGoingReflect = OnGoingReflect(instance, property) + companion object { inline operator fun invoke( instance: T, diff --git a/lib/stove-testing-e2e/src/main/kotlin/com/trendyol/stove/testing/e2e/containers/ExposedCertificate.kt b/lib/stove-testing-e2e/src/main/kotlin/com/trendyol/stove/testing/e2e/containers/ExposedCertificate.kt deleted file mode 100644 index 245c4a78..00000000 --- a/lib/stove-testing-e2e/src/main/kotlin/com/trendyol/stove/testing/e2e/containers/ExposedCertificate.kt +++ /dev/null @@ -1,11 +0,0 @@ -package com.trendyol.stove.testing.e2e.containers - -/** - * Exposed certificate for container - */ -interface ExposedCertificate - -/** - * No certificate exposed - */ -object NoCertificate : ExposedCertificate