cssh - central secure shell client

Description

cssh is a client program that allows systems administrators to login to one or many remote ssh servers and perform tasks from one central location using either straight command line or configuration files NOT interactively.

If you are looking for a way to do remote administration of many machines interactively this is not the program for you. However, I have considered coding in semi-interactive abilties to this program. That won't be coded in until I get this to a more stable release as it is.

Introduction

In the past I have heard of systems administrators ask if there is a way for them to run a command over a bunch of machines at the same time. Some would recommend dsh which could do both rsh and ssh. I hadn't tried dsh before I started writing this cssh and it wasn't until I was about 85% finished with the alpha version of cssh that a friend of mine told me about dsh. So I figured, "Why re-invent the wheel"? I downloaded dsh and checked out the code only to find that there were a few things in that program I didn't particularly like *and* my program already had, what I thought was, more functionality in terms of flexibility.

So, I continued the development of cssh. I am pleased with it so far. I see an application that is useful and has growth potential. Cssh was written entirely in Perl (no system calls). I'm a purist, so shoot me (figuratively speaking).

Installation

Cssh was written on a Linux machine using Perl 5.8.x. It has been tested using 5.6.x and up. More testing will have to be done but I am confident that it will run fine on any machine that has Net::SSH::Perl installed successfully.

Cssh requires Net::SSH::Perl to be installed properly in order to work. If you don't know if Net::SSH::Perl is installed try:

perl -MNet::SSH::Perl -e 'print "ok\n"'

If you got the word 'ok' by itself then you are good to go. Note that Net::SSH::Perl need only be installed on the machine from which you intend to run cssh.

If Net::SSH::Perl is not installed on your machine and you don't know how to install Net::SSH::Perl then the following command- line might be helpful:

perl -MCPAN -e 'install Net::SSH::Perl'

You will most likely get a bunch of questions about dependencies for Net::SSH::Perl if you have used CPAN in the past. If this is the case I generally follow all of the dependencies for portability sake. I recommend installing all of the dependencies.

If you've never used CPAN then you will undoubtedly get a set of questions setting CPAN up first. Once CPAN is properly set up THEN you will get the dependency questions about Net::SSH::Perl. Pain in the rear? A little if you don't know what you are doing. You *might* have an out-dated version of Perl on your machine. I don't know what will happen if you try to use older versions of Perl (< 5.6.x) but chances are CPAN *might* try to upgrade your version of Perl if certain core libraries (modules) are out of date on your machine. Just make note that if you see CPAN attempt to download a file like perl5.8.1.tar.gz or something then that is what is happening. If you continue with the "upgrade" just make sure you have plenty of time and some know-how to what you are doing. Perl builds nicely and is pretty simple to upgrade but some of the questions it asks you can be confusing. Usually the default answers you are presented with are suffice. Perl installation is beyond the scope of this how-to so you should refer to Perl documentation in cases that refer directly to Perl.

If you want more information on how to use CPAN consider going to http://www.perlmonks.org and use their super-search. Oh, you should probably run this as root.

Usage

        cssh <-s server[[:'cmds'][,server[:'cmd;cmd2'],server]]... | 
             -c config file> [-h] [-d <1-3>] [-n] [-p ] [-a]
             [-l ] [-q] [-b] [-x] [-y] [-e] [-C] [-P] [-S] [-e]

        -s server     - specifies a list of servers to run remote commands
                        on.

        -c configfile - This is a config file that cssh can slurp and use 
                        rather than specifying what needs to be done via 
                        the command line.
                    
        -h            - This help message.

        -d            - Verbose or debug output.  Requires a value of 1 to
                        3.  The value '1' is very little output while '3'
                        is quite verbose.
                        If you specify anything greater than 3 then the
                        debug switch will be ignored.

        -x            - Do not send the default commands to the remote host.

        -n            - Do not send any commands to the remote host.  Only 
                        show what would be sent. (*)

        -l            - The login username for the ssh login on the remote 
                        host.  Currently, you can specify only one username
                        which means that the username must be the same for
                        all hosts you want to run commands on during a cssh
                        session.  In the future (near future I might add) I
                        will add support for separate usernames per host IF
                        I can work out some logic issues on how to implement
                        the feature.

        -p            - specify ssh login password.  NOTE: Not a
                        very secure thing to do and it is highly dis-
                        couraged.  If you do this, cssh will attempt to hide
                        the password in the process list (ps command) but
                        a guru knows how to get around the tactics that this
                        program employs to hide it.

        -a            - This switch will make cssh execute the argument 
                        supplied to this switch to every host during a 
                        session.
                   
        -q            - Quiet processing.  Logs all output instead of to 
                        STDOUT. (*)

        -b            - fork each connection into the background enabling
                        parallel runs on a per server basis. (*)

        -y            - assume yes answer to non-zero exit stati that occur
                        on remote commands.
                        Normally, when running in non-fork'ed mode, when 
                        a non-zero exit status occurs from a command run 
                        on the remote host then you will be asked if you 
                        want to continue.  If you specify -y then cssh 
                        assumes 'yes' to all such inquiries.  When running 
                        in fork'ed mode cssh normally quits on non-zero 
                        exit status events unless -y is specified then cssh 
                        continues after a non-zero exit status occurs.

        -e            - Shows some examples of how to use this program.

        -P            - Specify the ssh protocol to use.  The 
                        default is protocol 2.  SSH protocol 1 is secondary.

        -C            - Specify the cipher to use.  The default 
                        cipher is Blowfish.

        -S            - Turn ssh debugging on.
                        This is the same as using ssh -v.
    
        (*) means that this particular switch does not work 100% (DEFUNCT)
