Perl Net::SSH2 pubkey authentication issue - perl

I am trying to connect to a remote SSH server using Net::SSH2. Commandline ssh works fine. I can not seem to figure out the correct auth_hostbased parameters, though
This is my code:
use Net::SSH2;
my $ssh = Net::SSH2->new();
$ssh->debug(1);
$ssh->trace(-1);
$ssh->connect('remotehost.remotedomain.tld') or die;
$ssh->auth_hostbased('username',
'ssh-rsa AAAAB3Nz[..]C0JoaFF9 root#myhost',
'-----BEGIN RSA PRIVATE KEY-----
Proc-Type: 4,ENCRYPTED
DEK-Info: AES-128-CBC,FA97214E87562096A7E480C82DAE5EB4
XIMKnj9k[..]kpRo5V
-----END RSA PRIVATE KEY-----',
'myhost.mydomain.tld',
'username',
'keypassword') or die;
The snippet dies # $ssh->auth_hostbased with just a 'Net::SSH2::DESTROY object 0xe17de0'. Setting trace does not seem to matter. Replacing die with $ssh->die_with_error throws a 'die_with_error is not a valid Net::SSH2 macro'. Downloading the current 0.53 version of Net:SSH2 did not work as the script no longer compiles: 'Net::SSH2 object version 0.44 does not match bootstrap parameter 0.53'
Any help on the correct parameter format or an alternative module is appreciated.

