diff --git a/awx/conf/migrations/0009_rename_proot_settings.py b/awx/conf/migrations/0009_rename_proot_settings.py new file mode 100644 index 0000000000..2b0e2175aa --- /dev/null +++ b/awx/conf/migrations/0009_rename_proot_settings.py @@ -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)] diff --git a/awx/main/conf.py b/awx/main/conf.py index 2cfe06a25f..dbeaec9040 100644 --- a/awx/main/conf.py +++ b/awx/main/conf.py @@ -233,16 +233,7 @@ register( ) register( - 'AWX_PROOT_ENABLED', - 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', + 'AWX_ISOLATION_BASE_PATH', field_class=fields.CharField, label=_('Job execution path'), help_text=_( @@ -255,17 +246,7 @@ register( ) register( - 'AWX_PROOT_HIDE_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', + 'AWX_ISOLATION_SHOW_PATHS', field_class=fields.StringListField, required=False, label=_('Paths to expose to isolated jobs'), diff --git a/awx/main/constants.py b/awx/main/constants.py index db2e9c44d7..6a44087c28 100644 --- a/awx/main/constants.py +++ b/awx/main/constants.py @@ -52,7 +52,6 @@ ENV_BLOCKLIST = frozenset( 'VIRTUAL_ENV', 'PATH', 'PYTHONPATH', - 'PROOT_TMP_DIR', 'JOB_ID', 'INVENTORY_ID', 'INVENTORY_SOURCE_ID', diff --git a/awx/main/isolated/manager.py b/awx/main/isolated/manager.py index 79dac4445f..3fbda06ab8 100644 --- a/awx/main/isolated/manager.py +++ b/awx/main/isolated/manager.py @@ -135,7 +135,7 @@ class IsolatedManager(object): extravars = { 'src': self.private_data_dir, - 'dest': settings.AWX_PROOT_BASE_PATH, + 'dest': settings.AWX_ISOLATION_BASE_PATH, 'ident': self.ident, 'job_id': self.instance.id, } @@ -304,7 +304,7 @@ class IsolatedManager(object): if not len(instance_qs): return 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['private_data_dir'] = private_data_dir self.runner_params['forks'] = len(instance_qs) diff --git a/awx/main/management/commands/inventory_import.py b/awx/main/management/commands/inventory_import.py index 0a2a19937d..9cdd2f3017 100644 --- a/awx/main/management/commands/inventory_import.py +++ b/awx/main/management/commands/inventory_import.py @@ -69,8 +69,6 @@ class AnsibleInventoryLoader(object): def __init__(self, source, venv_path=None, verbosity=0): self.source = source self.verbosity = verbosity - # TODO: remove once proot has been removed - self.tmp_private_dir = None if venv_path: self.venv_path = venv_path else: diff --git a/awx/main/management/commands/test_isolated_connection.py b/awx/main/management/commands/test_isolated_connection.py index 3983967251..c89b71a892 100644 --- a/awx/main/management/commands/test_isolated_connection.py +++ b/awx/main/management/commands/test_isolated_connection.py @@ -25,7 +25,7 @@ class Command(BaseCommand): raise CommandError("--hostname is a required argument") 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 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 diff --git a/awx/main/tasks.py b/awx/main/tasks.py index 36c0350259..de1b15377b 100644 --- a/awx/main/tasks.py +++ b/awx/main/tasks.py @@ -841,7 +841,6 @@ class BaseTask(object): model = None event_model = None abstract = True - proot_show_paths = [] def __init__(self): self.cleanup_paths = [] @@ -908,9 +907,9 @@ class BaseTask(object): if pull: params['container_options'].append(f'--pull={pull}') - if settings.AWX_PROOT_SHOW_PATHS: + if settings.AWX_ISOLATION_SHOW_PATHS: 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') return params @@ -924,7 +923,7 @@ class BaseTask(object): """ 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) if settings.AWX_CLEANUP_PATHS: self.cleanup_paths.append(pdd_wrapper_path) @@ -1090,12 +1089,6 @@ class BaseTask(object): """ 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): script_params = dict(hostvars=True, towervars=True) if hasattr(instance, 'job_slice_number'): @@ -1371,8 +1364,8 @@ class BaseTask(object): status = self.instance.status raise RuntimeError('not starting %s task' % self.instance.status) - if not os.path.exists(settings.AWX_PROOT_BASE_PATH): - raise RuntimeError('AWX_PROOT_BASE_PATH=%s does not exist' % settings.AWX_PROOT_BASE_PATH) + if not os.path.exists(settings.AWX_ISOLATION_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 if hasattr(self.instance, 'custom_virtualenv'): @@ -1598,8 +1591,7 @@ class RunJob(BaseTask): env['ANSIBLE_CALLBACK_PLUGINS'] = ':'.join(settings.AWX_ANSIBLE_CALLBACK_PLUGINS) env['AWX_HOST'] = settings.TOWER_URL_BASE - # Create a directory for ControlPath sockets that is unique to each - # job and visible inside the proot environment (when enabled). + # Create a directory for ControlPath sockets that is unique to each job cp_dir = os.path.join(private_data_dir, 'cp') if not os.path.exists(cp_dir): os.mkdir(cp_dir, 0o700) @@ -1768,14 +1760,6 @@ class RunJob(BaseTask): """ 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): if settings.IS_K8S: return {} @@ -1929,10 +1913,6 @@ class RunProjectUpdate(BaseTask): event_model = ProjectUpdateEvent 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): super(RunProjectUpdate, self).__init__(*args, **kwargs) self.playbook_new_revision = None @@ -1990,7 +1970,7 @@ class RunProjectUpdate(BaseTask): env['DISPLAY'] = '' # Prevent stupid password popup when running tests. # give ansible a hint about the intended tmpdir to work around issues # 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) if settings.GALAXY_IGNORE_CERTS: env['ANSIBLE_GALAXY_IGNORE'] = True @@ -2394,12 +2374,6 @@ class RunProjectUpdate(BaseTask): if status == 'successful' and instance.launch_type != 'sync': 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): if settings.IS_K8S: return {} @@ -2790,7 +2764,7 @@ class RunAdHocCommand(BaseTask): env['ANSIBLE_SFTP_BATCH_MODE'] = 'False' # 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') if not os.path.exists(cp_dir): os.mkdir(cp_dir, 0o700) @@ -2894,14 +2868,6 @@ class RunAdHocCommand(BaseTask): d[r'Password:\s*?$'] = 'ssh_password' 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): super(RunAdHocCommand, self).final_run_hook(adhoc_job, status, private_data_dir, fact_modification_times) if isolated_manager_instance: diff --git a/awx/main/tests/functional/api/test_settings.py b/awx/main/tests/functional/api/test_settings.py index fa53c65aa9..84bfff2d18 100644 --- a/awx/main/tests/functional/api/test_settings.py +++ b/awx/main/tests/functional/api/test_settings.py @@ -33,16 +33,14 @@ def test_jobs_settings(get, put, patch, delete, admin): response = get(url, user=admin, expect=200) data = dict(response.data.items()) 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) - assert response.data['AWX_PROOT_HIDE_PATHS'] == ['/home'] - data.pop('AWX_PROOT_HIDE_PATHS') - data.pop('AWX_PROOT_SHOW_PATHS') + assert response.data['AWX_ISOLATION_SHOW_PATHS'] == ['/home'] + data.pop('AWX_ISOLATION_SHOW_PATHS') data.pop('AWX_ANSIBLE_CALLBACK_PLUGINS') put(url, user=admin, data=data, expect=200) response = get(url, user=admin, expect=200) - assert response.data['AWX_PROOT_HIDE_PATHS'] == [] - assert response.data['AWX_PROOT_SHOW_PATHS'] == [] + assert response.data['AWX_ISOLATION_SHOW_PATHS'] == [] assert response.data['AWX_ANSIBLE_CALLBACK_PLUGINS'] == [] diff --git a/awx/main/tests/unit/test_tasks.py b/awx/main/tests/unit/test_tasks.py index 01e02b67d7..26df22c4f2 100644 --- a/awx/main/tests/unit/test_tasks.py +++ b/awx/main/tests/unit/test_tasks.py @@ -725,7 +725,6 @@ class TestIsolatedExecution(TestJobExecution): extra_vars = json.loads(extra_vars) assert extra_vars['dest'] == '/tmp' assert extra_vars['src'] == private_data - assert extra_vars['proot_temp_dir'].startswith('/tmp/awx_proot_') def test_systemctl_failure(self): # If systemctl fails, read the contents of `artifacts/systemctl_logs` diff --git a/awx/main/utils/common.py b/awx/main/utils/common.py index 9ad45d68c6..b40f7fb270 100644 --- a/awx/main/utils/common.py +++ b/awx/main/utils/common.py @@ -69,9 +69,6 @@ __all__ = [ 'get_system_task_capacity', 'get_cpu_capacity', 'get_mem_capacity', - 'wrap_args_with_proot', - 'build_proot_temp_dir', - 'check_proot_installed', 'model_to_dict', 'NullablePromptPseudoField', 'model_instance_diff', @@ -842,94 +839,6 @@ def set_environ(**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): """ Helper for obtaining a pk from user data dict or None if not present. diff --git a/awx/settings/defaults.py b/awx/settings/defaults.py index 194d363a6d..e51f66007d 100644 --- a/awx/settings/defaults.py +++ b/awx/settings/defaults.py @@ -569,26 +569,15 @@ AWX_SHOW_PLAYBOOK_LINKS = False # Applies to any galaxy server 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. -AWX_PROOT_ENABLED = True - -# 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 = [] +AWX_ISOLATION_SHOW_PATHS = [] # The directory in which Tower will create new temporary directories for job # execution and isolation (such as credential files and custom # inventory scripts). # Note: This setting may be overridden by database settings. -AWX_PROOT_BASE_PATH = "/tmp" +AWX_ISOLATION_BASE_PATH = "/tmp" # Disable resource profiling by default AWX_RESOURCE_PROFILING_ENABLED = False diff --git a/awx/settings/development.py b/awx/settings/development.py index e2a42fef67..66dc12f50f 100644 --- a/awx/settings/development.py +++ b/awx/settings/development.py @@ -67,10 +67,6 @@ CALLBACK_QUEUE = "callback_tasks" # Note: This setting may be overridden by database settings. 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_CHECK_INTERVAL = 1 AWX_ISOLATED_PERIODIC_CHECK = 30 diff --git a/awx/ui_next/src/screens/Host/data.hostFacts.json b/awx/ui_next/src/screens/Host/data.hostFacts.json index 2507d267e3..526d83b344 100644 --- a/awx/ui_next/src/screens/Host/data.hostFacts.json +++ b/awx/ui_next/src/screens/Host/data.hostFacts.json @@ -101,7 +101,6 @@ "VIRTUAL_ENV": "/var/lib/awx/venv/ansible", "INVENTORY_ID": "1", "MAX_EVENT_RES": "700000", - "PROOT_TMP_DIR": "/tmp", "ANSIBLE_LIBRARY": "/awx_devel/awx/plugins/library", "SDB_NOTIFY_HOST": "docker.for.mac.host.internal", "AWX_GROUP_QUEUES": "tower", diff --git a/awx/ui_next/src/screens/Inventory/shared/data.hostFacts.json b/awx/ui_next/src/screens/Inventory/shared/data.hostFacts.json index 2507d267e3..526d83b344 100644 --- a/awx/ui_next/src/screens/Inventory/shared/data.hostFacts.json +++ b/awx/ui_next/src/screens/Inventory/shared/data.hostFacts.json @@ -101,7 +101,6 @@ "VIRTUAL_ENV": "/var/lib/awx/venv/ansible", "INVENTORY_ID": "1", "MAX_EVENT_RES": "700000", - "PROOT_TMP_DIR": "/tmp", "ANSIBLE_LIBRARY": "/awx_devel/awx/plugins/library", "SDB_NOTIFY_HOST": "docker.for.mac.host.internal", "AWX_GROUP_QUEUES": "tower", diff --git a/awx/ui_next/src/screens/Job/shared/data.job.json b/awx/ui_next/src/screens/Job/shared/data.job.json index 614e7d6aad..8b2d31a197 100644 --- a/awx/ui_next/src/screens/Job/shared/data.job.json +++ b/awx/ui_next/src/screens/Job/shared/data.job.json @@ -154,7 +154,6 @@ "ANSIBLE_INVENTORY_UNPARSED_FAILED": "True", "ANSIBLE_PARAMIKO_RECORD_HOST_KEYS": "False", "ANSIBLE_VENV_PATH": "/var/lib/awx/venv/ansible", - "PROOT_TMP_DIR": "/tmp", "AWX_PRIVATE_DATA_DIR": "/tmp/awx_2_a4b1afiw", "ANSIBLE_COLLECTIONS_PATHS": "/tmp/collections", "PYTHONPATH": "/var/lib/awx/venv/ansible/lib/python2.7/site-packages:/awx_devel/awx/lib:", diff --git a/awx/ui_next/src/screens/Setting/Jobs/JobsDetail/JobsDetail.test.jsx b/awx/ui_next/src/screens/Setting/Jobs/JobsDetail/JobsDetail.test.jsx index 02ec0b8118..715bd649e3 100644 --- a/awx/ui_next/src/screens/Setting/Jobs/JobsDetail/JobsDetail.test.jsx +++ b/awx/ui_next/src/screens/Setting/Jobs/JobsDetail/JobsDetail.test.jsx @@ -50,7 +50,6 @@ describe('', () => { }); test('should render expected details', () => { - assertDetail(wrapper, 'Enable job isolation', 'On'); assertDetail(wrapper, 'Job execution path', '/tmp'); assertDetail(wrapper, 'Isolated status check interval', '1 seconds'); assertDetail(wrapper, 'Isolated launch timeout', '600 seconds'); @@ -81,7 +80,6 @@ describe('', () => { 'Ansible Modules Allowed for Ad Hoc Jobs', '[\n "command"\n]' ); - assertVariableDetail(wrapper, 'Paths to hide from isolated jobs', '[]'); assertVariableDetail(wrapper, 'Paths to expose to isolated jobs', '[]'); assertVariableDetail(wrapper, 'Extra Environment Variables', '{}'); assertVariableDetail(wrapper, 'Ansible Callback Plugins', '[]'); diff --git a/awx/ui_next/src/screens/Setting/Jobs/JobsEdit/JobsEdit.jsx b/awx/ui_next/src/screens/Setting/Jobs/JobsEdit/JobsEdit.jsx index 143c805a01..3e5d287727 100644 --- a/awx/ui_next/src/screens/Setting/Jobs/JobsEdit/JobsEdit.jsx +++ b/awx/ui_next/src/screens/Setting/Jobs/JobsEdit/JobsEdit.jsx @@ -70,8 +70,7 @@ function JobsEdit() { await submitForm({ ...form, AD_HOC_COMMANDS: formatJson(form.AD_HOC_COMMANDS), - AWX_PROOT_SHOW_PATHS: formatJson(form.AWX_PROOT_SHOW_PATHS), - AWX_PROOT_HIDE_PATHS: formatJson(form.AWX_PROOT_HIDE_PATHS), + AWX_ISOLATION_SHOW_PATHS: formatJson(form.AWX_ISOLATION_SHOW_PATHS), AWX_ANSIBLE_CALLBACK_PLUGINS: formatJson( form.AWX_ANSIBLE_CALLBACK_PLUGINS ), @@ -116,8 +115,8 @@ function JobsEdit() {
- - {submitError && } diff --git a/awx/ui_next/src/screens/Setting/Jobs/JobsEdit/data.defaultJobSettings.json b/awx/ui_next/src/screens/Setting/Jobs/JobsEdit/data.defaultJobSettings.json index 70c73869d7..8770514f75 100644 --- a/awx/ui_next/src/screens/Setting/Jobs/JobsEdit/data.defaultJobSettings.json +++ b/awx/ui_next/src/screens/Setting/Jobs/JobsEdit/data.defaultJobSettings.json @@ -27,10 +27,8 @@ "AWX_ISOLATED_CONNECTION_TIMEOUT": 10, "AWX_ISOLATED_HOST_KEY_CHECKING": false, "AWX_ISOLATED_LAUNCH_TIMEOUT": 600, - "AWX_PROOT_BASE_PATH": "/tmp", - "AWX_PROOT_ENABLED": true, - "AWX_PROOT_HIDE_PATHS": [], - "AWX_PROOT_SHOW_PATHS": [], + "AWX_ISOLATION_BASE_PATH": "/tmp", + "AWX_ISOLATION_SHOW_PATHS": [], "AWX_RESOURCE_PROFILING_CPU_POLL_INTERVAL": 0.25, "AWX_RESOURCE_PROFILING_ENABLED": false, "AWX_RESOURCE_PROFILING_MEMORY_POLL_INTERVAL": 0.25, @@ -45,4 +43,4 @@ "MAX_FORKS": 200, "PROJECT_UPDATE_VVV": false, "SCHEDULE_MAX_JOBS": 10 -} \ No newline at end of file +} diff --git a/awx/ui_next/src/screens/Setting/shared/data.allSettingOptions.json b/awx/ui_next/src/screens/Setting/shared/data.allSettingOptions.json index 758e267ed3..43713bf1fa 100644 --- a/awx/ui_next/src/screens/Setting/shared/data.allSettingOptions.json +++ b/awx/ui_next/src/screens/Setting/shared/data.allSettingOptions.json @@ -166,15 +166,7 @@ ] ] }, - "AWX_PROOT_ENABLED": { - "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": { + "AWX_ISOLATION_BASE_PATH": { "type": "string", "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).", @@ -182,18 +174,7 @@ "category_slug": "jobs", "defined_in_file": false }, - "AWX_PROOT_HIDE_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": { + "AWX_ISOLATION_SHOW_PATHS": { "type": "list", "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.", @@ -3158,16 +3139,7 @@ ] ] }, - "AWX_PROOT_ENABLED": { - "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": { + "AWX_ISOLATION_BASE_PATH": { "type": "string", "required": true, "label": "Job execution path", @@ -3176,21 +3148,7 @@ "category_slug": "jobs", "default": "/tmp" }, - "AWX_PROOT_HIDE_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": { + "AWX_ISOLATION_SHOW_PATHS": { "type": "list", "required": false, "label": "Paths to expose to isolated jobs", diff --git a/awx/ui_next/src/screens/Setting/shared/data.allSettings.json b/awx/ui_next/src/screens/Setting/shared/data.allSettings.json index 2567289cf7..81d32ea5ac 100644 --- a/awx/ui_next/src/screens/Setting/shared/data.allSettings.json +++ b/awx/ui_next/src/screens/Setting/shared/data.allSettings.json @@ -34,10 +34,8 @@ "win_user" ], "ALLOW_JINJA_IN_EXTRA_VARS":"template", - "AWX_PROOT_ENABLED":true, - "AWX_PROOT_BASE_PATH":"/tmp", - "AWX_PROOT_HIDE_PATHS":[], - "AWX_PROOT_SHOW_PATHS":[], + "AWX_ISOLATION_BASE_PATH":"/tmp", + "AWX_ISOLATION_SHOW_PATHS":[], "AWX_ISOLATED_CHECK_INTERVAL":1, "AWX_ISOLATED_LAUNCH_TIMEOUT":600, "AWX_ISOLATED_CONNECTION_TIMEOUT":10, @@ -306,4 +304,4 @@ "users":{"fields":["username"],"adj_list":[]}, "instances":{"fields":["hostname"],"adj_list":[]} } -} \ No newline at end of file +} diff --git a/awx/ui_next/src/screens/Setting/shared/data.jobSettings.json b/awx/ui_next/src/screens/Setting/shared/data.jobSettings.json index 190a346560..d3910ded77 100644 --- a/awx/ui_next/src/screens/Setting/shared/data.jobSettings.json +++ b/awx/ui_next/src/screens/Setting/shared/data.jobSettings.json @@ -4,10 +4,8 @@ "command" ], "ALLOW_JINJA_IN_EXTRA_VARS": "template", - "AWX_PROOT_ENABLED": true, - "AWX_PROOT_BASE_PATH": "/tmp", - "AWX_PROOT_HIDE_PATHS": [], - "AWX_PROOT_SHOW_PATHS": [], + "AWX_ISOLATION_BASE_PATH": "/tmp", + "AWX_ISOLATION_SHOW_PATHS": [], "AWX_ISOLATED_CHECK_INTERVAL": 1, "AWX_ISOLATED_LAUNCH_TIMEOUT": 600, "AWX_ISOLATED_CONNECTION_TIMEOUT": 10, @@ -34,4 +32,4 @@ "DEFAULT_PROJECT_UPDATE_TIMEOUT": 0, "ANSIBLE_FACT_CACHE_TIMEOUT": 0, "MAX_FORKS": 200 -} \ No newline at end of file +} diff --git a/awx_collection/plugins/modules/tower_settings.py b/awx_collection/plugins/modules/tower_settings.py index 4e2f91dd14..18531a28d7 100644 --- a/awx_collection/plugins/modules/tower_settings.py +++ b/awx_collection/plugins/modules/tower_settings.py @@ -41,15 +41,15 @@ extends_documentation_fragment: awx.awx.auth ''' EXAMPLES = ''' -- name: Set the value of AWX_PROOT_BASE_PATH +- name: Set the value of AWX_ISOLATION_BASE_PATH tower_settings: - name: AWX_PROOT_BASE_PATH + name: AWX_ISOLATION_BASE_PATH value: "/tmp" register: testing_settings -- name: Set the value of AWX_PROOT_SHOW_PATHS +- name: Set the value of AWX_ISOLATION_SHOW_PATHS tower_settings: - name: "AWX_PROOT_SHOW_PATHS" + name: "AWX_ISOLATION_SHOW_PATHS" value: "'/var/lib/awx/projects/', '/tmp'" register: testing_settings diff --git a/awx_collection/tests/integration/targets/tower_project_manual/tasks/create_project_dir.yml b/awx_collection/tests/integration/targets/tower_project_manual/tasks/create_project_dir.yml index 7f5b3b49c2..5388a6d3fd 100644 --- a/awx_collection/tests/integration/targets/tower_project_manual/tasks/create_project_dir.yml +++ b/awx_collection/tests/integration/targets/tower_project_manual/tasks/create_project_dir.yml @@ -43,7 +43,7 @@ -----END EC PRIVATE KEY----- organization: Default -- name: Disable bubblewrap +- name: Disable process isolation command: tower-cli setting modify AWX_PROOT_ENABLED false - block: @@ -54,5 +54,5 @@ --credential dummy --module-name command --module-args "mkdir -p {{ project_base_dir }}/{{ project_dir_name }}" always: - - name: enable bubblewrap + - name: enable process isolation command: tower-cli setting modify AWX_PROOT_ENABLED true diff --git a/awx_collection/tests/integration/targets/tower_settings/tasks/main.yml b/awx_collection/tests/integration/targets/tower_settings/tasks/main.yml index 8a42f5768e..f6dcd002fd 100644 --- a/awx_collection/tests/integration/targets/tower_settings/tasks/main.yml +++ b/awx_collection/tests/integration/targets/tower_settings/tasks/main.yml @@ -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: - name: AWX_PROOT_SHOW_PATHS + name: AWX_ISOLATION_SHOW_PATHS 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: settings: - AWX_PROOT_SHOW_PATHS: + AWX_ISOLATION_SHOW_PATHS: 'not': 'a valid' 'tower': 'setting' register: result @@ -17,9 +17,9 @@ that: - "result is failed" -- name: Set the value of AWX_PROOT_SHOW_PATHS +- name: Set the value of AWX_ISOLATION_SHOW_PATHS tower_settings: - name: AWX_PROOT_SHOW_PATHS + name: AWX_ISOLATION_SHOW_PATHS value: '["/var/lib/awx/projects/", "/tmp"]' register: result @@ -27,9 +27,9 @@ that: - "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: - name: AWX_PROOT_BASE_PATH + name: AWX_ISOLATION_BASE_PATH value: /tmp register: result @@ -42,7 +42,7 @@ - name: Apply a single setting via settings tower_settings: - name: AWX_PROOT_SHOW_PATHS + name: AWX_ISOLATION_SHOW_PATHS value: '["/var/lib/awx/projects/", "/var/tmp"]' register: result @@ -53,8 +53,8 @@ - name: Apply multiple setting via settings with no change tower_settings: settings: - AWX_PROOT_BASE_PATH: /tmp - AWX_PROOT_SHOW_PATHS: ["/var/lib/awx/projects/", "/var/tmp"] + AWX_ISOLATION_BASE_PATH: /tmp + AWX_ISOLATION_SHOW_PATHS: ["/var/lib/awx/projects/", "/var/tmp"] register: result - debug: @@ -67,8 +67,8 @@ - name: Apply multiple setting via settings with change tower_settings: settings: - AWX_PROOT_BASE_PATH: /tmp - AWX_PROOT_SHOW_PATHS: [] + AWX_ISOLATION_BASE_PATH: /tmp + AWX_ISOLATION_SHOW_PATHS: [] register: result - assert: @@ -77,7 +77,7 @@ - name: Handle an omit value tower_settings: - name: AWX_PROOT_BASE_PATH + name: AWX_ISOLATION_BASE_PATH value: '{{ junk_var | default(omit) }}' register: result ignore_errors: true diff --git a/docs/clustering.md b/docs/clustering.md index c77968191d..d13da47ae7 100644 --- a/docs/clustering.md +++ b/docs/clustering.md @@ -103,7 +103,7 @@ When a job is scheduled to run on an "isolated" instance: - a static inventory file - pexpect passwords - 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. diff --git a/docs/process_isolation.md b/docs/process_isolation.md deleted file mode 100644 index c8fe21b0ad..0000000000 --- a/docs/process_isolation.md +++ /dev/null @@ -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. diff --git a/docs/tasks.md b/docs/tasks.md index 13d7f969b6..f2e29ec777 100644 --- a/docs/tasks.md +++ b/docs/tasks.md @@ -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 an environment dictionary for Ansible. - 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. diff --git a/tools/ansible/roles/dockerfile/files/settings.py b/tools/ansible/roles/dockerfile/files/settings.py index 507e9b4d7b..c2abeb2df2 100644 --- a/tools/ansible/roles/dockerfile/files/settings.py +++ b/tools/ansible/roles/dockerfile/files/settings.py @@ -22,10 +22,6 @@ SECRET_KEY = get_secret() ALLOWED_HOSTS = ['*'] -# Container environments don't like chroots -AWX_PROOT_ENABLED = False - - CLUSTER_HOST_ID = "awx" SYSTEM_UUID = '00000000-0000-0000-0000-000000000000'