Merge pull request #9670 from ryanpetrello/burst-your-bubble

remove unnecessary references to bwrap, bubblewrap, and proot

Reviewed-by: Jake McDermott <yo@jakemcdermott.me>
Reviewed-by: Ryan Petrello <None>
Reviewed-by: Bill Nottingham <None>
Reviewed-by: Shane McDonald <me@shanemcd.com>
This commit is contained in:
softwarefactory-project-zuul[bot]
2021-03-25 13:55:22 +00:00
committed by GitHub
28 changed files with 76 additions and 341 deletions

View File

@@ -0,0 +1,16 @@
# -*- coding: utf-8 -*-
from __future__ import unicode_literals
from django.db import migrations
from awx.conf.migrations import _rename_setting
def rename_proot_settings(apps, schema_editor):
_rename_setting.rename_setting(apps, schema_editor, old_key='AWX_PROOT_BASE_PATH', new_key='AWX_ISOLATION_BASE_PATH')
_rename_setting.rename_setting(apps, schema_editor, old_key='AWX_PROOT_SHOW_PATHS', new_key='AWX_ISOLATION_SHOW_PATHS')
class Migration(migrations.Migration):
dependencies = [('conf', '0008_subscriptions')]
operations = [migrations.RunPython(rename_proot_settings)]

View File