Why not using Net::OpenSSH ?
That is a simple ssh wrapper script, i wrote some time ago:
#!/usr/bin/perl
#Simple SSH Remote Executor using Net::OpenSSH Library
use warnings;
use strict;
use Net::OpenSSH;
# see http://search.cpan.org/~salva/Net-OpenSSH-0.62/lib/Net/OpenSSH.pm#DEBUGGING
$Net::OpenSSH::debug = undef;
use Getopt::Long;
my $timeout = 10;
my ($username,$identity,$hostname,$command) = undef;
my $uid=getpwuid($<);
my $ctl_dir=qq{/tmp/.libnet-puppet-$uid};
my $ctl_mode=0700;
if ( ! -d $ctl_dir ) { mkdir( $ctl_dir,$ctl_mode ) };
open my $stderr_fh, '>>', '/dev/null' or die $!;
sub print_help{
print qq{\nusage: $0 [options] -h Hostname
-u username
-i identity
-c command
long options are supported !
};
exit (1);
}
GetOptions ("hostname=s" => \$hostname, # string
"username=s" => \$username, # string
"identity=s" => \$identity, # string
"command=s" => \$command) # string
or print_help;
if ( not defined $username or not defined $identity or not defined $hostname or not defined $command ) { print_help };
my $port = q{22};
my $user = $username;
my $ssh;
my $cmd = qq{$command};
my $options = {
host => $hostname,
user => $user,
port => $port,
default_stderr_fh => $stderr_fh,
ctl_dir => $ctl_dir,
master_opts => [
-o => "UserKnownHostsFile=/dev/null",
-o => "StrictHostKeyChecking=no",
-o => qq{IdentityFile=$identity},
],
timeout => $timeout };
#ALARM Timer timeout handling
$SIG{ALRM} = sub {
printf( "%s\n", qq{invalid-timeout-connecting-to-node-$hostname});
exit(1);
};
#init alarm timer ;-)
alarm( $timeout );
$ssh = Net::OpenSSH->new( %{$options} )
or $ssh->error and die "Couldn't establish SSH connection: ". $ssh->error;
my (#out, $err) = $ssh->capture2({ timeout => 10 }, $cmd);
die("Error: %s\n", $err) if defined $err;
if ( (scalar(#out)) eq 0 ) {
printf( "%s\n", qq{invalid-empty-string-received-by-node-$hostname});
exit(1);
}
foreach my $line ( #out ) {
$line =~ s/^\s{1,}//;
printf ("%s",$line);
}
Install it using cpanm (cpanm Net::OpenSSH) or as debian package "libnet-openssh-perl".
See "man ssh_config" for available master options.
I think that script will be of great help though.
Rgds. Franz

Related

Retry SSH to Host if Connection to the Host Fails in Perl

I have a script, which does SSH to the server and execute some command (In this script, for demonstration I am running Perl print statement with Hello message).
Here is my script:
#!/usr/bin/perl
use strict; use warnings;
use Net::OpenSSH;
$Net::OpenSSH::debug = ~0;
BEGIN {
open my $out, '>', '/tmp/debug.txt' or warn $!;
$Net::OpenSSH::debug_fh = $out;
$Net::OpenSSH::debug = -1;
}
my #hosts = ("ipaddress1","ipaddress2");
my $ssh;
my $command = "perl -e 'print \"Hello..\"'";
foreach my $n (#hosts) {
#Here if connection to the host($n) fails, is it possible to retry again
$ssh = Connect($n, "user", "passwd");
$ssh->capture($command);
print "Done execution in Host: $n\n";
}
undef $ssh;
print "**End**\n";
sub Connect {
my ( $host, $user, $passwd ) = #_;
my $ssh = Net::OpenSSH->new($host, user=>$user, password=>$passwd);
$ssh->error and die "Couldn't establish SSH connection: " . $ssh->error;
return $ssh;
}
Whenever I execute this script, sometimes it successfully prints below message:
Done execution in Host: ipaddress1
Done execution in Host: ipaddress2
**End**
But sometimes cannot do ssh to host (either ipaddress1 or ipaddress2) and gives following message:
Couldn't establish SSH connection: unable to establish master SSH connection: master process exited unexpectedly at script.pl ....
Its being get died in Connect subroutine (cause I couldn't trace, opened question here).
So, is there any way if I cannot connect(ssh) to the host, retry can be done after certain period of time (for n number times) instead of printing error message and make the script die?
OpenSSH provides a nice interface for errors. I'd start by looking at the examples on the cpan page. Try the following
foreach my $n (#hosts) {
#Here if connection to the host($n) fails, is it possible to retry again
$ssh = Connect($n, "user", "passwd", 3);
$ssh->capture($command);
print "Done execution in Host: $n\n";
}
undef $ssh;
print "**End**\n";
sub Connect {
my ( $host, $user, $passwd , $retry_limit ) = #_;
my $timeout = 10;
my $con;
while ( $retry_limit-- > 0 )
{
$con = Net::OpenSSH->new($host,
user=>$user,
password=>$passwd,
timeout=> $timeout,
);
last unless $con->error();
}
die "unable to connect ".$con->error() if retry_limit <0;
return $con;
}

Transfer file from Remote machine to local machine using Net::OpenSSH

I have built a script which should get a file from remote machine to local machine.
#!/usr/bin/perl
use strict;
use warnings;
use Net::OpenSSH;
use Data::Dumper;
my $local_dir = "/LOCAL/DIR/LOCATION/"
print "[LOCAL DIR]-> $local_dir\n";
my $remote_dir = "/REMOTE/DIR/LOCATION/";
print "[REMOTE DIR]-> $remote_dir\n";
my ($host, $user, $password) = ("remote.machine.ip.address", "userid", "password");
my $ssh = Net::OpenSSH->new($host,
user => $user,
password => $passwd,
master_opts => [-o => "StrictHostKeyChecking=no"]
);
$ssh->error and die "Couldn't establish SSH connection: ". $ssh->error;
my #file = $ssh->capture("cd $remote_dir && ls -1tr | grep Report | tail -1");
print "[FILE]:\n".Dumper(\#file);
$ssh->scp_get({glob => 1}, "$remote_dir$file[0]", $local_dir)
or die "scp failed: " . $ssh->error;
undef $ssh;
In the above code its able to print the Dumper value for #file but unable to get the file in local system.
Here is the error it throws at the end:
[FILE]:
$VAR1 = [
'Report_Managable_20200705.csv
'
];
scp: /REMOTE/DIR/LOCATION/Report_Managable_20200705.csv
protocol error: expected control record
scp failed: scp failed: child exited with code 1 at file_get_test.pl line 22.
Can anybody help me to fix this issue. TIA.
The list returned by $ssh->capture() has new lines at the end of each item. Try use chomp #file to remove the newlines.

Perl Issue with concurrent requests with IO::Async and Future::Utils

I'm trying to use an IO loop to send concurrent requests (5) to a pool of hosts (3), but the code stops after 3 requests. I've had help to kickstart this code, but I certainly understand most of it now. What I don't get is why the number of processed requests is linked to the number of hosts in my pool of hosts. The objective of the code is to determine routing information from a given IP.
use strict;
use warnings;
use Net::OpenSSH;
use IO::Async::Loop;
use Future::Utils 'fmap_concat';
my #hosts = qw(host1 host2 host3);
my #ssh;
my $user = 'myuser';
my $pass = 'mypassword';
foreach my $host (#hosts) {
my $ssh = Net::OpenSSH->new(host => $host, user => $user, password => $pass, master_opts => [-o => "StrictHostKeyChecking=no"]);
die "Failed to connect to $host: " . $ssh->error if $ssh->error;
push #ssh, $ssh;
}
my #ipv4 = (
'ip1','ip2','ip3','ip4','ip5'
);
my $loop = IO::Async::Loop->new;
my $future = fmap_concat {
my $ip = shift;
my $ssh = shift #ssh;
my $cmd = 'show ip route '.$ip.' | i \*';
my #remote_cmd = $ssh->make_remote_command($cmd);
return $loop->run_process(command => \#remote_cmd)
->transform(done => sub { [#_] })
->on_ready(sub { push #ssh, $ssh });
} generate => sub { return () unless #ssh and #ipv4; shift #ipv4 }, concurrent => scalar #ssh;
my #results = $future->get;
foreach my $result (#results) {
my ($exit, $stdout) = #$result;
print $stdout, "\n";
}
Here are the results
Connection to host1 closed by remote host.
Connection to host2 closed by remote host.
Connection to host3 closed by remote host.
* ip1, from host1, 3w0d ago, via GigabitEthernet0/0/0
* ip2, from host2, 7w0d ago, via GigabitEthernet0/0/0
* ip3, from host3, 3w0d ago, via GigabitEthernet0/0/1
After researching on the problem, I found out that network devices such as Cisco might have an issue handling several requests over the same connection. So the code changed a bit in a way where a new connection is opened everytime the future is called instead of using a pool of pre-opened connections.
use strict;
use warnings;
use Net::OpenSSH;
use IO::Async::Loop;
use Future::Utils 'fmap_concat';
my #hosts = qw(host1 host2 host3);
my #ssh;
my $user = 'myuser';
my $pass = 'mypassword';
my #ipv4 = (
'ip1','ip2','ip3','ip4','ip5'
);
my $loop = IO::Async::Loop->new;
my $future = fmap_concat {
my $ip = shift;
my $host = shift #hosts;
my $ssh = Net::OpenSSH->new(host => $host, user => $user, password => $pass, master_opts => [-o => "StrictHostKeyChecking=no"]);
die "Failed to connect to $host: " . $ssh->error if $ssh->error;
my $cmd = 'show ip route '.$ip.' | i \*|^Routing entry';
my #remote_cmd = $ssh->make_remote_command($cmd);
return $loop->run_process(command => \#remote_cmd)
->transform(done => sub { [#_] })
->on_ready(sub { push #hosts, $host ; });
} generate => sub { return () unless #hosts and #ipv4; shift #ipv4 }, concurrent => scalar #hosts;
my #results = $future->get;
foreach my $result (#results) {
my ($exit, $stdout) = #$result;
print $stdout, "\n";
}
But this has leaded to other problems with underlying openssh library.
It looks like there was a race condition with the ssh connection not being released properly when the future was being invoked on the $host again.
undef $ssh fixed it
->on_ready(sub { undef $ssh; push #hosts, $host ; });

Can't call method “send” on an undefined value at - Net::SSH::Expect Perl error

I am running some commands in a remote server by connecting from my local server. To achieve this I am connecting to remote server using Net::SSH::Expect perl module. Connection is establishing here, but sometimes when I execute the command the following error comes up -
Can't call method "send" on an undefined value at .....
Here is my code:
my ($ip, $user, $passwd) = ("my.ip.address.here","user", "password");
my $ssh = SSH_Connection( $ip, $user, $passwd );
my $command_to_execute = "<Command to be executed will build here>";
print "$command_to_execute\n";
$str = 'Bye';
$ssh->send("$command_to_execute; echo $str");
$output = $ssh->waitfor($str, undef);
$ssh->close();
print "END\n";
sub SSH_Connection {
my ( $host, $user, $passwd ) = #_;
my $ssh = Net::SSH::Expect->new (
host => $host, #ip
user => $user, #'user'
password => $passwd, #'password'
raw_pty => 1,
no_terminal => 0,
);
my $login_output;
my $handledie = eval {
$login_output = $ssh->login();
};
if ( $# ) {
if ($# =~ m/SSHConnectionError/i ) {
print "SSH Connection Error\n";
} elsif ( $# =~ m/SSHProcessError/ix ) {
print "SSH Process Error\n";
} elsif ( $# =~ m/SSHConnectionAborted/ix ) {
print "SSH Connection Aborted\n";
} else {
print "SSH Unknown Error: $#\n";
}
}
if ($login_output !~ /Last login/) {
die "Login has failed.";
} else {
return $ssh;
}
print "SSH to ip - $host failed\n";
}
First I'm building the command and storing it in $command_to_execute variable.
At the end of command execution I'll get keyword Bye. So I am waiting for that keyword to match.
My question is -
Why I am getting above mentioned error?
Suppose if my command execution is failed, will the control will come back to my script ? Because its waiting for $str word.
I doubt about the error catching method is not proper. Please suggest a better solution.

Perl Net::SSH::Expect not printing out all expected output

I am using expect in perl to get interface information from my router. When I run the command on the remote router its missing about 10-15 lines that should be there. Not sure why its stopping, any ideas?
#!/usr/bin/perl -w
#use strict;
use warnings;
use Net::SSH::Expect;
my $ssh = Net::SSH::Expect->new (
host => "10.10.10.10",
user => 'user',
password => 'pass'
);
my $login_output = $ssh->login();
if ($login_output !~ /router#/) {
die "Login has failed. Login output was $login_output";
}
#$ssh->run_ssh() or die "SSH process couldn't start: $!";
$ssh->send("show int g2/1");
my $line;
while (defined ($line = $ssh->read_line()) ) {
print $line."\n";
}
Net::SSH::Expect is not reliable. Use other module as Net::OpenSSH, Net::SSH2, Net::SSH::Any or just Expect
use Net::OpenSSH;
my $ssh = Net::OpenSSH->new("10.10.10.10",
user => 'user',
password => 'pass',
timeout => 60 );
my $output = $ssh->capture('show int g2/1');
# or for some non-conforming SSH server implementations rather
# common in network equipment you will have to do...
my $output = $ssh->capture({stdin_data => "show int g2/1\n"});
$ssh->error and die "unable to run remote command: " . $ssh->error;
I suspect since you are dealing with a router, you want to enable raw_pty => 1 like the Net::SSH::Expect documentation suggests. Also, it might be easier for you to use the ->exec calls instead of the ->send + read_line.
For debugging further, pass in the log_stdout to the Net::SSH::Expect constructor and see if you can detect anything awry happening. Why did you comment out 'use strict'? Always 'use strict' and 'use warnings'