Is the sudo password sent in plain text?
Not on the network. Everything in an SSH session is encrypted, including what you type at a sudo prompt. The password is decrypted on the server, as it has to be for sudo to check it, so a compromised server can capture it. This is one reason not to reuse that password anywhere else.
The normal way: ssh -t
sudo needs a terminal to ask for a password, and a remote command gets none by default:
$ ssh user@server 'sudo whoami'
sudo: a terminal is required to read the password; either use the -S option to read from standard input or configure an askpass helper
sudo: a password is required
The error suggests -S, and this is where people go wrong. The simple fix is -t, which asks for a terminal:
ssh -t user@server 'sudo whoami'
You get the normal prompt, nothing is echoed, and nothing goes in a command line.
What leaks with sudo -S
Here is the pattern to avoid:
ssh user@server 'echo mypassword | sudo -S systemctl restart myapp'
The password is now part of a command, so it appears in three places:
- The process list on the server. While the command ran, another unprivileged account on the test server saw this with plain
ps:user bash -c echo mypassword | sudo -S -p "" sleep 3. - The process list on your machine. The whole text is an argument of
ssh, sopsshows it there as well. - Your local shell history, if you typed it, and any script or CI log that holds it.
If you really must feed sudo from a script
Read the password without echo and send it over standard input, so it never becomes an argument:
read -rs SUDO_PW
printf '%s\n' "$SUDO_PW" | ssh user@server 'sudo -S -p "" whoami'
unset SUDO_PW
printf is a shell builtin, so it does not start a process that ps can show. In the same test the other account saw only sudo -S -p sleep 3. -p "" removes the prompt from the output. sudo uses standard input for the password here, so the remote command should not need input of its own.
For automation: NOPASSWD for one exact command
A script should not hold a password at all. Allow only what it needs:
# /tmp/deploy.sudoers
deploy ALL=(root) NOPASSWD: /usr/bin/systemctl restart myapp, /usr/bin/journalctl -u myapp -n 100 --no-pager
Check the file before installing it, because a broken sudoers file can lock you out of sudo:
sudo visudo -cf /tmp/deploy.sudoers
sudo install -m 0440 -o root -g root /tmp/deploy.sudoers /etc/sudoers.d/deploy
sudo visudo -c
Then use it, with -n so a mismatch fails at once and does not hang waiting for a password:
$ ssh deploy@server 'sudo -n /usr/bin/systemctl restart myapp'
$ ssh deploy@server 'sudo -n /usr/bin/systemctl stop myapp'
sudo: a password is required
List what an account may run with sudo -l. Four traps:
- A command with no arguments in the rule allows any arguments. With
/usr/bin/idalone in the rule, bothsudo -n /usr/bin/idandsudo -n /usr/bin/id -uran in our test. Write the arguments you mean. - File names with a dot are ignored.
/etc/sudoers.d/deploy.confhad no effect in our test, while the same file nameddeployworked. - Some commands can start a shell. Editors, pagers,
find -execandtarcan run other programs as root, so allowing one of them can amount to allowing everything. This is why the rule above has--no-pager: without it, journalctl can open a pager. - Wildcards match spaces too. The sudoers manual warns that wildcards in arguments match any character, including white space, so they can match more arguments than you planned.
How long sudo remembers you
By default sudo does not ask again for 5 minutes, and the record is separate for each terminal. In the test, one ssh -t session ran a second sudo -n whoami with no prompt. To end it early, run sudo -k. To ask every time for one account, add this with visudo:
Defaults:deploy timestamp_timeout=0
A root shell left open is where most slips happen. See how to avoid mistakes on production servers.
sudo -A and askpass
With -A, sudo runs the program named in SUDO_ASKPASS and reads the password from its output:
SUDO_ASKPASS=$HOME/bin/askpass sudo -A whoami
It keeps the password out of the process list, but the helper must get it from somewhere. On a server with no graphical session, that usually means a file, as in our test, so the secret now sits on disk. A narrow NOPASSWD rule is normally the better answer.
Ansible
Use --ask-become-pass (-K) to be prompted, or keep ansible_become_password in a file encrypted with Ansible Vault. Do not pass it with -e on the command line, because that puts it in the process list and your history like any other argument.
A NOPASSWD rule for a few commands does not work with Ansible. Its documentation says privilege escalation cannot be limited to certain commands, because modules run from a temporary file whose name changes each time. It suggests using a separate account without this constraint.
Questions
Is the sudo password sent in plain text over SSH?
No. Everything in an SSH session is encrypted, including what you type at a sudo prompt. It is decrypted on the server, which has to check it.
Why does ssh say a terminal is required to read the password?
A remote command gets no terminal by default, and sudo needs one to ask for the password. Add -t, as in ssh -t user@server 'sudo command'.
Is NOPASSWD safe for scripts?
It is safer than a stored password when the rule names one exact command with its arguments. Avoid editors, pagers and other programs that can start a shell.
Sources
- sudo(8) manual
- sudoers(5) manual
- visudo(8) manual
- Understanding privilege escalation, Ansible documentation