Add :node-api, factor out some artemis code

This commit is contained in:
Andras Slemmer
2017-03-17 10:33:01 +00:00
parent 486368d926
commit c3c1f3d801
24 changed files with 270 additions and 175 deletions

View File

@ -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 <T : Enum<T>> enumBridge(clazz: Class<T>, enumValueString: String): T {
return java.lang.Enum.valueOf(clazz, enumValueString)
}
private class DummyEnum : Enum<DummyEnum>("", 0)
@Suppress("UNCHECKED_CAST", "PLATFORM_CLASS_MAPPED_TO_KOTLIN")
operator fun <T> 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<DummyEnum>, getString(metadata.name)) as T
}
throw IllegalArgumentException("Unsupported type ${metadata.returnType}")
}
}
}
/**
* Helper class for optional configurations
*/
class OptionalConfig<out T>(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 <T> Config.getOrElse(lambda: () -> T): OptionalConfig<T> = 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 <reified T : Any> Config.getListOrElse(path: String, default: Config.() -> List<T>): List<T> {
return if (hasPath(path)) {
(if (T::class == String::class) getStringList(path) else getConfigList(path)) as List<T>
} else {
this.default()
}
}

View File

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

View File

@ -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<String, Any?>(
// 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<String, Any?>(
// 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" }
}