Replaced DoormanIntegrationTest with NodeRegistrationTest which does a simple end-to-end registration test using the node driver. (#279)

This commit is contained in:
Shams Asari 2018-01-08 21:45:39 +00:00 committed by GitHub
parent bbcdb639ab
commit 2832eb489b
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
3 changed files with 168 additions and 203 deletions

View File

@ -1,203 +0,0 @@
package com.r3.corda.networkmanage.doorman
import com.nhaarman.mockito_kotlin.doReturn
import com.nhaarman.mockito_kotlin.whenever
import com.r3.corda.networkmanage.common.persistence.configureDatabase
import com.r3.corda.networkmanage.doorman.signer.LocalSigner
import net.corda.core.crypto.Crypto
import net.corda.core.crypto.SecureHash
import net.corda.core.crypto.sign
import net.corda.core.identity.PartyAndCertificate
import net.corda.core.internal.createDirectories
import net.corda.core.internal.div
import net.corda.core.node.NodeInfo
import net.corda.core.serialization.serialize
import net.corda.core.utilities.NetworkHostAndPort
import net.corda.core.utilities.minutes
import net.corda.core.utilities.seconds
import net.corda.node.services.config.NodeConfiguration
import net.corda.node.services.network.NetworkMapClient
import net.corda.node.utilities.registration.HTTPNetworkRegistrationService
import net.corda.node.utilities.registration.NetworkRegistrationHelper
import net.corda.nodeapi.internal.SignedNodeInfo
import net.corda.nodeapi.internal.crypto.*
import net.corda.testing.ALICE_NAME
import net.corda.testing.SerializationEnvironmentRule
import net.corda.testing.common.internal.testNetworkParameters
import net.corda.testing.internal.rigorousMock
import org.junit.Rule
import org.junit.Test
import org.junit.rules.TemporaryFolder
import java.net.URL
import java.security.cert.X509Certificate
import java.util.*
import javax.security.auth.x500.X500Principal
import kotlin.test.assertEquals
import kotlin.test.assertNotNull
class DoormanIntegrationTest {
@Rule
@JvmField
val tempFolder = TemporaryFolder()
@Rule
@JvmField
val testSerialization = SerializationEnvironmentRule(true)
@Test
fun `initial registration`() {
val rootCertAndKey = createDoormanRootCertificateAndKeyPair()
val intermediateCertAndKey = createDoormanIntermediateCertificateAndKeyPair(rootCertAndKey)
//Start doorman server
val doorman = startDoorman(intermediateCertAndKey, rootCertAndKey.certificate)
val doormanHostAndPort = doorman.hostAndPort
// Start Corda network registration.
val config = createConfig().also {
doReturn(URL("http://${doormanHostAndPort.host}:${doormanHostAndPort.port}")).whenever(it).compatibilityZoneURL
}
config.trustStoreFile.parent.createDirectories()
loadOrCreateKeyStore(config.trustStoreFile, config.trustStorePassword).also {
it.addOrReplaceCertificate(X509Utilities.CORDA_ROOT_CA, rootCertAndKey.certificate)
it.save(config.trustStoreFile, config.trustStorePassword)
}
NetworkRegistrationHelper(config, HTTPNetworkRegistrationService(config.compatibilityZoneURL!!)).buildKeystore()
// Checks the keystore are created with the right certificates and keys.
assert(config.nodeKeystore.toFile().exists())
assert(config.sslKeystore.toFile().exists())
assert(config.trustStoreFile.toFile().exists())
val intermediateCACert = intermediateCertAndKey.certificate
val rootCACert = rootCertAndKey.certificate
loadKeyStore(config.nodeKeystore, config.keyStorePassword).apply {
assert(containsAlias(X509Utilities.CORDA_CLIENT_CA))
assertEquals(ALICE_NAME.x500Principal, getX509Certificate(X509Utilities.CORDA_CLIENT_CA).subjectX500Principal)
assertEquals(listOf(intermediateCACert, rootCACert), getCertificateChain(X509Utilities.CORDA_CLIENT_CA).drop(1).toList())
}
loadKeyStore(config.sslKeystore, config.keyStorePassword).apply {
assert(containsAlias(X509Utilities.CORDA_CLIENT_TLS))
assertEquals(ALICE_NAME.x500Principal, getX509Certificate(X509Utilities.CORDA_CLIENT_TLS).subjectX500Principal)
assertEquals(listOf(intermediateCACert, rootCACert), getCertificateChain(X509Utilities.CORDA_CLIENT_TLS).drop(2).toList())
}
loadKeyStore(config.trustStoreFile, config.trustStorePassword).apply {
assert(containsAlias(X509Utilities.CORDA_ROOT_CA))
assertEquals(rootCACert.subjectX500Principal, getX509Certificate(X509Utilities.CORDA_ROOT_CA).subjectX500Principal)
}
doorman.close()
}
@Test
fun `nodeInfo is published to the network map`() {
// Given
val rootCertAndKey = createDoormanRootCertificateAndKeyPair()
val intermediateCertAndKey = createDoormanIntermediateCertificateAndKeyPair(rootCertAndKey)
//Start doorman server
val doorman = startDoorman(intermediateCertAndKey, rootCertAndKey.certificate)
val doormanHostAndPort = doorman.hostAndPort
// Start Corda network registration.
val config = createConfig().also {
doReturn(URL("http://${doormanHostAndPort.host}:${doormanHostAndPort.port}")).whenever(it).compatibilityZoneURL
}
config.trustStoreFile.parent.createDirectories()
loadOrCreateKeyStore(config.trustStoreFile, config.trustStorePassword).also {
it.addOrReplaceCertificate(X509Utilities.CORDA_ROOT_CA, rootCertAndKey.certificate)
it.save(config.trustStoreFile, config.trustStorePassword)
}
NetworkRegistrationHelper(config, HTTPNetworkRegistrationService(config.compatibilityZoneURL!!)).buildKeystore()
// Publish NodeInfo
val networkMapClient = NetworkMapClient(config.compatibilityZoneURL!!, rootCertAndKey.certificate)
val keyStore = loadKeyStore(config.nodeKeystore, config.keyStorePassword)
val clientCertPath = keyStore.getCertificateChain(X509Utilities.CORDA_CLIENT_CA)
val clientCA = keyStore.getCertificateAndKeyPair(X509Utilities.CORDA_CLIENT_CA, config.keyStorePassword)
val identityKeyPair = Crypto.generateKeyPair()
val identityCert = X509Utilities.createCertificate(
CertificateType.LEGAL_IDENTITY,
clientCA.certificate,
clientCA.keyPair,
ALICE_NAME.x500Principal,
identityKeyPair.public)
val certPath = X509CertificateFactory().generateCertPath(identityCert, *clientCertPath)
val nodeInfo = NodeInfo(listOf(NetworkHostAndPort("my.company.com", 1234)), listOf(PartyAndCertificate(certPath)), 1, serial = 1L)
val nodeInfoBytes = nodeInfo.serialize()
// When
val signedNodeInfo = SignedNodeInfo(nodeInfoBytes, listOf(identityKeyPair.private.sign(nodeInfoBytes.bytes)))
networkMapClient.publish(signedNodeInfo)
// Then
val networkMapNodeInfo = networkMapClient.getNodeInfo(nodeInfoBytes.hash)
assertNotNull(networkMapNodeInfo)
assertEquals(nodeInfo, networkMapNodeInfo)
doorman.close()
}
private fun createConfig(): NodeConfiguration {
return rigorousMock<NodeConfiguration>().also {
doReturn(tempFolder.root.toPath()).whenever(it).baseDirectory
doReturn(ALICE_NAME).whenever(it).myLegalName
doReturn(it.baseDirectory / "certificates").whenever(it).certificatesDirectory
doReturn(it.certificatesDirectory / "truststore.jks").whenever(it).trustStoreFile
doReturn(it.certificatesDirectory / "nodekeystore.jks").whenever(it).nodeKeystore
doReturn(it.certificatesDirectory / "sslkeystore.jks").whenever(it).sslKeystore
doReturn("trustpass").whenever(it).trustStorePassword
doReturn("cordacadevpass").whenever(it).keyStorePassword
doReturn("iTest@R3.com").whenever(it).emailAddress
}
}
}
fun createDoormanIntermediateCertificateAndKeyPair(rootCa: CertificateAndKeyPair): CertificateAndKeyPair {
val keyPair = Crypto.generateKeyPair(X509Utilities.DEFAULT_TLS_SIGNATURE_SCHEME)
val intermediateCACert = X509Utilities.createCertificate(
CertificateType.INTERMEDIATE_CA,
rootCa.certificate,
rootCa.keyPair,
X500Principal("CN=Integration Test Corda Node Intermediate CA,O=R3 Ltd,L=London,C=GB"),
keyPair.public)
return CertificateAndKeyPair(intermediateCACert, keyPair)
}
fun createDoormanRootCertificateAndKeyPair(): CertificateAndKeyPair {
val keyPair = Crypto.generateKeyPair(X509Utilities.DEFAULT_TLS_SIGNATURE_SCHEME)
val rootCaCert = X509Utilities.createSelfSignedCACertificate(
X500Principal("CN=Integration Test Corda Node Root CA,O=R3 Ltd,L=London,C=GB"),
keyPair)
return CertificateAndKeyPair(rootCaCert, keyPair)
}
fun makeTestDataSourceProperties(nodeName: String = SecureHash.randomSHA256().toString()): Properties {
val props = Properties()
props.setProperty("dataSourceClassName", "org.h2.jdbcx.JdbcDataSource")
props.setProperty("dataSource.url", "jdbc:h2:mem:${nodeName}_persistence;LOCK_TIMEOUT=10000;DB_CLOSE_ON_EXIT=FALSE")
props.setProperty("dataSource.user", "sa")
props.setProperty("dataSource.password", "")
return props
}
fun startDoorman(intermediateCACertAndKey: CertificateAndKeyPair, rootCACert: X509Certificate): NetworkManagementServer {
val signer = LocalSigner(intermediateCACertAndKey.keyPair, arrayOf(intermediateCACertAndKey.certificate, rootCACert))
//Start doorman server
return startDoorman(signer)
}
fun startDoorman(localSigner: LocalSigner? = null): NetworkManagementServer {
val database = configureDatabase(makeTestDataSourceProperties())
//Start doorman server
val server = NetworkManagementServer()
server.start(NetworkHostAndPort("localhost", 0), database, localSigner, testNetworkParameters(emptyList()), NetworkMapConfig(1.minutes.toMillis(), 1.minutes.toMillis()), DoormanConfig(true, null, 3.seconds.toMillis()))
return server
}

View File

@ -0,0 +1,166 @@
package com.r3.corda.networkmanage.doorman
import com.r3.corda.networkmanage.common.persistence.configureDatabase
import com.r3.corda.networkmanage.doorman.signer.LocalSigner
import net.corda.cordform.CordformNode
import net.corda.core.crypto.random63BitValue
import net.corda.core.identity.CordaX500Name
import net.corda.core.internal.concurrent.transpose
import net.corda.core.internal.div
import net.corda.core.internal.exists
import net.corda.core.internal.list
import net.corda.core.messaging.startFlow
import net.corda.core.utilities.OpaqueBytes
import net.corda.core.utilities.getOrThrow
import net.corda.core.utilities.seconds
import net.corda.finance.DOLLARS
import net.corda.finance.flows.CashIssueAndPaymentFlow
import net.corda.nodeapi.internal.crypto.CertificateAndKeyPair
import net.corda.nodeapi.internal.network.NetworkParameters
import net.corda.testing.SerializationEnvironmentRule
import net.corda.testing.common.internal.testNetworkParameters
import net.corda.testing.driver.NodeHandle
import net.corda.testing.driver.PortAllocation
import net.corda.testing.internal.IntegrationTest
import net.corda.testing.internal.IntegrationTestSchemas
import net.corda.testing.internal.createDevIntermediateCaCertPath
import net.corda.testing.node.NotarySpec
import net.corda.testing.node.internal.CompatibilityZoneParams
import net.corda.testing.node.internal.internalDriver
import net.corda.testing.singleIdentity
import org.assertj.core.api.Assertions.assertThat
import org.junit.*
import java.net.URL
import java.security.cert.X509Certificate
import java.util.*
import kotlin.streams.toList
// This is the same test as the one in net.corda.node.utilities.registration but using the real doorman and with some
// extra checks on the network map.
class NodeRegistrationTest : IntegrationTest() {
companion object {
private val notaryName = CordaX500Name("NotaryService", "Zurich", "CH")
private val aliceName = CordaX500Name("Alice", "London", "GB")
private val genevieveName = CordaX500Name("Genevieve", "London", "GB")
@ClassRule
@JvmField
val databaseSchemas = IntegrationTestSchemas(notaryName.organisation, aliceName.organisation, genevieveName.organisation)
private val timeoutMillis = 5.seconds.toMillis()
}
@Rule
@JvmField
val testSerialization = SerializationEnvironmentRule(true)
private val portAllocation = PortAllocation.Incremental(10000)
private val serverAddress = portAllocation.nextHostAndPort()
private val dbId = random63BitValue().toString()
private lateinit var rootCaCert: X509Certificate
private lateinit var intermediateCa: CertificateAndKeyPair
private var server: NetworkManagementServer? = null
@Before
fun init() {
val (rootCa, intermediateCa) = createDevIntermediateCaCertPath()
rootCaCert = rootCa.certificate
this.intermediateCa = intermediateCa
}
@After
fun cleanUp() {
server?.close()
}
@Test
fun `register nodes with doorman and then they transact with each other`() {
// Start the server without the network parameters since we don't have them yet
server = startNetworkManagementServer(networkParameters = null)
val compatibilityZone = CompatibilityZoneParams(
URL("http://$serverAddress"),
publishNotaries = { notaryInfos ->
// Restart the server once we're able to generate the network parameters
server!!.close()
server = startNetworkManagementServer(testNetworkParameters(notaryInfos))
// Once restarted we delay starting the nodes to make sure the network map server has processed the
// network parameters, otherwise the nodes will fail to start as the network parameters won't be
// available for them to download.
Thread.sleep(2 * timeoutMillis)
},
rootCert = rootCaCert
)
internalDriver(
portAllocation = portAllocation,
compatibilityZone = compatibilityZone,
initialiseSerialization = false,
notarySpecs = listOf(NotarySpec(notaryName)),
extraCordappPackagesToScan = listOf("net.corda.finance")
) {
val (alice, notary) = listOf(
startNode(providedName = aliceName),
defaultNotaryNode
).transpose().getOrThrow()
alice.onlySeesFromNetworkMap(alice, notary)
notary.onlySeesFromNetworkMap(alice, notary)
val genevieve = startNode(providedName = genevieveName).getOrThrow()
// Wait for the nodes to poll again
Thread.sleep(timeoutMillis * 2)
// Make sure the new node is visible to everyone
alice.onlySeesFromNetworkMap(alice, genevieve, notary)
notary.onlySeesFromNetworkMap(alice, genevieve, notary)
genevieve.onlySeesFromNetworkMap(alice, genevieve, notary)
// Check the nodes can communicate among themselves (and the notary).
val anonymous = false
genevieve.rpc.startFlow(
::CashIssueAndPaymentFlow,
1000.DOLLARS,
OpaqueBytes.of(12),
alice.nodeInfo.singleIdentity(),
anonymous,
defaultNotaryIdentity
).returnValue.getOrThrow()
}
}
private fun startNetworkManagementServer(networkParameters: NetworkParameters?): NetworkManagementServer {
return NetworkManagementServer().apply {
start(
serverAddress,
configureDatabase(makeTestDataSourceProperties()),
LocalSigner(intermediateCa.keyPair, arrayOf(intermediateCa.certificate, rootCaCert)),
networkParameters,
networkParameters?.let { NetworkMapConfig(cacheTimeout = timeoutMillis, signInterval = timeoutMillis) },
DoormanConfig(approveAll = true, jiraConfig = null, approveInterval = timeoutMillis)
)
}
}
private fun NodeHandle.onlySeesFromNetworkMap(vararg nodes: NodeHandle) {
// Make sure the nodes aren't getting the node infos from their additional directories
val nodeInfosDir = configuration.baseDirectory / CordformNode.NODE_INFO_DIRECTORY
if (nodeInfosDir.exists()) {
assertThat(nodeInfosDir.list { it.toList() }).isEmpty()
}
assertThat(rpc.networkMapSnapshot()).containsOnlyElementsOf(nodes.map { it.nodeInfo })
}
// TODO Use the other dbs in the integration tests
private fun makeTestDataSourceProperties(): Properties {
val props = Properties()
props.setProperty("dataSourceClassName", "org.h2.jdbcx.JdbcDataSource")
props.setProperty("dataSource.url", "jdbc:h2:mem:${dbId}_persistence;LOCK_TIMEOUT=10000;DB_CLOSE_ON_EXIT=FALSE")
props.setProperty("dataSource.user", "sa")
props.setProperty("dataSource.password", "")
return props
}
}

View File

@ -88,6 +88,7 @@ class PersistentNetworkMapStorage(private val database: CordaPersistence, privat
return database.transaction {
val builder = session.criteriaBuilder
val query = builder.createQuery(NetworkParametersEntity::class.java).run {
// TODO a limit of 1 since we only need the first result
from(NetworkParametersEntity::class.java).run {
orderBy(builder.desc(get<String>(NetworkParametersEntity::version.name)))
}
@ -102,6 +103,7 @@ class PersistentNetworkMapStorage(private val database: CordaPersistence, privat
val builder = session.criteriaBuilder
val query = builder.createQuery(NetworkMapEntity::class.java).run {
from(NetworkMapEntity::class.java).run {
// TODO a limit of 1 since we only need the first result
where(builder.isNotNull(get<ByteArray?>(NetworkMapEntity::signature.name)))
orderBy(builder.desc(get<String>(NetworkMapEntity::version.name)))
}