Add action plugins to restrict untrusted execution
There are actions undertaken by action plugins in normal ansible that allow for executing code on the host that ansible is executing on. We do not want to allow that for untrusted code, so add a set of action plugins that override the upstream ones and simply return errors. Additionally, we can trap for attempts to execute local commands in the normal action plugin by looking at remote_addr, connection and delegate_to. Change-Id: I57dbe5648a9dc6ec9147c8698ad46c4fa1326e5a
This commit is contained in:
parent
5f0f514215
commit
c231d939ea
@ -757,11 +757,12 @@ class RecordingAnsibleJob(zuul.launcher.server.AnsibleJob):
|
||||
self.launcher_server.lock.release()
|
||||
return result
|
||||
|
||||
def runAnsible(self, cmd, timeout):
|
||||
def runAnsible(self, cmd, timeout, secure=False):
|
||||
build = self.launcher_server.job_builds[self.job.unique]
|
||||
|
||||
if self.launcher_server._run_ansible:
|
||||
result = super(RecordingAnsibleJob, self).runAnsible(cmd, timeout)
|
||||
result = super(RecordingAnsibleJob, self).runAnsible(
|
||||
cmd, timeout, secure=secure)
|
||||
else:
|
||||
result = build.run()
|
||||
return result
|
||||
|
@ -3,3 +3,6 @@
|
||||
- file:
|
||||
path: "{{zuul._test.test_root}}/{{zuul.uuid}}.flag"
|
||||
state: touch
|
||||
- copy:
|
||||
src: "{{zuul._test.test_root}}/{{zuul.uuid}}.flag"
|
||||
dest: "{{zuul._test.test_root}}/{{zuul.uuid}}.copied"
|
||||
|
@ -1,6 +1,11 @@
|
||||
- job:
|
||||
parent: python27
|
||||
name: faillocal
|
||||
|
||||
- project:
|
||||
name: org/project
|
||||
|
||||
check:
|
||||
jobs:
|
||||
- python27
|
||||
- faillocal
|
||||
|
5
tests/fixtures/config/ansible/git/org_project/playbooks/faillocal.yaml
vendored
Normal file
5
tests/fixtures/config/ansible/git/org_project/playbooks/faillocal.yaml
vendored
Normal file
@ -0,0 +1,5 @@
|
||||
- hosts: all
|
||||
tasks:
|
||||
- copy:
|
||||
src: "{{zuul._test.test_root}}/{{zuul.uuid}}.flag"
|
||||
dest: "{{zuul._test.test_root}}/{{zuul.uuid}}.failed"
|
@ -125,10 +125,18 @@ class TestAnsible(AnsibleZuulTestCase):
|
||||
A = self.fake_gerrit.addFakeChange('org/project', 'master', 'A')
|
||||
self.fake_gerrit.addEvent(A.getPatchsetCreatedEvent(1))
|
||||
self.waitUntilSettled()
|
||||
build = self.getJobFromHistory('faillocal')
|
||||
self.assertEqual(build.result, 'FAILURE')
|
||||
build = self.getJobFromHistory('python27')
|
||||
self.assertEqual(build.result, 'SUCCESS')
|
||||
flag_path = os.path.join(self.test_root, build.uuid + '.flag')
|
||||
self.assertTrue(os.path.exists(flag_path))
|
||||
copied_path = os.path.join(self.test_root, build.uuid +
|
||||
'.copied')
|
||||
self.assertTrue(os.path.exists(copied_path))
|
||||
failed_path = os.path.join(self.test_root, build.uuid +
|
||||
'.failed')
|
||||
self.assertFalse(os.path.exists(failed_path))
|
||||
pre_flag_path = os.path.join(self.test_root, build.uuid +
|
||||
'.pre.flag')
|
||||
self.assertTrue(os.path.exists(pre_flag_path))
|
||||
|
0
zuul/ansible/action/__init__.py
Normal file
0
zuul/ansible/action/__init__.py
Normal file
25
zuul/ansible/action/add_host.py
Normal file
25
zuul/ansible/action/add_host.py
Normal file
@ -0,0 +1,25 @@
|
||||
# Copyright 2016 Red Hat, Inc.
|
||||
#
|
||||
# This module is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU General Public License as published by
|
||||
# the Free Software Foundation, either version 3 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# This software is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with this software. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
from zuul.ansible.plugins.action import add_host
|
||||
|
||||
|
||||
class ActionModule(add_host.ActionModule):
|
||||
|
||||
def run(self, tmp=None, task_vars=None):
|
||||
|
||||
return dict(
|
||||
failed=True,
|
||||
msg="Adding hosts to the inventory is prohibited")
|
1
zuul/ansible/action/asa_config.py
Symbolic link
1
zuul/ansible/action/asa_config.py
Symbolic link
@ -0,0 +1 @@
|
||||
network.py
|
1
zuul/ansible/action/asa_template.py
Symbolic link
1
zuul/ansible/action/asa_template.py
Symbolic link
@ -0,0 +1 @@
|
||||
network.py
|
30
zuul/ansible/action/assemble.py
Normal file
30
zuul/ansible/action/assemble.py
Normal file
@ -0,0 +1,30 @@
|
||||
# Copyright 2016 Red Hat, Inc.
|
||||
#
|
||||
# This module is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU General Public License as published by
|
||||
# the Free Software Foundation, either version 3 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# This software is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with this software. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
|
||||
from zuul.ansible import paths
|
||||
from zuul.ansible.plugins.action import assemble
|
||||
|
||||
|
||||
class ActionModule(assemble.ActionModule):
|
||||
|
||||
def run(self, tmp=None, task_vars=None):
|
||||
|
||||
source = self._task.args.get('src', None)
|
||||
remote_src = self._task.args.get('remote_src', False)
|
||||
|
||||
if not remote_src and not paths._is_safe_path(source):
|
||||
return paths._fail_dict(source)
|
||||
return super(ActionModule, self).run(tmp, task_vars)
|
30
zuul/ansible/action/copy.py
Normal file
30
zuul/ansible/action/copy.py
Normal file
@ -0,0 +1,30 @@
|
||||
# Copyright 2016 Red Hat, Inc.
|
||||
#
|
||||
# This module is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU General Public License as published by
|
||||
# the Free Software Foundation, either version 3 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# This software is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with this software. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
|
||||
from zuul.ansible import paths
|
||||
from zuul.ansible.plugins.action import copy
|
||||
|
||||
|
||||
class ActionModule(copy.ActionModule):
|
||||
|
||||
def run(self, tmp=None, task_vars=None):
|
||||
|
||||
source = self._task.args.get('src', None)
|
||||
remote_src = self._task.args.get('remote_src', False)
|
||||
|
||||
if not remote_src and not paths._is_safe_path(source):
|
||||
return paths._fail_dict(source)
|
||||
return super(ActionModule, self).run(tmp, task_vars)
|
1
zuul/ansible/action/dellos10_config.py
Symbolic link
1
zuul/ansible/action/dellos10_config.py
Symbolic link
@ -0,0 +1 @@
|
||||
network.py
|
1
zuul/ansible/action/dellos6_config.py
Symbolic link
1
zuul/ansible/action/dellos6_config.py
Symbolic link
@ -0,0 +1 @@
|
||||
network.py
|
1
zuul/ansible/action/dellos9_config.py
Symbolic link
1
zuul/ansible/action/dellos9_config.py
Symbolic link
@ -0,0 +1 @@
|
||||
network.py
|
1
zuul/ansible/action/eos_config.py
Symbolic link
1
zuul/ansible/action/eos_config.py
Symbolic link
@ -0,0 +1 @@
|
||||
network.py
|
1
zuul/ansible/action/eos_template.py
Symbolic link
1
zuul/ansible/action/eos_template.py
Symbolic link
@ -0,0 +1 @@
|
||||
network.py
|
29
zuul/ansible/action/fetch.py
Normal file
29
zuul/ansible/action/fetch.py
Normal file
@ -0,0 +1,29 @@
|
||||
# Copyright 2016 Red Hat, Inc.
|
||||
#
|
||||
# This module is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU General Public License as published by
|
||||
# the Free Software Foundation, either version 3 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# This software is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with this software. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
|
||||
from zuul.ansible import paths
|
||||
from zuul.ansible.plugins.action import fetch
|
||||
|
||||
|
||||
class ActionModule(fetch.ActionModule):
|
||||
|
||||
def run(self, tmp=None, task_vars=None):
|
||||
|
||||
dest = self._task.args.get('dest', None)
|
||||
|
||||
if dest and not paths._is_safe_path(dest):
|
||||
return paths._fail_dict(dest)
|
||||
return super(ActionModule, self).run(tmp, task_vars)
|
31
zuul/ansible/action/include_vars.py
Normal file
31
zuul/ansible/action/include_vars.py
Normal file
@ -0,0 +1,31 @@
|
||||
# Copyright 2016 Red Hat, Inc.
|
||||
#
|
||||
# This module is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU General Public License as published by
|
||||
# the Free Software Foundation, either version 3 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# This software is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with this software. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
|
||||
from zuul.ansible import paths
|
||||
from zuul.ansible.plugins.action import include_vars
|
||||
|
||||
|
||||
class ActionModule(include_vars.ActionModule):
|
||||
|
||||
def run(self, tmp=None, task_vars=None):
|
||||
|
||||
source_dir = self._task.args.get('dir', None)
|
||||
source_file = self._task.args.get('file', None)
|
||||
|
||||
for fileloc in (source_dir, source_file):
|
||||
if fileloc and not paths._is_safe_path(fileloc):
|
||||
return paths._fail_dict(fileloc)
|
||||
return super(ActionModule, self).run(tmp, task_vars)
|
1
zuul/ansible/action/ios_config.py
Symbolic link
1
zuul/ansible/action/ios_config.py
Symbolic link
@ -0,0 +1 @@
|
||||
network.py
|
1
zuul/ansible/action/ios_template.py
Symbolic link
1
zuul/ansible/action/ios_template.py
Symbolic link
@ -0,0 +1 @@
|
||||
network.py
|
1
zuul/ansible/action/iosxr_config.py
Symbolic link
1
zuul/ansible/action/iosxr_config.py
Symbolic link
@ -0,0 +1 @@
|
||||
network.py
|
1
zuul/ansible/action/iosxr_template.py
Symbolic link
1
zuul/ansible/action/iosxr_template.py
Symbolic link
@ -0,0 +1 @@
|
||||
network.py
|
1
zuul/ansible/action/junos_config.py
Symbolic link
1
zuul/ansible/action/junos_config.py
Symbolic link
@ -0,0 +1 @@
|
||||
network.py
|
1
zuul/ansible/action/junos_template.py
Symbolic link
1
zuul/ansible/action/junos_template.py
Symbolic link
@ -0,0 +1 @@
|
||||
network.py
|
1
zuul/ansible/action/net_config.py
Symbolic link
1
zuul/ansible/action/net_config.py
Symbolic link
@ -0,0 +1 @@
|
||||
network.py
|
1
zuul/ansible/action/net_template.py
Symbolic link
1
zuul/ansible/action/net_template.py
Symbolic link
@ -0,0 +1 @@
|
||||
network.py
|
24
zuul/ansible/action/network.py
Normal file
24
zuul/ansible/action/network.py
Normal file
@ -0,0 +1,24 @@
|
||||
# Copyright 2016 Red Hat, Inc.
|
||||
#
|
||||
# This module is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU General Public License as published by
|
||||
# the Free Software Foundation, either version 3 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# This software is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with this software. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
|
||||
from zuul.ansible.plugins.action import network
|
||||
|
||||
|
||||
class ActionModule(network.ActionModule):
|
||||
|
||||
def run(self, tmp=None, task_vars=None):
|
||||
|
||||
return dict(failed=True, msg='Use of network modules is prohibited')
|
33
zuul/ansible/action/normal.py
Normal file
33
zuul/ansible/action/normal.py
Normal file
@ -0,0 +1,33 @@
|
||||
# Copyright 2016 Red Hat, Inc.
|
||||
#
|
||||
# This module is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU General Public License as published by
|
||||
# the Free Software Foundation, either version 3 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# This software is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with this software. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
|
||||
from zuul.ansible.plugins.action import normal
|
||||
|
||||
|
||||
class ActionModule(normal.ActionModule):
|
||||
|
||||
def run(self, tmp=None, task_vars=None):
|
||||
|
||||
if (self._play_context.connection == 'local'
|
||||
or self._play_context.remote_addr == 'localhost'
|
||||
or self._play_context.remote_addr.startswith('127.')
|
||||
or self._task.delegate_to == 'localhost'
|
||||
or (self._task.delegate_to
|
||||
and self._task.delegate_to.startswtih('127.'))):
|
||||
return dict(
|
||||
failed=True,
|
||||
msg="Executing local code is prohibited")
|
||||
return super(ActionModule, self).run(tmp, task_vars)
|
1
zuul/ansible/action/nxos_config.py
Symbolic link
1
zuul/ansible/action/nxos_config.py
Symbolic link
@ -0,0 +1 @@
|
||||
network.py
|
1
zuul/ansible/action/nxos_template.py
Symbolic link
1
zuul/ansible/action/nxos_template.py
Symbolic link
@ -0,0 +1 @@
|
||||
network.py
|
1
zuul/ansible/action/ops_config.py
Symbolic link
1
zuul/ansible/action/ops_config.py
Symbolic link
@ -0,0 +1 @@
|
||||
network.py
|
1
zuul/ansible/action/ops_template.py
Symbolic link
1
zuul/ansible/action/ops_template.py
Symbolic link
@ -0,0 +1 @@
|
||||
network.py
|
30
zuul/ansible/action/patch.py
Normal file
30
zuul/ansible/action/patch.py
Normal file
@ -0,0 +1,30 @@
|
||||
# Copyright 2016 Red Hat, Inc.
|
||||
#
|
||||
# This module is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU General Public License as published by
|
||||
# the Free Software Foundation, either version 3 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# This software is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with this software. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
|
||||
from zuul.ansible import paths
|
||||
from zuul.ansible.plugins.action import patch
|
||||
|
||||
|
||||
class ActionModule(patch.ActionModule):
|
||||
|
||||
def run(self, tmp=None, task_vars=None):
|
||||
|
||||
source = self._task.args.get('src', None)
|
||||
remote_src = self._task.args.get('remote_src', False)
|
||||
|
||||
if not remote_src and not paths._is_safe_path(source):
|
||||
return paths._fail_dict(source)
|
||||
return super(ActionModule, self).run(tmp, task_vars)
|
34
zuul/ansible/action/script.py
Normal file
34
zuul/ansible/action/script.py
Normal file
@ -0,0 +1,34 @@
|
||||
# Copyright 2016 Red Hat, Inc.
|
||||
#
|
||||
# This module is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU General Public License as published by
|
||||
# the Free Software Foundation, either version 3 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# This software is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with this software. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
|
||||
from zuul.ansible import paths
|
||||
from zuul.ansible.plugins.action import copy
|
||||
|
||||
|
||||
class ActionModule(copy.ActionModule):
|
||||
|
||||
def run(self, tmp=None, task_vars=None):
|
||||
|
||||
# the script name is the first item in the raw params, so we split it
|
||||
# out now so we know the file name we need to transfer to the remote,
|
||||
# and everything else is an argument to the script which we need later
|
||||
# to append to the remote command
|
||||
parts = self._task.args.get('_raw_params', '').strip().split()
|
||||
source = parts[0]
|
||||
|
||||
if not paths._is_safe_path(source):
|
||||
return paths._fail_dict(source)
|
||||
return super(ActionModule, self).run(tmp, task_vars)
|
1
zuul/ansible/action/sros_config.py
Symbolic link
1
zuul/ansible/action/sros_config.py
Symbolic link
@ -0,0 +1 @@
|
||||
network.py
|
33
zuul/ansible/action/synchronize.py
Normal file
33
zuul/ansible/action/synchronize.py
Normal file
@ -0,0 +1,33 @@
|
||||
# Copyright 2016 Red Hat, Inc.
|
||||
#
|
||||
# This module is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU General Public License as published by
|
||||
# the Free Software Foundation, either version 3 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# This software is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with this software. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
|
||||
from zuul.ansible import paths
|
||||
from zuul.ansible.plugins.action import synchronize
|
||||
|
||||
|
||||
class ActionModule(synchronize.ActionModule):
|
||||
|
||||
def run(self, tmp=None, task_vars=None):
|
||||
|
||||
source = self._task.args.get('src', None)
|
||||
dest = self._task.args.get('dest', None)
|
||||
pull = self._task.args.get('pull', False)
|
||||
|
||||
if not pull and not paths._is_safe_path(source):
|
||||
return paths._fail_dict(source, prefix='Syncing files from')
|
||||
if pull and not paths._is_safe_path(dest):
|
||||
return paths._fail_dict(dest, prefix='Syncing files to')
|
||||
return super(ActionModule, self).run(tmp, task_vars)
|
29
zuul/ansible/action/template.py
Normal file
29
zuul/ansible/action/template.py
Normal file
@ -0,0 +1,29 @@
|
||||
# Copyright 2016 Red Hat, Inc.
|
||||
#
|
||||
# This module is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU General Public License as published by
|
||||
# the Free Software Foundation, either version 3 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# This software is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with this software. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
|
||||
from zuul.ansible import paths
|
||||
from zuul.ansible.plugins.action import template
|
||||
|
||||
|
||||
class ActionModule(template.ActionModule):
|
||||
|
||||
def run(self, tmp=None, task_vars=None):
|
||||
|
||||
source = self._task.args.get('src', None)
|
||||
|
||||
if not paths._is_safe_path(source):
|
||||
return paths._fail_dict(source)
|
||||
return super(ActionModule, self).run(tmp, task_vars)
|
30
zuul/ansible/action/unarchive.py
Normal file
30
zuul/ansible/action/unarchive.py
Normal file
@ -0,0 +1,30 @@
|
||||
# Copyright 2016 Red Hat, Inc.
|
||||
#
|
||||
# This module is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU General Public License as published by
|
||||
# the Free Software Foundation, either version 3 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# This software is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with this software. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
|
||||
from zuul.ansible import paths
|
||||
from zuul.ansible.plugins.action import unarchive
|
||||
|
||||
|
||||
class ActionModule(unarchive.ActionModule):
|
||||
|
||||
def run(self, tmp=None, task_vars=None):
|
||||
|
||||
source = self._task.args.get('src', None)
|
||||
remote_src = self._task.args.get('remote_src', False)
|
||||
|
||||
if not remote_src and not paths._is_safe_path(source):
|
||||
return paths._fail_dict(source)
|
||||
return super(ActionModule, self).run(tmp, task_vars)
|
1
zuul/ansible/action/vyos_config.py
Symbolic link
1
zuul/ansible/action/vyos_config.py
Symbolic link
@ -0,0 +1 @@
|
||||
network.py
|
30
zuul/ansible/action/win_copy.py
Normal file
30
zuul/ansible/action/win_copy.py
Normal file
@ -0,0 +1,30 @@
|
||||
# Copyright 2016 Red Hat, Inc.
|
||||
#
|
||||
# This module is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU General Public License as published by
|
||||
# the Free Software Foundation, either version 3 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# This software is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with this software. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
|
||||
from zuul.ansible import paths
|
||||
from zuul.ansible.plugins.action import win_copy
|
||||
|
||||
|
||||
class ActionModule(win_copy.ActionModule):
|
||||
|
||||
def run(self, tmp=None, task_vars=None):
|
||||
|
||||
source = self._task.args.get('src', None)
|
||||
remote_src = self._task.args.get('remote_src', False)
|
||||
|
||||
if not remote_src and not paths._is_safe_path(source):
|
||||
return paths._fail_dict(source)
|
||||
return super(ActionModule, self).run(tmp, task_vars)
|
30
zuul/ansible/action/win_template.py
Normal file
30
zuul/ansible/action/win_template.py
Normal file
@ -0,0 +1,30 @@
|
||||
# Copyright 2016 Red Hat, Inc.
|
||||
#
|
||||
# This module is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU General Public License as published by
|
||||
# the Free Software Foundation, either version 3 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# This software is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with this software. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
|
||||
from zuul.ansible import paths
|
||||
from zuul.ansible.plugins.action import win_template
|
||||
|
||||
|
||||
class ActionModule(win_template.ActionModule):
|
||||
|
||||
def run(self, tmp=None, task_vars=None):
|
||||
|
||||
source = self._task.args.get('src', None)
|
||||
remote_src = self._task.args.get('remote_src', False)
|
||||
|
||||
if not remote_src and not paths._is_safe_path(source):
|
||||
return paths._fail_dict(source)
|
||||
return super(ActionModule, self).run(tmp, task_vars)
|
33
zuul/ansible/paths.py
Normal file
33
zuul/ansible/paths.py
Normal file
@ -0,0 +1,33 @@
|
||||
# Copyright 2016 Red Hat, Inc.
|
||||
#
|
||||
# This module is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU General Public License as published by
|
||||
# the Free Software Foundation, either version 3 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# This software is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with this software. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
import os
|
||||
|
||||
|
||||
def _is_safe_path(path):
|
||||
if os.path.isabs(path):
|
||||
return False
|
||||
if not os.path.abspath(os.path.expanduser(path)).startswith(
|
||||
os.path.abspath(os.path.curdir)):
|
||||
return False
|
||||
return True
|
||||
|
||||
|
||||
def _fail_dict(path, prefix='Accessing files from'):
|
||||
return dict(
|
||||
failed=True,
|
||||
path=path,
|
||||
msg="{prefix} outside the working dir is prohibited".format(
|
||||
prefix=prefix))
|
@ -29,6 +29,7 @@ import yaml
|
||||
import gear
|
||||
|
||||
import zuul.merger.merger
|
||||
import zuul.ansible.action
|
||||
import zuul.ansible.library
|
||||
from zuul.lib import commandsocket
|
||||
|
||||
@ -85,6 +86,8 @@ class JobDir(object):
|
||||
os.makedirs(self.git_root)
|
||||
self.ansible_root = os.path.join(self.root, 'ansible')
|
||||
os.makedirs(self.ansible_root)
|
||||
self.secure_ansible_root = os.path.join(self.ansible_root, 'secure')
|
||||
os.makedirs(self.secure_ansible_root)
|
||||
self.known_hosts = os.path.join(self.ansible_root, 'known_hosts')
|
||||
self.inventory = os.path.join(self.ansible_root, 'inventory')
|
||||
self.vars = os.path.join(self.ansible_root, 'vars.yaml')
|
||||
@ -93,6 +96,8 @@ class JobDir(object):
|
||||
self.pre_playbooks = []
|
||||
self.post_playbooks = []
|
||||
self.config = os.path.join(self.ansible_root, 'ansible.cfg')
|
||||
self.secure_config = os.path.join(
|
||||
self.secure_ansible_root, 'ansible.cfg')
|
||||
self.ansible_log = os.path.join(self.ansible_root, 'ansible_log.txt')
|
||||
|
||||
def addPrePlaybook(self):
|
||||
@ -238,11 +243,18 @@ class LaunchServer(object):
|
||||
self.library_dir = os.path.join(ansible_dir, 'library')
|
||||
if not os.path.exists(self.library_dir):
|
||||
os.makedirs(self.library_dir)
|
||||
self.action_dir = os.path.join(ansible_dir, 'action')
|
||||
if not os.path.exists(self.action_dir):
|
||||
os.makedirs(self.action_dir)
|
||||
|
||||
library_path = os.path.dirname(os.path.abspath(
|
||||
zuul.ansible.library.__file__))
|
||||
for fn in os.listdir(library_path):
|
||||
shutil.copy(os.path.join(library_path, fn), self.library_dir)
|
||||
action_path = os.path.dirname(os.path.abspath(
|
||||
zuul.ansible.action.__file__))
|
||||
for fn in os.listdir(action_path):
|
||||
shutil.copy(os.path.join(action_path, fn), self.action_dir)
|
||||
|
||||
self.job_workers = {}
|
||||
|
||||
@ -580,10 +592,28 @@ class AnsibleJob(object):
|
||||
hosts.append((node['name'], dict(ansible_connection='local')))
|
||||
return hosts
|
||||
|
||||
def findPlaybook(self, path, required=False):
|
||||
def _blockPluginDirs(self, fn):
|
||||
'''Prevent execution of playbooks with plugins
|
||||
|
||||
Plugins are loaded from roles and also if there is a plugin dir
|
||||
adjacent to the playbook. Role exclusion will be handled elsewhere,
|
||||
but while we're looking for playbooks, throw an error if the playbook
|
||||
exists in a location that would cause a plugin to get loaded if the
|
||||
playbook is not in a secure repository.
|
||||
'''
|
||||
playbook_dir = os.path.dirname(os.path.abspath(fn))
|
||||
for entry in os.listdir(playbook_dir):
|
||||
if os.path.isdir(entry) and entry.endswith('_plugins'):
|
||||
raise Exception(
|
||||
"Ansible plugin dir %s found adjacent to playbook %s in"
|
||||
" non-secure repo." % (entry, fn))
|
||||
|
||||
def findPlaybook(self, path, required=False, secure=False):
|
||||
for ext in ['.yaml', '.yml']:
|
||||
fn = path + ext
|
||||
if os.path.exists(fn):
|
||||
if not secure:
|
||||
self._blockPluginDirs(fn)
|
||||
return fn
|
||||
if required:
|
||||
raise Exception("Unable to find playbook %s" % path)
|
||||
@ -631,7 +661,10 @@ class AnsibleJob(object):
|
||||
path = os.path.join(self.jobdir.git_root,
|
||||
project.name,
|
||||
playbook['path'])
|
||||
jobdir_playbook.path = self.findPlaybook(path, main)
|
||||
jobdir_playbook.path = self.findPlaybook(
|
||||
path,
|
||||
required=main,
|
||||
secure=playbook['secure'])
|
||||
return
|
||||
# The playbook repo is either a config repo, or it isn't in
|
||||
# the stack of changes we are testing, so check out the branch
|
||||
@ -643,7 +676,10 @@ class AnsibleJob(object):
|
||||
path = os.path.join(jobdir_playbook.root,
|
||||
project.name,
|
||||
playbook['path'])
|
||||
jobdir_playbook.path = self.findPlaybook(path, main)
|
||||
jobdir_playbook.path = self.findPlaybook(
|
||||
path,
|
||||
required=main,
|
||||
secure=playbook['secure'])
|
||||
|
||||
def prepareAnsibleFiles(self, args):
|
||||
with open(self.jobdir.inventory, 'w') as inventory:
|
||||
@ -657,7 +693,11 @@ class AnsibleJob(object):
|
||||
zuul_vars = dict(zuul=args['zuul'])
|
||||
vars_yaml.write(
|
||||
yaml.safe_dump(zuul_vars, default_flow_style=False))
|
||||
with open(self.jobdir.config, 'w') as config:
|
||||
self.writeAnsibleConfig(self.jobdir.config)
|
||||
self.writeAnsibleConfig(self.jobdir.secure_config, secure=True)
|
||||
|
||||
def writeAnsibleConfig(self, config_path, secure=False):
|
||||
with open(config_path, 'w') as config:
|
||||
config.write('[defaults]\n')
|
||||
config.write('hostfile = %s\n' % self.jobdir.inventory)
|
||||
config.write('local_tmp = %s/.ansible/local_tmp\n' %
|
||||
@ -673,6 +713,9 @@ class AnsibleJob(object):
|
||||
# bump the timeout because busy nodes may take more than
|
||||
# 10s to respond
|
||||
config.write('timeout = 30\n')
|
||||
if not secure:
|
||||
config.write('action_plugins = %s\n'
|
||||
% self.launcher_server.action_dir)
|
||||
|
||||
config.write('[ssh_connection]\n')
|
||||
# NB: when setting pipelining = True, keep_remote_files
|
||||
@ -706,17 +749,22 @@ class AnsibleJob(object):
|
||||
self.log.exception("Exception while killing "
|
||||
"ansible process:")
|
||||
|
||||
def runAnsible(self, cmd, timeout):
|
||||
def runAnsible(self, cmd, timeout, secure=False):
|
||||
env_copy = os.environ.copy()
|
||||
env_copy['LOGNAME'] = 'zuul'
|
||||
|
||||
if secure:
|
||||
cwd = self.jobdir.secure_ansible_root
|
||||
else:
|
||||
cwd = self.jobdir.ansible_root
|
||||
|
||||
with self.proc_lock:
|
||||
if self.aborted:
|
||||
return (self.RESULT_ABORTED, None)
|
||||
self.log.debug("Ansible command: %s" % (cmd,))
|
||||
self.proc = subprocess.Popen(
|
||||
cmd,
|
||||
cwd=self.jobdir.ansible_root,
|
||||
cwd=cwd,
|
||||
stdout=subprocess.PIPE,
|
||||
stderr=subprocess.STDOUT,
|
||||
preexec_fn=os.setsid,
|
||||
@ -771,4 +819,5 @@ class AnsibleJob(object):
|
||||
# TODOv3: get this from the job
|
||||
timeout = 60
|
||||
|
||||
return self.runAnsible(cmd, timeout)
|
||||
return self.runAnsible(
|
||||
cmd=cmd, timeout=timeout, secure=playbook.secure)
|
||||
|
Loading…
Reference in New Issue
Block a user