for more on -s see the section on Running CLI.
for more on -c see the section on The Configuration File.

Running CLI


Using the CLI (command line interface) is confusing at first but its actually quite simple.

Lets start with a few simple rules. When specifying the -s argument you always need to supply at least a hostname. If you want to specify a set of commands to be run on that host then simply denote that by using a colon ':' delimiter immediately after the hostname.

example:
If you wanted to see a listing of files on foo.com:

cssh -s foo.com:ls

Simple enough, right? Ok. Now one may ask, "What about if I want to run several commands to foo.com besides the 'ls' command?" Remember this. Cssh only sends the series of commands after the ':' to the remote host. Therefore everything you place after the ':' is interpretted by the remote host. So, having said that, you would place multiple commands for the remote host by simply separating the commands (after the ':') with a series of commands delimited by semi- colons. Don't worry though. Cssh doesn't actualy send the commands to the remote host as one string. Cssh sends each individual command (between semi-colons) to the remote host and watches for the exit code from each command. Cssh, by default, stops and asks what should be done if a command exits with a non-zero exit status. you want to do if a command fails for some reason.

example:
So, now you want to see a listing of files on foo.com and you also want to remove those files! Remember, this is just an example.

cssh -s foo.com:'ls;rm *'

The only thing that you might ask here is why am I using the ''s. If you put multiple commands for a host in the command section for that host you need to encapsulate the commands with quotes. You can use single or double ticks but I use single to that the command host's shell (the shell you are running cssh from) doesn't try to interpret the metacharacters within the command argument. Now, getting a little more complicated we will move on...

example:
cssh -s foo.com:'ls;rm *',bar.com:'ls;rm *'

Now I have told cssh that I want it to list files and then remove them on both foo.com and bar.com. Sending cssh a series of hosts and commands is done by separating host-commands pairs with commas. You can do this with as many host-commands pairs as you want. For purposes of example only, I used the same commands for both hosts. Cssh doesn't care what commands you tell it to send to each host. Each host-commands pair is completely autonomous from the others.

example:
cssh -s foo.com,bar.com -a 'ls;rm *'

Now, this is where things get fun. This example does *exactly* the same thing as the previous example does. This is what makes cssh nice and flexible. What this command says is for servers foo.com and bar.com (for all servers) run the commands 'ls' and 'rm *'.

Configuration File

Preface

Cssh will look for the config file you specify with the -c switch in $HOME/cssh_configs by default if you do not use a path to your config file.

example:
cssh -c myconfig.cfg

Cssh looks for $HOME/cssh_configs/myconfig.cfg and slurps it.

example:
cssh -c /path/to/myconfig.cfg

Cssh looks for /path/to/myconfig.cfg and slurps it.

So, now, you ask, how do I use the configuration file? For people who do not know Perl this section is for you. For those of you who do know Perl, here is a brief description. Cssh requires the file you specify with the -c switch. That means that Perl is basically loading your config file as a module. Therefore, the config file needs to be written with Perl structure. The %server hash is required to be correct as it is important for Perl to understand what it is reading.

Structure

The configuration file is a file with some Perl structure to it. I decided it was easier to allow the Perl interpretter to read the config file and to create the structure required from there rather than to write a whole parser to create the data structure for cssh to munch on. So, here are a few things about the structure of the config file. This is not intended to explain *why* the structure is as it is but rather this is how the structure *is*. You can memorize this and be well with it.

Anything following a hash symbol ('#') will be ignored. This allows you to create comments which are good.

1. The first line of the config file should be:

package cfg;

DO NOT FORGET IT! If you do then the config file will not work and you will get errors when attempting to load the config file (when you use the -c switch).

2. The second (optional) line could be:

our @CMDS = ('cmd1','cmd2','cmd3');

Let me 'splain this one to you. This becomes a global array of commands that will get run for every server specified during a session of cssh that uses this config file. In fact, the commands used in this array would be called the *default* commands; meaning the commands that will get run for every host specified.

example:
our @CMDS = ('ls','cd /tmp','find . -size +0c -exec rm {} \;');

