From 070c7c319ff6c2246c8df402a80370e656e99135 Mon Sep 17 00:00:00 2001 From: James Cammarata Date: Fri, 13 Mar 2015 11:57:27 -0500 Subject: [PATCH] Started implementing become in v2 --- v2/ansible/constants.py | 20 ++++- v2/ansible/executor/connection_info.py | 98 +++++++++++++++---------- v2/ansible/executor/play_iterator.py | 2 +- v2/ansible/executor/task_executor.py | 1 + v2/ansible/playbook/base.py | 5 ++ v2/ansible/playbook/become.py | 88 ++++++++++++++++++++++ v2/ansible/playbook/block.py | 4 +- v2/ansible/playbook/play.py | 20 ++--- v2/ansible/playbook/playbook_include.py | 2 +- v2/ansible/playbook/role/definition.py | 2 +- v2/ansible/playbook/task.py | 12 +-- v2/ansible/plugins/action/__init__.py | 25 +++---- v2/ansible/plugins/connections/local.py | 6 +- v2/ansible/plugins/connections/ssh.py | 28 +++---- v2/ansible/utils/cli.py | 9 +++ v2/samples/test_become.yml | 7 ++ 16 files changed, 236 insertions(+), 93 deletions(-) create mode 100644 v2/ansible/playbook/become.py create mode 100644 v2/samples/test_become.yml diff --git a/v2/ansible/constants.py b/v2/ansible/constants.py index 78eeaf8c20c..f2da07ffb02 100644 --- a/v2/ansible/constants.py +++ b/v2/ansible/constants.py @@ -24,7 +24,6 @@ import pwd import sys from . compat import configparser - from string import ascii_letters, digits # copied from utils, avoid circular reference fun :) @@ -143,6 +142,19 @@ DEFAULT_ASK_SU_PASS = get_config(p, DEFAULTS, 'ask_su_pass', 'ANSIBLE_ASK_ DEFAULT_GATHERING = get_config(p, DEFAULTS, 'gathering', 'ANSIBLE_GATHERING', 'implicit').lower() DEFAULT_LOG_PATH = shell_expand_path(get_config(p, DEFAULTS, 'log_path', 'ANSIBLE_LOG_PATH', '')) +#TODO: get rid of ternary chain mess +BECOME_METHODS = ['sudo','su','pbrun','pfexec','runas'] +BECOME_ERROR_STRINGS = {'sudo': 'Sorry, try again.', 'su': 'Authentication failure', 'pbrun': '', 'pfexec': '', 'runas': ''} +DEFAULT_BECOME = get_config(p, 'privilege_escalation', 'become', 'ANSIBLE_BECOME',True if DEFAULT_SUDO or DEFAULT_SU else False, boolean=True) +DEFAULT_BECOME_METHOD = get_config(p, 'privilege_escalation', 'become_method', 'ANSIBLE_BECOME_METHOD','sudo' if DEFAULT_SUDO else 'su' if DEFAULT_SU else 'sudo' ).lower() +DEFAULT_BECOME_USER = get_config(p, 'privilege_escalation', 'become_user', 'ANSIBLE_BECOME_USER',DEFAULT_SUDO_USER if DEFAULT_SUDO else DEFAULT_SU_USER if DEFAULT_SU else 'root') +DEFAULT_BECOME_ASK_PASS = get_config(p, 'privilege_escalation', 'become_ask_pass', 'ANSIBLE_BECOME_ASK_PASS',True if DEFAULT_ASK_SUDO_PASS else False, boolean=True) +# need to rethink impementing these 2 +DEFAULT_BECOME_EXE = None +#DEFAULT_BECOME_EXE = get_config(p, DEFAULTS, 'become_exe', 'ANSIBLE_BECOME_EXE','sudo' if DEFAULT_SUDO else 'su' if DEFAULT_SU else 'sudo') +#DEFAULT_BECOME_FLAGS = get_config(p, DEFAULTS, 'become_flags', 'ANSIBLE_BECOME_FLAGS',DEFAULT_SUDO_FLAGS if DEFAULT_SUDO else DEFAULT_SU_FLAGS if DEFAULT_SU else '-H') + + DEFAULT_ACTION_PLUGIN_PATH = get_config(p, DEFAULTS, 'action_plugins', 'ANSIBLE_ACTION_PLUGINS', '~/.ansible/plugins/action_plugins:/usr/share/ansible_plugins/action_plugins') DEFAULT_CACHE_PLUGIN_PATH = get_config(p, DEFAULTS, 'cache_plugins', 'ANSIBLE_CACHE_PLUGINS', '~/.ansible/plugins/cache_plugins:/usr/share/ansible_plugins/cache_plugins') DEFAULT_CALLBACK_PLUGIN_PATH = get_config(p, DEFAULTS, 'callback_plugins', 'ANSIBLE_CALLBACK_PLUGINS', '~/.ansible/plugins/callback_plugins:/usr/share/ansible_plugins/callback_plugins') @@ -168,12 +180,15 @@ DEFAULT_CALLABLE_WHITELIST = get_config(p, DEFAULTS, 'callable_whitelist', ' COMMAND_WARNINGS = get_config(p, DEFAULTS, 'command_warnings', 'ANSIBLE_COMMAND_WARNINGS', False, boolean=True) DEFAULT_LOAD_CALLBACK_PLUGINS = get_config(p, DEFAULTS, 'bin_ansible_callbacks', 'ANSIBLE_LOAD_CALLBACK_PLUGINS', False, boolean=True) +RETRY_FILES_ENABLED = get_config(p, DEFAULTS, 'retry_files_enabled', 'ANSIBLE_RETRY_FILES_ENABLED', True, boolean=True) +RETRY_FILES_SAVE_PATH = get_config(p, DEFAULTS, 'retry_files_save_path', 'ANSIBLE_RETRY_FILES_SAVE_PATH', '~/') + # CONNECTION RELATED ANSIBLE_SSH_ARGS = get_config(p, 'ssh_connection', 'ssh_args', 'ANSIBLE_SSH_ARGS', None) ANSIBLE_SSH_CONTROL_PATH = get_config(p, 'ssh_connection', 'control_path', 'ANSIBLE_SSH_CONTROL_PATH', "%(directory)s/ansible-ssh-%%h-%%p-%%r") ANSIBLE_SSH_PIPELINING = get_config(p, 'ssh_connection', 'pipelining', 'ANSIBLE_SSH_PIPELINING', False, boolean=True) PARAMIKO_RECORD_HOST_KEYS = get_config(p, 'paramiko_connection', 'record_host_keys', 'ANSIBLE_PARAMIKO_RECORD_HOST_KEYS', True, boolean=True) -# obsolete -- will be formally removed in 1.6 +# obsolete -- will be formally removed ZEROMQ_PORT = get_config(p, 'fireball_connection', 'zeromq_port', 'ANSIBLE_ZEROMQ_PORT', 5099, integer=True) ACCELERATE_PORT = get_config(p, 'accelerate', 'accelerate_port', 'ACCELERATE_PORT', 5099, integer=True) ACCELERATE_TIMEOUT = get_config(p, 'accelerate', 'accelerate_timeout', 'ACCELERATE_TIMEOUT', 30, integer=True) @@ -189,6 +204,7 @@ PARAMIKO_PTY = get_config(p, 'paramiko_connection', 'pty', 'AN DEFAULT_PASSWORD_CHARS = ascii_letters + digits + ".,:-_" # non-configurable things +DEFAULT_BECOME_PASS = None DEFAULT_SUDO_PASS = None DEFAULT_REMOTE_PASS = None DEFAULT_SUBSET = None diff --git a/v2/ansible/executor/connection_info.py b/v2/ansible/executor/connection_info.py index 7522ac210c2..f2eaec630d4 100644 --- a/v2/ansible/executor/connection_info.py +++ b/v2/ansible/executor/connection_info.py @@ -48,16 +48,16 @@ class ConnectionInformation: self.password = '' self.port = 22 self.private_key_file = None - self.su = False - self.su_user = '' - self.su_pass = '' - self.sudo = False - self.sudo_user = '' - self.sudo_pass = '' self.verbosity = 0 self.only_tags = set() self.skip_tags = set() + # privilege escalation + self.become = False + self.become_method = C.DEFAULT_BECOME_METHOD + self.become_user = '' + self.become_pass = '' + self.no_log = False self.check_mode = False @@ -84,15 +84,13 @@ class ConnectionInformation: if play.connection: self.connection = play.connection - self.remote_user = play.remote_user - self.password = '' - self.port = int(play.port) if play.port else 22 - self.su = play.su - self.su_user = play.su_user - self.su_pass = play.su_pass - self.sudo = play.sudo - self.sudo_user = play.sudo_user - self.sudo_pass = play.sudo_pass + self.remote_user = play.remote_user + self.password = '' + self.port = int(play.port) if play.port else 22 + self.become = play.become + self.become_method = play.become_method + self.become_user = play.become_user + self.become_pass = play.become_pass # non connection related self.no_log = play.no_log @@ -158,7 +156,7 @@ class ConnectionInformation: new_info = ConnectionInformation() new_info.copy(self) - for attr in ('connection', 'remote_user', 'su', 'su_user', 'su_pass', 'sudo', 'sudo_user', 'sudo_pass', 'environment', 'no_log'): + for attr in ('connection', 'remote_user', 'become', 'become_user', 'become_pass', 'become_method', 'environment', 'no_log'): if hasattr(task, attr): attr_val = getattr(task, attr) if attr_val: @@ -166,31 +164,58 @@ class ConnectionInformation: return new_info - def make_sudo_cmd(self, sudo_exe, executable, cmd): - """ - Helper function for wrapping commands with sudo. + def make_become_cmd(self, cmd, shell, become_settings=None): - Rather than detect if sudo wants a password this time, -k makes - sudo always ask for a password if one is required. Passing a quoted - compound command to sudo (or sudo -s) directly doesn't work, so we - shellquote it with pipes.quote() and pass the quoted string to the - user's shell. We loop reading output until we see the randomly- - generated sudo prompt set with the -p option. + """ + helper function to create privilege escalation commands """ - randbits = ''.join(chr(random.randint(ord('a'), ord('z'))) for x in xrange(32)) - prompt = '[sudo via ansible, key=%s] password: ' % randbits - success_key = 'SUDO-SUCCESS-%s' % randbits + # FIXME: become settings should probably be stored in the connection info itself + if become_settings is None: + become_settings = {} - sudocmd = '%s -k && %s %s -S -p "%s" -u %s %s -c %s' % ( - sudo_exe, sudo_exe, C.DEFAULT_SUDO_FLAGS, prompt, - self.sudo_user, executable or '$SHELL', - pipes.quote('echo %s; %s' % (success_key, cmd)) - ) + randbits = ''.join(chr(random.randint(ord('a'), ord('z'))) for x in xrange(32)) + success_key = 'BECOME-SUCCESS-%s' % randbits + prompt = None + becomecmd = None - # FIXME: old code, can probably be removed as it's been commented out for a while - #return ('/bin/sh -c ' + pipes.quote(sudocmd), prompt, success_key) - return (sudocmd, prompt, success_key) + shell = shell or '$SHELL' + + if self.become_method == 'sudo': + # Rather than detect if sudo wants a password this time, -k makes sudo always ask for + # a password if one is required. Passing a quoted compound command to sudo (or sudo -s) + # directly doesn't work, so we shellquote it with pipes.quote() and pass the quoted + # string to the user's shell. We loop reading output until we see the randomly-generated + # sudo prompt set with the -p option. + prompt = '[sudo via ansible, key=%s] password: ' % randbits + exe = become_settings.get('sudo_exe', C.DEFAULT_SUDO_EXE) + flags = become_settings.get('sudo_flags', C.DEFAULT_SUDO_FLAGS) + becomecmd = '%s -k && %s %s -S -p "%s" -u %s %s -c "%s"' % \ + (exe, exe, flags or C.DEFAULT_SUDO_FLAGS, prompt, self.become_user, shell, 'echo %s; %s' % (success_key, cmd)) + + elif self.become_method == 'su': + exe = become_settings.get('su_exe', C.DEFAULT_SU_EXE) + flags = become_settings.get('su_flags', C.DEFAULT_SU_FLAGS) + becomecmd = '%s %s %s -c "%s -c %s"' % (exe, flags, self.become_user, shell, pipes.quote('echo %s; %s' % (success_key, cmd))) + + elif self.become_method == 'pbrun': + exe = become_settings.get('pbrun_exe', 'pbrun') + flags = become_settings.get('pbrun_flags', '') + becomecmd = '%s -b -l %s -u %s "%s"' % (exe, flags, self.become_user, 'echo %s; %s' % (success_key,cmd)) + + elif self.become_method == 'pfexec': + exe = become_settings.get('pfexec_exe', 'pbrun') + flags = become_settings.get('pfexec_flags', '') + # No user as it uses it's own exec_attr to figure it out + becomecmd = '%s %s "%s"' % (exe, flags, 'echo %s; %s' % (success_key,cmd)) + elif self.become: + raise errors.AnsibleError("Privilege escalation method not found: %s" % method) + + return (('%s -c ' % shell) + pipes.quote(becomecmd), prompt, success_key) + + def check_become_success(self, output, become_settings): + #TODO: implement + pass def _get_fields(self): return [i for i in self.__dict__.keys() if i[:1] != '_'] @@ -204,4 +229,3 @@ class ConnectionInformation: for field in self._get_fields(): value = templar.template(getattr(self, field)) setattr(self, field, value) - diff --git a/v2/ansible/executor/play_iterator.py b/v2/ansible/executor/play_iterator.py index 0461fc87f2c..4a149243d91 100644 --- a/v2/ansible/executor/play_iterator.py +++ b/v2/ansible/executor/play_iterator.py @@ -197,7 +197,7 @@ class PlayIterator: self._host_states[host.name] = s def get_failed_hosts(self): - return dict((host, True) for (host, state) in self._host_states.iteritems() if state.run_state == self.ITERATING_COMPLETE and state.failed_state != self.FAILED_NONE) + return dict((host, True) for (host, state) in self._host_states.iteritems() if state.run_state == self.ITERATING_COMPLETE and state.fail_state != self.FAILED_NONE) def get_original_task(self, host, task): ''' diff --git a/v2/ansible/executor/task_executor.py b/v2/ansible/executor/task_executor.py index 012fb991949..bad47279a5e 100644 --- a/v2/ansible/executor/task_executor.py +++ b/v2/ansible/executor/task_executor.py @@ -33,6 +33,7 @@ __all__ = ['TaskExecutor'] import json import time +import pipes class TaskExecutor: diff --git a/v2/ansible/playbook/base.py b/v2/ansible/playbook/base.py index 691de0c9f0f..949e6a09fdc 100644 --- a/v2/ansible/playbook/base.py +++ b/v2/ansible/playbook/base.py @@ -72,6 +72,11 @@ class Base: def munge(self, ds): ''' infrequently used method to do some pre-processing of legacy terms ''' + for base_class in self.__class__.__bases__: + method = getattr(self, ("_munge_%s" % base_class.__name__).lower(), None) + if method: + ds = method(ds) + return ds def load_data(self, ds, variable_manager=None, loader=None): diff --git a/v2/ansible/playbook/become.py b/v2/ansible/playbook/become.py new file mode 100644 index 00000000000..6ac1d2bad98 --- /dev/null +++ b/v2/ansible/playbook/become.py @@ -0,0 +1,88 @@ +# (c) 2012-2014, Michael DeHaan +# +# This file is part of Ansible +# +# Ansible is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# Ansible is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with Ansible. If not, see . + +# Make coding more python3-ish +from __future__ import (absolute_import, division, print_function) +__metaclass__ = type + +from ansible.errors import AnsibleError, AnsibleParserError +from ansible.playbook.attribute import Attribute, FieldAttribute +#from ansible.utils.display import deprecated + +class Become: + + # Privlege escalation + _become = FieldAttribute(isa='bool', default=False) + _become_method = FieldAttribute(isa='string') + _become_user = FieldAttribute(isa='string') + _become_pass = FieldAttribute(isa='string') + + def __init__(self): + return super(Become, self).__init__() + + def _detect_privilege_escalation_conflict(self, ds): + + # Fail out if user specifies conflicting privelege escalations + has_become = 'become' in ds or 'become_user'in ds + has_sudo = 'sudo' in ds or 'sudo_user' in ds + has_su = 'su' in ds or 'su_user' in ds + + if has_become: + msg = 'The become params ("become", "become_user") and' + if has_sudo: + raise errors.AnsibleParserError('%s sudo params ("sudo", "sudo_user") cannot be used together' % msg) + elif has_su: + raise errors.AnsibleParserError('%s su params ("su", "su_user") cannot be used together' % msg) + elif has_sudo and has_su: + raise errors.AnsibleParserError('sudo params ("sudo", "sudo_user") and su params ("su", "su_user") cannot be used together') + + def _munge_become(self, ds): + + self._detect_privilege_escalation_conflict(ds) + + # Setting user implies setting become/sudo/su to true + if 'become_user' in ds and not ds.get('become', False): + ds['become'] = True + + # Privilege escalation, backwards compatibility for sudo/su + if 'sudo' in ds or 'sudo_user' in ds: + ds['become_method'] = 'sudo' + if 'sudo' in ds: + ds['become'] = ds['sudo'] + del ds['sudo'] + else: + ds['become'] = True + if 'sudo_user' in ds: + ds['become_user'] = ds['sudo_user'] + del ds['sudo_user'] + + #deprecated("Instead of sudo/sudo_user, use become/become_user and set become_method to 'sudo' (default)") + + elif 'su' in ds or 'su_user' in ds: + ds['become_method'] = 'su' + if 'su' in ds: + ds['become'] = ds['su'] + del ds['su'] + else: + ds['become'] = True + if 'su_user' in ds: + ds['become_user'] = ds['su_user'] + del ds['su_user'] + + #deprecated("Instead of su/su_user, use become/become_user and set become_method to 'su' (default is sudo)") + + return ds diff --git a/v2/ansible/playbook/block.py b/v2/ansible/playbook/block.py index 533b552f22e..49f65a15349 100644 --- a/v2/ansible/playbook/block.py +++ b/v2/ansible/playbook/block.py @@ -21,6 +21,7 @@ __metaclass__ = type from ansible.playbook.attribute import Attribute, FieldAttribute from ansible.playbook.base import Base +#from ansible.playbook.become import Become from ansible.playbook.conditional import Conditional from ansible.playbook.helpers import load_list_of_tasks from ansible.playbook.role import Role @@ -80,7 +81,8 @@ class Block(Base, Conditional, Taggable): return dict(block=ds) else: return dict(block=[ds]) - return ds + + return super(Block, self).munge(ds) def _load_block(self, attr, ds): return load_list_of_tasks( diff --git a/v2/ansible/playbook/play.py b/v2/ansible/playbook/play.py index 29c9c04cc8e..e9847fccd90 100644 --- a/v2/ansible/playbook/play.py +++ b/v2/ansible/playbook/play.py @@ -23,6 +23,7 @@ from ansible.errors import AnsibleError, AnsibleParserError from ansible.playbook.attribute import Attribute, FieldAttribute from ansible.playbook.base import Base +from ansible.playbook.become import Become from ansible.playbook.helpers import load_list_of_blocks, load_list_of_roles, compile_block_list from ansible.playbook.role import Role from ansible.playbook.taggable import Taggable @@ -33,7 +34,7 @@ from ansible.utils.vars import combine_vars __all__ = ['Play'] -class Play(Base, Taggable): +class Play(Base, Taggable, Become): """ A play is a language feature that represents a list of roles and/or @@ -47,21 +48,19 @@ class Play(Base, Taggable): # ================================================================================= # Connection-Related Attributes + + # TODO: generalize connection _accelerate = FieldAttribute(isa='bool', default=False) _accelerate_ipv6 = FieldAttribute(isa='bool', default=False) - _accelerate_port = FieldAttribute(isa='int', default=5099) + _accelerate_port = FieldAttribute(isa='int', default=5099) # should be alias of port + + # Connection _connection = FieldAttribute(isa='string', default='smart') _gather_facts = FieldAttribute(isa='string', default='smart') _hosts = FieldAttribute(isa='list', default=[], required=True) _name = FieldAttribute(isa='string', default='') _port = FieldAttribute(isa='int', default=22) _remote_user = FieldAttribute(isa='string', default='root') - _su = FieldAttribute(isa='bool', default=False) - _su_user = FieldAttribute(isa='string', default='root') - _su_pass = FieldAttribute(isa='string') - _sudo = FieldAttribute(isa='bool', default=False) - _sudo_user = FieldAttribute(isa='string', default='root') - _sudo_pass = FieldAttribute(isa='string') # Variable Attributes _vars = FieldAttribute(isa='dict', default=dict()) @@ -101,6 +100,7 @@ class Play(Base, Taggable): @staticmethod def load(data, variable_manager=None, loader=None): p = Play() + print("in play load, become is: %s" % getattr(p, 'become')) return p.load_data(data, variable_manager=variable_manager, loader=loader) def munge(self, ds): @@ -122,7 +122,7 @@ class Play(Base, Taggable): ds['remote_user'] = ds['user'] del ds['user'] - return ds + return super(Play, self).munge(ds) def _load_vars(self, attr, ds): ''' @@ -187,7 +187,7 @@ class Play(Base, Taggable): roles.append(Role.load(ri)) return roles - # FIXME: post_validation needs to ensure that su/sudo are not both set + # FIXME: post_validation needs to ensure that become/su/sudo have only 1 set def _compile_roles(self): ''' diff --git a/v2/ansible/playbook/playbook_include.py b/v2/ansible/playbook/playbook_include.py index 159c3d25da8..e1d7f6be34f 100644 --- a/v2/ansible/playbook/playbook_include.py +++ b/v2/ansible/playbook/playbook_include.py @@ -98,7 +98,7 @@ class PlaybookInclude(Base): raise AnsibleParserError("vars for include statements must be specified as a dictionary", obj=ds) new_ds[k] = v - return new_ds + return super(PlaybookInclude, self).munge(new_ds) def _munge_include(self, ds, new_ds, k, v): ''' diff --git a/v2/ansible/playbook/role/definition.py b/v2/ansible/playbook/role/definition.py index c9ec4259c17..d52c6795fb9 100644 --- a/v2/ansible/playbook/role/definition.py +++ b/v2/ansible/playbook/role/definition.py @@ -88,7 +88,7 @@ class RoleDefinition(Base, Conditional, Taggable): self._ds = ds # and return the cleaned-up data structure - return new_ds + return super(RoleDefinition, self).munge(new_ds) def _load_role_name(self, ds): ''' diff --git a/v2/ansible/playbook/task.py b/v2/ansible/playbook/task.py index e6fcc13d259..79ec2df3401 100644 --- a/v2/ansible/playbook/task.py +++ b/v2/ansible/playbook/task.py @@ -29,6 +29,7 @@ from ansible.plugins import module_loader, lookup_loader from ansible.playbook.attribute import Attribute, FieldAttribute from ansible.playbook.base import Base +from ansible.playbook.become import Become from ansible.playbook.block import Block from ansible.playbook.conditional import Conditional from ansible.playbook.role import Role @@ -36,7 +37,7 @@ from ansible.playbook.taggable import Taggable __all__ = ['Task'] -class Task(Base, Conditional, Taggable): +class Task(Base, Conditional, Taggable, Become): """ A task is a language feature that represents a call to a module, with given arguments and other parameters. @@ -86,12 +87,6 @@ class Task(Base, Conditional, Taggable): _remote_user = FieldAttribute(isa='string') _retries = FieldAttribute(isa='int', default=1) _run_once = FieldAttribute(isa='bool') - _su = FieldAttribute(isa='bool') - _su_pass = FieldAttribute(isa='string') - _su_user = FieldAttribute(isa='string') - _sudo = FieldAttribute(isa='bool') - _sudo_user = FieldAttribute(isa='string') - _sudo_pass = FieldAttribute(isa='string') _transport = FieldAttribute(isa='string') _until = FieldAttribute(isa='list') # ? _vars = FieldAttribute(isa='dict', default=dict()) @@ -172,6 +167,7 @@ class Task(Base, Conditional, Taggable): args_parser = ModuleArgsParser(task_ds=ds) (action, args, delegate_to) = args_parser.parse() + new_ds['action'] = action new_ds['args'] = args new_ds['delegate_to'] = delegate_to @@ -186,7 +182,7 @@ class Task(Base, Conditional, Taggable): else: new_ds[k] = v - return new_ds + return super(Task, self).munge(new_ds) def post_validate(self, all_vars=dict(), fail_on_undefined=True): ''' diff --git a/v2/ansible/plugins/action/__init__.py b/v2/ansible/plugins/action/__init__.py index 1dc9d59aa08..46f25ec503c 100644 --- a/v2/ansible/plugins/action/__init__.py +++ b/v2/ansible/plugins/action/__init__.py @@ -130,7 +130,7 @@ class ActionBase: if tmp and "tmp" in tmp: # tmp has already been created return False - if not self._connection._has_pipelining or not C.ANSIBLE_SSH_PIPELINING or C.DEFAULT_KEEP_REMOTE_FILES or self._connection_info.su: + if not self._connection._has_pipelining or not C.ANSIBLE_SSH_PIPELINING or C.DEFAULT_KEEP_REMOTE_FILES or self._connection_info.become: # tmp is necessary to store module source code return True if not self._connection._has_pipelining: @@ -152,12 +152,11 @@ class ActionBase: basefile = 'ansible-tmp-%s-%s' % (time.time(), random.randint(0, 2**48)) use_system_tmp = False - if (self._connection_info.sudo and self._connection_info.sudo_user != 'root') or (self._connection_info.su and self._connection_info.su_user != 'root'): + if self._connection_info.become and self._connection_info.become_user != 'root': use_system_tmp = True tmp_mode = None - if self._connection_info.remote_user != 'root' or \ - ((self._connection_info.sudo and self._connection_info.sudo_user != 'root') or (self._connection_info.su and self._connection_info.su_user != 'root')): + if self._connection_info.remote_user != 'root' or self._connection_info.become and self._connection_info.become_user != 'root': tmp_mode = 'a+rx' cmd = self._shell.mkdtemp(basefile, use_system_tmp, tmp_mode) @@ -291,10 +290,8 @@ class ActionBase: split_path = path.split(os.path.sep, 1) expand_path = split_path[0] if expand_path == '~': - if self._connection_info.sudo and self._connection_info.sudo_user: - expand_path = '~%s' % self._connection_info.sudo_user - elif self._connection_info.su and self._connection_info.su_user: - expand_path = '~%s' % self._connection_info.su_user + if self._connection_info.become and self._connection_info.become_user: + expand_path = '~%s' % self._connection_info.become_user cmd = self._shell.expand_user(expand_path) debug("calling _low_level_execute_command to expand the remote user path") @@ -373,7 +370,7 @@ class ActionBase: environment_string = self._compute_environment_string() - if tmp and "tmp" in tmp and ((self._connection_info.sudo and self._connection_info.sudo_user != 'root') or (self._connection_info.su and self._connection_info.su_user != 'root')): + if tmp and "tmp" in tmp and self._connection_info.become and self._connection_info.become_user != 'root': # deal with possible umask issues once sudo'ed to other user self._remote_chmod(tmp, 'a+r', remote_module_path) @@ -391,7 +388,7 @@ class ActionBase: rm_tmp = None if tmp and "tmp" in tmp and not C.DEFAULT_KEEP_REMOTE_FILES and not persist_files and delete_remote_tmp: - if not self._connection_info.sudo or self._connection_info.su or self._connection_info.sudo_user == 'root' or self._connection_info.su_user == 'root': + if not self._connection_info.become or self._connection_info.become_user == 'root': # not sudoing or sudoing to root, so can cleanup files in the same step rm_tmp = tmp @@ -409,7 +406,7 @@ class ActionBase: debug("_low_level_execute_command returned ok") if tmp and "tmp" in tmp and not C.DEFAULT_KEEP_REMOTE_FILES and not persist_files and delete_remote_tmp: - if (self._connection_info.sudo and self._connection_info.sudo_user != 'root') or (self._connection_info.su and self._connection_info.su_user != 'root'): + if self._connection_info.become and self._connection_info.become_user != 'root': # not sudoing to root, so maybe can't delete files as that other user # have to clean up temp files as original user in a second step cmd2 = self._shell.remove(tmp, recurse=True) @@ -457,11 +454,7 @@ class ActionBase: success_key = None if sudoable: - if self._connection_info.su and self._connection_info.su_user: - cmd, prompt, success_key = self._connection_info.make_su_cmd(executable, cmd) - elif self._connection_info.sudo and self._connection_info.sudo_user: - # FIXME: hard-coded sudo_exe here - cmd, prompt, success_key = self._connection_info.make_sudo_cmd('/usr/bin/sudo', executable, cmd) + cmd, prompt, success_key = self._connection_info.make_become_cmd(executable, cmd) debug("executing the command %s through the connection" % cmd) rc, stdin, stdout, stderr = self._connection.exec_command(cmd, tmp, executable=executable, in_data=in_data) diff --git a/v2/ansible/plugins/connections/local.py b/v2/ansible/plugins/connections/local.py index 963c8c2d4ec..d75ee70159e 100644 --- a/v2/ansible/plugins/connections/local.py +++ b/v2/ansible/plugins/connections/local.py @@ -44,8 +44,8 @@ class Connection(ConnectionBase): debug("in local.exec_command()") # su requires to be run from a terminal, and therefore isn't supported here (yet?) - if self._connection_info.su: - raise AnsibleError("Internal Error: this module does not support running commands via su") + #if self._connection_info.su: + # raise AnsibleError("Internal Error: this module does not support running commands via su") if in_data: raise AnsibleError("Internal Error: this module does not support optimized module pipelining") @@ -57,7 +57,7 @@ class Connection(ConnectionBase): # else: # local_cmd = cmd #else: - # local_cmd, prompt, success_key = utils.make_sudo_cmd(self.runner.sudo_exe, sudo_user, executable, cmd) + # local_cmd, prompt, success_key = utils.make_become_cmd(self.runner.sudo_exe, sudo_user, executable, cmd) if executable: local_cmd = executable.split() + ['-c', cmd] else: diff --git a/v2/ansible/plugins/connections/ssh.py b/v2/ansible/plugins/connections/ssh.py index 6c0ab9917c1..e5b397f5659 100644 --- a/v2/ansible/plugins/connections/ssh.py +++ b/v2/ansible/plugins/connections/ssh.py @@ -281,19 +281,19 @@ class Connection(ConnectionBase): # ssh_cmd += ['-6'] ssh_cmd += [self._connection_info.remote_addr] - if not (self._connection_info.sudo or self._connection_info.su): - prompt = None - if executable: - ssh_cmd.append(executable + ' -c ' + pipes.quote(cmd)) - else: - ssh_cmd.append(cmd) - elif self._connection_info.su and self._connection_info.su_user: - su_cmd, prompt, success_key = self._connection_info.make_su_cmd(executable, cmd) - ssh_cmd.append(su_cmd) - else: - # FIXME: hard-coded sudo_exe here - sudo_cmd, prompt, success_key = self._connection_info.make_sudo_cmd('/usr/bin/sudo', executable, cmd) - ssh_cmd.append(sudo_cmd) + #if not (self._connection_info.sudo or self._connection_info.su): + # prompt = None + # if executable: + # ssh_cmd.append(executable + ' -c ' + pipes.quote(cmd)) + # else: + # ssh_cmd.append(cmd) + #elif self._connection_info.su and self._connection_info.su_user: + # su_cmd, prompt, success_key = self._connection_info.make_su_cmd(executable, cmd) + # ssh_cmd.append(su_cmd) + #else: + # # FIXME: hard-coded sudo_exe here + # sudo_cmd, prompt, success_key = self._connection_info.make_become_cmd('/usr/bin/sudo', executable, cmd) + # ssh_cmd.append(sudo_cmd) self._display.vvv("EXEC %s" % ' '.join(ssh_cmd), host=self._connection_info.remote_addr) @@ -369,6 +369,8 @@ class Connection(ConnectionBase): # no_prompt_err += sudo_errput #(returncode, stdout, stderr) = self._communicate(p, stdin, in_data, su=su, sudoable=sudoable, prompt=prompt) + # FIXME: the prompt won't be here anymore + prompt="" (returncode, stdout, stderr) = self._communicate(p, stdin, in_data, prompt=prompt) #if C.HOST_KEY_CHECKING and not_in_host_file: diff --git a/v2/ansible/utils/cli.py b/v2/ansible/utils/cli.py index 43aa21470d3..f846d6f73ca 100644 --- a/v2/ansible/utils/cli.py +++ b/v2/ansible/utils/cli.py @@ -59,6 +59,8 @@ def base_parser(usage="", output_opts=False, runas_opts=False, help='ask for sudo password') parser.add_option('--ask-su-pass', default=False, dest='ask_su_pass', action='store_true', help='ask for su password') + parser.add_option('--ask-become-pass', default=False, dest='ask_become_pass', action='store_true', + help='ask for privlege escalation password') parser.add_option('--ask-vault-pass', default=False, dest='ask_vault_pass', action='store_true', help='ask for vault password') parser.add_option('--vault-password-file', default=C.DEFAULT_VAULT_PASSWORD_FILE, @@ -84,6 +86,10 @@ def base_parser(usage="", output_opts=False, runas_opts=False, help='log output to this directory') if runas_opts: + parser.add_option("-b", "--become", default=C.DEFAULT_BECOME, action="store_true", + dest='become', help="run operations with become (nopasswd implied)") + parser.add_option('-B', '--become-user', help='run operations with as this ' + 'user (default=%s)' % C.DEFAULT_BECOME_USER) parser.add_option("-s", "--sudo", default=C.DEFAULT_SUDO, action="store_true", dest='sudo', help="run operations with sudo (nopasswd)") parser.add_option('-U', '--sudo-user', dest='sudo_user', default=None, @@ -100,6 +106,9 @@ def base_parser(usage="", output_opts=False, runas_opts=False, parser.add_option('-c', '--connection', dest='connection', default=C.DEFAULT_TRANSPORT, help="connection type to use (default=%s)" % C.DEFAULT_TRANSPORT) + parser.add_option('--become-method', dest='become_method', + default=C.DEFAULT_BECOME_METHOD, + help="privlege escalation method to use (default=%s)" % C.DEFAULT_BECOME_METHOD) if async_opts: parser.add_option('-P', '--poll', default=C.DEFAULT_POLL_INTERVAL, type='int', diff --git a/v2/samples/test_become.yml b/v2/samples/test_become.yml new file mode 100644 index 00000000000..7e229af5de2 --- /dev/null +++ b/v2/samples/test_become.yml @@ -0,0 +1,7 @@ +- hosts: all + gather_facts: no + tasks: + - command: whoami + become: yes + become_user: jamesc + become_method: su