ssh2 in NodeJS - connecting to and getting data from lots of hosts
September 27, 2019 2:07 PM   Subscribe

I needs to connect to about 200 different SSH servers, login to a network device, and run a command and get some output. Help? If you suggest a tool other than node, please keep in mind it needs to run in Windows. Thank you.

I am using the ssh2 package: https://www.npmjs.com/package/ssh2

This is my code except that the MyWANs variable has several hundred individual sitename/site ip arrays in it.

If I keep the number of sites low, this tends to work, however when running on the whole list it just seems to throw errors for them, I just get a big list of errors and no proper output.

It seems to just be running the "conn.on('error', function(){console.log('Error: '+wan[0]+' '+wan[1]);});" line for every iteration of the loop. I haven't done any node code in awhile. Can someone school me?

var MyWANs = [
['SITENAME ','SITEIP'],
['SITENAME2','SITEIP2']];

var Client = require('ssh2').Client;

MyWANs.forEach(function(wan){
var conn = new Client();
conn.on('error', function(){console.log('Error: '+wan[0]+' '+wan[1]);});
conn.on('ready', function() {
console.log(wan[0]+' :: ready');
conn.shell(function(err, stream) {
var pastMenu = false;
var loginAttempt = false;
var loginToVC = false;
var sureConnect = false;
var listedConfig = false;
var ended = false;
stream.on('close', function() {
console.log('Stream :: close');
conn.end();
}).on('data', function(data) {
if (data.includes('8) Shell') && pastMenu == false){
stream.write('8\n');
//console.log(wan[0]+": Main Menu Done\n");
pastMenu = true;
}
if (data.includes('/root') && loginAttempt == false){
stream.write("rm /root/.ssh/known_hosts\n");
stream.write("ssh admin@192.168.10.5\n");
//console.log(wan[0]+": Login to VC attempted\n");
loginAttempt = true;
}
if (data.includes('Are you sure you want to continue connecting (yes/no)?') && sureConnect == false){
stream.write("yes\n");
//console.log(wan[0]+": Yes I'm sure\n");
sureConnect = true;
}
if (data.includes("admin@192.168.10.5's password") && loginToVC == false){
stream.write('MYPASSWORD\n');
//console.log(wan[0]+": Sent VC PW\n");
loginToVC = true;
}
if (data.includes('#') && loginToVC == true && listedConfig == false){
stream.write("show running-config | begin \"wlan access-rule HONDATOOLS\"\n");
//console.log(wan[0]+": Showed Running Config\n");
listedConfig = true;
}
if (data.includes('rule any any match any any any deny') && pastMenu == true && loginAttempt == true && loginToVC == true && listedConfig == true && ended == false) {
console.log('===================== '+wan[0]+' =====================\n'+data+"\n");
ended = true;
}
if (ended == true){ stream.end();}
});
});
}).connect({
host: wan[1],
port: 22,
username: 'admin',
password: 'SSHPASSWORD'
});
});
posted by signsofrain to Technology (12 answers total) 2 users marked this as a favorite
 
When I need to ssh in to a bunch of machines to do a thing, I use Ansible, but that's we provision stuff on our assets, so we're already 90% there.
posted by advicepig at 2:19 PM on September 27, 2019 [2 favorites]


Is Windows Subsystem for Linux an option? It's available, but not enabled by default, on any Windows 10 version from the last few years. That would let you write a simple shell script with command line ssh and expect(1). The script could take the site name and IP as arguments, and then you invoke it (sequentially or in parallel) using xargs and a text file of inputs.

I find Node's async + streams paradigm to be hard to reason about, personally. And yes, Ansible is the elegant way to do this sort of stuff, but it has a learning curve.
posted by serathen at 2:43 PM on September 27, 2019


I'd use the Fabric Python library.
posted by djb at 3:03 PM on September 27, 2019 [1 favorite]


Seconding Ansible if that's already how you roll, and a great way to go for DevOps/*aaS. Likewise the Python lib above may be easier if you do Python.

Here's what I (an old-school Linux/Unix admin) use for a more interactive experience:. This is heavy on command-line and will take some learning and effort to set up, but I believe your patience will be rewarded.