Now, these commands will run on every machine you specify in this config file. I will show a more comprehensive view of this later.

3. Finally, the last thing in the config file:

our %servers = ( 'host1' => [ 'cmd1','cmd2',... ], 'host2' => ...);

This takes a little explaining. Before I explain I will break this down a bit into an easier-to-read format.

    our %servers = (
                      'host1' => [ 
                                    'cmd1',
                                    'cmd2',
                                    'cmd3',
                                 ],
                      'host2' => [
                                    'diff command'  ,
                                    'something else',
                                 ],
                      'host3' => [
                                    'echo blah'   ,
                                    'ls /tmp'     ,
                                    'rm /tmp/*'   ,
                                    'cmd1'        ,
                                    'diff command',
                                 ],
                   );
I shouldn't have to explain this too much. In a nutshell:

For host1 run the hypothetical commands "cmd1", "cmd2", and "cmd3". Run "diff command", and "something else" on host2 and run "echo blah", "ls /tmp", "rm /tmp/*', "cmd1", and "diff command" on host3. The commands are run in the order that is specified in the config file. The hosts, however, are not run in the order, necessarily, that they are specified in the config file. This is a weakness of Perl and is not easily remedied.

Notice that each host is capable of running any number of commands. You may also want to take note that each set of commands is autonomous with the other commands being run by each host. In other words, notice how host3 runs some of the same commands as host1 and host2.

example:
(using the above config file which I will call myconfig.cfg)

cssh -c myconfig.cfg

This will run cssh against myconfig.cfg and run the commands specified for each host namely host1, host2, and host3. If you have the @CMDS variable set then those commands will also be run on every host for a given session.

example:
cssh -c myconfig.cfg -a 'find /home -size 0c'

This will run cssh against myconfig.cfg and run the commands specified for each host namely host1, host2, and host3 AND it will also run the "find" command listed after the -a switch on every host specified by the config file. If you have the @CMDS variable set then those commands will also be run on every host for a given session.

example:
cssh -c myconfig.cfg -a 'find /home -size 0c' -s host4:'grep err /v/log'

This will run cssh against myconfig.cfg and run the commands specified for each host namely host1, host2, and host3 AND it will also run the "find" command listed after the -a switch on every host specified by the config file AND by the -s switch. It will also run the command "grep err /v/log" on host4. If you have the @CMDS variable set then those commands will also be run on every host for a given session.

SECURITY

Cssh is written to allow users to perform sudo commands. The password you supply to cssh is the password cssh will use for every login for every machine its suppose to hit for a session!! This is very important to note so that you don't attempt to have cssh login to multiple machines with a single user/password that won't work on some machines. Perhaps a future build will be able to run as multiple users with multiple passwords. However, doing such a thing will impact security but that is a discussion for another day.

You can run cssh with the password specified on the command line but I strongly recommend you not do such things in heavily or moderately used environments. Doing so will cause that the password for a machine be capable of showing in a process list and a user watching that process list being able to see it. Cssh attempts to mask the password in the process list but such masking is easy to get around. I only provided the ability for ease of use in low-risk environments. Even then I still recommend not using the -p switch!

EXAMPLE

myconfig.cfg
    package cfg;

    our %servers = (
                      'host1' => [],
                      'host2' => [],
                      'host3' => [],
                   );
This configuration is a really easy way to get cssh to run the same commands on all hosts stated in the config file. Now, using the config above and the following cssh command you can do just that!

cssh -c myconfig.cfg -a 'cmd1;cmd2;cmd3'

BUGS

  • Plenty right now since this program is still in alpha stages of development. The most notable ones are the background processing of hosts thus dissallowing cssh from being able to perform parallel runs of hosts.
  • The logging of output (both for -q and for -b). This part is just poorly implemented and needs to be re-done.
  • Hosts are not hit in order specified in the config file. This is a Perl weakness more than a true bug but could probably be worked around.
  • Runs fine with sudo access but what su? Right now, nothing is coded in cssh to handle 'su' password prompts.
  • Please report all bugs to the maintainer of this program. Currently, that is Jim Conner .
  • TODO

  • Use of ssh keys
  • Ability to specify username per host (questionable)
  • forking to be fixed so that parallel runs work. * Output logging
    facility cleaned up * more to come * an installation script? Hmm, that would be nice.
  • AUTHOR

    This program was written by Jim Conner

    The cssh project is being housed by sourceforge.net at http://cssh.sourceforge.net

    You can find me on IRC:
    irc.undernet.org - NOTJames #LDS
    irc.freenode.net - cloaked #Perl
    irc.netfrag.com - devnull[Zap] #zap

    NOTES

    I was really surprised to learn that something like this wasn't available out there on the Internet (big 'I'). On the other hand I truly hope that this project grows into something useful as one of the reasons I write programs is to make other peoples' lives (and my own) easier.
    - Jim