mirror of
https://github.com/corda/corda.git
synced 2024-12-27 08:22:35 +00:00
Various cleanups of the network-management code (#545)
This commit is contained in:
parent
98f554c08d
commit
e22e7acd67
@ -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)
|
||||
)
|
||||
}
|
||||
|
@ -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
|
||||
}
|
||||
}
|
@ -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
|
||||
}
|
@ -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()
|
||||
}
|
@ -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
|
||||
|
@ -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<NetworkManagementServerParameters>(false)
|
||||
.parseAs<NetworkManagementServerConfig>(false)
|
||||
|
||||
// Make sure trust store password is only specified in root keygen mode.
|
||||
if (config.mode != Mode.ROOT_KEYGEN) {
|
||||
|
@ -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,64 +18,69 @@ 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<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 NetworkManagementServerStatus(var serverStartTime: Instant = Instant.now(), var lastRequestCheckTime: Instant? = null)
|
||||
|
||||
private fun processKeyStore(parameters: NetworkManagementServerParameters): Pair<CertPathAndKey, LocalSigner>? {
|
||||
if (parameters.keystorePath == null) return null
|
||||
private fun processKeyStore(config: NetworkManagementServerConfig): Pair<CertPathAndKey, LocalSigner>? {
|
||||
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 logDoormanVersion() {
|
||||
if (Manifests.exists("Doorman-Version")) {
|
||||
println("Doorman Version: ${Manifests.read("Doorman-Version")}")
|
||||
}
|
||||
}
|
||||
|
||||
fun main(args: Array<String>) {
|
||||
try {
|
||||
parseParameters(*args).run {
|
||||
println("Starting in $mode mode")
|
||||
logDoormanVersion()
|
||||
when (mode) {
|
||||
Mode.ROOT_KEYGEN -> generateRootKeyPair(
|
||||
private fun NetworkManagementServerConfig.rootKeyGenMode() {
|
||||
generateRootKeyPair(
|
||||
rootStorePath ?: throw IllegalArgumentException("The 'rootStorePath' parameter must be specified when generating keys!"),
|
||||
rootKeystorePassword,
|
||||
rootPrivateKeyPassword,
|
||||
trustStorePassword)
|
||||
Mode.CA_KEYGEN -> generateSigningKeyPairs(
|
||||
trustStorePassword
|
||||
)
|
||||
}
|
||||
|
||||
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)
|
||||
Mode.DOORMAN -> {
|
||||
caPrivateKeyPassword
|
||||
)
|
||||
}
|
||||
|
||||
private fun NetworkManagementServerConfig.doormanMode() {
|
||||
initialiseSerialization()
|
||||
val persistence = configureDatabase(dataSourceProperties, database)
|
||||
// TODO: move signing to signing server.
|
||||
@ -89,9 +93,9 @@ fun main(args: Array<String>) {
|
||||
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" }
|
||||
requireNotNull(networkMap) { "'networkMap' config is required for applying network parameters" }
|
||||
println("Parsing network parameters from '${it.toAbsolutePath()}'...")
|
||||
parseNetworkParametersFrom(it)
|
||||
parseNetworkParametersConfig(it).toNetworkParameters(modifiedTime = Instant.now(), epoch = 1)
|
||||
}
|
||||
val networkMapStartParams = networkMap?.let {
|
||||
NetworkMapStartParams(csrAndNetworkMap?.second, networkParameters, it)
|
||||
@ -103,10 +107,3 @@ fun main(args: Array<String>) {
|
||||
networkManagementServer.close()
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch (e: ShowHelpException) {
|
||||
e.errorMessage?.let(::println)
|
||||
e.parser.printHelpOn(System.out)
|
||||
}
|
||||
}
|
||||
|
@ -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<NetworkManagementServer>()
|
||||
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)
|
||||
}
|
||||
|
@ -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()
|
||||
}
|
@ -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())
|
||||
}
|
@ -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<Boolean, Pair<SignedNetworkMap?, NetworkParameters?>> = 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<SignedNodeInfo>()
|
||||
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)
|
||||
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
|
||||
}
|
||||
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")
|
||||
}
|
||||
}
|
||||
|
@ -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
|
||||
}
|
||||
}
|
@ -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)
|
||||
}
|
||||
}
|
@ -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<NodeInfoWithSigned, PrivateKey> {
|
||||
storage: CertificateSigningRequestStorage): Pair<NodeInfoAndSigned, PrivateKey> {
|
||||
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)
|
||||
}
|
Loading…
Reference in New Issue
Block a user