From f2465e057136374729982a5e7a742fdd0481265a Mon Sep 17 00:00:00 2001 From: Michael DeHaan Date: Fri, 13 Apr 2012 19:06:11 -0400 Subject: [PATCH] Add support for specifying sudo passwords to both ansible & playbook. Nopasswd sudo is no longer required. --- bin/ansible | 7 +++++-- bin/ansible-playbook | 6 +++++- lib/ansible/connection.py | 17 +++++++++++++++++ lib/ansible/constants.py | 8 +++++--- lib/ansible/playbook.py | 10 ++++++---- lib/ansible/runner.py | 6 ++++-- lib/ansible/utils.py | 4 +++- 7 files changed, 45 insertions(+), 13 deletions(-) diff --git a/bin/ansible b/bin/ansible index 494d54d2edf..538e4d0f02a 100755 --- a/bin/ansible +++ b/bin/ansible @@ -72,7 +72,9 @@ class Cli(object): sshpass = None if options.ask_pass: sshpass = getpass.getpass(prompt="SSH password: ") - + if options.ask_sudo_pass: + sudopass = getpass.getpass(prompt="sudo password: ") + if options.tree: utils.prepare_writeable_dir(options.tree) @@ -86,7 +88,8 @@ class Cli(object): host_list=options.inventory, timeout=options.timeout, remote_port=options.remote_port, forks=options.forks, background=options.seconds, pattern=pattern, - callbacks=self.callbacks, sudo=options.sudo, verbose=True, + callbacks=self.callbacks, sudo=options.sudo, + sudo_pass=sudopass, verbose=True, transport=options.connection, debug=options.debug ) return (runner, runner.run()) diff --git a/bin/ansible-playbook b/bin/ansible-playbook index 6957f9a536c..c1e1cb55383 100755 --- a/bin/ansible-playbook +++ b/bin/ansible-playbook @@ -46,8 +46,11 @@ def main(args): return 1 sshpass = None + sudopass = None if options.ask_pass: sshpass = getpass.getpass(prompt="SSH password: ") + if options.ask_sudo_pass: + sudopass = getpass.getpass(prompt="sudo password: ") override_hosts = None if options.override_hosts: override_hosts = options.override_hosts.split(",") @@ -66,7 +69,8 @@ def main(args): forks=options.forks, debug=options.debug, verbose=True, remote_pass=sshpass, remote_port=options.remote_port, callbacks=playbook_cb, runner_callbacks=runner_cb, stats=stats, - timeout=options.timeout, transport=options.connection + timeout=options.timeout, transport=options.connection, + sudo_pass=sudopass ) try: diff --git a/lib/ansible/connection.py b/lib/ansible/connection.py index 609e53535f6..2abeac60bfb 100755 --- a/lib/ansible/connection.py +++ b/lib/ansible/connection.py @@ -112,15 +112,26 @@ class ParamikoConnection(object): sudo_chan = ssh_sudo.invoke_shell() sudo_chan.send("sudo -s\n") + # FIXME: using sudo with a password adds more delay, someone may wish + # to optimize to see when the channel is actually ready + if self.runner.sudo_pass: + time.sleep(0.1) # this is conservative + sudo_chan.send("%s\n" % self.runner.sudo_pass) + time.sleep(0.1) + # to avoid ssh expect logic, redirect output to file and move the # file when we are done with it... sudo_chan.send("(%s >%s_pre 2>/dev/null ; mv %s_pre %s) &\n" % (cmd, result_file, result_file, result_file)) + # FIXME: someone may wish to optimize to not background the launch, and tell when the command + # returns, removing the time.sleep(1) here time.sleep(1) sudo_chan.close() self.ssh = self._get_conn() # now load the results of the JSON execution... # FIXME: really need some timeout logic here + # though it doesn't make since to use the SSH timeout or impose any particular + # limit. Upgrades welcome. sftp = self.ssh.open_sftp() while True: # print "waiting on %s" % result_file @@ -181,6 +192,12 @@ class LocalConnection(object): ''' run a command on the local host ''' if self.runner.sudo and sudoable: cmd = "sudo -s %s" % cmd + if self.runner.sudo_pass: + # NOTE: if someone wants to add sudo w/ password to the local connection type, they are welcome + # to do so. The primary usage of the local connection is for crontab and kickstart usage however + # so this doesn't seem to be a huge priority + raise errors.AnsibleError("sudo with password is presently only supported on the paramiko (SSH) connection type") + p = subprocess.Popen(cmd, shell=True, stdin=None, stdout=subprocess.PIPE, stderr=subprocess.PIPE) stdout, stderr = p.communicate() diff --git a/lib/ansible/constants.py b/lib/ansible/constants.py index 751d48488dd..c7a2bf0c9e7 100644 --- a/lib/ansible/constants.py +++ b/lib/ansible/constants.py @@ -19,11 +19,11 @@ import os -# control side (aka 'overlord') DEFAULT_HOST_LIST = os.environ.get('ANSIBLE_HOSTS', - '/etc/ansible/hosts') + '/etc/ansible/hosts') DEFAULT_MODULE_PATH = os.environ.get('ANSIBLE_LIBRARY', - '/usr/share/ansible') + '/usr/share/ansible') + DEFAULT_MODULE_NAME = 'command' DEFAULT_PATTERN = '*' DEFAULT_FORKS = 5 @@ -32,6 +32,8 @@ DEFAULT_TIMEOUT = 10 DEFAULT_POLL_INTERVAL = 15 DEFAULT_REMOTE_USER = 'root' DEFAULT_REMOTE_PASS = None +DEFAULT_SUDO_PASS = None DEFAULT_REMOTE_PORT = 22 DEFAULT_TRANSPORT = 'paramiko' DEFAULT_TRANSPORT_OPTS = ['local', 'paramiko'] + diff --git a/lib/ansible/playbook.py b/lib/ansible/playbook.py index 8e64d193958..7757caafa23 100755 --- a/lib/ansible/playbook.py +++ b/lib/ansible/playbook.py @@ -54,6 +54,7 @@ class PlayBook(object): timeout = C.DEFAULT_TIMEOUT, remote_user = C.DEFAULT_REMOTE_USER, remote_pass = C.DEFAULT_REMOTE_PASS, + sudo_pass = C.DEFAULT_SUDO_PASS, remote_port = C.DEFAULT_REMOTE_PORT, transport = C.DEFAULT_TRANSPORT, override_hosts = None, @@ -82,9 +83,10 @@ class PlayBook(object): self.override_hosts = override_hosts self.extra_vars = extra_vars self.stats = stats + self.sudo_pass = sudo_pass - self.basedir = os.path.dirname(playbook) - self.playbook = self._parse_playbook(playbook) + self.basedir = os.path.dirname(playbook) + self.playbook = self._parse_playbook(playbook) self.host_list, self.groups = ansible.runner.Runner.parse_hosts( host_list, override_hosts=self.override_hosts, extra_vars=self.extra_vars) @@ -288,7 +290,7 @@ class PlayBook(object): setup_cache=SETUP_CACHE, basedir=self.basedir, conditional=only_if, callbacks=self.runner_callbacks, extra_vars=self.extra_vars, debug=self.debug, sudo=sudo, - transport=transport + transport=transport, sudo_pass=self.sudo_pass ) if async_seconds == 0: @@ -450,7 +452,7 @@ class PlayBook(object): remote_pass=self.remote_pass, remote_port=self.remote_port, setup_cache=SETUP_CACHE, callbacks=self.runner_callbacks, sudo=sudo, debug=self.debug, - transport=transport, + transport=transport, sudo_pass=self.sudo_pass ).run() self.stats.compute(setup_results, setup=True) diff --git a/lib/ansible/runner.py b/lib/ansible/runner.py index f40dfa32622..b4185503762 100755 --- a/lib/ansible/runner.py +++ b/lib/ansible/runner.py @@ -73,8 +73,9 @@ class Runner(object): module_name=C.DEFAULT_MODULE_NAME, module_args=C.DEFAULT_MODULE_ARGS, forks=C.DEFAULT_FORKS, timeout=C.DEFAULT_TIMEOUT, pattern=C.DEFAULT_PATTERN, remote_user=C.DEFAULT_REMOTE_USER, remote_pass=C.DEFAULT_REMOTE_PASS, - remote_port=C.DEFAULT_REMOTE_PORT, background=0, basedir=None, setup_cache=None, - transport=C.DEFAULT_TRANSPORT, conditional='True', groups={}, callbacks=None, verbose=False, + sudo_pass=C.DEFAULT_SUDO_PASS, remote_port=C.DEFAULT_REMOTE_PORT, background=0, + basedir=None, setup_cache=None, transport=C.DEFAULT_TRANSPORT, + conditional='True', groups={}, callbacks=None, verbose=False, debug=False, sudo=False, extra_vars=None, module_vars=None): if setup_cache is None: @@ -115,6 +116,7 @@ class Runner(object): self.background = background self.basedir = basedir self.sudo = sudo + self.sudo_pass = sudo_pass euid = pwd.getpwuid(os.geteuid())[0] if self.transport == 'local' and self.remote_user != euid: diff --git a/lib/ansible/utils.py b/lib/ansible/utils.py index 5e0ac2063a3..5bfaebd1925 100755 --- a/lib/ansible/utils.py +++ b/lib/ansible/utils.py @@ -290,8 +290,10 @@ def base_parser(constants=C, usage="", output_opts=False, runas_opts=False, asyn parser.add_option('-i', '--inventory-file', dest='inventory', help="specify inventory host file (default=%s)" % constants.DEFAULT_HOST_LIST, default=constants.DEFAULT_HOST_LIST) - parser.add_option('-k', '--ask-pass', default=False, action='store_true', + parser.add_option('-k', '--ask-pass', default=False, dest='ask_pass', action='store_true', help='ask for SSH password') + parser.add_option('-K', '--ask-sudo-pass', default=False, dest='ask_sudo_pass', action='store_true', + help='ask for sudo password') parser.add_option('-M', '--module-path', dest='module_path', help="specify path to module library (default=%s)" % constants.DEFAULT_MODULE_PATH, default=constants.DEFAULT_MODULE_PATH)