@@ -233,16 +233,7 @@ register(
) )
register( register(
'AWX_PROOT_ENABLED', 'AWX_ISOLATION_BASE_PATH',
field_class=fields.BooleanField,
label=_('Enable job isolation'),
help_text=_('Isolates an Ansible job from protected parts of the system to prevent exposing sensitive information.'),
category=_('Jobs'),
category_slug='jobs',
)
register(
'AWX_PROOT_BASE_PATH',
field_class=fields.CharField, field_class=fields.CharField,
label=_('Job execution path'), label=_('Job execution path'),
help_text=_( help_text=_(
@@ -255,17 +246,7 @@ register(
) )
register( register(
'AWX_PROOT_HIDE_PATHS', 'AWX_ISOLATION_SHOW_PATHS',
field_class=fields.StringListField,
required=False,
label=_('Paths to hide from isolated jobs'),
help_text=_('Additional paths to hide from isolated processes. Enter one path per line.'),
category=_('Jobs'),
category_slug='jobs',
)
register(
'AWX_PROOT_SHOW_PATHS',
field_class=fields.StringListField, field_class=fields.StringListField,
required=False, required=False,
label=_('Paths to expose to isolated jobs'), label=_('Paths to expose to isolated jobs'),

View File

@@ -52,7 +52,6 @@ ENV_BLOCKLIST = frozenset(
'VIRTUAL_ENV', 'VIRTUAL_ENV',
'PATH', 'PATH',
'PYTHONPATH', 'PYTHONPATH',
'PROOT_TMP_DIR',
'JOB_ID', 'JOB_ID',
'INVENTORY_ID', 'INVENTORY_ID',
'INVENTORY_SOURCE_ID', 'INVENTORY_SOURCE_ID',

View File

@@ -135,7 +135,7 @@ class IsolatedManager(object):
extravars = { extravars = {
'src': self.private_data_dir, 'src': self.private_data_dir,
'dest': settings.AWX_PROOT_BASE_PATH, 'dest': settings.AWX_ISOLATION_BASE_PATH,
'ident': self.ident, 'ident': self.ident,
'job_id': self.instance.id, 'job_id': self.instance.id,
} }
@@ -304,7 +304,7 @@ class IsolatedManager(object):
if not len(instance_qs): if not len(instance_qs):
return return
try: try:
private_data_dir = tempfile.mkdtemp(prefix='awx_iso_heartbeat_', dir=settings.AWX_PROOT_BASE_PATH) private_data_dir = tempfile.mkdtemp(prefix='awx_iso_heartbeat_', dir=settings.AWX_ISOLATION_BASE_PATH)
self.runner_params = self.build_runner_params([instance.hostname for instance in instance_qs]) self.runner_params = self.build_runner_params([instance.hostname for instance in instance_qs])
self.runner_params['private_data_dir'] = private_data_dir self.runner_params['private_data_dir'] = private_data_dir
self.runner_params['forks'] = len(instance_qs) self.runner_params['forks'] = len(instance_qs)

View File

@@ -69,8 +69,6 @@ class AnsibleInventoryLoader(object):
def __init__(self, source, venv_path=None, verbosity=0): def __init__(self, source, venv_path=None, verbosity=0):
self.source = source self.source = source
self.verbosity = verbosity self.verbosity = verbosity
# TODO: remove once proot has been removed
self.tmp_private_dir = None
if venv_path: if venv_path:
self.venv_path = venv_path self.venv_path = venv_path
else: else:

View File

@@ -25,7 +25,7 @@ class Command(BaseCommand):
raise CommandError("--hostname is a required argument") raise CommandError("--hostname is a required argument")
try: try:
path = tempfile.mkdtemp(prefix='awx_isolated_ssh', dir=settings.AWX_PROOT_BASE_PATH) path = tempfile.mkdtemp(prefix='awx_isolated_ssh', dir=settings.AWX_ISOLATION_BASE_PATH)
ssh_key = None ssh_key = None
if all([getattr(settings, 'AWX_ISOLATED_KEY_GENERATION', False) is True, getattr(settings, 'AWX_ISOLATED_PRIVATE_KEY', None)]): if all([getattr(settings, 'AWX_ISOLATED_KEY_GENERATION', False) is True, getattr(settings, 'AWX_ISOLATED_PRIVATE_KEY', None)]):
ssh_key = settings.AWX_ISOLATED_PRIVATE_KEY ssh_key = settings.AWX_ISOLATED_PRIVATE_KEY

View File

@@ -841,7 +841,6 @@ class BaseTask(object):
model = None model = None
event_model = None event_model = None
abstract = True abstract = True
proot_show_paths = []
def __init__(self): def __init__(self):
self.cleanup_paths = [] self.cleanup_paths = []
@@ -908,9 +907,9 @@ class BaseTask(object):
if pull: if pull:
params['container_options'].append(f'--pull={pull}') params['container_options'].append(f'--pull={pull}')
if settings.AWX_PROOT_SHOW_PATHS: if settings.AWX_ISOLATION_SHOW_PATHS:
params['container_volume_mounts'] = [] params['container_volume_mounts'] = []
for this_path in settings.AWX_PROOT_SHOW_PATHS: for this_path in settings.AWX_ISOLATION_SHOW_PATHS:
params['container_volume_mounts'].append(f'{this_path}:{this_path}:Z') params['container_volume_mounts'].append(f'{this_path}:{this_path}:Z')
return params return params
@@ -924,7 +923,7 @@ class BaseTask(object):
""" """
Create a temporary directory for job-related files. Create a temporary directory for job-related files.
""" """
pdd_wrapper_path = tempfile.mkdtemp(prefix=f'pdd_wrapper_{instance.pk}_', dir=settings.AWX_PROOT_BASE_PATH) pdd_wrapper_path = tempfile.mkdtemp(prefix=f'pdd_wrapper_{instance.pk}_', dir=settings.AWX_ISOLATION_BASE_PATH)
os.chmod(pdd_wrapper_path, stat.S_IRUSR | stat.S_IWUSR | stat.S_IXUSR) os.chmod(pdd_wrapper_path, stat.S_IRUSR | stat.S_IWUSR | stat.S_IXUSR)
if settings.AWX_CLEANUP_PATHS: if settings.AWX_CLEANUP_PATHS:
self.cleanup_paths.append(pdd_wrapper_path) self.cleanup_paths.append(pdd_wrapper_path)
@@ -1090,12 +1089,6 @@ class BaseTask(object):
""" """
return False return False
def should_use_proot(self, instance):
"""
Return whether this task should use proot.
"""
return False
def build_inventory(self, instance, private_data_dir): def build_inventory(self, instance, private_data_dir):
script_params = dict(hostvars=True, towervars=True) script_params = dict(hostvars=True, towervars=True)
if hasattr(instance, 'job_slice_number'): if hasattr(instance, 'job_slice_number'):
@@ -1371,8 +1364,8 @@ class BaseTask(object):
status = self.instance.status status = self.instance.status
raise RuntimeError('not starting %s task' % self.instance.status) raise RuntimeError('not starting %s task' % self.instance.status)
if not os.path.exists(settings.AWX_PROOT_BASE_PATH): if not os.path.exists(settings.AWX_ISOLATION_BASE_PATH):
raise RuntimeError('AWX_PROOT_BASE_PATH=%s does not exist' % settings.AWX_PROOT_BASE_PATH) raise RuntimeError('AWX_ISOLATION_BASE_PATH=%s does not exist' % settings.AWX_ISOLATION_BASE_PATH)
# store a record of the venv used at runtime # store a record of the venv used at runtime
if hasattr(self.instance, 'custom_virtualenv'): if hasattr(self.instance, 'custom_virtualenv'):
@@ -1598,8 +1591,7 @@ class RunJob(BaseTask):
env['ANSIBLE_CALLBACK_PLUGINS'] = ':'.join(settings.AWX_ANSIBLE_CALLBACK_PLUGINS) env['ANSIBLE_CALLBACK_PLUGINS'] = ':'.join(settings.AWX_ANSIBLE_CALLBACK_PLUGINS)
env['AWX_HOST'] = settings.TOWER_URL_BASE env['AWX_HOST'] = settings.TOWER_URL_BASE
# Create a directory for ControlPath sockets that is unique to each # Create a directory for ControlPath sockets that is unique to each job
# job and visible inside the proot environment (when enabled).
cp_dir = os.path.join(private_data_dir, 'cp') cp_dir = os.path.join(private_data_dir, 'cp')
if not os.path.exists(cp_dir): if not os.path.exists(cp_dir):
os.mkdir(cp_dir, 0o700) os.mkdir(cp_dir, 0o700)
@@ -1768,14 +1760,6 @@ class RunJob(BaseTask):
""" """
return settings.AWX_RESOURCE_PROFILING_ENABLED return settings.AWX_RESOURCE_PROFILING_ENABLED
def should_use_proot(self, job):
"""
Return whether this task should use proot.
"""
if job.is_container_group_task:
return False
return getattr(settings, 'AWX_PROOT_ENABLED', False)
def build_execution_environment_params(self, instance): def build_execution_environment_params(self, instance):
if settings.IS_K8S: if settings.IS_K8S:
return {} return {}
@@ -1929,10 +1913,6 @@ class RunProjectUpdate(BaseTask):
event_model = ProjectUpdateEvent event_model = ProjectUpdateEvent
event_data_key = 'project_update_id' event_data_key = 'project_update_id'
@property
def proot_show_paths(self):
return [settings.PROJECTS_ROOT]
def __init__(self, *args, job_private_data_dir=None, **kwargs): def __init__(self, *args, job_private_data_dir=None, **kwargs):
super(RunProjectUpdate, self).__init__(*args, **kwargs) super(RunProjectUpdate, self).__init__(*args, **kwargs)
self.playbook_new_revision = None self.playbook_new_revision = None
@@ -1990,7 +1970,7 @@ class RunProjectUpdate(BaseTask):
env['DISPLAY'] = '' # Prevent stupid password popup when running tests. env['DISPLAY'] = '' # Prevent stupid password popup when running tests.
# give ansible a hint about the intended tmpdir to work around issues # give ansible a hint about the intended tmpdir to work around issues
# like https://github.com/ansible/ansible/issues/30064 # like https://github.com/ansible/ansible/issues/30064
env['TMP'] = settings.AWX_PROOT_BASE_PATH env['TMP'] = settings.AWX_ISOLATION_BASE_PATH
env['PROJECT_UPDATE_ID'] = str(project_update.pk) env['PROJECT_UPDATE_ID'] = str(project_update.pk)
if settings.GALAXY_IGNORE_CERTS: if settings.GALAXY_IGNORE_CERTS:
env['ANSIBLE_GALAXY_IGNORE'] = True env['ANSIBLE_GALAXY_IGNORE'] = True
@@ -2394,12 +2374,6 @@ class RunProjectUpdate(BaseTask):
if status == 'successful' and instance.launch_type != 'sync': if status == 'successful' and instance.launch_type != 'sync':
self._update_dependent_inventories(instance, dependent_inventory_sources) self._update_dependent_inventories(instance, dependent_inventory_sources)
def should_use_proot(self, project_update):
"""
Return whether this task should use proot.
"""
return getattr(settings, 'AWX_PROOT_ENABLED', False)
def build_execution_environment_params(self, instance): def build_execution_environment_params(self, instance):
if settings.IS_K8S: if settings.IS_K8S:
return {} return {}
@@ -2790,7 +2764,7 @@ class RunAdHocCommand(BaseTask):
env['ANSIBLE_SFTP_BATCH_MODE'] = 'False' env['ANSIBLE_SFTP_BATCH_MODE'] = 'False'
# Create a directory for ControlPath sockets that is unique to each # Create a directory for ControlPath sockets that is unique to each
# ad hoc command and visible inside the proot environment (when enabled). # ad hoc command
cp_dir = os.path.join(private_data_dir, 'cp') cp_dir = os.path.join(private_data_dir, 'cp')
if not os.path.exists(cp_dir): if not os.path.exists(cp_dir):
os.mkdir(cp_dir, 0o700) os.mkdir(cp_dir, 0o700)
@@ -2894,14 +2868,6 @@ class RunAdHocCommand(BaseTask):
d[r'Password:\s*?$'] = 'ssh_password' d[r'Password:\s*?$'] = 'ssh_password'
return d return d
def should_use_proot(self, ad_hoc_command):
"""
Return whether this task should use proot.
"""
if ad_hoc_command.is_container_group_task:
return False
return getattr(settings, 'AWX_PROOT_ENABLED', False)
def final_run_hook(self, adhoc_job, status, private_data_dir, fact_modification_times, isolated_manager_instance=None): def final_run_hook(self, adhoc_job, status, private_data_dir, fact_modification_times, isolated_manager_instance=None):
super(RunAdHocCommand, self).final_run_hook(adhoc_job, status, private_data_dir, fact_modification_times) super(RunAdHocCommand, self).final_run_hook(adhoc_job, status, private_data_dir, fact_modification_times)
if isolated_manager_instance: if isolated_manager_instance:

View File

@@ -33,16 +33,14 @@ def test_jobs_settings(get, put, patch, delete, admin):
response = get(url, user=admin, expect=200) response = get(url, user=admin, expect=200)
data = dict(response.data.items()) data = dict(response.data.items())
put(url, user=admin, data=data, expect=200) put(url, user=admin, data=data, expect=200)
patch(url, user=admin, data={'AWX_PROOT_HIDE_PATHS': ['/home']}, expect=200) patch(url, user=admin, data={'AWX_ISOLATION_SHOW_PATHS': ['/home']}, expect=200)
response = get(url, user=admin, expect=200) response = get(url, user=admin, expect=200)
assert response.data['AWX_PROOT_HIDE_PATHS'] == ['/home'] assert response.data['AWX_ISOLATION_SHOW_PATHS'] == ['/home']
data.pop('AWX_PROOT_HIDE_PATHS') data.pop('AWX_ISOLATION_SHOW_PATHS')
data.pop('AWX_PROOT_SHOW_PATHS')
data.pop('AWX_ANSIBLE_CALLBACK_PLUGINS') data.pop('AWX_ANSIBLE_CALLBACK_PLUGINS')
put(url, user=admin, data=data, expect=200) put(url, user=admin, data=data, expect=200)
response = get(url, user=admin, expect=200) response = get(url, user=admin, expect=200)
assert response.data['AWX_PROOT_HIDE_PATHS'] == [] assert response.data['AWX_ISOLATION_SHOW_PATHS'] == []
assert response.data['AWX_PROOT_SHOW_PATHS'] == []
assert response.data['AWX_ANSIBLE_CALLBACK_PLUGINS'] == [] assert response.data['AWX_ANSIBLE_CALLBACK_PLUGINS'] == []

View File

@@ -725,7 +725,6 @@ class TestIsolatedExecution(TestJobExecution):
extra_vars = json.loads(extra_vars) extra_vars = json.loads(extra_vars)
assert extra_vars['dest'] == '/tmp' assert extra_vars['dest'] == '/tmp'
assert extra_vars['src'] == private_data assert extra_vars['src'] == private_data
assert extra_vars['proot_temp_dir'].startswith('/tmp/awx_proot_')
def test_systemctl_failure(self): def test_systemctl_failure(self):
# If systemctl fails, read the contents of `artifacts/systemctl_logs` # If systemctl fails, read the contents of `artifacts/systemctl_logs`

View File

@@ -69,9 +69,6 @@ __all__ = [
'get_system_task_capacity', 'get_system_task_capacity',
'get_cpu_capacity', 'get_cpu_capacity',
'get_mem_capacity', 'get_mem_capacity',
'wrap_args_with_proot',
'build_proot_temp_dir',
'check_proot_installed',
'model_to_dict', 'model_to_dict',
'NullablePromptPseudoField', 'NullablePromptPseudoField',
'model_instance_diff', 'model_instance_diff',
@@ -842,94 +839,6 @@ def set_environ(**environ):
os.environ.update(old_environ) os.environ.update(old_environ)
@memoize()
def check_proot_installed():
"""
Check that proot is installed.
"""
from django.conf import settings
cmd = [getattr(settings, 'AWX_PROOT_CMD', 'bwrap'), '--version']
try:
proc = subprocess.Popen(cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
proc.communicate()
return bool(proc.returncode == 0)
except (OSError, ValueError) as e:
if isinstance(e, ValueError) or getattr(e, 'errno', 1) != 2: # ENOENT, no such file or directory
logger.exception('bwrap unavailable for unexpected reason.')
return False
def build_proot_temp_dir():
"""
Create a temporary directory for proot to use.
"""
from django.conf import settings
path = tempfile.mkdtemp(prefix='awx_proot_', dir=settings.AWX_PROOT_BASE_PATH)
os.chmod(path, stat.S_IRUSR | stat.S_IWUSR | stat.S_IXUSR)
return path
def wrap_args_with_proot(args, cwd, **kwargs):
"""
Wrap existing command line with proot to restrict access to:
- AWX_PROOT_BASE_PATH (generally, /tmp) (except for own /tmp files)
For non-isolated nodes:
- /etc/tower (to prevent obtaining db info or secret key)
- /var/lib/awx (except for current project)
- /var/log/tower
- /var/log/supervisor
"""
from django.conf import settings
cwd = os.path.realpath(cwd)
new_args = [getattr(settings, 'AWX_PROOT_CMD', 'bwrap'), '--unshare-pid', '--dev-bind', '/', '/', '--proc', '/proc']
hide_paths = [settings.AWX_PROOT_BASE_PATH]
if not kwargs.get('isolated'):
hide_paths.extend(['/etc/tower', '/var/lib/awx', '/var/log', '/etc/ssh', settings.PROJECTS_ROOT, settings.JOBOUTPUT_ROOT])
hide_paths.extend(getattr(settings, 'AWX_PROOT_HIDE_PATHS', None) or [])
for path in sorted(set(hide_paths)):
if not os.path.exists(path):
continue
path = os.path.realpath(path)
if os.path.isdir(path):
new_path = tempfile.mkdtemp(dir=kwargs['proot_temp_dir'])
os.chmod(new_path, stat.S_IRUSR | stat.S_IWUSR | stat.S_IXUSR)
else:
handle, new_path = tempfile.mkstemp(dir=kwargs['proot_temp_dir'])
os.close(handle)
os.chmod(new_path, stat.S_IRUSR | stat.S_IWUSR)
new_args.extend(['--bind', '%s' % (new_path,), '%s' % (path,)])
if kwargs.get('isolated'):
show_paths = [kwargs['private_data_dir']]
elif 'private_data_dir' in kwargs:
show_paths = [cwd, kwargs['private_data_dir']]
else:
show_paths = [cwd]
for venv in (settings.ANSIBLE_VENV_PATH, settings.AWX_VENV_PATH, kwargs.get('proot_custom_virtualenv')):
if venv:
new_args.extend(['--ro-bind', venv, venv])
show_paths.extend(getattr(settings, 'AWX_PROOT_SHOW_PATHS', None) or [])
show_paths.extend(kwargs.get('proot_show_paths', []))
for path in sorted(set(show_paths)):
if not os.path.exists(path):
continue
path = os.path.realpath(path)
new_args.extend(['--bind', '%s' % (path,), '%s' % (path,)])
if kwargs.get('isolated'):
if '/bin/ansible-playbook' in ' '.join(args):
# playbook runs should cwd to the SCM checkout dir
new_args.extend(['--chdir', os.path.join(kwargs['private_data_dir'], 'project')])
else:
# ad-hoc runs should cwd to the root of the private data dir
new_args.extend(['--chdir', kwargs['private_data_dir']])
else:
new_args.extend(['--chdir', cwd])
new_args.extend(args)
return new_args
def get_pk_from_dict(_dict, key): def get_pk_from_dict(_dict, key):
""" """
Helper for obtaining a pk from user data dict or None if not present. Helper for obtaining a pk from user data dict or None if not present.

View File

@@ -569,26 +569,15 @@ AWX_SHOW_PLAYBOOK_LINKS = False
# Applies to any galaxy server # Applies to any galaxy server
GALAXY_IGNORE_CERTS = False GALAXY_IGNORE_CERTS = False
# Enable bubblewrap support for running jobs (playbook runs only). # Additional paths to show for jobs using process isolation.
# Note: This setting may be overridden by database settings. # Note: This setting may be overridden by database settings.
AWX_PROOT_ENABLED = True AWX_ISOLATION_SHOW_PATHS = []
# Command/path to bubblewrap.
AWX_PROOT_CMD = 'bwrap'
# Additional paths to hide from jobs using bubblewrap.
# Note: This setting may be overridden by database settings.
AWX_PROOT_HIDE_PATHS = []
# Additional paths to show for jobs using bubbelwrap.
# Note: This setting may be overridden by database settings.
AWX_PROOT_SHOW_PATHS = []
# The directory in which Tower will create new temporary directories for job # The directory in which Tower will create new temporary directories for job
# execution and isolation (such as credential files and custom # execution and isolation (such as credential files and custom
# inventory scripts). # inventory scripts).
# Note: This setting may be overridden by database settings. # Note: This setting may be overridden by database settings.
AWX_PROOT_BASE_PATH = "/tmp" AWX_ISOLATION_BASE_PATH = "/tmp"
# Disable resource profiling by default # Disable resource profiling by default
AWX_RESOURCE_PROFILING_ENABLED = False AWX_RESOURCE_PROFILING_ENABLED = False

View File

@@ -67,10 +67,6 @@ CALLBACK_QUEUE = "callback_tasks"
# Note: This setting may be overridden by database settings. # Note: This setting may be overridden by database settings.
AWX_ROLES_ENABLED = True AWX_ROLES_ENABLED = True
# Enable PROOT for tower-qa integration tests.
# Note: This setting may be overridden by database settings.
AWX_PROOT_ENABLED = True
AWX_ISOLATED_USERNAME = 'root' AWX_ISOLATED_USERNAME = 'root'
AWX_ISOLATED_CHECK_INTERVAL = 1 AWX_ISOLATED_CHECK_INTERVAL = 1
AWX_ISOLATED_PERIODIC_CHECK = 30 AWX_ISOLATED_PERIODIC_CHECK = 30

View File

@@ -101,7 +101,6 @@
"VIRTUAL_ENV": "/var/lib/awx/venv/ansible", "VIRTUAL_ENV": "/var/lib/awx/venv/ansible",
"INVENTORY_ID": "1", "INVENTORY_ID": "1",
"MAX_EVENT_RES": "700000", "MAX_EVENT_RES": "700000",
"PROOT_TMP_DIR": "/tmp",
"ANSIBLE_LIBRARY": "/awx_devel/awx/plugins/library", "ANSIBLE_LIBRARY": "/awx_devel/awx/plugins/library",
"SDB_NOTIFY_HOST": "docker.for.mac.host.internal", "SDB_NOTIFY_HOST": "docker.for.mac.host.internal",
"AWX_GROUP_QUEUES": "tower", "AWX_GROUP_QUEUES": "tower",

View File

@@ -101,7 +101,6 @@
"VIRTUAL_ENV": "/var/lib/awx/venv/ansible", "VIRTUAL_ENV": "/var/lib/awx/venv/ansible",
"INVENTORY_ID": "1", "INVENTORY_ID": "1",
"MAX_EVENT_RES": "700000", "MAX_EVENT_RES": "700000",
"PROOT_TMP_DIR": "/tmp",
"ANSIBLE_LIBRARY": "/awx_devel/awx/plugins/library", "ANSIBLE_LIBRARY": "/awx_devel/awx/plugins/library",
"SDB_NOTIFY_HOST": "docker.for.mac.host.internal", "SDB_NOTIFY_HOST": "docker.for.mac.host.internal",
"AWX_GROUP_QUEUES": "tower", "AWX_GROUP_QUEUES": "tower",

View File

@@ -154,7 +154,6 @@
"ANSIBLE_INVENTORY_UNPARSED_FAILED": "True", "ANSIBLE_INVENTORY_UNPARSED_FAILED": "True",
"ANSIBLE_PARAMIKO_RECORD_HOST_KEYS": "False", "ANSIBLE_PARAMIKO_RECORD_HOST_KEYS": "False",
"ANSIBLE_VENV_PATH": "/var/lib/awx/venv/ansible", "ANSIBLE_VENV_PATH": "/var/lib/awx/venv/ansible",
"PROOT_TMP_DIR": "/tmp",
"AWX_PRIVATE_DATA_DIR": "/tmp/awx_2_a4b1afiw", "AWX_PRIVATE_DATA_DIR": "/tmp/awx_2_a4b1afiw",
"ANSIBLE_COLLECTIONS_PATHS": "/tmp/collections", "ANSIBLE_COLLECTIONS_PATHS": "/tmp/collections",
"PYTHONPATH": "/var/lib/awx/venv/ansible/lib/python2.7/site-packages:/awx_devel/awx/lib:", "PYTHONPATH": "/var/lib/awx/venv/ansible/lib/python2.7/site-packages:/awx_devel/awx/lib:",

View File

@@ -50,7 +50,6 @@ describe('<JobsDetail />', () => {
}); });
test('should render expected details', () => { test('should render expected details', () => {
assertDetail(wrapper, 'Enable job isolation', 'On');
assertDetail(wrapper, 'Job execution path', '/tmp'); assertDetail(wrapper, 'Job execution path', '/tmp');
assertDetail(wrapper, 'Isolated status check interval', '1 seconds'); assertDetail(wrapper, 'Isolated status check interval', '1 seconds');
assertDetail(wrapper, 'Isolated launch timeout', '600 seconds'); assertDetail(wrapper, 'Isolated launch timeout', '600 seconds');
@@ -81,7 +80,6 @@ describe('<JobsDetail />', () => {
'Ansible Modules Allowed for Ad Hoc Jobs', 'Ansible Modules Allowed for Ad Hoc Jobs',
'[\n "command"\n]' '[\n "command"\n]'
); );
assertVariableDetail(wrapper, 'Paths to hide from isolated jobs', '[]');
assertVariableDetail(wrapper, 'Paths to expose to isolated jobs', '[]'); assertVariableDetail(wrapper, 'Paths to expose to isolated jobs', '[]');
assertVariableDetail(wrapper, 'Extra Environment Variables', '{}'); assertVariableDetail(wrapper, 'Extra Environment Variables', '{}');
assertVariableDetail(wrapper, 'Ansible Callback Plugins', '[]'); assertVariableDetail(wrapper, 'Ansible Callback Plugins', '[]');

View File

@@ -70,8 +70,7 @@ function JobsEdit() {
await submitForm({ await submitForm({
...form, ...form,
AD_HOC_COMMANDS: formatJson(form.AD_HOC_COMMANDS), AD_HOC_COMMANDS: formatJson(form.AD_HOC_COMMANDS),
AWX_PROOT_SHOW_PATHS: formatJson(form.AWX_PROOT_SHOW_PATHS), AWX_ISOLATION_SHOW_PATHS: formatJson(form.AWX_ISOLATION_SHOW_PATHS),
AWX_PROOT_HIDE_PATHS: formatJson(form.AWX_PROOT_HIDE_PATHS),
AWX_ANSIBLE_CALLBACK_PLUGINS: formatJson( AWX_ANSIBLE_CALLBACK_PLUGINS: formatJson(
form.AWX_ANSIBLE_CALLBACK_PLUGINS form.AWX_ANSIBLE_CALLBACK_PLUGINS
), ),
@@ -116,8 +115,8 @@ function JobsEdit() {
<Form autoComplete="off" onSubmit={formik.handleSubmit}> <Form autoComplete="off" onSubmit={formik.handleSubmit}>
<FormColumnLayout> <FormColumnLayout>
<InputField <InputField
name="AWX_PROOT_BASE_PATH" name="AWX_ISOLATION_BASE_PATH"
config={jobs.AWX_PROOT_BASE_PATH} config={jobs.AWX_ISOLATION_BASE_PATH}
isRequired isRequired
/> />
<InputField <InputField
@@ -151,10 +150,6 @@ function JobsEdit() {
config={jobs.MAX_FORKS} config={jobs.MAX_FORKS}
type="number" type="number"
/> />
<BooleanField
name="AWX_PROOT_ENABLED"
config={jobs.AWX_PROOT_ENABLED}
/>
<BooleanField <BooleanField
name="PROJECT_UPDATE_VVV" name="PROJECT_UPDATE_VVV"
config={jobs.PROJECT_UPDATE_VVV} config={jobs.PROJECT_UPDATE_VVV}
@@ -209,12 +204,8 @@ function JobsEdit() {
config={jobs.AWX_ANSIBLE_CALLBACK_PLUGINS} config={jobs.AWX_ANSIBLE_CALLBACK_PLUGINS}
/> />
<ObjectField <ObjectField
name="AWX_PROOT_SHOW_PATHS" name="AWX_ISOLATION_SHOW_PATHS"
config={jobs.AWX_PROOT_SHOW_PATHS} config={jobs.AWX_ISOLATION_SHOW_PATHS}
/>
<ObjectField
name="AWX_PROOT_HIDE_PATHS"
config={jobs.AWX_PROOT_HIDE_PATHS}
/> />
<ObjectField name="AWX_TASK_ENV" config={jobs.AWX_TASK_ENV} /> <ObjectField name="AWX_TASK_ENV" config={jobs.AWX_TASK_ENV} />
{submitError && <FormSubmitError error={submitError} />} {submitError && <FormSubmitError error={submitError} />}

View File

@@ -27,10 +27,8 @@
"AWX_ISOLATED_CONNECTION_TIMEOUT": 10, "AWX_ISOLATED_CONNECTION_TIMEOUT": 10,
"AWX_ISOLATED_HOST_KEY_CHECKING": false, "AWX_ISOLATED_HOST_KEY_CHECKING": false,
"AWX_ISOLATED_LAUNCH_TIMEOUT": 600, "AWX_ISOLATED_LAUNCH_TIMEOUT": 600,
"AWX_PROOT_BASE_PATH": "/tmp", "AWX_ISOLATION_BASE_PATH": "/tmp",
"AWX_PROOT_ENABLED": true, "AWX_ISOLATION_SHOW_PATHS": [],
"AWX_PROOT_HIDE_PATHS": [],
"AWX_PROOT_SHOW_PATHS": [],
"AWX_RESOURCE_PROFILING_CPU_POLL_INTERVAL": 0.25, "AWX_RESOURCE_PROFILING_CPU_POLL_INTERVAL": 0.25,
"AWX_RESOURCE_PROFILING_ENABLED": false, "AWX_RESOURCE_PROFILING_ENABLED": false,
"AWX_RESOURCE_PROFILING_MEMORY_POLL_INTERVAL": 0.25, "AWX_RESOURCE_PROFILING_MEMORY_POLL_INTERVAL": 0.25,
@@ -45,4 +43,4 @@
"MAX_FORKS": 200, "MAX_FORKS": 200,
"PROJECT_UPDATE_VVV": false, "PROJECT_UPDATE_VVV": false,
"SCHEDULE_MAX_JOBS": 10 "SCHEDULE_MAX_JOBS": 10
} }

View File

@@ -166,15 +166,7 @@
] ]
] ]
}, },
"AWX_PROOT_ENABLED": { "AWX_ISOLATION_BASE_PATH": {
"type": "boolean",
"label": "Enable job isolation",
"help_text": "Isolates an Ansible job from protected parts of the system to prevent exposing sensitive information.",
"category": "Jobs",
"category_slug": "jobs",
"defined_in_file": false
},
"AWX_PROOT_BASE_PATH": {
"type": "string", "type": "string",
"label": "Job execution path", "label": "Job execution path",
"help_text": "The directory in which Tower will create new temporary directories for job execution and isolation (such as credential files and custom inventory scripts).", "help_text": "The directory in which Tower will create new temporary directories for job execution and isolation (such as credential files and custom inventory scripts).",
@@ -182,18 +174,7 @@
"category_slug": "jobs", "category_slug": "jobs",
"defined_in_file": false "defined_in_file": false
}, },
"AWX_PROOT_HIDE_PATHS": { "AWX_ISOLATION_SHOW_PATHS": {
"type": "list",
"label": "Paths to hide from isolated jobs",
"help_text": "Additional paths to hide from isolated processes. Enter one path per line.",
"category": "Jobs",
"category_slug": "jobs",
"defined_in_file": false,
"child": {
"type": "string"
}
},
"AWX_PROOT_SHOW_PATHS": {
"type": "list", "type": "list",
"label": "Paths to expose to isolated jobs", "label": "Paths to expose to isolated jobs",
"help_text": "List of paths that would otherwise be hidden to expose to isolated jobs. Enter one path per line.", "help_text": "List of paths that would otherwise be hidden to expose to isolated jobs. Enter one path per line.",
@@ -3158,16 +3139,7 @@
] ]
] ]
}, },
"AWX_PROOT_ENABLED": { "AWX_ISOLATION_BASE_PATH": {
"type": "boolean",
"required": true,
"label": "Enable job isolation",
"help_text": "Isolates an Ansible job from protected parts of the system to prevent exposing sensitive information.",
"category": "Jobs",
"category_slug": "jobs",
"default": true
},
"AWX_PROOT_BASE_PATH": {
"type": "string", "type": "string",
"required": true, "required": true,
"label": "Job execution path", "label": "Job execution path",
@@ -3176,21 +3148,7 @@
"category_slug": "jobs", "category_slug": "jobs",
"default": "/tmp" "default": "/tmp"
}, },
"AWX_PROOT_HIDE_PATHS": { "AWX_ISOLATION_SHOW_PATHS": {
"type": "list",
"required": false,
"label": "Paths to hide from isolated jobs",
"help_text": "Additional paths to hide from isolated processes. Enter one path per line.",
"category": "Jobs",
"category_slug": "jobs",
"default": [],
"child": {
"type": "string",
"required": true,
"read_only": false
}
},
"AWX_PROOT_SHOW_PATHS": {
"type": "list", "type": "list",
"required": false, "required": false,
"label": "Paths to expose to isolated jobs", "label": "Paths to expose to isolated jobs",

View File

@@ -34,10 +34,8 @@
"win_user" "win_user"
], ],
"ALLOW_JINJA_IN_EXTRA_VARS":"template", "ALLOW_JINJA_IN_EXTRA_VARS":"template",
"AWX_PROOT_ENABLED":true, "AWX_ISOLATION_BASE_PATH":"/tmp",
"AWX_PROOT_BASE_PATH":"/tmp", "AWX_ISOLATION_SHOW_PATHS":[],
"AWX_PROOT_HIDE_PATHS":[],
"AWX_PROOT_SHOW_PATHS":[],
"AWX_ISOLATED_CHECK_INTERVAL":1, "AWX_ISOLATED_CHECK_INTERVAL":1,
"AWX_ISOLATED_LAUNCH_TIMEOUT":600, "AWX_ISOLATED_LAUNCH_TIMEOUT":600,
"AWX_ISOLATED_CONNECTION_TIMEOUT":10, "AWX_ISOLATED_CONNECTION_TIMEOUT":10,
@@ -306,4 +304,4 @@
"users":{"fields":["username"],"adj_list":[]}, "users":{"fields":["username"],"adj_list":[]},
"instances":{"fields":["hostname"],"adj_list":[]} "instances":{"fields":["hostname"],"adj_list":[]}
} }
} }

View File

@@ -4,10 +4,8 @@
"command" "command"
], ],
"ALLOW_JINJA_IN_EXTRA_VARS": "template", "ALLOW_JINJA_IN_EXTRA_VARS": "template",
"AWX_PROOT_ENABLED": true, "AWX_ISOLATION_BASE_PATH": "/tmp",
"AWX_PROOT_BASE_PATH": "/tmp", "AWX_ISOLATION_SHOW_PATHS": [],
"AWX_PROOT_HIDE_PATHS": [],
"AWX_PROOT_SHOW_PATHS": [],
"AWX_ISOLATED_CHECK_INTERVAL": 1, "AWX_ISOLATED_CHECK_INTERVAL": 1,
"AWX_ISOLATED_LAUNCH_TIMEOUT": 600, "AWX_ISOLATED_LAUNCH_TIMEOUT": 600,
"AWX_ISOLATED_CONNECTION_TIMEOUT": 10, "AWX_ISOLATED_CONNECTION_TIMEOUT": 10,
@@ -34,4 +32,4 @@
"DEFAULT_PROJECT_UPDATE_TIMEOUT": 0, "DEFAULT_PROJECT_UPDATE_TIMEOUT": 0,
"ANSIBLE_FACT_CACHE_TIMEOUT": 0, "ANSIBLE_FACT_CACHE_TIMEOUT": 0,
"MAX_FORKS": 200 "MAX_FORKS": 200
} }

