From 4c7dc581355885fab49c36180a9f83acb5091500 Mon Sep 17 00:00:00 2001 From: mkit Date: Mon, 13 Nov 2017 11:50:24 +0000 Subject: [PATCH] Adding HSM simulator to integration tests (#92) * Adding HSM simulator to integration tests * Addressing review comments * Adjusting implementation to the agreed TC setup * Addressing review comments * Addressing review comments - round 2 --- network-management/build.gradle | 1 + .../r3/corda/networkmanage/HsmSimulator.kt | 126 ++++++++++++++++++ .../com/r3/corda/networkmanage/hsm/HsmTest.kt | 53 ++++++++ 3 files changed, 180 insertions(+) create mode 100644 network-management/src/integration-test/kotlin/com/r3/corda/networkmanage/HsmSimulator.kt create mode 100644 network-management/src/integration-test/kotlin/com/r3/corda/networkmanage/hsm/HsmTest.kt diff --git a/network-management/build.gradle b/network-management/build.gradle index 00cd79b861..c2e3d1d7cb 100644 --- a/network-management/build.gradle +++ b/network-management/build.gradle @@ -121,6 +121,7 @@ dependencies { testCompile 'junit:junit:4.12' testCompile "org.assertj:assertj-core:${assertj_version}" testCompile "com.nhaarman:mockito-kotlin:0.6.1" + testCompile "com.spotify:docker-client:8.9.1" compile('com.atlassian.jira:jira-rest-java-client-core:4.0.0') { // The jira client includes jersey-core 1.5 which breaks everything. diff --git a/network-management/src/integration-test/kotlin/com/r3/corda/networkmanage/HsmSimulator.kt b/network-management/src/integration-test/kotlin/com/r3/corda/networkmanage/HsmSimulator.kt new file mode 100644 index 0000000000..39d68a5a2f --- /dev/null +++ b/network-management/src/integration-test/kotlin/com/r3/corda/networkmanage/HsmSimulator.kt @@ -0,0 +1,126 @@ +package com.r3.corda.networkmanage + +import com.spotify.docker.client.DefaultDockerClient +import com.spotify.docker.client.DockerClient +import com.spotify.docker.client.messages.ContainerConfig +import com.spotify.docker.client.messages.HostConfig +import com.spotify.docker.client.messages.PortBinding +import com.spotify.docker.client.messages.RegistryAuth +import net.corda.core.utilities.loggerFor +import net.corda.testing.freeLocalHostAndPort +import org.junit.Assume.assumeFalse +import org.junit.rules.ExternalResource + +data class CryptoUserCredentials(val username: String, val password: String) + +/** + * HSM Simulator rule allowing to use the HSM Simulator within the integration tests. It is designed to be used mainly + * on the TeamCity, but if the required setup is available it can be executed locally as well. + * It will bind to the simulator to the local port + * To use it locally, the following pre-requisites need to be met: + * 1) Docker engine needs to be installed on the machine + * 2) Environment variables (AZURE_CR_USER and AZURE_CR_PASS) are available and hold valid credentials to the corda.azurecr.io + * repository + * 3) HSM requires Unlimited Strength Jurisdiction extension to be installed on the machine connecting with the HSM box. + * See http://www.oracle.com/technetwork/java/javase/downloads/jce8-download-2133166.html + * + * Since the above setup is not a strong requirement for the integration tests to be executed it is intended that this + * rule is used together with the assumption mechanism in tests. + */ +class HsmSimulator(private val serverAddress: String = DEFAULT_SERVER_ADDRESS, + private val imageRepoTag: String = DEFAULT_IMAGE_REPO_TAG, + private val imageVersion: String = DEFAULT_IMAGE_VERSION) : ExternalResource() { + + private companion object { + val DEFAULT_SERVER_ADDRESS = "corda.azurecr.io" + val DEFAULT_IMAGE_REPO_TAG = "corda.azurecr.io/network-management/hsm-simulator" + val DEFAULT_IMAGE_VERSION = "latest" + + val HSM_SIMULATOR_PORT = "3001/tcp" + val CONTAINER_KILL_TIMEOUT_SECONDS = 10 + + val CRYPTO_USER = System.getProperty("CRYPTO_USER", "INTEGRATION_TEST") + val CRYPTO_PASSWORD = System.getProperty("CRYPTO_PASSWORD", "INTEGRATION_TEST") + + val REGISTRY_USERNAME = System.getenv("AZURE_CR_USER") + val REGISTRY_PASSWORD = System.getenv("AZURE_CR_PASS") + + val log = loggerFor() + } + + private val localHostAndPortBinding = freeLocalHostAndPort() + private lateinit var docker: DockerClient + private var containerId: String? = null + + override fun before() { + assumeFalse("Docker registry username is not set!. Skipping the test.", REGISTRY_USERNAME.isNullOrBlank()) + assumeFalse("Docker registry password is not set!. Skipping the test.", REGISTRY_PASSWORD.isNullOrBlank()) + docker = DefaultDockerClient.fromEnv().build().pullHsmSimulatorImageFromRepository() + containerId = docker.createContainer() + docker.startHsmSimulatorContainer() + } + + override fun after() { + docker.stopAndRemoveHsmSimulatorContainer() + } + + /** + * Retrieves the port at which simulator is listening at. + */ + val port get(): Int = localHostAndPortBinding.port + + /** + * Retrieves the host IP address, which the simulator is listening at. + */ + val host get(): String = localHostAndPortBinding.host + + /** + * Retrieves the HSM user credentials. Those are supposed to be preconfigured on the HSM itself. Thus, when + * tests are executed these credentials can be used to access HSM crypto user's functionality. + * It is assumed that the docker image state has those configured already and they should match the ones returned. + */ + fun cryptoUserCredentials(): CryptoUserCredentials { + return CryptoUserCredentials(CRYPTO_USER, CRYPTO_PASSWORD) + } + + private fun DockerClient.stopAndRemoveHsmSimulatorContainer() { + if (containerId != null) { + log.debug("Stopping container $containerId...") + this.stopContainer(containerId, CONTAINER_KILL_TIMEOUT_SECONDS) + log.debug("Removing container $containerId...") + this.removeContainer(containerId) + } + } + + private fun DockerClient.startHsmSimulatorContainer() { + if (containerId != null) { + log.debug("Starting container $containerId...") + this.startContainer(containerId) + } + } + + private fun getImageFullName() = "$imageRepoTag:$imageVersion" + + private fun DockerClient.pullHsmSimulatorImageFromRepository(): DockerClient { + this.pull(imageRepoTag, + RegistryAuth.builder() + .serverAddress(serverAddress) + .username(REGISTRY_USERNAME) + .password(REGISTRY_PASSWORD) + .build()) + return this + } + + private fun DockerClient.createContainer(): String? { + val portBindings = mapOf(HSM_SIMULATOR_PORT to listOf(PortBinding.create(localHostAndPortBinding.host, localHostAndPortBinding.port.toString()))) + val hostConfig = HostConfig.builder().portBindings(portBindings).build() + val containerConfig = ContainerConfig.builder() + .hostConfig(hostConfig) + .portSpecs() + .image(getImageFullName()) + .exposedPorts(HSM_SIMULATOR_PORT) + .build() + val containerCreation = this.createContainer(containerConfig) + return containerCreation.id() + } +} \ No newline at end of file diff --git a/network-management/src/integration-test/kotlin/com/r3/corda/networkmanage/hsm/HsmTest.kt b/network-management/src/integration-test/kotlin/com/r3/corda/networkmanage/hsm/HsmTest.kt new file mode 100644 index 0000000000..49df4fc685 --- /dev/null +++ b/network-management/src/integration-test/kotlin/com/r3/corda/networkmanage/hsm/HsmTest.kt @@ -0,0 +1,53 @@ +package com.r3.corda.networkmanage.hsm + +import com.nhaarman.mockito_kotlin.any +import com.nhaarman.mockito_kotlin.mock +import com.nhaarman.mockito_kotlin.whenever +import com.r3.corda.networkmanage.HsmSimulator +import com.r3.corda.networkmanage.hsm.authentication.Authenticator +import com.r3.corda.networkmanage.hsm.authentication.createProvider +import com.r3.corda.networkmanage.hsm.configuration.Parameters +import org.junit.Before +import org.junit.Rule +import org.junit.Test +import java.io.Console +import kotlin.test.assertTrue + +class HsmTest { + + @Rule + @JvmField + val hsmSimulator: HsmSimulator = HsmSimulator() + + private var console: Console? = null + + @Before + fun setUp() { + console = mock() + } + + @Test + fun `Authenticator executes the block once user is successfully authenticated`() { + // given + val parameters = Parameters( + dataSourceProperties = mock(), + device = "${hsmSimulator.port}@${hsmSimulator.host}", + keySpecifier = 1, + keyGroup = "*" + ) + whenever(console?.readLine()).thenReturn(hsmSimulator.cryptoUserCredentials().username) + whenever(console?.readPassword(any())).thenReturn(hsmSimulator.cryptoUserCredentials().password.toCharArray()) + val authenticator = Authenticator(parameters.createProvider(), console = console) + var executed = false + + // when + authenticator.connectAndAuthenticate({ provider, signers -> + executed = true + }) + + // then + assertTrue(executed) + } + + +} \ No newline at end of file