Do you need to execute some commands from batch scripts on a remote host but don’t want to allow full shell access? For a single command, you can use sshd’s ForceCommand
, but it allows only one command per account. This text explains how you can execute safe, remote, limited multiple commands.
The trick :
- Create a special, unique, passwordless user for remote commands, such as “sudo_lover”.
- ‘
ForceCommand
‘ runs a Python script named “use_sudo,” which acts like a safe version of “sudo $SSH_ORIGINAL_COMMAND”. You can configure allowed commands with sudo. - ‘
use_sudo
‘ logs commands (auth.log), making it easy to add a command by almost a copy and paste (for example, configuring rsync without the log would be almost impossible).
One drawback is the difficulty in redirecting STDOUT/STDERR. To redirect STDOUT, I use a second program named “out_to,” which takes the file to redirect to and the command to execute.
cat use_sudo
#!/usr/bin/python3
import os
import shlex
import logging
import logging.handlers
from logging.handlers import SysLogHandler
# logger configuration : auth.info
syslogHandler = logging.handlers.SysLogHandler(address='/dev/log', facility="auth")
formatter = logging.Formatter('use_sudo: %(levelname)s - %(message)s')
syslogHandler.setFormatter(formatter)
logger = logging.getLogger('use_sudo')
logger.setLevel(logging.INFO)
logger.addHandler(syslogHandler)
command_from_env = os.environ.get("SSH_ORIGINAL_COMMAND")
if(not command_from_env):
print("SSH_ORIGINAL_COMMAND is undefined.")
exit(1)
command_with_arguments = shlex.split(command_from_env)
logger.info(str(command_with_arguments))
command_for_sudo = [f"/usr/bin/sudo -n {command_with_arguments[0]} ...",'-n'] + command_with_arguments
os.execv("/usr/bin/sudo", command_for_sudo)
cat out_to
#!/usr/bin/python3
import os
import sys
import subprocess
arguments = sys.argv
outfile=arguments[1]
command = arguments[2:]
with open(outfile, 'w') as outfile_h:
try:
subprocess.run(command, stdin=sys.stdin, stdout=outfile_h, stderr=sys.stderr, check=True)
except subprocess.CalledProcessError as e:
print(f"Error while executing : {e}")