Thursday, January 15, 2015

Remote sudo with reasonably secure supplying of password

I've recently been exposed to some scripts where the approach to getting info requiring root access on a remote box was to run ssh via an expect script, then run a sudo su -, supply the password, and run commands. No checking of the output of commands was run, the expect script just waited for the very last character of the prompt to shovel in more shit.

It kinda made me feel like this.
Did it work? Sure... but I still didn't think this was a great idea for about as many reasons as I listed steps. Probably most of you would agree. Plus, the output was then everything you'd get from an interactive session, so they had to use a pipeline of greps and awks to get the data they wanted.

It hurt my soul to look at it. Not only was it sloppy and dangerous, it was just inefficient and ugly as shit. Even when the output was a single line of exactly what they wanted, they actually had to add a tag to that line so their grep could find it - and then strip the tag.


So I was curious - what's the best way to supply a password to a remote system when you have ssh access and sudo - but not NOPASSWD sudo?  Also, as a bonus, I want to keep the password out of plain visible text (especially in ps output) as much as possible. I never want my password in a command line, I never want it in a file, and I never want it appearing anywhere where it could possibly be read. (but I'm funny like that)

Here's what I came up with which might be useful for you.

user=cmh
host=myhost.example.com
read -s -p "Enter the password for $user@$host: " MYPASS
sn=$(echo -E $MYPASS | \
    ssh $user@$host 'sudo -Sp "" /usr/sbin/dmidecode -s system-serial-number')

First we set the username and remote hostname in vars. Then, the third line is a read command which suppresses echoing the input (-s) and specifies a nice prompt with the -p option, putting the input into a var called "MYPASS".

The last line assigns the output of a command substitution (the "$(...)" ) to a variable "sn". The substituted command echoes the value of MYPASS with escape chars disabled. (-E) It then pipes that password to the ssh which connects to the remote host, running the sudo command with the option to take the password via STDIN (-S) and to nullify the prompt (-p "") and running the command "/usr/sbin/dmidecode -s system-serial-number".

Obviously getting the remote system's serial number is just one thing, but it's a good example.

One sudo, one command run, no interactive shells, and it returns exactly what you want. Easy peasy, clean, and elegant.

Notes/Limitations/Dire warnings:
  • If you had to supply STDIN to the remote command, you might be out of luck, since you're supplying the password to sudo. Interestingly, though - this works:
    echo -e "$MYPASS\nderp" | \
        ssh $user@$host 'sudo -Sp "" /bin/bash -c "cat > /tmp/derpina"'

    My password goes to sudo - and then the remainder of the STDIN, in this case the word "derp" - goes to the bash command, which creates a file /tmp/derpina containing that text. This is starting to border on ugly, though, and if sudo doesn't want a password (your account has NOPASSWD set for the command, for example) that password goes right on through to the command... and that's horribad.
  • Lemme just reiterate what's said above - if sudo doesn't look for a password - even though you told it to use the -S option, it ignores it and passes that password right on through to the remote command. That could be suboptimal.
  • Careful with the quoting. In the example I used single quotes because I wasn't expanding any vars, but if I had to - supplying options, for instance - I'd have to use double quotes, at which point I'd have to change the sudo's "-p" option to use single quotes - or backslash escape the double quotes. Shell quoting isn't tricky if you understand it, but can be a beast if you don't. (learn your shell quoting rules)
  • Obviously the ssh works best if you have public key authentication set up. However, even if you don't, the pipeline doesn't interrupt standard SSH password input. I've tested that this still works if the remote system asks for a password. Yes, you then need to enter the password twice. (so, use public key auth) You might be able to use SSH_ASKPASS to supply the password, but that's another topic altogether.
  • You're storing your password in a shell variable. On the upside, even if you were to export the variable, it wouldn't show up in the /proc/{pid}/env for your shell - but it would for subshells. You'd need root or be the same user to read those anyway.
  • As bad as the script was - I did learn about dmidecode's -s option. Previously I'd been using dmidecode -t 1 and processing the output. This illustrates two things:
    • You can learn something useful from other folks, even if it's cleverly hidden amongst shitty work.
    • Manpages are your friend. Read them, and go back to them often, especially when you're scripting with those commands. Especially for commands you "know" really well.
  • You could skip the echo (and any issues with options) by using a here string:
       
    ssh $user@$remote .... <<< "$MYPASS"
    This would probably avoid potential problems with backslashes or other odd characters.
Other limitations? Any better ways that I'm missing?

No comments: