mirror of
https://github.com/corda/corda.git
synced 2024-12-28 00:38:55 +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.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)
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
@ -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.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
|
||||||
}
|
}
|
@ -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.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
|
||||||
|
@ -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) {
|
||||||
|
@ -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()
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
@ -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)
|
||||||
}
|
}
|
||||||
|
@ -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.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")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -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.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)
|
||||||
}
|
}
|
Loading…
Reference in New Issue
Block a user