diff --git a/bin/sudoers-add b/bin/sudoers-add new file mode 100755 index 0000000..5bec3d1 --- /dev/null +++ b/bin/sudoers-add @@ -0,0 +1,76 @@ +#!/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 + +# 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" > "/etc/sudoers.d/$FILE_NAME" + chmod 0440 "/etc/sudoers.d/$FILE_NAME" + echo "The sudoers file /etc/sudoers.d/$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 5a28f6a..2adddf6 100644 --- a/docs/installation.rst +++ b/docs/installation.rst @@ -5,8 +5,18 @@ Installation pip install sshuttle +- Debain package manager:: + sudo apt install sshuttle + - Clone:: git clone https://github.com/sshuttle/sshuttle.git cd sshuttle ./setup.py install + + +Optionally after installation +----------------------------- + +- Add to sudoers file + sshuttle --sudoers diff --git a/docs/manpage.rst b/docs/manpage.rst index 5d09c25..34516ed 100644 --- a/docs/manpage.rst +++ b/docs/manpage.rst @@ -234,6 +234,29 @@ 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. + +.. 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. + .. option:: --version Print program version. diff --git a/docs/usage.rst b/docs/usage.rst index d782a95..d1960c1 100644 --- a/docs/usage.rst +++ b/docs/usage.rst @@ -60,3 +60,46 @@ the data back and forth through ssh. Fun, right? A poor man's instant VPN, and you don't even have to have 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 + +DO NOT run this command with sudo, it will ask for your sudo password when +it is needed. + +A costume 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 helpfull is 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. + + sshuttle --sudoers-no-modify + +This will simply sprint the generated configuration to STDOUT. Example + + 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 diff --git a/run b/run index c52932a..c2c1a33 100755 --- a/run +++ b/run @@ -1,6 +1,7 @@ #!/usr/bin/env sh set -e export PYTHONPATH="$(dirname $0):$PYTHONPATH" +export PATH="$(dirname $0)/bin:$PATH" python_best_version() { if [ -x "$(command -v python3)" ] && diff --git a/setup.py b/setup.py index d5f3c7e..5d71237 100755 --- a/setup.py +++ b/setup.py @@ -52,6 +52,7 @@ setup( "Programming Language :: Python :: 3.5", "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 31a57bf..5f1ba10 100644 --- a/sshuttle/cmdline.py +++ b/sshuttle/cmdline.py @@ -1,5 +1,6 @@ import re import socket +import platform import sshuttle.helpers as helpers import sshuttle.client as client import sshuttle.firewall as firewall @@ -7,11 +8,27 @@ import sshuttle.hostwatch as hostwatch import sshuttle.ssyslog as ssyslog from sshuttle.options import parser, parse_ipport from sshuttle.helpers import family_ip_tuple, log, Fatal +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') + exit(1) + + if not opt.sudoers_filename: + log('--sudoers-file must be set or omited.') + exit(1) + + sudoers( + user_name=opt.sudoers_user, + no_modify=opt.sudoers_no_modify, + file_name=opt.sudoers_filename + ) + if opt.daemon: opt.syslog = 1 if opt.wrap: diff --git a/sshuttle/options.py b/sshuttle/options.py index 62f3510..79c404b 100644 --- a/sshuttle/options.py +++ b/sshuttle/options.py @@ -321,6 +321,37 @@ 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. + """ +) +parser.add_argument( + "--sudoers-user", + 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. + """ +) parser.add_argument( "--no-sudo-pythonpath", action="store_false", diff --git a/sshuttle/sudoers.py b/sshuttle/sudoers.py new file mode 100644 index 0000000..3f01e8e --- /dev/null +++ b/sshuttle/sudoers.py @@ -0,0 +1,64 @@ +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()} + +# 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 +''' + + +def build_config(user_name): + content = template % { + 'ca': command_alias, + 'dist_packages': path_to_dist_packages, + 'py': sys.executable, + 'path': path_to_sshuttle, + '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] + returncode = process.returncode + + if returncode: + log('Failed updating sudoers file.\n') + debug1(streamdata) + exit(returncode) + else: + log('Success, sudoers file update.\n') + exit(0) + + +def sudoers(user_name=None, no_modify=None, file_name=None): + user_name = user_name or getpass.getuser() + content = build_config(user_name) + + if no_modify: + sys.stdout.write(content) + exit(0) + else: + save_config(content, file_name)