View File

@@ -41,15 +41,15 @@ extends_documentation_fragment: awx.awx.auth
''' '''
EXAMPLES = ''' EXAMPLES = '''
- name: Set the value of AWX_PROOT_BASE_PATH - name: Set the value of AWX_ISOLATION_BASE_PATH
tower_settings: tower_settings:
name: AWX_PROOT_BASE_PATH name: AWX_ISOLATION_BASE_PATH
value: "/tmp" value: "/tmp"
register: testing_settings register: testing_settings
- name: Set the value of AWX_PROOT_SHOW_PATHS - name: Set the value of AWX_ISOLATION_SHOW_PATHS
tower_settings: tower_settings:
name: "AWX_PROOT_SHOW_PATHS" name: "AWX_ISOLATION_SHOW_PATHS"
value: "'/var/lib/awx/projects/', '/tmp'" value: "'/var/lib/awx/projects/', '/tmp'"
register: testing_settings register: testing_settings

View File

@@ -43,7 +43,7 @@
-----END EC PRIVATE KEY----- -----END EC PRIVATE KEY-----
organization: Default organization: Default
- name: Disable bubblewrap - name: Disable process isolation
command: tower-cli setting modify AWX_PROOT_ENABLED false command: tower-cli setting modify AWX_PROOT_ENABLED false
- block: - block:
@@ -54,5 +54,5 @@
--credential dummy --module-name command --credential dummy --module-name command
--module-args "mkdir -p {{ project_base_dir }}/{{ project_dir_name }}" --module-args "mkdir -p {{ project_base_dir }}/{{ project_dir_name }}"
always: always:
- name: enable bubblewrap - name: enable process isolation
command: tower-cli setting modify AWX_PROOT_ENABLED true command: tower-cli setting modify AWX_PROOT_ENABLED true

