mirror of
https://github.com/corda/corda.git
synced 2025-06-15 13:48:14 +00:00
Add :node-api, factor out some artemis code
This commit is contained in:
75
node-api/src/main/kotlin/net/corda/config/ConfigUtilities.kt
Normal file
75
node-api/src/main/kotlin/net/corda/config/ConfigUtilities.kt
Normal 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()
|
||||
}
|
||||
}
|
@ -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"
|
||||
}
|
@ -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" }
|
||||
}
|
Reference in New Issue
Block a user