mirror of
https://github.com/corda/corda.git
synced 2025-06-16 22:28:15 +00:00
CORDA-1343 Make the RPCClient ssl constructors public. Clean up broke… (#3039)
* CORDA-1343 Make the RPCClient ssl constructors public. Clean up broker authentication logic * CORDA-1343 small fix * CORDA-1343 cleanup * CORDA-1343 fixed api changes script * CORDA-1343 fixed merge * CORDA-1343 removed unused property * CORDA-1343 add separate p2p and rpc node users * CORDA-1343 remove test configuration * CORDA-1343 fix tests * CORDA-1343 address core review comments * CORDA-1343 some documentation and adding createWithSsl method for a haAddressPool * CORDA-1343 clean up the CordaRPCClient interface * CORDA-1343 add internal shell test * CORDA-1343 address code review comments * CORDA-1343 split the internalShell user from the System Rpc user * CORDA-1343 fix test * CORDA-1343 Add warning when certificateChainCheckPolicies is being configured * CORDA-1343 Address code review changes * CORDA-1343 fix merge * CORDA-1343 added test, docs, clarify comments * CORDA-1343 clean up docs * CORDA-1343 fix api * CORDA-1343 fix merge * CORDA-1343 fix merge * CORDA-1343 fix merge * CORDA-1343 fix merge
This commit is contained in:
@ -2504,6 +2504,26 @@ public final class net.corda.core.identity.PartyAndCertificate extends java.lang
|
|||||||
@CordaSerializable
|
@CordaSerializable
|
||||||
public interface net.corda.core.messaging.AllPossibleRecipients extends net.corda.core.messaging.MessageRecipients
|
public interface net.corda.core.messaging.AllPossibleRecipients extends net.corda.core.messaging.MessageRecipients
|
||||||
##
|
##
|
||||||
|
public final class net.corda.core.messaging.ClientRpcSslOptions extends java.lang.Object
|
||||||
|
public <init>(java.nio.file.Path, String, String)
|
||||||
|
@NotNull
|
||||||
|
public final java.nio.file.Path component1()
|
||||||
|
@NotNull
|
||||||
|
public final String component2()
|
||||||
|
@NotNull
|
||||||
|
public final String component3()
|
||||||
|
@NotNull
|
||||||
|
public final net.corda.core.messaging.ClientRpcSslOptions copy(java.nio.file.Path, String, String)
|
||||||
|
public boolean equals(Object)
|
||||||
|
@NotNull
|
||||||
|
public final String getTrustStorePassword()
|
||||||
|
@NotNull
|
||||||
|
public final java.nio.file.Path getTrustStorePath()
|
||||||
|
@NotNull
|
||||||
|
public final String getTrustStoreProvider()
|
||||||
|
public int hashCode()
|
||||||
|
public String toString()
|
||||||
|
##
|
||||||
@DoNotImplement
|
@DoNotImplement
|
||||||
public interface net.corda.core.messaging.CordaRPCOps extends net.corda.core.messaging.RPCOps
|
public interface net.corda.core.messaging.CordaRPCOps extends net.corda.core.messaging.RPCOps
|
||||||
public abstract void acceptNewNetworkParameters(net.corda.core.crypto.SecureHash)
|
public abstract void acceptNewNetworkParameters(net.corda.core.crypto.SecureHash)
|
||||||
@ -6494,6 +6514,8 @@ public final class net.corda.testing.node.User extends java.lang.Object
|
|||||||
public String toString()
|
public String toString()
|
||||||
##
|
##
|
||||||
public final class net.corda.client.rpc.CordaRPCClient extends java.lang.Object
|
public final class net.corda.client.rpc.CordaRPCClient extends java.lang.Object
|
||||||
|
public <init>(java.util.List<net.corda.core.utilities.NetworkHostAndPort>)
|
||||||
|
public <init>(java.util.List<net.corda.core.utilities.NetworkHostAndPort>, net.corda.client.rpc.CordaRPCClientConfiguration)
|
||||||
public <init>(net.corda.core.utilities.NetworkHostAndPort)
|
public <init>(net.corda.core.utilities.NetworkHostAndPort)
|
||||||
public <init>(net.corda.core.utilities.NetworkHostAndPort, net.corda.client.rpc.CordaRPCClientConfiguration)
|
public <init>(net.corda.core.utilities.NetworkHostAndPort, net.corda.client.rpc.CordaRPCClientConfiguration)
|
||||||
@NotNull
|
@NotNull
|
||||||
@ -6504,6 +6526,10 @@ public final class net.corda.client.rpc.CordaRPCClient extends java.lang.Object
|
|||||||
public static final net.corda.client.rpc.CordaRPCClient$Companion Companion
|
public static final net.corda.client.rpc.CordaRPCClient$Companion Companion
|
||||||
##
|
##
|
||||||
public static final class net.corda.client.rpc.CordaRPCClient$Companion extends java.lang.Object
|
public static final class net.corda.client.rpc.CordaRPCClient$Companion extends java.lang.Object
|
||||||
|
@NotNull
|
||||||
|
public final net.corda.client.rpc.CordaRPCClient createWithSsl(java.util.List<net.corda.core.utilities.NetworkHostAndPort>, net.corda.core.messaging.ClientRpcSslOptions, net.corda.client.rpc.CordaRPCClientConfiguration)
|
||||||
|
@NotNull
|
||||||
|
public final net.corda.client.rpc.CordaRPCClient createWithSsl(net.corda.core.utilities.NetworkHostAndPort, net.corda.core.messaging.ClientRpcSslOptions, net.corda.client.rpc.CordaRPCClientConfiguration)
|
||||||
##
|
##
|
||||||
public interface net.corda.client.rpc.CordaRPCClientConfiguration
|
public interface net.corda.client.rpc.CordaRPCClientConfiguration
|
||||||
public abstract int getCacheConcurrencyLevel()
|
public abstract int getCacheConcurrencyLevel()
|
||||||
|
@ -48,7 +48,7 @@ EOF
|
|||||||
#an internal class
|
#an internal class
|
||||||
#TODO: check that only classes in a whitelist are part of the API rather than look for specific invalid cases going forward
|
#TODO: check that only classes in a whitelist are part of the API rather than look for specific invalid cases going forward
|
||||||
newInternalExposures=$(echo "$userDiffContents" | grep "^+" | grep "\.internal\." )
|
newInternalExposures=$(echo "$userDiffContents" | grep "^+" | grep "\.internal\." )
|
||||||
newNodeExposures=$(echo "$userDiffContents" | grep "^+" | grep "net.corda.node" )
|
newNodeExposures=$(echo "$userDiffContents" | grep "^+" | grep "net\.corda\.node\.")
|
||||||
|
|
||||||
internalCount=`grep -v "^$" <<EOF | wc -l
|
internalCount=`grep -v "^$" <<EOF | wc -l
|
||||||
$newInternalExposures
|
$newInternalExposures
|
||||||
|
@ -8,8 +8,8 @@ import net.corda.core.context.Trace
|
|||||||
import net.corda.core.messaging.CordaRPCOps
|
import net.corda.core.messaging.CordaRPCOps
|
||||||
import net.corda.core.serialization.internal.effectiveSerializationEnv
|
import net.corda.core.serialization.internal.effectiveSerializationEnv
|
||||||
import net.corda.core.utilities.NetworkHostAndPort
|
import net.corda.core.utilities.NetworkHostAndPort
|
||||||
import net.corda.nodeapi.ArtemisTcpTransport.Companion.tcpTransport
|
import net.corda.nodeapi.ArtemisTcpTransport.Companion.rpcConnectorTcpTransport
|
||||||
import net.corda.nodeapi.ConnectionDirection
|
import net.corda.core.messaging.ClientRpcSslOptions
|
||||||
import net.corda.nodeapi.internal.config.SSLConfiguration
|
import net.corda.nodeapi.internal.config.SSLConfiguration
|
||||||
import net.corda.serialization.internal.AMQP_RPC_CLIENT_CONTEXT
|
import net.corda.serialization.internal.AMQP_RPC_CLIENT_CONTEXT
|
||||||
import java.time.Duration
|
import java.time.Duration
|
||||||
@ -98,7 +98,7 @@ interface CordaRPCClientConfiguration {
|
|||||||
*
|
*
|
||||||
* @param hostAndPort The network address to connect to.
|
* @param hostAndPort The network address to connect to.
|
||||||
* @param configuration An optional configuration used to tweak client behaviour.
|
* @param configuration An optional configuration used to tweak client behaviour.
|
||||||
* @param sslConfiguration An optional [SSLConfiguration] used to enable secure communication with the server.
|
* @param sslConfiguration An optional [ClientRpcSslOptions] used to enable secure communication with the server.
|
||||||
* @param haAddressPool A list of [NetworkHostAndPort] representing the addresses of servers in HA mode.
|
* @param haAddressPool A list of [NetworkHostAndPort] representing the addresses of servers in HA mode.
|
||||||
* The client will attempt to connect to a live server by trying each address in the list. If the servers are not in
|
* The client will attempt to connect to a live server by trying each address in the list. If the servers are not in
|
||||||
* HA mode, the client will round-robin from the beginning of the list and try all servers.
|
* HA mode, the client will round-robin from the beginning of the list and try all servers.
|
||||||
@ -106,9 +106,11 @@ interface CordaRPCClientConfiguration {
|
|||||||
class CordaRPCClient private constructor(
|
class CordaRPCClient private constructor(
|
||||||
private val hostAndPort: NetworkHostAndPort,
|
private val hostAndPort: NetworkHostAndPort,
|
||||||
private val configuration: CordaRPCClientConfiguration = CordaRPCClientConfiguration.default(),
|
private val configuration: CordaRPCClientConfiguration = CordaRPCClientConfiguration.default(),
|
||||||
private val sslConfiguration: SSLConfiguration? = null,
|
private val sslConfiguration: ClientRpcSslOptions? = null,
|
||||||
|
private val nodeSslConfiguration: SSLConfiguration? = null,
|
||||||
private val classLoader: ClassLoader? = null,
|
private val classLoader: ClassLoader? = null,
|
||||||
private val haAddressPool: List<NetworkHostAndPort> = emptyList()
|
private val haAddressPool: List<NetworkHostAndPort> = emptyList(),
|
||||||
|
private val internalConnection: Boolean = false
|
||||||
) {
|
) {
|
||||||
@JvmOverloads
|
@JvmOverloads
|
||||||
constructor(hostAndPort: NetworkHostAndPort,
|
constructor(hostAndPort: NetworkHostAndPort,
|
||||||
@ -122,24 +124,41 @@ class CordaRPCClient private constructor(
|
|||||||
* @param configuration An optional configuration used to tweak client behaviour.
|
* @param configuration An optional configuration used to tweak client behaviour.
|
||||||
*/
|
*/
|
||||||
@JvmOverloads
|
@JvmOverloads
|
||||||
constructor(haAddressPool: List<NetworkHostAndPort>, configuration: CordaRPCClientConfiguration = CordaRPCClientConfiguration.default()) : this(haAddressPool.first(), configuration, null, null, haAddressPool)
|
constructor(haAddressPool: List<NetworkHostAndPort>, configuration: CordaRPCClientConfiguration = CordaRPCClientConfiguration.default()) : this(haAddressPool.first(), configuration, null, null, null, haAddressPool)
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
internal fun createWithSsl(
|
fun createWithSsl(
|
||||||
hostAndPort: NetworkHostAndPort,
|
hostAndPort: NetworkHostAndPort,
|
||||||
configuration: CordaRPCClientConfiguration = CordaRPCClientConfiguration.default(),
|
sslConfiguration: ClientRpcSslOptions,
|
||||||
sslConfiguration: SSLConfiguration? = null
|
configuration: CordaRPCClientConfiguration = CordaRPCClientConfiguration.default()
|
||||||
): CordaRPCClient {
|
): CordaRPCClient {
|
||||||
return CordaRPCClient(hostAndPort, configuration, sslConfiguration)
|
return CordaRPCClient(hostAndPort, configuration, sslConfiguration)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun createWithSsl(
|
||||||
|
haAddressPool: List<NetworkHostAndPort>,
|
||||||
|
sslConfiguration: ClientRpcSslOptions,
|
||||||
|
configuration: CordaRPCClientConfiguration = CordaRPCClientConfiguration.default()
|
||||||
|
): CordaRPCClient {
|
||||||
|
return CordaRPCClient(haAddressPool.first(), configuration, sslConfiguration, haAddressPool = haAddressPool)
|
||||||
|
}
|
||||||
|
|
||||||
internal fun createWithSslAndClassLoader(
|
internal fun createWithSslAndClassLoader(
|
||||||
hostAndPort: NetworkHostAndPort,
|
hostAndPort: NetworkHostAndPort,
|
||||||
configuration: CordaRPCClientConfiguration = CordaRPCClientConfiguration.default(),
|
configuration: CordaRPCClientConfiguration = CordaRPCClientConfiguration.default(),
|
||||||
sslConfiguration: SSLConfiguration? = null,
|
sslConfiguration: ClientRpcSslOptions? = null,
|
||||||
classLoader: ClassLoader? = null
|
classLoader: ClassLoader? = null
|
||||||
): CordaRPCClient {
|
): CordaRPCClient {
|
||||||
return CordaRPCClient(hostAndPort, configuration, sslConfiguration, classLoader)
|
return CordaRPCClient(hostAndPort, configuration, sslConfiguration, null, classLoader)
|
||||||
|
}
|
||||||
|
|
||||||
|
internal fun createWithInternalSslAndClassLoader(
|
||||||
|
hostAndPort: NetworkHostAndPort,
|
||||||
|
configuration: CordaRPCClientConfiguration = CordaRPCClientConfiguration.default(),
|
||||||
|
sslConfiguration: SSLConfiguration?,
|
||||||
|
classLoader: ClassLoader? = null
|
||||||
|
): CordaRPCClient {
|
||||||
|
return CordaRPCClient(hostAndPort, configuration, null, sslConfiguration, classLoader, internalConnection = true)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -156,18 +175,23 @@ class CordaRPCClient private constructor(
|
|||||||
}
|
}
|
||||||
|
|
||||||
private fun getRpcClient(): RPCClient<CordaRPCOps> {
|
private fun getRpcClient(): RPCClient<CordaRPCOps> {
|
||||||
return if (haAddressPool.isEmpty()) {
|
return when {
|
||||||
RPCClient(
|
// Node->RPC broker, mutually authenticated SSL. This is used when connecting the integrated shell
|
||||||
tcpTransport(ConnectionDirection.Outbound(), hostAndPort, config = sslConfiguration),
|
internalConnection == true -> RPCClient(hostAndPort, nodeSslConfiguration!!)
|
||||||
|
|
||||||
|
// Client->RPC broker
|
||||||
|
haAddressPool.isEmpty() -> RPCClient(
|
||||||
|
rpcConnectorTcpTransport(hostAndPort, config = sslConfiguration),
|
||||||
configuration,
|
configuration,
|
||||||
if (classLoader != null) AMQP_RPC_CLIENT_CONTEXT.withClassLoader(classLoader) else AMQP_RPC_CLIENT_CONTEXT)
|
if (classLoader != null) AMQP_RPC_CLIENT_CONTEXT.withClassLoader(classLoader) else AMQP_RPC_CLIENT_CONTEXT)
|
||||||
} else {
|
else -> {
|
||||||
RPCClient(haAddressPool,
|
RPCClient(haAddressPool,
|
||||||
sslConfiguration,
|
sslConfiguration,
|
||||||
configuration,
|
configuration,
|
||||||
if (classLoader != null) AMQP_RPC_CLIENT_CONTEXT.withClassLoader(classLoader) else AMQP_RPC_CLIENT_CONTEXT)
|
if (classLoader != null) AMQP_RPC_CLIENT_CONTEXT.withClassLoader(classLoader) else AMQP_RPC_CLIENT_CONTEXT)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Logs in to the target server and returns an active connection. The returned connection is a [java.io.Closeable]
|
* Logs in to the target server and returns an active connection. The returned connection is a [java.io.Closeable]
|
||||||
|
@ -5,22 +5,24 @@ import net.corda.client.rpc.CordaRPCClientConfiguration
|
|||||||
import net.corda.core.messaging.CordaRPCOps
|
import net.corda.core.messaging.CordaRPCOps
|
||||||
import net.corda.core.messaging.pendingFlowsCount
|
import net.corda.core.messaging.pendingFlowsCount
|
||||||
import net.corda.core.utilities.NetworkHostAndPort
|
import net.corda.core.utilities.NetworkHostAndPort
|
||||||
|
import net.corda.core.messaging.ClientRpcSslOptions
|
||||||
import net.corda.nodeapi.internal.config.SSLConfiguration
|
import net.corda.nodeapi.internal.config.SSLConfiguration
|
||||||
import rx.Observable
|
import rx.Observable
|
||||||
|
|
||||||
/** Utility which exposes the internal Corda RPC constructor to other internal Corda components */
|
/** Utility which exposes the internal Corda RPC constructor to other internal Corda components */
|
||||||
fun createCordaRPCClientWithSsl(
|
fun createCordaRPCClientWithSslAndClassLoader(
|
||||||
hostAndPort: NetworkHostAndPort,
|
hostAndPort: NetworkHostAndPort,
|
||||||
configuration: CordaRPCClientConfiguration = CordaRPCClientConfiguration.default(),
|
configuration: CordaRPCClientConfiguration = CordaRPCClientConfiguration.default(),
|
||||||
sslConfiguration: SSLConfiguration? = null
|
sslConfiguration: ClientRpcSslOptions? = null,
|
||||||
) = CordaRPCClient.createWithSsl(hostAndPort, configuration, sslConfiguration)
|
classLoader: ClassLoader? = null
|
||||||
|
) = CordaRPCClient.createWithSslAndClassLoader(hostAndPort, configuration, sslConfiguration, classLoader)
|
||||||
|
|
||||||
fun createCordaRPCClientWithSslAndClassLoader(
|
fun createCordaRPCClientWithInternalSslAndClassLoader(
|
||||||
hostAndPort: NetworkHostAndPort,
|
hostAndPort: NetworkHostAndPort,
|
||||||
configuration: CordaRPCClientConfiguration = CordaRPCClientConfiguration.default(),
|
configuration: CordaRPCClientConfiguration = CordaRPCClientConfiguration.default(),
|
||||||
sslConfiguration: SSLConfiguration? = null,
|
sslConfiguration: SSLConfiguration? = null,
|
||||||
classLoader: ClassLoader? = null
|
classLoader: ClassLoader? = null
|
||||||
) = CordaRPCClient.createWithSslAndClassLoader(hostAndPort, configuration, sslConfiguration, classLoader)
|
) = CordaRPCClient.createWithInternalSslAndClassLoader(hostAndPort, configuration, sslConfiguration, classLoader)
|
||||||
|
|
||||||
fun CordaRPCOps.drainAndShutdown(): Observable<Unit> {
|
fun CordaRPCOps.drainAndShutdown(): Observable<Unit> {
|
||||||
|
|
||||||
|
@ -13,10 +13,11 @@ import net.corda.core.serialization.SerializationContext
|
|||||||
import net.corda.core.serialization.SerializationDefaults
|
import net.corda.core.serialization.SerializationDefaults
|
||||||
import net.corda.core.serialization.internal.nodeSerializationEnv
|
import net.corda.core.serialization.internal.nodeSerializationEnv
|
||||||
import net.corda.core.utilities.*
|
import net.corda.core.utilities.*
|
||||||
import net.corda.nodeapi.ArtemisTcpTransport.Companion.tcpTransport
|
import net.corda.nodeapi.ArtemisTcpTransport.Companion.rpcConnectorTcpTransport
|
||||||
import net.corda.nodeapi.ArtemisTcpTransport.Companion.tcpTransportsFromList
|
import net.corda.nodeapi.ArtemisTcpTransport.Companion.rpcConnectorTcpTransportsFromList
|
||||||
import net.corda.nodeapi.ConnectionDirection
|
import net.corda.nodeapi.ArtemisTcpTransport.Companion.rpcInternalClientTcpTransport
|
||||||
import net.corda.nodeapi.RPCApi
|
import net.corda.nodeapi.RPCApi
|
||||||
|
import net.corda.core.messaging.ClientRpcSslOptions
|
||||||
import net.corda.nodeapi.internal.config.SSLConfiguration
|
import net.corda.nodeapi.internal.config.SSLConfiguration
|
||||||
import org.apache.activemq.artemis.api.core.SimpleString
|
import org.apache.activemq.artemis.api.core.SimpleString
|
||||||
import org.apache.activemq.artemis.api.core.TransportConfiguration
|
import org.apache.activemq.artemis.api.core.TransportConfiguration
|
||||||
@ -58,6 +59,9 @@ data class CordaRPCClientConfigurationImpl(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This runs on the client JVM
|
||||||
|
*/
|
||||||
class RPCClient<I : RPCOps>(
|
class RPCClient<I : RPCOps>(
|
||||||
val transport: TransportConfiguration,
|
val transport: TransportConfiguration,
|
||||||
val rpcConfiguration: CordaRPCClientConfiguration = CordaRPCClientConfigurationImpl.default,
|
val rpcConfiguration: CordaRPCClientConfiguration = CordaRPCClientConfigurationImpl.default,
|
||||||
@ -66,18 +70,25 @@ class RPCClient<I : RPCOps>(
|
|||||||
) {
|
) {
|
||||||
constructor(
|
constructor(
|
||||||
hostAndPort: NetworkHostAndPort,
|
hostAndPort: NetworkHostAndPort,
|
||||||
sslConfiguration: SSLConfiguration? = null,
|
sslConfiguration: ClientRpcSslOptions? = null,
|
||||||
configuration: CordaRPCClientConfiguration = CordaRPCClientConfigurationImpl.default,
|
configuration: CordaRPCClientConfiguration = CordaRPCClientConfigurationImpl.default,
|
||||||
serializationContext: SerializationContext = SerializationDefaults.RPC_CLIENT_CONTEXT
|
serializationContext: SerializationContext = SerializationDefaults.RPC_CLIENT_CONTEXT
|
||||||
) : this(tcpTransport(ConnectionDirection.Outbound(), hostAndPort, sslConfiguration), configuration, serializationContext)
|
) : this(rpcConnectorTcpTransport(hostAndPort, sslConfiguration), configuration, serializationContext)
|
||||||
|
|
||||||
|
constructor(
|
||||||
|
hostAndPort: NetworkHostAndPort,
|
||||||
|
sslConfiguration: SSLConfiguration,
|
||||||
|
configuration: CordaRPCClientConfiguration = CordaRPCClientConfigurationImpl.default,
|
||||||
|
serializationContext: SerializationContext = SerializationDefaults.RPC_CLIENT_CONTEXT
|
||||||
|
) : this(rpcInternalClientTcpTransport(hostAndPort, sslConfiguration), configuration, serializationContext)
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
haAddressPool: List<NetworkHostAndPort>,
|
haAddressPool: List<NetworkHostAndPort>,
|
||||||
sslConfiguration: SSLConfiguration? = null,
|
sslConfiguration: ClientRpcSslOptions? = null,
|
||||||
configuration: CordaRPCClientConfiguration = CordaRPCClientConfigurationImpl.default,
|
configuration: CordaRPCClientConfiguration = CordaRPCClientConfigurationImpl.default,
|
||||||
serializationContext: SerializationContext = SerializationDefaults.RPC_CLIENT_CONTEXT
|
serializationContext: SerializationContext = SerializationDefaults.RPC_CLIENT_CONTEXT
|
||||||
) : this(tcpTransport(ConnectionDirection.Outbound(), haAddressPool.first(), sslConfiguration),
|
) : this(rpcConnectorTcpTransport(haAddressPool.first(), sslConfiguration),
|
||||||
configuration, serializationContext, tcpTransportsFromList(ConnectionDirection.Outbound(), haAddressPool, sslConfiguration))
|
configuration, serializationContext, rpcConnectorTcpTransportsFromList(haAddressPool, sslConfiguration))
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
private val log = contextLogger()
|
private val log = contextLogger()
|
||||||
|
@ -0,0 +1,6 @@
|
|||||||
|
package net.corda.core.messaging
|
||||||
|
|
||||||
|
import java.nio.file.Path
|
||||||
|
|
||||||
|
/** As an RPC Client, use this class to point to the truststore that contains the RPC SSL certificate provided by the node admin */
|
||||||
|
data class ClientRpcSslOptions(val trustStorePath: Path, val trustStorePassword: String, val trustStoreProvider: String = "JKS")
|
@ -49,7 +49,11 @@ Unreleased
|
|||||||
* The deprecated web server now has its own ``web-server.conf`` file, separate from ``node.conf``.
|
* The deprecated web server now has its own ``web-server.conf`` file, separate from ``node.conf``.
|
||||||
* Property keys with double quotes (e.g. `"key"`) in ``node.conf`` are no longer allowed, for rationale refer to :doc:`corda-configuration-file`.
|
* Property keys with double quotes (e.g. `"key"`) in ``node.conf`` are no longer allowed, for rationale refer to :doc:`corda-configuration-file`.
|
||||||
|
|
||||||
* More types can be serialized now: java.security.cert.CRLReason, java.security.cert.X509CRL, java.math.BigInteger
|
* Added public support for creating ``CordaRPCClient`` using SSL. For this to work the node needs to provide client applications
|
||||||
|
a certificate to be added to a truststore. See :doc:`tutorial-clientrpc-api`
|
||||||
|
|
||||||
|
* The node RPC broker opens 2 endpoints that are configured with ``address`` and ``adminAddress``. RPC Clients would connect to the address, while the node will connect
|
||||||
|
to the adminAddress. Previously if ssl was enabled for RPC the ``adminAddress`` was equal to ``address``.
|
||||||
|
|
||||||
* Upgraded H2 to v1.4.197
|
* Upgraded H2 to v1.4.197
|
||||||
|
|
||||||
|
@ -307,9 +307,17 @@ The client RPC wire protocol is defined and documented in ``net/corda/client/rpc
|
|||||||
|
|
||||||
Wire security
|
Wire security
|
||||||
-------------
|
-------------
|
||||||
``CordaRPCClient`` has an optional constructor parameter of type ``SSLConfiguration``, defaulted to ``null``, which allows
|
``CordaRPCClient`` has an optional constructor parameter of type ``ClientRpcSslOptions``, defaulted to ``null``, which allows
|
||||||
communication with the node using SSL. Default ``null`` value means no SSL used in the context of RPC.
|
communication with the node using SSL. Default ``null`` value means no SSL used in the context of RPC.
|
||||||
|
|
||||||
|
To use this feature, the ``CordaRPCClient`` object provides a static factory method ``createWithSsl``.
|
||||||
|
|
||||||
|
In order for this to work, the client needs to provide a truststore containing a certificate received from the node admin.
|
||||||
|
(The Node does not expect the RPC client to present a certificate, as the client already authenticates using the mechanism described above.)
|
||||||
|
|
||||||
|
For the communication to be secure, we recommend using the standard SSL best practices for key management.
|
||||||
|
|
||||||
|
|
||||||
Whitelisting classes with the Corda node
|
Whitelisting classes with the Corda node
|
||||||
----------------------------------------
|
----------------------------------------
|
||||||
CorDapps must whitelist any classes used over RPC with Corda's serialization framework, unless they are whitelisted by
|
CorDapps must whitelist any classes used over RPC with Corda's serialization framework, unless they are whitelisted by
|
||||||
|
@ -1,3 +0,0 @@
|
|||||||
myLegalName : "O=Bank A,L=London,C=GB"
|
|
||||||
p2pAddress : "my-corda-node:10002"
|
|
||||||
verifierType: "OutOfProcess"
|
|
@ -28,8 +28,7 @@ class ExampleConfigTest {
|
|||||||
@Test
|
@Test
|
||||||
fun `example node_confs parses fine`() {
|
fun `example node_confs parses fine`() {
|
||||||
readAndCheckConfigurations(
|
readAndCheckConfigurations(
|
||||||
"example-node.conf",
|
"example-node.conf"
|
||||||
"example-out-of-process-verifier-node.conf"
|
|
||||||
) {
|
) {
|
||||||
val baseDirectory = Paths.get("some-example-base-dir")
|
val baseDirectory = Paths.get("some-example-base-dir")
|
||||||
ConfigHelper.loadConfig(
|
ConfigHelper.loadConfig(
|
||||||
|
@ -79,7 +79,7 @@ Security
|
|||||||
|
|
||||||
Clients attempting to connect to the node's broker fall in one of four groups:
|
Clients attempting to connect to the node's broker fall in one of four groups:
|
||||||
|
|
||||||
#. Anyone connecting with the username ``SystemUsers/Node`` is treated as the node hosting the broker, or a logical
|
#. Anyone connecting with the username ``SystemUsers/Node`` or ``SystemUsers/NodeRPC`` is treated as the node hosting the brokers, or a logical
|
||||||
component of the node. The TLS certificate they provide must match the one broker has for the node. If that's the case
|
component of the node. The TLS certificate they provide must match the one broker has for the node. If that's the case
|
||||||
they are given full access to all valid queues, otherwise they are rejected.
|
they are given full access to all valid queues, otherwise they are rejected.
|
||||||
|
|
||||||
@ -101,3 +101,21 @@ this to determine what permissions the user has.
|
|||||||
|
|
||||||
The broker also does host verification when connecting to another peer. It checks that the TLS certificate subject matches
|
The broker also does host verification when connecting to another peer. It checks that the TLS certificate subject matches
|
||||||
with the advertised X.500 legal name from the network map service.
|
with the advertised X.500 legal name from the network map service.
|
||||||
|
|
||||||
|
|
||||||
|
Implementation details
|
||||||
|
----------------------
|
||||||
|
|
||||||
|
The components of the system that need to communicate and authenticate each other are:
|
||||||
|
- The Artemis P2P broker (Currently runs inside the Nodes JVM process, but in the future it will be able to run as a separate server)
|
||||||
|
* opens Acceptor configured with the doorman's certificate in the truststore and the node's ssl certificate in the keystore
|
||||||
|
- The Artemis RPC broker (Currently runs inside the Nodes JVM process, but in the future it will be able to run as a separate server)
|
||||||
|
* opens "Admin" Acceptor configured with the doorman's certificate in the truststore and the node's ssl certificate in the keystore
|
||||||
|
* opens "Client" Acceptor with the ssl settings configurable. This acceptor does not require ssl client-auth.
|
||||||
|
- The current node hosting the brokers
|
||||||
|
* connects to the P2P broker using the ``SystemUsers/Node`` user and the node's keystore and trustore
|
||||||
|
* connects to the "Admin" Acceptor of the RPC broker using the ``SystemUsers/NodeRPC`` user and the node's keystore and trustore
|
||||||
|
- RPC clients ( Third party applications that need to communicate with the Node. )
|
||||||
|
* connect to the "Client" Acceptor of the RPC broker using the username/password provided by the node's admin. The client verifies the node's certificate using a truststore provided by the node's admin.
|
||||||
|
- Peer nodes (Other nodes on the network)
|
||||||
|
* connect to the P2P broker using the ``SystemUsers/Peer`` user and a doorman signed certificate. The authentication is performed based on the root CA.
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
package net.corda.nodeapi
|
package net.corda.nodeapi
|
||||||
|
|
||||||
import net.corda.core.identity.CordaX500Name
|
import net.corda.core.messaging.ClientRpcSslOptions
|
||||||
import net.corda.core.serialization.internal.nodeSerializationEnv
|
import net.corda.core.serialization.internal.nodeSerializationEnv
|
||||||
import net.corda.core.utilities.NetworkHostAndPort
|
import net.corda.core.utilities.NetworkHostAndPort
|
||||||
import net.corda.nodeapi.internal.config.SSLConfiguration
|
import net.corda.nodeapi.internal.config.SSLConfiguration
|
||||||
@ -8,20 +8,11 @@ import net.corda.nodeapi.internal.requireOnDefaultFileSystem
|
|||||||
import org.apache.activemq.artemis.api.core.TransportConfiguration
|
import org.apache.activemq.artemis.api.core.TransportConfiguration
|
||||||
import org.apache.activemq.artemis.core.remoting.impl.netty.NettyConnectorFactory
|
import org.apache.activemq.artemis.core.remoting.impl.netty.NettyConnectorFactory
|
||||||
import org.apache.activemq.artemis.core.remoting.impl.netty.TransportConstants
|
import org.apache.activemq.artemis.core.remoting.impl.netty.TransportConstants
|
||||||
|
import java.nio.file.Path
|
||||||
sealed class ConnectionDirection {
|
|
||||||
data class Inbound(val acceptorFactoryClassName: String) : ConnectionDirection()
|
|
||||||
data class Outbound(
|
|
||||||
val expectedCommonNames: Set<CordaX500Name> = emptySet(), // TODO SNI? Or we need a notion of node's network identity?
|
|
||||||
val connectorFactoryClassName: String = NettyConnectorFactory::class.java.name
|
|
||||||
) : ConnectionDirection()
|
|
||||||
}
|
|
||||||
|
|
||||||
/** Class to set Artemis TCP configuration options. */
|
/** Class to set Artemis TCP configuration options. */
|
||||||
class ArtemisTcpTransport {
|
class ArtemisTcpTransport {
|
||||||
companion object {
|
companion object {
|
||||||
const val VERIFY_PEER_LEGAL_NAME = "corda.verifyPeerCommonName"
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Corda supported TLS schemes.
|
* Corda supported TLS schemes.
|
||||||
* <p><ul>
|
* <p><ul>
|
||||||
@ -47,14 +38,7 @@ class ArtemisTcpTransport {
|
|||||||
/** Supported TLS versions, currently TLSv1.2 only. */
|
/** Supported TLS versions, currently TLSv1.2 only. */
|
||||||
val TLS_VERSIONS = listOf("TLSv1.2")
|
val TLS_VERSIONS = listOf("TLSv1.2")
|
||||||
|
|
||||||
/** Specify [TransportConfiguration] for TCP communication. */
|
private fun defaultArtemisOptions(hostAndPort: NetworkHostAndPort) = mapOf(
|
||||||
fun tcpTransport(
|
|
||||||
direction: ConnectionDirection,
|
|
||||||
hostAndPort: NetworkHostAndPort,
|
|
||||||
config: SSLConfiguration?,
|
|
||||||
enableSSL: Boolean = true
|
|
||||||
): TransportConfiguration {
|
|
||||||
val options = mutableMapOf<String, Any?>(
|
|
||||||
// Basic TCP target details.
|
// Basic TCP target details.
|
||||||
TransportConstants.HOST_PROP_NAME to hostAndPort.host,
|
TransportConstants.HOST_PROP_NAME to hostAndPort.host,
|
||||||
TransportConstants.PORT_PROP_NAME to hostAndPort.port,
|
TransportConstants.PORT_PROP_NAME to hostAndPort.port,
|
||||||
@ -68,48 +52,100 @@ class ArtemisTcpTransport {
|
|||||||
TransportConstants.REMOTING_THREADS_PROPNAME to (if (nodeSerializationEnv != null) -1 else 1),
|
TransportConstants.REMOTING_THREADS_PROPNAME to (if (nodeSerializationEnv != null) -1 else 1),
|
||||||
// turn off direct delivery in Artemis - this is latency optimisation that can lead to
|
// turn off direct delivery in Artemis - this is latency optimisation that can lead to
|
||||||
//hick-ups under high load (CORDA-1336)
|
//hick-ups under high load (CORDA-1336)
|
||||||
TransportConstants.DIRECT_DELIVER to false
|
TransportConstants.DIRECT_DELIVER to false)
|
||||||
)
|
|
||||||
|
private val defaultSSLOptions = mapOf(
|
||||||
|
TransportConstants.ENABLED_CIPHER_SUITES_PROP_NAME to CIPHER_SUITES.joinToString(","),
|
||||||
|
TransportConstants.ENABLED_PROTOCOLS_PROP_NAME to TLS_VERSIONS.joinToString(","))
|
||||||
|
|
||||||
|
private fun SSLConfiguration.toTransportOptions() = mapOf(
|
||||||
|
TransportConstants.SSL_ENABLED_PROP_NAME to true,
|
||||||
|
TransportConstants.KEYSTORE_PROVIDER_PROP_NAME to "JKS",
|
||||||
|
TransportConstants.KEYSTORE_PATH_PROP_NAME to sslKeystore,
|
||||||
|
TransportConstants.KEYSTORE_PASSWORD_PROP_NAME to keyStorePassword,
|
||||||
|
TransportConstants.TRUSTSTORE_PROVIDER_PROP_NAME to "JKS",
|
||||||
|
TransportConstants.TRUSTSTORE_PATH_PROP_NAME to trustStoreFile,
|
||||||
|
TransportConstants.TRUSTSTORE_PASSWORD_PROP_NAME to trustStorePassword,
|
||||||
|
TransportConstants.NEED_CLIENT_AUTH_PROP_NAME to true)
|
||||||
|
|
||||||
|
private fun ClientRpcSslOptions.toTransportOptions() = mapOf(
|
||||||
|
TransportConstants.SSL_ENABLED_PROP_NAME to true,
|
||||||
|
TransportConstants.TRUSTSTORE_PROVIDER_PROP_NAME to trustStoreProvider,
|
||||||
|
TransportConstants.TRUSTSTORE_PATH_PROP_NAME to trustStorePath,
|
||||||
|
TransportConstants.TRUSTSTORE_PASSWORD_PROP_NAME to trustStorePassword)
|
||||||
|
|
||||||
|
private fun BrokerRpcSslOptions.toTransportOptions() = mapOf(
|
||||||
|
TransportConstants.SSL_ENABLED_PROP_NAME to true,
|
||||||
|
TransportConstants.KEYSTORE_PROVIDER_PROP_NAME to "JKS",
|
||||||
|
TransportConstants.KEYSTORE_PATH_PROP_NAME to keyStorePath,
|
||||||
|
TransportConstants.KEYSTORE_PASSWORD_PROP_NAME to keyStorePassword,
|
||||||
|
TransportConstants.NEED_CLIENT_AUTH_PROP_NAME to false)
|
||||||
|
|
||||||
|
private val acceptorFactoryClassName = "org.apache.activemq.artemis.core.remoting.impl.netty.NettyAcceptorFactory"
|
||||||
|
private val connectorFactoryClassName = NettyConnectorFactory::class.java.name
|
||||||
|
|
||||||
|
fun p2pAcceptorTcpTransport(hostAndPort: NetworkHostAndPort, config: SSLConfiguration?, enableSSL: Boolean = true): TransportConfiguration {
|
||||||
|
val options = defaultArtemisOptions(hostAndPort).toMutableMap()
|
||||||
|
|
||||||
if (config != null && enableSSL) {
|
if (config != null && enableSSL) {
|
||||||
config.sslKeystore.requireOnDefaultFileSystem()
|
config.sslKeystore.requireOnDefaultFileSystem()
|
||||||
config.trustStoreFile.requireOnDefaultFileSystem()
|
config.trustStoreFile.requireOnDefaultFileSystem()
|
||||||
val tlsOptions = mapOf(
|
options.putAll(defaultSSLOptions)
|
||||||
// Enable TLS transport layer with client certs and restrict to at least SHA256 in handshake
|
options.putAll(config.toTransportOptions())
|
||||||
// and AES encryption.
|
|
||||||
TransportConstants.SSL_ENABLED_PROP_NAME to true,
|
|
||||||
TransportConstants.KEYSTORE_PROVIDER_PROP_NAME to "JKS",
|
|
||||||
TransportConstants.KEYSTORE_PATH_PROP_NAME to config.sslKeystore,
|
|
||||||
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 TLS_VERSIONS.joinToString(","),
|
|
||||||
TransportConstants.NEED_CLIENT_AUTH_PROP_NAME to true,
|
|
||||||
VERIFY_PEER_LEGAL_NAME to (direction as? ConnectionDirection.Outbound)?.expectedCommonNames
|
|
||||||
)
|
|
||||||
options.putAll(tlsOptions)
|
|
||||||
}
|
}
|
||||||
val factoryName = when (direction) {
|
return TransportConfiguration(acceptorFactoryClassName, options)
|
||||||
is ConnectionDirection.Inbound -> direction.acceptorFactoryClassName
|
|
||||||
is ConnectionDirection.Outbound -> direction.connectorFactoryClassName
|
|
||||||
}
|
}
|
||||||
return TransportConfiguration(factoryName, options)
|
|
||||||
|
fun p2pConnectorTcpTransport(hostAndPort: NetworkHostAndPort, config: SSLConfiguration?, enableSSL: Boolean = true): TransportConfiguration {
|
||||||
|
val options = defaultArtemisOptions(hostAndPort).toMutableMap()
|
||||||
|
|
||||||
|
if (config != null && enableSSL) {
|
||||||
|
config.sslKeystore.requireOnDefaultFileSystem()
|
||||||
|
config.trustStoreFile.requireOnDefaultFileSystem()
|
||||||
|
options.putAll(defaultSSLOptions)
|
||||||
|
options.putAll(config.toTransportOptions())
|
||||||
|
}
|
||||||
|
return TransportConfiguration(connectorFactoryClassName, options)
|
||||||
|
}
|
||||||
|
|
||||||
|
/** [TransportConfiguration] for RPC TCP communication - server side. */
|
||||||
|
fun rpcAcceptorTcpTransport(hostAndPort: NetworkHostAndPort, config: BrokerRpcSslOptions?, enableSSL: Boolean = true): TransportConfiguration {
|
||||||
|
val options = defaultArtemisOptions(hostAndPort).toMutableMap()
|
||||||
|
|
||||||
|
if (config != null && enableSSL) {
|
||||||
|
config.keyStorePath.requireOnDefaultFileSystem()
|
||||||
|
options.putAll(config.toTransportOptions())
|
||||||
|
options.putAll(defaultSSLOptions)
|
||||||
|
}
|
||||||
|
return TransportConfiguration(acceptorFactoryClassName, options)
|
||||||
|
}
|
||||||
|
|
||||||
|
/** [TransportConfiguration] for RPC TCP communication
|
||||||
|
* This is the Transport that connects the client JVM to the broker. */
|
||||||
|
fun rpcConnectorTcpTransport(hostAndPort: NetworkHostAndPort, config: ClientRpcSslOptions?, enableSSL: Boolean = true): TransportConfiguration {
|
||||||
|
val options = defaultArtemisOptions(hostAndPort).toMutableMap()
|
||||||
|
|
||||||
|
if (config != null && enableSSL) {
|
||||||
|
config.trustStorePath.requireOnDefaultFileSystem()
|
||||||
|
options.putAll(config.toTransportOptions())
|
||||||
|
options.putAll(defaultSSLOptions)
|
||||||
|
}
|
||||||
|
return TransportConfiguration(connectorFactoryClassName, options)
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Create as list of [TransportConfiguration]. **/
|
/** Create as list of [TransportConfiguration]. **/
|
||||||
fun tcpTransportsFromList(
|
fun rpcConnectorTcpTransportsFromList(hostAndPortList: List<NetworkHostAndPort>, config: ClientRpcSslOptions?, enableSSL: Boolean = true): List<TransportConfiguration> = hostAndPortList.map {
|
||||||
direction: ConnectionDirection,
|
rpcConnectorTcpTransport(it, config, enableSSL)
|
||||||
hostAndPortList: List<NetworkHostAndPort>,
|
|
||||||
config: SSLConfiguration?,
|
|
||||||
enableSSL: Boolean = true): List<TransportConfiguration> {
|
|
||||||
val tcpTransports = ArrayList<TransportConfiguration>(hostAndPortList.size)
|
|
||||||
hostAndPortList.forEach {
|
|
||||||
tcpTransports.add(tcpTransport(direction, it, config, enableSSL))
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return tcpTransports
|
fun rpcInternalClientTcpTransport(hostAndPort: NetworkHostAndPort, config: SSLConfiguration): TransportConfiguration {
|
||||||
|
return TransportConfiguration(connectorFactoryClassName, defaultArtemisOptions(hostAndPort) + defaultSSLOptions + config.toTransportOptions())
|
||||||
|
}
|
||||||
|
|
||||||
|
fun rpcInternalAcceptorTcpTransport(hostAndPort: NetworkHostAndPort, config: SSLConfiguration): TransportConfiguration {
|
||||||
|
return TransportConfiguration(acceptorFactoryClassName, defaultArtemisOptions(hostAndPort) + defaultSSLOptions + config.toTransportOptions())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
data class BrokerRpcSslOptions(val keyStorePath: Path, val keyStorePassword: String)
|
||||||
|
@ -4,8 +4,7 @@ import net.corda.core.serialization.internal.nodeSerializationEnv
|
|||||||
import net.corda.core.utilities.NetworkHostAndPort
|
import net.corda.core.utilities.NetworkHostAndPort
|
||||||
import net.corda.core.utilities.loggerFor
|
import net.corda.core.utilities.loggerFor
|
||||||
import net.corda.nodeapi.ArtemisTcpTransport
|
import net.corda.nodeapi.ArtemisTcpTransport
|
||||||
import net.corda.nodeapi.ConnectionDirection
|
import net.corda.nodeapi.internal.ArtemisMessagingComponent.Companion.NODE_P2P_USER
|
||||||
import net.corda.nodeapi.internal.ArtemisMessagingComponent.Companion.NODE_USER
|
|
||||||
import net.corda.nodeapi.internal.config.SSLConfiguration
|
import net.corda.nodeapi.internal.config.SSLConfiguration
|
||||||
import org.apache.activemq.artemis.api.core.client.ActiveMQClient
|
import org.apache.activemq.artemis.api.core.client.ActiveMQClient
|
||||||
import org.apache.activemq.artemis.api.core.client.ActiveMQClient.DEFAULT_ACK_BATCH_SIZE
|
import org.apache.activemq.artemis.api.core.client.ActiveMQClient.DEFAULT_ACK_BATCH_SIZE
|
||||||
@ -35,7 +34,7 @@ class ArtemisMessagingClient(private val config: SSLConfiguration,
|
|||||||
check(started == null) { "start can't be called twice" }
|
check(started == null) { "start can't be called twice" }
|
||||||
log.info("Connecting to message broker: $serverAddress")
|
log.info("Connecting to message broker: $serverAddress")
|
||||||
// TODO Add broker CN to config for host verification in case the embedded broker isn't used
|
// TODO Add broker CN to config for host verification in case the embedded broker isn't used
|
||||||
val tcpTransport = ArtemisTcpTransport.tcpTransport(ConnectionDirection.Outbound(), serverAddress, config)
|
val tcpTransport = ArtemisTcpTransport.p2pConnectorTcpTransport(serverAddress, config)
|
||||||
val locator = ActiveMQClient.createServerLocatorWithoutHA(tcpTransport).apply {
|
val locator = ActiveMQClient.createServerLocatorWithoutHA(tcpTransport).apply {
|
||||||
// Never time out on our loopback Artemis connections. If we switch back to using the InVM transport this
|
// Never time out on our loopback Artemis connections. If we switch back to using the InVM transport this
|
||||||
// would be the default and the two lines below can be deleted.
|
// would be the default and the two lines below can be deleted.
|
||||||
@ -50,7 +49,7 @@ class ArtemisMessagingClient(private val config: SSLConfiguration,
|
|||||||
// using our TLS certificate.
|
// using our TLS certificate.
|
||||||
// Note that the acknowledgement of messages is not flushed to the Artermis journal until the default buffer
|
// Note that the acknowledgement of messages is not flushed to the Artermis journal until the default buffer
|
||||||
// size of 1MB is acknowledged.
|
// size of 1MB is acknowledged.
|
||||||
val session = sessionFactory!!.createSession(NODE_USER, NODE_USER, false, true, true, locator.isPreAcknowledge, DEFAULT_ACK_BATCH_SIZE)
|
val session = sessionFactory!!.createSession(NODE_P2P_USER, NODE_P2P_USER, false, true, true, locator.isPreAcknowledge, DEFAULT_ACK_BATCH_SIZE)
|
||||||
session.start()
|
session.start()
|
||||||
// Create a general purpose producer.
|
// Create a general purpose producer.
|
||||||
val producer = session.createProducer()
|
val producer = session.createProducer()
|
||||||
|
@ -22,8 +22,12 @@ class ArtemisMessagingComponent {
|
|||||||
|
|
||||||
// System users must contain an invalid RPC username character to prevent any chance of name clash which in this
|
// System users must contain an invalid RPC username character to prevent any chance of name clash which in this
|
||||||
// case is a forward slash
|
// case is a forward slash
|
||||||
const val NODE_USER = "SystemUsers/Node"
|
const val NODE_P2P_USER = "SystemUsers/Node"
|
||||||
|
const val NODE_RPC_USER = "SystemUsers/NodeRPC"
|
||||||
const val PEER_USER = "SystemUsers/Peer"
|
const val PEER_USER = "SystemUsers/Peer"
|
||||||
|
// User used only in devMode when nodes have a shell attached by default.
|
||||||
|
const val INTERNAL_SHELL_USER = "internalShell"
|
||||||
|
|
||||||
const val INTERNAL_PREFIX = "internal."
|
const val INTERNAL_PREFIX = "internal."
|
||||||
const val PEERS_PREFIX = "${INTERNAL_PREFIX}peers." //TODO Come up with better name for common peers/services queue
|
const val PEERS_PREFIX = "${INTERNAL_PREFIX}peers." //TODO Come up with better name for common peers/services queue
|
||||||
const val P2P_PREFIX = "p2p.inbound."
|
const val P2P_PREFIX = "p2p.inbound."
|
||||||
|
@ -9,7 +9,7 @@ import net.corda.core.utilities.NetworkHostAndPort
|
|||||||
import net.corda.core.utilities.debug
|
import net.corda.core.utilities.debug
|
||||||
import net.corda.nodeapi.internal.ArtemisMessagingClient
|
import net.corda.nodeapi.internal.ArtemisMessagingClient
|
||||||
import net.corda.nodeapi.internal.ArtemisMessagingComponent
|
import net.corda.nodeapi.internal.ArtemisMessagingComponent
|
||||||
import net.corda.nodeapi.internal.ArtemisMessagingComponent.Companion.NODE_USER
|
import net.corda.nodeapi.internal.ArtemisMessagingComponent.Companion.NODE_P2P_USER
|
||||||
import net.corda.nodeapi.internal.ArtemisMessagingComponent.Companion.P2PMessagingHeaders
|
import net.corda.nodeapi.internal.ArtemisMessagingComponent.Companion.P2PMessagingHeaders
|
||||||
import net.corda.nodeapi.internal.ArtemisMessagingComponent.Companion.PEER_USER
|
import net.corda.nodeapi.internal.ArtemisMessagingComponent.Companion.PEER_USER
|
||||||
import net.corda.nodeapi.internal.ArtemisMessagingComponent.RemoteInboxAddress.Companion.translateLocalQueueToInboxAddress
|
import net.corda.nodeapi.internal.ArtemisMessagingComponent.RemoteInboxAddress.Companion.translateLocalQueueToInboxAddress
|
||||||
@ -112,7 +112,7 @@ class AMQPBridgeManager(config: NodeSSLConfiguration, private val maxMessageSize
|
|||||||
if (connected) {
|
if (connected) {
|
||||||
log.info("Bridge Connected")
|
log.info("Bridge Connected")
|
||||||
val sessionFactory = artemis.started!!.sessionFactory
|
val sessionFactory = artemis.started!!.sessionFactory
|
||||||
val session = sessionFactory.createSession(NODE_USER, NODE_USER, false, true, true, false, DEFAULT_ACK_BATCH_SIZE)
|
val session = sessionFactory.createSession(NODE_P2P_USER, NODE_P2P_USER, false, true, true, false, DEFAULT_ACK_BATCH_SIZE)
|
||||||
this.session = session
|
this.session = session
|
||||||
val consumer = session.createConsumer(queueName)
|
val consumer = session.createConsumer(queueName)
|
||||||
this.consumer = consumer
|
this.consumer = consumer
|
||||||
|
@ -175,7 +175,6 @@ class AMQPBridgeTest {
|
|||||||
doReturn("cordacadevpass").whenever(it).keyStorePassword
|
doReturn("cordacadevpass").whenever(it).keyStorePassword
|
||||||
doReturn(artemisAddress).whenever(it).p2pAddress
|
doReturn(artemisAddress).whenever(it).p2pAddress
|
||||||
doReturn(null).whenever(it).jmxMonitoringHttpPort
|
doReturn(null).whenever(it).jmxMonitoringHttpPort
|
||||||
doReturn(emptyList<CertChainPolicyConfig>()).whenever(it).certificateChainCheckPolicies
|
|
||||||
}
|
}
|
||||||
artemisConfig.configureWithDevSSLCertificate()
|
artemisConfig.configureWithDevSSLCertificate()
|
||||||
val artemisServer = ArtemisMessagingServer(artemisConfig, NetworkHostAndPort("0.0.0.0", artemisPort), MAX_MESSAGE_SIZE)
|
val artemisServer = ArtemisMessagingServer(artemisConfig, NetworkHostAndPort("0.0.0.0", artemisPort), MAX_MESSAGE_SIZE)
|
||||||
|
@ -365,7 +365,6 @@ class ProtonWrapperTests {
|
|||||||
doReturn("cordacadevpass").whenever(it).keyStorePassword
|
doReturn("cordacadevpass").whenever(it).keyStorePassword
|
||||||
doReturn(NetworkHostAndPort("0.0.0.0", artemisPort)).whenever(it).p2pAddress
|
doReturn(NetworkHostAndPort("0.0.0.0", artemisPort)).whenever(it).p2pAddress
|
||||||
doReturn(null).whenever(it).jmxMonitoringHttpPort
|
doReturn(null).whenever(it).jmxMonitoringHttpPort
|
||||||
doReturn(emptyList<CertChainPolicyConfig>()).whenever(it).certificateChainCheckPolicies
|
|
||||||
doReturn(true).whenever(it).crlCheckSoftFail
|
doReturn(true).whenever(it).crlCheckSoftFail
|
||||||
}
|
}
|
||||||
artemisConfig.configureWithDevSSLCertificate()
|
artemisConfig.configureWithDevSSLCertificate()
|
||||||
|
@ -1,69 +1,144 @@
|
|||||||
package net.corda.node.services.rpc
|
package net.corda.node.services.rpc
|
||||||
|
|
||||||
import net.corda.client.rpc.CordaRPCClient
|
import net.corda.client.rpc.CordaRPCClient
|
||||||
import net.corda.client.rpc.internal.createCordaRPCClientWithSsl
|
import net.corda.core.internal.div
|
||||||
import net.corda.core.identity.CordaX500Name
|
|
||||||
import net.corda.core.utilities.getOrThrow
|
import net.corda.core.utilities.getOrThrow
|
||||||
import net.corda.node.services.Permissions.Companion.all
|
import net.corda.node.services.Permissions.Companion.all
|
||||||
import net.corda.testing.common.internal.withCertificates
|
import net.corda.nodeapi.BrokerRpcSslOptions
|
||||||
import net.corda.testing.common.internal.withKeyStores
|
import net.corda.core.messaging.ClientRpcSslOptions
|
||||||
|
import net.corda.nodeapi.internal.ArtemisMessagingComponent.Companion.NODE_RPC_USER
|
||||||
import net.corda.testing.driver.DriverParameters
|
import net.corda.testing.driver.DriverParameters
|
||||||
import net.corda.testing.driver.driver
|
import net.corda.testing.driver.driver
|
||||||
import net.corda.testing.driver.internal.RandomFree
|
import net.corda.testing.driver.internal.RandomFree
|
||||||
|
import net.corda.testing.internal.createKeyPairAndSelfSignedCertificate
|
||||||
|
import net.corda.testing.internal.saveToKeyStore
|
||||||
|
import net.corda.testing.internal.saveToTrustStore
|
||||||
import net.corda.testing.internal.useSslRpcOverrides
|
import net.corda.testing.internal.useSslRpcOverrides
|
||||||
import net.corda.testing.node.User
|
import net.corda.testing.node.User
|
||||||
|
import org.apache.activemq.artemis.api.core.ActiveMQException
|
||||||
|
import org.apache.activemq.artemis.api.core.ActiveMQNotConnectedException
|
||||||
|
import org.apache.activemq.artemis.api.core.ActiveMQSecurityException
|
||||||
|
import org.assertj.core.api.Assertions
|
||||||
import org.assertj.core.api.Assertions.assertThat
|
import org.assertj.core.api.Assertions.assertThat
|
||||||
|
import org.junit.Rule
|
||||||
import org.junit.Test
|
import org.junit.Test
|
||||||
|
import org.junit.rules.TemporaryFolder
|
||||||
|
|
||||||
class RpcSslTest {
|
class RpcSslTest {
|
||||||
|
|
||||||
|
@Rule
|
||||||
|
@JvmField
|
||||||
|
val tempFolder = TemporaryFolder()
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun rpc_client_using_ssl() {
|
fun `RPC client using ssl is able to run a command`() {
|
||||||
val user = User("mark", "dadada", setOf(all()))
|
val user = User("mark", "dadada", setOf(all()))
|
||||||
withCertificates { server, client, createSelfSigned, createSignedBy ->
|
var successfulLogin = false
|
||||||
val rootCertificate = createSelfSigned(CordaX500Name("SystemUsers/Node", "IT", "R3 London", "London", "London", "GB"))
|
var failedLogin = false
|
||||||
val markCertificate = createSignedBy(CordaX500Name("mark", "IT", "R3 London", "London", "London", "GB"), rootCertificate)
|
|
||||||
|
|
||||||
// truststore needs to contain root CA for how the driver works...
|
val (keyPair, cert) = createKeyPairAndSelfSignedCertificate()
|
||||||
server.keyStore["cordaclienttls"] = rootCertificate
|
val keyStorePath = saveToKeyStore(tempFolder.root.toPath() / "keystore.jks", keyPair, cert)
|
||||||
server.trustStore["cordaclienttls"] = rootCertificate
|
val brokerSslOptions = BrokerRpcSslOptions(keyStorePath, "password")
|
||||||
server.trustStore["mark"] = markCertificate
|
|
||||||
|
|
||||||
client.keyStore["mark"] = markCertificate
|
val trustStorePath = saveToTrustStore(tempFolder.root.toPath() / "truststore.jks", cert)
|
||||||
client.trustStore["cordaclienttls"] = rootCertificate
|
val clientSslOptions = ClientRpcSslOptions(trustStorePath, "password")
|
||||||
|
|
||||||
withKeyStores(server, client) { nodeSslOptions, clientSslOptions ->
|
|
||||||
var successful = false
|
|
||||||
driver(DriverParameters(isDebug = true, startNodesInProcess = true, portAllocation = RandomFree)) {
|
driver(DriverParameters(isDebug = true, startNodesInProcess = true, portAllocation = RandomFree)) {
|
||||||
val node = startNode(rpcUsers = listOf(user), customOverrides = nodeSslOptions.useSslRpcOverrides()).getOrThrow()
|
val node = startNode(rpcUsers = listOf(user), customOverrides = brokerSslOptions.useSslRpcOverrides()).getOrThrow()
|
||||||
val client = createCordaRPCClientWithSsl(node.rpcAddress, sslConfiguration = clientSslOptions)
|
val client = CordaRPCClient.createWithSsl(node.rpcAddress, sslConfiguration = clientSslOptions)
|
||||||
val connection = client.start(user.username, user.password)
|
val connection = client.start(user.username, user.password)
|
||||||
|
|
||||||
|
connection.proxy.apply {
|
||||||
|
val nodeInfo = nodeInfo()
|
||||||
|
assertThat(nodeInfo.legalIdentities).isNotEmpty
|
||||||
|
successfulLogin = true
|
||||||
|
}
|
||||||
|
connection.close()
|
||||||
|
|
||||||
|
Assertions.assertThatThrownBy {
|
||||||
|
val connection2 = CordaRPCClient.createWithSsl(node.rpcAddress, sslConfiguration = clientSslOptions).start(user.username, "wrong")
|
||||||
|
connection2.proxy.apply {
|
||||||
|
nodeInfo()
|
||||||
|
failedLogin = true
|
||||||
|
}
|
||||||
|
connection2.close()
|
||||||
|
}.isInstanceOf(ActiveMQSecurityException::class.java)
|
||||||
|
}
|
||||||
|
assertThat(successfulLogin).isTrue()
|
||||||
|
assertThat(failedLogin).isFalse()
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `RPC client using ssl will fail if connecting to a node that cannot present a matching certificate`() {
|
||||||
|
val user = User("mark", "dadada", setOf(all()))
|
||||||
|
var successful = false
|
||||||
|
|
||||||
|
val (keyPair, cert) = createKeyPairAndSelfSignedCertificate()
|
||||||
|
val keyStorePath = saveToKeyStore(tempFolder.root.toPath() / "keystore.jks", keyPair, cert)
|
||||||
|
val brokerSslOptions = BrokerRpcSslOptions(keyStorePath, "password")
|
||||||
|
|
||||||
|
val (_, cert1) = createKeyPairAndSelfSignedCertificate()
|
||||||
|
val trustStorePath = saveToTrustStore(tempFolder.root.toPath() / "truststore.jks", cert1)
|
||||||
|
val clientSslOptions = ClientRpcSslOptions(trustStorePath, "password")
|
||||||
|
|
||||||
|
driver(DriverParameters(isDebug = true, startNodesInProcess = true, portAllocation = RandomFree)) {
|
||||||
|
val node = startNode(rpcUsers = listOf(user), customOverrides = brokerSslOptions.useSslRpcOverrides()).getOrThrow()
|
||||||
|
Assertions.assertThatThrownBy {
|
||||||
|
val connection = CordaRPCClient.createWithSsl(node.rpcAddress, sslConfiguration = clientSslOptions).start(user.username, user.password)
|
||||||
connection.proxy.apply {
|
connection.proxy.apply {
|
||||||
nodeInfo()
|
nodeInfo()
|
||||||
successful = true
|
successful = true
|
||||||
}
|
}
|
||||||
|
|
||||||
connection.close()
|
connection.close()
|
||||||
|
}.isInstanceOf(ActiveMQNotConnectedException::class.java)
|
||||||
|
|
||||||
}
|
}
|
||||||
assertThat(successful).isTrue()
|
|
||||||
}
|
assertThat(successful).isFalse()
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun rpc_client_not_using_ssl() {
|
fun `RPC client not using ssl can run commands`() {
|
||||||
val user = User("mark", "dadada", setOf(all()))
|
val user = User("mark", "dadada", setOf(all()))
|
||||||
var successful = false
|
var successful = false
|
||||||
driver(DriverParameters(isDebug = true, startNodesInProcess = true, portAllocation = RandomFree)) {
|
driver(DriverParameters(isDebug = true, startNodesInProcess = true, portAllocation = RandomFree)) {
|
||||||
val node = startNode(rpcUsers = listOf(user)).getOrThrow()
|
val node = startNode(rpcUsers = listOf(user)).getOrThrow()
|
||||||
val client = CordaRPCClient(node.rpcAddress)
|
val connection = CordaRPCClient(node.rpcAddress).start(user.username, user.password)
|
||||||
val connection = client.start(user.username, user.password)
|
|
||||||
connection.proxy.apply {
|
connection.proxy.apply {
|
||||||
nodeInfo()
|
nodeInfo()
|
||||||
successful = true
|
successful = true
|
||||||
}
|
}
|
||||||
|
|
||||||
connection.close()
|
connection.close()
|
||||||
}
|
}
|
||||||
assertThat(successful).isTrue()
|
assertThat(successful).isTrue()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `The system RPC user can not connect to the rpc broker without the node's key`() {
|
||||||
|
val (keyPair, cert) = createKeyPairAndSelfSignedCertificate()
|
||||||
|
val keyStorePath = saveToKeyStore(tempFolder.root.toPath() / "keystore.jks", keyPair, cert)
|
||||||
|
val brokerSslOptions = BrokerRpcSslOptions(keyStorePath, "password")
|
||||||
|
val trustStorePath = saveToTrustStore(tempFolder.root.toPath() / "truststore.jks", cert)
|
||||||
|
val clientSslOptions = ClientRpcSslOptions(trustStorePath, "password")
|
||||||
|
|
||||||
|
driver(DriverParameters(isDebug = true, startNodesInProcess = true, portAllocation = RandomFree)) {
|
||||||
|
val node = startNode(customOverrides = brokerSslOptions.useSslRpcOverrides()).getOrThrow()
|
||||||
|
val client = CordaRPCClient.createWithSsl(node.rpcAddress, sslConfiguration = clientSslOptions)
|
||||||
|
|
||||||
|
Assertions.assertThatThrownBy {
|
||||||
|
client.start(NODE_RPC_USER, NODE_RPC_USER).use { connection ->
|
||||||
|
connection.proxy.nodeInfo()
|
||||||
|
}
|
||||||
|
}.isInstanceOf(ActiveMQException::class.java)
|
||||||
|
|
||||||
|
val clientAdmin = CordaRPCClient.createWithSsl(node.rpcAdminAddress, sslConfiguration = clientSslOptions)
|
||||||
|
|
||||||
|
Assertions.assertThatThrownBy {
|
||||||
|
clientAdmin.start(NODE_RPC_USER, NODE_RPC_USER).use { connection ->
|
||||||
|
connection.proxy.nodeInfo()
|
||||||
|
}
|
||||||
|
}.isInstanceOf(ActiveMQException::class.java)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
@ -7,7 +7,7 @@ import net.corda.core.internal.createDirectories
|
|||||||
import net.corda.core.internal.exists
|
import net.corda.core.internal.exists
|
||||||
import net.corda.core.internal.x500Name
|
import net.corda.core.internal.x500Name
|
||||||
import net.corda.nodeapi.RPCApi
|
import net.corda.nodeapi.RPCApi
|
||||||
import net.corda.nodeapi.internal.ArtemisMessagingComponent.Companion.NODE_USER
|
import net.corda.nodeapi.internal.ArtemisMessagingComponent.Companion.NODE_P2P_USER
|
||||||
import net.corda.nodeapi.internal.ArtemisMessagingComponent.Companion.PEER_USER
|
import net.corda.nodeapi.internal.ArtemisMessagingComponent.Companion.PEER_USER
|
||||||
import net.corda.nodeapi.internal.DEV_INTERMEDIATE_CA
|
import net.corda.nodeapi.internal.DEV_INTERMEDIATE_CA
|
||||||
import net.corda.nodeapi.internal.DEV_ROOT_CA
|
import net.corda.nodeapi.internal.DEV_ROOT_CA
|
||||||
@ -43,10 +43,10 @@ class MQSecurityAsNodeTest : P2PMQSecurityTest() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun `only the node running the broker can login using the special node user`() {
|
fun `only the node running the broker can login using the special P2P node user`() {
|
||||||
val attacker = clientTo(alice.internals.configuration.p2pAddress)
|
val attacker = clientTo(alice.internals.configuration.p2pAddress)
|
||||||
assertThatExceptionOfType(ActiveMQSecurityException::class.java).isThrownBy {
|
assertThatExceptionOfType(ActiveMQSecurityException::class.java).isThrownBy {
|
||||||
attacker.start(NODE_USER, NODE_USER)
|
attacker.start(NODE_P2P_USER, NODE_P2P_USER)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -70,7 +70,7 @@ class MQSecurityAsNodeTest : P2PMQSecurityTest() {
|
|||||||
fun `login to a non ssl port as a node user`() {
|
fun `login to a non ssl port as a node user`() {
|
||||||
val attacker = clientTo(alice.internals.configuration.rpcOptions.address!!, sslConfiguration = null)
|
val attacker = clientTo(alice.internals.configuration.rpcOptions.address!!, sslConfiguration = null)
|
||||||
assertThatExceptionOfType(ActiveMQSecurityException::class.java).isThrownBy {
|
assertThatExceptionOfType(ActiveMQSecurityException::class.java).isThrownBy {
|
||||||
attacker.start(NODE_USER, NODE_USER, enableSSL = false)
|
attacker.start(NODE_P2P_USER, NODE_P2P_USER, enableSSL = false)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -4,7 +4,6 @@ import net.corda.core.identity.CordaX500Name
|
|||||||
import net.corda.core.serialization.internal.nodeSerializationEnv
|
import net.corda.core.serialization.internal.nodeSerializationEnv
|
||||||
import net.corda.core.utilities.NetworkHostAndPort
|
import net.corda.core.utilities.NetworkHostAndPort
|
||||||
import net.corda.nodeapi.ArtemisTcpTransport
|
import net.corda.nodeapi.ArtemisTcpTransport
|
||||||
import net.corda.nodeapi.ConnectionDirection
|
|
||||||
import net.corda.nodeapi.internal.config.SSLConfiguration
|
import net.corda.nodeapi.internal.config.SSLConfiguration
|
||||||
import net.corda.testing.internal.configureTestSSL
|
import net.corda.testing.internal.configureTestSSL
|
||||||
import org.apache.activemq.artemis.api.core.client.*
|
import org.apache.activemq.artemis.api.core.client.*
|
||||||
@ -23,7 +22,7 @@ class SimpleMQClient(val target: NetworkHostAndPort,
|
|||||||
lateinit var producer: ClientProducer
|
lateinit var producer: ClientProducer
|
||||||
|
|
||||||
fun start(username: String? = null, password: String? = null, enableSSL: Boolean = true) {
|
fun start(username: String? = null, password: String? = null, enableSSL: Boolean = true) {
|
||||||
val tcpTransport = ArtemisTcpTransport.tcpTransport(ConnectionDirection.Outbound(), target, config, enableSSL = enableSSL)
|
val tcpTransport = ArtemisTcpTransport.p2pConnectorTcpTransport(target, config, enableSSL = enableSSL)
|
||||||
val locator = ActiveMQClient.createServerLocatorWithoutHA(tcpTransport).apply {
|
val locator = ActiveMQClient.createServerLocatorWithoutHA(tcpTransport).apply {
|
||||||
isBlockOnNonDurableSend = true
|
isBlockOnNonDurableSend = true
|
||||||
threadPoolMaxSize = 1
|
threadPoolMaxSize = 1
|
||||||
|
@ -313,10 +313,7 @@ abstract class AbstractNode(val configuration: NodeConfiguration,
|
|||||||
|
|
||||||
open fun startShell() {
|
open fun startShell() {
|
||||||
if (configuration.shouldInitCrashShell()) {
|
if (configuration.shouldInitCrashShell()) {
|
||||||
if (configuration.rpcOptions.address == null) {
|
InteractiveShell.startShellInternal(configuration.toShellConfig(), cordappLoader.appClassLoader)
|
||||||
throw ConfigurationException("Cannot init CrashShell because node RPC address is not set (via 'rpcSettings' option).")
|
|
||||||
}
|
|
||||||
InteractiveShell.startShell(configuration.toShellConfig(), cordappLoader.appClassLoader)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -3,6 +3,7 @@ package net.corda.node.internal
|
|||||||
import com.codahale.metrics.JmxReporter
|
import com.codahale.metrics.JmxReporter
|
||||||
import net.corda.client.rpc.internal.serialization.amqp.AMQPClientSerializationScheme
|
import net.corda.client.rpc.internal.serialization.amqp.AMQPClientSerializationScheme
|
||||||
import net.corda.core.concurrent.CordaFuture
|
import net.corda.core.concurrent.CordaFuture
|
||||||
|
import net.corda.core.identity.CordaX500Name
|
||||||
import net.corda.core.internal.Emoji
|
import net.corda.core.internal.Emoji
|
||||||
import net.corda.core.internal.concurrent.openFuture
|
import net.corda.core.internal.concurrent.openFuture
|
||||||
import net.corda.core.internal.concurrent.thenMatch
|
import net.corda.core.internal.concurrent.thenMatch
|
||||||
@ -28,19 +29,22 @@ import net.corda.node.internal.security.RPCSecurityManagerImpl
|
|||||||
import net.corda.node.internal.security.RPCSecurityManagerWithAdditionalUser
|
import net.corda.node.internal.security.RPCSecurityManagerWithAdditionalUser
|
||||||
import net.corda.node.serialization.amqp.AMQPServerSerializationScheme
|
import net.corda.node.serialization.amqp.AMQPServerSerializationScheme
|
||||||
import net.corda.node.serialization.kryo.KryoServerSerializationScheme
|
import net.corda.node.serialization.kryo.KryoServerSerializationScheme
|
||||||
|
import net.corda.node.services.Permissions
|
||||||
import net.corda.node.services.api.NodePropertiesStore
|
import net.corda.node.services.api.NodePropertiesStore
|
||||||
import net.corda.node.services.api.SchemaService
|
import net.corda.node.services.api.SchemaService
|
||||||
import net.corda.node.services.config.*
|
import net.corda.node.services.config.*
|
||||||
import net.corda.node.services.config.shell.localShellUser
|
|
||||||
import net.corda.node.services.messaging.*
|
import net.corda.node.services.messaging.*
|
||||||
import net.corda.node.services.rpc.ArtemisRpcBroker
|
import net.corda.node.services.rpc.ArtemisRpcBroker
|
||||||
import net.corda.node.services.transactions.InMemoryTransactionVerifierService
|
import net.corda.node.services.transactions.InMemoryTransactionVerifierService
|
||||||
import net.corda.node.utilities.AddressUtils
|
import net.corda.node.utilities.AddressUtils
|
||||||
import net.corda.node.utilities.AffinityExecutor
|
import net.corda.node.utilities.AffinityExecutor
|
||||||
import net.corda.node.utilities.DemoClock
|
import net.corda.node.utilities.DemoClock
|
||||||
|
import net.corda.nodeapi.internal.ArtemisMessagingComponent.Companion.INTERNAL_SHELL_USER
|
||||||
import net.corda.nodeapi.internal.ShutdownHook
|
import net.corda.nodeapi.internal.ShutdownHook
|
||||||
import net.corda.nodeapi.internal.addShutdownHook
|
import net.corda.nodeapi.internal.addShutdownHook
|
||||||
import net.corda.nodeapi.internal.bridging.BridgeControlListener
|
import net.corda.nodeapi.internal.bridging.BridgeControlListener
|
||||||
|
import net.corda.nodeapi.internal.config.User
|
||||||
|
import net.corda.nodeapi.internal.crypto.X509Utilities
|
||||||
import net.corda.nodeapi.internal.persistence.CordaPersistence
|
import net.corda.nodeapi.internal.persistence.CordaPersistence
|
||||||
import net.corda.serialization.internal.*
|
import net.corda.serialization.internal.*
|
||||||
import org.slf4j.Logger
|
import org.slf4j.Logger
|
||||||
@ -164,10 +168,11 @@ open class Node(configuration: NodeConfiguration,
|
|||||||
networkParameters: NetworkParameters): MessagingService {
|
networkParameters: NetworkParameters): MessagingService {
|
||||||
// Construct security manager reading users data either from the 'security' config section
|
// Construct security manager reading users data either from the 'security' config section
|
||||||
// if present or from rpcUsers list if the former is missing from config.
|
// if present or from rpcUsers list if the former is missing from config.
|
||||||
val securityManagerConfig = configuration.security?.authService ?: SecurityConfiguration.AuthService.fromUsers(configuration.rpcUsers)
|
val securityManagerConfig = configuration.security?.authService
|
||||||
|
?: SecurityConfiguration.AuthService.fromUsers(configuration.rpcUsers)
|
||||||
|
|
||||||
securityManager = with(RPCSecurityManagerImpl(securityManagerConfig)) {
|
securityManager = with(RPCSecurityManagerImpl(securityManagerConfig)) {
|
||||||
if (configuration.shouldStartLocalShell()) RPCSecurityManagerWithAdditionalUser(this, localShellUser()) else this
|
if (configuration.shouldStartLocalShell()) RPCSecurityManagerWithAdditionalUser(this, User(INTERNAL_SHELL_USER, INTERNAL_SHELL_USER, setOf(Permissions.all()))) else this
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!configuration.messagingServerExternal) {
|
if (!configuration.messagingServerExternal) {
|
||||||
@ -175,7 +180,8 @@ open class Node(configuration: NodeConfiguration,
|
|||||||
messageBroker = ArtemisMessagingServer(configuration, brokerBindAddress, networkParameters.maxMessageSize)
|
messageBroker = ArtemisMessagingServer(configuration, brokerBindAddress, networkParameters.maxMessageSize)
|
||||||
}
|
}
|
||||||
|
|
||||||
val serverAddress = configuration.messagingServerAddress ?: NetworkHostAndPort("localhost", configuration.p2pAddress.port)
|
val serverAddress = configuration.messagingServerAddress
|
||||||
|
?: NetworkHostAndPort("localhost", configuration.p2pAddress.port)
|
||||||
val rpcServerAddresses = if (configuration.rpcOptions.standAloneBroker) {
|
val rpcServerAddresses = if (configuration.rpcOptions.standAloneBroker) {
|
||||||
BrokerAddresses(configuration.rpcOptions.address!!, configuration.rpcOptions.adminAddress)
|
BrokerAddresses(configuration.rpcOptions.address!!, configuration.rpcOptions.adminAddress)
|
||||||
} else {
|
} else {
|
||||||
@ -186,7 +192,7 @@ open class Node(configuration: NodeConfiguration,
|
|||||||
|
|
||||||
printBasicNodeInfo("Advertised P2P messaging addresses", info.addresses.joinToString())
|
printBasicNodeInfo("Advertised P2P messaging addresses", info.addresses.joinToString())
|
||||||
rpcServerAddresses?.let {
|
rpcServerAddresses?.let {
|
||||||
rpcMessagingClient = RPCMessagingClient(configuration.rpcOptions.sslConfig, it.admin, MAX_RPC_MESSAGE_SIZE)
|
internalRpcMessagingClient = InternalRPCMessagingClient(configuration, it.admin, MAX_RPC_MESSAGE_SIZE, CordaX500Name.build(configuration.loadSslKeyStore().getCertificate(X509Utilities.CORDA_CLIENT_TLS).subjectX500Principal))
|
||||||
printBasicNodeInfo("RPC connection address", it.primary.toString())
|
printBasicNodeInfo("RPC connection address", it.primary.toString())
|
||||||
printBasicNodeInfo("RPC admin connection address", it.admin.toString())
|
printBasicNodeInfo("RPC admin connection address", it.admin.toString())
|
||||||
}
|
}
|
||||||
@ -212,18 +218,17 @@ open class Node(configuration: NodeConfiguration,
|
|||||||
}
|
}
|
||||||
|
|
||||||
private fun startLocalRpcBroker(): BrokerAddresses? {
|
private fun startLocalRpcBroker(): BrokerAddresses? {
|
||||||
with(configuration) {
|
return with(configuration) {
|
||||||
return rpcOptions.address?.let {
|
rpcOptions.address.let {
|
||||||
require(rpcOptions.address != null) { "RPC address needs to be specified for local RPC broker." }
|
|
||||||
val rpcBrokerDirectory: Path = baseDirectory / "brokers" / "rpc"
|
val rpcBrokerDirectory: Path = baseDirectory / "brokers" / "rpc"
|
||||||
with(rpcOptions) {
|
with(rpcOptions) {
|
||||||
rpcBroker = if (useSsl) {
|
rpcBroker = if (useSsl) {
|
||||||
ArtemisRpcBroker.withSsl(this.address!!, sslConfig, securityManager, certificateChainCheckPolicies, MAX_RPC_MESSAGE_SIZE, jmxMonitoringHttpPort != null, rpcBrokerDirectory)
|
ArtemisRpcBroker.withSsl(configuration, this.address, adminAddress, sslConfig, securityManager, MAX_RPC_MESSAGE_SIZE, jmxMonitoringHttpPort != null, rpcBrokerDirectory, shouldStartLocalShell())
|
||||||
} else {
|
} else {
|
||||||
ArtemisRpcBroker.withoutSsl(this.address!!, adminAddress!!, sslConfig, securityManager, certificateChainCheckPolicies, MAX_RPC_MESSAGE_SIZE, jmxMonitoringHttpPort != null, rpcBrokerDirectory)
|
ArtemisRpcBroker.withoutSsl(configuration, this.address, adminAddress, securityManager, MAX_RPC_MESSAGE_SIZE, jmxMonitoringHttpPort != null, rpcBrokerDirectory, shouldStartLocalShell())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return rpcBroker!!.addresses
|
rpcBroker!!.addresses
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -286,12 +291,11 @@ open class Node(configuration: NodeConfiguration,
|
|||||||
start()
|
start()
|
||||||
}
|
}
|
||||||
// Start up the MQ clients.
|
// Start up the MQ clients.
|
||||||
rpcMessagingClient?.run {
|
internalRpcMessagingClient?.run {
|
||||||
runOnStop += this::close
|
runOnStop += this::close
|
||||||
when (rpcOps) {
|
when (rpcOps) {
|
||||||
// not sure what this RPCOps base interface is for
|
is SecureCordaRPCOps -> init(RpcExceptionHandlingProxy(rpcOps), securityManager)
|
||||||
is SecureCordaRPCOps -> start(RpcExceptionHandlingProxy(rpcOps), securityManager)
|
else -> init(rpcOps, securityManager)
|
||||||
else -> start(rpcOps, securityManager)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
verifierMessagingClient?.run {
|
verifierMessagingClient?.run {
|
||||||
@ -351,10 +355,7 @@ open class Node(configuration: NodeConfiguration,
|
|||||||
// Begin exporting our own metrics via JMX. These can be monitored using any agent, e.g. Jolokia:
|
// Begin exporting our own metrics via JMX. These can be monitored using any agent, e.g. Jolokia:
|
||||||
//
|
//
|
||||||
// https://jolokia.org/agent/jvm.html
|
// https://jolokia.org/agent/jvm.html
|
||||||
JmxReporter.
|
JmxReporter.forRegistry(started.services.monitoringService.metrics).inDomain("net.corda").createsObjectNamesWith { _, domain, name ->
|
||||||
forRegistry(started.services.monitoringService.metrics).
|
|
||||||
inDomain("net.corda").
|
|
||||||
createsObjectNamesWith { _, domain, name ->
|
|
||||||
// Make the JMX hierarchy a bit better organised.
|
// Make the JMX hierarchy a bit better organised.
|
||||||
val category = name.substringBefore('.')
|
val category = name.substringBefore('.')
|
||||||
val subName = name.substringAfter('.', "")
|
val subName = name.substringAfter('.', "")
|
||||||
@ -362,9 +363,7 @@ open class Node(configuration: NodeConfiguration,
|
|||||||
ObjectName("$domain:name=$category")
|
ObjectName("$domain:name=$category")
|
||||||
else
|
else
|
||||||
ObjectName("$domain:type=$category,name=$subName")
|
ObjectName("$domain:type=$category,name=$subName")
|
||||||
}.
|
}.build().start()
|
||||||
build().
|
|
||||||
start()
|
|
||||||
|
|
||||||
_startupComplete.set(Unit)
|
_startupComplete.set(Unit)
|
||||||
}
|
}
|
||||||
@ -395,11 +394,11 @@ open class Node(configuration: NodeConfiguration,
|
|||||||
rpcClientContext = if (configuration.shouldInitCrashShell()) AMQP_RPC_CLIENT_CONTEXT.withClassLoader(classloader) else null) //even Shell embeded in the node connects via RPC to the node
|
rpcClientContext = if (configuration.shouldInitCrashShell()) AMQP_RPC_CLIENT_CONTEXT.withClassLoader(classloader) else null) //even Shell embeded in the node connects via RPC to the node
|
||||||
}
|
}
|
||||||
|
|
||||||
private var rpcMessagingClient: RPCMessagingClient? = null
|
private var internalRpcMessagingClient: InternalRPCMessagingClient? = null
|
||||||
private var verifierMessagingClient: VerifierMessagingClient? = null
|
private var verifierMessagingClient: VerifierMessagingClient? = null
|
||||||
/** Starts a blocking event loop for message dispatch. */
|
/** Starts a blocking event loop for message dispatch. */
|
||||||
fun run() {
|
fun run() {
|
||||||
rpcMessagingClient?.start2(rpcBroker!!.serverControl)
|
internalRpcMessagingClient?.start(rpcBroker!!.serverControl)
|
||||||
verifierMessagingClient?.start2()
|
verifierMessagingClient?.start2()
|
||||||
(network as P2PMessagingClient).run()
|
(network as P2PMessagingClient).run()
|
||||||
}
|
}
|
||||||
|
@ -0,0 +1,217 @@
|
|||||||
|
package net.corda.node.internal.artemis
|
||||||
|
|
||||||
|
import net.corda.core.internal.uncheckedCast
|
||||||
|
import net.corda.core.utilities.contextLogger
|
||||||
|
import net.corda.core.utilities.debug
|
||||||
|
import net.corda.node.internal.security.Password
|
||||||
|
import net.corda.node.internal.security.RPCSecurityManager
|
||||||
|
import net.corda.node.services.rpc.LoginListener
|
||||||
|
import net.corda.nodeapi.RPCApi
|
||||||
|
import net.corda.nodeapi.internal.ArtemisMessagingComponent
|
||||||
|
import org.apache.activemq.artemis.spi.core.security.jaas.CertificateCallback
|
||||||
|
import org.apache.activemq.artemis.spi.core.security.jaas.RolePrincipal
|
||||||
|
import org.apache.activemq.artemis.spi.core.security.jaas.UserPrincipal
|
||||||
|
import java.io.IOException
|
||||||
|
import java.security.KeyStore
|
||||||
|
import java.security.Principal
|
||||||
|
import java.util.*
|
||||||
|
import javax.security.auth.Subject
|
||||||
|
import javax.security.auth.callback.CallbackHandler
|
||||||
|
import javax.security.auth.callback.NameCallback
|
||||||
|
import javax.security.auth.callback.PasswordCallback
|
||||||
|
import javax.security.auth.callback.UnsupportedCallbackException
|
||||||
|
import javax.security.auth.login.FailedLoginException
|
||||||
|
import javax.security.auth.login.LoginException
|
||||||
|
import javax.security.auth.spi.LoginModule
|
||||||
|
import javax.security.cert.X509Certificate
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* The Participants in the system are "The current node", "Peer nodes", "The Artemis P2P broker", "RPC clients", "The Artemis RPC broker".
|
||||||
|
*
|
||||||
|
* These participants need to communicate and authenticate each other.
|
||||||
|
*
|
||||||
|
* Peer Nodes must use TLS when connecting to the current node's P2P broker.
|
||||||
|
*
|
||||||
|
* RPC Clients may use TLS, and need to provide a username/password.
|
||||||
|
*
|
||||||
|
* Note that the "login" method is called after the SSL handshake was successful.
|
||||||
|
* Based on the provided username, we execute extra authentication logic based on the presented client certificates or the username/password:
|
||||||
|
*
|
||||||
|
* * If someone connects with [PEER_USER] then we confirm they belong on our P2P network by checking their root CA is
|
||||||
|
* the same as our root CA. If that's the case, the only access they're given is the ability to send to our P2P address.
|
||||||
|
* The messages these authenticated nodes send to us are tagged with their subject DN.
|
||||||
|
*
|
||||||
|
* * If someone connects with [NODE_P2P_USER] or [NODE_RPC_USER] then we confirm it's the current node by checking their TLS certificate
|
||||||
|
* is the same as our one in our key store. Then they're given full access to all valid queues.
|
||||||
|
*
|
||||||
|
* * Otherwise, if the username is neither of the above, we assume it's an RPC user and authenticate against our list of valid RPC users.
|
||||||
|
* RPC clients are given permission to perform RPC and nothing else.
|
||||||
|
*
|
||||||
|
*
|
||||||
|
* Basically, our security policy is to use some hardcoded usernames as discriminators to determine what checks to run on the presented client certificates,
|
||||||
|
* and after the checks pass, what roles to assign.
|
||||||
|
*
|
||||||
|
* The node starts one broker for RPC and one broker for P2P.
|
||||||
|
*
|
||||||
|
* The P2P broker has only 1 acceptor to which only clients that have a doorman signed certificate can connect.
|
||||||
|
* If a Peer attempts to connect as the current node, it would fail because it can't present the node's certificate.
|
||||||
|
*
|
||||||
|
* The RPC broker has 2 acceptors:
|
||||||
|
* * The Admin acceptor to which only the current node should connect. This port should be blocked to any outside connection.
|
||||||
|
* To connect as the current Node, you need to present the nodes ssl certificate.
|
||||||
|
* Theoretically, if the port is open, any Peer Node could connect to this endpoint, but to actually run commands it would have to know a valid rpc username/password.
|
||||||
|
* If the connection was made as [NODE_RPC_USER], the client would have to present the owner node's certificate.
|
||||||
|
* * The RPC client acceptor. This can be configured to use SSL. The authentication is based on username/password.
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
class BrokerJaasLoginModule : BaseBrokerJaasLoginModule() {
|
||||||
|
companion object {
|
||||||
|
const val PEER_ROLE = "SystemRoles/Peer"
|
||||||
|
const val NODE_P2P_ROLE = "SystemRoles/NodeP2P"
|
||||||
|
const val NODE_RPC_ROLE = "SystemRoles/NodeRPC"
|
||||||
|
const val RPC_ROLE = "SystemRoles/RPC"
|
||||||
|
|
||||||
|
internal val RPC_SECURITY_CONFIG = "RPC_SECURITY_CONFIG"
|
||||||
|
internal val P2P_SECURITY_CONFIG = "P2P_SECURITY_CONFIG"
|
||||||
|
internal val NODE_SECURITY_CONFIG = "NODE_SECURITY_CONFIG"
|
||||||
|
|
||||||
|
private val log = contextLogger()
|
||||||
|
}
|
||||||
|
|
||||||
|
private lateinit var nodeJaasConfig: NodeJaasConfig
|
||||||
|
private var rpcJaasConfig: RPCJaasConfig? = null
|
||||||
|
private var p2pJaasConfig: P2PJaasConfig? = null
|
||||||
|
|
||||||
|
override fun initialize(subject: Subject, callbackHandler: CallbackHandler, sharedState: Map<String, *>, options: Map<String, *>) {
|
||||||
|
super.initialize(subject, callbackHandler, sharedState, options)
|
||||||
|
|
||||||
|
nodeJaasConfig = uncheckedCast(options[NODE_SECURITY_CONFIG])
|
||||||
|
p2pJaasConfig = uncheckedCast(options[P2P_SECURITY_CONFIG])
|
||||||
|
rpcJaasConfig = uncheckedCast(options[RPC_SECURITY_CONFIG])
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun login(): Boolean {
|
||||||
|
try {
|
||||||
|
val (username, password, certificates) = getUsernamePasswordAndCerts()
|
||||||
|
log.debug("Processing login for $username")
|
||||||
|
|
||||||
|
val userAndRoles = authenticateAndAuthorise(username, certificates, password)
|
||||||
|
principals += UserPrincipal(userAndRoles.first)
|
||||||
|
principals += userAndRoles.second
|
||||||
|
|
||||||
|
log.debug("Login for $username succeeded")
|
||||||
|
loginSucceeded = true
|
||||||
|
return true
|
||||||
|
} catch (e: Exception) {
|
||||||
|
log.error("Login failed: ${e.message}", e)
|
||||||
|
if (e is LoginException) {
|
||||||
|
throw e
|
||||||
|
} else {
|
||||||
|
throw FailedLoginException(e.message)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// The Main authentication logic, responsible for running all the configured checks for each user type
|
||||||
|
// and return the actual User and principals
|
||||||
|
private fun authenticateAndAuthorise(username: String, certificates: Array<X509Certificate>?, password: String): Pair<String, List<RolePrincipal>> {
|
||||||
|
fun requireTls(certificates: Array<X509Certificate>?) = requireNotNull(certificates) { "No client certificates presented." }
|
||||||
|
|
||||||
|
return when (username) {
|
||||||
|
ArtemisMessagingComponent.NODE_P2P_USER -> {
|
||||||
|
requireTls(certificates)
|
||||||
|
CertificateChainCheckPolicy.LeafMustMatch.createCheck(nodeJaasConfig.keyStore, nodeJaasConfig.trustStore).checkCertificateChain(certificates!!)
|
||||||
|
Pair(certificates.first().subjectDN.name, listOf(RolePrincipal(NODE_P2P_ROLE)))
|
||||||
|
}
|
||||||
|
ArtemisMessagingComponent.NODE_RPC_USER -> {
|
||||||
|
requireTls(certificates)
|
||||||
|
CertificateChainCheckPolicy.LeafMustMatch.createCheck(nodeJaasConfig.keyStore, nodeJaasConfig.trustStore).checkCertificateChain(certificates!!)
|
||||||
|
Pair(ArtemisMessagingComponent.NODE_RPC_USER, listOf(RolePrincipal(NODE_RPC_ROLE)))
|
||||||
|
}
|
||||||
|
ArtemisMessagingComponent.PEER_USER -> {
|
||||||
|
requireNotNull(p2pJaasConfig) { "Attempted to connect as a peer to the rpc broker." }
|
||||||
|
requireTls(certificates)
|
||||||
|
// This check is redundant as it was performed already during the SSL handshake
|
||||||
|
CertificateChainCheckPolicy.RootMustMatch.createCheck(p2pJaasConfig!!.keyStore, p2pJaasConfig!!.trustStore).checkCertificateChain(certificates!!)
|
||||||
|
Pair(certificates.first().subjectDN.name, listOf(RolePrincipal(PEER_ROLE)))
|
||||||
|
}
|
||||||
|
else -> {
|
||||||
|
requireNotNull(rpcJaasConfig) { "Attempted to connect as an rpc user to the P2P broker." }
|
||||||
|
rpcJaasConfig!!.run {
|
||||||
|
securityManager.authenticate(username, Password(password))
|
||||||
|
// This will assign the username the actual rights.
|
||||||
|
loginListener(username)
|
||||||
|
// This enables the RPC client to send requests and to receive responses.
|
||||||
|
Pair(username, listOf(RolePrincipal(RPC_ROLE), RolePrincipal("${RPCApi.RPC_CLIENT_QUEUE_NAME_PREFIX}.$username")))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
// Configs used for setting up the broker custom security module.
|
||||||
|
data class RPCJaasConfig(
|
||||||
|
val securityManager: RPCSecurityManager, //used to authenticate users - implemented with Shiro
|
||||||
|
val loginListener: LoginListener, //callback that dynamically assigns security roles to RPC users on their authentication
|
||||||
|
val useSslForRPC: Boolean)
|
||||||
|
|
||||||
|
data class P2PJaasConfig(val keyStore: KeyStore, val trustStore: KeyStore)
|
||||||
|
|
||||||
|
data class NodeJaasConfig(val keyStore: KeyStore, val trustStore: KeyStore)
|
||||||
|
|
||||||
|
|
||||||
|
// Boilerplate required for JAAS.
|
||||||
|
abstract class BaseBrokerJaasLoginModule : LoginModule {
|
||||||
|
protected var loginSucceeded: Boolean = false
|
||||||
|
protected lateinit var subject: Subject
|
||||||
|
protected lateinit var callbackHandler: CallbackHandler
|
||||||
|
protected val principals = ArrayList<Principal>()
|
||||||
|
|
||||||
|
protected fun getUsernamePasswordAndCerts(): Triple<String, String, Array<X509Certificate>?> {
|
||||||
|
val nameCallback = NameCallback("Username: ")
|
||||||
|
val passwordCallback = PasswordCallback("Password: ", false)
|
||||||
|
val certificateCallback = CertificateCallback()
|
||||||
|
try {
|
||||||
|
callbackHandler.handle(arrayOf(nameCallback, passwordCallback, certificateCallback))
|
||||||
|
} catch (e: IOException) {
|
||||||
|
throw LoginException(e.message)
|
||||||
|
} catch (e: UnsupportedCallbackException) {
|
||||||
|
throw LoginException("${e.message} not available to obtain information from user")
|
||||||
|
}
|
||||||
|
|
||||||
|
val username = nameCallback.name ?: throw FailedLoginException("Username not provided")
|
||||||
|
val password = String(passwordCallback.password ?: throw FailedLoginException("Password not provided"))
|
||||||
|
val certificates = certificateCallback.certificates
|
||||||
|
return Triple(username, password, certificates)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun initialize(subject: Subject, callbackHandler: CallbackHandler, sharedState: Map<String, *>, options: Map<String, *>) {
|
||||||
|
this.subject = subject
|
||||||
|
this.callbackHandler = callbackHandler
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun commit(): Boolean {
|
||||||
|
val result = loginSucceeded
|
||||||
|
if (result) {
|
||||||
|
subject.principals.addAll(principals)
|
||||||
|
}
|
||||||
|
clear()
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun abort(): Boolean {
|
||||||
|
clear()
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun logout(): Boolean {
|
||||||
|
subject.principals.removeAll(principals)
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun clear() {
|
||||||
|
loginSucceeded = false
|
||||||
|
}
|
||||||
|
}
|
@ -10,6 +10,10 @@ import net.corda.core.utilities.seconds
|
|||||||
import net.corda.node.internal.artemis.CertificateChainCheckPolicy
|
import net.corda.node.internal.artemis.CertificateChainCheckPolicy
|
||||||
import net.corda.node.services.config.rpc.NodeRpcOptions
|
import net.corda.node.services.config.rpc.NodeRpcOptions
|
||||||
import net.corda.nodeapi.internal.config.*
|
import net.corda.nodeapi.internal.config.*
|
||||||
|
import net.corda.nodeapi.BrokerRpcSslOptions
|
||||||
|
import net.corda.nodeapi.internal.config.NodeSSLConfiguration
|
||||||
|
import net.corda.nodeapi.internal.config.User
|
||||||
|
import net.corda.nodeapi.internal.config.parseAs
|
||||||
import net.corda.nodeapi.internal.persistence.DatabaseConfig
|
import net.corda.nodeapi.internal.persistence.DatabaseConfig
|
||||||
import net.corda.tools.shell.SSHDConfiguration
|
import net.corda.tools.shell.SSHDConfiguration
|
||||||
import org.bouncycastle.asn1.x500.X500Name
|
import org.bouncycastle.asn1.x500.X500Name
|
||||||
@ -19,7 +23,6 @@ import java.nio.file.Path
|
|||||||
import java.time.Duration
|
import java.time.Duration
|
||||||
import java.util.*
|
import java.util.*
|
||||||
|
|
||||||
|
|
||||||
val Int.MB: Long get() = this * 1024L * 1024L
|
val Int.MB: Long get() = this * 1024L * 1024L
|
||||||
|
|
||||||
interface NodeConfiguration : NodeSSLConfiguration {
|
interface NodeConfiguration : NodeSSLConfiguration {
|
||||||
@ -148,7 +151,8 @@ data class NodeConfigurationImpl(
|
|||||||
override val messagingServerAddress: NetworkHostAndPort?,
|
override val messagingServerAddress: NetworkHostAndPort?,
|
||||||
override val messagingServerExternal: Boolean = (messagingServerAddress != null),
|
override val messagingServerExternal: Boolean = (messagingServerAddress != null),
|
||||||
override val notary: NotaryConfig?,
|
override val notary: NotaryConfig?,
|
||||||
override val certificateChainCheckPolicies: List<CertChainPolicyConfig>,
|
@Deprecated("Do not configure")
|
||||||
|
override val certificateChainCheckPolicies: List<CertChainPolicyConfig> = emptyList(),
|
||||||
override val devMode: Boolean = false,
|
override val devMode: Boolean = false,
|
||||||
override val noLocalShell: Boolean = false,
|
override val noLocalShell: Boolean = false,
|
||||||
override val devModeOptions: DevModeOptions? = null,
|
override val devModeOptions: DevModeOptions? = null,
|
||||||
@ -171,9 +175,9 @@ data class NodeConfigurationImpl(
|
|||||||
private val logger = loggerFor<NodeConfigurationImpl>()
|
private val logger = loggerFor<NodeConfigurationImpl>()
|
||||||
}
|
}
|
||||||
|
|
||||||
override val rpcOptions: NodeRpcOptions = initialiseRpcOptions(rpcAddress, rpcSettings, SslOptions(baseDirectory / "certificates", keyStorePassword, trustStorePassword, crlCheckSoftFail))
|
override val rpcOptions: NodeRpcOptions = initialiseRpcOptions(rpcAddress, rpcSettings, BrokerRpcSslOptions(baseDirectory / "certificates" / "nodekeystore.jks", keyStorePassword))
|
||||||
|
|
||||||
private fun initialiseRpcOptions(explicitAddress: NetworkHostAndPort?, settings: NodeRpcSettings, fallbackSslOptions: SSLConfiguration): NodeRpcOptions {
|
private fun initialiseRpcOptions(explicitAddress: NetworkHostAndPort?, settings: NodeRpcSettings, fallbackSslOptions: BrokerRpcSslOptions): NodeRpcOptions {
|
||||||
return when {
|
return when {
|
||||||
explicitAddress != null -> {
|
explicitAddress != null -> {
|
||||||
require(settings.address == null) { "Can't provide top-level rpcAddress and rpcSettings.address (they control the same property)." }
|
require(settings.address == null) { "Can't provide top-level rpcAddress and rpcSettings.address (they control the same property)." }
|
||||||
@ -243,17 +247,22 @@ data class NodeConfigurationImpl(
|
|||||||
require(security == null || rpcUsers.isEmpty()) {
|
require(security == null || rpcUsers.isEmpty()) {
|
||||||
"Cannot specify both 'rpcUsers' and 'security' in configuration"
|
"Cannot specify both 'rpcUsers' and 'security' in configuration"
|
||||||
}
|
}
|
||||||
|
if(certificateChainCheckPolicies.isNotEmpty()) {
|
||||||
|
logger.warn("""You are configuring certificateChainCheckPolicies. This is a setting that is not used, and will be removed in a future version.
|
||||||
|
|Please contact the R3 team on the public slack to discuss your use case.
|
||||||
|
""".trimMargin())
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
data class NodeRpcSettings(
|
data class NodeRpcSettings(
|
||||||
val address: NetworkHostAndPort?,
|
val address: NetworkHostAndPort,
|
||||||
val adminAddress: NetworkHostAndPort?,
|
val adminAddress: NetworkHostAndPort,
|
||||||
val standAloneBroker: Boolean = false,
|
val standAloneBroker: Boolean = false,
|
||||||
val useSsl: Boolean = false,
|
val useSsl: Boolean = false,
|
||||||
val ssl: SslOptions?
|
val ssl: BrokerRpcSslOptions?
|
||||||
) {
|
) {
|
||||||
fun asOptions(fallbackSslOptions: SSLConfiguration): NodeRpcOptions {
|
fun asOptions(fallbackSslOptions: BrokerRpcSslOptions): NodeRpcOptions {
|
||||||
return object : NodeRpcOptions {
|
return object : NodeRpcOptions {
|
||||||
override val address = this@NodeRpcSettings.address
|
override val address = this@NodeRpcSettings.address
|
||||||
override val adminAddress = this@NodeRpcSettings.adminAddress
|
override val adminAddress = this@NodeRpcSettings.adminAddress
|
||||||
@ -281,6 +290,7 @@ enum class CertChainPolicyType {
|
|||||||
UsernameMustMatch
|
UsernameMustMatch
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Deprecated("Do not use")
|
||||||
data class CertChainPolicyConfig(val role: String, private val policy: CertChainPolicyType, private val trustedAliases: Set<String>) {
|
data class CertChainPolicyConfig(val role: String, private val policy: CertChainPolicyType, private val trustedAliases: Set<String>) {
|
||||||
val certificateChainCheckPolicy: CertificateChainCheckPolicy
|
val certificateChainCheckPolicy: CertificateChainCheckPolicy
|
||||||
get() {
|
get() {
|
||||||
|
@ -1,23 +0,0 @@
|
|||||||
package net.corda.node.services.config
|
|
||||||
|
|
||||||
import net.corda.nodeapi.internal.config.SSLConfiguration
|
|
||||||
import java.nio.file.Path
|
|
||||||
import java.nio.file.Paths
|
|
||||||
|
|
||||||
// TODO: we use both SSL and Ssl for names. We should pick one of them, or even better change to TLS
|
|
||||||
data class SslOptions(override val certificatesDirectory: Path,
|
|
||||||
override val keyStorePassword: String,
|
|
||||||
override val trustStorePassword: String,
|
|
||||||
override val crlCheckSoftFail: Boolean) : SSLConfiguration {
|
|
||||||
|
|
||||||
fun copy(certificatesDirectory: String = this.certificatesDirectory.toString(),
|
|
||||||
keyStorePassword: String = this.keyStorePassword,
|
|
||||||
trustStorePassword: String = this.trustStorePassword,
|
|
||||||
crlCheckSoftFail: Boolean = this.crlCheckSoftFail): SslOptions = copy(
|
|
||||||
certificatesDirectory = certificatesDirectory.toAbsolutePath(),
|
|
||||||
keyStorePassword = keyStorePassword,
|
|
||||||
trustStorePassword = trustStorePassword,
|
|
||||||
crlCheckSoftFail = crlCheckSoftFail)
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun String.toAbsolutePath() = Paths.get(this).toAbsolutePath()
|
|
@ -1,12 +1,12 @@
|
|||||||
package net.corda.node.services.config.rpc
|
package net.corda.node.services.config.rpc
|
||||||
|
|
||||||
import net.corda.core.utilities.NetworkHostAndPort
|
import net.corda.core.utilities.NetworkHostAndPort
|
||||||
import net.corda.nodeapi.internal.config.SSLConfiguration
|
import net.corda.nodeapi.BrokerRpcSslOptions
|
||||||
|
|
||||||
interface NodeRpcOptions {
|
interface NodeRpcOptions {
|
||||||
val address: NetworkHostAndPort?
|
val address: NetworkHostAndPort
|
||||||
val adminAddress: NetworkHostAndPort?
|
val adminAddress: NetworkHostAndPort
|
||||||
val standAloneBroker: Boolean
|
val standAloneBroker: Boolean
|
||||||
val useSsl: Boolean
|
val useSsl: Boolean
|
||||||
val sslConfig: SSLConfiguration
|
val sslConfig: BrokerRpcSslOptions
|
||||||
}
|
}
|
@ -1,42 +1,21 @@
|
|||||||
package net.corda.node.services.config.shell
|
package net.corda.node.services.config.shell
|
||||||
|
|
||||||
import net.corda.core.internal.div
|
import net.corda.core.internal.div
|
||||||
import net.corda.core.utilities.NetworkHostAndPort
|
|
||||||
import net.corda.node.services.Permissions
|
|
||||||
import net.corda.node.services.config.NodeConfiguration
|
import net.corda.node.services.config.NodeConfiguration
|
||||||
import net.corda.nodeapi.internal.config.User
|
import net.corda.nodeapi.internal.ArtemisMessagingComponent.Companion.INTERNAL_SHELL_USER
|
||||||
import net.corda.tools.shell.ShellConfiguration
|
import net.corda.tools.shell.ShellConfiguration
|
||||||
import net.corda.tools.shell.ShellConfiguration.Companion.COMMANDS_DIR
|
import net.corda.tools.shell.ShellConfiguration.Companion.COMMANDS_DIR
|
||||||
import net.corda.tools.shell.ShellConfiguration.Companion.CORDAPPS_DIR
|
import net.corda.tools.shell.ShellConfiguration.Companion.CORDAPPS_DIR
|
||||||
import net.corda.tools.shell.ShellConfiguration.Companion.SSHD_HOSTKEY_DIR
|
import net.corda.tools.shell.ShellConfiguration.Companion.SSHD_HOSTKEY_DIR
|
||||||
import net.corda.tools.shell.ShellConfiguration.Companion.SSH_PORT
|
|
||||||
import net.corda.tools.shell.ShellSslOptions
|
|
||||||
|
|
||||||
//re-packs data to Shell specific classes
|
//re-packs data to Shell specific classes
|
||||||
fun NodeConfiguration.toShellConfig(): ShellConfiguration {
|
fun NodeConfiguration.toShellConfig() = ShellConfiguration(
|
||||||
|
|
||||||
val sslConfiguration = if (this.rpcOptions.useSsl) {
|
|
||||||
with(this.rpcOptions.sslConfig) {
|
|
||||||
ShellSslOptions(sslKeystore,
|
|
||||||
keyStorePassword,
|
|
||||||
trustStoreFile,
|
|
||||||
trustStorePassword,
|
|
||||||
crlCheckSoftFail)
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
null
|
|
||||||
}
|
|
||||||
val localShellUser: User = localShellUser()
|
|
||||||
return ShellConfiguration(
|
|
||||||
commandsDirectory = this.baseDirectory / COMMANDS_DIR,
|
commandsDirectory = this.baseDirectory / COMMANDS_DIR,
|
||||||
cordappsDirectory = this.baseDirectory.toString() / CORDAPPS_DIR,
|
cordappsDirectory = this.baseDirectory.toString() / CORDAPPS_DIR,
|
||||||
user = localShellUser.username,
|
user = INTERNAL_SHELL_USER,
|
||||||
password = localShellUser.password,
|
password = INTERNAL_SHELL_USER,
|
||||||
hostAndPort = this.rpcOptions.address ?: NetworkHostAndPort("localhost", SSH_PORT),
|
hostAndPort = this.rpcOptions.adminAddress,
|
||||||
ssl = sslConfiguration,
|
nodeSslConfig = this,
|
||||||
sshdPort = this.sshd?.port,
|
sshdPort = this.sshd?.port,
|
||||||
sshHostKeyDirectory = this.baseDirectory / SSHD_HOSTKEY_DIR,
|
sshHostKeyDirectory = this.baseDirectory / SSHD_HOSTKEY_DIR,
|
||||||
noLocalShell = this.noLocalShell)
|
noLocalShell = this.noLocalShell)
|
||||||
}
|
|
||||||
|
|
||||||
fun localShellUser() = User("shell", "shell", setOf(Permissions.all()))
|
|
||||||
|
@ -2,59 +2,36 @@ package net.corda.node.services.messaging
|
|||||||
|
|
||||||
import net.corda.core.internal.ThreadBox
|
import net.corda.core.internal.ThreadBox
|
||||||
import net.corda.core.internal.div
|
import net.corda.core.internal.div
|
||||||
import net.corda.core.internal.noneOrSingle
|
|
||||||
import net.corda.core.internal.uncheckedCast
|
|
||||||
import net.corda.core.serialization.SingletonSerializeAsToken
|
import net.corda.core.serialization.SingletonSerializeAsToken
|
||||||
import net.corda.core.utilities.NetworkHostAndPort
|
import net.corda.core.utilities.NetworkHostAndPort
|
||||||
import net.corda.core.utilities.contextLogger
|
import net.corda.core.utilities.contextLogger
|
||||||
import net.corda.core.utilities.debug
|
import net.corda.core.utilities.debug
|
||||||
import net.corda.node.internal.artemis.ArtemisBroker
|
import net.corda.node.internal.artemis.*
|
||||||
import net.corda.node.internal.artemis.BrokerAddresses
|
import net.corda.node.internal.artemis.BrokerJaasLoginModule.Companion.NODE_P2P_ROLE
|
||||||
import net.corda.node.internal.artemis.CertificateChainCheckPolicy
|
import net.corda.node.internal.artemis.BrokerJaasLoginModule.Companion.PEER_ROLE
|
||||||
import net.corda.node.internal.artemis.SecureArtemisConfiguration
|
|
||||||
import net.corda.node.services.config.NodeConfiguration
|
import net.corda.node.services.config.NodeConfiguration
|
||||||
import net.corda.node.services.messaging.NodeLoginModule.Companion.NODE_ROLE
|
import net.corda.nodeapi.ArtemisTcpTransport.Companion.p2pAcceptorTcpTransport
|
||||||
import net.corda.node.services.messaging.NodeLoginModule.Companion.PEER_ROLE
|
|
||||||
import net.corda.node.services.messaging.NodeLoginModule.Companion.VERIFIER_ROLE
|
|
||||||
import net.corda.nodeapi.ArtemisTcpTransport
|
|
||||||
import net.corda.nodeapi.ConnectionDirection
|
|
||||||
import net.corda.nodeapi.VerifierApi
|
|
||||||
import net.corda.nodeapi.internal.AmqpMessageSizeChecksInterceptor
|
import net.corda.nodeapi.internal.AmqpMessageSizeChecksInterceptor
|
||||||
import net.corda.nodeapi.internal.ArtemisMessageSizeChecksInterceptor
|
import net.corda.nodeapi.internal.ArtemisMessageSizeChecksInterceptor
|
||||||
import net.corda.nodeapi.internal.ArtemisMessagingComponent.Companion.INTERNAL_PREFIX
|
import net.corda.nodeapi.internal.ArtemisMessagingComponent.Companion.INTERNAL_PREFIX
|
||||||
import net.corda.nodeapi.internal.ArtemisMessagingComponent.Companion.JOURNAL_HEADER_SIZE
|
import net.corda.nodeapi.internal.ArtemisMessagingComponent.Companion.JOURNAL_HEADER_SIZE
|
||||||
import net.corda.nodeapi.internal.ArtemisMessagingComponent.Companion.NODE_USER
|
|
||||||
import net.corda.nodeapi.internal.ArtemisMessagingComponent.Companion.NOTIFICATIONS_ADDRESS
|
import net.corda.nodeapi.internal.ArtemisMessagingComponent.Companion.NOTIFICATIONS_ADDRESS
|
||||||
import net.corda.nodeapi.internal.ArtemisMessagingComponent.Companion.P2P_PREFIX
|
import net.corda.nodeapi.internal.ArtemisMessagingComponent.Companion.P2P_PREFIX
|
||||||
import net.corda.nodeapi.internal.ArtemisMessagingComponent.Companion.PEER_USER
|
|
||||||
import net.corda.nodeapi.internal.requireOnDefaultFileSystem
|
import net.corda.nodeapi.internal.requireOnDefaultFileSystem
|
||||||
import org.apache.activemq.artemis.api.core.SimpleString
|
import org.apache.activemq.artemis.api.core.SimpleString
|
||||||
import org.apache.activemq.artemis.api.core.management.ActiveMQServerControl
|
import org.apache.activemq.artemis.api.core.management.ActiveMQServerControl
|
||||||
import org.apache.activemq.artemis.core.config.Configuration
|
import org.apache.activemq.artemis.core.config.Configuration
|
||||||
import org.apache.activemq.artemis.core.config.impl.ConfigurationImpl
|
import org.apache.activemq.artemis.core.config.impl.ConfigurationImpl
|
||||||
import org.apache.activemq.artemis.core.config.impl.SecurityConfiguration
|
import org.apache.activemq.artemis.core.config.impl.SecurityConfiguration
|
||||||
import org.apache.activemq.artemis.core.remoting.impl.netty.NettyAcceptorFactory
|
|
||||||
import org.apache.activemq.artemis.core.security.Role
|
import org.apache.activemq.artemis.core.security.Role
|
||||||
import org.apache.activemq.artemis.core.server.ActiveMQServer
|
import org.apache.activemq.artemis.core.server.ActiveMQServer
|
||||||
import org.apache.activemq.artemis.core.server.impl.ActiveMQServerImpl
|
import org.apache.activemq.artemis.core.server.impl.ActiveMQServerImpl
|
||||||
import org.apache.activemq.artemis.spi.core.security.ActiveMQJAASSecurityManager
|
import org.apache.activemq.artemis.spi.core.security.ActiveMQJAASSecurityManager
|
||||||
import org.apache.activemq.artemis.spi.core.security.jaas.CertificateCallback
|
|
||||||
import org.apache.activemq.artemis.spi.core.security.jaas.RolePrincipal
|
|
||||||
import org.apache.activemq.artemis.spi.core.security.jaas.UserPrincipal
|
|
||||||
import java.io.IOException
|
import java.io.IOException
|
||||||
import java.security.KeyStoreException
|
import java.security.KeyStoreException
|
||||||
import java.security.Principal
|
|
||||||
import java.util.*
|
|
||||||
import javax.annotation.concurrent.ThreadSafe
|
import javax.annotation.concurrent.ThreadSafe
|
||||||
import javax.security.auth.Subject
|
|
||||||
import javax.security.auth.callback.CallbackHandler
|
|
||||||
import javax.security.auth.callback.NameCallback
|
|
||||||
import javax.security.auth.callback.UnsupportedCallbackException
|
|
||||||
import javax.security.auth.login.AppConfigurationEntry
|
import javax.security.auth.login.AppConfigurationEntry
|
||||||
import javax.security.auth.login.AppConfigurationEntry.LoginModuleControlFlag.REQUIRED
|
import javax.security.auth.login.AppConfigurationEntry.LoginModuleControlFlag.REQUIRED
|
||||||
import javax.security.auth.login.FailedLoginException
|
|
||||||
import javax.security.auth.login.LoginException
|
|
||||||
import javax.security.auth.spi.LoginModule
|
|
||||||
|
|
||||||
// TODO: Verify that nobody can connect to us and fiddle with our config over the socket due to the secman.
|
// TODO: Verify that nobody can connect to us and fiddle with our config over the socket due to the secman.
|
||||||
// TODO: Implement a discovery engine that can trigger builds of new connections when another node registers? (later)
|
// TODO: Implement a discovery engine that can trigger builds of new connections when another node registers? (later)
|
||||||
@ -133,11 +110,7 @@ class ArtemisMessagingServer(private val config: NodeConfiguration,
|
|||||||
bindingsDirectory = (artemisDir / "bindings").toString()
|
bindingsDirectory = (artemisDir / "bindings").toString()
|
||||||
journalDirectory = (artemisDir / "journal").toString()
|
journalDirectory = (artemisDir / "journal").toString()
|
||||||
largeMessagesDirectory = (artemisDir / "large-messages").toString()
|
largeMessagesDirectory = (artemisDir / "large-messages").toString()
|
||||||
val connectionDirection = ConnectionDirection.Inbound(
|
acceptorConfigurations = mutableSetOf(p2pAcceptorTcpTransport(NetworkHostAndPort(messagingServerAddress.host, messagingServerAddress.port), config))
|
||||||
acceptorFactoryClassName = NettyAcceptorFactory::class.java.name
|
|
||||||
)
|
|
||||||
val acceptors = mutableSetOf(createTcpTransport(connectionDirection, messagingServerAddress.host, messagingServerAddress.port))
|
|
||||||
acceptorConfigurations = acceptors
|
|
||||||
// Enable built in message deduplication. Note we still have to do our own as the delayed commits
|
// Enable built in message deduplication. Note we still have to do our own as the delayed commits
|
||||||
// and our own definition of commit mean that the built in deduplication cannot remove all duplicates.
|
// and our own definition of commit mean that the built in deduplication cannot remove all duplicates.
|
||||||
idCacheSize = 2000 // Artemis Default duplicate cache size i.e. a guess
|
idCacheSize = 2000 // Artemis Default duplicate cache size i.e. a guess
|
||||||
@ -164,11 +137,9 @@ class ArtemisMessagingServer(private val config: NodeConfiguration,
|
|||||||
* 4. Verifiers. These are given read access to the verification request queue and write access to the response queue.
|
* 4. Verifiers. These are given read access to the verification request queue and write access to the response queue.
|
||||||
*/
|
*/
|
||||||
private fun ConfigurationImpl.configureAddressSecurity(): Configuration {
|
private fun ConfigurationImpl.configureAddressSecurity(): Configuration {
|
||||||
val nodeInternalRole = Role(NODE_ROLE, true, true, true, true, true, true, true, true, true, true)
|
val nodeInternalRole = Role(NODE_P2P_ROLE, true, true, true, true, true, true, true, true, true, true)
|
||||||
securityRoles["$INTERNAL_PREFIX#"] = setOf(nodeInternalRole) // Do not add any other roles here as it's only for the node
|
securityRoles["$INTERNAL_PREFIX#"] = setOf(nodeInternalRole) // Do not add any other roles here as it's only for the node
|
||||||
securityRoles["$P2P_PREFIX#"] = setOf(nodeInternalRole, restrictedRole(PEER_ROLE, send = true))
|
securityRoles["$P2P_PREFIX#"] = setOf(nodeInternalRole, restrictedRole(PEER_ROLE, send = true))
|
||||||
securityRoles[VerifierApi.VERIFICATION_REQUESTS_QUEUE_NAME] = setOf(nodeInternalRole, restrictedRole(VERIFIER_ROLE, consume = true))
|
|
||||||
securityRoles["${VerifierApi.VERIFICATION_RESPONSES_QUEUE_NAME_PREFIX}.#"] = setOf(nodeInternalRole, restrictedRole(VERIFIER_ROLE, send = true))
|
|
||||||
return this
|
return this
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -184,158 +155,16 @@ class ArtemisMessagingServer(private val config: NodeConfiguration,
|
|||||||
val keyStore = config.loadSslKeyStore().internal
|
val keyStore = config.loadSslKeyStore().internal
|
||||||
val trustStore = config.loadTrustStore().internal
|
val trustStore = config.loadTrustStore().internal
|
||||||
|
|
||||||
val defaultCertPolicies = mapOf(
|
|
||||||
PEER_ROLE to CertificateChainCheckPolicy.RootMustMatch,
|
|
||||||
NODE_ROLE to CertificateChainCheckPolicy.LeafMustMatch,
|
|
||||||
VERIFIER_ROLE to CertificateChainCheckPolicy.RootMustMatch
|
|
||||||
)
|
|
||||||
val certChecks = defaultCertPolicies.mapValues { (role, defaultPolicy) ->
|
|
||||||
val configPolicy = config.certificateChainCheckPolicies.noneOrSingle { it.role == role }?.certificateChainCheckPolicy
|
|
||||||
(configPolicy ?: defaultPolicy).createCheck(keyStore, trustStore)
|
|
||||||
}
|
|
||||||
val securityConfig = object : SecurityConfiguration() {
|
val securityConfig = object : SecurityConfiguration() {
|
||||||
// Override to make it work with our login module
|
// Override to make it work with our login module
|
||||||
override fun getAppConfigurationEntry(name: String): Array<AppConfigurationEntry> {
|
override fun getAppConfigurationEntry(name: String): Array<AppConfigurationEntry> {
|
||||||
val options = mapOf(NodeLoginModule.CERT_CHAIN_CHECKS_OPTION_NAME to certChecks)
|
val options = mapOf(
|
||||||
|
BrokerJaasLoginModule.P2P_SECURITY_CONFIG to P2PJaasConfig(keyStore, trustStore),
|
||||||
|
BrokerJaasLoginModule.NODE_SECURITY_CONFIG to NodeJaasConfig(keyStore, trustStore)
|
||||||
|
)
|
||||||
return arrayOf(AppConfigurationEntry(name, REQUIRED, options))
|
return arrayOf(AppConfigurationEntry(name, REQUIRED, options))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return ActiveMQJAASSecurityManager(NodeLoginModule::class.java.name, securityConfig)
|
return ActiveMQJAASSecurityManager(BrokerJaasLoginModule::class.java.name, securityConfig)
|
||||||
}
|
|
||||||
|
|
||||||
private fun createTcpTransport(connectionDirection: ConnectionDirection, host: String, port: Int, enableSSL: Boolean = true) =
|
|
||||||
ArtemisTcpTransport.tcpTransport(connectionDirection, NetworkHostAndPort(host, port), config, enableSSL = enableSSL)
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Clients must connect to us with a username and password and must use TLS. If a someone connects with
|
|
||||||
* [ArtemisMessagingComponent.NODE_USER] then we confirm it's just us as the node by checking their TLS certificate
|
|
||||||
* is the same as our one in our key store. Then they're given full access to all valid queues. If they connect with
|
|
||||||
* [ArtemisMessagingComponent.PEER_USER] then we confirm they belong on our P2P network by checking their root CA is
|
|
||||||
* the same as our root CA. If that's the case the only access they're given is the ablility send to our P2P address.
|
|
||||||
* In both cases the messages these authenticated nodes send to us are tagged with their subject DN and we assume
|
|
||||||
* the CN within that is their legal name.
|
|
||||||
* Otherwise if the username is neither of the above we assume it's an RPC user and authenticate against our list of
|
|
||||||
* valid RPC users. RPC clients are given permission to perform RPC and nothing else.
|
|
||||||
*/
|
|
||||||
class NodeLoginModule : LoginModule {
|
|
||||||
companion object {
|
|
||||||
// Include forbidden username character to prevent name clash with any RPC usernames
|
|
||||||
const val PEER_ROLE = "SystemRoles/Peer"
|
|
||||||
const val NODE_ROLE = "SystemRoles/Node"
|
|
||||||
const val VERIFIER_ROLE = "SystemRoles/Verifier"
|
|
||||||
|
|
||||||
const val CERT_CHAIN_CHECKS_OPTION_NAME = "CertChainChecks"
|
|
||||||
private val log = contextLogger()
|
|
||||||
}
|
|
||||||
|
|
||||||
private var loginSucceeded: Boolean = false
|
|
||||||
private lateinit var subject: Subject
|
|
||||||
private lateinit var callbackHandler: CallbackHandler
|
|
||||||
private lateinit var peerCertCheck: CertificateChainCheckPolicy.Check
|
|
||||||
private lateinit var nodeCertCheck: CertificateChainCheckPolicy.Check
|
|
||||||
private lateinit var verifierCertCheck: CertificateChainCheckPolicy.Check
|
|
||||||
private val principals = ArrayList<Principal>()
|
|
||||||
|
|
||||||
override fun initialize(subject: Subject, callbackHandler: CallbackHandler, sharedState: Map<String, *>, options: Map<String, *>) {
|
|
||||||
this.subject = subject
|
|
||||||
this.callbackHandler = callbackHandler
|
|
||||||
val certChainChecks: Map<String, CertificateChainCheckPolicy.Check> = uncheckedCast(options[CERT_CHAIN_CHECKS_OPTION_NAME])
|
|
||||||
peerCertCheck = certChainChecks[PEER_ROLE]!!
|
|
||||||
nodeCertCheck = certChainChecks[NODE_ROLE]!!
|
|
||||||
verifierCertCheck = certChainChecks[VERIFIER_ROLE]!!
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun login(): Boolean {
|
|
||||||
val nameCallback = NameCallback("Username: ")
|
|
||||||
val certificateCallback = CertificateCallback()
|
|
||||||
try {
|
|
||||||
callbackHandler.handle(arrayOf(nameCallback, certificateCallback))
|
|
||||||
} catch (e: IOException) {
|
|
||||||
throw LoginException(e.message)
|
|
||||||
} catch (e: UnsupportedCallbackException) {
|
|
||||||
throw LoginException("${e.message} not available to obtain information from user")
|
|
||||||
}
|
|
||||||
|
|
||||||
val username = nameCallback.name ?: throw FailedLoginException("Username not provided")
|
|
||||||
val certificates = certificateCallback.certificates
|
|
||||||
|
|
||||||
log.debug { "Processing login for $username" }
|
|
||||||
|
|
||||||
try {
|
|
||||||
val validatedUser = when (determineUserRole(certificates, username)) {
|
|
||||||
PEER_ROLE -> authenticatePeer(certificates)
|
|
||||||
NODE_ROLE -> authenticateNode(certificates)
|
|
||||||
VERIFIER_ROLE -> authenticateVerifier(certificates)
|
|
||||||
else -> throw FailedLoginException("Peer does not belong on our network")
|
|
||||||
}
|
|
||||||
principals += UserPrincipal(validatedUser)
|
|
||||||
|
|
||||||
loginSucceeded = true
|
|
||||||
return loginSucceeded
|
|
||||||
} catch (exception: FailedLoginException) {
|
|
||||||
log.warn("$exception")
|
|
||||||
throw exception
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun authenticateNode(certificates: Array<javax.security.cert.X509Certificate>): String {
|
|
||||||
nodeCertCheck.checkCertificateChain(certificates)
|
|
||||||
principals += RolePrincipal(NODE_ROLE)
|
|
||||||
return certificates.first().subjectDN.name
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun authenticateVerifier(certificates: Array<javax.security.cert.X509Certificate>): String {
|
|
||||||
verifierCertCheck.checkCertificateChain(certificates)
|
|
||||||
principals += RolePrincipal(VERIFIER_ROLE)
|
|
||||||
return certificates.first().subjectDN.name
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun authenticatePeer(certificates: Array<javax.security.cert.X509Certificate>): String {
|
|
||||||
peerCertCheck.checkCertificateChain(certificates)
|
|
||||||
principals += RolePrincipal(PEER_ROLE)
|
|
||||||
return certificates.first().subjectDN.name
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun determineUserRole(certificates: Array<javax.security.cert.X509Certificate>?, username: String): String? {
|
|
||||||
fun requireTls() = require(certificates != null) { "No TLS?" }
|
|
||||||
return when (username) {
|
|
||||||
PEER_USER -> {
|
|
||||||
requireTls()
|
|
||||||
PEER_ROLE
|
|
||||||
}
|
|
||||||
NODE_USER -> {
|
|
||||||
requireTls()
|
|
||||||
NODE_ROLE
|
|
||||||
}
|
|
||||||
VerifierApi.VERIFIER_USERNAME -> {
|
|
||||||
requireTls()
|
|
||||||
VERIFIER_ROLE
|
|
||||||
}
|
|
||||||
else -> null
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun commit(): Boolean {
|
|
||||||
val result = loginSucceeded
|
|
||||||
if (result) {
|
|
||||||
subject.principals.addAll(principals)
|
|
||||||
}
|
|
||||||
clear()
|
|
||||||
return result
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun abort(): Boolean {
|
|
||||||
clear()
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun logout(): Boolean {
|
|
||||||
subject.principals.removeAll(principals)
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun clear() {
|
|
||||||
loginSucceeded = false
|
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -0,0 +1,48 @@
|
|||||||
|
package net.corda.node.services.messaging
|
||||||
|
|
||||||
|
import net.corda.core.identity.CordaX500Name
|
||||||
|
import net.corda.core.messaging.RPCOps
|
||||||
|
import net.corda.core.serialization.SingletonSerializeAsToken
|
||||||
|
import net.corda.core.serialization.internal.nodeSerializationEnv
|
||||||
|
import net.corda.core.utilities.NetworkHostAndPort
|
||||||
|
import net.corda.node.internal.security.RPCSecurityManager
|
||||||
|
import net.corda.nodeapi.ArtemisTcpTransport
|
||||||
|
import net.corda.nodeapi.internal.ArtemisMessagingComponent.Companion.NODE_RPC_USER
|
||||||
|
import net.corda.nodeapi.internal.config.SSLConfiguration
|
||||||
|
import org.apache.activemq.artemis.api.core.client.ActiveMQClient
|
||||||
|
import org.apache.activemq.artemis.api.core.client.ServerLocator
|
||||||
|
import org.apache.activemq.artemis.api.core.management.ActiveMQServerControl
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Used by the Node to communicate with the RPC broker.
|
||||||
|
*/
|
||||||
|
class InternalRPCMessagingClient(val sslConfig: SSLConfiguration, val serverAddress: NetworkHostAndPort, val maxMessageSize: Int, val nodeName: CordaX500Name) : SingletonSerializeAsToken(), AutoCloseable {
|
||||||
|
private var locator: ServerLocator? = null
|
||||||
|
private var rpcServer: RPCServer? = null
|
||||||
|
|
||||||
|
fun init(rpcOps: RPCOps, securityManager: RPCSecurityManager) = synchronized(this) {
|
||||||
|
|
||||||
|
val tcpTransport = ArtemisTcpTransport.rpcInternalClientTcpTransport(serverAddress, sslConfig)
|
||||||
|
locator = ActiveMQClient.createServerLocatorWithoutHA(tcpTransport).apply {
|
||||||
|
// Never time out on our loopback Artemis connections. If we switch back to using the InVM transport this
|
||||||
|
// would be the default and the two lines below can be deleted.
|
||||||
|
connectionTTL = -1
|
||||||
|
clientFailureCheckPeriod = -1
|
||||||
|
minLargeMessageSize = maxMessageSize
|
||||||
|
isUseGlobalPools = nodeSerializationEnv != null
|
||||||
|
}
|
||||||
|
|
||||||
|
rpcServer = RPCServer(rpcOps, NODE_RPC_USER, NODE_RPC_USER, locator!!, securityManager, nodeName)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun start(serverControl: ActiveMQServerControl) = synchronized(this) {
|
||||||
|
rpcServer!!.start(serverControl)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun stop(): Unit = synchronized(this) {
|
||||||
|
rpcServer?.close()
|
||||||
|
locator?.close()
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun close() = stop()
|
||||||
|
}
|
@ -29,8 +29,7 @@ import net.corda.node.services.config.NodeConfiguration
|
|||||||
import net.corda.node.services.statemachine.DeduplicationId
|
import net.corda.node.services.statemachine.DeduplicationId
|
||||||
import net.corda.node.utilities.AffinityExecutor
|
import net.corda.node.utilities.AffinityExecutor
|
||||||
import net.corda.node.utilities.PersistentMap
|
import net.corda.node.utilities.PersistentMap
|
||||||
import net.corda.nodeapi.ArtemisTcpTransport
|
import net.corda.nodeapi.ArtemisTcpTransport.Companion.p2pConnectorTcpTransport
|
||||||
import net.corda.nodeapi.ConnectionDirection
|
|
||||||
import net.corda.nodeapi.internal.ArtemisMessagingComponent
|
import net.corda.nodeapi.internal.ArtemisMessagingComponent
|
||||||
import net.corda.nodeapi.internal.ArtemisMessagingComponent.ArtemisAddress
|
import net.corda.nodeapi.internal.ArtemisMessagingComponent.ArtemisAddress
|
||||||
import net.corda.nodeapi.internal.ArtemisMessagingComponent.Companion.BRIDGE_CONTROL
|
import net.corda.nodeapi.internal.ArtemisMessagingComponent.Companion.BRIDGE_CONTROL
|
||||||
@ -200,7 +199,7 @@ class P2PMessagingClient(val config: NodeConfiguration,
|
|||||||
started = true
|
started = true
|
||||||
log.info("Connecting to message broker: $serverAddress")
|
log.info("Connecting to message broker: $serverAddress")
|
||||||
// TODO Add broker CN to config for host verification in case the embedded broker isn't used
|
// TODO Add broker CN to config for host verification in case the embedded broker isn't used
|
||||||
val tcpTransport = ArtemisTcpTransport.tcpTransport(ConnectionDirection.Outbound(), serverAddress, config)
|
val tcpTransport = p2pConnectorTcpTransport(serverAddress, config)
|
||||||
locator = ActiveMQClient.createServerLocatorWithoutHA(tcpTransport).apply {
|
locator = ActiveMQClient.createServerLocatorWithoutHA(tcpTransport).apply {
|
||||||
// Never time out on our loopback Artemis connections. If we switch back to using the InVM transport this
|
// Never time out on our loopback Artemis connections. If we switch back to using the InVM transport this
|
||||||
// would be the default and the two lines below can be deleted.
|
// would be the default and the two lines below can be deleted.
|
||||||
@ -214,7 +213,7 @@ class P2PMessagingClient(val config: NodeConfiguration,
|
|||||||
// using our TLS certificate.
|
// using our TLS certificate.
|
||||||
// Note that the acknowledgement of messages is not flushed to the Artermis journal until the default buffer
|
// Note that the acknowledgement of messages is not flushed to the Artermis journal until the default buffer
|
||||||
// size of 1MB is acknowledged.
|
// size of 1MB is acknowledged.
|
||||||
val createNewSession = { sessionFactory!!.createSession(ArtemisMessagingComponent.NODE_USER, ArtemisMessagingComponent.NODE_USER, false, true, true, locator!!.isPreAcknowledge, ActiveMQClient.DEFAULT_ACK_BATCH_SIZE) }
|
val createNewSession = { sessionFactory!!.createSession(ArtemisMessagingComponent.NODE_P2P_USER, ArtemisMessagingComponent.NODE_P2P_USER, false, true, true, locator!!.isPreAcknowledge, ActiveMQClient.DEFAULT_ACK_BATCH_SIZE) }
|
||||||
|
|
||||||
producerSession = createNewSession()
|
producerSession = createNewSession()
|
||||||
bridgeSession = createNewSession()
|
bridgeSession = createNewSession()
|
||||||
|
@ -1,34 +0,0 @@
|
|||||||
package net.corda.node.services.messaging
|
|
||||||
|
|
||||||
import net.corda.core.identity.CordaX500Name
|
|
||||||
import net.corda.core.messaging.RPCOps
|
|
||||||
import net.corda.core.serialization.SingletonSerializeAsToken
|
|
||||||
import net.corda.core.utilities.NetworkHostAndPort
|
|
||||||
import net.corda.node.internal.security.RPCSecurityManager
|
|
||||||
import net.corda.nodeapi.internal.ArtemisMessagingClient
|
|
||||||
import net.corda.nodeapi.internal.ArtemisMessagingComponent.Companion.NODE_USER
|
|
||||||
import net.corda.nodeapi.internal.config.SSLConfiguration
|
|
||||||
import net.corda.nodeapi.internal.crypto.X509Utilities
|
|
||||||
import org.apache.activemq.artemis.api.core.management.ActiveMQServerControl
|
|
||||||
|
|
||||||
class RPCMessagingClient(private val config: SSLConfiguration, serverAddress: NetworkHostAndPort, maxMessageSize: Int) : SingletonSerializeAsToken(), AutoCloseable {
|
|
||||||
private val artemis = ArtemisMessagingClient(config, serverAddress, maxMessageSize)
|
|
||||||
private var rpcServer: RPCServer? = null
|
|
||||||
|
|
||||||
fun start(rpcOps: RPCOps, securityManager: RPCSecurityManager) = synchronized(this) {
|
|
||||||
val locator = artemis.start().sessionFactory.serverLocator
|
|
||||||
val myCert = config.loadSslKeyStore().getCertificate(X509Utilities.CORDA_CLIENT_TLS)
|
|
||||||
rpcServer = RPCServer(rpcOps, NODE_USER, NODE_USER, locator, securityManager, CordaX500Name.build(myCert.subjectX500Principal))
|
|
||||||
}
|
|
||||||
|
|
||||||
fun start2(serverControl: ActiveMQServerControl) = synchronized(this) {
|
|
||||||
rpcServer!!.start(serverControl)
|
|
||||||
}
|
|
||||||
|
|
||||||
fun stop() = synchronized(this) {
|
|
||||||
rpcServer?.close()
|
|
||||||
artemis.stop()
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun close() = stop()
|
|
||||||
}
|
|
@ -1,47 +1,44 @@
|
|||||||
package net.corda.node.services.rpc
|
package net.corda.node.services.rpc
|
||||||
|
|
||||||
import net.corda.core.internal.noneOrSingle
|
|
||||||
import net.corda.core.utilities.NetworkHostAndPort
|
import net.corda.core.utilities.NetworkHostAndPort
|
||||||
import net.corda.core.utilities.loggerFor
|
import net.corda.core.utilities.loggerFor
|
||||||
import net.corda.node.internal.artemis.ArtemisBroker
|
import net.corda.node.internal.artemis.*
|
||||||
import net.corda.node.internal.artemis.BrokerAddresses
|
import net.corda.node.internal.artemis.BrokerJaasLoginModule.Companion.NODE_SECURITY_CONFIG
|
||||||
|
import net.corda.node.internal.artemis.BrokerJaasLoginModule.Companion.RPC_SECURITY_CONFIG
|
||||||
import net.corda.node.internal.security.RPCSecurityManager
|
import net.corda.node.internal.security.RPCSecurityManager
|
||||||
import net.corda.node.services.config.CertChainPolicyConfig
|
import net.corda.nodeapi.BrokerRpcSslOptions
|
||||||
import net.corda.node.internal.artemis.CertificateChainCheckPolicy
|
|
||||||
import net.corda.nodeapi.internal.config.SSLConfiguration
|
import net.corda.nodeapi.internal.config.SSLConfiguration
|
||||||
import net.corda.nodeapi.internal.crypto.loadKeyStore
|
|
||||||
import org.apache.activemq.artemis.api.core.management.ActiveMQServerControl
|
import org.apache.activemq.artemis.api.core.management.ActiveMQServerControl
|
||||||
import org.apache.activemq.artemis.core.config.impl.SecurityConfiguration
|
import org.apache.activemq.artemis.core.config.impl.SecurityConfiguration
|
||||||
import org.apache.activemq.artemis.core.server.ActiveMQServer
|
import org.apache.activemq.artemis.core.server.ActiveMQServer
|
||||||
import org.apache.activemq.artemis.core.server.impl.ActiveMQServerImpl
|
import org.apache.activemq.artemis.core.server.impl.ActiveMQServerImpl
|
||||||
import org.apache.activemq.artemis.spi.core.security.ActiveMQJAASSecurityManager
|
import org.apache.activemq.artemis.spi.core.security.ActiveMQJAASSecurityManager
|
||||||
import rx.Observable
|
|
||||||
import java.io.IOException
|
import java.io.IOException
|
||||||
import java.nio.file.Path
|
import java.nio.file.Path
|
||||||
import java.security.KeyStoreException
|
import java.security.KeyStoreException
|
||||||
import java.util.concurrent.CompletableFuture
|
|
||||||
import javax.security.auth.login.AppConfigurationEntry
|
import javax.security.auth.login.AppConfigurationEntry
|
||||||
|
|
||||||
internal class ArtemisRpcBroker internal constructor(
|
internal class ArtemisRpcBroker internal constructor(
|
||||||
address: NetworkHostAndPort,
|
address: NetworkHostAndPort,
|
||||||
private val adminAddressOptional: NetworkHostAndPort?,
|
private val adminAddressOptional: NetworkHostAndPort?,
|
||||||
private val sslOptions: SSLConfiguration,
|
private val sslOptions: BrokerRpcSslOptions?,
|
||||||
private val useSsl: Boolean,
|
private val useSsl: Boolean,
|
||||||
private val securityManager: RPCSecurityManager,
|
private val securityManager: RPCSecurityManager,
|
||||||
private val certificateChainCheckPolicies: List<CertChainPolicyConfig>,
|
|
||||||
private val maxMessageSize: Int,
|
private val maxMessageSize: Int,
|
||||||
private val jmxEnabled: Boolean = false,
|
private val jmxEnabled: Boolean = false,
|
||||||
private val baseDirectory: Path) : ArtemisBroker {
|
private val baseDirectory: Path,
|
||||||
|
private val nodeConfiguration: SSLConfiguration,
|
||||||
|
private val shouldStartLocalShell: Boolean) : ArtemisBroker {
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
private val logger = loggerFor<ArtemisRpcBroker>()
|
private val logger = loggerFor<ArtemisRpcBroker>()
|
||||||
|
|
||||||
fun withSsl(address: NetworkHostAndPort, sslOptions: SSLConfiguration, securityManager: RPCSecurityManager, certificateChainCheckPolicies: List<CertChainPolicyConfig>, maxMessageSize: Int, jmxEnabled: Boolean, baseDirectory: Path): ArtemisBroker {
|
fun withSsl(configuration: SSLConfiguration, address: NetworkHostAndPort, adminAddress: NetworkHostAndPort, sslOptions: BrokerRpcSslOptions, securityManager: RPCSecurityManager, maxMessageSize: Int, jmxEnabled: Boolean, baseDirectory: Path, shouldStartLocalShell: Boolean): ArtemisBroker {
|
||||||
return ArtemisRpcBroker(address, null, sslOptions, true, securityManager, certificateChainCheckPolicies, maxMessageSize, jmxEnabled, baseDirectory)
|
return ArtemisRpcBroker(address, adminAddress, sslOptions, true, securityManager, maxMessageSize, jmxEnabled, baseDirectory, configuration, shouldStartLocalShell)
|
||||||
}
|
}
|
||||||
|
|
||||||
fun withoutSsl(address: NetworkHostAndPort, adminAddress: NetworkHostAndPort, sslOptions: SSLConfiguration, securityManager: RPCSecurityManager, certificateChainCheckPolicies: List<CertChainPolicyConfig>, maxMessageSize: Int, jmxEnabled: Boolean, baseDirectory: Path): ArtemisBroker {
|
fun withoutSsl(configuration: SSLConfiguration, address: NetworkHostAndPort, adminAddress: NetworkHostAndPort, securityManager: RPCSecurityManager, maxMessageSize: Int, jmxEnabled: Boolean, baseDirectory: Path, shouldStartLocalShell: Boolean): ArtemisBroker {
|
||||||
return ArtemisRpcBroker(address, adminAddress, sslOptions, false, securityManager, certificateChainCheckPolicies, maxMessageSize, jmxEnabled, baseDirectory)
|
return ArtemisRpcBroker(address, adminAddress, null, false, securityManager, maxMessageSize, jmxEnabled, baseDirectory, configuration, shouldStartLocalShell)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -66,8 +63,8 @@ internal class ArtemisRpcBroker internal constructor(
|
|||||||
private val server = initialiseServer()
|
private val server = initialiseServer()
|
||||||
|
|
||||||
private fun initialiseServer(): ActiveMQServer {
|
private fun initialiseServer(): ActiveMQServer {
|
||||||
val serverConfiguration = RpcBrokerConfiguration(baseDirectory, maxMessageSize, jmxEnabled, addresses.primary, adminAddressOptional, sslOptions, useSsl)
|
val serverConfiguration = RpcBrokerConfiguration(baseDirectory, maxMessageSize, jmxEnabled, addresses.primary, adminAddressOptional, sslOptions, useSsl, nodeConfiguration, shouldStartLocalShell)
|
||||||
val serverSecurityManager = createArtemisSecurityManager(serverConfiguration.loginListener, sslOptions)
|
val serverSecurityManager = createArtemisSecurityManager(serverConfiguration.loginListener)
|
||||||
|
|
||||||
return ActiveMQServerImpl(serverConfiguration, serverSecurityManager).apply {
|
return ActiveMQServerImpl(serverConfiguration, serverSecurityManager).apply {
|
||||||
registerActivationFailureListener { exception -> throw exception }
|
registerActivationFailureListener { exception -> throw exception }
|
||||||
@ -76,33 +73,21 @@ internal class ArtemisRpcBroker internal constructor(
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Throws(IOException::class, KeyStoreException::class)
|
@Throws(IOException::class, KeyStoreException::class)
|
||||||
private fun createArtemisSecurityManager(loginListener: LoginListener, sslOptions: SSLConfiguration): ActiveMQJAASSecurityManager {
|
private fun createArtemisSecurityManager(loginListener: LoginListener): ActiveMQJAASSecurityManager {
|
||||||
val keyStore = loadKeyStore(sslOptions.sslKeystore, sslOptions.keyStorePassword)
|
val keyStore = nodeConfiguration.loadSslKeyStore().internal
|
||||||
val trustStore = loadKeyStore(sslOptions.trustStoreFile, sslOptions.trustStorePassword)
|
val trustStore = nodeConfiguration.loadTrustStore().internal
|
||||||
|
|
||||||
val defaultCertPolicies = mapOf(
|
|
||||||
NodeLoginModule.NODE_ROLE to CertificateChainCheckPolicy.LeafMustMatch,
|
|
||||||
NodeLoginModule.RPC_ROLE to CertificateChainCheckPolicy.Any
|
|
||||||
)
|
|
||||||
val certChecks = defaultCertPolicies.mapValues { (role, defaultPolicy) ->
|
|
||||||
val policy = certificateChainCheckPolicies.noneOrSingle { it.role == role }?.certificateChainCheckPolicy ?: defaultPolicy
|
|
||||||
policy.createCheck(keyStore, trustStore)
|
|
||||||
}
|
|
||||||
|
|
||||||
val securityConfig = object : SecurityConfiguration() {
|
val securityConfig = object : SecurityConfiguration() {
|
||||||
override fun getAppConfigurationEntry(name: String): Array<AppConfigurationEntry> {
|
override fun getAppConfigurationEntry(name: String): Array<AppConfigurationEntry> {
|
||||||
val options = mapOf(
|
val options = mapOf(
|
||||||
NodeLoginModule.LOGIN_LISTENER_ARG to loginListener,
|
RPC_SECURITY_CONFIG to RPCJaasConfig(securityManager, loginListener, useSsl),
|
||||||
NodeLoginModule.SECURITY_MANAGER_ARG to securityManager,
|
NODE_SECURITY_CONFIG to NodeJaasConfig(keyStore, trustStore)
|
||||||
NodeLoginModule.USE_SSL_ARG to useSsl,
|
)
|
||||||
NodeLoginModule.CERT_CHAIN_CHECKS_ARG to certChecks)
|
|
||||||
return arrayOf(AppConfigurationEntry(name, AppConfigurationEntry.LoginModuleControlFlag.REQUIRED, options))
|
return arrayOf(AppConfigurationEntry(name, AppConfigurationEntry.LoginModuleControlFlag.REQUIRED, options))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return ActiveMQJAASSecurityManager(NodeLoginModule::class.java.name, securityConfig)
|
return ActiveMQJAASSecurityManager(BrokerJaasLoginModule::class.java.name, securityConfig)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
typealias LoginListener = (String) -> Unit
|
typealias LoginListener = (String) -> Unit
|
||||||
|
|
||||||
private fun <RESULT> CompletableFuture<RESULT>.toObservable() = Observable.from(this)
|
|
@ -1,169 +0,0 @@
|
|||||||
package net.corda.node.services.rpc
|
|
||||||
|
|
||||||
import net.corda.core.internal.uncheckedCast
|
|
||||||
import net.corda.core.utilities.loggerFor
|
|
||||||
import net.corda.node.internal.security.Password
|
|
||||||
import net.corda.node.internal.security.RPCSecurityManager
|
|
||||||
import net.corda.node.internal.artemis.CertificateChainCheckPolicy
|
|
||||||
import net.corda.nodeapi.RPCApi
|
|
||||||
import net.corda.nodeapi.internal.ArtemisMessagingComponent
|
|
||||||
import net.corda.nodeapi.internal.ArtemisMessagingComponent.Companion.NODE_USER
|
|
||||||
import org.apache.activemq.artemis.spi.core.security.jaas.CertificateCallback
|
|
||||||
import org.apache.activemq.artemis.spi.core.security.jaas.RolePrincipal
|
|
||||||
import org.apache.activemq.artemis.spi.core.security.jaas.UserPrincipal
|
|
||||||
import java.io.IOException
|
|
||||||
import java.security.Principal
|
|
||||||
import java.util.*
|
|
||||||
import javax.security.auth.Subject
|
|
||||||
import javax.security.auth.callback.CallbackHandler
|
|
||||||
import javax.security.auth.callback.NameCallback
|
|
||||||
import javax.security.auth.callback.PasswordCallback
|
|
||||||
import javax.security.auth.callback.UnsupportedCallbackException
|
|
||||||
import javax.security.auth.login.FailedLoginException
|
|
||||||
import javax.security.auth.login.LoginException
|
|
||||||
import javax.security.auth.spi.LoginModule
|
|
||||||
import javax.security.cert.X509Certificate
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Clients must connect to us with a username and password and must use TLS. If a someone connects with
|
|
||||||
* [ArtemisMessagingComponent.NODE_USER] then we confirm it's just us as the node by checking their TLS certificate
|
|
||||||
* is the same as our one in our key store. Then they're given full access to all valid queues. If they connect with
|
|
||||||
* [ArtemisMessagingComponent.PEER_USER] then we confirm they belong on our P2P network by checking their root CA is
|
|
||||||
* the same as our root CA. If that's the case the only access they're given is the ablility send to our P2P address.
|
|
||||||
* In both cases the messages these authenticated nodes send to us are tagged with their subject DN and we assume
|
|
||||||
* the CN within that is their legal name.
|
|
||||||
* Otherwise if the username is neither of the above we assume it's an RPC user and authenticate against our list of
|
|
||||||
* valid RPC users. RPC clients are given permission to perform RPC and nothing else.
|
|
||||||
*/
|
|
||||||
internal class NodeLoginModule : LoginModule {
|
|
||||||
companion object {
|
|
||||||
internal const val NODE_ROLE = "SystemRoles/Node"
|
|
||||||
internal const val RPC_ROLE = "SystemRoles/RPC"
|
|
||||||
|
|
||||||
internal const val CERT_CHAIN_CHECKS_ARG = "CertChainChecks"
|
|
||||||
internal const val USE_SSL_ARG = "useSsl"
|
|
||||||
internal const val SECURITY_MANAGER_ARG = "RpcSecurityManager"
|
|
||||||
internal const val LOGIN_LISTENER_ARG = "LoginListener"
|
|
||||||
private val log = loggerFor<NodeLoginModule>()
|
|
||||||
}
|
|
||||||
|
|
||||||
private var loginSucceeded: Boolean = false
|
|
||||||
private lateinit var subject: Subject
|
|
||||||
private lateinit var callbackHandler: CallbackHandler
|
|
||||||
private lateinit var securityManager: RPCSecurityManager
|
|
||||||
private lateinit var loginListener: LoginListener
|
|
||||||
private var useSsl: Boolean? = null
|
|
||||||
private lateinit var nodeCertCheck: CertificateChainCheckPolicy.Check
|
|
||||||
private lateinit var rpcCertCheck: CertificateChainCheckPolicy.Check
|
|
||||||
private val principals = ArrayList<Principal>()
|
|
||||||
|
|
||||||
override fun initialize(subject: Subject, callbackHandler: CallbackHandler, sharedState: Map<String, *>, options: Map<String, *>) {
|
|
||||||
this.subject = subject
|
|
||||||
this.callbackHandler = callbackHandler
|
|
||||||
securityManager = uncheckedCast(options[SECURITY_MANAGER_ARG])
|
|
||||||
loginListener = uncheckedCast(options[LOGIN_LISTENER_ARG])
|
|
||||||
useSsl = options[USE_SSL_ARG] as Boolean
|
|
||||||
val certChainChecks: Map<String, CertificateChainCheckPolicy.Check> = uncheckedCast(options[CERT_CHAIN_CHECKS_ARG])
|
|
||||||
nodeCertCheck = certChainChecks[NODE_ROLE]!!
|
|
||||||
rpcCertCheck = certChainChecks[RPC_ROLE]!!
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun login(): Boolean {
|
|
||||||
val nameCallback = NameCallback("Username: ")
|
|
||||||
val passwordCallback = PasswordCallback("Password: ", false)
|
|
||||||
val certificateCallback = CertificateCallback()
|
|
||||||
try {
|
|
||||||
callbackHandler.handle(arrayOf(nameCallback, passwordCallback, certificateCallback))
|
|
||||||
} catch (e: IOException) {
|
|
||||||
throw LoginException(e.message)
|
|
||||||
} catch (e: UnsupportedCallbackException) {
|
|
||||||
throw LoginException("${e.message} not available to obtain information from user")
|
|
||||||
}
|
|
||||||
|
|
||||||
val username = nameCallback.name ?: throw FailedLoginException("Username not provided")
|
|
||||||
val password = String(passwordCallback.password ?: throw FailedLoginException("Password not provided"))
|
|
||||||
val certificates = certificateCallback.certificates ?: emptyArray()
|
|
||||||
|
|
||||||
if (rpcCertCheck is CertificateChainCheckPolicy.UsernameMustMatchCommonNameCheck) {
|
|
||||||
(rpcCertCheck as CertificateChainCheckPolicy.UsernameMustMatchCommonNameCheck).username = username
|
|
||||||
}
|
|
||||||
|
|
||||||
log.debug("Logging user in")
|
|
||||||
|
|
||||||
try {
|
|
||||||
val role = determineUserRole(certificates, username, useSsl!!)
|
|
||||||
val validatedUser = when (role) {
|
|
||||||
NodeLoginModule.NODE_ROLE -> {
|
|
||||||
authenticateNode(certificates)
|
|
||||||
NODE_USER
|
|
||||||
}
|
|
||||||
RPC_ROLE -> {
|
|
||||||
authenticateRpcUser(username, Password(password), certificates, useSsl!!)
|
|
||||||
username
|
|
||||||
}
|
|
||||||
else -> throw FailedLoginException("Peer does not belong on our network")
|
|
||||||
}
|
|
||||||
principals += UserPrincipal(validatedUser)
|
|
||||||
|
|
||||||
loginSucceeded = true
|
|
||||||
return loginSucceeded
|
|
||||||
} catch (exception: FailedLoginException) {
|
|
||||||
log.warn("$exception")
|
|
||||||
throw exception
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun authenticateNode(certificates: Array<X509Certificate>) {
|
|
||||||
nodeCertCheck.checkCertificateChain(certificates)
|
|
||||||
principals += RolePrincipal(NodeLoginModule.NODE_ROLE)
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun authenticateRpcUser(username: String, password: Password, certificates: Array<X509Certificate>, useSsl: Boolean) {
|
|
||||||
if (useSsl) {
|
|
||||||
rpcCertCheck.checkCertificateChain(certificates)
|
|
||||||
// no point in matching username with CN because companies wouldn't want to provide a certificate for each user
|
|
||||||
}
|
|
||||||
securityManager.authenticate(username, password)
|
|
||||||
loginListener(username)
|
|
||||||
principals += RolePrincipal(RPC_ROLE) // This enables the RPC client to send requests
|
|
||||||
principals += RolePrincipal("${RPCApi.RPC_CLIENT_QUEUE_NAME_PREFIX}.$username") // This enables the RPC client to receive responses
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun determineUserRole(certificates: Array<X509Certificate>, username: String, useSsl: Boolean): String? {
|
|
||||||
return when (username) {
|
|
||||||
ArtemisMessagingComponent.NODE_USER -> {
|
|
||||||
require(certificates.isNotEmpty()) { "No TLS?" }
|
|
||||||
NodeLoginModule.NODE_ROLE
|
|
||||||
}
|
|
||||||
else -> {
|
|
||||||
if (useSsl) {
|
|
||||||
require(certificates.isNotEmpty()) { "No TLS?" }
|
|
||||||
}
|
|
||||||
return RPC_ROLE
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun commit(): Boolean {
|
|
||||||
val result = loginSucceeded
|
|
||||||
if (result) {
|
|
||||||
subject.principals.addAll(principals)
|
|
||||||
}
|
|
||||||
clear()
|
|
||||||
return result
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun abort(): Boolean {
|
|
||||||
clear()
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun logout(): Boolean {
|
|
||||||
subject.principals.removeAll(principals)
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun clear() {
|
|
||||||
loginSucceeded = false
|
|
||||||
}
|
|
||||||
}
|
|
@ -8,10 +8,10 @@ import org.apache.activemq.artemis.core.settings.HierarchicalRepository
|
|||||||
* Helper class to dynamically assign security roles to RPC users
|
* Helper class to dynamically assign security roles to RPC users
|
||||||
* on their authentication. This object is plugged into the server
|
* on their authentication. This object is plugged into the server
|
||||||
* as [SecuritySettingPlugin]. It responds to authentication events
|
* as [SecuritySettingPlugin]. It responds to authentication events
|
||||||
* from [NodeLoginModule] by adding the address -> roles association
|
* from [BrokerJaasLoginModule] by adding the address -> roles association
|
||||||
* generated by the given [source], unless already done before.
|
* generated by the given [source], unless already done before.
|
||||||
*/
|
*/
|
||||||
internal class RolesAdderOnLogin(val source: (String) -> Pair<String, Set<Role>>) : SecuritySettingPlugin {
|
internal class RolesAdderOnLogin(val systemUsers: List<String> = emptyList(), val source: (String) -> Pair<String, Set<Role>>) : SecuritySettingPlugin {
|
||||||
private lateinit var repository: RolesRepository
|
private lateinit var repository: RolesRepository
|
||||||
|
|
||||||
fun onLogin(username: String) {
|
fun onLogin(username: String) {
|
||||||
@ -24,6 +24,8 @@ internal class RolesAdderOnLogin(val source: (String) -> Pair<String, Set<Role>>
|
|||||||
|
|
||||||
override fun setSecurityRepository(repository: RolesRepository) {
|
override fun setSecurityRepository(repository: RolesRepository) {
|
||||||
this.repository = repository
|
this.repository = repository
|
||||||
|
// This dynamically adds roles to the specified system users as soon as the repository is initialized.
|
||||||
|
systemUsers.forEach(::onLogin)
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun stop() = this
|
override fun stop() = this
|
||||||
|
@ -2,30 +2,32 @@ package net.corda.node.services.rpc
|
|||||||
|
|
||||||
import net.corda.core.internal.div
|
import net.corda.core.internal.div
|
||||||
import net.corda.core.utilities.NetworkHostAndPort
|
import net.corda.core.utilities.NetworkHostAndPort
|
||||||
|
import net.corda.node.internal.artemis.BrokerJaasLoginModule
|
||||||
import net.corda.node.internal.artemis.SecureArtemisConfiguration
|
import net.corda.node.internal.artemis.SecureArtemisConfiguration
|
||||||
import net.corda.nodeapi.ArtemisTcpTransport.Companion.tcpTransport
|
import net.corda.nodeapi.ArtemisTcpTransport.Companion.rpcAcceptorTcpTransport
|
||||||
import net.corda.nodeapi.ConnectionDirection
|
import net.corda.nodeapi.ArtemisTcpTransport.Companion.rpcInternalAcceptorTcpTransport
|
||||||
|
import net.corda.nodeapi.BrokerRpcSslOptions
|
||||||
import net.corda.nodeapi.RPCApi
|
import net.corda.nodeapi.RPCApi
|
||||||
import net.corda.nodeapi.internal.ArtemisMessagingComponent
|
import net.corda.nodeapi.internal.ArtemisMessagingComponent
|
||||||
import net.corda.nodeapi.internal.config.SSLConfiguration
|
import net.corda.nodeapi.internal.config.SSLConfiguration
|
||||||
import org.apache.activemq.artemis.api.core.SimpleString
|
import org.apache.activemq.artemis.api.core.SimpleString
|
||||||
import org.apache.activemq.artemis.api.core.TransportConfiguration
|
|
||||||
import org.apache.activemq.artemis.core.config.CoreQueueConfiguration
|
import org.apache.activemq.artemis.core.config.CoreQueueConfiguration
|
||||||
import org.apache.activemq.artemis.core.remoting.impl.netty.NettyAcceptorFactory
|
|
||||||
import org.apache.activemq.artemis.core.security.Role
|
import org.apache.activemq.artemis.core.security.Role
|
||||||
import org.apache.activemq.artemis.core.settings.impl.AddressFullMessagePolicy
|
import org.apache.activemq.artemis.core.settings.impl.AddressFullMessagePolicy
|
||||||
import org.apache.activemq.artemis.core.settings.impl.AddressSettings
|
import org.apache.activemq.artemis.core.settings.impl.AddressSettings
|
||||||
import java.nio.file.Path
|
import java.nio.file.Path
|
||||||
|
|
||||||
internal class RpcBrokerConfiguration(baseDirectory: Path, maxMessageSize: Int, jmxEnabled: Boolean, address: NetworkHostAndPort, adminAddress: NetworkHostAndPort?, sslOptions: SSLConfiguration?, useSsl: Boolean) : SecureArtemisConfiguration() {
|
internal class RpcBrokerConfiguration(baseDirectory: Path, maxMessageSize: Int, jmxEnabled: Boolean, address: NetworkHostAndPort, adminAddress: NetworkHostAndPort?, sslOptions: BrokerRpcSslOptions?, useSsl: Boolean, nodeConfiguration: SSLConfiguration, shouldStartLocalShell: Boolean) : SecureArtemisConfiguration() {
|
||||||
val loginListener: (String) -> Unit
|
val loginListener: (String) -> Unit
|
||||||
|
|
||||||
init {
|
init {
|
||||||
setDirectories(baseDirectory)
|
setDirectories(baseDirectory)
|
||||||
|
|
||||||
val acceptorConfigurationsSet = mutableSetOf(acceptorConfiguration(address, useSsl, sslOptions))
|
val acceptorConfigurationsSet = mutableSetOf(
|
||||||
|
rpcAcceptorTcpTransport(address, sslOptions, useSsl)
|
||||||
|
)
|
||||||
adminAddress?.let {
|
adminAddress?.let {
|
||||||
acceptorConfigurationsSet += acceptorConfiguration(adminAddress, true, sslOptions)
|
acceptorConfigurationsSet += rpcInternalAcceptorTcpTransport(it, nodeConfiguration)
|
||||||
}
|
}
|
||||||
acceptorConfigurations = acceptorConfigurationsSet
|
acceptorConfigurations = acceptorConfigurationsSet
|
||||||
|
|
||||||
@ -41,9 +43,10 @@ internal class RpcBrokerConfiguration(baseDirectory: Path, maxMessageSize: Int,
|
|||||||
|
|
||||||
initialiseSettings(maxMessageSize)
|
initialiseSettings(maxMessageSize)
|
||||||
|
|
||||||
val nodeInternalRole = Role(NodeLoginModule.NODE_ROLE, true, true, true, true, true, true, true, true, true, true)
|
val nodeInternalRole = Role(BrokerJaasLoginModule.NODE_RPC_ROLE, true, true, true, true, true, true, true, true, true, true)
|
||||||
|
|
||||||
val rolesAdderOnLogin = RolesAdderOnLogin { username ->
|
val addRPCRoleToUsers = if (shouldStartLocalShell) listOf(ArtemisMessagingComponent.INTERNAL_SHELL_USER) else emptyList()
|
||||||
|
val rolesAdderOnLogin = RolesAdderOnLogin(addRPCRoleToUsers) { username ->
|
||||||
"${RPCApi.RPC_CLIENT_QUEUE_NAME_PREFIX}.$username.#" to setOf(nodeInternalRole, restrictedRole(
|
"${RPCApi.RPC_CLIENT_QUEUE_NAME_PREFIX}.$username.#" to setOf(nodeInternalRole, restrictedRole(
|
||||||
"${RPCApi.RPC_CLIENT_QUEUE_NAME_PREFIX}.$username",
|
"${RPCApi.RPC_CLIENT_QUEUE_NAME_PREFIX}.$username",
|
||||||
consume = true,
|
consume = true,
|
||||||
@ -63,7 +66,7 @@ internal class RpcBrokerConfiguration(baseDirectory: Path, maxMessageSize: Int,
|
|||||||
|
|
||||||
private fun configureAddressSecurity(nodeInternalRole: Role, rolesAdderOnLogin: RolesAdderOnLogin) {
|
private fun configureAddressSecurity(nodeInternalRole: Role, rolesAdderOnLogin: RolesAdderOnLogin) {
|
||||||
securityRoles["${ArtemisMessagingComponent.INTERNAL_PREFIX}#"] = setOf(nodeInternalRole)
|
securityRoles["${ArtemisMessagingComponent.INTERNAL_PREFIX}#"] = setOf(nodeInternalRole)
|
||||||
securityRoles[RPCApi.RPC_SERVER_QUEUE_NAME] = setOf(nodeInternalRole, restrictedRole(NodeLoginModule.RPC_ROLE, send = true))
|
securityRoles[RPCApi.RPC_SERVER_QUEUE_NAME] = setOf(nodeInternalRole, restrictedRole(BrokerJaasLoginModule.RPC_ROLE, send = true))
|
||||||
securitySettingPlugins.add(rolesAdderOnLogin)
|
securitySettingPlugins.add(rolesAdderOnLogin)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -118,11 +121,6 @@ internal class RpcBrokerConfiguration(baseDirectory: Path, maxMessageSize: Int,
|
|||||||
return configuration
|
return configuration
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
private fun acceptorConfiguration(address: NetworkHostAndPort, enableSsl: Boolean, sslOptions: SSLConfiguration?): TransportConfiguration {
|
|
||||||
return tcpTransport(ConnectionDirection.Inbound(NettyAcceptorFactory::class.java.name), address, sslOptions, enableSsl)
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun restrictedRole(name: String, send: Boolean = false, consume: Boolean = false, createDurableQueue: Boolean = false,
|
private fun restrictedRole(name: String, send: Boolean = false, consume: Boolean = false, createDurableQueue: Boolean = false,
|
||||||
deleteDurableQueue: Boolean = false, createNonDurableQueue: Boolean = false,
|
deleteDurableQueue: Boolean = false, createNonDurableQueue: Boolean = false,
|
||||||
deleteNonDurableQueue: Boolean = false, manage: Boolean = false, browse: Boolean = false): Role {
|
deleteNonDurableQueue: Boolean = false, manage: Boolean = false, browse: Boolean = false): Role {
|
||||||
|
@ -6,6 +6,7 @@ import net.corda.core.internal.div
|
|||||||
import net.corda.core.internal.toPath
|
import net.corda.core.internal.toPath
|
||||||
import net.corda.core.utilities.NetworkHostAndPort
|
import net.corda.core.utilities.NetworkHostAndPort
|
||||||
import net.corda.core.utilities.seconds
|
import net.corda.core.utilities.seconds
|
||||||
|
import net.corda.nodeapi.BrokerRpcSslOptions
|
||||||
import net.corda.testing.core.ALICE_NAME
|
import net.corda.testing.core.ALICE_NAME
|
||||||
import net.corda.testing.node.MockServices.Companion.makeTestDataSourceProperties
|
import net.corda.testing.node.MockServices.Companion.makeTestDataSourceProperties
|
||||||
import net.corda.tools.shell.SSHDConfiguration
|
import net.corda.tools.shell.SSHDConfiguration
|
||||||
@ -151,7 +152,7 @@ class NodeConfigurationImplTest {
|
|||||||
adminAddress = NetworkHostAndPort("localhost", 2),
|
adminAddress = NetworkHostAndPort("localhost", 2),
|
||||||
standAloneBroker = false,
|
standAloneBroker = false,
|
||||||
useSsl = false,
|
useSsl = false,
|
||||||
ssl = SslOptions(baseDirectory / "certificates", keyStorePassword, trustStorePassword, true))
|
ssl = null)
|
||||||
return NodeConfigurationImpl(
|
return NodeConfigurationImpl(
|
||||||
baseDirectory = baseDirectory,
|
baseDirectory = baseDirectory,
|
||||||
myLegalName = ALICE_NAME,
|
myLegalName = ALICE_NAME,
|
||||||
@ -165,7 +166,6 @@ class NodeConfigurationImplTest {
|
|||||||
messagingServerAddress = null,
|
messagingServerAddress = null,
|
||||||
p2pMessagingRetry = P2PMessagingRetryConfiguration(5.seconds, 3, 1.0),
|
p2pMessagingRetry = P2PMessagingRetryConfiguration(5.seconds, 3, 1.0),
|
||||||
notary = null,
|
notary = null,
|
||||||
certificateChainCheckPolicies = emptyList(),
|
|
||||||
devMode = true,
|
devMode = true,
|
||||||
noLocalShell = false,
|
noLocalShell = false,
|
||||||
rpcSettings = rpcSettings,
|
rpcSettings = rpcSettings,
|
||||||
|
@ -3,23 +3,26 @@ package net.corda.node.services.rpc
|
|||||||
import net.corda.client.rpc.internal.RPCClient
|
import net.corda.client.rpc.internal.RPCClient
|
||||||
import net.corda.core.context.AuthServiceId
|
import net.corda.core.context.AuthServiceId
|
||||||
import net.corda.core.identity.CordaX500Name
|
import net.corda.core.identity.CordaX500Name
|
||||||
|
import net.corda.core.internal.div
|
||||||
import net.corda.core.messaging.RPCOps
|
import net.corda.core.messaging.RPCOps
|
||||||
import net.corda.core.utilities.NetworkHostAndPort
|
import net.corda.core.utilities.NetworkHostAndPort
|
||||||
import net.corda.node.internal.artemis.ArtemisBroker
|
import net.corda.node.internal.artemis.ArtemisBroker
|
||||||
import net.corda.node.internal.security.RPCSecurityManager
|
import net.corda.node.internal.security.RPCSecurityManager
|
||||||
import net.corda.node.internal.security.RPCSecurityManagerImpl
|
import net.corda.node.internal.security.RPCSecurityManagerImpl
|
||||||
import net.corda.node.services.Permissions.Companion.all
|
import net.corda.node.services.Permissions.Companion.all
|
||||||
import net.corda.node.services.config.CertChainPolicyConfig
|
import net.corda.node.services.messaging.InternalRPCMessagingClient
|
||||||
import net.corda.node.services.messaging.RPCMessagingClient
|
import net.corda.nodeapi.ArtemisTcpTransport.Companion.rpcConnectorTcpTransport
|
||||||
import net.corda.nodeapi.ArtemisTcpTransport.Companion.tcpTransport
|
import net.corda.nodeapi.BrokerRpcSslOptions
|
||||||
import net.corda.nodeapi.ConnectionDirection
|
import net.corda.core.messaging.ClientRpcSslOptions
|
||||||
import net.corda.nodeapi.internal.config.SSLConfiguration
|
import net.corda.nodeapi.internal.config.SSLConfiguration
|
||||||
import net.corda.nodeapi.internal.config.User
|
import net.corda.nodeapi.internal.config.User
|
||||||
import net.corda.testing.common.internal.withCertificates
|
|
||||||
import net.corda.testing.common.internal.withKeyStores
|
|
||||||
import net.corda.testing.core.SerializationEnvironmentRule
|
import net.corda.testing.core.SerializationEnvironmentRule
|
||||||
import net.corda.testing.driver.PortAllocation
|
import net.corda.testing.driver.PortAllocation
|
||||||
import net.corda.testing.driver.internal.RandomFree
|
import net.corda.testing.driver.internal.RandomFree
|
||||||
|
import net.corda.testing.internal.createKeyPairAndSelfSignedCertificate
|
||||||
|
import net.corda.testing.internal.createNodeSslConfig
|
||||||
|
import net.corda.testing.internal.saveToKeyStore
|
||||||
|
import net.corda.testing.internal.saveToTrustStore
|
||||||
import org.apache.activemq.artemis.api.core.ActiveMQConnectionTimedOutException
|
import org.apache.activemq.artemis.api.core.ActiveMQConnectionTimedOutException
|
||||||
import org.apache.activemq.artemis.api.core.ActiveMQNotConnectedException
|
import org.apache.activemq.artemis.api.core.ActiveMQNotConnectedException
|
||||||
import org.apache.activemq.artemis.api.core.management.ActiveMQServerControl
|
import org.apache.activemq.artemis.api.core.management.ActiveMQServerControl
|
||||||
@ -27,9 +30,8 @@ import org.assertj.core.api.Assertions.assertThat
|
|||||||
import org.assertj.core.api.Assertions.assertThatThrownBy
|
import org.assertj.core.api.Assertions.assertThatThrownBy
|
||||||
import org.junit.Rule
|
import org.junit.Rule
|
||||||
import org.junit.Test
|
import org.junit.Test
|
||||||
import java.nio.file.Files
|
import org.junit.rules.TemporaryFolder
|
||||||
import java.nio.file.Path
|
import java.nio.file.Path
|
||||||
import kotlin.reflect.KClass
|
|
||||||
|
|
||||||
class ArtemisRpcTests {
|
class ArtemisRpcTests {
|
||||||
private val ports: PortAllocation = RandomFree
|
private val ports: PortAllocation = RandomFree
|
||||||
@ -42,168 +44,86 @@ class ArtemisRpcTests {
|
|||||||
@JvmField
|
@JvmField
|
||||||
val testSerialization = SerializationEnvironmentRule(true)
|
val testSerialization = SerializationEnvironmentRule(true)
|
||||||
|
|
||||||
|
@Rule
|
||||||
|
@JvmField
|
||||||
|
val tempFolder = TemporaryFolder()
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun rpc_with_ssl_enabled() {
|
fun rpc_with_ssl_enabled() {
|
||||||
withCertificates { server, client, createSelfSigned, createSignedBy ->
|
val (rpcKeyPair, selfSignCert) = createKeyPairAndSelfSignedCertificate()
|
||||||
val rootCertificate = createSelfSigned(CordaX500Name("SystemUsers/Node", "IT", "R3 London", "London", "London", "GB"))
|
val keyStorePath = saveToKeyStore(tempFile("rpcKeystore.jks"), rpcKeyPair, selfSignCert)
|
||||||
val markCertificate = createSignedBy(CordaX500Name("mark", "IT", "R3 London", "London", "London", "GB"), rootCertificate)
|
val brokerSslOptions = BrokerRpcSslOptions(keyStorePath, "password")
|
||||||
|
val trustStorePath = saveToTrustStore(tempFile("rpcTruststore.jks"), selfSignCert)
|
||||||
// truststore needs to contain root CA for how the driver works...
|
val clientSslOptions = ClientRpcSslOptions(trustStorePath, "password")
|
||||||
server.keyStore["cordaclienttls"] = rootCertificate
|
testSslCommunication(createNodeSslConfig(tempFolder.root.toPath()), brokerSslOptions, true, clientSslOptions)
|
||||||
server.trustStore["cordaclienttls"] = rootCertificate
|
|
||||||
server.trustStore["mark"] = markCertificate
|
|
||||||
|
|
||||||
client.keyStore["mark"] = markCertificate
|
|
||||||
client.trustStore["cordaclienttls"] = rootCertificate
|
|
||||||
|
|
||||||
withKeyStores(server, client) { brokerSslOptions, clientSslOptions ->
|
|
||||||
testSslCommunication(brokerSslOptions, true, clientSslOptions)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun rpc_with_ssl_disabled() {
|
fun rpc_with_ssl_disabled() {
|
||||||
withCertificates { server, client, createSelfSigned, _ ->
|
testSslCommunication(createNodeSslConfig(tempFolder.root.toPath()), null, false, null)
|
||||||
val rootCertificate = createSelfSigned(CordaX500Name("SystemUsers/Node", "IT", "R3 London", "London", "London", "GB"))
|
|
||||||
|
|
||||||
// truststore needs to contain root CA for how the driver works...
|
|
||||||
server.keyStore["cordaclienttls"] = rootCertificate
|
|
||||||
server.trustStore["cordaclienttls"] = rootCertificate
|
|
||||||
|
|
||||||
withKeyStores(server, client) { brokerSslOptions, _ ->
|
|
||||||
// here server is told not to use SSL, and client sslOptions are null (as in, do not use SSL)
|
|
||||||
testSslCommunication(brokerSslOptions, false, null)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
fun rpc_with_server_certificate_untrusted_to_client() {
|
|
||||||
withCertificates { server, client, createSelfSigned, createSignedBy ->
|
|
||||||
val rootCertificate = createSelfSigned(CordaX500Name("SystemUsers/Node", "IT", "R3 London", "London", "London", "GB"))
|
|
||||||
val markCertificate = createSignedBy(CordaX500Name("mark", "IT", "R3 London", "London", "London", "GB"), rootCertificate)
|
|
||||||
|
|
||||||
// truststore needs to contain root CA for how the driver works...
|
|
||||||
server.keyStore["cordaclienttls"] = rootCertificate
|
|
||||||
server.trustStore["cordaclienttls"] = rootCertificate
|
|
||||||
server.trustStore["mark"] = markCertificate
|
|
||||||
|
|
||||||
client.keyStore["mark"] = markCertificate
|
|
||||||
// here the server certificate is not trusted by the client
|
|
||||||
// client.trustStore["cordaclienttls"] = rootCertificate
|
|
||||||
|
|
||||||
withKeyStores(server, client) { brokerSslOptions, clientSslOptions ->
|
|
||||||
testSslCommunication(brokerSslOptions, true, clientSslOptions, clientConnectionSpy = expectExceptionOfType(ActiveMQNotConnectedException::class))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
fun rpc_with_no_client_certificate() {
|
|
||||||
withCertificates { server, client, createSelfSigned, createSignedBy ->
|
|
||||||
val rootCertificate = createSelfSigned(CordaX500Name("SystemUsers/Node", "IT", "R3 London", "London", "London", "GB"))
|
|
||||||
val markCertificate = createSignedBy(CordaX500Name("mark", "IT", "R3 London", "London", "London", "GB"), rootCertificate)
|
|
||||||
|
|
||||||
// truststore needs to contain root CA for how the driver works...
|
|
||||||
server.keyStore["cordaclienttls"] = rootCertificate
|
|
||||||
server.trustStore["cordaclienttls"] = rootCertificate
|
|
||||||
server.trustStore["mark"] = markCertificate
|
|
||||||
|
|
||||||
// here client keystore is empty
|
|
||||||
// client.keyStore["mark"] = markCertificate
|
|
||||||
client.trustStore["cordaclienttls"] = rootCertificate
|
|
||||||
|
|
||||||
withKeyStores(server, client) { brokerSslOptions, clientSslOptions ->
|
|
||||||
testSslCommunication(brokerSslOptions, true, clientSslOptions, clientConnectionSpy = expectExceptionOfType(ActiveMQNotConnectedException::class))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun rpc_with_no_ssl_on_client_side_and_ssl_on_server_side() {
|
fun rpc_with_no_ssl_on_client_side_and_ssl_on_server_side() {
|
||||||
withCertificates { server, client, createSelfSigned, createSignedBy ->
|
val (rpcKeyPair, selfSignCert) = createKeyPairAndSelfSignedCertificate()
|
||||||
val rootCertificate = createSelfSigned(CordaX500Name("SystemUsers/Node", "IT", "R3 London", "London", "London", "GB"))
|
val keyStorePath = saveToKeyStore(tempFile("rpcKeystore.jks"), rpcKeyPair, selfSignCert)
|
||||||
val markCertificate = createSignedBy(CordaX500Name("mark", "IT", "R3 London", "London", "London", "GB"), rootCertificate)
|
val brokerSslOptions = BrokerRpcSslOptions(keyStorePath, "password")
|
||||||
|
|
||||||
// truststore needs to contain root CA for how the driver works...
|
|
||||||
server.keyStore["cordaclienttls"] = rootCertificate
|
|
||||||
server.trustStore["cordaclienttls"] = rootCertificate
|
|
||||||
server.trustStore["mark"] = markCertificate
|
|
||||||
|
|
||||||
client.keyStore["mark"] = markCertificate
|
|
||||||
client.trustStore["cordaclienttls"] = rootCertificate
|
|
||||||
|
|
||||||
withKeyStores(server, client) { brokerSslOptions, _ ->
|
|
||||||
// here client sslOptions are passed null (as in, do not use SSL)
|
// here client sslOptions are passed null (as in, do not use SSL)
|
||||||
testSslCommunication(brokerSslOptions, true, null, clientConnectionSpy = expectExceptionOfType(ActiveMQConnectionTimedOutException::class))
|
assertThatThrownBy {
|
||||||
}
|
testSslCommunication(createNodeSslConfig(tempFolder.root.toPath()), brokerSslOptions, true, null)
|
||||||
}
|
}.isInstanceOf(ActiveMQConnectionTimedOutException::class.java)
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun rpc_client_certificate_untrusted_to_server() {
|
fun rpc_client_certificate_untrusted_to_server() {
|
||||||
withCertificates { server, client, createSelfSigned, _ ->
|
val (rpcKeyPair, selfSignCert) = createKeyPairAndSelfSignedCertificate()
|
||||||
val rootCertificate = createSelfSigned(CordaX500Name("SystemUsers/Node", "IT", "R3 London", "London", "London", "GB"))
|
val keyStorePath = saveToKeyStore(tempFile("rpcKeystore.jks"), rpcKeyPair, selfSignCert)
|
||||||
// here client's certificate is self-signed, otherwise Artemis allows the connection (the issuing certificate is in the truststore)
|
|
||||||
val markCertificate = createSelfSigned(CordaX500Name("mark", "IT", "R3 London", "London", "London", "GB"))
|
|
||||||
|
|
||||||
// truststore needs to contain root CA for how the driver works...
|
// create another keypair and certificate and add that to the client truststore
|
||||||
server.keyStore["cordaclienttls"] = rootCertificate
|
// the ssl connection should not
|
||||||
server.trustStore["cordaclienttls"] = rootCertificate
|
val (_, selfSignCert1) = createKeyPairAndSelfSignedCertificate()
|
||||||
// here the client certificate is not trusted by the server
|
val trustStorePath = saveToTrustStore(tempFile("rpcTruststore.jks"), selfSignCert1)
|
||||||
// server.trustStore["mark"] = markCertificate
|
|
||||||
|
|
||||||
client.keyStore["mark"] = markCertificate
|
val brokerSslOptions = BrokerRpcSslOptions(keyStorePath, "password")
|
||||||
client.trustStore["cordaclienttls"] = rootCertificate
|
val clientSslOptions = ClientRpcSslOptions(trustStorePath, "password")
|
||||||
|
|
||||||
withKeyStores(server, client) { brokerSslOptions, clientSslOptions ->
|
assertThatThrownBy {
|
||||||
testSslCommunication(brokerSslOptions, true, clientSslOptions, clientConnectionSpy = expectExceptionOfType(ActiveMQNotConnectedException::class))
|
testSslCommunication(createNodeSslConfig(tempFolder.root.toPath()), brokerSslOptions, true, clientSslOptions)
|
||||||
}
|
}.isInstanceOf(ActiveMQNotConnectedException::class.java)
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun testSslCommunication(brokerSslOptions: SSLConfiguration, useSslForBroker: Boolean, clientSslOptions: SSLConfiguration?, address: NetworkHostAndPort = ports.nextHostAndPort(),
|
private fun testSslCommunication(nodeSSlconfig: SSLConfiguration, brokerSslOptions: BrokerRpcSslOptions?, useSslForBroker: Boolean, clientSslOptions: ClientRpcSslOptions?, address: NetworkHostAndPort = ports.nextHostAndPort(),
|
||||||
adminAddress: NetworkHostAndPort = ports.nextHostAndPort(), baseDirectory: Path = Files.createTempDirectory(null), clientConnectionSpy: (() -> Unit) -> Unit = {}) {
|
adminAddress: NetworkHostAndPort = ports.nextHostAndPort(), baseDirectory: Path = tempFolder.root.toPath()) {
|
||||||
val maxMessageSize = 10000
|
val maxMessageSize = 10000
|
||||||
val jmxEnabled = false
|
val jmxEnabled = false
|
||||||
val certificateChainCheckPolicies: List<CertChainPolicyConfig> = listOf()
|
|
||||||
|
|
||||||
val artemisBroker: ArtemisBroker = if (useSslForBroker) {
|
val artemisBroker: ArtemisBroker = if (useSslForBroker) {
|
||||||
ArtemisRpcBroker.withSsl(address, brokerSslOptions, securityManager, certificateChainCheckPolicies, maxMessageSize, jmxEnabled, baseDirectory)
|
ArtemisRpcBroker.withSsl(nodeSSlconfig, address, adminAddress, brokerSslOptions!!, securityManager, maxMessageSize, jmxEnabled, baseDirectory, false)
|
||||||
} else {
|
} else {
|
||||||
ArtemisRpcBroker.withoutSsl(address, adminAddress, brokerSslOptions, securityManager, certificateChainCheckPolicies, maxMessageSize, jmxEnabled, baseDirectory)
|
ArtemisRpcBroker.withoutSsl(nodeSSlconfig, address, adminAddress, securityManager, maxMessageSize, jmxEnabled, baseDirectory, false)
|
||||||
}
|
}
|
||||||
artemisBroker.use { broker ->
|
artemisBroker.use { broker ->
|
||||||
broker.start()
|
broker.start()
|
||||||
RPCMessagingClient(brokerSslOptions, broker.addresses.admin, maxMessageSize).use { server ->
|
InternalRPCMessagingClient(nodeSSlconfig, adminAddress, maxMessageSize, CordaX500Name("MegaCorp", "London", "GB")).use { server ->
|
||||||
server.start(TestRpcOpsImpl(), securityManager, broker.serverControl)
|
server.start(TestRpcOpsImpl(), securityManager, broker.serverControl)
|
||||||
|
|
||||||
val client = RPCClient<TestRpcOps>(tcpTransport(ConnectionDirection.Outbound(), broker.addresses.primary, clientSslOptions))
|
val client = RPCClient<TestRpcOps>(rpcConnectorTcpTransport(broker.addresses.primary, clientSslOptions))
|
||||||
|
|
||||||
clientConnectionSpy {
|
val greeting = client.start(TestRpcOps::class.java, user.username, user.password).use { connection ->
|
||||||
client.start(TestRpcOps::class.java, user.username, user.password).use { connection ->
|
connection.proxy.greet("Frodo")
|
||||||
connection.proxy.apply {
|
}
|
||||||
val greeting = greet("Frodo")
|
|
||||||
assertThat(greeting).isEqualTo("Oh, hello Frodo!")
|
assertThat(greeting).isEqualTo("Oh, hello Frodo!")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun <OPS : RPCOps> RPCMessagingClient.start(ops: OPS, securityManager: RPCSecurityManager, brokerControl: ActiveMQServerControl) {
|
private fun <OPS : RPCOps> InternalRPCMessagingClient.start(ops: OPS, securityManager: RPCSecurityManager, brokerControl: ActiveMQServerControl) {
|
||||||
apply {
|
apply {
|
||||||
start(ops, securityManager)
|
init(ops, securityManager)
|
||||||
start2(brokerControl)
|
start(brokerControl)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun <EXCEPTION : Exception> expectExceptionOfType(exceptionType: KClass<EXCEPTION>): (() -> Unit) -> Unit {
|
|
||||||
return { action -> assertThatThrownBy { action.invoke() }.isInstanceOf(exceptionType.java) }
|
|
||||||
}
|
|
||||||
|
|
||||||
interface TestRpcOps : RPCOps {
|
interface TestRpcOps : RPCOps {
|
||||||
fun greet(name: String): String
|
fun greet(name: String): String
|
||||||
}
|
}
|
||||||
@ -213,4 +133,7 @@ class ArtemisRpcTests {
|
|||||||
|
|
||||||
override val protocolVersion: Int = 1
|
override val protocolVersion: Int = 1
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private fun tempFile(name: String): Path = tempFolder.root.toPath() / name
|
||||||
|
|
||||||
}
|
}
|
@ -50,6 +50,8 @@ interface NodeHandle : AutoCloseable {
|
|||||||
val p2pAddress: NetworkHostAndPort
|
val p2pAddress: NetworkHostAndPort
|
||||||
/** Get the rpc address for this node **/
|
/** Get the rpc address for this node **/
|
||||||
val rpcAddress: NetworkHostAndPort
|
val rpcAddress: NetworkHostAndPort
|
||||||
|
/** Get the rpc admin address for this node **/
|
||||||
|
val rpcAdminAddress: NetworkHostAndPort
|
||||||
/** Get a [List] of [User]'s for this node **/
|
/** Get a [List] of [User]'s for this node **/
|
||||||
val rpcUsers: List<User>
|
val rpcUsers: List<User>
|
||||||
/** The location of the node's base directory **/
|
/** The location of the node's base directory **/
|
||||||
|
@ -24,7 +24,8 @@ interface NodeHandleInternal : NodeHandle {
|
|||||||
val useHTTPS: Boolean
|
val useHTTPS: Boolean
|
||||||
val webAddress: NetworkHostAndPort
|
val webAddress: NetworkHostAndPort
|
||||||
override val p2pAddress: NetworkHostAndPort get() = configuration.p2pAddress
|
override val p2pAddress: NetworkHostAndPort get() = configuration.p2pAddress
|
||||||
override val rpcAddress: NetworkHostAndPort get() = configuration.rpcOptions.address!!
|
override val rpcAddress: NetworkHostAndPort get() = configuration.rpcOptions.address
|
||||||
|
override val rpcAdminAddress: NetworkHostAndPort get() = configuration.rpcOptions.adminAddress
|
||||||
override val baseDirectory: Path get() = configuration.baseDirectory
|
override val baseDirectory: Path get() = configuration.baseDirectory
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -6,8 +6,7 @@ import com.typesafe.config.Config
|
|||||||
import com.typesafe.config.ConfigFactory
|
import com.typesafe.config.ConfigFactory
|
||||||
import com.typesafe.config.ConfigRenderOptions
|
import com.typesafe.config.ConfigRenderOptions
|
||||||
import com.typesafe.config.ConfigValueFactory
|
import com.typesafe.config.ConfigValueFactory
|
||||||
import net.corda.client.rpc.CordaRPCClient
|
import net.corda.client.rpc.internal.createCordaRPCClientWithInternalSslAndClassLoader
|
||||||
import net.corda.client.rpc.internal.createCordaRPCClientWithSsl
|
|
||||||
import net.corda.cordform.CordformContext
|
import net.corda.cordform.CordformContext
|
||||||
import net.corda.cordform.CordformNode
|
import net.corda.cordform.CordformNode
|
||||||
import net.corda.core.concurrent.CordaFuture
|
import net.corda.core.concurrent.CordaFuture
|
||||||
@ -149,12 +148,8 @@ class DriverDSLImpl(
|
|||||||
}
|
}
|
||||||
|
|
||||||
private fun establishRpc(config: NodeConfig, processDeathFuture: CordaFuture<out Process>): CordaFuture<CordaRPCOps> {
|
private fun establishRpc(config: NodeConfig, processDeathFuture: CordaFuture<out Process>): CordaFuture<CordaRPCOps> {
|
||||||
val rpcAddress = config.corda.rpcOptions.address!!
|
val rpcAddress = config.corda.rpcOptions.address
|
||||||
val client = if (config.corda.rpcOptions.useSsl) {
|
val client = createCordaRPCClientWithInternalSslAndClassLoader(config.corda.rpcOptions.adminAddress, sslConfiguration = config.corda)
|
||||||
createCordaRPCClientWithSsl(rpcAddress, sslConfiguration = config.corda.rpcOptions.sslConfig)
|
|
||||||
} else {
|
|
||||||
CordaRPCClient(rpcAddress)
|
|
||||||
}
|
|
||||||
val connectionFuture = poll(executorService, "RPC connection") {
|
val connectionFuture = poll(executorService, "RPC connection") {
|
||||||
try {
|
try {
|
||||||
config.corda.rpcUsers[0].run { client.start(username, password) }
|
config.corda.rpcUsers[0].run { client.start(username, password) }
|
||||||
@ -238,9 +233,13 @@ class DriverDSLImpl(
|
|||||||
baseDirectory = baseDirectory,
|
baseDirectory = baseDirectory,
|
||||||
allowMissingConfig = true,
|
allowMissingConfig = true,
|
||||||
configOverrides = configOf(
|
configOverrides = configOf(
|
||||||
"p2pAddress" to "localhost:1222", // required argument, not really used
|
"p2pAddress" to portAllocation.nextHostAndPort().toString(),
|
||||||
"compatibilityZoneURL" to compatibilityZoneURL.toString(),
|
"compatibilityZoneURL" to compatibilityZoneURL.toString(),
|
||||||
"myLegalName" to providedName.toString(),
|
"myLegalName" to providedName.toString(),
|
||||||
|
"rpcSettings" to mapOf(
|
||||||
|
"address" to portAllocation.nextHostAndPort().toString(),
|
||||||
|
"adminAddress" to portAllocation.nextHostAndPort().toString()
|
||||||
|
),
|
||||||
"devMode" to false)
|
"devMode" to false)
|
||||||
))
|
))
|
||||||
|
|
||||||
@ -341,18 +340,14 @@ class DriverDSLImpl(
|
|||||||
val rpcAddress = if (cordform.rpcAddress == null) {
|
val rpcAddress = if (cordform.rpcAddress == null) {
|
||||||
val overrides = mutableMapOf<String, Any>("rpcSettings.address" to portAllocation.nextHostAndPort().toString())
|
val overrides = mutableMapOf<String, Any>("rpcSettings.address" to portAllocation.nextHostAndPort().toString())
|
||||||
cordform.config.apply {
|
cordform.config.apply {
|
||||||
if (!hasPath("rpcSettings.useSsl") || !getBoolean("rpcSettings.useSsl")) {
|
|
||||||
overrides += "rpcSettings.adminAddress" to portAllocation.nextHostAndPort().toString()
|
overrides += "rpcSettings.adminAddress" to portAllocation.nextHostAndPort().toString()
|
||||||
}
|
}
|
||||||
}
|
|
||||||
overrides
|
overrides
|
||||||
} else {
|
} else {
|
||||||
val overrides = mutableMapOf<String, Any>()
|
val overrides = mutableMapOf<String, Any>()
|
||||||
cordform.config.apply {
|
cordform.config.apply {
|
||||||
if ((!hasPath("rpcSettings.useSsl") || !getBoolean("rpcSettings.useSsl")) && !hasPath("rpcSettings.adminAddress")) {
|
|
||||||
overrides += "rpcSettings.adminAddress" to portAllocation.nextHostAndPort().toString()
|
overrides += "rpcSettings.adminAddress" to portAllocation.nextHostAndPort().toString()
|
||||||
}
|
}
|
||||||
}
|
|
||||||
overrides
|
overrides
|
||||||
}
|
}
|
||||||
val webAddress = cordform.webAddress?.let { NetworkHostAndPort.parse(it) } ?: portAllocation.nextHostAndPort()
|
val webAddress = cordform.webAddress?.let { NetworkHostAndPort.parse(it) } ?: portAllocation.nextHostAndPort()
|
||||||
@ -853,7 +848,7 @@ class DriverDSLImpl(
|
|||||||
var config = ConfigFactory.empty()
|
var config = ConfigFactory.empty()
|
||||||
config += "webAddress" to webAddress.toString()
|
config += "webAddress" to webAddress.toString()
|
||||||
config += "myLegalName" to configuration.myLegalName.toString()
|
config += "myLegalName" to configuration.myLegalName.toString()
|
||||||
config += "rpcAddress" to configuration.rpcOptions.address!!.toString()
|
config += "rpcAddress" to configuration.rpcOptions.address.toString()
|
||||||
config += "rpcUsers" to configuration.toConfig().getValue("rpcUsers")
|
config += "rpcUsers" to configuration.toConfig().getValue("rpcUsers")
|
||||||
config += "useHTTPS" to useHTTPS
|
config += "useHTTPS" to useHTTPS
|
||||||
config += "baseDirectory" to configuration.baseDirectory.toAbsolutePath().toString()
|
config += "baseDirectory" to configuration.baseDirectory.toAbsolutePath().toString()
|
||||||
|
@ -467,7 +467,6 @@ private fun mockNodeConfiguration(): NodeConfiguration {
|
|||||||
doReturn(null).whenever(it).jmxMonitoringHttpPort
|
doReturn(null).whenever(it).jmxMonitoringHttpPort
|
||||||
doReturn(true).whenever(it).devMode
|
doReturn(true).whenever(it).devMode
|
||||||
doReturn(null).whenever(it).compatibilityZoneURL
|
doReturn(null).whenever(it).compatibilityZoneURL
|
||||||
doReturn(emptyList<CertChainPolicyConfig>()).whenever(it).certificateChainCheckPolicies
|
|
||||||
doReturn(VerifierType.InMemory).whenever(it).verifierType
|
doReturn(VerifierType.InMemory).whenever(it).verifierType
|
||||||
doReturn(P2PMessagingRetryConfiguration(5.seconds, 3, backoffBase = 1.0)).whenever(it).p2pMessagingRetry
|
doReturn(P2PMessagingRetryConfiguration(5.seconds, 3, backoffBase = 1.0)).whenever(it).p2pMessagingRetry
|
||||||
doReturn(5.seconds.toMillis()).whenever(it).additionalNodeInfoPollingFrequencyMsec
|
doReturn(5.seconds.toMillis()).whenever(it).additionalNodeInfoPollingFrequencyMsec
|
||||||
|
@ -21,7 +21,6 @@ import net.corda.node.internal.security.RPCSecurityManagerImpl
|
|||||||
import net.corda.node.services.messaging.RPCServer
|
import net.corda.node.services.messaging.RPCServer
|
||||||
import net.corda.node.services.messaging.RPCServerConfiguration
|
import net.corda.node.services.messaging.RPCServerConfiguration
|
||||||
import net.corda.nodeapi.ArtemisTcpTransport
|
import net.corda.nodeapi.ArtemisTcpTransport
|
||||||
import net.corda.nodeapi.ConnectionDirection
|
|
||||||
import net.corda.nodeapi.RPCApi
|
import net.corda.nodeapi.RPCApi
|
||||||
import net.corda.serialization.internal.AMQP_RPC_CLIENT_CONTEXT
|
import net.corda.serialization.internal.AMQP_RPC_CLIENT_CONTEXT
|
||||||
import net.corda.testing.common.internal.testNetworkParameters
|
import net.corda.testing.common.internal.testNetworkParameters
|
||||||
@ -41,7 +40,6 @@ import org.apache.activemq.artemis.core.config.CoreQueueConfiguration
|
|||||||
import org.apache.activemq.artemis.core.config.impl.ConfigurationImpl
|
import org.apache.activemq.artemis.core.config.impl.ConfigurationImpl
|
||||||
import org.apache.activemq.artemis.core.remoting.impl.invm.InVMAcceptorFactory
|
import org.apache.activemq.artemis.core.remoting.impl.invm.InVMAcceptorFactory
|
||||||
import org.apache.activemq.artemis.core.remoting.impl.invm.InVMConnectorFactory
|
import org.apache.activemq.artemis.core.remoting.impl.invm.InVMConnectorFactory
|
||||||
import org.apache.activemq.artemis.core.remoting.impl.netty.NettyAcceptorFactory
|
|
||||||
import org.apache.activemq.artemis.core.security.CheckType
|
import org.apache.activemq.artemis.core.security.CheckType
|
||||||
import org.apache.activemq.artemis.core.security.Role
|
import org.apache.activemq.artemis.core.security.Role
|
||||||
import org.apache.activemq.artemis.core.server.embedded.EmbeddedActiveMQ
|
import org.apache.activemq.artemis.core.server.embedded.EmbeddedActiveMQ
|
||||||
@ -212,20 +210,19 @@ data class RPCDriverDSL(
|
|||||||
}
|
}
|
||||||
|
|
||||||
fun createRpcServerArtemisConfig(maxFileSize: Int, maxBufferedBytesPerClient: Long, baseDirectory: Path, hostAndPort: NetworkHostAndPort): Configuration {
|
fun createRpcServerArtemisConfig(maxFileSize: Int, maxBufferedBytesPerClient: Long, baseDirectory: Path, hostAndPort: NetworkHostAndPort): Configuration {
|
||||||
val connectionDirection = ConnectionDirection.Inbound(acceptorFactoryClassName = NettyAcceptorFactory::class.java.name)
|
|
||||||
return ConfigurationImpl().apply {
|
return ConfigurationImpl().apply {
|
||||||
val artemisDir = "$baseDirectory/artemis"
|
val artemisDir = "$baseDirectory/artemis"
|
||||||
bindingsDirectory = "$artemisDir/bindings"
|
bindingsDirectory = "$artemisDir/bindings"
|
||||||
journalDirectory = "$artemisDir/journal"
|
journalDirectory = "$artemisDir/journal"
|
||||||
largeMessagesDirectory = "$artemisDir/large-messages"
|
largeMessagesDirectory = "$artemisDir/large-messages"
|
||||||
acceptorConfigurations = setOf(ArtemisTcpTransport.tcpTransport(connectionDirection, hostAndPort, null))
|
acceptorConfigurations = setOf(ArtemisTcpTransport.rpcAcceptorTcpTransport(hostAndPort, null))
|
||||||
configureCommonSettings(maxFileSize, maxBufferedBytesPerClient)
|
configureCommonSettings(maxFileSize, maxBufferedBytesPerClient)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
val inVmClientTransportConfiguration = TransportConfiguration(InVMConnectorFactory::class.java.name)
|
val inVmClientTransportConfiguration = TransportConfiguration(InVMConnectorFactory::class.java.name)
|
||||||
fun createNettyClientTransportConfiguration(hostAndPort: NetworkHostAndPort): TransportConfiguration {
|
fun createNettyClientTransportConfiguration(hostAndPort: NetworkHostAndPort): TransportConfiguration {
|
||||||
return ArtemisTcpTransport.tcpTransport(ConnectionDirection.Outbound(), hostAndPort, null)
|
return ArtemisTcpTransport.rpcConnectorTcpTransport(hostAndPort, null)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -336,7 +333,7 @@ data class RPCDriverDSL(
|
|||||||
configuration: CordaRPCClientConfigurationImpl = CordaRPCClientConfigurationImpl.default
|
configuration: CordaRPCClientConfigurationImpl = CordaRPCClientConfigurationImpl.default
|
||||||
): CordaFuture<I> {
|
): CordaFuture<I> {
|
||||||
return driverDSL.executorService.fork {
|
return driverDSL.executorService.fork {
|
||||||
val client = RPCClient<I>(ArtemisTcpTransport.tcpTransport(ConnectionDirection.Outbound(), rpcAddress, null), configuration)
|
val client = RPCClient<I>(ArtemisTcpTransport.rpcConnectorTcpTransport(rpcAddress, null), configuration)
|
||||||
val connection = client.start(rpcOpsClass, username, password, externalTrace)
|
val connection = client.start(rpcOpsClass, username, password, externalTrace)
|
||||||
driverDSL.shutdownManager.registerShutdown {
|
driverDSL.shutdownManager.registerShutdown {
|
||||||
connection.close()
|
connection.close()
|
||||||
|
@ -10,14 +10,16 @@ import net.corda.core.node.NodeInfo
|
|||||||
import net.corda.core.utilities.NetworkHostAndPort
|
import net.corda.core.utilities.NetworkHostAndPort
|
||||||
import net.corda.core.utilities.loggerFor
|
import net.corda.core.utilities.loggerFor
|
||||||
import net.corda.node.services.config.configureDevKeyAndTrustStores
|
import net.corda.node.services.config.configureDevKeyAndTrustStores
|
||||||
|
import net.corda.nodeapi.BrokerRpcSslOptions
|
||||||
import net.corda.nodeapi.internal.config.SSLConfiguration
|
import net.corda.nodeapi.internal.config.SSLConfiguration
|
||||||
|
import net.corda.nodeapi.internal.createDevKeyStores
|
||||||
import net.corda.nodeapi.internal.createDevNodeCa
|
import net.corda.nodeapi.internal.createDevNodeCa
|
||||||
import net.corda.nodeapi.internal.crypto.CertificateAndKeyPair
|
import net.corda.nodeapi.internal.crypto.*
|
||||||
import net.corda.nodeapi.internal.crypto.CertificateType
|
|
||||||
import net.corda.nodeapi.internal.crypto.X509Utilities
|
|
||||||
import net.corda.serialization.internal.amqp.AMQP_ENABLED
|
import net.corda.serialization.internal.amqp.AMQP_ENABLED
|
||||||
import java.nio.file.Files
|
import java.nio.file.Files
|
||||||
|
import java.nio.file.Path
|
||||||
import java.security.KeyPair
|
import java.security.KeyPair
|
||||||
|
import java.security.cert.X509Certificate
|
||||||
import javax.security.auth.x500.X500Principal
|
import javax.security.auth.x500.X500Principal
|
||||||
|
|
||||||
@Suppress("unused")
|
@Suppress("unused")
|
||||||
@ -92,13 +94,11 @@ fun createDevNodeCaCertPath(
|
|||||||
return Triple(rootCa, intermediateCa, nodeCa)
|
return Triple(rootCa, intermediateCa, nodeCa)
|
||||||
}
|
}
|
||||||
|
|
||||||
fun SSLConfiguration.useSslRpcOverrides(): Map<String, Any> {
|
fun BrokerRpcSslOptions.useSslRpcOverrides(): Map<String, String> {
|
||||||
return mapOf(
|
return mapOf(
|
||||||
"rpcSettings.useSsl" to "true",
|
"rpcSettings.useSsl" to "true",
|
||||||
"rpcSettings.ssl.certificatesDirectory" to certificatesDirectory.toString(),
|
"rpcSettings.ssl.keyStorePath" to keyStorePath.toAbsolutePath().toString(),
|
||||||
"rpcSettings.ssl.keyStorePassword" to keyStorePassword,
|
"rpcSettings.ssl.keyStorePassword" to keyStorePassword
|
||||||
"rpcSettings.ssl.trustStorePassword" to trustStorePassword,
|
|
||||||
"rpcSettings.ssl.crlCheckSoftFail" to true
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -125,3 +125,40 @@ fun NodeInfo.chooseIdentityAndCert(): PartyAndCertificate = legalIdentitiesAndCe
|
|||||||
* TODO: Should be removed after multiple identities are introduced.
|
* TODO: Should be removed after multiple identities are introduced.
|
||||||
*/
|
*/
|
||||||
fun NodeInfo.chooseIdentity(): Party = chooseIdentityAndCert().party
|
fun NodeInfo.chooseIdentity(): Party = chooseIdentityAndCert().party
|
||||||
|
|
||||||
|
fun createNodeSslConfig(path: Path, name: CordaX500Name = CordaX500Name("MegaCorp", "London", "GB")): SSLConfiguration {
|
||||||
|
val sslConfig = object : SSLConfiguration {
|
||||||
|
override val crlCheckSoftFail = true
|
||||||
|
override val certificatesDirectory = path
|
||||||
|
override val keyStorePassword = "serverstorepass"
|
||||||
|
override val trustStorePassword = "trustpass"
|
||||||
|
}
|
||||||
|
val (rootCa, intermediateCa) = createDevIntermediateCaCertPath()
|
||||||
|
sslConfig.createDevKeyStores(name, rootCa.certificate, intermediateCa)
|
||||||
|
val trustStore = loadOrCreateKeyStore(sslConfig.trustStoreFile, sslConfig.trustStorePassword)
|
||||||
|
trustStore.addOrReplaceCertificate(X509Utilities.CORDA_ROOT_CA, rootCa.certificate)
|
||||||
|
trustStore.save(sslConfig.trustStoreFile, sslConfig.trustStorePassword)
|
||||||
|
|
||||||
|
return sslConfig
|
||||||
|
}
|
||||||
|
|
||||||
|
fun createKeyPairAndSelfSignedCertificate(): Pair<KeyPair, X509Certificate> {
|
||||||
|
val rpcKeyPair = Crypto.generateKeyPair(X509Utilities.DEFAULT_TLS_SIGNATURE_SCHEME)
|
||||||
|
val testName = X500Principal("CN=Test,O=R3 Ltd,L=London,C=GB")
|
||||||
|
val selfSignCert = X509Utilities.createSelfSignedCACertificate(testName, rpcKeyPair)
|
||||||
|
return Pair(rpcKeyPair, selfSignCert)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun saveToKeyStore(keyStorePath: Path, rpcKeyPair: KeyPair, selfSignCert: X509Certificate, password: String = "password"): Path {
|
||||||
|
val keyStore = loadOrCreateKeyStore(keyStorePath, password)
|
||||||
|
keyStore.addOrReplaceKey("Key", rpcKeyPair.private, password.toCharArray(), arrayOf(selfSignCert))
|
||||||
|
keyStore.save(keyStorePath, password)
|
||||||
|
return keyStorePath
|
||||||
|
}
|
||||||
|
|
||||||
|
fun saveToTrustStore(trustStorePath: Path, selfSignCert: X509Certificate, password: String = "password"): Path {
|
||||||
|
val trustStore = loadOrCreateKeyStore(trustStorePath, password)
|
||||||
|
trustStore.addOrReplaceCertificate("Key", selfSignCert)
|
||||||
|
trustStore.save(trustStorePath, password)
|
||||||
|
return trustStorePath
|
||||||
|
}
|
@ -3,17 +3,22 @@ package net.corda.tools.shell
|
|||||||
import com.google.common.io.Files
|
import com.google.common.io.Files
|
||||||
import com.jcraft.jsch.ChannelExec
|
import com.jcraft.jsch.ChannelExec
|
||||||
import com.jcraft.jsch.JSch
|
import com.jcraft.jsch.JSch
|
||||||
import net.corda.core.identity.CordaX500Name
|
import net.corda.core.internal.div
|
||||||
import net.corda.core.messaging.CordaRPCOps
|
import net.corda.core.messaging.CordaRPCOps
|
||||||
import net.corda.core.utilities.getOrThrow
|
import net.corda.core.utilities.getOrThrow
|
||||||
import net.corda.node.services.Permissions
|
import net.corda.node.services.Permissions
|
||||||
import net.corda.node.services.Permissions.Companion.all
|
import net.corda.node.services.Permissions.Companion.all
|
||||||
import net.corda.testing.common.internal.withCertificates
|
import net.corda.node.services.config.shell.toShellConfig
|
||||||
import net.corda.testing.common.internal.withKeyStores
|
import net.corda.nodeapi.BrokerRpcSslOptions
|
||||||
|
import net.corda.core.messaging.ClientRpcSslOptions
|
||||||
import net.corda.testing.core.ALICE_NAME
|
import net.corda.testing.core.ALICE_NAME
|
||||||
import net.corda.testing.driver.DriverParameters
|
import net.corda.testing.driver.DriverParameters
|
||||||
import net.corda.testing.driver.driver
|
import net.corda.testing.driver.driver
|
||||||
|
import net.corda.testing.driver.internal.NodeHandleInternal
|
||||||
import net.corda.testing.driver.internal.RandomFree
|
import net.corda.testing.driver.internal.RandomFree
|
||||||
|
import net.corda.testing.internal.createKeyPairAndSelfSignedCertificate
|
||||||
|
import net.corda.testing.internal.saveToKeyStore
|
||||||
|
import net.corda.testing.internal.saveToTrustStore
|
||||||
import net.corda.testing.internal.useSslRpcOverrides
|
import net.corda.testing.internal.useSslRpcOverrides
|
||||||
import net.corda.testing.node.User
|
import net.corda.testing.node.User
|
||||||
import org.apache.activemq.artemis.api.core.ActiveMQNotConnectedException
|
import org.apache.activemq.artemis.api.core.ActiveMQNotConnectedException
|
||||||
@ -22,11 +27,17 @@ import org.assertj.core.api.Assertions.assertThat
|
|||||||
import org.assertj.core.api.Assertions.assertThatThrownBy
|
import org.assertj.core.api.Assertions.assertThatThrownBy
|
||||||
import org.bouncycastle.util.io.Streams
|
import org.bouncycastle.util.io.Streams
|
||||||
import org.junit.Ignore
|
import org.junit.Ignore
|
||||||
|
import org.junit.Rule
|
||||||
import org.junit.Test
|
import org.junit.Test
|
||||||
|
import org.junit.rules.TemporaryFolder
|
||||||
import kotlin.test.assertTrue
|
import kotlin.test.assertTrue
|
||||||
|
|
||||||
class InteractiveShellIntegrationTest {
|
class InteractiveShellIntegrationTest {
|
||||||
|
|
||||||
|
@Rule
|
||||||
|
@JvmField
|
||||||
|
val tempFolder = TemporaryFolder()
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun `shell should not log in with invalid credentials`() {
|
fun `shell should not log in with invalid credentials`() {
|
||||||
val user = User("u", "p", setOf())
|
val user = User("u", "p", setOf())
|
||||||
@ -44,7 +55,7 @@ class InteractiveShellIntegrationTest {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun `shell should log in with valid crentials`() {
|
fun `shell should log in with valid credentials`() {
|
||||||
val user = User("u", "p", setOf())
|
val user = User("u", "p", setOf())
|
||||||
driver {
|
driver {
|
||||||
val nodeFuture = startNode(providedName = ALICE_NAME, rpcUsers = listOf(user), startInSameProcess = true)
|
val nodeFuture = startNode(providedName = ALICE_NAME, rpcUsers = listOf(user), startInSameProcess = true)
|
||||||
@ -62,29 +73,22 @@ class InteractiveShellIntegrationTest {
|
|||||||
@Test
|
@Test
|
||||||
fun `shell should log in with ssl`() {
|
fun `shell should log in with ssl`() {
|
||||||
val user = User("mark", "dadada", setOf(all()))
|
val user = User("mark", "dadada", setOf(all()))
|
||||||
withCertificates { server, client, createSelfSigned, createSignedBy ->
|
|
||||||
val rootCertificate = createSelfSigned(CordaX500Name("SystemUsers/Node", "IT", "R3 London", "London", "London", "GB"))
|
|
||||||
val markCertificate = createSignedBy(CordaX500Name("shell", "IT", "R3 London", "London", "London", "GB"), rootCertificate)
|
|
||||||
|
|
||||||
// truststore needs to contain root CA for how the driver works...
|
|
||||||
server.keyStore["cordaclienttls"] = rootCertificate
|
|
||||||
server.trustStore["cordaclienttls"] = rootCertificate
|
|
||||||
server.trustStore["shell"] = markCertificate
|
|
||||||
|
|
||||||
client.keyStore["shell"] = markCertificate
|
|
||||||
client.trustStore["cordaclienttls"] = rootCertificate
|
|
||||||
|
|
||||||
withKeyStores(server, client) { nodeSslOptions, clientSslOptions ->
|
|
||||||
var successful = false
|
var successful = false
|
||||||
driver(DriverParameters(isDebug = true, startNodesInProcess = true, portAllocation = RandomFree)) {
|
|
||||||
startNode(rpcUsers = listOf(user), customOverrides = nodeSslOptions.useSslRpcOverrides()).getOrThrow().use { node ->
|
|
||||||
|
|
||||||
val sslConfiguration = ShellSslOptions(clientSslOptions.sslKeystore, clientSslOptions.keyStorePassword,
|
val (keyPair, cert) = createKeyPairAndSelfSignedCertificate()
|
||||||
clientSslOptions.trustStoreFile, clientSslOptions.trustStorePassword, clientSslOptions.crlCheckSoftFail)
|
val keyStorePath = saveToKeyStore(tempFolder.root.toPath() / "keystore.jks", keyPair, cert)
|
||||||
|
val brokerSslOptions = BrokerRpcSslOptions(keyStorePath, "password")
|
||||||
|
|
||||||
|
val trustStorePath = saveToTrustStore(tempFolder.root.toPath() / "truststore.jks", cert)
|
||||||
|
val clientSslOptions = ClientRpcSslOptions(trustStorePath, "password")
|
||||||
|
|
||||||
|
driver(DriverParameters(isDebug = true, startNodesInProcess = true, portAllocation = RandomFree)) {
|
||||||
|
startNode(rpcUsers = listOf(user), customOverrides = brokerSslOptions.useSslRpcOverrides()).getOrThrow().use { node ->
|
||||||
|
|
||||||
val conf = ShellConfiguration(commandsDirectory = Files.createTempDir().toPath(),
|
val conf = ShellConfiguration(commandsDirectory = Files.createTempDir().toPath(),
|
||||||
user = user.username, password = user.password,
|
user = user.username, password = user.password,
|
||||||
hostAndPort = node.rpcAddress,
|
hostAndPort = node.rpcAddress,
|
||||||
ssl = sslConfiguration)
|
ssl = clientSslOptions)
|
||||||
|
|
||||||
InteractiveShell.startShell(conf)
|
InteractiveShell.startShell(conf)
|
||||||
|
|
||||||
@ -94,34 +98,25 @@ class InteractiveShellIntegrationTest {
|
|||||||
}
|
}
|
||||||
assertThat(successful).isTrue()
|
assertThat(successful).isTrue()
|
||||||
}
|
}
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun `shell shoud not log in without ssl keystore`() {
|
fun `shell shoud not log in with invalid truststore`() {
|
||||||
val user = User("mark", "dadada", setOf("ALL"))
|
val user = User("mark", "dadada", setOf("ALL"))
|
||||||
withCertificates { server, client, createSelfSigned, createSignedBy ->
|
val (keyPair, cert) = createKeyPairAndSelfSignedCertificate()
|
||||||
val rootCertificate = createSelfSigned(CordaX500Name("SystemUsers/Node", "IT", "R3 London", "London", "London", "GB"))
|
val keyStorePath = saveToKeyStore(tempFolder.root.toPath() / "keystore.jks", keyPair, cert)
|
||||||
val markCertificate = createSignedBy(CordaX500Name("shell", "IT", "R3 London", "London", "London", "GB"), rootCertificate)
|
val brokerSslOptions = BrokerRpcSslOptions(keyStorePath, "password")
|
||||||
|
|
||||||
// truststore needs to contain root CA for how the driver works...
|
val (_, cert1) = createKeyPairAndSelfSignedCertificate()
|
||||||
server.keyStore["cordaclienttls"] = rootCertificate
|
val trustStorePath = saveToTrustStore(tempFolder.root.toPath() / "truststore.jks", cert1)
|
||||||
server.trustStore["cordaclienttls"] = rootCertificate
|
val clientSslOptions = ClientRpcSslOptions(trustStorePath, "password")
|
||||||
server.trustStore["shell"] = markCertificate
|
|
||||||
|
|
||||||
//client key store doesn't have "mark" certificate
|
|
||||||
client.trustStore["cordaclienttls"] = rootCertificate
|
|
||||||
|
|
||||||
withKeyStores(server, client) { nodeSslOptions, clientSslOptions ->
|
|
||||||
driver(DriverParameters(isDebug = true, startNodesInProcess = true, portAllocation = RandomFree)) {
|
driver(DriverParameters(isDebug = true, startNodesInProcess = true, portAllocation = RandomFree)) {
|
||||||
startNode(rpcUsers = listOf(user), customOverrides = nodeSslOptions.useSslRpcOverrides()).getOrThrow().use { node ->
|
startNode(rpcUsers = listOf(user), customOverrides = brokerSslOptions.useSslRpcOverrides()).getOrThrow().use { node ->
|
||||||
|
|
||||||
val sslConfiguration = ShellSslOptions(clientSslOptions.sslKeystore, clientSslOptions.keyStorePassword,
|
|
||||||
clientSslOptions.trustStoreFile, clientSslOptions.trustStorePassword, clientSslOptions.crlCheckSoftFail)
|
|
||||||
val conf = ShellConfiguration(commandsDirectory = Files.createTempDir().toPath(),
|
val conf = ShellConfiguration(commandsDirectory = Files.createTempDir().toPath(),
|
||||||
user = user.username, password = user.password,
|
user = user.username, password = user.password,
|
||||||
hostAndPort = node.rpcAddress,
|
hostAndPort = node.rpcAddress,
|
||||||
ssl = sslConfiguration)
|
ssl = clientSslOptions)
|
||||||
|
|
||||||
InteractiveShell.startShell(conf)
|
InteractiveShell.startShell(conf)
|
||||||
|
|
||||||
@ -129,6 +124,15 @@ class InteractiveShellIntegrationTest {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `internal shell user should not be able to connect if node started with devMode=false`() {
|
||||||
|
driver(DriverParameters(isDebug = true, startNodesInProcess = true, portAllocation = RandomFree)) {
|
||||||
|
startNode().getOrThrow().use { node ->
|
||||||
|
val conf = (node as NodeHandleInternal).configuration.toShellConfig()
|
||||||
|
InteractiveShell.startShellInternal(conf)
|
||||||
|
assertThatThrownBy { InteractiveShell.nodeInfo() }.isInstanceOf(ActiveMQSecurityException::class.java)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -181,29 +185,21 @@ class InteractiveShellIntegrationTest {
|
|||||||
val user = User("mark", "dadada", setOf(Permissions.startFlow<SSHServerTest.FlowICanRun>(),
|
val user = User("mark", "dadada", setOf(Permissions.startFlow<SSHServerTest.FlowICanRun>(),
|
||||||
Permissions.invokeRpc(CordaRPCOps::registeredFlows),
|
Permissions.invokeRpc(CordaRPCOps::registeredFlows),
|
||||||
Permissions.invokeRpc(CordaRPCOps::nodeInfo)/*all()*/))
|
Permissions.invokeRpc(CordaRPCOps::nodeInfo)/*all()*/))
|
||||||
withCertificates { server, client, createSelfSigned, createSignedBy ->
|
|
||||||
val rootCertificate = createSelfSigned(CordaX500Name("SystemUsers/Node", "IT", "R3 London", "London", "London", "GB"))
|
|
||||||
val markCertificate = createSignedBy(CordaX500Name("shell", "IT", "R3 London", "London", "London", "GB"), rootCertificate)
|
|
||||||
|
|
||||||
// truststore needs to contain root CA for how the driver works...
|
val (keyPair, cert) = createKeyPairAndSelfSignedCertificate()
|
||||||
server.keyStore["cordaclienttls"] = rootCertificate
|
val keyStorePath = saveToKeyStore(tempFolder.root.toPath() / "keystore.jks", keyPair, cert)
|
||||||
server.trustStore["cordaclienttls"] = rootCertificate
|
val brokerSslOptions = BrokerRpcSslOptions(keyStorePath, "password")
|
||||||
server.trustStore["shell"] = markCertificate
|
val trustStorePath = saveToTrustStore(tempFolder.root.toPath() / "truststore.jks", cert)
|
||||||
|
val clientSslOptions = ClientRpcSslOptions(trustStorePath, "password")
|
||||||
|
|
||||||
client.keyStore["shell"] = markCertificate
|
|
||||||
client.trustStore["cordaclienttls"] = rootCertificate
|
|
||||||
|
|
||||||
withKeyStores(server, client) { nodeSslOptions, clientSslOptions ->
|
|
||||||
var successful = false
|
var successful = false
|
||||||
driver(DriverParameters(isDebug = true, startNodesInProcess = true, portAllocation = RandomFree)) {
|
driver(DriverParameters(isDebug = true, startNodesInProcess = true, portAllocation = RandomFree)) {
|
||||||
startNode(rpcUsers = listOf(user), customOverrides = nodeSslOptions.useSslRpcOverrides()).getOrThrow().use { node ->
|
startNode(rpcUsers = listOf(user), customOverrides = brokerSslOptions.useSslRpcOverrides()).getOrThrow().use { node ->
|
||||||
|
|
||||||
val sslConfiguration = ShellSslOptions(clientSslOptions.sslKeystore, clientSslOptions.keyStorePassword,
|
|
||||||
clientSslOptions.trustStoreFile, clientSslOptions.trustStorePassword, clientSslOptions.crlCheckSoftFail)
|
|
||||||
val conf = ShellConfiguration(commandsDirectory = Files.createTempDir().toPath(),
|
val conf = ShellConfiguration(commandsDirectory = Files.createTempDir().toPath(),
|
||||||
user = user.username, password = user.password,
|
user = user.username, password = user.password,
|
||||||
hostAndPort = node.rpcAddress,
|
hostAndPort = node.rpcAddress,
|
||||||
ssl = sslConfiguration,
|
ssl = clientSslOptions,
|
||||||
sshdPort = 2223)
|
sshdPort = 2223)
|
||||||
|
|
||||||
InteractiveShell.startShell(conf)
|
InteractiveShell.startShell(conf)
|
||||||
@ -234,9 +230,9 @@ class InteractiveShellIntegrationTest {
|
|||||||
|
|
||||||
successful = true
|
successful = true
|
||||||
}
|
}
|
||||||
}
|
|
||||||
assertThat(successful).isTrue()
|
assertThat(successful).isTrue()
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -9,6 +9,7 @@ import net.corda.client.jackson.StringToMethodCallParser
|
|||||||
import net.corda.client.rpc.CordaRPCClientConfiguration
|
import net.corda.client.rpc.CordaRPCClientConfiguration
|
||||||
import net.corda.client.rpc.CordaRPCConnection
|
import net.corda.client.rpc.CordaRPCConnection
|
||||||
import net.corda.client.rpc.PermissionException
|
import net.corda.client.rpc.PermissionException
|
||||||
|
import net.corda.client.rpc.internal.createCordaRPCClientWithInternalSslAndClassLoader
|
||||||
import net.corda.client.rpc.internal.createCordaRPCClientWithSslAndClassLoader
|
import net.corda.client.rpc.internal.createCordaRPCClientWithSslAndClassLoader
|
||||||
import net.corda.core.CordaException
|
import net.corda.core.CordaException
|
||||||
import net.corda.core.concurrent.CordaFuture
|
import net.corda.core.concurrent.CordaFuture
|
||||||
@ -80,7 +81,6 @@ object InteractiveShell {
|
|||||||
* internals.
|
* internals.
|
||||||
*/
|
*/
|
||||||
fun startShell(configuration: ShellConfiguration, classLoader: ClassLoader? = null) {
|
fun startShell(configuration: ShellConfiguration, classLoader: ClassLoader? = null) {
|
||||||
shellConfiguration = configuration
|
|
||||||
rpcOps = { username: String, credentials: String ->
|
rpcOps = { username: String, credentials: String ->
|
||||||
val client = createCordaRPCClientWithSslAndClassLoader(hostAndPort = configuration.hostAndPort,
|
val client = createCordaRPCClientWithSslAndClassLoader(hostAndPort = configuration.hostAndPort,
|
||||||
configuration = object : CordaRPCClientConfiguration {
|
configuration = object : CordaRPCClientConfiguration {
|
||||||
@ -91,6 +91,29 @@ object InteractiveShell {
|
|||||||
this.connection = client.start(username, credentials)
|
this.connection = client.start(username, credentials)
|
||||||
connection.proxy
|
connection.proxy
|
||||||
}
|
}
|
||||||
|
_startShell(configuration, classLoader)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Starts an interactive shell connected to the local terminal. This shell gives administrator access to the node
|
||||||
|
* internals.
|
||||||
|
*/
|
||||||
|
fun startShellInternal(configuration: ShellConfiguration, classLoader: ClassLoader? = null) {
|
||||||
|
rpcOps = { username: String, credentials: String ->
|
||||||
|
val client = createCordaRPCClientWithInternalSslAndClassLoader(hostAndPort = configuration.hostAndPort,
|
||||||
|
configuration = object : CordaRPCClientConfiguration {
|
||||||
|
override val maxReconnectAttempts = 1
|
||||||
|
},
|
||||||
|
sslConfiguration = configuration.nodeSslConfig,
|
||||||
|
classLoader = classLoader)
|
||||||
|
this.connection = client.start(username, credentials)
|
||||||
|
connection.proxy
|
||||||
|
}
|
||||||
|
_startShell(configuration, classLoader)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun _startShell(configuration: ShellConfiguration, classLoader: ClassLoader? = null) {
|
||||||
|
shellConfiguration = configuration
|
||||||
InteractiveShell.classLoader = classLoader
|
InteractiveShell.classLoader = classLoader
|
||||||
val runSshDaemon = configuration.sshdPort != null
|
val runSshDaemon = configuration.sshdPort != null
|
||||||
|
|
||||||
|
@ -1,9 +1,9 @@
|
|||||||
package net.corda.tools.shell
|
package net.corda.tools.shell
|
||||||
|
|
||||||
import net.corda.core.utilities.NetworkHostAndPort
|
import net.corda.core.utilities.NetworkHostAndPort
|
||||||
|
import net.corda.core.messaging.ClientRpcSslOptions
|
||||||
import net.corda.nodeapi.internal.config.SSLConfiguration
|
import net.corda.nodeapi.internal.config.SSLConfiguration
|
||||||
import java.nio.file.Path
|
import java.nio.file.Path
|
||||||
import java.nio.file.Paths
|
|
||||||
|
|
||||||
data class ShellConfiguration(
|
data class ShellConfiguration(
|
||||||
val commandsDirectory: Path,
|
val commandsDirectory: Path,
|
||||||
@ -11,7 +11,8 @@ data class ShellConfiguration(
|
|||||||
var user: String = "",
|
var user: String = "",
|
||||||
var password: String = "",
|
var password: String = "",
|
||||||
val hostAndPort: NetworkHostAndPort,
|
val hostAndPort: NetworkHostAndPort,
|
||||||
val ssl: ShellSslOptions? = null,
|
val ssl: ClientRpcSslOptions? = null,
|
||||||
|
val nodeSslConfig: SSLConfiguration? = null,
|
||||||
val sshdPort: Int? = null,
|
val sshdPort: Int? = null,
|
||||||
val sshHostKeyDirectory: Path? = null,
|
val sshHostKeyDirectory: Path? = null,
|
||||||
val noLocalShell: Boolean = false) {
|
val noLocalShell: Boolean = false) {
|
||||||
@ -22,13 +23,3 @@ data class ShellConfiguration(
|
|||||||
const val SSHD_HOSTKEY_DIR = "ssh"
|
const val SSHD_HOSTKEY_DIR = "ssh"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
//TODO: sslKeystore -> it's a path not the keystore itself.
|
|
||||||
//TODO: trustStoreFile -> it's a path not the file itself.
|
|
||||||
data class ShellSslOptions(override val sslKeystore: Path,
|
|
||||||
override val keyStorePassword: String,
|
|
||||||
override val trustStoreFile: Path,
|
|
||||||
override val trustStorePassword: String,
|
|
||||||
override val crlCheckSoftFail: Boolean) : SSLConfiguration {
|
|
||||||
override val certificatesDirectory: Path get() = Paths.get("")
|
|
||||||
}
|
|
@ -6,6 +6,7 @@ import joptsimple.OptionParser
|
|||||||
import joptsimple.util.EnumConverter
|
import joptsimple.util.EnumConverter
|
||||||
import net.corda.core.internal.div
|
import net.corda.core.internal.div
|
||||||
import net.corda.core.utilities.NetworkHostAndPort
|
import net.corda.core.utilities.NetworkHostAndPort
|
||||||
|
import net.corda.core.messaging.ClientRpcSslOptions
|
||||||
import net.corda.nodeapi.internal.config.parseAs
|
import net.corda.nodeapi.internal.config.parseAs
|
||||||
import net.corda.tools.shell.ShellConfiguration.Companion.COMMANDS_DIR
|
import net.corda.tools.shell.ShellConfiguration.Companion.COMMANDS_DIR
|
||||||
import org.slf4j.event.Level
|
import org.slf4j.event.Level
|
||||||
@ -52,15 +53,6 @@ class CommandLineOptionParser {
|
|||||||
private val helpArg = optionParser
|
private val helpArg = optionParser
|
||||||
.accepts("help")
|
.accepts("help")
|
||||||
.forHelp()
|
.forHelp()
|
||||||
private val keyStorePasswordArg = optionParser
|
|
||||||
.accepts("keystore-password", "The password to unlock the KeyStore file.")
|
|
||||||
.withOptionalArg()
|
|
||||||
private val keyStoreDirArg = optionParser
|
|
||||||
.accepts("keystore-file", "The path to the KeyStore file.")
|
|
||||||
.withOptionalArg()
|
|
||||||
private val keyStoreTypeArg = optionParser
|
|
||||||
.accepts("keystore-type", "The type of the KeyStore (e.g. JKS).")
|
|
||||||
.withOptionalArg()
|
|
||||||
private val trustStorePasswordArg = optionParser
|
private val trustStorePasswordArg = optionParser
|
||||||
.accepts("truststore-password", "The password to unlock the TrustStore file.")
|
.accepts("truststore-password", "The password to unlock the TrustStore file.")
|
||||||
.withOptionalArg()
|
.withOptionalArg()
|
||||||
@ -85,11 +77,8 @@ class CommandLineOptionParser {
|
|||||||
loggingLevel = optionSet.valueOf(loggerLevel),
|
loggingLevel = optionSet.valueOf(loggerLevel),
|
||||||
sshdPort = optionSet.valueOf(sshdPortArg),
|
sshdPort = optionSet.valueOf(sshdPortArg),
|
||||||
sshdHostKeyDirectory = (optionSet.valueOf(sshdHostKeyDirectoryArg))?.let { Paths.get(it).normalize().toAbsolutePath() },
|
sshdHostKeyDirectory = (optionSet.valueOf(sshdHostKeyDirectoryArg))?.let { Paths.get(it).normalize().toAbsolutePath() },
|
||||||
keyStorePassword = optionSet.valueOf(keyStorePasswordArg),
|
|
||||||
trustStorePassword = optionSet.valueOf(trustStorePasswordArg),
|
trustStorePassword = optionSet.valueOf(trustStorePasswordArg),
|
||||||
keyStoreFile = (optionSet.valueOf(keyStoreDirArg))?.let { Paths.get(it).normalize().toAbsolutePath() },
|
|
||||||
trustStoreFile = (optionSet.valueOf(trustStoreDirArg))?.let { Paths.get(it).normalize().toAbsolutePath() },
|
trustStoreFile = (optionSet.valueOf(trustStoreDirArg))?.let { Paths.get(it).normalize().toAbsolutePath() },
|
||||||
keyStoreType = optionSet.valueOf(keyStoreTypeArg),
|
|
||||||
trustStoreType = optionSet.valueOf(trustStoreTypeArg))
|
trustStoreType = optionSet.valueOf(trustStoreTypeArg))
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -107,11 +96,8 @@ data class CommandLineOptions(val configFile: String?,
|
|||||||
val loggingLevel: Level,
|
val loggingLevel: Level,
|
||||||
val sshdPort: String?,
|
val sshdPort: String?,
|
||||||
val sshdHostKeyDirectory: Path?,
|
val sshdHostKeyDirectory: Path?,
|
||||||
val keyStorePassword: String?,
|
|
||||||
val trustStorePassword: String?,
|
val trustStorePassword: String?,
|
||||||
val keyStoreFile: Path?,
|
|
||||||
val trustStoreFile: Path?,
|
val trustStoreFile: Path?,
|
||||||
val keyStoreType: String?,
|
|
||||||
val trustStoreType: String?) {
|
val trustStoreType: String?) {
|
||||||
|
|
||||||
private fun toConfigFile(): Config {
|
private fun toConfigFile(): Config {
|
||||||
@ -123,9 +109,6 @@ data class CommandLineOptions(val configFile: String?,
|
|||||||
password?.apply { cmdOpts["node.password"] = this }
|
password?.apply { cmdOpts["node.password"] = this }
|
||||||
host?.apply { cmdOpts["node.addresses.rpc.host"] = this }
|
host?.apply { cmdOpts["node.addresses.rpc.host"] = this }
|
||||||
port?.apply { cmdOpts["node.addresses.rpc.port"] = this }
|
port?.apply { cmdOpts["node.addresses.rpc.port"] = this }
|
||||||
keyStoreFile?.apply { cmdOpts["ssl.keystore.path"] = this.toString() }
|
|
||||||
keyStorePassword?.apply { cmdOpts["ssl.keystore.password"] = this }
|
|
||||||
keyStoreType?.apply { cmdOpts["ssl.keystore.type"] = this }
|
|
||||||
trustStoreFile?.apply { cmdOpts["ssl.truststore.path"] = this.toString() }
|
trustStoreFile?.apply { cmdOpts["ssl.truststore.path"] = this.toString() }
|
||||||
trustStorePassword?.apply { cmdOpts["ssl.truststore.password"] = this }
|
trustStorePassword?.apply { cmdOpts["ssl.truststore.password"] = this }
|
||||||
trustStoreType?.apply { cmdOpts["ssl.truststore.type"] = this }
|
trustStoreType?.apply { cmdOpts["ssl.truststore.type"] = this }
|
||||||
@ -187,12 +170,11 @@ private class ShellConfigurationFile {
|
|||||||
|
|
||||||
data class KeyStore(
|
data class KeyStore(
|
||||||
val path: String,
|
val path: String,
|
||||||
val type: String,
|
val type: String = "JKS",
|
||||||
val password: String
|
val password: String
|
||||||
)
|
)
|
||||||
|
|
||||||
data class Ssl(
|
data class Ssl(
|
||||||
val keystore: KeyStore,
|
|
||||||
val truststore: KeyStore
|
val truststore: KeyStore
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -205,12 +187,9 @@ private class ShellConfigurationFile {
|
|||||||
|
|
||||||
val sslOptions =
|
val sslOptions =
|
||||||
ssl?.let {
|
ssl?.let {
|
||||||
ShellSslOptions(
|
ClientRpcSslOptions(
|
||||||
sslKeystore = Paths.get(it.keystore.path),
|
trustStorePath = Paths.get(it.truststore.path),
|
||||||
keyStorePassword = it.keystore.password,
|
trustStorePassword = it.truststore.password)
|
||||||
trustStoreFile = Paths.get(it.truststore.path),
|
|
||||||
trustStorePassword = it.truststore.password,
|
|
||||||
crlCheckSoftFail = true)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return ShellConfiguration(
|
return ShellConfiguration(
|
||||||
|
@ -2,6 +2,7 @@ package net.corda.tools.shell
|
|||||||
|
|
||||||
import net.corda.core.internal.toPath
|
import net.corda.core.internal.toPath
|
||||||
import net.corda.core.utilities.NetworkHostAndPort
|
import net.corda.core.utilities.NetworkHostAndPort
|
||||||
|
import net.corda.core.messaging.ClientRpcSslOptions
|
||||||
import org.assertj.core.api.Assertions.assertThat
|
import org.assertj.core.api.Assertions.assertThat
|
||||||
import org.junit.Test
|
import org.junit.Test
|
||||||
import org.slf4j.event.Level
|
import org.slf4j.event.Level
|
||||||
@ -24,12 +25,9 @@ class StandaloneShellArgsParserTest {
|
|||||||
"--sshd-port", "2223",
|
"--sshd-port", "2223",
|
||||||
"--sshd-hostkey-directory", "/x/y/ssh",
|
"--sshd-hostkey-directory", "/x/y/ssh",
|
||||||
"--help",
|
"--help",
|
||||||
"--keystore-password", "pass1",
|
|
||||||
"--truststore-password", "pass2",
|
"--truststore-password", "pass2",
|
||||||
"--keystore-file", "/x/y/keystore.jks",
|
|
||||||
"--truststore-file", "/x/y/truststore.jks",
|
"--truststore-file", "/x/y/truststore.jks",
|
||||||
"--truststore-type", "dummy",
|
"--truststore-type", "dummy")
|
||||||
"--keystore-type", "JKS")
|
|
||||||
|
|
||||||
val expectedOptions = CommandLineOptions(
|
val expectedOptions = CommandLineOptions(
|
||||||
configFile = "/x/y/z/config.conf",
|
configFile = "/x/y/z/config.conf",
|
||||||
@ -43,12 +41,9 @@ class StandaloneShellArgsParserTest {
|
|||||||
loggingLevel = Level.DEBUG,
|
loggingLevel = Level.DEBUG,
|
||||||
sshdPort = "2223",
|
sshdPort = "2223",
|
||||||
sshdHostKeyDirectory = Paths.get("/x/y/ssh").normalize().toAbsolutePath(),
|
sshdHostKeyDirectory = Paths.get("/x/y/ssh").normalize().toAbsolutePath(),
|
||||||
keyStorePassword = "pass1",
|
|
||||||
trustStorePassword = "pass2",
|
trustStorePassword = "pass2",
|
||||||
keyStoreFile = Paths.get("/x/y/keystore.jks").normalize().toAbsolutePath(),
|
|
||||||
trustStoreFile = Paths.get("/x/y/truststore.jks").normalize().toAbsolutePath(),
|
trustStoreFile = Paths.get("/x/y/truststore.jks").normalize().toAbsolutePath(),
|
||||||
trustStoreType = "dummy",
|
trustStoreType = "dummy")
|
||||||
keyStoreType = "JKS")
|
|
||||||
|
|
||||||
val options = CommandLineOptionParser().parse(*args)
|
val options = CommandLineOptionParser().parse(*args)
|
||||||
|
|
||||||
@ -70,12 +65,9 @@ class StandaloneShellArgsParserTest {
|
|||||||
loggingLevel = Level.INFO,
|
loggingLevel = Level.INFO,
|
||||||
sshdPort = null,
|
sshdPort = null,
|
||||||
sshdHostKeyDirectory = null,
|
sshdHostKeyDirectory = null,
|
||||||
keyStorePassword = null,
|
|
||||||
trustStorePassword = null,
|
trustStorePassword = null,
|
||||||
keyStoreFile = null,
|
|
||||||
trustStoreFile = null,
|
trustStoreFile = null,
|
||||||
trustStoreType = null,
|
trustStoreType = null)
|
||||||
keyStoreType = null)
|
|
||||||
|
|
||||||
val options = CommandLineOptionParser().parse(*args)
|
val options = CommandLineOptionParser().parse(*args)
|
||||||
|
|
||||||
@ -96,19 +88,14 @@ class StandaloneShellArgsParserTest {
|
|||||||
loggingLevel = Level.DEBUG,
|
loggingLevel = Level.DEBUG,
|
||||||
sshdPort = "2223",
|
sshdPort = "2223",
|
||||||
sshdHostKeyDirectory = Paths.get("/x/y/ssh"),
|
sshdHostKeyDirectory = Paths.get("/x/y/ssh"),
|
||||||
keyStorePassword = "pass1",
|
|
||||||
trustStorePassword = "pass2",
|
trustStorePassword = "pass2",
|
||||||
keyStoreFile = Paths.get("/x/y/keystore.jks"),
|
|
||||||
trustStoreFile = Paths.get("/x/y/truststore.jks"),
|
trustStoreFile = Paths.get("/x/y/truststore.jks"),
|
||||||
keyStoreType = "dummy",
|
|
||||||
trustStoreType = "dummy"
|
trustStoreType = "dummy"
|
||||||
)
|
)
|
||||||
|
|
||||||
val expectedSsl = ShellSslOptions(sslKeystore = Paths.get("/x/y/keystore.jks"),
|
val expectedSsl = ClientRpcSslOptions(
|
||||||
keyStorePassword = "pass1",
|
trustStorePath = Paths.get("/x/y/truststore.jks"),
|
||||||
trustStoreFile = Paths.get("/x/y/truststore.jks"),
|
trustStorePassword = "pass2")
|
||||||
trustStorePassword = "pass2",
|
|
||||||
crlCheckSoftFail = true)
|
|
||||||
val expectedConfig = ShellConfiguration(
|
val expectedConfig = ShellConfiguration(
|
||||||
commandsDirectory = Paths.get("/x/y/commands"),
|
commandsDirectory = Paths.get("/x/y/commands"),
|
||||||
cordappsDirectory = Paths.get("/x/y/cordapps"),
|
cordappsDirectory = Paths.get("/x/y/cordapps"),
|
||||||
@ -139,25 +126,19 @@ class StandaloneShellArgsParserTest {
|
|||||||
loggingLevel = Level.DEBUG,
|
loggingLevel = Level.DEBUG,
|
||||||
sshdPort = null,
|
sshdPort = null,
|
||||||
sshdHostKeyDirectory = null,
|
sshdHostKeyDirectory = null,
|
||||||
keyStorePassword = null,
|
|
||||||
trustStorePassword = null,
|
trustStorePassword = null,
|
||||||
keyStoreFile = null,
|
|
||||||
trustStoreFile = null,
|
trustStoreFile = null,
|
||||||
keyStoreType = null,
|
|
||||||
trustStoreType = null)
|
trustStoreType = null)
|
||||||
|
|
||||||
val expectedSsl = ShellSslOptions(sslKeystore = Paths.get("/x/y/keystore.jks"),
|
|
||||||
keyStorePassword = "pass1",
|
|
||||||
trustStoreFile = Paths.get("/x/y/truststore.jks"),
|
|
||||||
trustStorePassword = "pass2",
|
|
||||||
crlCheckSoftFail = true)
|
|
||||||
val expectedConfig = ShellConfiguration(
|
val expectedConfig = ShellConfiguration(
|
||||||
commandsDirectory = Paths.get("/x/y/commands"),
|
commandsDirectory = Paths.get("/x/y/commands"),
|
||||||
cordappsDirectory = Paths.get("/x/y/cordapps"),
|
cordappsDirectory = Paths.get("/x/y/cordapps"),
|
||||||
user = "demo",
|
user = "demo",
|
||||||
password = "abcd1234",
|
password = "abcd1234",
|
||||||
hostAndPort = NetworkHostAndPort("alocalhost", 1234),
|
hostAndPort = NetworkHostAndPort("alocalhost", 1234),
|
||||||
ssl = expectedSsl,
|
ssl = ClientRpcSslOptions(
|
||||||
|
trustStorePath = Paths.get("/x/y/truststore.jks"),
|
||||||
|
trustStorePassword = "pass2"),
|
||||||
sshdPort = 2223)
|
sshdPort = 2223)
|
||||||
|
|
||||||
val config = options.toConfig()
|
val config = options.toConfig()
|
||||||
@ -179,18 +160,13 @@ class StandaloneShellArgsParserTest {
|
|||||||
loggingLevel = Level.DEBUG,
|
loggingLevel = Level.DEBUG,
|
||||||
sshdPort = null,
|
sshdPort = null,
|
||||||
sshdHostKeyDirectory = null,
|
sshdHostKeyDirectory = null,
|
||||||
keyStorePassword = null,
|
|
||||||
trustStorePassword = null,
|
trustStorePassword = null,
|
||||||
keyStoreFile = Paths.get("/x/y/cmd.jks"),
|
|
||||||
trustStoreFile = null,
|
trustStoreFile = null,
|
||||||
keyStoreType = null,
|
|
||||||
trustStoreType = null)
|
trustStoreType = null)
|
||||||
|
|
||||||
val expectedSsl = ShellSslOptions(sslKeystore = Paths.get("/x/y/cmd.jks"),
|
val expectedSsl = ClientRpcSslOptions(
|
||||||
keyStorePassword = "pass1",
|
trustStorePath = Paths.get("/x/y/truststore.jks"),
|
||||||
trustStoreFile = Paths.get("/x/y/truststore.jks"),
|
trustStorePassword = "pass2")
|
||||||
trustStorePassword = "pass2",
|
|
||||||
crlCheckSoftFail = true)
|
|
||||||
val expectedConfig = ShellConfiguration(
|
val expectedConfig = ShellConfiguration(
|
||||||
commandsDirectory = Paths.get("/x/y/commands"),
|
commandsDirectory = Paths.get("/x/y/commands"),
|
||||||
cordappsDirectory = Paths.get("/x/y/cordapps"),
|
cordappsDirectory = Paths.get("/x/y/cordapps"),
|
||||||
|
@ -21,11 +21,6 @@ extensions {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
ssl {
|
ssl {
|
||||||
keystore {
|
|
||||||
path : "/x/y/keystore.jks"
|
|
||||||
type : "JKS"
|
|
||||||
password : "pass1"
|
|
||||||
}
|
|
||||||
truststore {
|
truststore {
|
||||||
path : "/x/y/truststore.jks"
|
path : "/x/y/truststore.jks"
|
||||||
type : "JKS"
|
type : "JKS"
|
||||||
|
Reference in New Issue
Block a user