View File

@@ -1,13 +1,13 @@
--- ---
- name: Set the value of AWX_PROOT_SHOW_PATHS to a baseline - name: Set the value of AWX_ISOLATION_SHOW_PATHS to a baseline
tower_settings: tower_settings:
name: AWX_PROOT_SHOW_PATHS name: AWX_ISOLATION_SHOW_PATHS
value: '["/var/lib/awx/projects/"]' value: '["/var/lib/awx/projects/"]'
- name: Set the value of AWX_PROOT_SHOW_PATHS to get an error back from Tower - name: Set the value of AWX_ISOLATION_SHOW_PATHS to get an error back from Tower
tower_settings: tower_settings:
settings: settings:
AWX_PROOT_SHOW_PATHS: AWX_ISOLATION_SHOW_PATHS:
'not': 'a valid' 'not': 'a valid'
'tower': 'setting' 'tower': 'setting'
register: result register: result
@@ -17,9 +17,9 @@
that: that:
- "result is failed" - "result is failed"
- name: Set the value of AWX_PROOT_SHOW_PATHS - name: Set the value of AWX_ISOLATION_SHOW_PATHS
tower_settings: tower_settings:
name: AWX_PROOT_SHOW_PATHS name: AWX_ISOLATION_SHOW_PATHS
value: '["/var/lib/awx/projects/", "/tmp"]' value: '["/var/lib/awx/projects/", "/tmp"]'
register: result register: result
@@ -27,9 +27,9 @@
that: that:
- "result is changed" - "result is changed"
- name: Attempt to set the value of AWX_PROOT_BASE_PATH to what it already is - name: Attempt to set the value of AWX_ISOLATION_BASE_PATH to what it already is
tower_settings: tower_settings:
name: AWX_PROOT_BASE_PATH name: AWX_ISOLATION_BASE_PATH
value: /tmp value: /tmp
register: result register: result
@@ -42,7 +42,7 @@
- name: Apply a single setting via settings - name: Apply a single setting via settings
tower_settings: tower_settings:
name: AWX_PROOT_SHOW_PATHS name: AWX_ISOLATION_SHOW_PATHS
value: '["/var/lib/awx/projects/", "/var/tmp"]' value: '["/var/lib/awx/projects/", "/var/tmp"]'
register: result register: result
@@ -53,8 +53,8 @@
- name: Apply multiple setting via settings with no change - name: Apply multiple setting via settings with no change
tower_settings: tower_settings:
settings: settings:
AWX_PROOT_BASE_PATH: /tmp AWX_ISOLATION_BASE_PATH: /tmp
AWX_PROOT_SHOW_PATHS: ["/var/lib/awx/projects/", "/var/tmp"] AWX_ISOLATION_SHOW_PATHS: ["/var/lib/awx/projects/", "/var/tmp"]
register: result register: result
- debug: - debug:
@@ -67,8 +67,8 @@
- name: Apply multiple setting via settings with change - name: Apply multiple setting via settings with change
tower_settings: tower_settings:
settings: settings:
AWX_PROOT_BASE_PATH: /tmp AWX_ISOLATION_BASE_PATH: /tmp
AWX_PROOT_SHOW_PATHS: [] AWX_ISOLATION_SHOW_PATHS: []
register: result register: result
- assert: - assert:
@@ -77,7 +77,7 @@
- name: Handle an omit value - name: Handle an omit value
tower_settings: tower_settings:
name: AWX_PROOT_BASE_PATH name: AWX_ISOLATION_BASE_PATH
value: '{{ junk_var | default(omit) }}' value: '{{ junk_var | default(omit) }}'
register: result register: result
ignore_errors: true ignore_errors: true

