How to skip incorrect ssh passwords - ruby

I have a script that logs into 1000+ wireless units and audits and them. Some of their passwords have been changed. I would like to skip these units without specifically telling ruby which ips to skip. The script is multithreaded and gets hung up because when ruby sshes into a unit and uses the wrong password it will ask for the password and the whole script comes to a halt.
*How to skip or rescue wrong password attempts for ruby net/ssh
Net::SSH.start('ip','user',password: 'pass', paranoid: false) do |ssh|
end
with the incorrect password will prompt you like so
admin#192.168.1.1's password:

In latest net-ssh (2.10.0.beta1), the new option :non_interactive was added to ignore the password prompt. Here are the details.
It works for me. For your case it should be
Net::SSH.start('ip','user',password: 'pass', paranoid: false, :non_interactive=>true) do |ssh|
end

options = {
password: password,
number_of_password_prompts: 0,
timeout: SSH_TIMEOUT
}
try to use 'number_of_password_prompts: 0,', It may be perfect

module Net::SSH::PromptMethods::Clear
def prompt(prompt,echo = false)
raise Error, "Details"
# $stdout.print(prompt)
# $stdout.flush
# $stdin.gets.chomp
end
end
Ended up using something similar to this

Related

Can I programmatically interact with an "installer" (ie. ./install.sh) [duplicate]

