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
This commit is contained in:
mkit 2017-11-13 11:50:24 +00:00 committed by GitHub
parent 12124bc0d9
commit 4c7dc58135
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
3 changed files with 180 additions and 0 deletions

View File

@ -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.

View File

@ -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<HsmSimulator>()
}
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()
}
}

View File

@ -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)
}
}