Fix ssh for windows (#121)

* Make ssh tunnels work with Pageant on windows and allow specifying explicit
ssh remote user

* Update comments
This commit is contained in:
Christian Sailer 2017-11-23 10:06:39 +00:00 committed by rick.parker
parent f382639ed8
commit 2b217b6eea
5 changed files with 75 additions and 18 deletions

View File

@ -7,7 +7,7 @@ the Corda node via RPC.
To run up the JMeter UI, using the jmeter.properties in the resources folder, To run up the JMeter UI, using the jmeter.properties in the resources folder,
type the following: type the following:
./gradlew tools:jmeter:run `./gradlew tools:jmeter:run`
You can then open the example script in "Example Flow Properties.jmx" via the File -> Open menu option. You need to You can then open the example script in "Example Flow Properties.jmx" via the File -> Open menu option. You need to
configure the host, ports, user name and password in the Java Sampler that correspond to your chosen target Corda node. configure the host, ports, user name and password in the Java Sampler that correspond to your chosen target Corda node.
@ -15,7 +15,7 @@ Simply running from the UI will result in the RPC client running inside the UI J
If you wish to pass additional arguments to JMeter, you can do this: If you wish to pass additional arguments to JMeter, you can do this:
./gradlew tools:jmeter:run -PjmeterArgs="['-n', '-Ljmeter.engine=DEBUG']" `./gradlew tools:jmeter:run -PjmeterArgs="['-n', '-Ljmeter.engine=DEBUG']"`
The intention is to run against a remote Corda node or nodes, hosted on servers rather than desktops. To The intention is to run against a remote Corda node or nodes, hosted on servers rather than desktops. To
this end, we leverage the JMeter ability to run remote agents that actually execute the tests, with these this end, we leverage the JMeter ability to run remote agents that actually execute the tests, with these
@ -26,7 +26,7 @@ The remote agents then run close to the nodes, so the latency of RPC calls is mi
A Capsule (http://www.capsule.io) based launchable JAR is created that can be run with the simple command line A Capsule (http://www.capsule.io) based launchable JAR is created that can be run with the simple command line
java -jar jmeter-corda-<version>.jar `java -jar jmeter-corda-<version>.jar`
Embedded in the JAR is all of the corda code for flows and RPC, as well as the jmeter.propeties. This Embedded in the JAR is all of the corda code for flows and RPC, as well as the jmeter.propeties. This
JAR will also include a properties file based on the hostname in the JMeter configuration, JAR will also include a properties file based on the hostname in the JMeter configuration,
@ -34,7 +34,7 @@ so we allocate different SSH tunneled port numbers this way.
To launch JMeter with the tunnels automatically created: To launch JMeter with the tunnels automatically created:
./gradlew tools:jmeter:run -PjmeterHosts="['hostname1', 'hostname2']" `./gradlew tools:jmeter:run -PjmeterHosts="['hostname1', 'hostname2']"`
The list of hostnames should be of at least length one, with a maximum equal to the length of the remote_hosts The list of hostnames should be of at least length one, with a maximum equal to the length of the remote_hosts
option in jmeter.properties. We effectively "zip" together the hostnames and that list to build the SSH tunnels. option in jmeter.properties. We effectively "zip" together the hostnames and that list to build the SSH tunnels.
@ -45,4 +45,13 @@ from remote agents.
The SSH tunnels can be started independently with: The SSH tunnels can be started independently with:
./gradlew tools:jmeter:runSsh -PjmeterHosts="['hostname1', 'hostname2']" `./gradlew tools:jmeter:runSsh -PjmeterHosts="['hostname1', 'hostname2']"`
For the ssh tunneling to work, an ssh agent must be running on your local machine with the
appropriate private key loaded. If the environment variable `SSH_AUTH_SOCK` is set, the code
assumes that a posix sshagent process is being used, if it is not set, it assumes that
[Pageant](https://www.ssh.com/ssh/putty/putty-manuals/0.68/Chapter9.html) is in use. If the
remote user name is different from the current user name, `-XsshUser <remote user name>`
can be used to set this, or in the gradle call:
`./gradlew tools:jmeter:runSsh -PjmeterHosts="['hostname1', 'hostname2']" -PsshUser="'username'"`

View File

@ -13,6 +13,7 @@ dependencies {
compile group: 'com.jcraft', name: 'jsch.agentproxy.core', version: '0.0.9' compile group: 'com.jcraft', name: 'jsch.agentproxy.core', version: '0.0.9'
compile group: 'com.jcraft', name: 'jsch.agentproxy.sshagent', version: '0.0.9' compile group: 'com.jcraft', name: 'jsch.agentproxy.sshagent', version: '0.0.9'
compile group: 'com.jcraft', name: 'jsch.agentproxy.usocket-jna', version: '0.0.9' compile group: 'com.jcraft', name: 'jsch.agentproxy.usocket-jna', version: '0.0.9'
compile group: 'com.jcraft', name: 'jsch.agentproxy.pageant', version: '0.0.9'
// Log4J: logging framework (with SLF4J bindings) // Log4J: logging framework (with SLF4J bindings)
compile "org.apache.logging.log4j:log4j-slf4j-impl:$log4j_version" compile "org.apache.logging.log4j:log4j-slf4j-impl:$log4j_version"
@ -60,8 +61,13 @@ task(runServer, dependsOn: 'classes', type: JavaExec) {
task(runSsh, dependsOn: 'classes', type: JavaExec) { task(runSsh, dependsOn: 'classes', type: JavaExec) {
classpath = sourceSets.main.runtimeClasspath classpath = sourceSets.main.runtimeClasspath
main = 'com.r3.corda.jmeter.Ssh' main = 'com.r3.corda.jmeter.Ssh'
if ( project.hasProperty("sshUser") ){
args+= "-XsshUser"
args+= Eval.me(sshUser)
}
if ( project.hasProperty("jmeterHosts") ) { if ( project.hasProperty("jmeterHosts") ) {
args Eval.me(jmeterHosts) args+= "-Xssh"
args+= Eval.me(jmeterHosts)
} }
standardInput = System.in standardInput = System.in
} }
@ -85,6 +91,10 @@ run {
if ( project.hasProperty("jmeterArgs") ) { if ( project.hasProperty("jmeterArgs") ) {
args+= Eval.me(jmeterHosts) args+= Eval.me(jmeterHosts)
} }
if ( project.hasProperty("sshUser") ){
args+= "-XsshUser"
args+= Eval.me(sshUser)
}
if ( project.hasProperty("jmeterHosts") ) { if ( project.hasProperty("jmeterHosts") ) {
args+= "-Xssh" args+= "-Xssh"
args+= Eval.me(jmeterHosts) args+= Eval.me(jmeterHosts)

View File

@ -5,6 +5,7 @@ import com.jcraft.jsch.Identity
import com.jcraft.jsch.IdentityRepository import com.jcraft.jsch.IdentityRepository
import com.jcraft.jsch.JSch import com.jcraft.jsch.JSch
import com.jcraft.jsch.agentproxy.AgentProxy import com.jcraft.jsch.agentproxy.AgentProxy
import com.jcraft.jsch.agentproxy.connector.PageantConnector
import com.jcraft.jsch.agentproxy.connector.SSHAgentConnector import com.jcraft.jsch.agentproxy.connector.SSHAgentConnector
import com.jcraft.jsch.agentproxy.usocket.JNAUSocketFactory import com.jcraft.jsch.agentproxy.usocket.JNAUSocketFactory
import org.slf4j.LoggerFactory import org.slf4j.LoggerFactory
@ -16,7 +17,11 @@ private val log = LoggerFactory.getLogger(Ssh::class.java)
* Creates a new [JSch] instance with identities loaded from the running SSH agent. * Creates a new [JSch] instance with identities loaded from the running SSH agent.
*/ */
fun setupJSchWithSshAgent(): JSch { fun setupJSchWithSshAgent(): JSch {
val connector = SSHAgentConnector(JNAUSocketFactory()) val connector =
if (System.getenv("SSH_AUTH_SOCK") == null)
PageantConnector()
else
SSHAgentConnector(JNAUSocketFactory())
val agentProxy = AgentProxy(connector) val agentProxy = AgentProxy(connector)
val identities = agentProxy.identities val identities = agentProxy.identities
require(identities.isNotEmpty()) { "No SSH identities found, please add one to the agent" } require(identities.isNotEmpty()) { "No SSH identities found, please add one to the agent" }

View File

@ -1,5 +1,6 @@
package com.r3.corda.jmeter package com.r3.corda.jmeter
import com.sun.javaws.exceptions.InvalidArgumentException
import net.corda.core.internal.div import net.corda.core.internal.div
import org.apache.jmeter.JMeter import org.apache.jmeter.JMeter
import org.slf4j.LoggerFactory import org.slf4j.LoggerFactory
@ -54,19 +55,29 @@ class Launcher {
} }
} }
private fun maybeOpenSshTunnels(args: Array<String>): Array<String> { fun maybeOpenSshTunnels(args: Array<String>): Array<String> {
// We trim the args at the point "-Xssh" appears in the array of args. Anything after that is a host to // We trim the args at the point "-Xssh" appears in the array of args. Anything after that is a host to
// SSH tunnel to. // SSH tunnel to. Also get and remove the "-XsshUser" argument if it appears.
var index = 0 var index = 0
for (arg in args) { var userName = System.getProperty("user.name")
if (arg == "-Xssh") { val returnArgs = mutableListOf<String>()
while (index < args.size) {
if (args[index] == "-XsshUser") {
++index
if (index == args.size || args[index].startsWith("-")) {
throw InvalidArgumentException(args)
}
userName = args[index]
} else if (args[index] == "-Xssh") {
// start ssh // start ssh
Ssh.main(args.copyOfRange(index + 1, args.size), false) Ssh.createSshTunnels(args.copyOfRange(index + 1, args.size), userName, false)
return if (index == 0) emptyArray() else args.copyOfRange(0, index) return returnArgs.toTypedArray()
} else {
returnArgs.add(args[index])
} }
index++ index++
} }
return args return returnArgs.toTypedArray()
} }
} }
} }

View File

@ -2,6 +2,7 @@ package com.r3.corda.jmeter
import com.jcraft.jsch.JSch import com.jcraft.jsch.JSch
import com.jcraft.jsch.Session import com.jcraft.jsch.Session
import com.sun.javaws.exceptions.InvalidArgumentException
import net.corda.core.utilities.NetworkHostAndPort import net.corda.core.utilities.NetworkHostAndPort
import net.corda.nodeapi.internal.addShutdownHook import net.corda.nodeapi.internal.addShutdownHook
import org.slf4j.LoggerFactory import org.slf4j.LoggerFactory
@ -17,9 +18,30 @@ class Ssh {
val log = LoggerFactory.getLogger(this::class.java) val log = LoggerFactory.getLogger(this::class.java)
@JvmStatic @JvmStatic
@JvmOverloads fun main(args: Array<String>) {
fun main(args: Array<String>, wait: Boolean = true) { // parse the args and call createSshTunnels
val userName = System.getProperty("user.name") // the only arguments recognised are "-XsshUser" for the remote user name
// and "-Xssh" - everything after this will be treated as a remote host name
var userName = System.getProperty("user.name")
var index = 0
while (index < args.size) {
if (args[index] == "-XsshUser") {
++index
if (index == args.size || args[index].startsWith("-")) {
throw InvalidArgumentException(args)
}
userName = args[index]
} else if (args[index] == "-Xssh") {
createSshTunnels(args.copyOfRange(index + 1, args.size), userName, true)
return
}
}
log.info("Nothing to be done - did you specify hosts to tunnel to with -Xssh?")
}
fun createSshTunnels(hosts: Array<String>, userName: String, wait: Boolean) {
log.info("User name for ssh: ${userName}")
val jsch = setupJSchWithSshAgent() val jsch = setupJSchWithSshAgent()
val sessions = mutableListOf<Session>() val sessions = mutableListOf<Session>()
@ -34,7 +56,7 @@ class Ssh {
// Where JMeter driver will try to connect for remote agents (should all be localhost so can ssh tunnel). // Where JMeter driver will try to connect for remote agents (should all be localhost so can ssh tunnel).
val localHostsAndPorts = jmeterProps.getProperty("remote_hosts", "").split(',').map { it.trim() } val localHostsAndPorts = jmeterProps.getProperty("remote_hosts", "").split(',').map { it.trim() }
args.zip(localHostsAndPorts) { remoteHost, localHostAndPortString -> hosts.zip(localHostsAndPorts) { remoteHost, localHostAndPortString ->
// Actual remote host and port we will tunnel to. // Actual remote host and port we will tunnel to.
log.info("Creating tunnels for $remoteHost") log.info("Creating tunnels for $remoteHost")
val localHostAndPort = NetworkHostAndPort.parse(localHostAndPortString) val localHostAndPort = NetworkHostAndPort.parse(localHostAndPortString)