Is there an Expect equivalent gem for Ruby?
I tried searching on code.google and rubygems.org, but sadly it did not show up.
FYI: Expect is a Unix automation and testing tool, written by Don Libes as an extension to the Tcl scripting language, for interactive applications such as telnet, ftp, passwd, fsck, rlogin, tip, ssh, and others.
Ruby comes with the PTY module for setting up pseudoterminals to drive interactive command line applications. With it comes an expect method that allows you to interact with an application kinda like Expect. For learning how to use expect, I found "What to expect from the Ruby expect library?" helpful.
As far as gems go, maybe checkout greenletters which is supposed to improve upon PTY + expect (although I haven't tried it myself).
I recently spent quite a bit of time struggling with this issue (I am stuck with 1.8.7). I found this question, this blog post and this forum thread really useful.
At the end this is my application code if anyone is interested in a little example (pass the password to rpm when signing packages):
def run_interactive command, password, promt
output = ''
begin
r, w, pid = PTY.spawn(command)
puts r.expect(promt)
sleep(0.5)
w.puts(password)
begin
r.each { |l| output += l }
rescue Errno::EIO
end
$?.exitstatus
Process.wait(pid)
rescue PTY::ChildExited => e
$stderr.puts "The child process #{e} exited! #{$!.status.exitstatus}"
end
output
end
password = "mypassword"
command = "rpm --define '_signature gpg' --define '_gpg_name #{key_id}' --addsign #{package}"
promt = %r{.*: }
expected = %r{good}
output = run_interactive(command, password, promt)
if output.match(expected)
puts output
else
abort "Error: expected: '#{expected}' got '#{output}'"
end
It has little error checking but it was all I needed.
Edit: Update the code with Process.wait(pid) to make sure it finishes before continuing and add comment about this being for 1.8.7.
checkout this rubygem: https://github.com/abates/ruby_expect. It could handle some small task for you. from its official example, it's enough to 'enter password' and login and interactive with local script.
here is an example that update the git code (which is authenticated with password):
require 'rubygems'
require 'ruby_expect'
def update_code
password = 'your password here'
exp = RubyExpect::Expect.spawn('git pull', :debug => true)
exp.procedure do
each do
expect /password: / do
send password
end
end
end
end
update_code
just run the code above, and your will see like this:
$ ruby update_code.rb
shensiwei#gforge.1ver??.net's password:
remote: Counting objects: 133, done.
remote: Compressing objects: 100% (84/84), done.
remote: Total 85 (delta 62), reused 0 (delta 0)
Unpacking objects: 100% (85/85), done.
for more example and details, please dive into its source code.
expect4r seems to do what you are asking for, though it is made specific for connections to Cisco and Juniper devices.
Perhaps even better is yax as this is "yet another expect".
RExpect
From the project's website:
RExpect is a drop in replacement for the expect.rb module in the
standard library that is faster and more robust, cabable of driving
many devices simultaneously.
parley is another one you can try, (written by me). It is inspired by Perl expect.

Detect Vagrant up in vagrantfile

In my vagrantfile I use two lines of rudy code to get the username and password from the console and pass it on to the shell script for creating an additional user account instead of the default vagrant
This works well for vagrant up but it beats me by prompting the same when I run any other vagrant commands like vagrant status. Is there a way to catch if the vagrant file is called with up command?
require 'io/console'
print "Please enter username: "
#username = STDIN.gets
print "Please enter password: "
#password = STDIN.noecho(&:gets)
Vagrant.configure("2") do |config|
config.vm.box = "generic/debian10"
["Node 01"].each do |node_name|
config.vm.define node_name do |node|
node.vm.provision "shell" do |p|
p.args = [#username, #password]
p.path = "create_user.sh"
end
end
end
end
UPDATE
After #Matt Schuchard's suggestion, I've tried triggers; However, the global variables that are captured inside the trigger block are not shared with the place where I pass them into the shell script (p.args = [#username, #password])
config.trigger.before :up do |trigger|
if #credential_captured == nil # I've used this to prevent executing the following block per each vm
print "Please enter username: "
#username = STDIN.gets
print "Please enter password: "
#password = STDIN.noecho(&:gets)
#credential_captured = true
end
end
Sprinkle Some Plain Ruby into the Vagrantfile
The best practice would really be to put an idempotent provisioning block at the end that picks up your information from the environment or some other fixed location rather than trying to do this as interactive user input within the Vagrantfile. However, if you really want to do it the way you're doing it, just remember that a Vagrantfile is mostly just a DSL on top of Ruby.
You can get the status of a given virtual machine with vagrant status. For a single node, you could just wrap your current prompts at the top in a conditional. For example:
# assuming a single node named "default"
if %x(vagrant status default) =~ /running/
# prompt here
end
If you're running multiple nodes, then I'd suggest wrapping your prompts into a method and moving your conditional down into the body of the shell provisioning block you already have. For example:
def prompt_user
# prompt here
end
# ...
if `vagrant status #{node_name}` =~ running
prompt_user unless #username && #password
node.vm.provision 'shell' do |p|
# provision your node here
end
end
There are likely ways to make this code more efficient, and probably other ways to do what you want, but this approach is fairly straightforward and should at least get you headed in the right direction.

ruby running commands on a remote machine

I'm trying to run commands remotely with ruby's net ssh. I need the output as well as the exit code. There are other stackoverflow threads on this topic but the accepted solutions do not work. Some people suggested using net-ssh-shell gem but as I tried it, I got an error saying that this package is in conflict with the version of my net-ssh package...
Here is what I have:
Net::SSH.start(remote_ip, username,:keys => [ssh_key_path], :verbose => :debug) do |ssh|
cmd = "blah bla"
result = ssh.exec!(cmd)
puts result
end
It works but it does not raise an exception if it fails. I've also tried using the channel to retrieve the exit code but it always returns 0:
channel.on_request("exit-status") do |ch,data|
exit_code = data.read_long
end
Please help me out. I've already tried several things based on wrong info on the internet.
If you are using Ruby 1.9+ I would suggest Open3.popen3:
i, o, e, t = Open3.popen3("ssh ... remote_user#remote_host remote_command")
i.write ... # if your command needs input
output = o.read
error = e.read
status = t.value.exitstatus

Proper way of dealing with input prompts in Capistrano?

What is the correct way of dealing with input prompts that are triggered by commands I run through Capistrano?
One example is the iptables-persistent package I install using aptitude. Despite the --no-gui flag, a prompt still comes up asking me to confirm how I want things configured.
Is there a way to pass parameters through command line to avoid such prompts?
I found and was able to implement this very helpful handle_command_with_input method from:
https://github.com/nesquena/cap-recipes/blob/master/lib/cap_recipes/tasks/utilities.rb
def handle_command_with_input(local_run_method, shell_command, input_query, response=nil)
send(local_run_method, shell_command, {:pty => true}) do |channel, stream, data|
if data =~ input_query
if response
logger.info "#{data} #{"*"*(rand(10)+5)}", channel[:host]
channel.send_data "#{response}\n"
else
logger.info data, channel[:host]
response = ::Capistrano::CLI.password_prompt "#{data}"
channel.send_data "#{response}\n"
end
else
logger.info data, channel[:host]
end
end
end
None of the code is mine. Gracias a Nesquena.

Respond to a SSH prompt before first command with ruby and Net::SSH

I'm trying to connect, using Net::SSH, to a server that immediately after
login executes a script that requires input from user. The user has to enter "1" or "2" and will receive some data via in the terminal afterwards.
My problem is that, although I am able to connect, I can not figure out a way to send "1\n" to the server and to receive the output.
The following code stops at "INFO -- net.ssh.connection.session[80906b74]: channel_open_confirmation: 0 0 0 32768".
Using channel.exec( "1\n" ) instead of channel.send_data unsurprisingly does not work either.
Net::SSH.start('host', 'user', :password => "pass", :auth_methods => ["password"], :verbose => :debug) do |session|
session.open_channel do |channel|
channel.on_data do |ch, data|
STDOUT.print data
end
channel.send_data( "1\n")
end
session.loop
end
Any ideas, anyone?
Thanks in advance
Can you verify that your send_data call is happening after you get the prompt from the remote server? Try constructing a channel.on_data block around your send_data call so that you can verify that you get the expected prompt from the server before you send a response.
You might not want to be using exec here. From the docs for Net::SSH::Connection::Channel:
Sends a channel request asking that
the given command be invoked.
You are wanting to send a text string to reply to a prompt, not invoke a command. The docs show exec being used to send full CLI commands like "ls -l /home".
Instead, send_data is probably what you want. The docs show it used to send arbitrary text such as channel.send_data("the password\n"). Note, however, this sentence in the docs:
Note that it does not immediately send
the data across the channel, but
instead merely appends the given data
to the channel‘s output buffer,
preparatory to being packaged up and
sent out the next time the connection
is accepting data.
You might want to take a look at channel.request_pty. It appears to be designed for interaction with a console-based application.
If you are trying to (in essence) script an SSH session that you would normally do manually, you may find it easier to use an expect-like interface (for example, a gem like sshExpect might be worth a try).
Thank you all for the pointers. I have been able to put my finger on the problem – besides using channel.request_pty it was also necessary to request a shell. The following finally works as expected:
Net::SSH.start('host', 'user', :password => "pass", :auth_methods => ["password"]) do |session|
session.open_channel do |channel|
channel.request_pty do |ch, success|
raise "Error requesting pty" unless success
ch.send_channel_request("shell") do |ch, success|
raise "Error opening shell" unless success
end
end
channel.on_data do |ch, data|
STDOUT.print data
end
channel.on_extended_data do |ch, type, data|
STDOUT.print "Error: #{data}\n"
end
channel.send_data( "1\n" )
session.loop
end
end
I'm not terribly familiar with the Net::SSH libs so I can't help with that per-se but it sounds like you could achieve what you want using Capistrano.
For example I have a capistrano task which connects to a remote server, runs a command which expects input and then continues. Capistrano takes care of the remote i/o. Maybe that could be a solution for you?
Hope it helps!
If I execute "1\n" in a shell the reply I get is: bash: 1: command not found
If I execute echo "1" I get: 1
Are you sure you want to try to execute the text you are sending? Maybe you were looking for something like:
output = ""
Net::SSH.start('host', 'user', :password => "pass") do |ssh|
output = ssh.exec! "echo 1"
end
puts output
I'm not proficient with that lib, but on SSH you can open multiple channels. Maybe the server only responds to the first default channel and if you open another one you get a fresh shell.

Resources