Allowing automatic command execution as root on Linux using SSH

What is a good setup that allows automatic execution of a command or a script on a remote server with root privileges using SSH?

I'm aware (only vaguely for some the options) of the following options:

  • Allowing a direct login by root (PermitRootLogin) (and possibly forcing key authentication).
  • Configuring sudo not to require a password (NOPASSWD flag in sudoers) and TTY (requiretty flag).
  • Configuring sudo to allow an execution of specific commands/scripts, when authenticated with a specific private key.
  • Setting the script owner as root and setting setuid permission.

But first, I'm not sure what are security consequences of these. For example I know that allowing root login is frowned upon. But I'm not sure, if that is not an obsolete point of view. From what I've understood, it looks like a password authentication is the danger. With public key authentication, the direct root login might be ok. And for some of the options, particularly the sudo, I'm not sure even about the configuration needed. While I am able to google all that, there might be security considerations that I may miss, that's why I'm asking for experts' opinion.

Note, that I'm asking for a server-side setup. The execution will be triggered by a program, not a tool like ssh, so I'm not looking for things like automatic client authentication.


Background: Being active in ssh tag on Stack Overflow, one of frequent questions that come up, are about various hacks that people attempt, while trying to execute a command/script (or even an SFTP server) over an SSH on a remote Unix/Linux server server using a root account using various programming languages (C#, Java, VB.NET, Python, etc.) and SSH libraries (SSH.NET, JSch, Paramiko, etc.).

The implementations, that the people attempt, usually try using su or sudo. These then prompt for a password. So the implementations then try to feed the password to the command input. As su and sudo often require terminal emulation for the password prompt, the implementation have to require PTY. Which in turn causes further troubles, as sessions with the terminal emulation often employ interactive features, like ANSI escape codes, pagination, etc. All these lead to loads of further unreliable hacks that attempt to remove or even interpret the ANSI escape codes or simulate large enough terminal to avoid pagination.

Few examples out of many:

  • “sudo” command executed with JSch requires password, even when the password is not required in an interactive SSH session
  • Getting “must be run from a terminal” when switching to root user using Paramiko module in Python
  • Executing command using “su -l” in SSH using Python
  • Using JSch to SFTP when one must also switch user

While I usually can provide a help with implementing these hacks, I also usually add a suggestion that there are better ways than automating sudo/su. But I'm not actually confident about providing details of those purported "better ways". A related question: Is sudo almost useless?

So I'm looking for a canonical answer from a Super User perspective, which can then be referred to and adapted for Stack Overflow purposes.


Solution 1:

General Considerations

  • Whenever using SSH, password authentication should be avoided, i.e. the /etc/ssh/sshd_config file should contain the following lines:

    PermitRootLogin no
    PasswordAuthentication no
    
  • However, if one - for some reason - has to use password authentication, one should use established, well known and tested tools, like sshpass. Do not start to pipe around passwords by yourself.

  • If using pubkey authentication it does not make sense to protect the private key by a passphrase, if the passphrase is in turn stored inside a config file or alike. If an attacker is able to gain filesystem read access to steal the private key file, he will also be able to steal the config file.

  • All commands that can run with user privileges, should run with user instead of root privileges.

  • The used programming language will not be considered in this answer, since there is no difference between Python's subprocess.call, Java's java.lang.Runtime.exec or a subshell environment inside a shell script with regards to security.

  • Further hardening the remote host against external attackers by configuring a firewall, putting IDS/IPS systems in place and the like will also not be considered, since it is out of scope.

That said, let us consider different scenarios:

Running Commands of Which a Finite Subset Requires Root Privileges

One should use a separate user (adduser --shell /bin/rbash --disabled-password remcmdexec will create a user with a restricted shell, not able to do local but only remote logins, see man adduser and man rbash) in combination with a sudoers file that allows this user to only run the required, finite set of commands as root (see man sudoers) on the remote host:

# /etc/sudoers

remcmdexec ALL = (root:root) NOPASSWD: /usr/bin/systemctl reload some.service

Requiring a password to run these commands with sudo does not make sense, since local login was disabled (--disabled-password parameter) and an attacker who was able to steal the key file will also be able to steel the required password (see answer's first section).

The authorized_keys file should contain further restrictions to prevent e.g. port forwarding, see man sshd:

restrict ssh-rsa <BASE64-PUBKEY-REPRESENTATION> remcmdexec

It should further be owned by root and being read- but not writable by the remcmdexec user, to prevent removal of SSH restrictions:

$ ls -l /home/remcmdexec/.ssh/authorized_keys
-rw-r--r-- 1 root root 739 Sep  18 22:47 /home/remcmdexec/.ssh/authorized_keys

Invoke e.g. ssh remcmdexec@remhost sudo /usr/bin/systemctl reload some.service for a command that requires root privileges. For all other commands skip sudo.

Running Commands of Which a Nonfinite but Proper Subset Requires Root Privileges

The same setup as presented at the answer's previous section can be used, but the sudoers file needs to be adapted:

remcmdexec ALL = (ALL:ALL) NOPASSWD: ALL

This is, because remcmdexec finally needs to gain root privileges. Understand that an attacker who is able to login as remcmdexec is now able to remove each and every restriction put in place on the remote host, no matter how intricately the way to achieve root privileges was designed. Therefore all those restrictions are futile in regards to security (A willing attacker). However, they are not futile in regards to safety (System failures).

Running a Nonfinite Set of Commands of Which All Require Root Privileges

It does no longer make sense to use sudo. Instead login in as a user with root privileges to run a command: ssh root@remhost /usr/bin/somecmd

Therefore the following line has to be present at the /etc/ssh/sshd_config file:

PermitRootLogin prohibit-password

However, keep the restrictions inside the authorized_keys file to preserves some basic safety.

Solution 2:

I believe an appropriate solution for a majority of simple cases [ ie if a framework like Puppet/Chef/CfEngine/Ansible is overkill ], but a high level of control of the remote system is required is to

(a) Require key based authentication (b) lock down the locations that can be used by SSH - particularly to lock down the IP addresses that root users can use (c) Ensure that the machines with this trust relationship are properly secured.

I do not believe "logging in as root is frowned upon", as much as "logging in as root to do things which do not require root permissions" is frowned upon. Having a guessable password makes it possible for the account to be brute-forced, so that is an issue to contend with, and by allowing people to log in as root they can make stupid errors with a wider scope, and do questionable things which hide malicious activity - but depending on the architecture this may not actually be meaningful.

This XKCD commic points out that depending on the environment, it may not actually matter if a root account is compromised.

Probably the simpist and most effective solution for the general case is to limit exactly who can log in as root by controlling the keys in conjunction with specifying an appropriate AllowUsers line in /etc/ssh/sshd.conf. An appropriate line to allow normal users but lock root access down might look like:

  AllowUsers normaluser1 normaluser2 [email protected]

Of-course, it is important that password based login is disallowed, or that the root account does not have a password (ie has a "!" or "*" in the password field of the password file.)

As you have postulated, if only a single (or small number of) program(s) need to be run, you might be better off setting up a specific user with key based authentication and allowing appropriate sudo access to the required commands.

There are, of-course, a lot of things that can be done to "lock down" access further, depending on the value of the data - selinux, remote logging, limited shell, time-of-day constraints, notifications immediately on logins, active log monitoring like fail2ban [ in addition to network restrictions which I see as non-optional] can all be part of a solution. That said, if you are simply controlling a system which can be blown away and recreated easily, much of this is likely overkill.

Please remember that security is built up in layers, in order to be able to get root access, an account should need to get through a number of layers. Locking down access, SSH private keys, firewalls, time-of-day restrictions [ and principle of least access, ie only providing access to what is needed, no more, logging, backups ] should all function together as part of good security.

Additional - SUDO access

Sudo is a mechanism to allow a regular user to run certain commands with elevated access, and is quite flexible and varied in scope. While a rundown of sudo is not appopriate here (but is well documented if you type man sudo in most distributions, the appropriate steps might be -

  1. Edit /etc/sudoers - Most variants of Linux have a special program to do this called "visudo" which needs to be run as root, and roughly equivalent to using the system editor to edit /etc/sudoers (which is another way to do this - although probably fround upon)

  2. Add/change appropriate lines to let a given user do particular things. If, for example, you wanted them to be able to mount directories for example and without having to enter a password, you would enter a line like :

    username    ALL=/sbin/mount NOPASSWD: ALL
    

To then run this command the appropriate user would run something like

  sudo mount /dev/xxx /path/to/mount

You can list multiple lines for multiple commands, uses groups rather then uses - sudoers is a fairly flexible subsystem. In many distributions it is also possible to add files with commands to a subdirectory like /etc/sudoers.d

Solution 3:

The following is an answer from the Unix Stackexchange post
How to remote execute ssh command a sudo command without password.

This method lets the administrator of the server to allow your account to execute one command as sudo without specifying the password. The command is probably the call to your script, and it is only allowed when run under your account. As the permitted command is entirely specified including arguments, I believe that this method is pretty secure.

you can tell sudo to skip password for some command.

e.g. in /etc/sudoers

archemar  ALL = (www-data) NOPASSWD: /bin/rm -rf /var/www/log/upload.*

this allow me to use

sudo -u www-data /bin/rm -rf /var/www/log/upload.*

as archemar without password.

Note that

sudo -u www-data rm -rf /var/www/log/upload.*

won't work (will ask a password) as rm differ from /bin/rm.

Be sure to edit /etc/sudoers using visudo command.

Once you've reach advanced level, you might wish to have your own sudo files in /etc/sudoers.d.