mirror of
https://github.com/ansible/awx.git
synced 2026-03-18 17:37:30 -02:30
only allow the task dispatch worker to import and run decorated tasks
this _technically_ prevents a remote code exploit where a user who has access to publish AMQP messages to the dispatch queue could craft a special message that would import and run arbitrary Python functions; that said, the types of user with this privilege level are generally _already_ the awx user (so they can already do this by hand if they want)
This commit is contained in:
@@ -30,11 +30,18 @@ class TaskWorker(BaseWorker):
|
|||||||
awx.main.tasks.delete_inventory
|
awx.main.tasks.delete_inventory
|
||||||
awx.main.tasks.RunProjectUpdate
|
awx.main.tasks.RunProjectUpdate
|
||||||
'''
|
'''
|
||||||
|
if not task.startswith('awx.'):
|
||||||
|
raise ValueError('{} is not a valid awx task'.format(task))
|
||||||
module, target = task.rsplit('.', 1)
|
module, target = task.rsplit('.', 1)
|
||||||
module = importlib.import_module(module)
|
module = importlib.import_module(module)
|
||||||
_call = None
|
_call = None
|
||||||
if hasattr(module, target):
|
if hasattr(module, target):
|
||||||
_call = getattr(module, target, None)
|
_call = getattr(module, target, None)
|
||||||
|
if not (
|
||||||
|
hasattr(_call, 'apply_async') and hasattr(_call, 'delay')
|
||||||
|
):
|
||||||
|
raise ValueError('{} is not decorated with @task()'.format(task))
|
||||||
|
|
||||||
return _call
|
return _call
|
||||||
|
|
||||||
def run_callable(self, body):
|
def run_callable(self, body):
|
||||||
@@ -78,6 +85,7 @@ class TaskWorker(BaseWorker):
|
|||||||
try:
|
try:
|
||||||
result = self.run_callable(body)
|
result = self.run_callable(body)
|
||||||
except Exception as exc:
|
except Exception as exc:
|
||||||
|
result = exc
|
||||||
|
|
||||||
try:
|
try:
|
||||||
if getattr(exc, 'is_awx_task_error', False):
|
if getattr(exc, 'is_awx_task_error', False):
|
||||||
|
|||||||
@@ -14,6 +14,10 @@ from awx.main.dispatch.publish import task
|
|||||||
from awx.main.dispatch.worker import BaseWorker, TaskWorker
|
from awx.main.dispatch.worker import BaseWorker, TaskWorker
|
||||||
|
|
||||||
|
|
||||||
|
def restricted(a, b):
|
||||||
|
raise AssertionError("This code should not run because it isn't decorated with @task")
|
||||||
|
|
||||||
|
|
||||||
@task()
|
@task()
|
||||||
def add(a, b):
|
def add(a, b):
|
||||||
return a + b
|
return a + b
|
||||||
@@ -25,6 +29,11 @@ class BaseTask(object):
|
|||||||
return add(a, b)
|
return add(a, b)
|
||||||
|
|
||||||
|
|
||||||
|
class Restricted(object):
|
||||||
|
def run(self, a, b):
|
||||||
|
raise AssertionError("This code should not run because it isn't decorated with @task")
|
||||||
|
|
||||||
|
|
||||||
@task()
|
@task()
|
||||||
class Adder(BaseTask):
|
class Adder(BaseTask):
|
||||||
def run(self, a, b):
|
def run(self, a, b):
|
||||||
@@ -262,6 +271,14 @@ class TestTaskDispatcher:
|
|||||||
})
|
})
|
||||||
assert result == 4
|
assert result == 4
|
||||||
|
|
||||||
|
def test_function_dispatch_must_be_decorated(self):
|
||||||
|
result = self.tm.perform_work({
|
||||||
|
'task': 'awx.main.tests.functional.test_dispatch.restricted',
|
||||||
|
'args': [2, 2]
|
||||||
|
})
|
||||||
|
assert isinstance(result, ValueError)
|
||||||
|
assert result.message == 'awx.main.tests.functional.test_dispatch.restricted is not decorated with @task()' # noqa
|
||||||
|
|
||||||
def test_method_dispatch(self):
|
def test_method_dispatch(self):
|
||||||
result = self.tm.perform_work({
|
result = self.tm.perform_work({
|
||||||
'task': 'awx.main.tests.functional.test_dispatch.Adder',
|
'task': 'awx.main.tests.functional.test_dispatch.Adder',
|
||||||
@@ -269,6 +286,29 @@ class TestTaskDispatcher:
|
|||||||
})
|
})
|
||||||
assert result == 4
|
assert result == 4
|
||||||
|
|
||||||
|
def test_method_dispatch_must_be_decorated(self):
|
||||||
|
result = self.tm.perform_work({
|
||||||
|
'task': 'awx.main.tests.functional.test_dispatch.Restricted',
|
||||||
|
'args': [2, 2]
|
||||||
|
})
|
||||||
|
assert isinstance(result, ValueError)
|
||||||
|
assert result.message == 'awx.main.tests.functional.test_dispatch.Restricted is not decorated with @task()' # noqa
|
||||||
|
|
||||||
|
def test_python_function_cannot_be_imported(self):
|
||||||
|
result = self.tm.perform_work({
|
||||||
|
'task': 'os.system',
|
||||||
|
'args': ['ls'],
|
||||||
|
})
|
||||||
|
assert isinstance(result, ValueError)
|
||||||
|
assert result.message == 'os.system is not a valid awx task' # noqa
|
||||||
|
|
||||||
|
def test_undefined_function_cannot_be_imported(self):
|
||||||
|
result = self.tm.perform_work({
|
||||||
|
'task': 'awx.foo.bar'
|
||||||
|
})
|
||||||
|
assert isinstance(result, ImportError)
|
||||||
|
assert result.message == 'No module named foo' # noqa
|
||||||
|
|
||||||
|
|
||||||
class TestTaskPublisher:
|
class TestTaskPublisher:
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user