Automating SSH to windows with Ruby - ruby

I have a 13 windows servers running Jenkins Slaves. For some reason (windows updates?), the Jenkins slaves periodically quit working and the Jenkins Slave service needs to be restarted. If I manually SSH to the machines (cygwin ssh server is running) I simply type:
net stop "Jenkins Slave"
net start "Jenkins Slave"
and this (almost) always solves the problem.
So I wrote a Ruby script to automate this.
Here is is:
#!/usr/bin/env ruby
require 'rubygems'
require 'net/ssh'
USER = 'Administrator'
PASS = 'PASSWORD'
hosts = [:breckenridge, :carbondale, :crestone, :denali, :gunnison, :sneffels, "mammoth", "whitney", "snowmass", "firestone", "avon", :grizzly, :silverton]
hosts.each {|host|
puts "SSHing #{host} ..."
Net::SSH.start( HOST, USER, :password => PASS ) do |ssh|
puts ssh.exec!('net stop "Jenkins Slave"')
puts ssh.exec!('net start "Jenkins Slave"')
puts "Logging out..."
end
}
The script executes on all machines, I see output that the service has started. However, this never works. When I ssh back to the machine, the service hasn't started.
Sadly, I can't use Linux - I'm not in control of these machines.
Any ideas on why a manually executed SSH works, but the script doesn't?
Thanks
phil

I tried it out in Pry and found two issues:
HOST is undefined, it should be host as this is the variable being passed into the block.
SSH.start expects the parameters to be STRING class, so add the .to_s as indicated below.
Also, I switched it to the idiomatic Ruby pattern of using do...end when a block extends past 1 line.
hosts.each do |host|
puts "SSHing #{host} ..."
Net::SSH.start( host.to_s, USER, :password => PASS ) do |ssh|
puts ssh.exec!('date')
puts "Logging out..."
end
end
I tested this in Pry and it's now working. I hope this helps.

Related

Losing environment variable set via ssh execute. Ruby & net-ssh

I would like to set Linux environment variables in net-ssh start and use them further down in my code. But I am losing the scope of the variables. Can you please advise how that can be achieved.
I am using net-ssh and logging into Linux via rsa key. I have set a environment variable which I would like to use further down but I am losing the scope of the variable.
ssh = Net::SSH.start(host,
username
)
result = ssh.exec!('setenv SYBASE /opt/sybase && printenv') ### Can See environment variable SYBASE
puts result
puts "**********************************************************************************"
result = ssh.exec!('printenv') #### Lost the environment variable SYBASE set above
puts result
puts "&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&"
Each exec creates an environment on it's own, environmont variables are lost.
Like you do with your && (execute next command if first succeeds) or with ; (execute anyway) you can chain commands.
You can also send a block like this to do multiple actions
Net::SSH.start("host", "user") do |ssh|
ssh.exec! "cp /some/file /another/location"
hostname = ssh.exec!("hostname")
ssh.open_channel do |ch|
ch.exec "sudo -p 'sudo password: ' ls" do |ch, success|
abort "could not execute sudo ls" unless success
ch.on_data do |ch, data|
print data
if data =~ /sudo password: /
ch.send_data("password\n")
end
end
end
end
ssh.loop
end
Or use the gem net-ssh-session.
#peter thank you for suggesting net-ssh-session.
However net-ssh-session needed recompile to make it compatible with net-ssh 5.2.0 version.
Example here works great and is what needed for me.

Unix commands work on server but not in ruby ssh session

