Various cleanups of the network-management code (#545)

This commit is contained in:
Shams Asari 2018-03-13 10:58:04 +00:00 committed by GitHub
parent 98f554c08d
commit e22e7acd67
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
14 changed files with 282 additions and 306 deletions

View File

@ -17,7 +17,6 @@ import com.r3.corda.networkmanage.doorman.signer.LocalSigner
import net.corda.cordform.CordformNode import net.corda.cordform.CordformNode
import net.corda.core.crypto.random63BitValue import net.corda.core.crypto.random63BitValue
import net.corda.core.identity.CordaX500Name import net.corda.core.identity.CordaX500Name
import net.corda.core.internal.concurrent.transpose
import net.corda.core.internal.div import net.corda.core.internal.div
import net.corda.core.internal.exists import net.corda.core.internal.exists
import net.corda.core.internal.list import net.corda.core.internal.list
@ -94,7 +93,9 @@ class NodeRegistrationTest : IntegrationTest() {
@Test @Test
fun `register nodes with doorman and then they transact with each other`() { 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) server = startNetworkManagementServer(networkParameters = null)
val compatibilityZone = CompatibilityZoneParams( val compatibilityZone = CompatibilityZoneParams(
URL("http://$serverAddress"), URL("http://$serverAddress"),
@ -120,14 +121,12 @@ class NodeRegistrationTest : IntegrationTest() {
val (alice, notary) = listOf( val (alice, notary) = listOf(
startNode(providedName = aliceName), startNode(providedName = aliceName),
defaultNotaryNode defaultNotaryNode
).transpose().getOrThrow() ).map { it.getOrThrow() as NodeHandleInternal }
alice as NodeHandleInternal
notary as NodeHandleInternal
alice.onlySeesFromNetworkMap(alice, notary) alice.onlySeesFromNetworkMap(alice, notary)
notary.onlySeesFromNetworkMap(alice, notary) notary.onlySeesFromNetworkMap(alice, notary)
val genevieve = startNode(providedName = genevieveName).getOrThrow() val genevieve = startNode(providedName = genevieveName).getOrThrow() as NodeHandleInternal
genevieve as NodeHandleInternal
// Wait for the nodes to poll again // Wait for the nodes to poll again
Thread.sleep(timeoutMillis * 2) Thread.sleep(timeoutMillis * 2)
@ -161,7 +160,7 @@ class NodeRegistrationTest : IntegrationTest() {
networkParameters?.let { networkParameters?.let {
NetworkMapStartParams( NetworkMapStartParams(
LocalSigner(networkMapCa), LocalSigner(networkMapCa),
networkParameters, it,
NetworkMapConfig(cacheTimeout = timeoutMillis, signInterval = timeoutMillis) NetworkMapConfig(cacheTimeout = timeoutMillis, signInterval = timeoutMillis)
) )
} }

View File

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

View File

@ -12,6 +12,7 @@ package com.r3.corda.networkmanage.common.persistence
import net.corda.core.crypto.SecureHash import net.corda.core.crypto.SecureHash
import net.corda.core.node.NodeInfo import net.corda.core.node.NodeInfo
import net.corda.nodeapi.internal.NodeInfoAndSigned
import net.corda.nodeapi.internal.SignedNodeInfo import net.corda.nodeapi.internal.SignedNodeInfo
import java.security.cert.CertPath import java.security.cert.CertPath
@ -32,9 +33,9 @@ interface NodeInfoStorage {
fun getNodeInfo(nodeInfoHash: SecureHash): SignedNodeInfo? 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. * 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 signedNodeInfo signed node info data to be stored * @param nodeInfoAndSigned signed node info data to be stored
* @return hash for the newly created node info entry * @return hash for the newly created node info entry
*/ */
fun putNodeInfo(signedNodeInfo: NodeInfoWithSigned): SecureHash fun putNodeInfo(nodeInfoAndSigned: NodeInfoAndSigned): SecureHash
} }

View File

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

View File

@ -16,7 +16,9 @@ import com.r3.corda.networkmanage.common.utils.buildCertPath
import net.corda.core.crypto.SecureHash import net.corda.core.crypto.SecureHash
import net.corda.core.crypto.sha256 import net.corda.core.crypto.sha256
import net.corda.core.internal.CertRole import net.corda.core.internal.CertRole
import net.corda.core.internal.CertRole.NODE_CA
import net.corda.core.serialization.serialize import net.corda.core.serialization.serialize
import net.corda.nodeapi.internal.NodeInfoAndSigned
import net.corda.nodeapi.internal.SignedNodeInfo import net.corda.nodeapi.internal.SignedNodeInfo
import net.corda.nodeapi.internal.crypto.x509Certificates import net.corda.nodeapi.internal.crypto.x509Certificates
import net.corda.nodeapi.internal.persistence.CordaPersistence import net.corda.nodeapi.internal.persistence.CordaPersistence
@ -27,17 +29,18 @@ import java.security.cert.CertPath
* Database implementation of the [NetworkMapStorage] interface * Database implementation of the [NetworkMapStorage] interface
*/ */
class PersistentNodeInfoStorage(private val database: CordaPersistence) : NodeInfoStorage { class PersistentNodeInfoStorage(private val database: CordaPersistence) : NodeInfoStorage {
override fun putNodeInfo(nodeInfoWithSigned: NodeInfoWithSigned): SecureHash { override fun putNodeInfo(nodeInfoAndSigned: NodeInfoAndSigned): SecureHash {
val nodeInfo = nodeInfoWithSigned.nodeInfo val (nodeInfo, signedNodeInfo) = nodeInfoAndSigned
val signedNodeInfo = nodeInfoWithSigned.signedNodeInfo val nodeCaCert = nodeInfo.legalIdentitiesAndCerts[0].certPath.x509Certificates.find { CertRole.extract(it) == NODE_CA }
val nodeCaCert = nodeInfo.legalIdentitiesAndCerts[0].certPath.x509Certificates.find { CertRole.extract(it) == CertRole.NODE_CA } nodeCaCert ?: throw IllegalArgumentException("Missing Node CA")
return database.transaction { return database.transaction {
// TODO Move these checks out of data access layer // TODO Move these checks out of data access layer
val request = nodeCaCert?.let { val request = requireNotNull(getSignedRequestByPublicHash(nodeCaCert.publicKey.encoded.sha256(), this)) {
getSignedRequestByPublicHash(it.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 * Delete any previous [NodeInfoEntity] instance for this CSR

View File

@ -22,7 +22,7 @@ import java.nio.file.Path
import java.nio.file.Paths import java.nio.file.Paths
import java.util.* 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 host: String,
val port: Int, val port: Int,
val dataSourceProperties: Properties, val dataSourceProperties: Properties,
@ -62,11 +62,11 @@ data class NetworkManagementServerParameters(// TODO: Move local signing to sign
data class DoormanConfig(val approveAll: Boolean = false, data class DoormanConfig(val approveAll: Boolean = false,
val jira: JiraConfig? = null, 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, data class NetworkMapConfig(val cacheTimeout: Long,
// TODO: Move signing to signing server. // 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 { enum class Mode {
// TODO CA_KEYGEN now also generates the network map cert, so it should be renamed. // 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. * Parses the doorman command line options.
*/ */
fun parseParameters(vararg args: String): NetworkManagementServerParameters { fun parseParameters(vararg args: String): NetworkManagementServerConfig {
val argConfig = args.toConfigWithOptions { val argConfig = args.toConfigWithOptions {
accepts("config-file", "The path to the config file") accepts("config-file", "The path to the config file")
.withRequiredArg() .withRequiredArg()
@ -112,7 +112,7 @@ fun parseParameters(vararg args: String): NetworkManagementServerParameters {
val config = argConfig.withFallback(ConfigFactory.parseFile(configFile.toFile(), ConfigParseOptions.defaults().setAllowMissing(true))) val config = argConfig.withFallback(ConfigFactory.parseFile(configFile.toFile(), ConfigParseOptions.defaults().setAllowMissing(true)))
.resolve() .resolve()
.parseAs<NetworkManagementServerParameters>(false) .parseAs<NetworkManagementServerConfig>(false)
// Make sure trust store password is only specified in root keygen mode. // Make sure trust store password is only specified in root keygen mode.
if (config.mode != Mode.ROOT_KEYGEN) { if (config.mode != Mode.ROOT_KEYGEN) {

View File

@ -10,8 +10,7 @@
package com.r3.corda.networkmanage.doorman package com.r3.corda.networkmanage.doorman
import com.r3.corda.networkmanage.common.persistence.CertificateSigningRequestStorage import com.jcabi.manifests.Manifests
import com.r3.corda.networkmanage.common.persistence.CertificateSigningRequestStorage.Companion.DOORMAN_SIGNATURE
import com.r3.corda.networkmanage.common.persistence.configureDatabase import com.r3.corda.networkmanage.common.persistence.configureDatabase
import com.r3.corda.networkmanage.common.utils.* import com.r3.corda.networkmanage.common.utils.*
import com.r3.corda.networkmanage.doorman.signer.LocalSigner 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.core.utilities.NetworkHostAndPort
import net.corda.nodeapi.internal.crypto.X509KeyStore import net.corda.nodeapi.internal.crypto.X509KeyStore
import net.corda.nodeapi.internal.crypto.X509Utilities import net.corda.nodeapi.internal.crypto.X509Utilities
import org.bouncycastle.pkcs.PKCS10CertificationRequest
import java.time.Instant import java.time.Instant
import kotlin.concurrent.thread import kotlin.concurrent.thread
import com.jcabi.manifests.Manifests import kotlin.system.exitProcess
fun main(args: Array<String>) {
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 NetworkMapStartParams(val signer: LocalSigner?, val updateNetworkParameters: NetworkParameters?, val config: NetworkMapConfig)
data class NetworkManagementServerStatus(var serverStartTime: Instant = Instant.now(), var lastRequestCheckTime: Instant? = null) data class NetworkManagementServerStatus(var serverStartTime: Instant = Instant.now(), var lastRequestCheckTime: Instant? = null)
private fun processKeyStore(parameters: NetworkManagementServerParameters): Pair<CertPathAndKey, LocalSigner>? { private fun processKeyStore(config: NetworkManagementServerConfig): Pair<CertPathAndKey, LocalSigner>? {
if (parameters.keystorePath == null) return null if (config.keystorePath == null) return null
// Get password from console if not in config. // Get password from console if not in config.
val keyStorePassword = parameters.keystorePassword ?: readPassword("Key store password: ") val keyStorePassword = config.keystorePassword ?: readPassword("Key store password: ")
val privateKeyPassword = parameters.caPrivateKeyPassword ?: readPassword("Private key password: ") val privateKeyPassword = config.caPrivateKeyPassword ?: readPassword("Private key password: ")
val keyStore = X509KeyStore.fromFile(parameters.keystorePath, keyStorePassword) val keyStore = X509KeyStore.fromFile(config.keystorePath, keyStorePassword)
val csrCertPathAndKey = keyStore.getCertPathAndKey(X509Utilities.CORDA_INTERMEDIATE_CA, privateKeyPassword) val csrCertPathAndKey = keyStore.getCertPathAndKey(X509Utilities.CORDA_INTERMEDIATE_CA, privateKeyPassword)
val networkMapSigner = LocalSigner(keyStore.getCertificateAndKeyPair(CORDA_NETWORK_MAP, privateKeyPassword)) val networkMapSigner = LocalSigner(keyStore.getCertificateAndKeyPair(CORDA_NETWORK_MAP, privateKeyPassword))
return Pair(csrCertPathAndKey, networkMapSigner) return Pair(csrCertPathAndKey, networkMapSigner)
} }
/** private fun NetworkManagementServerConfig.rootKeyGenMode() {
* This storage automatically approves all created requests. generateRootKeyPair(
*/ rootStorePath ?: throw IllegalArgumentException("The 'rootStorePath' parameter must be specified when generating keys!"),
class ApproveAllCertificateRequestStorage(private val delegate: CertificateSigningRequestStorage) : CertificateSigningRequestStorage by delegate { rootKeystorePassword,
override fun saveRequest(request: PKCS10CertificationRequest): String { rootPrivateKeyPassword,
val requestId = delegate.saveRequest(request) trustStorePassword
delegate.markRequestTicketCreated(requestId) )
approveRequest(requestId, DOORMAN_SIGNATURE)
return requestId
}
} }
private fun logDoormanVersion() { private fun NetworkManagementServerConfig.caKeyGenMode() {
if (Manifests.exists("Doorman-Version")) { generateSigningKeyPairs(
println("Doorman Version: ${Manifests.read("Doorman-Version")}") 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<String>) { private fun NetworkManagementServerConfig.doormanMode() {
try { initialiseSerialization()
parseParameters(*args).run { val persistence = configureDatabase(dataSourceProperties, database)
println("Starting in $mode mode") // TODO: move signing to signing server.
logDoormanVersion() val csrAndNetworkMap = processKeyStore(this)
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)
if (csrAndNetworkMap != null) { if (csrAndNetworkMap != null) {
println("Starting network management services with local signing") 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)
} }
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()
})
} }

View File

@ -11,6 +11,7 @@
package com.r3.corda.networkmanage.doorman package com.r3.corda.networkmanage.doorman
import com.atlassian.jira.rest.client.internal.async.AsynchronousJiraRestClientFactory 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.PersistentCertificateSigningRequestStorage
import com.r3.corda.networkmanage.common.persistence.PersistentNetworkMapStorage import com.r3.corda.networkmanage.common.persistence.PersistentNetworkMapStorage
import com.r3.corda.networkmanage.common.persistence.PersistentNodeInfoStorage 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 com.r3.corda.networkmanage.doorman.webservice.RegistrationWebService
import net.corda.core.node.NetworkParameters import net.corda.core.node.NetworkParameters
import net.corda.core.utilities.NetworkHostAndPort 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 net.corda.nodeapi.internal.persistence.CordaPersistence
import java.io.Closeable import java.io.Closeable
import java.net.URI import java.net.URI
@ -35,7 +36,7 @@ import java.util.concurrent.TimeUnit
class NetworkManagementServer : Closeable { class NetworkManagementServer : Closeable {
companion object { companion object {
private val logger = loggerFor<NetworkManagementServer>() private val logger = contextLogger()
} }
private val closeActions = mutableListOf<() -> Unit>() private val closeActions = mutableListOf<() -> Unit>()
@ -95,7 +96,7 @@ class NetworkManagementServer : Closeable {
val requestService = if (config.approveAll) { val requestService = if (config.approveAll) {
require(config.jira == null) { "Jira configuration cannot be specified when the approveAll parameter is set to true." } 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.") logger.warn("Doorman server is in 'Approve All' mode, this will approve all incoming certificate signing requests.")
ApproveAllCertificateRequestStorage(PersistentCertificateSigningRequestStorage(database)) ApproveAllCertificateSigningRequestStorage(PersistentCertificateSigningRequestStorage(database))
} else { } else {
PersistentCertificateSigningRequestStorage(database) PersistentCertificateSigningRequestStorage(database)
} }

View File

@ -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<SignedNodeInfo>().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<NotaryConfig>,
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()
}

View File

@ -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<SignedNodeInfo>().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<NotaryConfiguration>,
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())
}

View File

@ -15,7 +15,6 @@ import com.google.common.cache.CacheLoader
import com.google.common.cache.LoadingCache import com.google.common.cache.LoadingCache
import com.r3.corda.networkmanage.common.persistence.NetworkMapStorage import com.r3.corda.networkmanage.common.persistence.NetworkMapStorage
import com.r3.corda.networkmanage.common.persistence.NodeInfoStorage 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.common.utils.SignedNetworkMap
import com.r3.corda.networkmanage.doorman.NetworkMapConfig import com.r3.corda.networkmanage.doorman.NetworkMapConfig
import com.r3.corda.networkmanage.doorman.webservice.NetworkMapWebService.Companion.NETWORK_MAP_PATH 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.deserialize
import net.corda.core.serialization.serialize import net.corda.core.serialization.serialize
import net.corda.core.utilities.contextLogger import net.corda.core.utilities.contextLogger
import net.corda.core.utilities.debug
import net.corda.core.utilities.trace import net.corda.core.utilities.trace
import net.corda.nodeapi.internal.NodeInfoAndSigned
import net.corda.nodeapi.internal.SignedNodeInfo import net.corda.nodeapi.internal.SignedNodeInfo
import java.io.InputStream import java.io.InputStream
import java.security.InvalidKeyException import java.security.InvalidKeyException
@ -52,32 +53,32 @@ class NetworkMapWebService(private val nodeInfoStorage: NodeInfoStorage,
private val networkMapCache: LoadingCache<Boolean, Pair<SignedNetworkMap?, NetworkParameters?>> = CacheBuilder.newBuilder() private val networkMapCache: LoadingCache<Boolean, Pair<SignedNetworkMap?, NetworkParameters?>> = CacheBuilder.newBuilder()
.expireAfterWrite(config.cacheTimeout, TimeUnit.MILLISECONDS) .expireAfterWrite(config.cacheTimeout, TimeUnit.MILLISECONDS)
.build(CacheLoader.from { _ -> Pair(networkMapStorage.getCurrentNetworkMap(), networkMapStorage.getNetworkParametersOfNetworkMap()?.verified()) }) .build(CacheLoader.from { _ ->
Pair(networkMapStorage.getCurrentNetworkMap(), networkMapStorage.getNetworkParametersOfNetworkMap()?.verified()) }
)
@POST @POST
@Path("publish") @Path("publish")
@Consumes(MediaType.APPLICATION_OCTET_STREAM) @Consumes(MediaType.APPLICATION_OCTET_STREAM)
fun registerNode(input: InputStream): Response { fun registerNode(input: InputStream): Response {
val signedNodeInfo = input.readBytes().deserialize<SignedNodeInfo>() val signedNodeInfo = input.readBytes().deserialize<SignedNodeInfo>()
var nodeInfo: NodeInfo? = null
return try { return try {
// Store the NodeInfo // Store the NodeInfo
val nodeInfoWithSignature = NodeInfoWithSigned(signedNodeInfo) val nodeInfoAndSigned = NodeInfoAndSigned(signedNodeInfo)
logger.trace { "Processing 'publish' request from '${nodeInfoWithSignature.nodeInfo.legalIdentities.first().name.organisation}'" } nodeInfo = nodeInfoAndSigned.nodeInfo
verifyNodeInfo(nodeInfoWithSignature.nodeInfo) logger.debug { "Publishing node-info: $nodeInfo" }
nodeInfoStorage.putNodeInfo(nodeInfoWithSignature) verifyNodeInfo(nodeInfo)
logger.trace { "Stored 'publish' request from '${nodeInfoWithSignature.nodeInfo.legalIdentities.first().name.organisation}'" } nodeInfoStorage.putNodeInfo(nodeInfoAndSigned)
ok() ok()
} catch (e: Exception) { } catch (e: Exception) {
// Catch exceptions thrown by signature verification. logger.warn("Unable to process node-info: $nodeInfo", e)
when (e) { when (e) {
is NetworkMapNotInitialisedException -> status(Response.Status.SERVICE_UNAVAILABLE).entity(e.message) is NetworkMapNotInitialisedException -> status(Response.Status.SERVICE_UNAVAILABLE).entity(e.message)
is InvalidPlatformVersionException -> status(Response.Status.BAD_REQUEST).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) 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. // Rethrow e if its not one of the expected exception, the server will return http 500 internal error.
else -> { else -> throw e
logger.error("Unexpected error encountered while processing request.", e)
throw e
}
} }
}.build() }.build()
} }
@ -107,20 +108,14 @@ class NetworkMapWebService(private val nodeInfoStorage: NodeInfoStorage,
@Path("my-ip") @Path("my-ip")
fun myIp(@Context request: HttpServletRequest): Response { fun myIp(@Context request: HttpServletRequest): Response {
val ip = request.getHeader("X-Forwarded-For")?.split(",")?.first() ?: "${request.remoteHost}:${request.remotePort}" 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() return ok(ip).build()
} }
private fun verifyNodeInfo(nodeInfo: NodeInfo) { private fun verifyNodeInfo(nodeInfo: NodeInfo) {
val minimumPlatformVersion = networkMapCache.get(true).second?.minimumPlatformVersion val minimumPlatformVersion = networkMapCache.get(true).second?.minimumPlatformVersion
if (minimumPlatformVersion == null) { ?: throw NetworkMapNotInitialisedException("Network parameters have not been initialised")
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")
}
if (nodeInfo.platformVersion < minimumPlatformVersion) { 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") throw InvalidPlatformVersionException("Minimum platform version is $minimumPlatformVersion")
} }
} }

View File

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

View File

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

View File

@ -17,6 +17,7 @@ import net.corda.core.crypto.SecureHash
import net.corda.core.identity.CordaX500Name import net.corda.core.identity.CordaX500Name
import net.corda.core.internal.CertRole import net.corda.core.internal.CertRole
import net.corda.core.serialization.serialize 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.CertificateAndKeyPair
import net.corda.nodeapi.internal.crypto.CertificateType import net.corda.nodeapi.internal.crypto.CertificateType
import net.corda.nodeapi.internal.crypto.X509Utilities import net.corda.nodeapi.internal.crypto.X509Utilities
@ -114,7 +115,7 @@ class PersistentNodeInfoStorageTest : TestBase() {
// Create node info. // Create node info.
val (node1, key) = createValidSignedNodeInfo("Test", requestStorage) val (node1, key) = createValidSignedNodeInfo("Test", requestStorage)
val nodeInfo2 = node1.nodeInfo.copy(serial = 2) 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) val nodeInfo1Hash = nodeInfoStorage.putNodeInfo(node1)
assertEquals(node1.nodeInfo, nodeInfoStorage.getNodeInfo(nodeInfo1Hash)?.verified()) assertEquals(node1.nodeInfo, nodeInfoStorage.getNodeInfo(nodeInfo1Hash)?.verified())
@ -137,12 +138,12 @@ class PersistentNodeInfoStorageTest : TestBase() {
// then // then
val persistedSignedNodeInfo = nodeInfoStorage.getNodeInfo(nodeInfoHash) val persistedSignedNodeInfo = nodeInfoStorage.getNodeInfo(nodeInfoHash)
assertThat(persistedSignedNodeInfo?.signatures).isEqualTo(nodeInfoWithSigned.signedNodeInfo.signatures) assertThat(persistedSignedNodeInfo?.signatures).isEqualTo(nodeInfoWithSigned.signed.signatures)
} }
} }
internal fun createValidSignedNodeInfo(organisation: String, internal fun createValidSignedNodeInfo(organisation: String,
storage: CertificateSigningRequestStorage): Pair<NodeInfoWithSigned, PrivateKey> { storage: CertificateSigningRequestStorage): Pair<NodeInfoAndSigned, PrivateKey> {
val (csr, nodeKeyPair) = createRequest(organisation, certRole = CertRole.NODE_CA) val (csr, nodeKeyPair) = createRequest(organisation, certRole = CertRole.NODE_CA)
val requestId = storage.saveRequest(csr) val requestId = storage.saveRequest(csr)
storage.markRequestTicketCreated(requestId) storage.markRequestTicketCreated(requestId)
@ -151,5 +152,5 @@ internal fun createValidSignedNodeInfo(organisation: String,
val (identity, key) = nodeInfoBuilder.addIdentity(CordaX500Name.build(X500Principal(csr.subject.encoded)), nodeKeyPair) val (identity, key) = nodeInfoBuilder.addIdentity(CordaX500Name.build(X500Principal(csr.subject.encoded)), nodeKeyPair)
storage.putCertificatePath(requestId, identity.certPath, "Test") storage.putCertificatePath(requestId, identity.certPath, "Test")
val (_, signedNodeInfo) = nodeInfoBuilder.buildWithSigned(1) val (_, signedNodeInfo) = nodeInfoBuilder.buildWithSigned(1)
return Pair(NodeInfoWithSigned(signedNodeInfo), key) return Pair(NodeInfoAndSigned(signedNodeInfo), key)
} }