Remote control with expect-lite

I’ve recently been playing around with expect-lite, a wrapper for expect that allows you to automatically login to hosts with telnet or ssh to run commands on remote hosts without getting too much under the bonnet of expect syntax.

But for my purposes I needed to do a little tweaking to get it to do what I wanted to do.

The Problem

In this scenario, I need to run a series of commands across an estate of hundreds of servers, as root. I must login as a normal user and sudo is not an option, so that leaves me with becoming root using the expect script – the challenge here is how to accomplish this without storing the root password anywhere, or having it show up in the output of ps.

The solution

A little digging around the code and the method I chose was to have a shell script read my password on stdin, and pass it as an environment variable to expect-lite to use in my script. By default you can’t directly pass environment variables from your local shell to be used remotely by the script without specifically stating them in the command – eg ./expect-lite foo=$BAR – the problem here is the value of $BAR will show up in ps.


Modifications to expect-lite

Firstly, we need to make 2 small modifications to the expect-lite script itself. The first is another option to the supplied arguments to allow me to pass an environment variable by name (not value) to the expect script. To do this you can use this small patch

--- expect-lite 2010-07-06 15:21:06.271119000 +0000
+++ 2010-07-06 15:34:04.490118000 +0000
@@ -416,6 +416,8 @@
u {set user $user_value }
user_password -
p {set pass $user_value }
+ localenv -
+ e { set cli_namespace($user_value) $env($user_value) }
default {
set cli_namespace($user_var) $user_value
puts "Const: $user_var = $cli_namespace($user_var)"

This patch will add an option “e” or “localenv” that tells expect-lite to get that environment variable and make it available in the namespace of the expect script.

Secondly, in order to prevent the root password from being echoed to STDOUT during execution, make sure DEBUG, DEBUG_LOG and INFO are all set to 0 in the script.

Wrapper script

Now for your wrapper script, this script will read your root password without outputting the characters to the terminal and read a list of files from hosts.txt and execute your expect-lite script for each host. Here is an example.


echo -n "Enter root password: "
stty -echo
read rootpwd
stty echo
export rootpwd

for host in cat hosts.txt; do
./expect-lite remote_host=$host c=myscript.elt u=username e=rootpwd

Expect script

Now create an expect-lite script called myscript.elt to implement the root password, heres an example

> su -
>touch /tmp/testfile.txt


… create a text file called hosts.txt listing your hosts and run, it should prompt you for your root password and go off and execute commands as root on your remote hosts.