I am trying to learn how to use the net-ssh gem for ruby. I want to execute the commands below, after I login to the directory - /home/james.
cd /
pwd
ls
When I do this with putty, it works and i can see a list of directories. But, when I do it with ruby code, it does not give me the the same output.
require 'rubygems'
require 'net/ssh'
host = 'server'
user = 'james'
pass = 'password123'
def get_ssh(host, user, pass)
ssh = nil
begin
ssh = Net::SSH.start(host, user, :password => pass)
puts "conn successful!"
rescue
puts "error - cannot connect to host"
end
return ssh
end
conn = get_ssh(host, user, pass)
def exec(linux_code, conn)
puts linux_code
result = conn.exec!(linux_code)
puts result
end
exec('cd /', conn)
exec('pwd', conn)
exec('ls', conn)
conn.close
Output -
conn successful!
cd /
nil
pwd
/home/james
ls
nil
I was expecting pwd to give me / instead of /home/james. That is how it works in putty. What is the mistake in the ruby code?
It seems like every command runs on it's own environment, so the current directory is not carried over exec to exec. You can verify this if you do:
exec('cd / && pwd', conn)
It will print /. It is not clear from the documentation how to make all the commands execute on the same environment or if this is even possible at all.
This is because net/ssh is stateless, so it opens a new connection with each command execution.
You can use the rye gem that implements a work around for this. but I do not know if it works with ruby > 2, since its development is not that active.
Another way is to use a pty process, in which you'll open a pseudo terminal with the ssh command, than use the input and output files to write commands for the terminal and read the results. To read the results you need to use the select method of the IO class. But you need to learn how to use those utilities since its not that obvious for a non experienced programmer.
And, Yey, I found how to do that, and in fact it is so simple. I think I did not get to this solution last time, because I was a little new to this thing of net-ssh, pty terminal. But yey, I found it finally, and here and example.
require 'net/ssh'
shell = {} #this will save the open channel so that we can use it accross threads
threads = []
# the shell thread
threads << Thread.new do
# Connect to the server
Net::SSH.start('localhost', 'your_user_name', password: 'your_password') do |session|
# Open an ssh channel
session.open_channel do |channel|
# send a shell request, this will open an interactive shell to the server
channel.send_channel_request "shell" do |ch, success|
if success
# Save the channel to be used in the other thread to send commands
shell[:ch] = ch
# Register a data event
# this will be triggered whenever there is data(output) from the server
ch.on_data do |ch, data|
puts data
end
end
end
end
end
end
# the commands thread
threads << Thread.new do
loop do
# This will prompt for a command in the terminal
print ">"
cmd = gets
# Here you've to make sure that cmd ends with '\n'
# since in this example the cmd is got from the user it ends with
#a trailing eol
shell[:ch].send_data cmd
# exit if the user enters the exit command
break if cmd == "exit\n"
end
end
threads.each(&:join)
and here we are, an interactive terminal using net-ssh ruby gem.
For more info look here its for the previous version 1, but it is so useful for you to understand how every piece works. And here

Chef Bash Resource Not Running

I've currently created a custom lwrp that essentially runs a bash script that curls for the localhost after tomcat restarts to make the sure the service is running.
My provider file looks like this:
use_inline_resources
action :run do
bash "checkhealth" do
user "root"
code <<-EOF
echo Started counting
curl http://localhost/version.html
...
EOF
end
end
On one of my nodes, I have the following block:
service "node" do
supports :start => true, :stop => true, :restart => true, :status => true
action :nothing
notifies :run, "healthcheck[check-status]", :delayed
end
And when i run chef-client, I can see the echos from the bash code running.
However, on a different node, I have a block like this:
service "tomcat" do
action :restart
notifies :run, "healthcheck[check-status]", :delayed
end
But I can't see any output from the echo and it doesn't look like the bash code is running. I know the bash resource is being executed because the log output says the bash resource was successfully run. However, there is a very long delay after the log says:
action run[2014-07-23T09:10:23-07:00] INFO: Processing bash[checkhealth] action run
and when it says it was successful, which makes me think something weird is going on with the bash code, but I'm not sure what. This is where I'm stuck and hoping you guys could help me figure out this weird bug :). I'm guessing it may have something to do with the fact that in the first block, the action is :nothing, but the second block has :restart.
Let me know what you guys think.
Thanks!
Why not emulate what the old Jenkins cookbook (v1.2.2) used to do?
See:
Ruby block that checks for running service
Helper functions contained in a chef library

How do you prompt for a sudo password using Ruby?

