@@ -10,7 +10,6 @@ import org.openprojectx.bigdata.test.core.KerberosAuthOptions
1010import org.openprojectx.bigdata.test.core.KafkaOptions
1111import org.openprojectx.bigdata.test.core.KerberosOptions
1212import org.openprojectx.hive.docker.testcontainers.HiveMetastoreContainer
13- import org.testcontainers.containers.BindMode
1413import org.testcontainers.containers.GenericContainer
1514import org.testcontainers.containers.Network
1615import org.testcontainers.containers.PostgreSQLContainer
@@ -20,6 +19,7 @@ import org.testcontainers.containers.output.OutputFrame
2019import org.testcontainers.images.builder.Transferable
2120import org.testcontainers.lifecycle.Startable
2221import org.testcontainers.utility.DockerImageName
22+ import org.testcontainers.utility.MountableFile
2323import java.io.OutputStreamWriter
2424import java.io.Closeable
2525import java.nio.file.Files
@@ -77,13 +77,10 @@ internal class BigDataContainerFactory(
7777 addIfEnabled(options.kafka.kafkaUiKerberos)
7878 if (options.kafka.kerberos.enabled && options.kafka.kafkaUiEnabled) addPrincipal(options.kafka.kafkaUiKerberos)
7979 }
80- writeContainerKerberosConf(kerberos)
81-
8280 val container = GenericBigDataContainer (kerberos.image)
8381 .withNetwork(network)
8482 .withNetworkAliases(" kerby-kdc" )
8583 .withServicePort(88 , options.portBindings.hostPort(88 , options.portBindings.kerberosKdc))
86- .withFileSystemBind(kerberosDirectory(), " /var/lib/kerby" )
8784 .withEnv(" KERBY_REALM" , kerberos.realm)
8885 .withEnv(" KERBY_KDC_HOST" , " kerby-kdc" )
8986 .withEnv(" KERBY_KDC_BIND_HOST" , " 0.0.0.0" )
@@ -102,7 +99,11 @@ internal class BigDataContainerFactory(
10299 container.withEnv(" KERBY_EXTRA_PRINCIPALS" , users)
103100 }
104101
105- return BigDataServiceContainer (BigDataService .KERBEROS , attachLogs(" kerberos" , container)) {
102+ return BigDataServiceContainer (
103+ service = BigDataService .KERBEROS ,
104+ container = attachLogs(" kerberos" , container),
105+ afterStart = { copyKerberosMaterialFromContainer(container) },
106+ ) {
106107 val localKrb5Conf = writeLocalKerberosConf(kerberos, container.host, container.getMappedPort(88 ))
107108 BigDataEndpoint (
108109 service = BigDataService .KERBEROS ,
@@ -399,7 +400,10 @@ internal class BigDataContainerFactory(
399400 .waitingFor(Wait .forHttp(" /subjects" ).forStatusCode(200 ).withStartupTimeout(Duration .ofMinutes(3 )))
400401 if (kafka.tls.enabled && ! kafka.kerberos.enabled) {
401402 container
402- .withFileSystemBind(tlsMaterial.trustStorePath.toString(), " /etc/schema-registry/tls/truststore.p12" , BindMode .READ_ONLY )
403+ .withCopyFileToContainer(
404+ MountableFile .forHostPath(tlsMaterial.trustStorePath),
405+ " /etc/schema-registry/tls/truststore.p12" ,
406+ )
403407 .withEnv(" SCHEMA_REGISTRY_KAFKASTORE_BOOTSTRAP_SERVERS" , " SSL://kafka:9093" )
404408 .withEnv(" SCHEMA_REGISTRY_KAFKASTORE_SECURITY_PROTOCOL" , " SSL" )
405409 .withEnv(" SCHEMA_REGISTRY_KAFKASTORE_SSL_TRUSTSTORE_LOCATION" , " /etc/schema-registry/tls/truststore.p12" )
@@ -448,7 +452,7 @@ internal class BigDataContainerFactory(
448452 if (kafka.tls.enabled) {
449453 val trustStore = tlsMaterial.trustStorePath
450454 container
451- .withFileSystemBind(trustStore.toString( ), " /etc/kafka/tls/truststore.p12" , BindMode . READ_ONLY )
455+ .withCopyFileToContainer( MountableFile .forHostPath(trustStore ), " /etc/kafka/tls/truststore.p12" )
452456 .withEnv(" KAFKA_CLUSTERS_0_BOOTSTRAPSERVERS" , if (kafka.kerberos.enabled) " broker1.example.com:9092" else " kafka:9093" )
453457 .withEnv(" KAFKA_CLUSTERS_0_PROPERTIES_SECURITY_PROTOCOL" , if (kafka.kerberos.enabled) " SASL_SSL" else " SSL" )
454458 .withEnv(" KAFKA_CLUSTERS_0_PROPERTIES_SSL_TRUSTSTORE_LOCATION" , " /etc/kafka/tls/truststore.p12" )
@@ -678,8 +682,8 @@ internal class BigDataContainerFactory(
678682 .withNetwork(network)
679683 .withNetworkAliases(" $name -tls" )
680684 .withServicePort(443 , hostPort)
681- .withFileSystemBind(config.toString( ), " /usr/local/etc/haproxy/haproxy.cfg" , BindMode . READ_ONLY )
682- .withFileSystemBind(pem.toString( ), " /usr/local/etc/haproxy/certs/service.pem" , BindMode . READ_ONLY )
685+ .withCopyFileToContainer( MountableFile .forHostPath(config ), " /usr/local/etc/haproxy/haproxy.cfg" )
686+ .withCopyFileToContainer( MountableFile .forHostPath(pem ), " /usr/local/etc/haproxy/certs/service.pem" )
683687 .waitingFor(Wait .forListeningPort().withStartupTimeout(Duration .ofMinutes(2 )))
684688 val proxied = attachLogs(" $name -tls" , container)
685689 proxied.start()
@@ -709,8 +713,11 @@ internal class BigDataContainerFactory(
709713 sanDomains = listOf (" kafka" , " broker1.example.com" ),
710714 )
711715 container
712- .withFileSystemBind(keyStore.path.toString(), " /etc/kafka/secrets/kafka.keystore.p12" , BindMode .READ_ONLY )
713- .withFileSystemBind(tlsMaterial.trustStorePath.toString(), " /etc/kafka/secrets/kafka.truststore.p12" , BindMode .READ_ONLY )
716+ .withCopyFileToContainer(MountableFile .forHostPath(keyStore.path), " /etc/kafka/secrets/kafka.keystore.p12" )
717+ .withCopyFileToContainer(
718+ MountableFile .forHostPath(tlsMaterial.trustStorePath),
719+ " /etc/kafka/secrets/kafka.truststore.p12" ,
720+ )
714721 .withCopyToContainer(Transferable .of(keyStore.password), " /etc/kafka/secrets/kafka.key.credentials" )
715722 .withCopyToContainer(Transferable .of(keyStore.password), " /etc/kafka/secrets/kafka.keystore.credentials" )
716723 .withCopyToContainer(Transferable .of(tlsMaterial.trustStorePassword), " /etc/kafka/secrets/kafka.truststore.credentials" )
@@ -877,12 +884,79 @@ internal class BigDataContainerFactory(
877884
878885 private fun mountKerberos (container : GenericContainer <* >) {
879886 writeContainerKerberosConf(options.kerberos)
880- container.withFileSystemBind(kerberosDirectory(), " /kerby" , BindMode .READ_ONLY )
887+ copyKerberosFileToContainer(container, " /kerby/client/krb5.conf" )
888+ kerberosContainerPaths().forEach { copyKerberosFileToContainer(container, it) }
881889 }
882890
883891 private fun kerberosDirectory (): String =
884892 kerberosDir?.toString() ? : error(" Kerberos directory was not initialized" )
885893
894+ private fun copyKerberosMaterialFromContainer (container : GenericContainer <* >) {
895+ val paths = kerberosContainerPaths()
896+ waitForKerberosFiles(container, paths)
897+ paths.forEach { containerPath ->
898+ copyKerberosFileFromContainer(
899+ container = container,
900+ source = containerPath.replace(" /kerby/" , " /var/lib/kerby/" ),
901+ destination = containerPath,
902+ )
903+ }
904+ }
905+
906+ private fun copyKerberosFileFromContainer (
907+ container : GenericContainer <* >,
908+ source : String ,
909+ destination : String ,
910+ ) {
911+ val path = hostKerberosMaterialPath(destination)
912+ Files .createDirectories(path.parent)
913+ container.copyFileFromContainer(source, path.toString())
914+ }
915+
916+ private fun waitForKerberosFiles (container : GenericContainer <* >, containerPaths : Set <String >) {
917+ val sources = containerPaths.map { it.replace(" /kerby/" , " /var/lib/kerby/" ) }
918+ val deadline = System .nanoTime() + Duration .ofSeconds(30 ).toNanos()
919+ while (true ) {
920+ val missing = sources.filterNot { source ->
921+ container.execInContainer(" sh" , " -lc" , " test -s '$source '" ).exitCode == 0
922+ }
923+ if (missing.isEmpty()) return
924+ if (System .nanoTime() >= deadline) {
925+ error(" Kerberos KDC did not generate expected keytab files: ${missing.joinToString(" , " )} " )
926+ }
927+ Thread .sleep(250 )
928+ }
929+ }
930+
931+ private fun copyKerberosFileToContainer (container : GenericContainer <* >, containerPath : String ) {
932+ val path = hostKerberosMaterialPath(containerPath)
933+ Files .createDirectories(path.parent)
934+ if (Files .notExists(path)) {
935+ Files .write(path, ByteArray (0 ))
936+ }
937+ container.withCopyFileToContainer(MountableFile .forHostPath(path), containerPath)
938+ }
939+
940+ private fun kerberosContainerPaths (): Set <String > =
941+ buildSet {
942+ add(" /kerby/keytabs/client.keytab" )
943+ options.kerberos.users.forEach { add(it.keytabPath) }
944+ addIfEnabled(options.hdfs.kerberos)
945+ addIfEnabled(options.hiveMetastore.kerberos)
946+ addIfEnabled(options.kafka.kerberos)
947+ addIfEnabled(options.kafka.kafkaUiKerberos)
948+ if (options.kafka.kerberos.enabled && options.kafka.kafkaUiEnabled) add(options.kafka.kafkaUiKerberos.keytabPath)
949+ }
950+
951+ private fun MutableSet<String>.addIfEnabled (options : KerberosAuthOptions ) {
952+ if (options.enabled) add(options.keytabPath)
953+ }
954+
955+ private fun hostKerberosMaterialPath (containerPath : String ): Path {
956+ require(containerPath.startsWith(" /kerby/" )) { " Kerberos material path must be under /kerby: $containerPath " }
957+ return Path .of(kerberosDirectory()).resolve(containerPath.removePrefix(" /kerby/" ))
958+ }
959+
886960 private fun kerberosProperties (prefix : String , options : KerberosAuthOptions ): Map <String , String > =
887961 if (options.enabled) {
888962 mapOf (
0 commit comments