MobaXterm app or Cygwin environment, or VirtualBox+Linux guest+shared folder to get bash shell and access to repositories/runtime libs. Haven't used Windows 10 Subsystem for Linux.

Use apt-cygwin on Cygwin and MobaXterm, or apt/yum/zypper etc. package managers to pull/install packages.

Package 'Parallel SSH' to do what you want. If using the 'free' MobaXterm you may need to limit pssh's parallelism to avoid its builtin session limits. You can log per-host error/output into a local subdirectoty for processing.
posted by zaixfeep at 3:04 PM on September 27, 2019 [2 favorites]


Best answer: This is exactly the sort of thing that expect is designed to do. Expect is available for Windows, but I've only ever used it in Linux. It's part of Tcl and specifically made to do these sorts of cli-type interactions with other things.
posted by jquinby at 5:39 PM on September 27, 2019 [2 favorites]


fanout is a bash script that does exactly that, and it works really great.

I would install WSL and use fanout over writing something myself.
posted by many more sunsets at 5:41 PM on September 27, 2019


Crap, my example should have gone here.
posted by jquinby at 5:43 PM on September 27, 2019


fanout is analogous to parallel ssh in function. You might also be able to install or compile it instead of pssh in my earlier solution.

jquimby caught something I missed and offered the solution for it. You want to ssh to a host, then pseudo-interactively login to the network device from that host. Assuming the login to the network device is not ssh - ie telnet or proprietary binary - Expect will simulate that login session. In fact I suspect your mode.js will map most cleanly into an Expect script. Although I guess you could ssh into each host then Expect from there if it were installed, I'm assuming you don't want to do it that way. You can install Expect like pssh on my solution as well.

The bad news from my experience is: Beware. Expect should generally work, but you may find that you must carefully tune a given 'expect' regex/timeout statement to work with all varying software versions / device revision levels/speeds. For example, I found that Expect would sometimes echo (in effect logging) silently transmitted sudo passwords and/or timeout without catching the sudo password prompt, depending on which CentOS release/sudo version/virtualization method was in use for the host. Weird.
posted by zaixfeep at 6:33 PM on September 27, 2019


To clarify - if I had to do this, I had a consistent password or ssh pubkey across all ssh hosts and I could ensure Expect was on every ssh host, I would:
- use pssh's "pscp" command to copy my dump-net-device Expect script to each ssh host
- use pssh to run the (remote) Expect script in parallel, logging the output to a per-host subdir
But that's me.
posted by zaixfeep at 6:44 PM on September 27, 2019 [1 favorite]


pssh sounds interesting! But fanout.sh is a single bash script ;)
posted by many more sunsets at 9:44 PM on September 27, 2019


Best answer: I would not even contemplate logging in to a huge pile of ssh servers using passwords. Just way way way too many opportunities for the logins to go pear-shaped. This use case is exactly what ssh public key logins and the OpenSSH client's ability to include remote commands on its command line were designed for, and failing to use them definitely counts as programming against the grain.

If I were doing this on Windows, I'd be using PowerShell to manage invocations of the inbuilt Windows ssh command line tool; Windows has included an almost completely vanilla OpenSSH client for some while now. If I needed my PowerShell script to be invocable via double-click, or more likely by dragging and dropping a list of site addresses onto it, I'd top and tail it with jscript. Again, using scripting tools that come as standard with the OS wherever possible counts as programming with the grain.

If you're getting errors while attempting parellel access to hundreds of ssh servers, the most likely cause is a shitty Internet router that falls over when required to maintain more than a few tens of simultaneous TCP connections. To work around this, your script should limit itself to running some configurable maximum number of ssh sessions in parallel.
posted by flabdablet at 7:30 AM on September 28, 2019 [2 favorites]


Response by poster: Thank you everyone for your contributions. I love it when programmers come together to brainstorm, you all are inspiring.
posted by signsofrain at 8:38 PM on September 28, 2019


« Older De-puff my crying eyes   |   Best way to do an ACH transfer to another person? Newer »
This thread is closed to new comments.