Tuesday 7 February 2012

Using a groovy script to ssh tunnel to a host and execute a command and saving the result

Using a groovy script to ssh tunnel to a host and execute a command and saving the result


Long title...
Anyway, recently need to use groovy to ssh tunnel to a remote host to execute commands.
Here is a rather convoluted and contrived example of how to do it.

Create x.groovy somewhere.
First we import jsch:

import com.jcraft.jsch.JSch
import com.jcraft.jsch.Session
import com.jcraft.jsch.UserInfo
import com.jcraft.jsch.Channel
import com.jcraft.jsch.ChannelExec

And properties..

import java.util.Properties

Now we create some variables defining the ssh firewall server, localhost and the target host:

def sshHost = '192.168.1.1'
def sshUser = 'ssh_user'
def sshPass = 'ssh_password'
def sshPort = 22

def localHost = '127.0.0.1'

def targetHost = '172.16.1.1'
def targetUser = 'target_user'
def targetPass = 'target_password'
def targetPort = 22

We start first by setting up the properties.
You don't strictly need properties as a HashMap would do.

println "Opening connection to ${sshUser}@${sshHost}:${sshPort}"
Properties config = new Properties()
config.put("StrictHostKeyChecking", "no")
JSch jsch = new JSch()

Now we get our session to the firewall:

Session sshSession = jsch.getSession(sshUser, sshHost, sshPort)
sshSession.setPassword(sshPass)
sshSession.setConfig(config)
sshSession.connect()
println "Connected"

Now we get a free port and forward localhost to the target machine:

println "Forwarding connection to ${targetHost}:${targetPort}"
def assignedPort = sshSession.setPortForwardingL(0, targetHost, targetPort)
println "Got port $assignedPort"

Now we have two choices. Use AntBuilder with the sshexec call, or do raw InputStream reads:

1 - Use AntBuilder:

def result = ''
def ant = new AntBuilder()
// This bit is to stop ant hijacking stdout.
logger = ant.project.buildListeners.find { it instanceof org.apache.tools.ant.DefaultLogger }
logger.messageOutputLevel = 0
logger.emacsMode = true
// Ok. Do it...
ant.sshexec(
  host: localHost,
  port: assignedPort,
  trust: true,
  username: targetUser,
  password: targetPass,
  command: '/bin/hostname',
  outputproperty: 'result',
  verbose: false
)
// And show the result
println ant.project.properties.'result'

2 - Use raw InputStream handling:

Session targetSession = jsch.getSession(targetUser, localHost, assignedPort)
targetSession.setPassword(targetPass)
targetSession.setConfig(config)
targetSession.connect()
println "Connected"

// Could use "shell"
Channel channel = targetSession.openChannel("exec")
// Let's just get the hostname
((ChannelExec)channel).setCommand("/bin/hostname")
// Spew errors to console
((ChannelExec)channel).setErrStream(System.err)
// We're not sending anything
channel.setInputStream(null)
// Get the input stream
InputStream is = channel.getInputStream()
// Connect
channel.connect()
// This could be written better and groovier...
byte[] tmp = new byte[1024]
// Uh oh. We really need a better way out of the loop... :-(
while (true) { 
  // But it's just an example... :-)
  while (is.available() > 0) {
    int i = is.read(tmp, 0, 1024)
    if (i<0)
      break
    System.out.print(new String(tmp, 0, i))
  }
  if (channel.isClosed()) {
    // All done.
    System.out.println("exit-status: " + channel.getExitStatus())
    break
  }
  // Ugly: You might want to change this
  try{Thread.sleep(1000);}catch(Exception ee){}
}
// Close channel and session
channel.disconnect()
targetSession.disconnect()

And finally disconnect:

sshSession.disconnect()

Now how do you run it? You have to tell groovy where the jars are...

groovy -cp /path/to/jsch-0.1.46.jar:/path/to/ant-jsch.jar x.groovy

No comments:

Post a Comment