http://www.perlmonks.org?node_id=172822

virtualsue has asked for the wisdom of the Perl Monks concerning the following question:

I have inherited a Perl program which performs a number of operations which require superuser/root privileges. The last thing it does is an ssh out to each a number of different hosts (specified by the user) and run local copies of the same program on those systems. Currently, this program is run by users via sudo, and the users are expected to set up their own ssh keys to the various hosts which the program will need to access.
sudo scriptname -a opt1 -b opt2 -w host1, host2, host3
scriptname does lots of things...then:
foreach $dest (@dests) { system /usr/local/bin/ssh -l <user> -i <user's ssh identity> -t $de +st sudo scriptname + opts }
This works OK when a human is in charge. They enter their password when they start the process, then for every host that the program needs to update. But now we also need to do this automagically, via cron. One condition is that root not be used for this task, as this would require setting up ssh keys for the root user. I can think of 2 ways to accomplish this in a way that won't have a large impact on the existing code. Assume a special user is created and ssh keys set up for all the hosts ever accessed, and then:
Have I stupidly missed some better way to do this? I like my 2nd option better, but wouldn't mind hearing your thoughts.

Replies are listed 'Best First'.
Re: Running Perl program w/root privs via cron
by Corion (Patriarch) on Jun 08, 2002 at 19:42 UTC

    This may be completely irrelevant, but are the cascading invocations really necessary ? My suggestion instead of cascading the program invocations, simply set up a crontab distribution scheme that starts the program on each (designated) machine - this is of course different from the commandline/manual invocation, but spares you a lot of hassle/security thoughts.

    At my shop, there are lots of rsh/rlogin solutions, so ssh is a major step up here, but as you're running from a cronjob already, you might want to create a special user and distribute his crontab all over the place, and have him mail/otherwise propagate the results from each machine.

    perl -MHTTP::Daemon -MHTTP::Response -MLWP::Simple -e ' ; # The $d = new HTTP::Daemon and fork and getprint $d->url and exit;#spider ($c = $d->accept())->get_request(); $c->send_response( new #in the HTTP::Response(200,$_,$_,qq(Just another Perl hacker\n))); ' # web
      Unfortunately, I didn't tell you some important info -
      1. The app decides which hosts it is going to update dynamically, it doesn't know ahead of time
      2. Eventually it may not be run via cron, it might also be triggered via db updates or some other method
      Your suggestion was very good given what I told you, and I thank you very much for it. :-)
Re: Running Perl program w/root privs via cron
by atcroft (Abbot) on Jun 08, 2002 at 19:35 UTC

    I'm not sure if I am reading this correctly (I fear I'm still asleep, and if so, please disregard), but something that might be helpful is the ability to make a public key without a password which, when used to log into an account, does one task only then exits. I do not know if this might be helpful, and I can (sleepily) see the possibility of using it to the localhost for the functions there requiring root-like abilities, and for the login to the other boxes.

    Not sure that helps, but look forward to hearing how you beat the problem, and the responses you get.

Re: Running Perl program w/root privs via cron
by Anonymous Monk on Jun 09, 2002 at 06:00 UTC
    Another option.

    The sudoers file allows you to specify that certain commands can be run by specific users as other users without a password. Scan its documentation for NOPASSWD for the syntax (unfortunately without any examples, nor do I have one handy).

    This combines the advantages of the two solutions that you list.

      Thanks! This looks like a technique that will fit the situation very well. sudo is a swiss army setuid wrapper tool, and I should have read the sudoers man page after reading the sudo man page. This way, the code stays essentially the same, and we retain the excellent logging/auditing capabilities of sudo.
Re: Running Perl program w/root privs via cron
by thraxil (Prior) on Jun 08, 2002 at 20:16 UTC
Re: Running Perl program w/root privs via cron
by greenFox (Vicar) on Jun 09, 2002 at 06:14 UTC

    Firstly let me say that embedding roots password in a script (any script) is very nasty and should be avoided at all cost!

    Since you don't seem to have a requirement to capture the output of the ssh command then the best solution (security wise) is to add a crontab entry on each host to run the script. No cross network priveledges required.

    If that is not a feasible option for some reason that you have not given us then read up on SSH forced commands. Basically a forced command is an SSH key pair which allows the running of only commands that you specify. It can be used with root with reasonable safety. If you use perl for your forced command (of course!) then you can switch on taint mode and basically treat it like a CGI (ie don't trust anything!). I have been meaning to write a tutorial on perl/ssh forced commands, here is some sample code for you-

    Sample call of ssh forced command using open3 so we can capture STDOUT & STDERR and also talk to STDIN (if required) on the remote host:

    my $ssh = "/usr/local/bin/ssh"; my @ssh_cmd_to_run=($ssh, "-i", $key_file, "user\@$host", "scriptn +ame", "options"); open3(*SSH_IN, *SSH_OUT, *ERROR, @ssh_cmd_to_run); print SSH_IN $parameters_you_dont_want_seen; close(SSH_IN); my $return = <SSH_OUT>; close(SSH_OUT); my @error=<ERROR>; close(ERROR);

    Sample ssh forced command would be something like this (untested)-

    #!/usr/bin/perl -wT # BEGIN { delete @ENV{qw(IFS CDPATH ENV BASH_ENV)}; $ENV{PATH} = ""; } use strict; process_ssh_command($ENV{SSH_ORIGINAL_COMMAND}); exit; sub process_ssh_command { my $ssh_command = shift || ''; if ( ! $ssh_command ){ print "Error: You must specify the command to run\n"; return; } if ( $ssh_command =~ /^scriptname\s+(options_regex)$/ ){ my $options= $1 || ''; # do something if options not set chomp(my $stdin = <STDIN>); # if you need it # untaint stdin system("/path/to/scriptname", $options); return; } print "Error: Unknown command or incorrect format \"$ssh_command\" +\n"; return; }
    I would tend to make the error messages terse so as not to give any information away to any-one snooping!

    In roots SSH authorized keys the forced command key would look something like this- command="/path/to/forced_command.pl" ssh-dss ......

    Update:If you look in the sshd(8) manpage under AUTHORIZED_KEYS FILE FORMAT the command="command" section describes this mechanism.

    --
    my $chainsaw = 'Perl';