Files
zuul/tests/remote/test_remote_action_modules.py
T
Tobias Henkel 5763b8e4d7 Add missing localhost delegation checks to some modules
Currently we don't check some modules for delegation to
localhost. This would make it possible to overwrite any data which is
writable within the bwrap context. Further the script module allows
arbitrary code execution when delegated to localhost.

The following modules are affected:
* assemble: add safe path check
* copy: add safe path check
* patch: add safe path check
* script: block completely
* template: add safe path check
* unarchive: add tests, fixed by safe path check of copy

Change-Id: I2360219f50e6a28bb134468ead08ec72148ad192
Story: 2001681
2018-03-22 20:42:01 +01:00

171 lines
6.8 KiB
Python

# Copyright 2018 Red Hat, Inc.
#
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
import os
import textwrap
from tests.base import AnsibleZuulTestCase, FIXTURE_DIR
ERROR_ACCESS_OUTSIDE = "Accessing files from outside the working dir"
class TestActionModules(AnsibleZuulTestCase):
tenant_config_file = 'config/remote-action-modules/main.yaml'
def setUp(self):
super(TestActionModules, self).setUp()
self.fake_nodepool.remote_ansible = True
ansible_remote = os.environ.get('ZUUL_REMOTE_IPV4')
self.assertIsNotNone(ansible_remote)
# inject some files as forbidden sources
fixture_dir = os.path.join(FIXTURE_DIR, 'bwrap-mounts')
self.executor_server.execution_wrapper.bwrap_command.extend(
['--ro-bind', fixture_dir, '/opt'])
def _run_job(self, job_name, result, expect_error=None):
# Keep the jobdir around so we can inspect contents if an
# assert fails. It will be cleaned up anyway as it is contained
# in a tmp dir which gets cleaned up after the test.
self.executor_server.keep_jobdir = True
# Output extra ansible info so we might see errors.
self.executor_server.verbose = True
conf = textwrap.dedent(
"""
- job:
name: {job_name}
run: playbooks/{job_name}.yaml
roles:
- zuul: org/common-config
nodeset:
nodes:
- name: controller
label: whatever
- project:
check:
jobs:
- {job_name}
""".format(job_name=job_name))
file_dict = {'zuul.yaml': conf}
A = self.fake_gerrit.addFakeChange('org/project', 'master', 'A',
files=file_dict)
self.fake_gerrit.addEvent(A.getPatchsetCreatedEvent(1))
self.waitUntilSettled()
job = self.getJobFromHistory(job_name)
with self.jobLog(job):
build = self.history[-1]
self.assertEqual(build.result, result)
if expect_error:
path = os.path.join(self.test_root, build.uuid,
'work', 'logs', 'job-output.txt')
with open(path, 'r') as f:
self.assertIn(expect_error, f.read())
def test_assemble_module(self):
self._run_job('assemble-good', 'SUCCESS')
# assemble-delegate does multiple tests with various delegates and
# safe and non-safe paths. It asserts by itself within ansible so we
# expect SUCCESS here.
self._run_job('assemble-delegate', 'SUCCESS')
self._run_job('assemble-localhost', 'SUCCESS')
self._run_job('assemble-bad', 'FAILURE', ERROR_ACCESS_OUTSIDE)
self._run_job('assemble-bad-symlink', 'FAILURE', ERROR_ACCESS_OUTSIDE)
self._run_job('assemble-bad-dir-with-symlink', 'FAILURE',
ERROR_ACCESS_OUTSIDE)
def test_copy_module(self):
self._run_job('copy-good', 'SUCCESS')
# copy-delegate does multiple tests with various delegates and
# safe and non-safe paths. It asserts by itself within ansible so we
# expect SUCCESS here.
self._run_job('copy-delegate', 'SUCCESS')
self._run_job('copy-localhost', 'SUCCESS')
self._run_job('copy-bad', 'FAILURE', ERROR_ACCESS_OUTSIDE)
self._run_job('copy-bad-symlink', 'FAILURE', ERROR_ACCESS_OUTSIDE)
self._run_job('copy-bad-dir-with-symlink', 'FAILURE',
ERROR_ACCESS_OUTSIDE)
def test_includevars_module(self):
self._run_job('includevars-good', 'SUCCESS')
self._run_job('includevars-good-dir', 'SUCCESS')
self._run_job('includevars-bad', 'FAILURE', ERROR_ACCESS_OUTSIDE)
self._run_job('includevars-bad-symlink', 'FAILURE',
ERROR_ACCESS_OUTSIDE)
self._run_job('includevars-bad-dir', 'FAILURE', ERROR_ACCESS_OUTSIDE)
self._run_job('includevars-bad-dir-symlink', 'FAILURE',
ERROR_ACCESS_OUTSIDE)
self._run_job('includevars-bad-dir-with-symlink', 'FAILURE',
ERROR_ACCESS_OUTSIDE)
self._run_job('includevars-bad-dir-with-double-symlink', 'FAILURE',
ERROR_ACCESS_OUTSIDE)
def test_patch_module(self):
self._run_job('patch-good', 'SUCCESS')
# patch-delegate does multiple tests with various delegates and
# safe and non-safe paths. It asserts by itself within ansible so we
# expect SUCCESS here.
self._run_job('patch-delegate', 'SUCCESS')
self._run_job('patch-localhost', 'SUCCESS')
self._run_job('patch-bad', 'FAILURE', ERROR_ACCESS_OUTSIDE)
self._run_job('patch-bad-symlink', 'FAILURE', ERROR_ACCESS_OUTSIDE)
def test_script_module(self):
self._run_job('script-good', 'SUCCESS')
# script-delegate does multiple tests with various delegates. It
# asserts by itself within ansible so we
# expect SUCCESS here.
self._run_job('script-delegate', 'SUCCESS')
self._run_job('script-localhost', 'SUCCESS')
self._run_job('script-bad', 'FAILURE', ERROR_ACCESS_OUTSIDE)
self._run_job('script-bad-symlink', 'FAILURE', ERROR_ACCESS_OUTSIDE)
def test_template_module(self):
self._run_job('template-good', 'SUCCESS')
# template-delegate does multiple tests with various delegates and
# safe and non-safe paths. It asserts by itself within ansible so we
# expect SUCCESS here.
self._run_job('template-delegate', 'SUCCESS')
self._run_job('template-localhost', 'SUCCESS')
self._run_job('template-bad', 'FAILURE', ERROR_ACCESS_OUTSIDE)
self._run_job('template-bad-symlink', 'FAILURE', ERROR_ACCESS_OUTSIDE)
def test_unarchive_module(self):
self._run_job('unarchive-good', 'SUCCESS')
# template-delegate does multiple tests with various delegates and
# safe and non-safe paths. It asserts by itself within ansible so we
# expect SUCCESS here.
self._run_job('unarchive-delegate', 'SUCCESS')
self._run_job('unarchive-localhost', 'SUCCESS')
self._run_job('unarchive-bad', 'FAILURE', ERROR_ACCESS_OUTSIDE)
self._run_job('unarchive-bad-symlink', 'FAILURE', ERROR_ACCESS_OUTSIDE)