View File

@@ -103,7 +103,7 @@ When a job is scheduled to run on an "isolated" instance:
- a static inventory file - a static inventory file
- pexpect passwords - pexpect passwords
- environment variables - environment variables
- the `ansible`/`ansible-playbook` command invocation, _i.e._, `bwrap ... ansible-playbook -i /path/to/inventory /path/to/playbook.yml -e ...` - the `ansible`/`ansible-playbook` command invocation, _i.e._, `ansible-playbook -i /path/to/inventory /path/to/playbook.yml -e ...`
* Once the metadata has been `rsync`ed to the isolated host, the "controller instance" starts a process on the "isolated" instance which consumes the metadata and starts running `ansible`/`ansible-playbook`. As the playbook runs, job artifacts (such as `stdout` and job events) are written to disk on the "isolated" instance. * Once the metadata has been `rsync`ed to the isolated host, the "controller instance" starts a process on the "isolated" instance which consumes the metadata and starts running `ansible`/`ansible-playbook`. As the playbook runs, job artifacts (such as `stdout` and job events) are written to disk on the "isolated" instance.

View File

@@ -1,50 +0,0 @@
## Process Isolation Overview
In older versions of Ansible Tower, we used a system called `proot` to isolate Tower job processes from the rest of the system.
Tower version 3.1 and later switched to using `bubblewrap`, which is a much lighter-weight and maintained process isolation system.
Tower 3.5 and later uses the process isolation feature in Ansible runner to achieve process isolation.
### Activating Process Isolation
`bubblewrap` is enabled by default; it can be turned off via Tower Config or from a Tower settings file:
AWX_PROOT_ENABLED = False
Process isolation, when enabled, will be used for the following Job Types:
* Job Templates - Launching jobs from regular job templates
* Ad-hoc Commands - Launching ad-hoc commands against one or more hosts in inventory
### Tunables
Process Isolation will, by default, hide the following directories from the tasks mentioned above:
* `/etc/tower` - To prevent exposing Tower configuration
* `/var/lib/awx` - With the exception of the current project being used (for regular job templates)
* `/var/log`
* `/tmp` (or whatever the system `temp dir` is) - With the exception of the processes's own temp files
If there is other information on the system that is sensitive and should be hidden, it can be added via the Tower Configuration Screen
or by updating the following entry in a tower settings file:
AWX_PROOT_HIDE_PATHS = ['/list/of/', '/paths']
If there are any directories that should specifically be exposed that can be set in a similar way:
AWX_PROOT_SHOW_PATHS = ['/list/of/', '/paths']
By default, the system will use the system's `tmp dir` (`/tmp` by default) as its staging area. This can be changed via the following setting:
AWX_PROOT_BASE_PATH = "/opt/tmp"
### Project Folder Isolation
Starting in AWX versions above 6.0.0, the project folder will be copied for each job run.
This allows playbooks to make local changes to the source tree for convenience,
such as creating temporary files, without the possibility of interference with
other jobs.

