Merge pull request #334 from ryanpetrello/fix-7506

add process isolation to project updates
This commit is contained in:
Ryan Petrello 2017-09-12 16:15:37 -04:00 committed by GitHub
commit bd088d31ca
3 changed files with 51 additions and 2 deletions

View File

@ -478,6 +478,7 @@ class BaseTask(LogErrorsTask):
model = None
abstract = True
cleanup_paths = []
proot_show_paths = []
def update_model(self, pk, _attempt=0, **updates):
"""Reload the model instance from the database and update the
@ -793,6 +794,7 @@ class BaseTask(LogErrorsTask):
# May have to serialize the value
kwargs['private_data_files'] = self.build_private_data_files(instance, **kwargs)
kwargs['passwords'] = self.build_passwords(instance, **kwargs)
kwargs['proot_show_paths'] = self.proot_show_paths
args = self.build_args(instance, **kwargs)
safe_args = self.build_safe_args(instance, **kwargs)
output_replacements = self.build_output_replacements(instance, **kwargs)
@ -1288,6 +1290,10 @@ class RunProjectUpdate(BaseTask):
name = 'awx.main.tasks.run_project_update'
model = ProjectUpdate
@property
def proot_show_paths(self):
return [settings.PROJECTS_ROOT]
def build_private_data(self, project_update, **kwargs):
'''
Return SSH private key data needed for this project update.
@ -1301,7 +1307,7 @@ class RunProjectUpdate(BaseTask):
}
}
'''
handle, self.revision_path = tempfile.mkstemp(dir=settings.AWX_PROOT_BASE_PATH)
handle, self.revision_path = tempfile.mkstemp(dir=settings.PROJECTS_ROOT)
self.cleanup_paths.append(self.revision_path)
private_data = {'credentials': {}}
if project_update.credential:
@ -1594,6 +1600,12 @@ class RunProjectUpdate(BaseTask):
if status == 'successful' and instance.launch_type != 'sync':
self._update_dependent_inventories(instance, dependent_inventory_sources)
def should_use_proot(self, instance, **kwargs):
'''
Return whether this task should use proot.
'''
return getattr(settings, 'AWX_PROOT_ENABLED', False)
class RunInventoryUpdate(BaseTask):

View File

@ -181,6 +181,8 @@ class TestJobExecution:
EXAMPLE_PRIVATE_KEY = '-----BEGIN PRIVATE KEY-----\nxyz==\n-----END PRIVATE KEY-----'
def setup_method(self, method):
if not os.path.exists(settings.PROJECTS_ROOT):
os.mkdir(settings.PROJECTS_ROOT)
self.project_path = tempfile.mkdtemp(prefix='awx_project_')
with open(os.path.join(self.project_path, 'helloworld.yml'), 'w') as f:
f.write('---')
@ -281,6 +283,15 @@ class TestGenericRun(TestJobExecution):
args, cwd, env, stdout = call_args
assert args[0] == 'bwrap'
def test_bwrap_virtualenvs_are_readonly(self):
self.task.run(self.pk)
assert self.run_pexpect.call_count == 1
call_args, _ = self.run_pexpect.call_args_list[0]
args, cwd, env, stdout = call_args
assert '--ro-bind %s %s' % (settings.ANSIBLE_VENV_PATH, settings.ANSIBLE_VENV_PATH) in ' '.join(args) # noqa
assert '--ro-bind %s %s' % (settings.AWX_VENV_PATH, settings.AWX_VENV_PATH) in ' '.join(args) # noqa
def test_awx_task_env(self):
patch = mock.patch('awx.main.tasks.settings.AWX_TASK_ENV', {'FOO': 'BAR'})
patch.start()
@ -1096,6 +1107,27 @@ class TestProjectUpdateCredentials(TestJobExecution):
]
}
def test_bwrap_exposes_projects_root(self):
ssh = CredentialType.defaults['ssh']()
self.instance.scm_type = 'git'
self.instance.credential = Credential(
pk=1,
credential_type=ssh,
)
self.task.run(self.pk)
assert self.run_pexpect.call_count == 1
call_args, call_kwargs = self.run_pexpect.call_args_list[0]
args, cwd, env, stdout = call_args
assert ' '.join(args).startswith('bwrap')
' '.join([
'--bind',
settings.PROJECTS_ROOT,
settings.PROJECTS_ROOT,
]) in ' '.join(args)
assert '"scm_revision_output": "/projects/tmp' in ' '.join(args)
def test_username_and_password_auth(self, scm_type):
ssh = CredentialType.defaults['ssh']()
self.instance.scm_type = scm_type

View File

@ -699,8 +699,13 @@ def wrap_args_with_proot(args, cwd, **kwargs):
show_paths = [cwd, kwargs['private_data_dir']]
else:
show_paths = [cwd]
show_paths.extend([settings.ANSIBLE_VENV_PATH, settings.AWX_VENV_PATH])
for venv in (
settings.ANSIBLE_VENV_PATH,
settings.AWX_VENV_PATH
):
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