From c3c1f3d801354803d6f7d6d41431e34514186bec Mon Sep 17 00:00:00 2001 From: Andras Slemmer Date: Fri, 17 Mar 2017 10:33:01 +0000 Subject: [PATCH] Add :node-api, factor out some artemis code --- .../corda/client/model/NodeMonitorModel.kt | 2 +- node-api/build.gradle | 52 ++++++++++++ .../net/corda/config/ConfigUtilities.kt | 75 ++++++++++++++++ .../net/corda/config/SSLConfiguration.kt | 12 +++ .../net/corda/node/ArtemisTcpTransport.kt | 85 +++++++++++++++++++ node/build.gradle | 1 + .../services/messaging/MQSecurityTest.kt | 2 +- .../kotlin/net/corda/node/driver/Driver.kt | 2 +- .../node/services/config/ConfigUtilities.kt | 60 +------------ .../node/services/config/NodeConfiguration.kt | 11 +-- .../messaging/ArtemisMessagingComponent.kt | 77 +---------------- .../messaging/ArtemisMessagingServer.kt | 23 +++-- .../node/services/messaging/CordaRPCClient.kt | 7 +- .../services/messaging/NodeMessagingClient.kt | 5 +- .../transactions/RaftUniquenessProvider.kt | 2 +- .../corda/attachmentdemo/AttachmentDemo.kt | 2 +- .../kotlin/net/corda/notarydemo/NotaryDemo.kt | 2 +- .../kotlin/net/corda/traderdemo/TraderDemo.kt | 2 +- settings.gradle | 1 + .../kotlin/net/corda/testing/CoreTestUtils.kt | 2 +- .../corda/testing/messaging/SimpleMQClient.kt | 7 +- .../net/corda/explorer/views/LoginView.kt | 9 +- .../net/corda/loadtest/ConnectionManager.kt | 2 +- .../corda/loadtest/LoadTestConfiguration.kt | 2 +- 24 files changed, 270 insertions(+), 175 deletions(-) create mode 100644 node-api/build.gradle create mode 100644 node-api/src/main/kotlin/net/corda/config/ConfigUtilities.kt create mode 100644 node-api/src/main/kotlin/net/corda/config/SSLConfiguration.kt create mode 100644 node-api/src/main/kotlin/net/corda/node/ArtemisTcpTransport.kt diff --git a/client/src/main/kotlin/net/corda/client/model/NodeMonitorModel.kt b/client/src/main/kotlin/net/corda/client/model/NodeMonitorModel.kt index 0bb3561979..1f058bd3b7 100644 --- a/client/src/main/kotlin/net/corda/client/model/NodeMonitorModel.kt +++ b/client/src/main/kotlin/net/corda/client/model/NodeMonitorModel.kt @@ -2,6 +2,7 @@ package net.corda.client.model import com.google.common.net.HostAndPort import javafx.beans.property.SimpleObjectProperty +import net.corda.config.SSLConfiguration import net.corda.core.flows.StateMachineRunId import net.corda.core.messaging.CordaRPCOps import net.corda.core.messaging.StateMachineInfo @@ -11,7 +12,6 @@ import net.corda.core.node.services.StateMachineTransactionMapping import net.corda.core.node.services.Vault import net.corda.core.seconds import net.corda.core.transactions.SignedTransaction -import net.corda.node.services.config.SSLConfiguration import net.corda.node.services.messaging.CordaRPCClient import rx.Observable import rx.subjects.PublishSubject diff --git a/node-api/build.gradle b/node-api/build.gradle new file mode 100644 index 0000000000..c2f05ad11f --- /dev/null +++ b/node-api/build.gradle @@ -0,0 +1,52 @@ +apply plugin: 'kotlin' +apply plugin: 'net.corda.plugins.quasar-utils' +apply plugin: 'net.corda.plugins.publish-utils' + +description 'Corda node Artemis API' + +buildscript { + repositories { + mavenCentral() + } +} + +repositories { + mavenLocal() + mavenCentral() + jcenter() + maven { + url 'http://oss.sonatype.org/content/repositories/snapshots' + } + maven { + url 'https://dl.bintray.com/kotlin/exposed' + } +} + +sourceSets { + test { + resources { + srcDir "../config/test" + } + } + main { + resources { + srcDir "../config/dev" + } + } +} + +dependencies { + compile project(":core") + + compile "org.jetbrains.kotlin:kotlin-stdlib:$kotlin_version" + compile "org.jetbrains.kotlin:kotlin-reflect:$kotlin_version" + compile "org.jetbrains.kotlinx:kotlinx-support-jdk8:0.3" + compile "org.jetbrains.kotlin:kotlin-test:$kotlin_version" + compile "org.apache.activemq:artemis-server:${artemis_version}" + compile "org.apache.activemq:artemis-core-client:${artemis_version}" + compile "org.apache.activemq:artemis-commons:${artemis_version}" + runtime "org.apache.activemq:artemis-amqp-protocol:${artemis_version}" + + // TypeSafe Config: for simple and human friendly config files. + compile "com.typesafe:config:$typesafe_config_version" +} diff --git a/node-api/src/main/kotlin/net/corda/config/ConfigUtilities.kt b/node-api/src/main/kotlin/net/corda/config/ConfigUtilities.kt new file mode 100644 index 0000000000..d68b050bb3 --- /dev/null +++ b/node-api/src/main/kotlin/net/corda/config/ConfigUtilities.kt @@ -0,0 +1,75 @@ +package net.corda.config + +import com.google.common.net.HostAndPort +import com.typesafe.config.Config +import java.net.URL +import java.nio.file.FileSystems +import java.nio.file.Path +import java.nio.file.Paths +import java.time.Instant +import java.time.LocalDate +import java.util.* +import kotlin.reflect.KProperty +import kotlin.reflect.jvm.javaType + +private fun > enumBridge(clazz: Class, enumValueString: String): T { + return java.lang.Enum.valueOf(clazz, enumValueString) +} +private class DummyEnum : Enum("", 0) + +@Suppress("UNCHECKED_CAST", "PLATFORM_CLASS_MAPPED_TO_KOTLIN") +operator fun Config.getValue(receiver: Any, metadata: KProperty<*>): T { + if (metadata.returnType.isMarkedNullable && !hasPath(metadata.name)) { + return null as T + } + val returnType = metadata.returnType.javaType + return when (metadata.returnType.javaType) { + String::class.java -> getString(metadata.name) as T + Int::class.java -> getInt(metadata.name) as T + Integer::class.java -> getInt(metadata.name) as T + Long::class.java -> getLong(metadata.name) as T + Double::class.java -> getDouble(metadata.name) as T + Boolean::class.java -> getBoolean(metadata.name) as T + LocalDate::class.java -> LocalDate.parse(getString(metadata.name)) as T + Instant::class.java -> Instant.parse(getString(metadata.name)) as T + HostAndPort::class.java -> HostAndPort.fromString(getString(metadata.name)) as T + Path::class.java -> Paths.get(getString(metadata.name)) as T + URL::class.java -> URL(getString(metadata.name)) as T + Properties::class.java -> getProperties(metadata.name) as T + else -> { + if (returnType is Class<*> && Enum::class.java.isAssignableFrom(returnType)) { + return enumBridge(returnType as Class, getString(metadata.name)) as T + } + throw IllegalArgumentException("Unsupported type ${metadata.returnType}") + } + } +} + +/** + * Helper class for optional configurations + */ +class OptionalConfig(val conf: Config, val lambda: () -> T) { + operator fun getValue(receiver: Any, metadata: KProperty<*>): T { + return if (conf.hasPath(metadata.name)) conf.getValue(receiver, metadata) else lambda() + } +} + +fun Config.getOrElse(lambda: () -> T): OptionalConfig = OptionalConfig(this, lambda) + +fun Config.getProperties(path: String): Properties { + val obj = this.getObject(path) + val props = Properties() + for ((property, objectValue) in obj.entries) { + props.setProperty(property, objectValue.unwrapped().toString()) + } + return props +} + +@Suppress("UNCHECKED_CAST") +inline fun Config.getListOrElse(path: String, default: Config.() -> List): List { + return if (hasPath(path)) { + (if (T::class == String::class) getStringList(path) else getConfigList(path)) as List + } else { + this.default() + } +} diff --git a/node-api/src/main/kotlin/net/corda/config/SSLConfiguration.kt b/node-api/src/main/kotlin/net/corda/config/SSLConfiguration.kt new file mode 100644 index 0000000000..a8fcde91d6 --- /dev/null +++ b/node-api/src/main/kotlin/net/corda/config/SSLConfiguration.kt @@ -0,0 +1,12 @@ +package net.corda.config + +import net.corda.core.div +import java.nio.file.Path + +interface SSLConfiguration { + val keyStorePassword: String + val trustStorePassword: String + val certificatesDirectory: Path + val keyStoreFile: Path get() = certificatesDirectory / "sslkeystore.jks" + val trustStoreFile: Path get() = certificatesDirectory / "truststore.jks" +} \ No newline at end of file diff --git a/node-api/src/main/kotlin/net/corda/node/ArtemisTcpTransport.kt b/node-api/src/main/kotlin/net/corda/node/ArtemisTcpTransport.kt new file mode 100644 index 0000000000..df80da2d4d --- /dev/null +++ b/node-api/src/main/kotlin/net/corda/node/ArtemisTcpTransport.kt @@ -0,0 +1,85 @@ +package net.corda.node + +import com.google.common.net.HostAndPort +import net.corda.config.SSLConfiguration +import org.apache.activemq.artemis.api.core.TransportConfiguration +import org.apache.activemq.artemis.core.remoting.impl.netty.NettyAcceptorFactory +import org.apache.activemq.artemis.core.remoting.impl.netty.NettyConnectorFactory +import org.apache.activemq.artemis.core.remoting.impl.netty.TransportConstants +import java.nio.file.FileSystems +import java.nio.file.Path + +sealed class ConnectionDirection { + object Inbound : ConnectionDirection() + class Outbound(val expectedCommonName: String? = null) : ConnectionDirection() +} + +class ArtemisTcpTransport { + companion object { + const val VERIFY_PEER_COMMON_NAME = "corda.verifyPeerCommonName" + + // Restrict enabled Cipher Suites to AES and GCM as minimum for the bulk cipher. + // Our self-generated certificates all use ECDSA for handshakes, but we allow classical RSA certificates to work + // in case we need to use keytool certificates in some demos + private val CIPHER_SUITES = listOf( + "TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256", + "TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256", + "TLS_RSA_WITH_AES_128_GCM_SHA256", + "TLS_ECDH_ECDSA_WITH_AES_128_GCM_SHA256", + "TLS_ECDH_RSA_WITH_AES_128_GCM_SHA256", + "TLS_DHE_RSA_WITH_AES_128_GCM_SHA256", + "TLS_DHE_DSS_WITH_AES_128_GCM_SHA256" + ) + + fun tcpTransport( + direction: ConnectionDirection, + hostAndPort: HostAndPort, + config: SSLConfiguration?, + acceptorFactoryClassName: String = NettyAcceptorFactory::class.java.name, + connectorFactoryClassName: String = NettyConnectorFactory::class.java.name, + enableSSL: Boolean = true + ): TransportConfiguration { + val options = mutableMapOf( + // Basic TCP target details + TransportConstants.HOST_PROP_NAME to hostAndPort.hostText, + TransportConstants.PORT_PROP_NAME to hostAndPort.port, + + // Turn on AMQP support, which needs the protocol jar on the classpath. + // Unfortunately we cannot disable core protocol as artemis only uses AMQP for interop + // It does not use AMQP messages for its own messages e.g. topology and heartbeats + // TODO further investigate how to ensure we use a well defined wire level protocol for Node to Node communications + TransportConstants.PROTOCOLS_PROP_NAME to "CORE,AMQP" + ) + + if (config != null && enableSSL) { + config.keyStoreFile.expectedOnDefaultFileSystem() + config.trustStoreFile.expectedOnDefaultFileSystem() + val tlsOptions = mapOf( + // Enable TLS transport layer with client certs and restrict to at least SHA256 in handshake + // and AES encryption + TransportConstants.SSL_ENABLED_PROP_NAME to true, + TransportConstants.KEYSTORE_PROVIDER_PROP_NAME to "JKS", + TransportConstants.KEYSTORE_PATH_PROP_NAME to config.keyStoreFile, + TransportConstants.KEYSTORE_PASSWORD_PROP_NAME to config.keyStorePassword, // TODO proper management of keystores and password + TransportConstants.TRUSTSTORE_PROVIDER_PROP_NAME to "JKS", + TransportConstants.TRUSTSTORE_PATH_PROP_NAME to config.trustStoreFile, + TransportConstants.TRUSTSTORE_PASSWORD_PROP_NAME to config.trustStorePassword, + TransportConstants.ENABLED_CIPHER_SUITES_PROP_NAME to CIPHER_SUITES.joinToString(","), + TransportConstants.ENABLED_PROTOCOLS_PROP_NAME to "TLSv1.2", + TransportConstants.NEED_CLIENT_AUTH_PROP_NAME to true, + VERIFY_PEER_COMMON_NAME to (direction as? ConnectionDirection.Outbound)?.expectedCommonName + ) + options.putAll(tlsOptions) + } + val factoryName = when (direction) { + is ConnectionDirection.Inbound -> acceptorFactoryClassName + is ConnectionDirection.Outbound -> connectorFactoryClassName + } + return TransportConfiguration(factoryName, options) + } + } +} + +fun Path.expectedOnDefaultFileSystem() { + require(fileSystem == FileSystems.getDefault()) { "Artemis only uses the default file system" } +} diff --git a/node/build.gradle b/node/build.gradle index 945ec15cf6..7c864e538b 100644 --- a/node/build.gradle +++ b/node/build.gradle @@ -68,6 +68,7 @@ dependencies { compile (project(':node-schemas')) { exclude group: 'javassist', module: 'javassist' } + compile project(':node-api') compile "com.google.code.findbugs:jsr305:3.0.1" diff --git a/node/src/integration-test/kotlin/net/corda/services/messaging/MQSecurityTest.kt b/node/src/integration-test/kotlin/net/corda/services/messaging/MQSecurityTest.kt index 74a131fd8e..cfdbc321e7 100644 --- a/node/src/integration-test/kotlin/net/corda/services/messaging/MQSecurityTest.kt +++ b/node/src/integration-test/kotlin/net/corda/services/messaging/MQSecurityTest.kt @@ -2,6 +2,7 @@ package net.corda.services.messaging import co.paralleluniverse.fibers.Suspendable import com.google.common.net.HostAndPort +import net.corda.config.SSLConfiguration import net.corda.core.crypto.Party import net.corda.core.crypto.composite import net.corda.core.crypto.generateKeyPair @@ -13,7 +14,6 @@ import net.corda.core.seconds import net.corda.core.utilities.unwrap import net.corda.node.internal.Node import net.corda.node.services.User -import net.corda.node.services.config.SSLConfiguration import net.corda.node.services.messaging.ArtemisMessagingComponent.Companion.CLIENTS_PREFIX import net.corda.node.services.messaging.ArtemisMessagingComponent.Companion.INTERNAL_PREFIX import net.corda.node.services.messaging.ArtemisMessagingComponent.Companion.NETWORK_MAP_QUEUE diff --git a/node/src/main/kotlin/net/corda/node/driver/Driver.kt b/node/src/main/kotlin/net/corda/node/driver/Driver.kt index 1f9d51bf87..c60cbfdb26 100644 --- a/node/src/main/kotlin/net/corda/node/driver/Driver.kt +++ b/node/src/main/kotlin/net/corda/node/driver/Driver.kt @@ -8,6 +8,7 @@ import com.google.common.util.concurrent.ListenableFuture import com.google.common.util.concurrent.SettableFuture import com.typesafe.config.Config import com.typesafe.config.ConfigRenderOptions +import net.corda.config.SSLConfiguration import net.corda.core.* import net.corda.core.crypto.Party import net.corda.core.messaging.CordaRPCOps @@ -18,7 +19,6 @@ import net.corda.core.utilities.loggerFor import net.corda.node.services.User import net.corda.node.services.config.ConfigHelper import net.corda.node.services.config.FullNodeConfiguration -import net.corda.node.services.config.SSLConfiguration import net.corda.node.services.messaging.ArtemisMessagingComponent import net.corda.node.services.messaging.CordaRPCClient import net.corda.node.services.messaging.NodeMessagingClient diff --git a/node/src/main/kotlin/net/corda/node/services/config/ConfigUtilities.kt b/node/src/main/kotlin/net/corda/node/services/config/ConfigUtilities.kt index 5da6f9bfb0..3fb5513ea1 100644 --- a/node/src/main/kotlin/net/corda/node/services/config/ConfigUtilities.kt +++ b/node/src/main/kotlin/net/corda/node/services/config/ConfigUtilities.kt @@ -3,11 +3,11 @@ package net.corda.node.services.config -import com.google.common.net.HostAndPort import com.typesafe.config.Config import com.typesafe.config.ConfigFactory import com.typesafe.config.ConfigParseOptions import com.typesafe.config.ConfigRenderOptions +import net.corda.config.SSLConfiguration import net.corda.core.copyTo import net.corda.core.createDirectories import net.corda.core.crypto.X509Utilities @@ -15,13 +15,8 @@ import net.corda.core.div import net.corda.core.exists import net.corda.core.utilities.loggerFor import java.net.URL +import java.nio.file.Files import java.nio.file.Path -import java.nio.file.Paths -import java.time.Instant -import java.time.LocalDate -import java.util.* -import kotlin.reflect.KProperty -import kotlin.reflect.jvm.javaType object ConfigHelper { private val log = loggerFor() @@ -46,57 +41,6 @@ object ConfigHelper { } } -@Suppress("UNCHECKED_CAST", "PLATFORM_CLASS_MAPPED_TO_KOTLIN") -operator fun Config.getValue(receiver: Any, metadata: KProperty<*>): T { - if (metadata.returnType.isMarkedNullable && !hasPath(metadata.name)) { - return null as T - } - return when (metadata.returnType.javaType) { - String::class.java -> getString(metadata.name) as T - Int::class.java -> getInt(metadata.name) as T - Integer::class.java -> getInt(metadata.name) as T - Long::class.java -> getLong(metadata.name) as T - Double::class.java -> getDouble(metadata.name) as T - Boolean::class.java -> getBoolean(metadata.name) as T - LocalDate::class.java -> LocalDate.parse(getString(metadata.name)) as T - Instant::class.java -> Instant.parse(getString(metadata.name)) as T - HostAndPort::class.java -> HostAndPort.fromString(getString(metadata.name)) as T - Path::class.java -> Paths.get(getString(metadata.name)) as T - URL::class.java -> URL(getString(metadata.name)) as T - Properties::class.java -> getProperties(metadata.name) as T - else -> throw IllegalArgumentException("Unsupported type ${metadata.returnType}") - } -} - -/** - * Helper class for optional configurations - */ -class OptionalConfig(val conf: Config, val lambda: () -> T) { - operator fun getValue(receiver: Any, metadata: KProperty<*>): T { - return if (conf.hasPath(metadata.name)) conf.getValue(receiver, metadata) else lambda() - } -} - -fun Config.getOrElse(lambda: () -> T): OptionalConfig = OptionalConfig(this, lambda) - -fun Config.getProperties(path: String): Properties { - val obj = this.getObject(path) - val props = Properties() - for ((property, objectValue) in obj.entries) { - props.setProperty(property, objectValue.unwrapped().toString()) - } - return props -} - -@Suppress("UNCHECKED_CAST") -inline fun Config.getListOrElse(path: String, default: Config.() -> List): List { - return if (hasPath(path)) { - (if (T::class == String::class) getStringList(path) else getConfigList(path)) as List - } else { - this.default() - } -} - /** * Strictly for dev only automatically construct a server certificate/private key signed from * the CA certs in Node resources. Then provision KeyStores into certificates folder under node path. diff --git a/node/src/main/kotlin/net/corda/node/services/config/NodeConfiguration.kt b/node/src/main/kotlin/net/corda/node/services/config/NodeConfiguration.kt index 444cc5d443..942e5a0cfb 100644 --- a/node/src/main/kotlin/net/corda/node/services/config/NodeConfiguration.kt +++ b/node/src/main/kotlin/net/corda/node/services/config/NodeConfiguration.kt @@ -2,6 +2,10 @@ package net.corda.node.services.config import com.google.common.net.HostAndPort import com.typesafe.config.Config +import net.corda.config.SSLConfiguration +import net.corda.config.getListOrElse +import net.corda.config.getOrElse +import net.corda.config.getValue import net.corda.core.div import net.corda.core.node.NodeVersionInfo import net.corda.core.node.services.ServiceInfo @@ -15,13 +19,6 @@ import java.net.URL import java.nio.file.Path import java.util.* -interface SSLConfiguration { - val keyStorePassword: String - val trustStorePassword: String - val certificatesDirectory: Path - val keyStoreFile: Path get() = certificatesDirectory / "sslkeystore.jks" - val trustStoreFile: Path get() = certificatesDirectory / "truststore.jks" -} interface NodeConfiguration : SSLConfiguration { val baseDirectory: Path diff --git a/node/src/main/kotlin/net/corda/node/services/messaging/ArtemisMessagingComponent.kt b/node/src/main/kotlin/net/corda/node/services/messaging/ArtemisMessagingComponent.kt index 1b286b8b8b..6e4fe2401a 100644 --- a/node/src/main/kotlin/net/corda/node/services/messaging/ArtemisMessagingComponent.kt +++ b/node/src/main/kotlin/net/corda/node/services/messaging/ArtemisMessagingComponent.kt @@ -2,6 +2,7 @@ package net.corda.node.services.messaging import com.google.common.annotations.VisibleForTesting import com.google.common.net.HostAndPort +import net.corda.config.SSLConfiguration import net.corda.core.crypto.CompositeKey import net.corda.core.messaging.MessageRecipientGroup import net.corda.core.messaging.MessageRecipients @@ -9,14 +10,6 @@ import net.corda.core.messaging.SingleMessageRecipient import net.corda.core.read import net.corda.core.serialization.CordaSerializable import net.corda.core.serialization.SingletonSerializeAsToken -import net.corda.node.services.config.SSLConfiguration -import net.corda.node.services.messaging.ArtemisMessagingComponent.ConnectionDirection.Inbound -import net.corda.node.services.messaging.ArtemisMessagingComponent.ConnectionDirection.Outbound -import org.apache.activemq.artemis.api.core.TransportConfiguration -import org.apache.activemq.artemis.core.remoting.impl.netty.NettyAcceptorFactory -import org.apache.activemq.artemis.core.remoting.impl.netty.TransportConstants -import java.nio.file.FileSystems -import java.nio.file.Path import java.security.KeyStore /** @@ -43,8 +36,6 @@ abstract class ArtemisMessagingComponent : SingletonSerializeAsToken() { const val NOTIFICATIONS_ADDRESS = "${INTERNAL_PREFIX}activemq.notifications" const val NETWORK_MAP_QUEUE = "${INTERNAL_PREFIX}networkmap" - const val VERIFY_PEER_COMMON_NAME = "corda.verifyPeerCommonName" - /** * Assuming the passed in target address is actually an ArtemisAddress will extract the host and port of the node. This should * only be used in unit tests and the internals of the messaging services to keep addressing opaque for the future. @@ -111,19 +102,6 @@ abstract class ArtemisMessagingComponent : SingletonSerializeAsToken() { /** The config object is used to pass in the passwords for the certificate KeyStore and TrustStore */ abstract val config: SSLConfiguration? - // Restrict enabled Cipher Suites to AES and GCM as minimum for the bulk cipher. - // Our self-generated certificates all use ECDSA for handshakes, but we allow classical RSA certificates to work - // in case we need to use keytool certificates in some demos - private val CIPHER_SUITES = listOf( - "TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256", - "TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256", - "TLS_RSA_WITH_AES_128_GCM_SHA256", - "TLS_ECDH_ECDSA_WITH_AES_128_GCM_SHA256", - "TLS_ECDH_RSA_WITH_AES_128_GCM_SHA256", - "TLS_DHE_RSA_WITH_AES_128_GCM_SHA256", - "TLS_DHE_DSS_WITH_AES_128_GCM_SHA256" - ) - /** * Returns nothing if the keystore was opened OK or throws if not. Useful to check the password, as * unfortunately Artemis tends to bury the exception when the password is wrong. @@ -137,57 +115,4 @@ abstract class ArtemisMessagingComponent : SingletonSerializeAsToken() { KeyStore.getInstance("JKS").load(it, config.trustStorePassword.toCharArray()) } } - - protected fun tcpTransport(direction: ConnectionDirection, host: String, port: Int, enableSSL: Boolean = true): TransportConfiguration { - // Will throw exception if enableSSL = true but config is missing - require(config != null || !enableSSL) { "SSL configuration cannot be null when SSL is enabled." } - - val config = config - val options = mutableMapOf( - // Basic TCP target details - TransportConstants.HOST_PROP_NAME to host, - TransportConstants.PORT_PROP_NAME to port, - - // Turn on AMQP support, which needs the protocol jar on the classpath. - // Unfortunately we cannot disable core protocol as artemis only uses AMQP for interop - // It does not use AMQP messages for its own messages e.g. topology and heartbeats - // TODO further investigate how to ensure we use a well defined wire level protocol for Node to Node communications - TransportConstants.PROTOCOLS_PROP_NAME to "CORE,AMQP" - ) - - if (config != null && enableSSL) { - config.keyStoreFile.expectedOnDefaultFileSystem() - config.trustStoreFile.expectedOnDefaultFileSystem() - val tlsOptions = mapOf( - // Enable TLS transport layer with client certs and restrict to at least SHA256 in handshake - // and AES encryption - TransportConstants.SSL_ENABLED_PROP_NAME to true, - TransportConstants.KEYSTORE_PROVIDER_PROP_NAME to "JKS", - TransportConstants.KEYSTORE_PATH_PROP_NAME to config.keyStoreFile, - TransportConstants.KEYSTORE_PASSWORD_PROP_NAME to config.keyStorePassword, // TODO proper management of keystores and password - TransportConstants.TRUSTSTORE_PROVIDER_PROP_NAME to "JKS", - TransportConstants.TRUSTSTORE_PATH_PROP_NAME to config.trustStoreFile, - TransportConstants.TRUSTSTORE_PASSWORD_PROP_NAME to config.trustStorePassword, - TransportConstants.ENABLED_CIPHER_SUITES_PROP_NAME to CIPHER_SUITES.joinToString(","), - TransportConstants.ENABLED_PROTOCOLS_PROP_NAME to "TLSv1.2", - TransportConstants.NEED_CLIENT_AUTH_PROP_NAME to true, - VERIFY_PEER_COMMON_NAME to (direction as? Outbound)?.expectedCommonName - ) - options.putAll(tlsOptions) - } - val factoryName = when (direction) { - is Inbound -> NettyAcceptorFactory::class.java.name - is Outbound -> VerifyingNettyConnectorFactory::class.java.name - } - return TransportConfiguration(factoryName, options) - } - - protected fun Path.expectedOnDefaultFileSystem() { - require(fileSystem == FileSystems.getDefault()) { "Artemis only uses the default file system" } - } - - protected sealed class ConnectionDirection { - object Inbound : ConnectionDirection() - class Outbound(val expectedCommonName: String? = null) : ConnectionDirection() - } } diff --git a/node/src/main/kotlin/net/corda/node/services/messaging/ArtemisMessagingServer.kt b/node/src/main/kotlin/net/corda/node/services/messaging/ArtemisMessagingServer.kt index b23eed6ad4..fa6f043843 100644 --- a/node/src/main/kotlin/net/corda/node/services/messaging/ArtemisMessagingServer.kt +++ b/node/src/main/kotlin/net/corda/node/services/messaging/ArtemisMessagingServer.kt @@ -16,14 +16,15 @@ import net.corda.core.node.services.NetworkMapCache.MapChange import net.corda.core.seconds import net.corda.core.utilities.debug import net.corda.core.utilities.loggerFor +import net.corda.node.ArtemisTcpTransport +import net.corda.node.ConnectionDirection +import net.corda.node.expectedOnDefaultFileSystem import net.corda.node.printBasicNodeInfo import net.corda.node.services.RPCUserService import net.corda.node.services.config.NodeConfiguration import net.corda.node.services.messaging.ArtemisMessagingComponent.Companion.CLIENTS_PREFIX import net.corda.node.services.messaging.ArtemisMessagingComponent.Companion.NODE_USER import net.corda.node.services.messaging.ArtemisMessagingComponent.Companion.PEER_USER -import net.corda.node.services.messaging.ArtemisMessagingComponent.ConnectionDirection.Inbound -import net.corda.node.services.messaging.ArtemisMessagingComponent.ConnectionDirection.Outbound import net.corda.node.services.messaging.NodeLoginModule.Companion.NODE_ROLE import net.corda.node.services.messaging.NodeLoginModule.Companion.PEER_ROLE import net.corda.node.services.messaging.NodeLoginModule.Companion.RPC_ROLE @@ -151,9 +152,9 @@ class ArtemisMessagingServer(override val config: NodeConfiguration, bindingsDirectory = (artemisDir / "bindings").toString() journalDirectory = (artemisDir / "journal").toString() largeMessagesDirectory = (artemisDir / "large-messages").toString() - val acceptors = mutableSetOf(tcpTransport(Inbound, "0.0.0.0", p2pHostPort.port)) + val acceptors = mutableSetOf(verifyingTcpTransport(ConnectionDirection.Inbound, "0.0.0.0", p2pHostPort.port)) if (rpcHostPort != null) { - acceptors.add(tcpTransport(Inbound, "0.0.0.0", rpcHostPort.port, enableSSL = false)) + acceptors.add(verifyingTcpTransport(ConnectionDirection.Inbound, "0.0.0.0", rpcHostPort.port, enableSSL = false)) } acceptorConfigurations = acceptors // Enable built in message deduplication. Note we still have to do our own as the delayed commits @@ -191,8 +192,8 @@ class ArtemisMessagingServer(override val config: NodeConfiguration, } /** - * Authenticated clients connecting to us fall in one of three groups: - * 1. The node hosting us and any of its logically connected components. These are given full access to all valid queues. + * Authenticated clients connecting to us fall in one of the following groups: + * 1. The node itself. It is given full access to all valid queues. * 2. Peers on the same network as us. These are only given permission to send to our P2P inbound queue. * 3. RPC users. These are only given sufficient access to perform RPC with us. */ @@ -327,6 +328,12 @@ class ArtemisMessagingServer(override val config: NodeConfiguration, deployBridge(address.queueName, address.hostAndPort, legalName) } + private fun verifyingTcpTransport(direction: ConnectionDirection, host: String, port: Int, enableSSL: Boolean = true) = + ArtemisTcpTransport.tcpTransport(direction, HostAndPort.fromParts(host, port), config, + enableSSL = enableSSL, + connectorFactoryClassName = VerifyingNettyConnectorFactory::class.java.name + ) + /** * All nodes are expected to have a public facing address called [ArtemisMessagingComponent.P2P_QUEUE] for receiving * messages from other nodes. When we want to send a message to a node we send it to our internal address/queue for it, @@ -334,7 +341,7 @@ class ArtemisMessagingServer(override val config: NodeConfiguration, * P2P address. */ private fun deployBridge(queueName: String, target: HostAndPort, legalName: String) { - val tcpTransport = tcpTransport(Outbound(expectedCommonName = legalName), target.hostText, target.port) + val tcpTransport = verifyingTcpTransport(ConnectionDirection.Outbound(expectedCommonName = legalName), target.hostText, target.port) tcpTransport.params[ArtemisMessagingServer::class.java.name] = this // We intentionally overwrite any previous connector config in case the peer legal name changed activeMQServer.configuration.addConnectorConfiguration(target.toString(), tcpTransport) @@ -407,7 +414,7 @@ private class VerifyingNettyConnector(configuration: MutableMap?, protocolManager: ClientProtocolManager?) : NettyConnector(configuration, handler, listener, closeExecutor, threadPool, scheduledThreadPool, protocolManager) { private val server = configuration?.get(ArtemisMessagingServer::class.java.name) as? ArtemisMessagingServer - private val expectedCommonName = configuration?.get(ArtemisMessagingComponent.VERIFY_PEER_COMMON_NAME) as? String + private val expectedCommonName = configuration?.get(ArtemisTcpTransport.VERIFY_PEER_COMMON_NAME) as? String override fun createConnection(): Connection? { val connection = super.createConnection() as NettyConnection? diff --git a/node/src/main/kotlin/net/corda/node/services/messaging/CordaRPCClient.kt b/node/src/main/kotlin/net/corda/node/services/messaging/CordaRPCClient.kt index ec32e9bd8c..d9f4e2a2ab 100644 --- a/node/src/main/kotlin/net/corda/node/services/messaging/CordaRPCClient.kt +++ b/node/src/main/kotlin/net/corda/node/services/messaging/CordaRPCClient.kt @@ -1,14 +1,15 @@ package net.corda.node.services.messaging import com.google.common.net.HostAndPort +import net.corda.config.SSLConfiguration import net.corda.core.ThreadBox import net.corda.core.logElapsedTime import net.corda.core.messaging.CordaRPCOps import net.corda.core.minutes import net.corda.core.seconds import net.corda.core.utilities.loggerFor -import net.corda.node.services.config.SSLConfiguration -import net.corda.node.services.messaging.ArtemisMessagingComponent.ConnectionDirection.Outbound +import net.corda.node.ArtemisTcpTransport.Companion.tcpTransport +import net.corda.node.ConnectionDirection import org.apache.activemq.artemis.api.core.ActiveMQException import org.apache.activemq.artemis.api.core.client.ActiveMQClient import org.apache.activemq.artemis.api.core.client.ClientSession @@ -52,7 +53,7 @@ class CordaRPCClient(val host: HostAndPort, override val config: SSLConfiguratio check(!running) log.logElapsedTime("Startup") { checkStorePasswords() - val serverLocator = ActiveMQClient.createServerLocatorWithoutHA(tcpTransport(Outbound(), host.hostText, host.port, enableSSL = config != null)).apply { + val serverLocator = ActiveMQClient.createServerLocatorWithoutHA(tcpTransport(ConnectionDirection.Outbound(), host, config, enableSSL = config != null)).apply { // TODO: Put these in config file or make it user configurable? threadPoolMaxSize = 1 confirmationWindowSize = 100000 // a guess diff --git a/node/src/main/kotlin/net/corda/node/services/messaging/NodeMessagingClient.kt b/node/src/main/kotlin/net/corda/node/services/messaging/NodeMessagingClient.kt index f9aee2122b..a8fb3f250e 100644 --- a/node/src/main/kotlin/net/corda/node/services/messaging/NodeMessagingClient.kt +++ b/node/src/main/kotlin/net/corda/node/services/messaging/NodeMessagingClient.kt @@ -12,10 +12,11 @@ import net.corda.core.serialization.opaque import net.corda.core.success import net.corda.core.utilities.loggerFor import net.corda.core.utilities.trace +import net.corda.node.ArtemisTcpTransport +import net.corda.node.ConnectionDirection import net.corda.node.services.RPCUserService import net.corda.node.services.api.MessagingServiceInternal import net.corda.node.services.config.NodeConfiguration -import net.corda.node.services.messaging.ArtemisMessagingComponent.ConnectionDirection.Outbound import net.corda.node.services.statemachine.StateMachineManager import net.corda.node.utilities.* import org.apache.activemq.artemis.api.core.ActiveMQObjectClosedException @@ -129,7 +130,7 @@ class NodeMessagingClient(override val config: NodeConfiguration, log.info("Connecting to server: $serverHostPort") // TODO Add broker CN to config for host verification in case the embedded broker isn't used - val tcpTransport = tcpTransport(Outbound(), serverHostPort.hostText, serverHostPort.port) + val tcpTransport = ArtemisTcpTransport.tcpTransport(ConnectionDirection.Outbound(), serverHostPort, config) val locator = ActiveMQClient.createServerLocatorWithoutHA(tcpTransport) clientFactory = locator.createSessionFactory() diff --git a/node/src/main/kotlin/net/corda/node/services/transactions/RaftUniquenessProvider.kt b/node/src/main/kotlin/net/corda/node/services/transactions/RaftUniquenessProvider.kt index 526d62f0c6..f62c012727 100644 --- a/node/src/main/kotlin/net/corda/node/services/transactions/RaftUniquenessProvider.kt +++ b/node/src/main/kotlin/net/corda/node/services/transactions/RaftUniquenessProvider.kt @@ -20,7 +20,7 @@ import net.corda.core.serialization.SingletonSerializeAsToken import net.corda.core.serialization.deserialize import net.corda.core.serialization.serialize import net.corda.core.utilities.loggerFor -import net.corda.node.services.config.SSLConfiguration +import net.corda.config.SSLConfiguration import org.jetbrains.exposed.sql.Database import java.nio.file.Path import java.util.concurrent.CompletableFuture diff --git a/samples/attachment-demo/src/main/kotlin/net/corda/attachmentdemo/AttachmentDemo.kt b/samples/attachment-demo/src/main/kotlin/net/corda/attachmentdemo/AttachmentDemo.kt index a84a0802b3..70740eb869 100644 --- a/samples/attachment-demo/src/main/kotlin/net/corda/attachmentdemo/AttachmentDemo.kt +++ b/samples/attachment-demo/src/main/kotlin/net/corda/attachmentdemo/AttachmentDemo.kt @@ -2,6 +2,7 @@ package net.corda.attachmentdemo import com.google.common.net.HostAndPort import joptsimple.OptionParser +import net.corda.config.SSLConfiguration import net.corda.core.contracts.TransactionType import net.corda.core.crypto.Party import net.corda.core.crypto.SecureHash @@ -11,7 +12,6 @@ import net.corda.core.messaging.CordaRPCOps import net.corda.core.messaging.startFlow import net.corda.core.utilities.Emoji import net.corda.flows.FinalityFlow -import net.corda.node.services.config.SSLConfiguration import net.corda.node.services.messaging.CordaRPCClient import net.corda.testing.ALICE_KEY import java.nio.file.Path diff --git a/samples/raft-notary-demo/src/main/kotlin/net/corda/notarydemo/NotaryDemo.kt b/samples/raft-notary-demo/src/main/kotlin/net/corda/notarydemo/NotaryDemo.kt index c72994b238..5cd625baa5 100644 --- a/samples/raft-notary-demo/src/main/kotlin/net/corda/notarydemo/NotaryDemo.kt +++ b/samples/raft-notary-demo/src/main/kotlin/net/corda/notarydemo/NotaryDemo.kt @@ -3,6 +3,7 @@ package net.corda.notarydemo import com.google.common.net.HostAndPort import com.google.common.util.concurrent.Futures import joptsimple.OptionParser +import net.corda.config.SSLConfiguration import net.corda.core.crypto.toStringShort import net.corda.core.div import net.corda.core.getOrThrow @@ -10,7 +11,6 @@ import net.corda.core.messaging.CordaRPCOps import net.corda.core.messaging.startFlow import net.corda.core.transactions.SignedTransaction import net.corda.flows.NotaryFlow -import net.corda.node.services.config.SSLConfiguration import net.corda.node.services.messaging.CordaRPCClient import net.corda.notarydemo.flows.DummyIssueAndMove import java.nio.file.Path diff --git a/samples/trader-demo/src/main/kotlin/net/corda/traderdemo/TraderDemo.kt b/samples/trader-demo/src/main/kotlin/net/corda/traderdemo/TraderDemo.kt index f512db96c9..2221614bb2 100644 --- a/samples/trader-demo/src/main/kotlin/net/corda/traderdemo/TraderDemo.kt +++ b/samples/trader-demo/src/main/kotlin/net/corda/traderdemo/TraderDemo.kt @@ -2,10 +2,10 @@ package net.corda.traderdemo import com.google.common.net.HostAndPort import joptsimple.OptionParser +import net.corda.config.SSLConfiguration import net.corda.core.contracts.DOLLARS import net.corda.core.div import net.corda.core.utilities.loggerFor -import net.corda.node.services.config.SSLConfiguration import net.corda.node.services.messaging.CordaRPCClient import org.slf4j.Logger import java.nio.file.Path diff --git a/settings.gradle b/settings.gradle index 8055c23454..11a8b4a211 100644 --- a/settings.gradle +++ b/settings.gradle @@ -4,6 +4,7 @@ rootProject.name = 'corda-project' include 'finance' include 'finance:isolated' include 'core' +include 'node-api' include 'node-schemas' include 'node' include 'node:capsule' diff --git a/test-utils/src/main/kotlin/net/corda/testing/CoreTestUtils.kt b/test-utils/src/main/kotlin/net/corda/testing/CoreTestUtils.kt index b60d689637..a934da8856 100644 --- a/test-utils/src/main/kotlin/net/corda/testing/CoreTestUtils.kt +++ b/test-utils/src/main/kotlin/net/corda/testing/CoreTestUtils.kt @@ -6,6 +6,7 @@ package net.corda.testing import com.google.common.net.HostAndPort import com.google.common.util.concurrent.ListenableFuture import com.typesafe.config.Config +import net.corda.config.SSLConfiguration import net.corda.core.contracts.StateRef import net.corda.core.crypto.* import net.corda.core.flows.FlowLogic @@ -20,7 +21,6 @@ import net.corda.core.utilities.DUMMY_NOTARY_KEY import net.corda.node.internal.AbstractNode import net.corda.node.internal.NetworkMapInfo import net.corda.node.services.config.NodeConfiguration -import net.corda.node.services.config.SSLConfiguration import net.corda.node.services.config.configureDevKeyAndTrustStores import net.corda.node.services.statemachine.FlowStateMachineImpl import net.corda.node.utilities.AddOrRemove.ADD diff --git a/test-utils/src/main/kotlin/net/corda/testing/messaging/SimpleMQClient.kt b/test-utils/src/main/kotlin/net/corda/testing/messaging/SimpleMQClient.kt index d12d557e92..218467f352 100644 --- a/test-utils/src/main/kotlin/net/corda/testing/messaging/SimpleMQClient.kt +++ b/test-utils/src/main/kotlin/net/corda/testing/messaging/SimpleMQClient.kt @@ -1,9 +1,10 @@ package net.corda.testing.messaging import com.google.common.net.HostAndPort -import net.corda.node.services.config.SSLConfiguration +import net.corda.config.SSLConfiguration +import net.corda.node.ArtemisTcpTransport +import net.corda.node.ConnectionDirection import net.corda.node.services.messaging.ArtemisMessagingComponent -import net.corda.node.services.messaging.ArtemisMessagingComponent.ConnectionDirection.Outbound import net.corda.testing.configureTestSSL import org.apache.activemq.artemis.api.core.client.* @@ -17,7 +18,7 @@ class SimpleMQClient(val target: HostAndPort, lateinit var producer: ClientProducer fun start(username: String? = null, password: String? = null, enableSSL: Boolean = true) { - val tcpTransport = tcpTransport(Outbound(), target.hostText, target.port, enableSSL) + val tcpTransport = ArtemisTcpTransport.tcpTransport(ConnectionDirection.Outbound(), target, config, enableSSL = enableSSL) val locator = ActiveMQClient.createServerLocatorWithoutHA(tcpTransport).apply { isBlockOnNonDurableSend = true threadPoolMaxSize = 1 diff --git a/tools/explorer/src/main/kotlin/net/corda/explorer/views/LoginView.kt b/tools/explorer/src/main/kotlin/net/corda/explorer/views/LoginView.kt index 341bbbee70..dec885b5dc 100644 --- a/tools/explorer/src/main/kotlin/net/corda/explorer/views/LoginView.kt +++ b/tools/explorer/src/main/kotlin/net/corda/explorer/views/LoginView.kt @@ -1,20 +1,13 @@ package net.corda.explorer.views import com.google.common.net.HostAndPort -import de.jensd.fx.glyphs.fontawesome.FontAwesomeIcon -import de.jensd.fx.glyphs.fontawesome.FontAwesomeIconView import javafx.beans.property.SimpleIntegerProperty import javafx.scene.control.* -import javafx.stage.FileChooser -import net.corda.client.fxutils.map import net.corda.client.model.NodeMonitorModel import net.corda.client.model.objectProperty import net.corda.explorer.model.SettingsModel -import net.corda.node.services.config.SSLConfiguration import org.controlsfx.dialog.ExceptionDialog -import tornadofx.* -import java.nio.file.Path -import java.nio.file.Paths +import tornadofx.View import kotlin.system.exitProcess class LoginView : View() { diff --git a/tools/loadtest/src/main/kotlin/net/corda/loadtest/ConnectionManager.kt b/tools/loadtest/src/main/kotlin/net/corda/loadtest/ConnectionManager.kt index 762dc03482..13adca8fa9 100644 --- a/tools/loadtest/src/main/kotlin/net/corda/loadtest/ConnectionManager.kt +++ b/tools/loadtest/src/main/kotlin/net/corda/loadtest/ConnectionManager.kt @@ -11,7 +11,7 @@ import net.corda.core.createDirectories import net.corda.core.div import net.corda.core.messaging.CordaRPCOps import net.corda.node.driver.PortAllocation -import net.corda.node.services.config.SSLConfiguration +import net.corda.config.SSLConfiguration import net.corda.node.services.messaging.CordaRPCClient import org.slf4j.LoggerFactory import java.io.ByteArrayOutputStream diff --git a/tools/loadtest/src/main/kotlin/net/corda/loadtest/LoadTestConfiguration.kt b/tools/loadtest/src/main/kotlin/net/corda/loadtest/LoadTestConfiguration.kt index 35170883eb..38c90e4799 100644 --- a/tools/loadtest/src/main/kotlin/net/corda/loadtest/LoadTestConfiguration.kt +++ b/tools/loadtest/src/main/kotlin/net/corda/loadtest/LoadTestConfiguration.kt @@ -1,8 +1,8 @@ package net.corda.loadtest import com.typesafe.config.Config +import net.corda.config.getValue import java.nio.file.Path -import net.corda.node.services.config.* /** * @param sshUser The UNIX username to use for SSH auth.