View File

@@ -187,7 +187,7 @@ This task spawns an `ansible` process, which then runs a command using Ansible.
- Build a dictionary of passwords for the SSH private key, SSH user and sudo/su. - Build a dictionary of passwords for the SSH private key, SSH user and sudo/su.
- Build an environment dictionary for Ansible. - Build an environment dictionary for Ansible.
- Build a command line argument list for running Ansible, optionally using `ssh-agent` for public/private key authentication. - Build a command line argument list for running Ansible, optionally using `ssh-agent` for public/private key authentication.
- Return whether the task should use `bwrap`. - Return whether the task should use process isolation.
For more information on ad hoc commands, read the [Running Ad Hoc Commands section](https://docs.ansible.com/ansible-tower/latest/html/userguide/inventories.html#running-ad-hoc-commands) of the Inventories page of the Ansible Tower User Guide. For more information on ad hoc commands, read the [Running Ad Hoc Commands section](https://docs.ansible.com/ansible-tower/latest/html/userguide/inventories.html#running-ad-hoc-commands) of the Inventories page of the Ansible Tower User Guide.

View File

@@ -22,10 +22,6 @@ SECRET_KEY = get_secret()
ALLOWED_HOSTS = ['*'] ALLOWED_HOSTS = ['*']
# Container environments don't like chroots
AWX_PROOT_ENABLED = False
CLUSTER_HOST_ID = "awx" CLUSTER_HOST_ID = "awx"
SYSTEM_UUID = '00000000-0000-0000-0000-000000000000' SYSTEM_UUID = '00000000-0000-0000-0000-000000000000'