Often I find myself needing to write scripts that have to execute some portions as a normal user and other portions as a super user. I am aware of one similar question on SO where the answer was to run the same script twice and execute it as sudo, however that is not sufficient for me. Some times I need to revert to being a normal user after a sudo operation.
I have written the following in Ruby to do this
#!/usr/bin/ruby
require 'rubygems'
require 'highline/import'
require 'pty'
require 'expect'
def sudorun(command, password)
`sudo -k`
PTY.spawn("sleep 1; sudo -u root #{command} 2>&1") { | stdin, stdout, pid |
begin
stdin.expect(/password/) {
stdout.write("#{password}\n")
puts stdin.read.lstrip
}
rescue Errno::EIO
end
}
end
Unfortunately, using that code if the user enters the wrong password the script crashes. Ideally it should give the user 3 tries to get the sudo password right. How do I fix this?
I am running this on Linux Ubuntu BTW.
In my opinion, running a script that does stuff internally with sudo is wrong. A better approach is to have the user run the whole script with sudo, and have the script fork lesser-privileged children to do stuff:
# Drops privileges to that of the specified user
def drop_priv user
Process.initgroups(user.username, user.gid)
Process::Sys.setegid(user.gid)
Process::Sys.setgid(user.gid)
Process::Sys.setuid(user.uid)
end
# Execute the provided block in a child process as the specified user
# The parent blocks until the child finishes.
def do_as_user user
unless pid = fork
drop_priv(user)
yield if block_given?
exit! 0 # prevent remainder of script from running in the child process
end
puts "Child running as PID #{pid} with reduced privs"
Process.wait(pid)
end
at_exit { puts 'Script finished.' }
User = Struct.new(:username, :uid, :gid)
user = User.new('nobody', 65534, 65534)
do_as_user(user) do
sleep 1 # do something more useful here
exit! 2 # optionally provide an exit code
end
puts "Child exited with status #{$?.exitstatus}"
puts 'Running stuff as root'
sleep 1
do_as_user(user) do
puts 'Doing stuff as a user'
sleep 1
end
This example script has two helper methods. #drop_priv takes an object with username, uid, and gid defined and properly reduces the permissions of the executing process. The #do_as_user method calls #drop_priv in a child process before yielding to the provided block. Note the use of #exit! to prevent the child from running any part of the script outside of the block while avoiding the at_exit hook.
Often overlooked security concerns to think about:
Inheritance of open file descriptors
Environment variable filtering
Run children in a chroot?
Depending on what the script is doing, any of these may need to be addressed. #drop_priv is an ideal place to handle all of them.
If it is possible, you could move the stuff you want executed as root to a seperate file and use the system() function to run it as sudo, including the sudo prompt etc:
system("sudo ruby stufftorunasroot.rb")
The system() function is blocking, so the flow of your program doesn't need to be changed.
I do not know if this is what you want or need, but have you tried sudo -A (search the web or the man page for SUDO_ASKPASS which might have a value like /usr/lib/openssh/gnome-ssh-askpass or similar)? This is what I use when I need to present a graphical password dialogue to users in GUI environments.
Sorry if this is the wrong answer, maybe you really want to remain on the console.
#!/usr/bin/ruby
# ... blabla, other code
# part which requires sudo:
system "sudo -p 'sudo password: ' #{command}"
# other stuff
# sudo again
system "sudo -p 'sudo password: ' #{command}"
# usually sudo 'remembers' that you just authenticated yourself successfuly and doesn't ask for the PW again...
# some more code...

Create process as user in ruby

The following code is in a file called run.rb, the idea is to run ruby as a different user so I can do some testing.
require 'rubygems'
require 'win32/process'
domain = 'WORLDWIDE'
user_name = 'user'
password = 'password'
rubyScript = 'ruby test.rb'
Process.create(:command_line => rubyScript, :domain => domain, :with_logon => user_name, :password => password, :close_handles => false)
the contents of test.rb is:
require 'rubygems'
require 'watir'
browser = Watir::IE.new
browser.goto('http://localhost:44001/Users/List')
puts browser.text
when I run 'ruby run.rb' a command shell opens and then closes straight away.
Any ideas what I am doing wrong here?
Reviving a dead question, but in Windows NT, other users can't interact with your desktop. So if you're logged on as DOMAIN\user1 and spawn a process as DOMAIN\user2, the DOMAIN\user2 process cannot interact with DOMAIN\user1's desktop. An exception to this is if the process is running as a service and has the "Can interact with desktop" box checked in the service properties (but I think that requires that it run as SYSTEM).
It is possible to use the Local Security Policy (secpol.msc) to assign user rights to that user that permits that user to interact with everyone's desktops, but that would be unsafe to do if the user is granted the right to logon interactively.
It's probably running your command just fine, opening a new command window (since it's a new windows Process), then closing that window when it completes. Try throwing a "sleep 1000" at the end of test.rb. If I'm correct, that should keep the window around so that you can view the output.

Resources