Removed SSLConfiguration parameter from CordaRPCClient, thus removing SSL support.

The current use of SSL RPC relies on access to the node's keystore file, and further to that some uses where using the NODE_USER to login on the p2p port.
This commit is contained in:
Shams Asari 2017-09-26 09:11:00 +01:00
parent b4e674c2fe
commit 4df8b427d2
11 changed files with 52 additions and 71 deletions

View File

@ -57,7 +57,7 @@ public class CordaRPCJavaClientTest extends NodeBasedTest {
CordaFuture<StartedNode<Node>> nodeFuture = startNode(getALICE().getName(), 1, services, singletonList(rpcUser), emptyMap());
node = nodeFuture.get();
node.getInternals().registerCustomSchemas(Collections.singleton(CashSchemaV1.INSTANCE));
client = new CordaRPCClient(requireNonNull(node.getInternals().getConfiguration().getRpcAddress()), null, getDefault(), false);
client = new CordaRPCClient(requireNonNull(node.getInternals().getConfiguration().getRpcAddress()), getDefault(), false);
}
@After

View File

@ -7,7 +7,6 @@ import net.corda.core.messaging.CordaRPCOps
import net.corda.core.utilities.NetworkHostAndPort
import net.corda.nodeapi.ArtemisTcpTransport.Companion.tcpTransport
import net.corda.nodeapi.ConnectionDirection
import net.corda.nodeapi.config.SSLConfiguration
import net.corda.nodeapi.internal.serialization.KRYO_RPC_CLIENT_CONTEXT
import java.time.Duration
@ -34,9 +33,9 @@ data class CordaRPCClientConfiguration(
}
/** @see RPCClient */
//TODO Add SSL support
class CordaRPCClient(
hostAndPort: NetworkHostAndPort,
sslConfiguration: SSLConfiguration? = null,
configuration: CordaRPCClientConfiguration = CordaRPCClientConfiguration.default,
initialiseSerialization: Boolean = true
) {
@ -50,7 +49,7 @@ class CordaRPCClient(
}
private val rpcClient = RPCClient<CordaRPCOps>(
tcpTransport(ConnectionDirection.Outbound(), hostAndPort, sslConfiguration),
tcpTransport(ConnectionDirection.Outbound(), hostAndPort, config = null),
configuration.toRpcClientConfiguration(),
KRYO_RPC_CLIENT_CONTEXT
)

View File

@ -228,6 +228,9 @@ Release 1.0
* ``@RPCSinceVersion``, ``RPCException`` and ``PermissionException`` have moved to ``net.corda.client.rpc``.
* Current implementation of SSL in ``CordaRPCClient`` has been removed until we have a better solution which doesn't rely
on the node's keystore.
Milestone 14
------------

View File

@ -1,17 +1,15 @@
package net.corda.nodeapi
import net.corda.core.identity.CordaX500Name
import net.corda.core.utilities.toBase58String
import net.corda.core.identity.Party
import net.corda.core.messaging.MessageRecipientGroup
import net.corda.core.messaging.MessageRecipients
import net.corda.core.messaging.SingleMessageRecipient
import net.corda.core.internal.read
import net.corda.core.serialization.CordaSerializable
import net.corda.core.serialization.SingletonSerializeAsToken
import net.corda.core.utilities.NetworkHostAndPort
import net.corda.core.utilities.toBase58String
import net.corda.nodeapi.config.SSLConfiguration
import java.security.KeyStore
import java.security.PublicKey
/**
@ -85,22 +83,6 @@ abstract class ArtemisMessagingComponent : SingletonSerializeAsToken() {
/** The config object is used to pass in the passwords for the certificate KeyStore and TrustStore */
abstract val config: SSLConfiguration?
/**
* Returns nothing if the keystore was opened OK or throws if not. Useful to check the password, as
* unfortunately Artemis tends to bury the exception when the password is wrong.
*/
fun checkStorePasswords() {
val config = config ?: return
arrayOf(config.sslKeystore, config.nodeKeystore).forEach {
it.read {
KeyStore.getInstance("JKS").load(it, config.keyStorePassword.toCharArray())
}
}
config.trustStoreFile.read {
KeyStore.getInstance("JKS").load(it, config.trustStorePassword.toCharArray())
}
}
// Used for bridges creation.
fun getArtemisPeerAddress(party: Party, address: NetworkHostAndPort, netMapName: CordaX500Name? = null): ArtemisPeerAddress {
return if (party.name == netMapName) {

View File

@ -1,10 +1,7 @@
package net.corda.services.messaging
import net.corda.nodeapi.User
import net.corda.testing.configureTestSSL
import net.corda.testing.messaging.SimpleMQClient
import org.apache.activemq.artemis.api.core.ActiveMQSecurityException
import org.assertj.core.api.Assertions.assertThatExceptionOfType
import org.junit.Test
/**
@ -26,11 +23,4 @@ class MQSecurityAsRPCTest : MQSecurityTest() {
override fun startAttacker(attacker: SimpleMQClient) {
attacker.start(extraRPCUsers[0].username, extraRPCUsers[0].password, false)
}
@Test
fun `login to a ssl port as a RPC user`() {
assertThatExceptionOfType(ActiveMQSecurityException::class.java).isThrownBy {
loginToRPC(alice.internals.configuration.p2pAddress, extraRPCUsers[0], configureTestSSL())
}
}
}

View File

@ -153,8 +153,8 @@ abstract class MQSecurityTest : NodeBasedTest() {
return client
}
fun loginToRPC(target: NetworkHostAndPort, rpcUser: User, sslConfiguration: SSLConfiguration? = null): CordaRPCOps {
return CordaRPCClient(target, sslConfiguration, initialiseSerialization = false).start(rpcUser.username, rpcUser.password).proxy
fun loginToRPC(target: NetworkHostAndPort, rpcUser: User): CordaRPCOps {
return CordaRPCClient(target, initialiseSerialization = false).start(rpcUser.username, rpcUser.password).proxy
}
fun loginToRPCAndGetClientQueue(): String {

View File

@ -20,15 +20,11 @@ import net.corda.core.utilities.seconds
import net.corda.finance.plugin.registerFinanceJSONMappers
import net.corda.irs.contract.InterestRateSwap
import net.corda.irs.utilities.uploadFile
import net.corda.nodeapi.internal.ServiceInfo
import net.corda.node.services.config.FullNodeConfiguration
import net.corda.node.services.transactions.SimpleNotaryService
import net.corda.nodeapi.User
import net.corda.testing.DUMMY_BANK_A
import net.corda.testing.DUMMY_BANK_B
import net.corda.testing.DUMMY_NOTARY
import net.corda.testing.IntegrationTestCategory
import net.corda.testing.chooseIdentity
import net.corda.nodeapi.internal.ServiceInfo
import net.corda.testing.*
import net.corda.testing.driver.driver
import net.corda.testing.http.HttpApi
import org.apache.commons.io.IOUtils
@ -45,7 +41,7 @@ class IRSDemoTest : IntegrationTestCategory {
val log = loggerFor<IRSDemoTest>()
}
private val rpcUser = User("user", "password", emptySet())
private val rpcUser = User("user", "password", setOf("ALL"))
private val currentDate: LocalDate = LocalDate.now()
private val futureDate: LocalDate = currentDate.plusMonths(6)
private val maxWaitTime: Duration = 60.seconds
@ -58,15 +54,16 @@ class IRSDemoTest : IntegrationTestCategory {
providedName = DUMMY_NOTARY.name,
advertisedServices = setOf(ServiceInfo(SimpleNotaryService.type))),
startNode(providedName = DUMMY_BANK_A.name, rpcUsers = listOf(rpcUser)),
startNode(providedName = DUMMY_BANK_B.name)).map { it.getOrThrow() }
startNode(providedName = DUMMY_BANK_B.name))
.map { it.getOrThrow() }
log.info("All nodes started")
val controllerAddrFuture = startWebserver(controller)
val nodeAAddrFuture = startWebserver(nodeA)
val nodeBAddrFuture = startWebserver(nodeB)
val (controllerAddr, nodeAAddr, nodeBAddr) =
listOf(controllerAddrFuture, nodeAAddrFuture, nodeBAddrFuture).map { it.getOrThrow().listenAddress }
val (controllerAddr, nodeAAddr, nodeBAddr) = listOf(
startWebserver(controller),
startWebserver(nodeA),
startWebserver(nodeB))
.map { it.getOrThrow().listenAddress }
log.info("All webservers started")

View File

@ -23,16 +23,14 @@ import net.corda.core.utilities.*
import net.corda.node.internal.Node
import net.corda.node.internal.NodeStartup
import net.corda.node.internal.StartedNode
import net.corda.nodeapi.internal.ServiceInfo
import net.corda.nodeapi.internal.ServiceType
import net.corda.node.services.config.*
import net.corda.node.services.network.NetworkMapService
import net.corda.node.services.transactions.RaftValidatingNotaryService
import net.corda.node.utilities.ServiceIdentityGenerator
import net.corda.nodeapi.ArtemisMessagingComponent
import net.corda.nodeapi.User
import net.corda.nodeapi.config.SSLConfiguration
import net.corda.nodeapi.config.parseAs
import net.corda.nodeapi.internal.ServiceInfo
import net.corda.nodeapi.internal.ServiceType
import net.corda.nodeapi.internal.addShutdownHook
import net.corda.testing.*
import net.corda.testing.node.MockServices.Companion.MOCK_VERSION_INFO
@ -178,6 +176,10 @@ interface DriverDSLInternalInterface : DriverDSLExposedInterface {
sealed class NodeHandle {
abstract val nodeInfo: NodeInfo
/**
* Interface to the node's RPC system. The first RPC user will be used to login if are any, otherwise a default one
* will be added and that will be used.
*/
abstract val rpc: CordaRPCOps
abstract val configuration: FullNodeConfiguration
abstract val webAddress: NetworkHostAndPort
@ -627,20 +629,21 @@ class DriverDSL(
_executorService?.shutdownNow()
}
private fun establishRpc(nodeAddress: NetworkHostAndPort, sslConfig: SSLConfiguration, processDeathFuture: CordaFuture<out Process>): CordaFuture<CordaRPCOps> {
val client = CordaRPCClient(nodeAddress, sslConfig, initialiseSerialization = false)
private fun establishRpc(config: FullNodeConfiguration, processDeathFuture: CordaFuture<out Process>): CordaFuture<CordaRPCOps> {
val rpcAddress = config.rpcAddress!!
val client = CordaRPCClient(rpcAddress, initialiseSerialization = false)
val connectionFuture = poll(executorService, "RPC connection") {
try {
client.start(ArtemisMessagingComponent.NODE_USER, ArtemisMessagingComponent.NODE_USER)
client.start(config.rpcUsers[0].username, config.rpcUsers[0].password)
} catch (e: Exception) {
if (processDeathFuture.isDone) throw e
log.error("Exception $e, Retrying RPC connection at $nodeAddress")
log.error("Exception $e, Retrying RPC connection at $rpcAddress")
null
}
}
return firstOf(connectionFuture, processDeathFuture) {
if (it == processDeathFuture) {
throw ListenProcessDeathException(nodeAddress, processDeathFuture.getOrThrow())
throw ListenProcessDeathException(rpcAddress, processDeathFuture.getOrThrow())
}
val connection = connectionFuture.getOrThrow()
shutdownManager.registerShutdown(connection::close)
@ -696,7 +699,7 @@ class DriverDSL(
"extraAdvertisedServiceIds" to advertisedServices.map { it.toString() },
"networkMapService" to networkMapServiceConfigLookup(name),
"useTestClock" to useTestClock,
"rpcUsers" to rpcUsers.map { it.toMap() },
"rpcUsers" to if (rpcUsers.isEmpty()) defaultRpcUserList else rpcUsers.map { it.toMap() },
"verifierType" to verifierType.name
) + customOverrides
)
@ -709,14 +712,14 @@ class DriverDSL(
portAllocation.nextHostAndPort() // rpcAddress
val webAddress = portAllocation.nextHostAndPort()
val name = CordaX500Name.parse(node.name)
val rpcUsers = node.rpcUsers
val config = ConfigHelper.loadConfig(
baseDirectory = baseDirectory(name),
allowMissingConfig = true,
configOverrides = node.config + mapOf(
"extraAdvertisedServiceIds" to node.advertisedServices,
"networkMapService" to networkMapServiceConfigLookup(name),
"rpcUsers" to node.rpcUsers,
"rpcUsers" to if (rpcUsers.isEmpty()) defaultRpcUserList else rpcUsers,
"notaryClusterAddresses" to node.notaryClusterAddresses
)
)
@ -808,6 +811,7 @@ class DriverDSL(
override fun startDedicatedNetworkMapService(startInProcess: Boolean?): CordaFuture<NodeHandle> {
val webAddress = portAllocation.nextHostAndPort()
val rpcAddress = portAllocation.nextHostAndPort()
val networkMapLegalName = networkMapStartStrategy.legalName
val config = ConfigHelper.loadConfig(
baseDirectory = baseDirectory(networkMapLegalName),
@ -817,6 +821,8 @@ class DriverDSL(
// TODO: remove the webAddress as NMS doesn't need to run a web server. This will cause all
// node port numbers to be shifted, so all demos and docs need to be updated accordingly.
"webAddress" to webAddress.toString(),
"rpcAddress" to rpcAddress.toString(),
"rpcUsers" to defaultRpcUserList,
"p2pAddress" to dedicatedNetworkMapAddress.toString(),
"useTestClock" to useTestClock,
"extraAdvertisedServiceIds" to listOf(ServiceInfo(NetworkMapService.type).toString())
@ -838,7 +844,7 @@ class DriverDSL(
}
)
return nodeAndThreadFuture.flatMap { (node, thread) ->
establishRpc(nodeConfiguration.p2pAddress, nodeConfiguration, openFuture()).flatMap { rpc ->
establishRpc(nodeConfiguration, openFuture()).flatMap { rpc ->
rpc.waitUntilNetworkReady().map {
NodeHandle.InProcess(rpc.nodeInfo(), rpc, nodeConfiguration, webAddress, node, thread)
}
@ -852,8 +858,7 @@ class DriverDSL(
val processDeathFuture = poll(executorService, "process death") {
if (process.isAlive) null else process
}
// We continue to use SSL enabled port for RPC when its for node user.
establishRpc(nodeConfiguration.p2pAddress, nodeConfiguration, processDeathFuture).flatMap { rpc ->
establishRpc(nodeConfiguration, processDeathFuture).flatMap { rpc ->
// Call waitUntilNetworkReady in background in case RPC is failing over:
val networkMapFuture = executorService.fork {
rpc.waitUntilNetworkReady()
@ -877,6 +882,8 @@ class DriverDSL(
}
companion object {
private val defaultRpcUserList = listOf(User("default", "default", setOf("ALL")).toMap())
private val names = arrayOf(
ALICE.name,
BOB.name,

View File

@ -235,7 +235,7 @@ class NodeConfigTest {
val webConfig = WebServerConfig(baseDir, nodeConfig)
assertEquals(localPort(20001), webConfig.webAddress)
assertEquals(localPort(10001), webConfig.p2pAddress)
assertEquals(localPort(40002), webConfig.rpcAddress)
assertEquals("trustpass", webConfig.trustStorePassword)
assertEquals("cordacadevpass", webConfig.keyStorePassword)
}

View File

@ -2,6 +2,7 @@ package net.corda.webserver
import com.typesafe.config.Config
import net.corda.core.utilities.NetworkHostAndPort
import net.corda.nodeapi.User
import net.corda.nodeapi.config.NodeSSLConfiguration
import net.corda.nodeapi.config.getValue
import java.nio.file.Path
@ -15,6 +16,7 @@ class WebServerConfig(override val baseDirectory: Path, val config: Config) : No
val exportJMXto: String get() = "http"
val useHTTPS: Boolean by config
val myLegalName: String by config
val p2pAddress: NetworkHostAndPort by config // TODO: Use RPC port instead of P2P port (RPC requires authentication, P2P does not)
val rpcAddress: NetworkHostAndPort by config
val webAddress: NetworkHostAndPort by config
val rpcUsers: List<User> by config
}

View File

@ -5,7 +5,6 @@ import net.corda.client.jackson.JacksonSupport
import net.corda.client.rpc.CordaRPCClient
import net.corda.core.messaging.CordaRPCOps
import net.corda.core.utilities.loggerFor
import net.corda.nodeapi.ArtemisMessagingComponent
import net.corda.webserver.WebServerConfig
import net.corda.webserver.converters.CordaConverterProvider
import net.corda.webserver.services.WebServerPluginRegistry
@ -26,6 +25,7 @@ import org.slf4j.LoggerFactory
import java.io.IOException
import java.io.Writer
import java.lang.reflect.InvocationTargetException
import java.nio.file.NoSuchFileException
import java.util.*
import javax.servlet.http.HttpServletRequest
import javax.ws.rs.core.MediaType
@ -187,12 +187,12 @@ class NodeWebServer(val config: WebServerConfig) {
try {
return connectLocalRpcAsNodeUser()
} catch (e: ActiveMQNotConnectedException) {
log.debug("Could not connect to ${config.p2pAddress} due to exception: ", e)
log.debug("Could not connect to ${config.rpcAddress} due to exception: ", e)
Thread.sleep(retryDelay)
// This error will happen if the server has yet to create the keystore
// Keep the fully qualified package name due to collisions with the Kotlin stdlib
// exception of the same name
} catch (e: java.nio.file.NoSuchFileException) {
} catch (e: NoSuchFileException) {
log.debug("Tried to open a file that doesn't yet exist, retrying", e)
Thread.sleep(retryDelay)
} catch (e: Throwable) {
@ -205,9 +205,10 @@ class NodeWebServer(val config: WebServerConfig) {
}
private fun connectLocalRpcAsNodeUser(): CordaRPCOps {
log.info("Connecting to node at ${config.p2pAddress} as node user")
val client = CordaRPCClient(config.p2pAddress, config)
val connection = client.start(ArtemisMessagingComponent.NODE_USER, ArtemisMessagingComponent.NODE_USER)
val rpcUser = config.rpcUsers.firstOrNull() ?: throw IllegalArgumentException("The node config has not specified any RPC users")
log.info("Connecting to node at ${config.rpcAddress} as $rpcUser")
val client = CordaRPCClient(config.rpcAddress)
val connection = client.start(rpcUser.username, rpcUser.password)
return connection.proxy
}