diff --git a/bin/sudoers-add b/bin/sudoers-add deleted file mode 100755 index e359d46..0000000 --- a/bin/sudoers-add +++ /dev/null @@ -1,84 +0,0 @@ -#!/usr/bin/env bash -# William Mantly -# MIT License -# https://github.com/wmantly/sudoers-add - -NEWLINE=$'\n' -CONTENT="" -ME="$(basename "$(test -L "$0" && readlink "$0" || echo "$0")")" - -if [ "$1" == "--help" ] || [ "$1" == "-h" ]; then - echo "Usage: $ME [file_path] [sudoers-file-name]" - echo "Usage: [content] | $ME sudoers-file-name" - echo "This will take a sudoers config validate it and add it to /etc/sudoers.d/{sudoers-file-name}" - echo "The config can come from a file, first usage example or piped in second example." - - exit 0 -fi - -if [ "$1" == "" ]; then - (>&2 echo "This command take at lest one argument. See $ME --help") - - exit 1 -fi - -if [ "$2" == "" ]; then - FILE_NAME=$1 - shift -else - FILE_NAME=$2 -fi - -if [[ $EUID -ne 0 ]]; then - echo "This script must be run as root" - - exit 1 -fi - -while read -r line -do - CONTENT+="${line}${NEWLINE}" -done < "${1:-/dev/stdin}" - -if [ "$CONTENT" == "" ]; then - (>&2 echo "No config content specified. See $ME --help") - exit 1 -fi - -if [ "$FILE_NAME" == "" ]; then - (>&2 echo "No sudoers file name specified. See $ME --help") - exit 1 -fi - -# Verify that the resulting file name begins with /etc/sudoers.d -FILE_NAME="$(realpath "/etc/sudoers.d/$FILE_NAME")" -if [[ "$FILE_NAME" != "/etc/sudoers.d/"* ]] ; then - echo -n "Invalid sudoers filename: Final sudoers file " - echo "location ($FILE_NAME) does not begin with /etc/sudoers.d" - exit 1 -fi - -# Make a temp file to hold the sudoers config -umask 077 -TEMP_FILE=$(mktemp) -echo "$CONTENT" > "$TEMP_FILE" - -# Make sure the content is valid -visudo_STDOUT=$(visudo -c -f "$TEMP_FILE" 2>&1) -visudo_code=$? -# The temp file is no longer needed -rm "$TEMP_FILE" - -if [ $visudo_code -eq 0 ]; then - echo "$CONTENT" > "$FILE_NAME" - chmod 0440 "$FILE_NAME" - echo "The sudoers file $FILE_NAME has been successfully created!" - - exit 0 -else - echo "Invalid sudoers config!" - echo "$visudo_STDOUT" - - exit 1 -fi - diff --git a/docs/installation.rst b/docs/installation.rst index 4dc18f3..97c62a8 100644 --- a/docs/installation.rst +++ b/docs/installation.rst @@ -19,6 +19,5 @@ Installation Optionally after installation ----------------------------- -- Add to sudoers file:: +- Install sudoers configuration. For details, see the "Sudoers File" section in :doc:`usage` - sshuttle --sudoers diff --git a/docs/manpage.rst b/docs/manpage.rst index 9445c8c..6ea75f3 100644 --- a/docs/manpage.rst +++ b/docs/manpage.rst @@ -262,28 +262,23 @@ Options makes it a lot easier to debug and test the :option:`--auto-hosts` feature. -.. option:: --sudoers - - sshuttle will auto generate the proper sudoers.d config file and add it. - Once this is completed, sshuttle will exit and tell the user if - it succeed or not. Do not call this options with sudo, it may generate a - incorrect config file. - .. option:: --sudoers-no-modify - sshuttle will auto generate the proper sudoers.d config and print it to - stdout. The option will not modify the system at all. + sshuttle prints a configuration to stdout which allows a user to + run sshuttle without a password. This option is INSECURE because, + with some cleverness, it also allows the user to run any command + as root without a password. The output also includes a suggested + method for you to install the configuration. + + Use --sudoers-user to modify the user that it applies to. .. option:: --sudoers-user - Set the user name or group with %group_name for passwordless operation. - Default is the current user.set ALL for all users. Only works with - --sudoers or --sudoers-no-modify option. - -.. option:: --sudoers-filename - - Set the file name for the sudoers.d file to be added. Default is - "sshuttle_auto". Only works with --sudoers. + Set the user name or group with %group_name for passwordless + operation. Default is the current user. Set to ALL for all users + (NOT RECOMMENDED: See note about security in --sudoers-no-modify + documentation above). Only works with the --sudoers-no-modify + option. .. option:: -t , --tmark= diff --git a/docs/usage.rst b/docs/usage.rst index 3711d04..912d846 100644 --- a/docs/usage.rst +++ b/docs/usage.rst @@ -71,44 +71,23 @@ admin access on the server. Sudoers File ------------ -sshuttle can auto-generate the proper sudoers.d file using the current user -for Linux and OSX. Doing this will allow sshuttle to run without asking for -the local sudo password and to give users who do not have sudo access -ability to run sshuttle:: - sshuttle --sudoers +sshuttle can generate a sudoers.d file for Linux and MacOS. This +allows one or more users to run sshuttle without entering the +local sudo password. **WARNING:** This option is *insecure* +because, with some cleverness, it also allows these users to run any +command (via the --ssh-cmd option) as root without a password. -DO NOT run this command with sudo, it will ask for your sudo password when -it is needed. - -A custom user or group can be set with the : -option:`sshuttle --sudoers --sudoers-username {user_descriptor}` option. Valid -values for this vary based on how your system is configured. Values such as -usernames, groups pre-pended with `%` and sudoers user aliases will work. See -the sudoers manual for more information on valid user specif actions. -The options must be used with `--sudoers`:: - - sshuttle --sudoers --sudoers-user mike - sshuttle --sudoers --sudoers-user %sudo - -The name of the file to be added to sudoers.d can be configured as well. This -is mostly not necessary but can be useful for giving more than one user -access to sshuttle. The default is `sshuttle_auto`:: - - sshuttle --sudoer --sudoers-filename sshuttle_auto_mike - sshuttle --sudoer --sudoers-filename sshuttle_auto_tommy - -You can also see what configuration will be added to your system without -modifying anything. This can be helpful if the auto feature does not work, or -you want more control. This option also works with `--sudoers-username`. -`--sudoers-filename` has no effect with this option:: +To print a sudo configuration file and see a suggested way to install it, run:: sshuttle --sudoers-no-modify -This will simply sprint the generated configuration to STDOUT. Example:: +A custom user or group can be set with the +:option:`sshuttle --sudoers-no-modify --sudoers-user {user_descriptor}` +option. Valid values for this vary based on how your system is configured. +Values such as usernames, groups pre-pended with `%` and sudoers user +aliases will work. See the sudoers manual for more information on valid +user specif actions. The option must be used with `--sudoers-no-modify`:: - 08:40 PM william$ sshuttle --sudoers-no-modify - - Cmnd_Alias SSHUTTLE304 = /usr/bin/env PYTHONPATH=/usr/local/lib/python2.7/dist-packages/sshuttle-0.78.5.dev30+gba5e6b5.d20180909-py2.7.egg /usr/bin/python /usr/local/bin/sshuttle --method auto --firewall - - william ALL=NOPASSWD: SSHUTTLE304 + sshuttle --sudoers-no-modify --sudoers-user mike + sshuttle --sudoers-no-modify --sudoers-user %sudo diff --git a/setup.py b/setup.py index 54b751d..df3032f 100755 --- a/setup.py +++ b/setup.py @@ -55,7 +55,6 @@ setup( "Programming Language :: Python :: 3.9", "Topic :: System :: Networking", ], - scripts=['bin/sudoers-add'], entry_points={ 'console_scripts': [ 'sshuttle = sshuttle.cmdline:main', diff --git a/sshuttle/cmdline.py b/sshuttle/cmdline.py index 2295d36..c619cd1 100644 --- a/sshuttle/cmdline.py +++ b/sshuttle/cmdline.py @@ -1,6 +1,5 @@ import re import socket -import platform import sshuttle.helpers as helpers import sshuttle.client as client import sshuttle.firewall as firewall @@ -14,20 +13,9 @@ from sshuttle.sudoers import sudoers def main(): opt = parser.parse_args() - if opt.sudoers or opt.sudoers_no_modify: - if platform.platform().startswith('OpenBSD'): - log('Automatic sudoers does not work on BSD') - return 1 - - if not opt.sudoers_filename: - log('--sudoers-file must be set or omitted.') - return 1 - - sudoers( - user_name=opt.sudoers_user, - no_modify=opt.sudoers_no_modify, - file_name=opt.sudoers_filename - ) + if opt.sudoers_no_modify: + # sudoers() calls exit() when it completes + sudoers(user_name=opt.sudoers_user) if opt.daemon: opt.syslog = 1 diff --git a/sshuttle/options.py b/sshuttle/options.py index 7cb05d1..0ac7690 100644 --- a/sshuttle/options.py +++ b/sshuttle/options.py @@ -396,18 +396,15 @@ parser.add_argument( (internal use only) """ ) -parser.add_argument( - "--sudoers", - action="store_true", - help=""" - Add sshuttle to the sudoers for this user - """ -) parser.add_argument( "--sudoers-no-modify", action="store_true", help=""" - Prints the sudoers config to STDOUT and DOES NOT modify anything. + Prints a sudo configuration to STDOUT which allows a user to + run sshuttle without a password. This option is INSECURE because, + with some cleverness, it also allows the user to run any command + as root without a password. The output also includes a suggested + method for you to install the configuration. """ ) parser.add_argument( @@ -415,16 +412,7 @@ parser.add_argument( default="", help=""" Set the user name or group with %%group_name for passwordless operation. - Default is the current user.set ALL for all users. Only works with - --sudoers or --sudoers-no-modify option. - """ -) -parser.add_argument( - "--sudoers-filename", - default="sshuttle_auto", - help=""" - Set the file name for the sudoers.d file to be added. Default is - "sshuttle_auto". Only works with --sudoers or --sudoers-no-modify option. + Default is the current user. Only works with the --sudoers-no-modify option. """ ) parser.add_argument( diff --git a/sshuttle/sudoers.py b/sshuttle/sudoers.py index ea67578..dece49a 100644 --- a/sshuttle/sudoers.py +++ b/sshuttle/sudoers.py @@ -2,70 +2,45 @@ import os import sys import getpass from uuid import uuid4 -from subprocess import Popen, PIPE -from sshuttle.helpers import log, debug1 -from distutils import spawn -path_to_sshuttle = sys.argv[0] -path_to_dist_packages = os.path.dirname(os.path.abspath(__file__))[:-9] -# randomize command alias to avoid collisions -command_alias = 'SSHUTTLE%(num)s' % {'num': uuid4().hex[-3:].upper()} +def build_config(user_name): + template = ''' +# WARNING: If you intend to restrict a user to only running the +# sshuttle command as root, THIS CONFIGURATION IS INSECURE. +# When a user can run sshuttle as root (with or without a password), +# they can also run other commands as root because sshuttle itself +# can run a command specified by the user with the --ssh-cmd option. + +# INSTRUCTIONS: Add this text to your sudo configuration to run +# sshuttle without needing to enter a sudo password. To use this +# configuration, run 'visudo /etc/sudoers.d/sshuttle_auto' as root and +# paste this text into the editor that it opens. If you want to give +# multiple users these privilages, you may wish to use use different +# filenames for each one (i.e., /etc/sudoers.d/sshuttle_auto_john). + +# This configuration was initially generated by the +# 'sshuttle --sudoers-no-modify' command. -# Template for the sudoers file -template = ''' Cmnd_Alias %(ca)s = /usr/bin/env PYTHONPATH=%(dist_packages)s %(py)s %(path)s * %(user_name)s ALL=NOPASSWD: %(ca)s ''' -warning_msg = "# WARNING: When you allow a user to run sshuttle as root,\n" \ - "# they can then use sshuttle's --ssh-cmd option to run any\n" \ - "# command as root.\n" - - -def build_config(user_name): - content = warning_msg - content += template % { - 'ca': command_alias, - 'dist_packages': path_to_dist_packages, + content = template % { + # randomize command alias to avoid collisions + 'ca': 'SSHUTTLE%(num)s' % {'num': uuid4().hex[-3:].upper()}, + 'dist_packages': os.path.dirname(os.path.abspath(__file__))[:-9], 'py': sys.executable, - 'path': path_to_sshuttle, + 'path': sys.argv[0], 'user_name': user_name, } return content -def save_config(content, file_name): - process = Popen([ - '/usr/bin/sudo', - spawn.find_executable('sudoers-add'), - file_name, - ], stdout=PIPE, stdin=PIPE) - - process.stdin.write(content.encode()) - - streamdata = process.communicate()[0] - sys.stdout.write(streamdata.decode("ASCII")) - returncode = process.returncode - - if returncode: - log('Failed updating sudoers file.') - debug1(streamdata) - exit(returncode) - else: - log('Success, sudoers file update.') - exit(0) - - -def sudoers(user_name=None, no_modify=None, file_name=None): +def sudoers(user_name=None): user_name = user_name or getpass.getuser() content = build_config(user_name) - - if no_modify: - sys.stdout.write(content) - exit(0) - else: - sys.stdout.write(warning_msg) - save_config(content, file_name) + sys.stdout.write(content) + exit(0)