Commit bce4bb2c by Brian Coca Committed by James Cammarata

preliminary privlege escalation unification + pbrun

- become constants inherit existing sudo/su ones
- become command line options, marked sudo/su as deprecated and moved sudo/su passwords to runas group
- changed method signatures as privlege escalation is collapsed to become
- added tests for su and become, diabled su for lack of support in local.py
- updated playbook,play and task objects to become
- added become to runner
- added whoami test for become/sudo/su
- added home override dir for plugins
- removed useless method from ask pass
- forced become pass to always be string also uses to_bytes
- fixed fakerunner for tests
- corrected reference in synchronize action plugin
- added pfexec (needs testing)
- removed unused sudo/su in runner init
- removed deprecated info
- updated pe tests to allow to run under sudo and not need root
- normalized become options into a funciton to avoid duplication and inconsistencies
- pushed suppored list to connection classs property
- updated all connection plugins to latest 'become' pe

- includes fixes from feedback (including typos)
- added draft docs
- stub of become_exe, leaving for future v2 fixes
parent f4329c89
......@@ -58,12 +58,12 @@ class Cli(object):
''' create an options parser for bin/ansible '''
parser = utils.base_parser(
constants=C,
runas_opts=True,
subset_opts=True,
constants=C,
runas_opts=True,
subset_opts=True,
async_opts=True,
output_opts=True,
connect_opts=True,
output_opts=True,
connect_opts=True,
check_opts=True,
diff_opts=False,
usage='%prog <host-pattern> [options]'
......@@ -82,12 +82,8 @@ class Cli(object):
parser.print_help()
sys.exit(1)
# su and sudo command line arguments need to be mutually exclusive
if (options.su or options.su_user or options.ask_su_pass) and \
(options.sudo or options.sudo_user or options.ask_sudo_pass):
parser.error("Sudo arguments ('--sudo', '--sudo-user', and '--ask-sudo-pass') "
"and su arguments ('-su', '--su-user', and '--ask-su-pass') are "
"mutually exclusive")
# privlege escalation command line arguments need to be mutually exclusive
utils.check_mutually_exclusive_privilege(options, parser)
if (options.ask_vault_pass and options.vault_password_file):
parser.error("--ask-vault-pass and --vault-password-file are mutually exclusive")
......@@ -101,20 +97,20 @@ class Cli(object):
pattern = args[0]
sshpass = None
sudopass = None
su_pass = None
vault_pass = None
sshpass = becomepass = vault_pass = become_method = None
options.ask_pass = options.ask_pass or C.DEFAULT_ASK_PASS
# Never ask for an SSH password when we run with local connection
if options.connection == "local":
options.ask_pass = False
options.ask_sudo_pass = options.ask_sudo_pass or C.DEFAULT_ASK_SUDO_PASS
options.ask_su_pass = options.ask_su_pass or C.DEFAULT_ASK_SU_PASS
else:
options.ask_pass = options.ask_pass or C.DEFAULT_ASK_PASS
options.ask_vault_pass = options.ask_vault_pass or C.DEFAULT_ASK_VAULT_PASS
(sshpass, sudopass, su_pass, vault_pass) = utils.ask_passwords(ask_pass=options.ask_pass, ask_sudo_pass=options.ask_sudo_pass, ask_su_pass=options.ask_su_pass, ask_vault_pass=options.ask_vault_pass)
# become
utils.normalize_become_options(options)
prompt_method = utils.choose_pass_prompt(options)
(sshpass, becomepass, vault_pass) = utils.ask_passwords(ask_pass=options.ask_pass, become_ask_pass=options.become_ask_pass, ask_vault_pass=options.ask_vault_pass, become_method=prompt_method)
# read vault_pass from a file
if not options.ask_vault_pass and options.vault_password_file:
......@@ -126,6 +122,7 @@ class Cli(object):
if options.subset:
inventory_manager.subset(options.subset)
hosts = inventory_manager.list_hosts(pattern)
if len(hosts) == 0:
callbacks.display("No hosts matched", stderr=True)
sys.exit(0)
......@@ -135,16 +132,10 @@ class Cli(object):
callbacks.display(' %s' % host)
sys.exit(0)
if ((options.module_name == 'command' or options.module_name == 'shell')
and not options.module_args):
if options.module_name in ['command','shell'] and not options.module_args:
callbacks.display("No argument passed to %s module" % options.module_name, color='red', stderr=True)
sys.exit(1)
if options.su_user or options.ask_su_pass:
options.su = True
options.sudo_user = options.sudo_user or C.DEFAULT_SUDO_USER
options.su_user = options.su_user or C.DEFAULT_SU_USER
if options.tree:
utils.prepare_writeable_dir(options.tree)
......@@ -160,17 +151,15 @@ class Cli(object):
forks=options.forks,
pattern=pattern,
callbacks=self.callbacks,
sudo=options.sudo,
sudo_pass=sudopass,
sudo_user=options.sudo_user,
transport=options.connection,
subset=options.subset,
check=options.check,
diff=options.check,
su=options.su,
su_pass=su_pass,
su_user=options.su_user,
vault_pass=vault_pass,
become=options.become,
become_method=options.become_method,
become_pass=becomepass,
become_user=options.become_user,
extra_vars=extra_vars,
)
......
......@@ -108,19 +108,14 @@ def main(args):
parser.print_help(file=sys.stderr)
return 1
# su and sudo command line arguments need to be mutually exclusive
if (options.su or options.su_user or options.ask_su_pass) and \
(options.sudo or options.sudo_user or options.ask_sudo_pass):
parser.error("Sudo arguments ('--sudo', '--sudo-user', and '--ask-sudo-pass') "
"and su arguments ('-su', '--su-user', and '--ask-su-pass') are "
"mutually exclusive")
# privlege escalation command line arguments need to be mutually exclusive
utils.check_mutually_exclusive_privilege(options, parser)
if (options.ask_vault_pass and options.vault_password_file):
parser.error("--ask-vault-pass and --vault-password-file are mutually exclusive")
sshpass = None
sudopass = None
su_pass = None
becomepass = None
vault_pass = None
options.ask_vault_pass = options.ask_vault_pass or C.DEFAULT_ASK_VAULT_PASS
......@@ -132,11 +127,14 @@ def main(args):
# Never ask for an SSH password when we run with local connection
if options.connection == "local":
options.ask_pass = False
options.ask_sudo_pass = options.ask_sudo_pass or C.DEFAULT_ASK_SUDO_PASS
options.ask_su_pass = options.ask_su_pass or C.DEFAULT_ASK_SU_PASS
(sshpass, sudopass, su_pass, vault_pass) = utils.ask_passwords(ask_pass=options.ask_pass, ask_sudo_pass=options.ask_sudo_pass, ask_su_pass=options.ask_su_pass, ask_vault_pass=options.ask_vault_pass)
options.sudo_user = options.sudo_user or C.DEFAULT_SUDO_USER
options.su_user = options.su_user or C.DEFAULT_SU_USER
# set pe options
utils.normalize_become_options(options)
prompt_method = utils.choose_pass_prompt(options)
(sshpass, becomepass, vault_pass) = utils.ask_passwords(ask_pass=options.ask_pass,
become_ask_pass=options.become_ask_pass,
ask_vault_pass=options.ask_vault_pass,
become_method=prompt_method)
# read vault_pass from a file
if not options.ask_vault_pass and options.vault_password_file:
......@@ -197,20 +195,18 @@ def main(args):
stats=stats,
timeout=options.timeout,
transport=options.connection,
sudo=options.sudo,
sudo_user=options.sudo_user,
sudo_pass=sudopass,
become=options.become,
become_method=options.become_method,
become_user=options.become_user,
become_pass=becomepass,
extra_vars=extra_vars,
private_key_file=options.private_key_file,
only_tags=only_tags,
skip_tags=skip_tags,
check=options.check,
diff=options.diff,
su=options.su,
su_pass=su_pass,
su_user=options.su_user,
vault_password=vault_pass,
force_handlers=options.force_handlers
force_handlers=options.force_handlers,
)
if options.flush_cache:
......
Ansible Privilege Escalation
++++++++++++++++++++++++++++
Ansible can use existing privilege escalation systems to allow a user to execute tasks as another.
.. contents:: Topics
Become
``````
Before 1.9 Ansible mostly allowed the use of sudo and a limited use of su to allow a login/remote user to become a different user
and execute tasks, create resources with the 2nd user's permissions. As of 1.9 'become' supersedes the old sudo/su, while still
being backwards compatible. This new system also makes it easier to add other privilege escalation tools like pbrun (Powerbroker),
pfexec and others.
New directives
--------------
become
equivalent to adding sudo: or su: to a play or task, set to true/yes to activate privilege escalation
become_user
equivalent to adding sudo_user: or su_user: to a play or task
become_method
at play or task level overrides the default method set in ansibile.cfg
New ansible_ variables
----------------------
Each allows you to set an option per group and/or host
ansible_become
equivalent to ansible_sudo or ansbile_su, allows to force privilege escalation
ansible_become_method
allows to set privilege escalation method
ansible_become_user
equivalent to ansible_sudo_user or ansbile_su_user, allows to set the user you become through privilege escalation
ansible_become_pass
equivalent to ansible_sudo_pass or ansbile_su_pass, allows you to set the privilege escalation password
New command line options
-----------------------
--ask-become-pass
ask for privilege escalation password
-b, --become
run operations with become (no passorwd implied)
--become-method=BECOME_METHOD
privilege escalation method to use (default=sudo),
valid choices: [ sudo | su | pbrun | pfexec ]
--become-user=BECOME_USER
run operations as this user (default=root)
sudo and su still work!
-----------------------
Old playbooks will not need to be changed, even though they are deprecated, sudo and su directives will continue to work though it
is recommended to move to become as they may be retired at one point. You cannot mix directives on the same object though, ansible
will complain if you try to.
Become will default to using the old sudo/su configs and variables if they exist, but will override them if you specify any of the
new ones.
.. note:: Privilege escalation methods must also be supported by the connection plugin used, most will warn if they do not, some will just ignore it as they always run as root (jail, chroot, etc).
.. seealso::
`Mailing List <http://groups.google.com/group/ansible-project>`_
Questions? Help? Ideas? Stop by the list on Google Groups
`irc.freenode.net <http://irc.freenode.net>`_
#ansible IRC chat channel
......@@ -159,6 +159,12 @@ fact_caching = memory
#retry_files_enabled = False
#retry_files_save_path = ~/.ansible-retry
[privilege_escalation]
#become=True
#become_method='sudo'
#become_user='root'
#become_ask_pass=False
[paramiko_connection]
# uncomment this line to cause the paramiko connection plugin to not record new host
......
......@@ -86,9 +86,6 @@ def shell_expand_path(path):
path = os.path.expanduser(os.path.expandvars(path))
return path
def get_plugin_paths(path):
return ':'.join([os.path.join(x, path) for x in [os.path.expanduser('~/.ansible/plugins/'), '/usr/share/ansible_plugins/']])
p = load_config_file()
active_user = pwd.getpwuid(os.geteuid())[0]
......@@ -137,16 +134,28 @@ DEFAULT_SU_FLAGS = get_config(p, DEFAULTS, 'su_flags', 'ANSIBLE_SU_FLAG
DEFAULT_SU_USER = get_config(p, DEFAULTS, 'su_user', 'ANSIBLE_SU_USER', 'root')
DEFAULT_ASK_SU_PASS = get_config(p, DEFAULTS, 'ask_su_pass', 'ANSIBLE_ASK_SU_PASS', False, boolean=True)
DEFAULT_GATHERING = get_config(p, DEFAULTS, 'gathering', 'ANSIBLE_GATHERING', 'implicit').lower()
DEFAULT_ACTION_PLUGIN_PATH = get_config(p, DEFAULTS, 'action_plugins', 'ANSIBLE_ACTION_PLUGINS', get_plugin_paths('action_plugins'))
DEFAULT_CACHE_PLUGIN_PATH = get_config(p, DEFAULTS, 'cache_plugins', 'ANSIBLE_CACHE_PLUGINS', get_plugin_paths('cache_plugins'))
DEFAULT_CALLBACK_PLUGIN_PATH = get_config(p, DEFAULTS, 'callback_plugins', 'ANSIBLE_CALLBACK_PLUGINS', get_plugin_paths('callback_plugins'))
DEFAULT_CONNECTION_PLUGIN_PATH = get_config(p, DEFAULTS, 'connection_plugins', 'ANSIBLE_CONNECTION_PLUGINS', get_plugin_paths('connection_plugins'))
DEFAULT_LOOKUP_PLUGIN_PATH = get_config(p, DEFAULTS, 'lookup_plugins', 'ANSIBLE_LOOKUP_PLUGINS', get_plugin_paths('lookup_plugins'))
DEFAULT_VARS_PLUGIN_PATH = get_config(p, DEFAULTS, 'vars_plugins', 'ANSIBLE_VARS_PLUGINS', get_plugin_paths('vars_plugins'))
DEFAULT_FILTER_PLUGIN_PATH = get_config(p, DEFAULTS, 'filter_plugins', 'ANSIBLE_FILTER_PLUGINS', get_plugin_paths('filter_plugins'))
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','runas','pfexec']
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')
DEFAULT_CONNECTION_PLUGIN_PATH = get_config(p, DEFAULTS, 'connection_plugins', 'ANSIBLE_CONNECTION_PLUGINS', '~/.ansible/plugins/connection_plugins:/usr/share/ansible_plugins/connection_plugins')
DEFAULT_LOOKUP_PLUGIN_PATH = get_config(p, DEFAULTS, 'lookup_plugins', 'ANSIBLE_LOOKUP_PLUGINS', '~/.ansible/plugins/lookup_plugins:/usr/share/ansible_plugins/lookup_plugins')
DEFAULT_VARS_PLUGIN_PATH = get_config(p, DEFAULTS, 'vars_plugins', 'ANSIBLE_VARS_PLUGINS', '~/.ansible/plugins/vars_plugins:/usr/share/ansible_plugins/vars_plugins')
DEFAULT_FILTER_PLUGIN_PATH = get_config(p, DEFAULTS, 'filter_plugins', 'ANSIBLE_FILTER_PLUGINS', '~/.ansible/plugins/filter_plugins:/usr/share/ansible_plugins/filter_plugins')
CACHE_PLUGIN = get_config(p, DEFAULTS, 'fact_caching', 'ANSIBLE_CACHE_PLUGIN', 'memory')
CACHE_PLUGIN_CONNECTION = get_config(p, DEFAULTS, 'fact_caching_connection', 'ANSIBLE_CACHE_PLUGIN_CONNECTION', None)
CACHE_PLUGIN_PREFIX = get_config(p, DEFAULTS, 'fact_caching_prefix', 'ANSIBLE_CACHE_PLUGIN_PREFIX', 'ansible_facts')
......@@ -172,7 +181,7 @@ ANSIBLE_SSH_ARGS = get_config(p, 'ssh_connection', 'ssh_args', 'AN
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)
......@@ -188,6 +197,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
......
......@@ -60,15 +60,12 @@ 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 = None,
transport = C.DEFAULT_TRANSPORT,
private_key_file = C.DEFAULT_PRIVATE_KEY_FILE,
callbacks = None,
runner_callbacks = None,
stats = None,
sudo = False,
sudo_user = C.DEFAULT_SUDO_USER,
extra_vars = None,
only_tags = None,
skip_tags = None,
......@@ -77,11 +74,13 @@ class PlayBook(object):
check = False,
diff = False,
any_errors_fatal = False,
su = False,
su_user = False,
su_pass = False,
vault_password = False,
force_handlers = False,
# privelege escalation
become = C.DEFAULT_BECOME,
become_method = C.DEFAULT_BECOME_METHOD,
become_user = C.DEFAULT_BECOME_USER,
become_pass = None,
):
"""
......@@ -92,13 +91,11 @@ class PlayBook(object):
timeout: connection timeout
remote_user: run as this user if not specified in a particular play
remote_pass: use this remote password (for all plays) vs using SSH keys
sudo_pass: if sudo==True, and a password is required, this is the sudo password
remote_port: default remote port to use if not specified with the host or play
transport: how to connect to hosts that don't specify a transport (local, paramiko, etc)
callbacks output callbacks for the playbook
runner_callbacks: more callbacks, this time for the runner API
stats: holds aggregrate data about events occurring to each host
sudo: if not specified per play, requests all plays use sudo mode
inventory: can be specified instead of host_list to use a pre-existing inventory object
check: don't change anything, just try to detect some potential changes
any_errors_fatal: terminate the entire execution immediately when one of the hosts has failed
......@@ -139,21 +136,20 @@ class PlayBook(object):
self.callbacks = callbacks
self.runner_callbacks = runner_callbacks
self.stats = stats
self.sudo = sudo
self.sudo_pass = sudo_pass
self.sudo_user = sudo_user
self.extra_vars = extra_vars
self.global_vars = {}
self.private_key_file = private_key_file
self.only_tags = only_tags
self.skip_tags = skip_tags
self.any_errors_fatal = any_errors_fatal
self.su = su
self.su_user = su_user
self.su_pass = su_pass
self.vault_password = vault_password
self.force_handlers = force_handlers
self.become = become
self.become_method = become_method
self.become_user = become_user
self.become_pass = become_pass
self.callbacks.playbook = self
self.runner_callbacks.playbook = self
......@@ -416,10 +412,7 @@ class PlayBook(object):
basedir=task.play.basedir,
conditional=task.when,
callbacks=self.runner_callbacks,
sudo=task.sudo,
sudo_user=task.sudo_user,
transport=task.transport,
sudo_pass=task.sudo_pass,
is_playbook=True,
check=self.check,
diff=self.diff,
......@@ -429,13 +422,14 @@ class PlayBook(object):
accelerate_port=task.play.accelerate_port,
accelerate_ipv6=task.play.accelerate_ipv6,
error_on_undefined_vars=C.DEFAULT_UNDEFINED_VAR_BEHAVIOR,
su=task.su,
su_user=task.su_user,
su_pass=task.su_pass,
vault_pass = self.vault_password,
run_hosts=hosts,
no_log=task.no_log,
run_once=task.run_once,
become=task.become,
become_method=task.become_method,
become_user=task.become_user,
become_pass=task.become_pass,
)
runner.module_vars.update({'play_hosts': hosts})
......@@ -616,12 +610,10 @@ class PlayBook(object):
setup_cache=self.SETUP_CACHE,
vars_cache=self.VARS_CACHE,
callbacks=self.runner_callbacks,
sudo=play.sudo,
sudo_user=play.sudo_user,
sudo_pass=self.sudo_pass,
su=play.su,
su_user=play.su_user,
su_pass=self.su_pass,
become=play.become,
become_method=play.become_method,
become_user=play.become_user,
become_pass=self.become_pass,
vault_pass=self.vault_password,
transport=play.transport,
is_playbook=True,
......
......@@ -24,26 +24,24 @@ import sys
class Task(object):
__slots__ = [
'name', 'meta', 'action', 'when', 'async_seconds', 'async_poll_interval',
'notify', 'module_name', 'module_args', 'module_vars', 'play_vars', 'play_file_vars', 'role_vars', 'role_params', 'default_vars',
'play', 'notified_by', 'tags', 'register', 'role_name',
'delegate_to', 'first_available_file', 'ignore_errors',
'local_action', 'transport', 'sudo', 'remote_user', 'sudo_user', 'sudo_pass',
'items_lookup_plugin', 'items_lookup_terms', 'environment', 'args',
'any_errors_fatal', 'changed_when', 'failed_when', 'always_run', 'delay', 'retries', 'until',
'su', 'su_user', 'su_pass', 'no_log', 'run_once',
_t_common = [
'action', 'always_run', 'any_errors_fatal', 'args', 'become', 'become_method', 'become_pass',
'become_user', 'changed_when', 'delay', 'delegate_to', 'environment', 'failed_when',
'first_available_file', 'ignore_errors', 'local_action', 'meta', 'name', 'no_log',
'notify', 'register', 'remote_user', 'retries', 'run_once', 'su', 'su_pass', 'su_user',
'sudo', 'sudo_pass', 'sudo_user', 'tags', 'transport', 'until', 'when',
]
__slots__ = [
'async_poll_interval', 'async_seconds', 'default_vars', 'first_available_file',
'items_lookup_plugin', 'items_lookup_terms', 'module_args', 'module_name', 'module_vars',
'notified_by', 'play', 'play_file_vars', 'play_vars', 'role_name', 'role_params', 'role_vars',
] + _t_common
# to prevent typos and such
VALID_KEYS = frozenset((
'name', 'meta', 'action', 'when', 'async', 'poll', 'notify',
'first_available_file', 'include', 'tags', 'register', 'ignore_errors',
'delegate_to', 'local_action', 'transport', 'remote_user', 'sudo', 'sudo_user',
'sudo_pass', 'when', 'connection', 'environment', 'args',
'any_errors_fatal', 'changed_when', 'failed_when', 'always_run', 'delay', 'retries', 'until',
'su', 'su_user', 'su_pass', 'no_log', 'run_once',
))
VALID_KEYS = frozenset([
'async', 'connection', 'include', 'poll',
] + _t_common)
def __init__(self, play, ds, module_vars=None, play_vars=None, play_file_vars=None, role_vars=None, role_params=None, default_vars=None, additional_conditions=None, role_name=None):
''' constructor loads from a task or handler datastructure '''
......@@ -131,14 +129,12 @@ class Task(object):
self.name = ds.get('name', None)
self.tags = [ 'untagged' ]
self.register = ds.get('register', None)
self.sudo = utils.boolean(ds.get('sudo', play.sudo))
self.su = utils.boolean(ds.get('su', play.su))
self.environment = ds.get('environment', play.environment)
self.role_name = role_name
self.no_log = utils.boolean(ds.get('no_log', "false")) or self.play.no_log
self.run_once = utils.boolean(ds.get('run_once', 'false'))
#Code to allow do until feature in a Task
#Code to allow do until feature in a Task
if 'until' in ds:
if not ds.get('register'):
raise errors.AnsibleError("register keyword is mandatory when using do until feature")
......@@ -160,24 +156,36 @@ class Task(object):
else:
self.remote_user = ds.get('remote_user', play.playbook.remote_user)
self.sudo_user = None
self.sudo_pass = None
self.su_user = None
self.su_pass = None
if self.sudo:
self.sudo_user = ds.get('sudo_user', play.sudo_user)
self.sudo_pass = ds.get('sudo_pass', play.playbook.sudo_pass)
elif self.su:
self.su_user = ds.get('su_user', play.su_user)
self.su_pass = ds.get('su_pass', play.playbook.su_pass)
# Fail out if user specifies a sudo param with a su param in a given play
if (ds.get('sudo') or ds.get('sudo_user') or ds.get('sudo_pass')) and \
(ds.get('su') or ds.get('su_user') or ds.get('su_pass')):
raise errors.AnsibleError('sudo params ("sudo", "sudo_user", "sudo_pass") '
'and su params "su", "su_user", "su_pass") '
'cannot be used together')
# Fail out if user specifies privilege escalation params in conflict
if (ds.get('become') or ds.get('become_user') or ds.get('become_pass')) and (ds.get('sudo') or ds.get('sudo_user') or ds.get('sudo_pass')):
raise errors.AnsibleError('incompatible parameters ("become", "become_user", "become_pass") and sudo params "sudo", "sudo_user", "sudo_pass" in task: %s' % self.name)
if (ds.get('become') or ds.get('become_user') or ds.get('become_pass')) and (ds.get('su') or ds.get('su_user') or ds.get('su_pass')):
raise errors.AnsibleError('incompatible parameters ("become", "become_user", "become_pass") and su params "su", "su_user", "sudo_pass" in task: %s' % self.name)
if (ds.get('sudo') or ds.get('sudo_user') or ds.get('sudo_pass')) and (ds.get('su') or ds.get('su_user') or ds.get('su_pass')):
raise errors.AnsibleError('incompatible parameters ("su", "su_user", "su_pass") and sudo params "sudo", "sudo_user", "sudo_pass" in task: %s' % self.name)
self.become = utils.boolean(ds.get('become', play.become))
self.become_method = ds.get('become_method', play.become_method)
self.become_user = ds.get('become_user', play.become_user)
self.become_pass = ds.get('become_pass', play.playbook.become_pass)
# set only if passed in current task data
if 'sudo' in ds or 'sudo_user' in ds:
self.become=ds['sudo']
self.become_method='sudo'
if 'sudo_user' in ds:
self.become_user = ds['sudo_user']
if 'sudo_pass' in ds:
self.become_pass = ds['sudo_pass']
if 'su' in ds or 'su_user' in ds:
self.become=ds['su']
self.become_method='su'
if 'su_user' in ds:
self.become_user = ds['su_user']
if 'su_pass' in ds:
self.become_pass = ds['su_pass']
# Both are defined
if ('action' in ds) and ('local_action' in ds):
......
......@@ -125,7 +125,7 @@ class ActionModule(object):
xfered = self.runner._transfer_str(conn, tmp, 'src', resultant)
# fix file permissions when the copy is done as a different user
if self.runner.sudo and self.runner.sudo_user != 'root' or self.runner.su and self.runner.su_user != 'root':
if self.runner.become and self.runner.become_user != 'root':
self.runner._remote_chmod(conn, 'a+r', xfered, tmp)
# run the copy module
......
......@@ -234,7 +234,7 @@ class ActionModule(object):
self._remove_tempfile_if_content_defined(content, content_tempfile)
# fix file permissions when the copy is done as a different user
if (self.runner.sudo and self.runner.sudo_user != 'root' or self.runner.su and self.runner.su_user != 'root') and not raw:
if self.runner.become and self.runner.become_user != 'root' and not raw:
self.runner._remote_chmod(conn, 'a+r', tmp_src, tmp_path)
if raw:
......
......@@ -78,7 +78,7 @@ class ActionModule(object):
# use slurp if sudo and permissions are lacking
remote_data = None
if remote_checksum in ('1', '2') or self.runner.sudo:
if remote_checksum in ('1', '2') or self.runner.become:
slurpres = self.runner._execute_module(conn, tmp, 'slurp', 'src=%s' % source, inject=inject)
if slurpres.is_successful():
if slurpres.result['encoding'] == 'base64':
......
......@@ -50,7 +50,7 @@ class ActionModule(object):
tmp_src = tmp + src
conn.put_file(src, tmp_src)
if self.runner.sudo and self.runner.sudo_user != 'root' or self.runner.su and self.runner.su_user != 'root':
if self.runner.become and self.runner.become_user != 'root':
if not self.runner.noop_on_check(inject):
self.runner._remote_chmod(conn, 'a+r', tmp_src, tmp)
......
......@@ -113,8 +113,7 @@ class ActionModule(object):
sudoable = True
# set file permissions, more permissive when the copy is done as a different user
if ((self.runner.sudo and self.runner.sudo_user != 'root') or
(self.runner.su and self.runner.su_user != 'root')):
if self.runner.become and self.runner.become_user != 'root':
chmod_mode = 'a+rx'
sudoable = False
else:
......
......@@ -78,7 +78,7 @@ class ActionModule(object):
# Store original transport and sudo values.
self.original_transport = inject.get('ansible_connection', self.runner.transport)
self.original_sudo = self.runner.sudo
self.original_become = self.runner.become
self.transport_overridden = False
if inject.get('delegate_to') is None:
......@@ -87,7 +87,7 @@ class ActionModule(object):
if self.original_transport != 'local':
inject['ansible_connection'] = 'local'
self.transport_overridden = True
self.runner.sudo = False
self.runner.become = False
def run(self, conn, tmp, module_name, module_args,
inject, complex_args=None, **kwargs):
......@@ -143,7 +143,7 @@ class ActionModule(object):
# use a delegate host instead of localhost
use_delegate = True
# COMPARE DELEGATE, HOST AND TRANSPORT
# COMPARE DELEGATE, HOST AND TRANSPORT
process_args = False
if not dest_host is src_host and self.original_transport != 'local':
# interpret and inject remote host info into src or dest
......@@ -160,7 +160,7 @@ class ActionModule(object):
if not use_delegate or not user:
user = inject.get('ansible_ssh_user',
self.runner.remote_user)
if use_delegate:
# FIXME
private_key = inject.get('ansible_ssh_private_key_file', self.runner.private_key_file)
......@@ -172,7 +172,7 @@ class ActionModule(object):
if not private_key is None:
private_key = os.path.expanduser(private_key)
options['private_key'] = private_key
# use the mode to define src and dest's url
if options.get('mode', 'push') == 'pull':
# src is a remote path: <user>@<host>, dest is a local path
......@@ -192,7 +192,7 @@ class ActionModule(object):
rsync_path = options.get('rsync_path', None)
# If no rsync_path is set, sudo was originally set, and dest is remote then add 'sudo rsync' argument.
if not rsync_path and self.transport_overridden and self.original_sudo and not dest_is_local:
if not rsync_path and self.transport_overridden and self.original_become and not dest_is_local and self.runner.become_method == 'sudo':
rsync_path = 'sudo rsync'
# make sure rsync path is quoted.
......@@ -206,8 +206,8 @@ class ActionModule(object):
# run the module and store the result
result = self.runner._execute_module(conn, tmp, 'synchronize', module_args, complex_args=options, inject=inject)
# reset the sudo property
self.runner.sudo = self.original_sudo
# reset the sudo property
self.runner.become = self.original_become
return result
......@@ -133,7 +133,7 @@ class ActionModule(object):
xfered = self.runner._transfer_str(conn, tmp, 'source', resultant)
# fix file permissions when the copy is done as a different user
if self.runner.sudo and self.runner.sudo_user != 'root' or self.runner.su and self.runner.su_user != 'root':
if self.runner.become and self.runner.become_user != 'root' or self.runner.su and self.runner.su_user != 'root':
self.runner._remote_chmod(conn, 'a+r', xfered, tmp)
# run the copy module
......
......@@ -99,7 +99,7 @@ class ActionModule(object):
# handle check mode client side
# fix file permissions when the copy is done as a different user
if copy:
if self.runner.sudo and self.runner.sudo_user != 'root' or self.runner.su and self.runner.su_user != 'root':
if self.runner.become and self.runner.become_user != 'root':
if not self.runner.noop_on_check(inject):
self.runner._remote_chmod(conn, 'a+r', tmp_src, tmp)
# Build temporary module_args.
......
......@@ -230,7 +230,7 @@ class ActionModule(object):
self._remove_tempfile_if_content_defined(content, content_tempfile)
# fix file permissions when the copy is done as a different user
if (self.runner.sudo and self.runner.sudo_user != 'root' or self.runner.su and self.runner.su_user != 'root') and not raw:
if self.runner.become and self.runner.become_user != 'root' and not raw:
self.runner._remote_chmod(conn, 'a+r', tmp_src, tmp_path)
if raw:
......
......@@ -109,7 +109,7 @@ class ActionModule(object):
xfered = self.runner._transfer_str(conn, tmp, 'source', resultant)
# fix file permissions when the copy is done as a different user
if self.runner.sudo and self.runner.sudo_user != 'root' or self.runner.su and self.runner.su_user != 'root':
if self.runner.become and self.runner.become_user != 'root':
self.runner._remote_chmod(conn, 'a+r', xfered, tmp)
# run the copy module
......
......@@ -50,6 +50,7 @@ class Connection(object):
self.accport = port[1]
self.is_connected = False
self.has_pipelining = False
self.become_methods_supported=['sudo']
if not self.port:
self.port = constants.DEFAULT_REMOTE_PORT
......@@ -226,11 +227,11 @@ class Connection(object):
else:
return response.get('rc') == 0
def exec_command(self, cmd, tmp_path, sudo_user=None, sudoable=False, executable='/bin/sh', in_data=None, su=None, su_user=None):
def exec_command(self, cmd, tmp_path, become_user=None, sudoable=False, executable='/bin/sh', in_data=None):
''' run a command on the remote host '''
if su or su_user:
raise AnsibleError("Internal Error: this module does not support running commands via su")
if sudoable and self.runner.become and self.runner.become_method not in self.become_methods_supported:
raise errors.AnsibleError("Internal Error: this module does not support running commands via %s" % self.runner.become_method)
if in_data:
raise AnsibleError("Internal Error: this module does not support optimized module pipelining")
......@@ -238,8 +239,8 @@ class Connection(object):
if executable == "":
executable = constants.DEFAULT_EXECUTABLE
if self.runner.sudo and sudoable and sudo_user:
cmd, prompt, success_key = utils.make_sudo_cmd(self.runner.sudo_exe, sudo_user, executable, cmd)
if self.runner.become and sudoable:
cmd, prompt, success_key = utils.make_become_cmd(cmd, become_user, executable, self.runner.become_method, '', self.runner.become_exe)
vvv("EXEC COMMAND %s" % cmd)
......@@ -292,8 +293,8 @@ class Connection(object):
if fd.tell() >= fstat.st_size:
last = True
data = dict(mode='put', data=base64.b64encode(data), out_path=out_path, last=last)
if self.runner.sudo:
data['user'] = self.runner.sudo_user
if self.runner.become:
data['user'] = self.runner.become_user
data = utils.jsonify(data)
data = utils.encrypt(self.key, data)
......
......@@ -24,6 +24,7 @@ import subprocess
from ansible import errors
from ansible import utils
from ansible.callbacks import vvv
import ansible.constants as C
class Connection(object):
''' Local chroot based connections '''
......@@ -31,6 +32,7 @@ class Connection(object):
def __init__(self, runner, host, port, *args, **kwargs):
self.chroot = host
self.has_pipelining = False
self.become_methods_supported=C.BECOME_METHODS
if os.geteuid() != 0:
raise errors.AnsibleError("chroot connection requires running as root")
......@@ -60,16 +62,16 @@ class Connection(object):
return self
def exec_command(self, cmd, tmp_path, sudo_user=None, sudoable=False, executable='/bin/sh', in_data=None, su=None, su_user=None):
def exec_command(self, cmd, tmp_path, become_user=None, sudoable=False, executable='/bin/sh', in_data=None):
''' run a command on the chroot '''
if su or su_user:
raise errors.AnsibleError("Internal Error: this module does not support running commands via su")
if sudoable and self.runner.become and self.runner.become_method not in self.become_methods_supported:
raise errors.AnsibleError("Internal Error: this module does not support running commands via %s" % self.runner.become_method)
if in_data:
raise errors.AnsibleError("Internal Error: this module does not support optimized module pipelining")
# We enter chroot as root so sudo stuff can be ignored
# We enter chroot as root so we ignore privlege escalation?
if executable:
local_cmd = [self.chroot_cmd, self.chroot, executable, '-c', cmd]
......
......@@ -53,6 +53,8 @@ class Connection(object):
else:
self.port = port
self.become_methods_supported=[]
def connect(self):
''' activates the connection object '''
......@@ -64,11 +66,11 @@ class Connection(object):
socket = self.context.socket(zmq.REQ)
addr = "tcp://%s:%s" % (self.host, self.port)
socket.connect(addr)
self.socket = socket
self.socket = socket
return self
def exec_command(self, cmd, tmp_path, sudo_user, sudoable=False, executable='/bin/sh', in_data=None, su_user=None, su=None):
def exec_command(self, cmd, tmp_path, become_user, sudoable=False, executable='/bin/sh', in_data=None):
''' run a command on the remote host '''
if in_data:
......@@ -76,7 +78,7 @@ class Connection(object):
vvv("EXEC COMMAND %s" % cmd)
if (self.runner.sudo and sudoable) or (self.runner.su and su):
if self.runner.become and sudoable:
raise errors.AnsibleError(
"When using fireball, do not specify sudo or su to run your tasks. " +
"Instead sudo the fireball action with sudo. " +
......
......@@ -53,16 +53,14 @@ class Connection(object):
self.client = fc.Client(self.host)
return self
def exec_command(self, cmd, tmp_path, sudo_user=None, sudoable=False,
executable='/bin/sh', in_data=None, su=None, su_user=None):
def exec_command(self, cmd, tmp_path, become_user=None, sudoable=False,
executable='/bin/sh', in_data=None):
''' run a command on the remote minion '''
if su or su_user:
raise errors.AnsibleError("Internal Error: this module does not support running commands via su")
if in_data:
raise errors.AnsibleError("Internal Error: this module does not support optimized module pipelining")
# totally ignores privlege escalation
vvv("EXEC %s" % (cmd), host=self.host)
p = self.client.command.run(cmd)[self.host]
return (p[0], '', p[1], p[2])
......
......@@ -24,6 +24,7 @@ import shutil
import subprocess
from ansible import errors
from ansible.callbacks import vvv
import ansible.constants as C
class Connection(object):
''' Local chroot based connections '''
......@@ -61,6 +62,7 @@ class Connection(object):
self.runner = runner
self.host = host
self.has_pipelining = False
self.become_methods_supported=C.BECOME_METHODS
if os.geteuid() != 0:
raise errors.AnsibleError("jail connection requires running as root")
......@@ -91,16 +93,16 @@ class Connection(object):
local_cmd = '%s "%s" %s' % (self.jexec_cmd, self.jail, cmd)
return local_cmd
def exec_command(self, cmd, tmp_path, sudo_user=None, sudoable=False, executable='/bin/sh', in_data=None, su=None, su_user=None):
def exec_command(self, cmd, tmp_path, become_user=None, sudoable=False, executable='/bin/sh', in_data=None):
''' run a command on the chroot '''
if su or su_user:
raise errors.AnsibleError("Internal Error: this module does not support running commands via su")
if sudoable and self.runner.become and self.runner.become_method not in self.become_methods_supported:
raise errors.AnsibleError("Internal Error: this module does not support running commands via %s" % self.runner.become_method)
if in_data:
raise errors.AnsibleError("Internal Error: this module does not support optimized module pipelining")
# We enter chroot as root so sudo stuff can be ignored
# Ignores privilege escalation
local_cmd = self._generate_cmd(executable, cmd)
vvv("EXEC %s" % (local_cmd), host=self.jail)
......
......@@ -22,6 +22,7 @@ import os
import subprocess
from ansible import errors
from ansible.callbacks import vvv
import ansible.constants as C
class Connection(object):
''' Local lxc based connections '''
......@@ -50,6 +51,7 @@ class Connection(object):
self.host = host
# port is unused, since this is local
self.port = port
self.become_methods_supported=C.BECOME_METHODS
def connect(self, port=None):
''' connect to the lxc; nothing to do here '''
......@@ -65,16 +67,16 @@ class Connection(object):
local_cmd = '%s -q -c lxc:/// lxc-enter-namespace %s -- %s' % (self.cmd, self.lxc, cmd)
return local_cmd
def exec_command(self, cmd, tmp_path, sudo_user, sudoable=False, executable='/bin/sh', in_data=None, su=None, su_user=None):
def exec_command(self, cmd, tmp_path, become_user, sudoable=False, executable='/bin/sh', in_data=None):
''' run a command on the chroot '''
if su or su_user:
raise errors.AnsibleError("Internal Error: this module does not support running commands via su")
if sudoable and self.runner.become and self.runner.become_method not in self.become_methods_supported:
raise errors.AnsibleError("Internal Error: this module does not support running commands via %s" % self.runner.become_method)
if in_data:
raise errors.AnsibleError("Internal Error: this module does not support optimized module pipelining")
# We enter lxc as root so sudo stuff can be ignored
# We ignore privelege escalation!
local_cmd = self._generate_cmd(executable, cmd)
vvv("EXEC %s" % (local_cmd), host=self.lxc)
......
......@@ -26,6 +26,7 @@ from ansible import errors
from ansible import utils
from ansible.callbacks import vvv
class Connection(object):
''' Local based connections '''
......@@ -33,31 +34,34 @@ class Connection(object):
self.runner = runner
self.host = host
# port is unused, since this is local
self.port = port
self.port = port
self.has_pipelining = False
# TODO: add su(needs tty), pbrun, pfexec
self.become_methods_supported=['sudo']
def connect(self, port=None):
''' connect to the local host; nothing to do here '''
return self
def exec_command(self, cmd, tmp_path, sudo_user=None, sudoable=False, executable='/bin/sh', in_data=None, su=None, su_user=None):
def exec_command(self, cmd, tmp_path, become_user=None, sudoable=False, executable='/bin/sh', in_data=None):
''' run a command on the local host '''
# su requires to be run from a terminal, and therefore isn't supported here (yet?)
if su or su_user:
raise errors.AnsibleError("Internal Error: this module does not support running commands via su")
if sudoable and self.runner.become and self.runner.become_method not in self.become_methods_supported:
raise errors.AnsibleError("Internal Error: this module does not support running commands via %s" % self.runner.become_method)
if in_data:
raise errors.AnsibleError("Internal Error: this module does not support optimized module pipelining")
if not self.runner.sudo or not sudoable:
if self.runner.become and sudoable:
local_cmd, prompt, success_key = utils.make_become_cmd(cmd, become_user, executable, self.runner.become_method, '-H', self.runner.become_exe)
else:
if executable:
local_cmd = executable.split() + ['-c', cmd]
else:
local_cmd = cmd
else:
local_cmd, prompt, success_key = utils.make_sudo_cmd(self.runner.sudo_exe, sudo_user, executable, cmd)
executable = executable.split()[0] if executable else None
vvv("EXEC %s" % (local_cmd), host=self.host)
......@@ -66,13 +70,19 @@ class Connection(object):
stdin=subprocess.PIPE,
stdout=subprocess.PIPE, stderr=subprocess.PIPE)
if self.runner.sudo and sudoable and self.runner.sudo_pass:
if self.runner.become and sudoable and self.runner.become_pass:
fcntl.fcntl(p.stdout, fcntl.F_SETFL,
fcntl.fcntl(p.stdout, fcntl.F_GETFL) | os.O_NONBLOCK)
fcntl.fcntl(p.stderr, fcntl.F_SETFL,
fcntl.fcntl(p.stderr, fcntl.F_GETFL) | os.O_NONBLOCK)
sudo_output = ''
while not sudo_output.endswith(prompt) and success_key not in sudo_output:
become_output = ''
while success_key not in become_output:
if prompt and become_output.endswith(prompt):
break
if utils.su_prompts.check_su_prompt(become_output):
break
rfd, wfd, efd = select.select([p.stdout, p.stderr], [],
[p.stdout, p.stderr], self.runner.timeout)
if p.stdout in rfd:
......@@ -81,13 +91,13 @@ class Connection(object):
chunk = p.stderr.read()
else:
stdout, stderr = p.communicate()
raise errors.AnsibleError('timeout waiting for sudo password prompt:\n' + sudo_output)
raise errors.AnsibleError('timeout waiting for %s password prompt:\n' % self.runner.become_method + become_output)
if not chunk:
stdout, stderr = p.communicate()
raise errors.AnsibleError('sudo output closed while waiting for password prompt:\n' + sudo_output)
sudo_output += chunk
if success_key not in sudo_output:
p.stdin.write(self.runner.sudo_pass + '\n')
raise errors.AnsibleError('%s output closed while waiting for password prompt:\n' % self.runner.become_method + become_output)
become_output += chunk
if success_key not in become_output:
p.stdin.write(self.runner.become_pass + '\n')
fcntl.fcntl(p.stdout, fcntl.F_SETFL, fcntl.fcntl(p.stdout, fcntl.F_GETFL) & ~os.O_NONBLOCK)
fcntl.fcntl(p.stderr, fcntl.F_SETFL, fcntl.fcntl(p.stderr, fcntl.F_GETFL) & ~os.O_NONBLOCK)
......
......@@ -125,6 +125,9 @@ class Connection(object):
self.private_key_file = private_key_file
self.has_pipelining = False
# TODO: add pbrun, pfexec
self.become_methods_supported=['sudo', 'su', 'pbrun']
def _cache_key(self):
return "%s__%s__" % (self.host, self.user)
......@@ -184,9 +187,12 @@ class Connection(object):
return ssh
def exec_command(self, cmd, tmp_path, sudo_user=None, sudoable=False, executable='/bin/sh', in_data=None, su=None, su_user=None):
def exec_command(self, cmd, tmp_path, become_user=None, sudoable=False, executable='/bin/sh', in_data=None):
''' run a command on the remote host '''
if self.runner.become and sudoable and self.runner.become_method not in self.become_methods_supported:
raise errors.AnsibleError("Internal Error: this module does not support running commands via %s" % self.runner.become_method)
if in_data:
raise errors.AnsibleError("Internal Error: this module does not support optimized module pipelining")
......@@ -206,7 +212,7 @@ class Connection(object):
no_prompt_out = ''
no_prompt_err = ''
if not (self.runner.sudo and sudoable) and not (self.runner.su and su):
if not (self.runner.become and sudoable):
if executable:
quoted_command = executable + ' -c ' + pipes.quote(cmd)
......@@ -224,50 +230,46 @@ class Connection(object):
chan.get_pty(term=os.getenv('TERM', 'vt100'),
width=int(os.getenv('COLUMNS', 0)),
height=int(os.getenv('LINES', 0)))
if self.runner.sudo or sudoable:
shcmd, prompt, success_key = utils.make_sudo_cmd(self.runner.sudo_exe, sudo_user, executable, cmd)
elif self.runner.su or su:
shcmd, prompt, success_key = utils.make_su_cmd(su_user, executable, cmd)
if self.runner.become and sudoable:
shcmd, prompt, success_key = utils.make_become_cmd(cmd, become_user, executable, self.runner.become_method, '', self.runner.become_exe)
vvv("EXEC %s" % shcmd, host=self.host)
sudo_output = ''
become_output = ''
try:
chan.exec_command(shcmd)
if self.runner.sudo_pass or self.runner.su_pass:
if self.runner.become_pass:
while True:
if success_key in sudo_output or \
(self.runner.sudo_pass and sudo_output.endswith(prompt)) or \
(self.runner.su_pass and utils.su_prompts.check_su_prompt(sudo_output)):
if success_key in become_output or \
(prompt and become_output.endswith(prompt)) or \
utils.su_prompts.check_su_prompt(become_output)):
break
chunk = chan.recv(bufsize)
if not chunk:
if 'unknown user' in sudo_output:
if 'unknown user' in become_output:
raise errors.AnsibleError(
'user %s does not exist' % sudo_user)
'user %s does not exist' % become_user)
else:
raise errors.AnsibleError('ssh connection ' +
'closed waiting for password prompt')
sudo_output += chunk
become_output += chunk
if success_key not in sudo_output:
if success_key not in become_output:
if sudoable:
chan.sendall(self.runner.sudo_pass + '\n')
elif su:
chan.sendall(self.runner.su_pass + '\n')
chan.sendall(self.runner.become_pass + '\n')
else:
no_prompt_out += sudo_output
no_prompt_err += sudo_output
no_prompt_out += become_output
no_prompt_err += become_output
except socket.timeout:
raise errors.AnsibleError('ssh timed out waiting for sudo.\n' + sudo_output)
raise errors.AnsibleError('ssh timed out waiting for privilege escalation.\n' + become_output)
stdout = ''.join(chan.makefile('rb', bufsize))
stderr = ''.join(chan.makefile_stderr('rb', bufsize))
......
......@@ -34,6 +34,7 @@ from ansible.callbacks import vvv
from ansible import errors
from ansible import utils
class Connection(object):
''' ssh based connections '''
......@@ -48,6 +49,9 @@ class Connection(object):
self.HASHED_KEY_MAGIC = "|1|"
self.has_pipelining = True
# TODO: add pbrun, pfexec
self.become_methods_supported=['sudo', 'su', 'pbrun']
fcntl.lockf(self.runner.process_lockfile, fcntl.LOCK_EX)
self.cp_dir = utils.prepare_writeable_dir('$HOME/.ansible/cp',mode=0700)
fcntl.lockf(self.runner.process_lockfile, fcntl.LOCK_UN)
......@@ -140,7 +144,7 @@ class Connection(object):
os.write(self.wfd, "%s\n" % self.password)
os.close(self.wfd)
def _communicate(self, p, stdin, indata, su=False, sudoable=False, prompt=None):
def _communicate(self, p, stdin, indata, sudoable=False, prompt=None):
fcntl.fcntl(p.stdout, fcntl.F_SETFL, fcntl.fcntl(p.stdout, fcntl.F_GETFL) & ~os.O_NONBLOCK)
fcntl.fcntl(p.stderr, fcntl.F_SETFL, fcntl.fcntl(p.stderr, fcntl.F_GETFL) & ~os.O_NONBLOCK)
# We can't use p.communicate here because the ControlMaster may have stdout open as well
......@@ -157,23 +161,20 @@ class Connection(object):
while True:
rfd, wfd, efd = select.select(rpipes, [], rpipes, 1)
# fail early if the sudo/su password is wrong
if self.runner.sudo and sudoable:
if self.runner.sudo_pass:
# fail early if the become password is wrong
if self.runner.become and sudoable:
if self.runner.become_pass:
incorrect_password = gettext.dgettext(
"sudo", "Sorry, try again.")
"Privilege Escalation", "Sorry, try again.")
if stdout.endswith("%s\r\n%s" % (incorrect_password,
prompt)):
raise errors.AnsibleError('Incorrect sudo password')
if stdout.endswith(prompt):
raise errors.AnsibleError('Missing sudo password')
raise errors.AnsibleError('Incorrect become password')
if self.runner.su and su and self.runner.su_pass:
incorrect_password = gettext.dgettext(
"su", "Sorry")
if stdout.endswith("%s\r\n%s" % (incorrect_password, prompt)):
raise errors.AnsibleError('Incorrect su password')
if prompt:
if stdout.endswith(prompt):
raise errors.AnsibleError('Missing become password')
elif stdout.endswith("%s\r\n%s" % (incorrect_password, prompt)):
raise errors.AnsibleError('Incorrect becom password')
if p.stdout in rfd:
dat = os.read(p.stdout.fileno(), 9000)
......@@ -256,9 +257,12 @@ class Connection(object):
vvv("EXEC previous known host file not found for %s" % host)
return True
def exec_command(self, cmd, tmp_path, sudo_user=None, sudoable=False, executable='/bin/sh', in_data=None, su_user=None, su=False):
def exec_command(self, cmd, tmp_path, become_user=None, sudoable=False, executable='/bin/sh', in_data=None):
''' run a command on the remote host '''
if sudoable and self.runner.become and self.runner.become_method not in self.become_methods_supported:
raise errors.AnsibleError("Internal Error: this module does not support running commands via %s" % self.runner.become_method)
ssh_cmd = self._password_cmd()
ssh_cmd += ["ssh", "-C"]
if not in_data:
......@@ -276,25 +280,22 @@ class Connection(object):
ssh_cmd += ['-6']
ssh_cmd += [self.host]
if su and su_user:
sudocmd, prompt, success_key = utils.make_su_cmd(su_user, executable, cmd)
ssh_cmd.append(sudocmd)
elif not self.runner.sudo or not sudoable:
if self.runner.become and sudoable:
becomecmd, prompt, success_key = utils.make_become_cmd(cmd, become_user, executable, self.runner.become_method, '', self.runner.become_exe)
ssh_cmd.append(becomecmd)
else:
prompt = None
if executable:
ssh_cmd.append(executable + ' -c ' + pipes.quote(cmd))
else:
ssh_cmd.append(cmd)
else:
sudocmd, prompt, success_key = utils.make_sudo_cmd(self.runner.sudo_exe, sudo_user, executable, cmd)
ssh_cmd.append(sudocmd)
vvv("EXEC %s" % ' '.join(ssh_cmd), host=self.host)
not_in_host_file = self.not_in_host_file(self.host)
if C.HOST_KEY_CHECKING and not_in_host_file:
# lock around the initial SSH connectivity so the user prompt about whether to add
# lock around the initial SSH connectivity so the user prompt about whether to add
# the host to known hosts is not intermingled with multiprocess output.
fcntl.lockf(self.runner.process_lockfile, fcntl.LOCK_EX)
fcntl.lockf(self.runner.output_lockfile, fcntl.LOCK_EX)
......@@ -306,9 +307,8 @@ class Connection(object):
no_prompt_out = ''
no_prompt_err = ''
if (self.runner.sudo and sudoable and self.runner.sudo_pass) or \
(self.runner.su and su and self.runner.su_pass):
# several cases are handled for sudo privileges with password
if self.runner.become and sudoable and self.runner.become_pass:
# several cases are handled for escalated privileges with password
# * NOPASSWD (tty & no-tty): detect success_key on stdout
# * without NOPASSWD:
# * detect prompt on stdout (tty)
......@@ -317,13 +317,14 @@ class Connection(object):
fcntl.fcntl(p.stdout, fcntl.F_GETFL) | os.O_NONBLOCK)
fcntl.fcntl(p.stderr, fcntl.F_SETFL,
fcntl.fcntl(p.stderr, fcntl.F_GETFL) | os.O_NONBLOCK)
sudo_output = ''
sudo_errput = ''
become_output = ''
become_errput = ''
while True:
if success_key in sudo_output or \
(self.runner.sudo_pass and sudo_output.endswith(prompt)) or \
(self.runner.su_pass and utils.su_prompts.check_su_prompt(sudo_output)):
while success_key not in become_output:
if prompt and become_output.endswith(prompt):
break
if utils.su_prompts.check_su_prompt(become_output):
break
rfd, wfd, efd = select.select([p.stdout, p.stderr], [],
......@@ -331,36 +332,34 @@ class Connection(object):
if p.stderr in rfd:
chunk = p.stderr.read()
if not chunk:
raise errors.AnsibleError('ssh connection closed waiting for sudo or su password prompt')
sudo_errput += chunk
raise errors.AnsibleError('ssh connection closed waiting for a privilege escalation password prompt')
become_errput += chunk
incorrect_password = gettext.dgettext(
"sudo", "Sorry, try again.")
if sudo_errput.strip().endswith("%s%s" % (prompt, incorrect_password)):
raise errors.AnsibleError('Incorrect sudo password')
elif prompt and sudo_errput.endswith(prompt):
stdin.write(self.runner.sudo_pass + '\n')
"become", "Sorry, try again.")
if become_errput.strip().endswith("%s%s" % (prompt, incorrect_password)):
raise errors.AnsibleError('Incorrect become password')
elif prompt and become_errput.endswith(prompt):
stdin.write(self.runner.become_pass + '\n')
if p.stdout in rfd:
chunk = p.stdout.read()
if not chunk:
raise errors.AnsibleError('ssh connection closed waiting for sudo or su password prompt')
sudo_output += chunk
raise errors.AnsibleError('ssh connection closed waiting for %s password prompt' % self.runner.become_method)
become_output += chunk
if not rfd:
# timeout. wrap up process communication
stdout = p.communicate()
raise errors.AnsibleError('ssh connection error waiting for sudo or su password prompt')
raise errors.AnsibleError('ssh connection error while waiting for %s password prompt' % self.runner.become_method)
if success_key not in sudo_output:
if success_key not in become_output:
if sudoable:
stdin.write(self.runner.sudo_pass + '\n')
elif su:
stdin.write(self.runner.su_pass + '\n')
stdin.write(self.runner.become_pass + '\n')
else:
no_prompt_out += sudo_output
no_prompt_err += sudo_errput
no_prompt_out += become_output
no_prompt_err += become_errput
(returncode, stdout, stderr) = self._communicate(p, stdin, in_data, su=su, sudoable=sudoable, prompt=prompt)
(returncode, stdout, stderr) = self._communicate(p, stdin, in_data, sudoable=sudoable, prompt=prompt)
if C.HOST_KEY_CHECKING and not_in_host_file:
# lock around the initial SSH connectivity so the user prompt about whether to add
......
......@@ -72,6 +72,10 @@ class Connection(object):
self.shell_id = None
self.delegate = None
# Add runas support
#self.become_methods_supported=['runas']
self.become_methods_supported=[]
def _winrm_connect(self):
'''
Establish a WinRM connection over HTTP/HTTPS.
......@@ -143,7 +147,11 @@ class Connection(object):
self.protocol = self._winrm_connect()
return self
def exec_command(self, cmd, tmp_path, sudo_user=None, sudoable=False, executable=None, in_data=None, su=None, su_user=None):
def exec_command(self, cmd, tmp_path, become_user=None, sudoable=False, executable=None, in_data=None):
if sudoable and self.runner.become and self.runner.become_method not in self.become_methods_supported:
raise errors.AnsibleError("Internal Error: this module does not support running commands via %s" % self.runner.become_method)
cmd = cmd.encode('utf-8')
cmd_parts = shlex.split(cmd, posix=False)
if '-EncodedCommand' in cmd_parts:
......
......@@ -26,6 +26,7 @@ import subprocess
from subprocess import Popen,PIPE
from ansible import errors
from ansible.callbacks import vvv
import ansible.constants as C
class Connection(object):
''' Local zone based connections '''
......@@ -68,6 +69,7 @@ class Connection(object):
self.runner = runner
self.host = host
self.has_pipelining = False
self.become_methods_supported=C.BECOME_METHODS
if os.geteuid() != 0:
raise errors.AnsibleError("zone connection requires running as root")
......@@ -98,17 +100,16 @@ class Connection(object):
local_cmd = '%s "%s" %s' % (self.zlogin_cmd, self.zone, cmd)
return local_cmd
#def exec_command(self, cmd, tmp_path, sudo_user=None, sudoable=False, executable='/bin/sh', in_data=None, su=None, su_user=None):
def exec_command(self, cmd, tmp_path, sudo_user=None, sudoable=False, executable=None, in_data=None, su=None, su_user=None):
def exec_command(self, cmd, tmp_path, become_user=None, sudoable=False, executable=None, in_data=None):
''' run a command on the zone '''
if su or su_user:
raise errors.AnsibleError("Internal Error: this module does not support running commands via su")
if sudoable and self.runner.become and self.runner.become_method not in self.become_methods_supported:
raise errors.AnsibleError("Internal Error: this module does not support running commands via %s" % self.runner.become_method)
if in_data:
raise errors.AnsibleError("Internal Error: this module does not support optimized module pipelining")
# We enter zone as root so sudo stuff can be ignored
# We happily ignore privelege escalation
if executable == '/bin/sh':
executable = None
local_cmd = self._generate_cmd(executable, cmd)
......
......@@ -3,6 +3,8 @@
roles:
# In destructive because it creates and removes a user
- { role: test_sudo, tags: test_sudo}
#- { role: test_su, tags: test_su} # wait till su support is added to local connection, needs tty
- { role: test_become, tags: test_become}
- { role: test_service, tags: test_service }
# Current pip unconditionally uses md5. We can re-enable if pip switches
# to a different hash or allows us to not check md5
......
testing tilde expansion with become
- include_vars: default.yml
- name: Create test user
become: True
become_user: root
user:
name: "{{ become_test_user }}"
- name: test becoming user
shell: whoami
become: True
become_user: "{{ become_test_user }}"
register: results
- assert:
that:
- "results.stdout == '{{ become_test_user }}'"
- name: tilde expansion honors become in file
become: True
become_user: "{{ become_test_user }}"
file:
path: "~/foo.txt"
state: touch
- name: check that the path in the user's home dir was created
stat:
path: "~{{ become_test_user }}/foo.txt"
register: results
- assert:
that:
- "results.stat.exists == True"
- "results.stat.path|dirname|basename == '{{ become_test_user }}'"
- name: tilde expansion honors become in template
become: True
become_user: "{{ become_test_user }}"
template:
src: "bar.j2"
dest: "~/bar.txt"
- name: check that the path in the user's home dir was created
stat:
path: "~{{ become_test_user }}/bar.txt"
register: results
- assert:
that:
- "results.stat.exists == True"
- "results.stat.path|dirname|basename == '{{ become_test_user }}'"
- name: tilde expansion honors become in copy
become: True
become_user: "{{ become_test_user }}"
copy:
src: baz.txt
dest: "~/baz.txt"
- name: check that the path in the user's home dir was created
stat:
path: "~{{ become_test_user }}/baz.txt"
register: results
- assert:
that:
- "results.stat.exists == True"
- "results.stat.path|dirname|basename == '{{ become_test_user }}'"
- name: Remove test user and their home dir
become: True
become_user: root
user:
name: "{{ become_test_user }}"
state: "absent"
remove: "yes"
testing tilde expansion with su
- include_vars: default.yml
- name: Create test user
su: True
user:
name: "{{ su_test_user }}"
- name: test becoming user
shell: whoami
su: True
su_user: "{{ su_test_user }}"
register: results
- assert:
that:
- "results.stdout == '{{ su_test_user }}'"
- name: tilde expansion honors su in file
su: True
su_user: "{{ su_test_user }}"
file:
path: "~/foo.txt"
state: touch
- name: check that the path in the user's home dir was created
stat:
path: "~{{ su_test_user }}/foo.txt"
register: results
- assert:
that:
- "results.stat.exists == True"
- "results.stat.path|dirname|basename == '{{ su_test_user }}'"
- name: tilde expansion honors su in template
su: True
su_user: "{{ su_test_user }}"
template:
src: "bar.j2"
dest: "~/bar.txt"
- name: check that the path in the user's home dir was created
stat:
path: "~{{ su_test_user }}/bar.txt"
register: results
- assert:
that:
- "results.stat.exists == True"
- "results.stat.path|dirname|basename == '{{ su_test_user }}'"
- name: tilde expansion honors su in copy
su: True
su_user: "{{ su_test_user }}"
copy:
src: baz.txt
dest: "~/baz.txt"
- name: check that the path in the user's home dir was created
stat:
path: "~{{ su_test_user }}/baz.txt"
register: results
- assert:
that:
- "results.stat.exists == True"
- "results.stat.path|dirname|basename == '{{ su_test_user }}'"
- name: Remove test user and their home dir
su: True
user:
name: "{{ su_test_user }}"
state: "absent"
remove: "yes"
- include_vars: default.yml
- name: Create test user
sudo: true
user:
name: "{{ sudo_test_user }}"
- name: test becoming user
shell: whoami
sudo: True
sudo_user: "{{ sudo_test_user }}"
register: results
- assert:
that:
- "results.stdout == '{{ sudo_test_user }}'"
- name: tilde expansion honors sudo in file
sudo: True
sudo_user: "{{ sudo_test_user }}"
......@@ -56,6 +67,7 @@
- "results.stat.path|dirname|basename == '{{ sudo_test_user }}'"
- name: Remove test user and their home dir
sudo: true
user:
name: "{{ sudo_test_user }}"
state: "absent"
......
......@@ -41,6 +41,9 @@ class FakePlayBook(object):
self.sudo_user = None
self.su = None
self.su_user = None
self.become = None
self.become_method = None
self.become_user = None
self.transport = None
self.only_tags = None
self.skip_tags = None
......
......@@ -18,6 +18,9 @@ class FakeRunner(object):
self.remote_user = None
self.private_key_file = None
self.check = False
self.become = False
self.become_method = False
self.become_user = False
def _execute_module(self, conn, tmp, module_name, args,
async_jid=None, async_module=None, async_limit=None, inject=None,
......@@ -76,7 +79,7 @@ class TestSynchronize(unittest.TestCase):
""" verify the synchronize action plugin unsets and then sets sudo """
runner = FakeRunner()
runner.sudo = True
runner.become = True
runner.remote_user = "root"
runner.transport = "ssh"
conn = FakeConn()
......@@ -97,7 +100,7 @@ class TestSynchronize(unittest.TestCase):
assert runner.executed_complex_args == {'dest':'root@el6.lab.net:/tmp/bar',
'src':'/tmp/foo',
'rsync_path':'"sudo rsync"'}, "wrong args used"
assert runner.sudo == True, "sudo was not reset to True"
assert runner.become == True, "sudo was not reset to True"
def test_synchronize_action_local(self):
......
......@@ -498,7 +498,7 @@ class TestUtils(unittest.TestCase):
self.assertEqual(len(cmd), 3)
self.assertTrue('-u root' in cmd[0])
self.assertTrue('-p "[sudo via ansible, key=' in cmd[0] and cmd[1].startswith('[sudo via ansible, key'))
self.assertTrue('echo SUDO-SUCCESS-' in cmd[0] and cmd[2].startswith('SUDO-SUCCESS-'))
self.assertTrue('echo BECOME-SUCCESS-' in cmd[0] and cmd[2].startswith('BECOME-SUCCESS-'))
self.assertTrue('sudo -k' in cmd[0])
def test_make_su_cmd(self):
......@@ -506,7 +506,7 @@ class TestUtils(unittest.TestCase):
self.assertTrue(isinstance(cmd, tuple))
self.assertEqual(len(cmd), 3)
self.assertTrue('root -c "/bin/sh' in cmd[0] or ' root -c /bin/sh' in cmd[0])
self.assertTrue('echo SUDO-SUCCESS-' in cmd[0] and cmd[2].startswith('SUDO-SUCCESS-'))
self.assertTrue('echo BECOME-SUCCESS-' in cmd[0] and cmd[2].startswith('BECOME-SUCCESS-'))
def test_to_unicode(self):
uni = ansible.utils.unicode.to_unicode(u'ansible')
......
......@@ -141,16 +141,16 @@ DEFAULT_SU_FLAGS = get_config(p, DEFAULTS, 'su_flags', 'ANSIBLE_SU_FLAG
DEFAULT_SU_USER = get_config(p, DEFAULTS, 'su_user', 'ANSIBLE_SU_USER', 'root')
DEFAULT_ASK_SU_PASS = get_config(p, DEFAULTS, 'ask_su_pass', 'ANSIBLE_ASK_SU_PASS', False, boolean=True)
DEFAULT_GATHERING = get_config(p, DEFAULTS, 'gathering', 'ANSIBLE_GATHERING', 'implicit').lower()
DEFAULT_ACTION_PLUGIN_PATH = get_config(p, DEFAULTS, 'action_plugins', 'ANSIBLE_ACTION_PLUGINS', '/usr/share/ansible_plugins/action_plugins')
DEFAULT_CACHE_PLUGIN_PATH = get_config(p, DEFAULTS, 'cache_plugins', 'ANSIBLE_CACHE_PLUGINS', '/usr/share/ansible_plugins/cache_plugins')
DEFAULT_CALLBACK_PLUGIN_PATH = get_config(p, DEFAULTS, 'callback_plugins', 'ANSIBLE_CALLBACK_PLUGINS', '/usr/share/ansible_plugins/callback_plugins')
DEFAULT_CONNECTION_PLUGIN_PATH = get_config(p, DEFAULTS, 'connection_plugins', 'ANSIBLE_CONNECTION_PLUGINS', '/usr/share/ansible_plugins/connection_plugins')
DEFAULT_LOOKUP_PLUGIN_PATH = get_config(p, DEFAULTS, 'lookup_plugins', 'ANSIBLE_LOOKUP_PLUGINS', '/usr/share/ansible_plugins/lookup_plugins')
DEFAULT_VARS_PLUGIN_PATH = get_config(p, DEFAULTS, 'vars_plugins', 'ANSIBLE_VARS_PLUGINS', '/usr/share/ansible_plugins/vars_plugins')
DEFAULT_FILTER_PLUGIN_PATH = get_config(p, DEFAULTS, 'filter_plugins', 'ANSIBLE_FILTER_PLUGINS', '/usr/share/ansible_plugins/filter_plugins')
DEFAULT_LOG_PATH = shell_expand_path(get_config(p, DEFAULTS, 'log_path', 'ANSIBLE_LOG_PATH', ''))
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')
DEFAULT_CONNECTION_PLUGIN_PATH = get_config(p, DEFAULTS, 'connection_plugins', 'ANSIBLE_CONNECTION_PLUGINS', '~/.ansible/plugins/connection_plugins:/usr/share/ansible_plugins/connection_plugins')
DEFAULT_LOOKUP_PLUGIN_PATH = get_config(p, DEFAULTS, 'lookup_plugins', 'ANSIBLE_LOOKUP_PLUGINS', '~/.ansible/plugins/lookup_plugins:/usr/share/ansible_plugins/lookup_plugins')
DEFAULT_VARS_PLUGIN_PATH = get_config(p, DEFAULTS, 'vars_plugins', 'ANSIBLE_VARS_PLUGINS', '~/.ansible/plugins/vars_plugins:/usr/share/ansible_plugins/vars_plugins')
DEFAULT_FILTER_PLUGIN_PATH = get_config(p, DEFAULTS, 'filter_plugins', 'ANSIBLE_FILTER_PLUGINS', '~/.ansible/plugins/filter_plugins:/usr/share/ansible_plugins/filter_plugins')
CACHE_PLUGIN = get_config(p, DEFAULTS, 'fact_caching', 'ANSIBLE_CACHE_PLUGIN', 'memory')
CACHE_PLUGIN_CONNECTION = get_config(p, DEFAULTS, 'fact_caching_connection', 'ANSIBLE_CACHE_PLUGIN_CONNECTION', None)
CACHE_PLUGIN_PREFIX = get_config(p, DEFAULTS, 'fact_caching_prefix', 'ANSIBLE_CACHE_PLUGIN_PREFIX', 'ansible_facts')
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment