Merge "Add action plugins to restrict untrusted execution" into feature/zuulv3
diff --git a/tests/base.py b/tests/base.py
index 41fa29f..59cc9ae 100755
--- a/tests/base.py
+++ b/tests/base.py
@@ -757,11 +757,12 @@
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
diff --git a/tests/fixtures/config/ansible/git/common-config/playbooks/python27.yaml b/tests/fixtures/config/ansible/git/common-config/playbooks/python27.yaml
index 6b0af99..408810e 100644
--- a/tests/fixtures/config/ansible/git/common-config/playbooks/python27.yaml
+++ b/tests/fixtures/config/ansible/git/common-config/playbooks/python27.yaml
@@ -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"
diff --git a/tests/fixtures/config/ansible/git/org_project/.zuul.yaml b/tests/fixtures/config/ansible/git/org_project/.zuul.yaml
index 6bedb07..6abfc47 100644
--- a/tests/fixtures/config/ansible/git/org_project/.zuul.yaml
+++ b/tests/fixtures/config/ansible/git/org_project/.zuul.yaml
@@ -1,6 +1,11 @@
+- job:
+ parent: python27
+ name: faillocal
+
- project:
name: org/project
check:
jobs:
- python27
+ - faillocal
diff --git a/tests/fixtures/config/ansible/git/org_project/playbooks/faillocal.yaml b/tests/fixtures/config/ansible/git/org_project/playbooks/faillocal.yaml
new file mode 100644
index 0000000..6689e18
--- /dev/null
+++ b/tests/fixtures/config/ansible/git/org_project/playbooks/faillocal.yaml
@@ -0,0 +1,5 @@
+- hosts: all
+ tasks:
+ - copy:
+ src: "{{zuul._test.test_root}}/{{zuul.uuid}}.flag"
+ dest: "{{zuul._test.test_root}}/{{zuul.uuid}}.failed"
diff --git a/tests/unit/test_v3.py b/tests/unit/test_v3.py
index 0ba5ff8..74d69c9 100644
--- a/tests/unit/test_v3.py
+++ b/tests/unit/test_v3.py
@@ -125,10 +125,18 @@
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))
diff --git a/zuul/ansible/action/__init__.py b/zuul/ansible/action/__init__.py
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/zuul/ansible/action/__init__.py
diff --git a/zuul/ansible/action/add_host.py b/zuul/ansible/action/add_host.py
new file mode 100644
index 0000000..e41e4e1
--- /dev/null
+++ b/zuul/ansible/action/add_host.py
@@ -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")
diff --git a/zuul/ansible/action/asa_config.py b/zuul/ansible/action/asa_config.py
new file mode 120000
index 0000000..7a739ba
--- /dev/null
+++ b/zuul/ansible/action/asa_config.py
@@ -0,0 +1 @@
+network.py
\ No newline at end of file
diff --git a/zuul/ansible/action/asa_template.py b/zuul/ansible/action/asa_template.py
new file mode 120000
index 0000000..7a739ba
--- /dev/null
+++ b/zuul/ansible/action/asa_template.py
@@ -0,0 +1 @@
+network.py
\ No newline at end of file
diff --git a/zuul/ansible/action/assemble.py b/zuul/ansible/action/assemble.py
new file mode 100644
index 0000000..d0bff37
--- /dev/null
+++ b/zuul/ansible/action/assemble.py
@@ -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)
diff --git a/zuul/ansible/action/copy.py b/zuul/ansible/action/copy.py
new file mode 100644
index 0000000..5dc9fa8
--- /dev/null
+++ b/zuul/ansible/action/copy.py
@@ -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)
diff --git a/zuul/ansible/action/dellos10_config.py b/zuul/ansible/action/dellos10_config.py
new file mode 120000
index 0000000..7a739ba
--- /dev/null
+++ b/zuul/ansible/action/dellos10_config.py
@@ -0,0 +1 @@
+network.py
\ No newline at end of file
diff --git a/zuul/ansible/action/dellos6_config.py b/zuul/ansible/action/dellos6_config.py
new file mode 120000
index 0000000..7a739ba
--- /dev/null
+++ b/zuul/ansible/action/dellos6_config.py
@@ -0,0 +1 @@
+network.py
\ No newline at end of file
diff --git a/zuul/ansible/action/dellos9_config.py b/zuul/ansible/action/dellos9_config.py
new file mode 120000
index 0000000..7a739ba
--- /dev/null
+++ b/zuul/ansible/action/dellos9_config.py
@@ -0,0 +1 @@
+network.py
\ No newline at end of file
diff --git a/zuul/ansible/action/eos_config.py b/zuul/ansible/action/eos_config.py
new file mode 120000
index 0000000..7a739ba
--- /dev/null
+++ b/zuul/ansible/action/eos_config.py
@@ -0,0 +1 @@
+network.py
\ No newline at end of file
diff --git a/zuul/ansible/action/eos_template.py b/zuul/ansible/action/eos_template.py
new file mode 120000
index 0000000..7a739ba
--- /dev/null
+++ b/zuul/ansible/action/eos_template.py
@@ -0,0 +1 @@
+network.py
\ No newline at end of file
diff --git a/zuul/ansible/action/fetch.py b/zuul/ansible/action/fetch.py
new file mode 100644
index 0000000..fe06c3b
--- /dev/null
+++ b/zuul/ansible/action/fetch.py
@@ -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)
diff --git a/zuul/ansible/action/include_vars.py b/zuul/ansible/action/include_vars.py
new file mode 100644
index 0000000..aa0e7d8
--- /dev/null
+++ b/zuul/ansible/action/include_vars.py
@@ -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)
diff --git a/zuul/ansible/action/ios_config.py b/zuul/ansible/action/ios_config.py
new file mode 120000
index 0000000..7a739ba
--- /dev/null
+++ b/zuul/ansible/action/ios_config.py
@@ -0,0 +1 @@
+network.py
\ No newline at end of file
diff --git a/zuul/ansible/action/ios_template.py b/zuul/ansible/action/ios_template.py
new file mode 120000
index 0000000..7a739ba
--- /dev/null
+++ b/zuul/ansible/action/ios_template.py
@@ -0,0 +1 @@
+network.py
\ No newline at end of file
diff --git a/zuul/ansible/action/iosxr_config.py b/zuul/ansible/action/iosxr_config.py
new file mode 120000
index 0000000..7a739ba
--- /dev/null
+++ b/zuul/ansible/action/iosxr_config.py
@@ -0,0 +1 @@
+network.py
\ No newline at end of file
diff --git a/zuul/ansible/action/iosxr_template.py b/zuul/ansible/action/iosxr_template.py
new file mode 120000
index 0000000..7a739ba
--- /dev/null
+++ b/zuul/ansible/action/iosxr_template.py
@@ -0,0 +1 @@
+network.py
\ No newline at end of file
diff --git a/zuul/ansible/action/junos_config.py b/zuul/ansible/action/junos_config.py
new file mode 120000
index 0000000..7a739ba
--- /dev/null
+++ b/zuul/ansible/action/junos_config.py
@@ -0,0 +1 @@
+network.py
\ No newline at end of file
diff --git a/zuul/ansible/action/junos_template.py b/zuul/ansible/action/junos_template.py
new file mode 120000
index 0000000..7a739ba
--- /dev/null
+++ b/zuul/ansible/action/junos_template.py
@@ -0,0 +1 @@
+network.py
\ No newline at end of file
diff --git a/zuul/ansible/action/net_config.py b/zuul/ansible/action/net_config.py
new file mode 120000
index 0000000..7a739ba
--- /dev/null
+++ b/zuul/ansible/action/net_config.py
@@ -0,0 +1 @@
+network.py
\ No newline at end of file
diff --git a/zuul/ansible/action/net_template.py b/zuul/ansible/action/net_template.py
new file mode 120000
index 0000000..7a739ba
--- /dev/null
+++ b/zuul/ansible/action/net_template.py
@@ -0,0 +1 @@
+network.py
\ No newline at end of file
diff --git a/zuul/ansible/action/network.py b/zuul/ansible/action/network.py
new file mode 100644
index 0000000..31a8739
--- /dev/null
+++ b/zuul/ansible/action/network.py
@@ -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')
diff --git a/zuul/ansible/action/normal.py b/zuul/ansible/action/normal.py
new file mode 100644
index 0000000..d4b2396
--- /dev/null
+++ b/zuul/ansible/action/normal.py
@@ -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)
diff --git a/zuul/ansible/action/nxos_config.py b/zuul/ansible/action/nxos_config.py
new file mode 120000
index 0000000..7a739ba
--- /dev/null
+++ b/zuul/ansible/action/nxos_config.py
@@ -0,0 +1 @@
+network.py
\ No newline at end of file
diff --git a/zuul/ansible/action/nxos_template.py b/zuul/ansible/action/nxos_template.py
new file mode 120000
index 0000000..7a739ba
--- /dev/null
+++ b/zuul/ansible/action/nxos_template.py
@@ -0,0 +1 @@
+network.py
\ No newline at end of file
diff --git a/zuul/ansible/action/ops_config.py b/zuul/ansible/action/ops_config.py
new file mode 120000
index 0000000..7a739ba
--- /dev/null
+++ b/zuul/ansible/action/ops_config.py
@@ -0,0 +1 @@
+network.py
\ No newline at end of file
diff --git a/zuul/ansible/action/ops_template.py b/zuul/ansible/action/ops_template.py
new file mode 120000
index 0000000..7a739ba
--- /dev/null
+++ b/zuul/ansible/action/ops_template.py
@@ -0,0 +1 @@
+network.py
\ No newline at end of file
diff --git a/zuul/ansible/action/patch.py b/zuul/ansible/action/patch.py
new file mode 100644
index 0000000..d630844
--- /dev/null
+++ b/zuul/ansible/action/patch.py
@@ -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)
diff --git a/zuul/ansible/action/script.py b/zuul/ansible/action/script.py
new file mode 100644
index 0000000..bd3d5d5
--- /dev/null
+++ b/zuul/ansible/action/script.py
@@ -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)
diff --git a/zuul/ansible/action/sros_config.py b/zuul/ansible/action/sros_config.py
new file mode 120000
index 0000000..7a739ba
--- /dev/null
+++ b/zuul/ansible/action/sros_config.py
@@ -0,0 +1 @@
+network.py
\ No newline at end of file
diff --git a/zuul/ansible/action/synchronize.py b/zuul/ansible/action/synchronize.py
new file mode 100644
index 0000000..cbb7ea2
--- /dev/null
+++ b/zuul/ansible/action/synchronize.py
@@ -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)
diff --git a/zuul/ansible/action/template.py b/zuul/ansible/action/template.py
new file mode 100644
index 0000000..96471ae
--- /dev/null
+++ b/zuul/ansible/action/template.py
@@ -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)
diff --git a/zuul/ansible/action/unarchive.py b/zuul/ansible/action/unarchive.py
new file mode 100644
index 0000000..c3f6e91
--- /dev/null
+++ b/zuul/ansible/action/unarchive.py
@@ -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)
diff --git a/zuul/ansible/action/vyos_config.py b/zuul/ansible/action/vyos_config.py
new file mode 120000
index 0000000..7a739ba
--- /dev/null
+++ b/zuul/ansible/action/vyos_config.py
@@ -0,0 +1 @@
+network.py
\ No newline at end of file
diff --git a/zuul/ansible/action/win_copy.py b/zuul/ansible/action/win_copy.py
new file mode 100644
index 0000000..eef3a1c
--- /dev/null
+++ b/zuul/ansible/action/win_copy.py
@@ -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)
diff --git a/zuul/ansible/action/win_template.py b/zuul/ansible/action/win_template.py
new file mode 100644
index 0000000..2a47216
--- /dev/null
+++ b/zuul/ansible/action/win_template.py
@@ -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)
diff --git a/zuul/ansible/paths.py b/zuul/ansible/paths.py
new file mode 100644
index 0000000..2bd0181
--- /dev/null
+++ b/zuul/ansible/paths.py
@@ -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))
diff --git a/zuul/launcher/server.py b/zuul/launcher/server.py
index df71cc9..ce0ced6 100644
--- a/zuul/launcher/server.py
+++ b/zuul/launcher/server.py
@@ -29,6 +29,7 @@
import gear
import zuul.merger.merger
+import zuul.ansible.action
import zuul.ansible.library
from zuul.lib import commandsocket
@@ -85,6 +86,8 @@
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 @@
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 @@
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 @@
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 @@
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 @@
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 @@
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 @@
# 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 @@
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 @@
# TODOv3: get this from the job
timeout = 60
- return self.runAnsible(cmd, timeout)
+ return self.runAnsible(
+ cmd=cmd, timeout=timeout, secure=playbook.secure)