From e22e7acd6795c37a95324569acdba9f601d668cb Mon Sep 17 00:00:00 2001 From: Shams Asari Date: Tue, 13 Mar 2018 10:58:04 +0000 Subject: [PATCH] Various cleanups of the network-management code (#545) --- .../doorman/NodeRegistrationTest.kt | 15 +- ...roveAllCertificateSigningRequestStorage.kt | 16 ++ .../common/persistence/NodeInfoStorage.kt | 7 +- .../common/persistence/NodeInfoWithSigned.kt | 18 --- .../persistence/PersistentNodeInfoStorage.kt | 19 ++- .../doorman/DoormanParameters.kt | 10 +- .../r3/corda/networkmanage/doorman/Main.kt | 141 +++++++++--------- .../doorman/NetworkManagementServer.kt | 7 +- .../doorman/NetworkParametersConfig.kt | 68 +++++++++ .../doorman/NetworkParametersConfiguration.kt | 84 ----------- .../webservice/NetworkMapWebService.kt | 39 +++-- .../NetworkParametersConfigTest.kt | 76 ++++++++++ .../NetworkParametersConfigurationTest.kt | 79 ---------- .../PersistentNodeInfoStorageTest.kt | 9 +- 14 files changed, 282 insertions(+), 306 deletions(-) create mode 100644 network-management/src/main/kotlin/com/r3/corda/networkmanage/common/persistence/ApproveAllCertificateSigningRequestStorage.kt delete mode 100644 network-management/src/main/kotlin/com/r3/corda/networkmanage/common/persistence/NodeInfoWithSigned.kt create mode 100644 network-management/src/main/kotlin/com/r3/corda/networkmanage/doorman/NetworkParametersConfig.kt delete mode 100644 network-management/src/main/kotlin/com/r3/corda/networkmanage/doorman/NetworkParametersConfiguration.kt create mode 100644 network-management/src/test/kotlin/com/r3/corda/networkmanage/NetworkParametersConfigTest.kt delete mode 100644 network-management/src/test/kotlin/com/r3/corda/networkmanage/NetworkParametersConfigurationTest.kt diff --git a/network-management/src/integration-test/kotlin/com/r3/corda/networkmanage/doorman/NodeRegistrationTest.kt b/network-management/src/integration-test/kotlin/com/r3/corda/networkmanage/doorman/NodeRegistrationTest.kt index e98ad63697..44bf2f9cdb 100644 --- a/network-management/src/integration-test/kotlin/com/r3/corda/networkmanage/doorman/NodeRegistrationTest.kt +++ b/network-management/src/integration-test/kotlin/com/r3/corda/networkmanage/doorman/NodeRegistrationTest.kt @@ -17,7 +17,6 @@ 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 @@ -94,7 +93,9 @@ class NodeRegistrationTest : IntegrationTest() { @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 + // Start the server without the network parameters config which won't start the network map. Just the doorman + // registration process will start up, allowing us to register the notaries which will then be used in the network + // parameters. server = startNetworkManagementServer(networkParameters = null) val compatibilityZone = CompatibilityZoneParams( URL("http://$serverAddress"), @@ -120,14 +121,12 @@ class NodeRegistrationTest : IntegrationTest() { val (alice, notary) = listOf( startNode(providedName = aliceName), defaultNotaryNode - ).transpose().getOrThrow() - alice as NodeHandleInternal - notary as NodeHandleInternal + ).map { it.getOrThrow() as NodeHandleInternal } + alice.onlySeesFromNetworkMap(alice, notary) notary.onlySeesFromNetworkMap(alice, notary) - val genevieve = startNode(providedName = genevieveName).getOrThrow() - genevieve as NodeHandleInternal + val genevieve = startNode(providedName = genevieveName).getOrThrow() as NodeHandleInternal // Wait for the nodes to poll again Thread.sleep(timeoutMillis * 2) @@ -161,7 +160,7 @@ class NodeRegistrationTest : IntegrationTest() { networkParameters?.let { NetworkMapStartParams( LocalSigner(networkMapCa), - networkParameters, + it, NetworkMapConfig(cacheTimeout = timeoutMillis, signInterval = timeoutMillis) ) } diff --git a/network-management/src/main/kotlin/com/r3/corda/networkmanage/common/persistence/ApproveAllCertificateSigningRequestStorage.kt b/network-management/src/main/kotlin/com/r3/corda/networkmanage/common/persistence/ApproveAllCertificateSigningRequestStorage.kt new file mode 100644 index 0000000000..4c249cec6a --- /dev/null +++ b/network-management/src/main/kotlin/com/r3/corda/networkmanage/common/persistence/ApproveAllCertificateSigningRequestStorage.kt @@ -0,0 +1,16 @@ +package com.r3.corda.networkmanage.common.persistence + +import com.r3.corda.networkmanage.common.persistence.CertificateSigningRequestStorage +import org.bouncycastle.pkcs.PKCS10CertificationRequest + +/** + * This storage automatically approves all created requests. + */ +class ApproveAllCertificateSigningRequestStorage(private val delegate: CertificateSigningRequestStorage) : CertificateSigningRequestStorage by delegate { + override fun saveRequest(request: PKCS10CertificationRequest): String { + val requestId = delegate.saveRequest(request) + delegate.markRequestTicketCreated(requestId) + approveRequest(requestId, CertificateSigningRequestStorage.DOORMAN_SIGNATURE) + return requestId + } +} diff --git a/network-management/src/main/kotlin/com/r3/corda/networkmanage/common/persistence/NodeInfoStorage.kt b/network-management/src/main/kotlin/com/r3/corda/networkmanage/common/persistence/NodeInfoStorage.kt index 2703e3af7d..d208db8d6d 100644 --- a/network-management/src/main/kotlin/com/r3/corda/networkmanage/common/persistence/NodeInfoStorage.kt +++ b/network-management/src/main/kotlin/com/r3/corda/networkmanage/common/persistence/NodeInfoStorage.kt @@ -12,6 +12,7 @@ package com.r3.corda.networkmanage.common.persistence import net.corda.core.crypto.SecureHash import net.corda.core.node.NodeInfo +import net.corda.nodeapi.internal.NodeInfoAndSigned import net.corda.nodeapi.internal.SignedNodeInfo import java.security.cert.CertPath @@ -32,9 +33,9 @@ interface NodeInfoStorage { fun getNodeInfo(nodeInfoHash: SecureHash): SignedNodeInfo? /** - * The [nodeInfo] is keyed by the public key, old node info with the same public key will be replaced by the new node info. - * @param signedNodeInfo signed node info data to be stored + * The [nodeInfoAndSigned] is keyed by the public key, old node info with the same public key will be replaced by the new node info. + * @param nodeInfoAndSigned signed node info data to be stored * @return hash for the newly created node info entry */ - fun putNodeInfo(signedNodeInfo: NodeInfoWithSigned): SecureHash + fun putNodeInfo(nodeInfoAndSigned: NodeInfoAndSigned): SecureHash } \ No newline at end of file diff --git a/network-management/src/main/kotlin/com/r3/corda/networkmanage/common/persistence/NodeInfoWithSigned.kt b/network-management/src/main/kotlin/com/r3/corda/networkmanage/common/persistence/NodeInfoWithSigned.kt deleted file mode 100644 index edbef807ef..0000000000 --- a/network-management/src/main/kotlin/com/r3/corda/networkmanage/common/persistence/NodeInfoWithSigned.kt +++ /dev/null @@ -1,18 +0,0 @@ -/* - * R3 Proprietary and Confidential - * - * Copyright (c) 2018 R3 Limited. All rights reserved. - * - * The intellectual and technical concepts contained herein are proprietary to R3 and its suppliers and are protected by trade secret law. - * - * Distribution of this file or any portion thereof via any medium without the express permission of R3 is strictly prohibited. - */ - -package com.r3.corda.networkmanage.common.persistence - -import net.corda.core.node.NodeInfo -import net.corda.nodeapi.internal.SignedNodeInfo - -class NodeInfoWithSigned(val signedNodeInfo: SignedNodeInfo) { - val nodeInfo: NodeInfo = signedNodeInfo.verified() -} \ No newline at end of file diff --git a/network-management/src/main/kotlin/com/r3/corda/networkmanage/common/persistence/PersistentNodeInfoStorage.kt b/network-management/src/main/kotlin/com/r3/corda/networkmanage/common/persistence/PersistentNodeInfoStorage.kt index 130ba4b383..15219edd90 100644 --- a/network-management/src/main/kotlin/com/r3/corda/networkmanage/common/persistence/PersistentNodeInfoStorage.kt +++ b/network-management/src/main/kotlin/com/r3/corda/networkmanage/common/persistence/PersistentNodeInfoStorage.kt @@ -16,7 +16,9 @@ import com.r3.corda.networkmanage.common.utils.buildCertPath import net.corda.core.crypto.SecureHash import net.corda.core.crypto.sha256 import net.corda.core.internal.CertRole +import net.corda.core.internal.CertRole.NODE_CA import net.corda.core.serialization.serialize +import net.corda.nodeapi.internal.NodeInfoAndSigned import net.corda.nodeapi.internal.SignedNodeInfo import net.corda.nodeapi.internal.crypto.x509Certificates import net.corda.nodeapi.internal.persistence.CordaPersistence @@ -27,17 +29,18 @@ import java.security.cert.CertPath * Database implementation of the [NetworkMapStorage] interface */ class PersistentNodeInfoStorage(private val database: CordaPersistence) : NodeInfoStorage { - override fun putNodeInfo(nodeInfoWithSigned: NodeInfoWithSigned): SecureHash { - val nodeInfo = nodeInfoWithSigned.nodeInfo - val signedNodeInfo = nodeInfoWithSigned.signedNodeInfo - val nodeCaCert = nodeInfo.legalIdentitiesAndCerts[0].certPath.x509Certificates.find { CertRole.extract(it) == CertRole.NODE_CA } + override fun putNodeInfo(nodeInfoAndSigned: NodeInfoAndSigned): SecureHash { + val (nodeInfo, signedNodeInfo) = nodeInfoAndSigned + val nodeCaCert = nodeInfo.legalIdentitiesAndCerts[0].certPath.x509Certificates.find { CertRole.extract(it) == NODE_CA } + nodeCaCert ?: throw IllegalArgumentException("Missing Node CA") return database.transaction { // TODO Move these checks out of data access layer - val request = nodeCaCert?.let { - getSignedRequestByPublicHash(it.publicKey.encoded.sha256(), this) + val request = requireNotNull(getSignedRequestByPublicHash(nodeCaCert.publicKey.encoded.sha256(), this)) { + "Node-info not registered with us" + } + request.certificateData?.certificateStatus.let { + require(it == CertificateStatus.VALID) { "Certificate is no longer valid: $it" } } - request ?: throw IllegalArgumentException("Unknown node info, this public key is not registered with the network management service.") - require(request.certificateData!!.certificateStatus == CertificateStatus.VALID) { "Certificate is no longer valid" } /* * Delete any previous [NodeInfoEntity] instance for this CSR diff --git a/network-management/src/main/kotlin/com/r3/corda/networkmanage/doorman/DoormanParameters.kt b/network-management/src/main/kotlin/com/r3/corda/networkmanage/doorman/DoormanParameters.kt index 674eb4a89c..7126d9ded3 100644 --- a/network-management/src/main/kotlin/com/r3/corda/networkmanage/doorman/DoormanParameters.kt +++ b/network-management/src/main/kotlin/com/r3/corda/networkmanage/doorman/DoormanParameters.kt @@ -22,7 +22,7 @@ import java.nio.file.Path import java.nio.file.Paths import java.util.* -data class NetworkManagementServerParameters(// TODO: Move local signing to signing server. +data class NetworkManagementServerConfig( // TODO: Move local signing to signing server. val host: String, val port: Int, val dataSourceProperties: Properties, @@ -62,11 +62,11 @@ data class NetworkManagementServerParameters(// TODO: Move local signing to sign data class DoormanConfig(val approveAll: Boolean = false, val jira: JiraConfig? = null, - val approveInterval: Long = NetworkManagementServerParameters.DEFAULT_APPROVE_INTERVAL.toMillis()) + val approveInterval: Long = NetworkManagementServerConfig.DEFAULT_APPROVE_INTERVAL.toMillis()) data class NetworkMapConfig(val cacheTimeout: Long, // TODO: Move signing to signing server. - val signInterval: Long = NetworkManagementServerParameters.DEFAULT_SIGN_INTERVAL.toMillis()) + val signInterval: Long = NetworkManagementServerConfig.DEFAULT_SIGN_INTERVAL.toMillis()) enum class Mode { // TODO CA_KEYGEN now also generates the network map cert, so it should be renamed. @@ -85,7 +85,7 @@ data class JiraConfig( /** * Parses the doorman command line options. */ -fun parseParameters(vararg args: String): NetworkManagementServerParameters { +fun parseParameters(vararg args: String): NetworkManagementServerConfig { val argConfig = args.toConfigWithOptions { accepts("config-file", "The path to the config file") .withRequiredArg() @@ -112,7 +112,7 @@ fun parseParameters(vararg args: String): NetworkManagementServerParameters { val config = argConfig.withFallback(ConfigFactory.parseFile(configFile.toFile(), ConfigParseOptions.defaults().setAllowMissing(true))) .resolve() - .parseAs(false) + .parseAs(false) // Make sure trust store password is only specified in root keygen mode. if (config.mode != Mode.ROOT_KEYGEN) { diff --git a/network-management/src/main/kotlin/com/r3/corda/networkmanage/doorman/Main.kt b/network-management/src/main/kotlin/com/r3/corda/networkmanage/doorman/Main.kt index 6096cd229a..14d45c3b03 100644 --- a/network-management/src/main/kotlin/com/r3/corda/networkmanage/doorman/Main.kt +++ b/network-management/src/main/kotlin/com/r3/corda/networkmanage/doorman/Main.kt @@ -10,8 +10,7 @@ package com.r3.corda.networkmanage.doorman -import com.r3.corda.networkmanage.common.persistence.CertificateSigningRequestStorage -import com.r3.corda.networkmanage.common.persistence.CertificateSigningRequestStorage.Companion.DOORMAN_SIGNATURE +import com.jcabi.manifests.Manifests import com.r3.corda.networkmanage.common.persistence.configureDatabase import com.r3.corda.networkmanage.common.utils.* import com.r3.corda.networkmanage.doorman.signer.LocalSigner @@ -19,94 +18,92 @@ import net.corda.core.node.NetworkParameters import net.corda.core.utilities.NetworkHostAndPort import net.corda.nodeapi.internal.crypto.X509KeyStore import net.corda.nodeapi.internal.crypto.X509Utilities -import org.bouncycastle.pkcs.PKCS10CertificationRequest import java.time.Instant import kotlin.concurrent.thread -import com.jcabi.manifests.Manifests +import kotlin.system.exitProcess + +fun main(args: Array) { + if (Manifests.exists("Doorman-Version")) { + println("Version: ${Manifests.read("Doorman-Version")}") + } + + val parameters = try { + parseParameters(*args) + } catch (e: ShowHelpException) { + e.errorMessage?.let(::println) + e.parser.printHelpOn(System.out) + exitProcess(0) + } + + // TODO Use the logger for this and elsewhere in this file. + println("Running in ${parameters.mode} mode") + when (parameters.mode) { + Mode.ROOT_KEYGEN -> parameters.rootKeyGenMode() + Mode.CA_KEYGEN -> parameters.caKeyGenMode() + Mode.DOORMAN -> parameters.doormanMode() + } +} data class NetworkMapStartParams(val signer: LocalSigner?, val updateNetworkParameters: NetworkParameters?, val config: NetworkMapConfig) data class NetworkManagementServerStatus(var serverStartTime: Instant = Instant.now(), var lastRequestCheckTime: Instant? = null) -private fun processKeyStore(parameters: NetworkManagementServerParameters): Pair? { - if (parameters.keystorePath == null) return null +private fun processKeyStore(config: NetworkManagementServerConfig): Pair? { + if (config.keystorePath == null) return null // Get password from console if not in config. - val keyStorePassword = parameters.keystorePassword ?: readPassword("Key store password: ") - val privateKeyPassword = parameters.caPrivateKeyPassword ?: readPassword("Private key password: ") - val keyStore = X509KeyStore.fromFile(parameters.keystorePath, keyStorePassword) + val keyStorePassword = config.keystorePassword ?: readPassword("Key store password: ") + val privateKeyPassword = config.caPrivateKeyPassword ?: readPassword("Private key password: ") + val keyStore = X509KeyStore.fromFile(config.keystorePath, keyStorePassword) val csrCertPathAndKey = keyStore.getCertPathAndKey(X509Utilities.CORDA_INTERMEDIATE_CA, privateKeyPassword) val networkMapSigner = LocalSigner(keyStore.getCertificateAndKeyPair(CORDA_NETWORK_MAP, privateKeyPassword)) return Pair(csrCertPathAndKey, networkMapSigner) } -/** - * This storage automatically approves all created requests. - */ -class ApproveAllCertificateRequestStorage(private val delegate: CertificateSigningRequestStorage) : CertificateSigningRequestStorage by delegate { - override fun saveRequest(request: PKCS10CertificationRequest): String { - val requestId = delegate.saveRequest(request) - delegate.markRequestTicketCreated(requestId) - approveRequest(requestId, DOORMAN_SIGNATURE) - return requestId - } +private fun NetworkManagementServerConfig.rootKeyGenMode() { + generateRootKeyPair( + rootStorePath ?: throw IllegalArgumentException("The 'rootStorePath' parameter must be specified when generating keys!"), + rootKeystorePassword, + rootPrivateKeyPassword, + trustStorePassword + ) } -private fun logDoormanVersion() { - if (Manifests.exists("Doorman-Version")) { - println("Doorman Version: ${Manifests.read("Doorman-Version")}") - } +private fun NetworkManagementServerConfig.caKeyGenMode() { + generateSigningKeyPairs( + keystorePath ?: throw IllegalArgumentException("The 'keystorePath' parameter must be specified when generating keys!"), + rootStorePath ?: throw IllegalArgumentException("The 'rootStorePath' parameter must be specified when generating keys!"), + rootKeystorePassword, + rootPrivateKeyPassword, + keystorePassword, + caPrivateKeyPassword + ) } -fun main(args: Array) { - try { - parseParameters(*args).run { - println("Starting in $mode mode") - logDoormanVersion() - when (mode) { - Mode.ROOT_KEYGEN -> generateRootKeyPair( - rootStorePath ?: throw IllegalArgumentException("The 'rootStorePath' parameter must be specified when generating keys!"), - rootKeystorePassword, - rootPrivateKeyPassword, - trustStorePassword) - Mode.CA_KEYGEN -> generateSigningKeyPairs( - keystorePath ?: throw IllegalArgumentException("The 'keystorePath' parameter must be specified when generating keys!"), - rootStorePath ?: throw IllegalArgumentException("The 'rootStorePath' parameter must be specified when generating keys!"), - rootKeystorePassword, - rootPrivateKeyPassword, - keystorePassword, - caPrivateKeyPassword) - Mode.DOORMAN -> { - initialiseSerialization() - val persistence = configureDatabase(dataSourceProperties, database) - // TODO: move signing to signing server. - val csrAndNetworkMap = processKeyStore(this) +private fun NetworkManagementServerConfig.doormanMode() { + initialiseSerialization() + val persistence = configureDatabase(dataSourceProperties, database) + // TODO: move signing to signing server. + val csrAndNetworkMap = processKeyStore(this) - if (csrAndNetworkMap != null) { - println("Starting network management services with local signing") - } - - val networkManagementServer = NetworkManagementServer() - val networkParameters = updateNetworkParameters?.let { - // TODO This check shouldn't be needed. Fix up the config design. - requireNotNull(networkMap) { "'networkMapConfig' config is required for applying network parameters" } - println("Parsing network parameters from '${it.toAbsolutePath()}'...") - parseNetworkParametersFrom(it) - } - val networkMapStartParams = networkMap?.let { - NetworkMapStartParams(csrAndNetworkMap?.second, networkParameters, it) - } - - networkManagementServer.start(NetworkHostAndPort(host, port), persistence, csrAndNetworkMap?.first, doorman, networkMapStartParams) - - Runtime.getRuntime().addShutdownHook(thread(start = false) { - networkManagementServer.close() - }) - } - } - } - } catch (e: ShowHelpException) { - e.errorMessage?.let(::println) - e.parser.printHelpOn(System.out) + if (csrAndNetworkMap != null) { + println("Starting network management services with local signing") } + + val networkManagementServer = NetworkManagementServer() + val networkParameters = updateNetworkParameters?.let { + // TODO This check shouldn't be needed. Fix up the config design. + requireNotNull(networkMap) { "'networkMap' config is required for applying network parameters" } + println("Parsing network parameters from '${it.toAbsolutePath()}'...") + parseNetworkParametersConfig(it).toNetworkParameters(modifiedTime = Instant.now(), epoch = 1) + } + val networkMapStartParams = networkMap?.let { + NetworkMapStartParams(csrAndNetworkMap?.second, networkParameters, it) + } + + networkManagementServer.start(NetworkHostAndPort(host, port), persistence, csrAndNetworkMap?.first, doorman, networkMapStartParams) + + Runtime.getRuntime().addShutdownHook(thread(start = false) { + networkManagementServer.close() + }) } diff --git a/network-management/src/main/kotlin/com/r3/corda/networkmanage/doorman/NetworkManagementServer.kt b/network-management/src/main/kotlin/com/r3/corda/networkmanage/doorman/NetworkManagementServer.kt index 4ee65c4694..ea7178938b 100644 --- a/network-management/src/main/kotlin/com/r3/corda/networkmanage/doorman/NetworkManagementServer.kt +++ b/network-management/src/main/kotlin/com/r3/corda/networkmanage/doorman/NetworkManagementServer.kt @@ -11,6 +11,7 @@ package com.r3.corda.networkmanage.doorman import com.atlassian.jira.rest.client.internal.async.AsynchronousJiraRestClientFactory +import com.r3.corda.networkmanage.common.persistence.ApproveAllCertificateSigningRequestStorage import com.r3.corda.networkmanage.common.persistence.PersistentCertificateSigningRequestStorage import com.r3.corda.networkmanage.common.persistence.PersistentNetworkMapStorage import com.r3.corda.networkmanage.common.persistence.PersistentNodeInfoStorage @@ -24,7 +25,7 @@ import com.r3.corda.networkmanage.doorman.webservice.NetworkMapWebService import com.r3.corda.networkmanage.doorman.webservice.RegistrationWebService import net.corda.core.node.NetworkParameters import net.corda.core.utilities.NetworkHostAndPort -import net.corda.core.utilities.loggerFor +import net.corda.core.utilities.contextLogger import net.corda.nodeapi.internal.persistence.CordaPersistence import java.io.Closeable import java.net.URI @@ -35,7 +36,7 @@ import java.util.concurrent.TimeUnit class NetworkManagementServer : Closeable { companion object { - private val logger = loggerFor() + private val logger = contextLogger() } private val closeActions = mutableListOf<() -> Unit>() @@ -95,7 +96,7 @@ class NetworkManagementServer : Closeable { val requestService = if (config.approveAll) { require(config.jira == null) { "Jira configuration cannot be specified when the approveAll parameter is set to true." } logger.warn("Doorman server is in 'Approve All' mode, this will approve all incoming certificate signing requests.") - ApproveAllCertificateRequestStorage(PersistentCertificateSigningRequestStorage(database)) + ApproveAllCertificateSigningRequestStorage(PersistentCertificateSigningRequestStorage(database)) } else { PersistentCertificateSigningRequestStorage(database) } diff --git a/network-management/src/main/kotlin/com/r3/corda/networkmanage/doorman/NetworkParametersConfig.kt b/network-management/src/main/kotlin/com/r3/corda/networkmanage/doorman/NetworkParametersConfig.kt new file mode 100644 index 0000000000..ad36eeaf25 --- /dev/null +++ b/network-management/src/main/kotlin/com/r3/corda/networkmanage/doorman/NetworkParametersConfig.kt @@ -0,0 +1,68 @@ +/* + * R3 Proprietary and Confidential + * + * Copyright (c) 2018 R3 Limited. All rights reserved. + * + * The intellectual and technical concepts contained herein are proprietary to R3 and its suppliers and are protected by trade secret law. + * + * Distribution of this file or any portion thereof via any medium without the express permission of R3 is strictly prohibited. + */ + +package com.r3.corda.networkmanage.doorman + +import com.typesafe.config.ConfigFactory +import com.typesafe.config.ConfigParseOptions +import net.corda.core.internal.exists +import net.corda.core.internal.readObject +import net.corda.core.node.NetworkParameters +import net.corda.core.node.NotaryInfo +import net.corda.nodeapi.internal.SignedNodeInfo +import net.corda.nodeapi.internal.config.parseAs +import java.nio.file.Path +import java.time.Instant + +/** + * Data class representing a [NotaryInfo] which can be easily parsed by a typesafe [ConfigFactory]. + * @property notaryNodeInfoFile path to the node info file of the notary node. + * @property validating whether the notary is validating + */ +data class NotaryConfig(private val notaryNodeInfoFile: Path, + private val validating: Boolean) { + fun toNotaryInfo(): NotaryInfo { + val nodeInfo = notaryNodeInfoFile.readObject().verified() + // It is always the last identity (in the list of identities) that corresponds to the notary identity. + // In case of a single notary, the list has only one element. In case of distributed notaries the list has + // two items and the second one corresponds to the notary identity. + return NotaryInfo(nodeInfo.legalIdentities.last(), validating) + } +} + +/** + * Data class containing the fields from [NetworkParameters] which can be read at start-up time from doorman. + * It is a proper subset of [NetworkParameters] except for the [notaries] field which is replaced by a list of + * [NotaryConfig] which is parsable. + * + * This is public only because [parseAs] needs to be able to call its constructor. + */ +data class NetworkParametersConfig(val minimumPlatformVersion: Int, + val notaries: List, + val maxMessageSize: Int, + val maxTransactionSize: Int) { + fun toNetworkParameters(modifiedTime: Instant, epoch: Int): NetworkParameters { + return NetworkParameters( + minimumPlatformVersion, + notaries.map { it.toNotaryInfo() }, + maxMessageSize, + maxTransactionSize, + modifiedTime, + epoch, + // TODO: Tudor, Michal - pass the actual network parameters where we figure out how + emptyMap() + ) + } +} + +fun parseNetworkParametersConfig(configFile: Path): NetworkParametersConfig { + check(configFile.exists()) { "File $configFile does not exist" } + return ConfigFactory.parseFile(configFile.toFile(), ConfigParseOptions.defaults()).parseAs() +} diff --git a/network-management/src/main/kotlin/com/r3/corda/networkmanage/doorman/NetworkParametersConfiguration.kt b/network-management/src/main/kotlin/com/r3/corda/networkmanage/doorman/NetworkParametersConfiguration.kt deleted file mode 100644 index fb1279d6e1..0000000000 --- a/network-management/src/main/kotlin/com/r3/corda/networkmanage/doorman/NetworkParametersConfiguration.kt +++ /dev/null @@ -1,84 +0,0 @@ -/* - * R3 Proprietary and Confidential - * - * Copyright (c) 2018 R3 Limited. All rights reserved. - * - * The intellectual and technical concepts contained herein are proprietary to R3 and its suppliers and are protected by trade secret law. - * - * Distribution of this file or any portion thereof via any medium without the express permission of R3 is strictly prohibited. - */ - -package com.r3.corda.networkmanage.doorman - -import com.typesafe.config.ConfigFactory -import com.typesafe.config.ConfigParseOptions -import net.corda.core.internal.exists -import net.corda.core.internal.readAll -import net.corda.core.node.NetworkParameters -import net.corda.core.node.NotaryInfo -import net.corda.core.serialization.deserialize -import net.corda.nodeapi.internal.SignedNodeInfo -import net.corda.nodeapi.internal.config.parseAs -import java.nio.file.Path -import java.time.Instant - -/** - * Initial value for [NetworkParameters.epoch]. - */ -private const val DEFAULT_EPOCH = 1 - -/** - * Data class representing a [NotaryInfo] which can be easily parsed by a typesafe [ConfigFactory]. - * @property notaryNodeInfoFile path to the node info file of the notary node. - * @property validating whether the notary is validating - */ -internal data class NotaryConfiguration(private val notaryNodeInfoFile: Path, - private val validating: Boolean) { - fun toNotaryInfo(): NotaryInfo { - val nodeInfo = notaryNodeInfoFile.readAll().deserialize().verified() - // It is always the last identity (in the list of identities) that corresponds to the notary identity. - // In case of a single notary, the list has only one element. In case of distributed notaries the list has - // two items and the second one corresponds to the notary identity. - return NotaryInfo(nodeInfo.legalIdentities.last(), validating) - } -} - -/** - * data class containing the fields from [NetworkParameters] which can be read at start-up time from doorman. - * It is a proper subset of [NetworkParameters] except for the [notaries] field which is replaced by a list of - * [NotaryConfiguration] which is parsable. - * - * This is public only because [parseAs] needs to be able to call its constructor. - */ -internal data class NetworkParametersConfiguration(val minimumPlatformVersion: Int, - val notaries: List, - val maxMessageSize: Int, - val maxTransactionSize: Int) - -/** - * Parses a file and returns a [NetworkParameters] instance. - * - * @return a [NetworkParameters] with values read from [configFile] except: - * an epoch of [DEFAULT_EPOCH] and - * a modifiedTime initialized with [Instant.now]. - */ -fun parseNetworkParametersFrom(configFile: Path, epoch: Int = DEFAULT_EPOCH): NetworkParameters { - return parseNetworkParameters(parseNetworkParametersConfigurationFrom(configFile), epoch) -} - -internal fun parseNetworkParametersConfigurationFrom(configFile: Path): NetworkParametersConfiguration { - check(configFile.exists()) { "File $configFile does not exist" } - return ConfigFactory.parseFile(configFile.toFile(), ConfigParseOptions.defaults()) - .parseAs(NetworkParametersConfiguration::class) -} - -internal fun parseNetworkParameters(configuration: NetworkParametersConfiguration, epoch: Int = DEFAULT_EPOCH): NetworkParameters { - return NetworkParameters(configuration.minimumPlatformVersion, - configuration.notaries.map { it.toNotaryInfo() }, - configuration.maxMessageSize, - configuration.maxTransactionSize, - Instant.now(), - epoch, - // TODO: Tudor, Michal - pass the actual network parameters where we figure out how - emptyMap()) -} \ No newline at end of file diff --git a/network-management/src/main/kotlin/com/r3/corda/networkmanage/doorman/webservice/NetworkMapWebService.kt b/network-management/src/main/kotlin/com/r3/corda/networkmanage/doorman/webservice/NetworkMapWebService.kt index ed119d2ea2..80961fddba 100644 --- a/network-management/src/main/kotlin/com/r3/corda/networkmanage/doorman/webservice/NetworkMapWebService.kt +++ b/network-management/src/main/kotlin/com/r3/corda/networkmanage/doorman/webservice/NetworkMapWebService.kt @@ -15,7 +15,6 @@ import com.google.common.cache.CacheLoader import com.google.common.cache.LoadingCache import com.r3.corda.networkmanage.common.persistence.NetworkMapStorage import com.r3.corda.networkmanage.common.persistence.NodeInfoStorage -import com.r3.corda.networkmanage.common.persistence.NodeInfoWithSigned import com.r3.corda.networkmanage.common.utils.SignedNetworkMap import com.r3.corda.networkmanage.doorman.NetworkMapConfig import com.r3.corda.networkmanage.doorman.webservice.NetworkMapWebService.Companion.NETWORK_MAP_PATH @@ -25,7 +24,9 @@ import net.corda.core.node.NodeInfo import net.corda.core.serialization.deserialize import net.corda.core.serialization.serialize import net.corda.core.utilities.contextLogger +import net.corda.core.utilities.debug import net.corda.core.utilities.trace +import net.corda.nodeapi.internal.NodeInfoAndSigned import net.corda.nodeapi.internal.SignedNodeInfo import java.io.InputStream import java.security.InvalidKeyException @@ -52,32 +53,32 @@ class NetworkMapWebService(private val nodeInfoStorage: NodeInfoStorage, private val networkMapCache: LoadingCache> = CacheBuilder.newBuilder() .expireAfterWrite(config.cacheTimeout, TimeUnit.MILLISECONDS) - .build(CacheLoader.from { _ -> Pair(networkMapStorage.getCurrentNetworkMap(), networkMapStorage.getNetworkParametersOfNetworkMap()?.verified()) }) + .build(CacheLoader.from { _ -> + Pair(networkMapStorage.getCurrentNetworkMap(), networkMapStorage.getNetworkParametersOfNetworkMap()?.verified()) } + ) @POST @Path("publish") @Consumes(MediaType.APPLICATION_OCTET_STREAM) fun registerNode(input: InputStream): Response { val signedNodeInfo = input.readBytes().deserialize() + var nodeInfo: NodeInfo? = null return try { // Store the NodeInfo - val nodeInfoWithSignature = NodeInfoWithSigned(signedNodeInfo) - logger.trace { "Processing 'publish' request from '${nodeInfoWithSignature.nodeInfo.legalIdentities.first().name.organisation}'" } - verifyNodeInfo(nodeInfoWithSignature.nodeInfo) - nodeInfoStorage.putNodeInfo(nodeInfoWithSignature) - logger.trace { "Stored 'publish' request from '${nodeInfoWithSignature.nodeInfo.legalIdentities.first().name.organisation}'" } + val nodeInfoAndSigned = NodeInfoAndSigned(signedNodeInfo) + nodeInfo = nodeInfoAndSigned.nodeInfo + logger.debug { "Publishing node-info: $nodeInfo" } + verifyNodeInfo(nodeInfo) + nodeInfoStorage.putNodeInfo(nodeInfoAndSigned) ok() } catch (e: Exception) { - // Catch exceptions thrown by signature verification. + logger.warn("Unable to process node-info: $nodeInfo", e) when (e) { is NetworkMapNotInitialisedException -> status(Response.Status.SERVICE_UNAVAILABLE).entity(e.message) is InvalidPlatformVersionException -> status(Response.Status.BAD_REQUEST).entity(e.message) - is IllegalArgumentException, is InvalidKeyException, is SignatureException -> status(Response.Status.UNAUTHORIZED).entity(e.message) - // Rethrow e if its not one of the expected exception, the server will return http 500 internal error. - else -> { - logger.error("Unexpected error encountered while processing request.", e) - throw e - } + is InvalidKeyException, is SignatureException -> status(Response.Status.UNAUTHORIZED).entity(e.message) + // Rethrow e if its not one of the expected exception, the server will return http 500 internal error. + else -> throw e } }.build() } @@ -107,20 +108,14 @@ class NetworkMapWebService(private val nodeInfoStorage: NodeInfoStorage, @Path("my-ip") fun myIp(@Context request: HttpServletRequest): Response { val ip = request.getHeader("X-Forwarded-For")?.split(",")?.first() ?: "${request.remoteHost}:${request.remotePort}" - logger.trace { "Precessed ip request from client, IP: '$ip'" } + logger.trace { "Processed IP request from client, IP: '$ip'" } return ok(ip).build() } private fun verifyNodeInfo(nodeInfo: NodeInfo) { val minimumPlatformVersion = networkMapCache.get(true).second?.minimumPlatformVersion - if (minimumPlatformVersion == null) { - logger.error("Error processing request from node '${nodeInfo.legalIdentities.first().name.organisation}' : Network parameters have not been initialised") - logger.trace { "$nodeInfo" } - throw NetworkMapNotInitialisedException("Network parameters have not been initialised") - } + ?: throw NetworkMapNotInitialisedException("Network parameters have not been initialised") if (nodeInfo.platformVersion < minimumPlatformVersion) { - logger.error("Error processing request from node '${nodeInfo.legalIdentities.first().name.organisation}' : Minimum platform version is $minimumPlatformVersion") - logger.trace { "$nodeInfo" } throw InvalidPlatformVersionException("Minimum platform version is $minimumPlatformVersion") } } diff --git a/network-management/src/test/kotlin/com/r3/corda/networkmanage/NetworkParametersConfigTest.kt b/network-management/src/test/kotlin/com/r3/corda/networkmanage/NetworkParametersConfigTest.kt new file mode 100644 index 0000000000..785e32c066 --- /dev/null +++ b/network-management/src/test/kotlin/com/r3/corda/networkmanage/NetworkParametersConfigTest.kt @@ -0,0 +1,76 @@ +/* + * R3 Proprietary and Confidential + * + * Copyright (c) 2018 R3 Limited. All rights reserved. + * + * The intellectual and technical concepts contained herein are proprietary to R3 and its suppliers and are protected by trade secret law. + * + * Distribution of this file or any portion thereof via any medium without the express permission of R3 is strictly prohibited. + */ + +package com.r3.corda.networkmanage + +import com.google.common.jimfs.Jimfs +import com.r3.corda.networkmanage.doorman.NetworkParametersConfig +import com.r3.corda.networkmanage.doorman.NotaryConfig +import net.corda.core.internal.copyTo +import net.corda.core.node.NotaryInfo +import net.corda.core.serialization.serialize +import net.corda.nodeapi.internal.SignedNodeInfo +import net.corda.testing.core.ALICE_NAME +import net.corda.testing.core.BOB_NAME +import net.corda.testing.core.SerializationEnvironmentRule +import net.corda.testing.internal.createNodeInfoAndSigned +import org.assertj.core.api.Assertions.assertThat +import org.junit.After +import org.junit.Rule +import org.junit.Test +import java.nio.file.Path +import java.time.Instant +import java.util.* + +class NetworkParametersConfigTest { + @Rule + @JvmField + val testSerialization = SerializationEnvironmentRule() + + private val fs = Jimfs.newFileSystem() + + @After + fun cleanUp() { + fs.close() + } + + @Test + fun toNetworkParameters() { + val (aliceNodeInfo, aliceSignedNodeInfo) = createNodeInfoAndSigned(ALICE_NAME) + val (bobNodeInfo, bobSignedNodeInfo) = createNodeInfoAndSigned(BOB_NAME) + val networkParametersConfig = NetworkParametersConfig( + notaries = listOf( + NotaryConfig(aliceSignedNodeInfo.writeToFile(), true), + NotaryConfig(bobSignedNodeInfo.writeToFile(), false) + ), + maxMessageSize = 100, + maxTransactionSize = 100, + minimumPlatformVersion = 3 + ) + + val modifiedTime = Instant.now() + val networkParameters = networkParametersConfig.toNetworkParameters(modifiedTime = modifiedTime, epoch = 2) + assertThat(networkParameters.modifiedTime).isEqualTo(modifiedTime) + assertThat(networkParameters.epoch).isEqualTo(2) + assertThat(networkParameters.notaries).containsExactly( + NotaryInfo(aliceNodeInfo.legalIdentities[0], true), + NotaryInfo(bobNodeInfo.legalIdentities[0], false) + ) + assertThat(networkParameters.maxMessageSize).isEqualTo(100) + assertThat(networkParameters.maxTransactionSize).isEqualTo(100) + assertThat(networkParameters.minimumPlatformVersion).isEqualTo(3) + } + + private fun SignedNodeInfo.writeToFile(): Path { + val path = fs.getPath(UUID.randomUUID().toString()) + serialize().open().copyTo(path) + return path + } +} diff --git a/network-management/src/test/kotlin/com/r3/corda/networkmanage/NetworkParametersConfigurationTest.kt b/network-management/src/test/kotlin/com/r3/corda/networkmanage/NetworkParametersConfigurationTest.kt deleted file mode 100644 index 18a27d1785..0000000000 --- a/network-management/src/test/kotlin/com/r3/corda/networkmanage/NetworkParametersConfigurationTest.kt +++ /dev/null @@ -1,79 +0,0 @@ -/* - * R3 Proprietary and Confidential - * - * Copyright (c) 2018 R3 Limited. All rights reserved. - * - * The intellectual and technical concepts contained herein are proprietary to R3 and its suppliers and are protected by trade secret law. - * - * Distribution of this file or any portion thereof via any medium without the express permission of R3 is strictly prohibited. - */ - -package com.r3.corda.networkmanage - -import com.r3.corda.networkmanage.doorman.NetworkParametersConfiguration -import com.r3.corda.networkmanage.doorman.NotaryConfiguration -import com.r3.corda.networkmanage.doorman.parseNetworkParameters -import com.r3.corda.networkmanage.doorman.parseNetworkParametersFrom -import net.corda.core.identity.CordaX500Name -import net.corda.core.internal.copyTo -import net.corda.core.internal.deleteIfExists -import net.corda.core.serialization.serialize -import net.corda.testing.core.SerializationEnvironmentRule -import net.corda.testing.internal.createNodeInfoAndSigned -import org.assertj.core.api.Assertions.assertThat -import org.assertj.core.api.Assertions.assertThatThrownBy -import org.junit.Rule -import org.junit.Test -import org.junit.rules.TemporaryFolder -import java.nio.file.Path -import java.nio.file.Paths -import java.time.Instant - -class NetworkParametersConfigurationTest { - - @Rule - @JvmField - val tempFolder = TemporaryFolder() - - @Rule - @JvmField - val testSerialization = SerializationEnvironmentRule() - - private fun generateNetworkParametersConfiguration() = NetworkParametersConfiguration( - notaries = listOf( - NotaryConfiguration(generateNodeInfoFile("Test1"), true), - NotaryConfiguration(generateNodeInfoFile("Test2"), false) - ), - maxMessageSize = 100, - maxTransactionSize = 100, - minimumPlatformVersion = 1 - ) - - private fun generateNodeInfoFile(organisation: String): Path { - val (_, signedNodeInfo) = createNodeInfoAndSigned(CordaX500Name(organisation, "Madrid", "ES")) - val path = tempFolder.newFile().toPath() - path.deleteIfExists() - signedNodeInfo.serialize().open().copyTo(path) - return path - } - - @Test - fun `reads an existing file`() { - val networkParameters = parseNetworkParameters(generateNetworkParametersConfiguration()) - assertThat(networkParameters.minimumPlatformVersion).isEqualTo(1) - val notaries = networkParameters.notaries - assertThat(notaries).hasSize(2) - assertThat(notaries[0].validating).isTrue() - assertThat(notaries[1].validating).isFalse() - assertThat(networkParameters.maxMessageSize).isEqualTo(100) - assertThat(networkParameters.maxTransactionSize).isEqualTo(100) - assertThat(networkParameters.epoch).isEqualTo(1) - } - - @Test - fun `throws on a non-existing file`() { - assertThatThrownBy { - parseNetworkParametersFrom(Paths.get("notHere")) - }.isInstanceOf(IllegalStateException::class.java) - } -} \ No newline at end of file diff --git a/network-management/src/test/kotlin/com/r3/corda/networkmanage/common/persistence/PersistentNodeInfoStorageTest.kt b/network-management/src/test/kotlin/com/r3/corda/networkmanage/common/persistence/PersistentNodeInfoStorageTest.kt index 0407a0fd80..2e18b529e5 100644 --- a/network-management/src/test/kotlin/com/r3/corda/networkmanage/common/persistence/PersistentNodeInfoStorageTest.kt +++ b/network-management/src/test/kotlin/com/r3/corda/networkmanage/common/persistence/PersistentNodeInfoStorageTest.kt @@ -17,6 +17,7 @@ import net.corda.core.crypto.SecureHash import net.corda.core.identity.CordaX500Name import net.corda.core.internal.CertRole import net.corda.core.serialization.serialize +import net.corda.nodeapi.internal.NodeInfoAndSigned import net.corda.nodeapi.internal.crypto.CertificateAndKeyPair import net.corda.nodeapi.internal.crypto.CertificateType import net.corda.nodeapi.internal.crypto.X509Utilities @@ -114,7 +115,7 @@ class PersistentNodeInfoStorageTest : TestBase() { // Create node info. val (node1, key) = createValidSignedNodeInfo("Test", requestStorage) val nodeInfo2 = node1.nodeInfo.copy(serial = 2) - val node2 = NodeInfoWithSigned(nodeInfo2.signWith(listOf(key))) + val node2 = NodeInfoAndSigned(nodeInfo2.signWith(listOf(key))) val nodeInfo1Hash = nodeInfoStorage.putNodeInfo(node1) assertEquals(node1.nodeInfo, nodeInfoStorage.getNodeInfo(nodeInfo1Hash)?.verified()) @@ -137,12 +138,12 @@ class PersistentNodeInfoStorageTest : TestBase() { // then val persistedSignedNodeInfo = nodeInfoStorage.getNodeInfo(nodeInfoHash) - assertThat(persistedSignedNodeInfo?.signatures).isEqualTo(nodeInfoWithSigned.signedNodeInfo.signatures) + assertThat(persistedSignedNodeInfo?.signatures).isEqualTo(nodeInfoWithSigned.signed.signatures) } } internal fun createValidSignedNodeInfo(organisation: String, - storage: CertificateSigningRequestStorage): Pair { + storage: CertificateSigningRequestStorage): Pair { val (csr, nodeKeyPair) = createRequest(organisation, certRole = CertRole.NODE_CA) val requestId = storage.saveRequest(csr) storage.markRequestTicketCreated(requestId) @@ -151,5 +152,5 @@ internal fun createValidSignedNodeInfo(organisation: String, val (identity, key) = nodeInfoBuilder.addIdentity(CordaX500Name.build(X500Principal(csr.subject.encoded)), nodeKeyPair) storage.putCertificatePath(requestId, identity.certPath, "Test") val (_, signedNodeInfo) = nodeInfoBuilder.buildWithSigned(1) - return Pair(NodeInfoWithSigned(signedNodeInfo), key) + return Pair(NodeInfoAndSigned(signedNodeInfo), key) } \ No newline at end of file