Skip to content

Commit

Permalink
Browse files Browse the repository at this point in the history
Implement playbook signature validation
  • Loading branch information
adamruzicka authored and ares committed Dec 8, 2020
1 parent 7db435d commit 1c0c8e3
Show file tree
Hide file tree
Showing 4 changed files with 68 additions and 6 deletions.
16 changes: 13 additions & 3 deletions receptor_satellite/run.py
@@ -1,5 +1,7 @@
import asyncio

from insights.client.apps.ansible import playbook_verifier

from .config import Config
from .host import Host
from .run_monitor import run_monitor
Expand Down Expand Up @@ -64,10 +66,11 @@ async def run(self):

await self.satellite_api.init_session()
try:
self.queue.ack(self.playbook_run_id)
self.playbook = playbook_verifier.verify(self.playbook)
response = await self.satellite_api.trigger(
{"playbook": self.playbook}, [host.name for host in self.hosts]
)
self.queue.ack(self.playbook_run_id)
if response["error"]:
self.abort(response["error"])
else:
Expand All @@ -80,6 +83,11 @@ async def run(self):
await self.finish()
self.logger.info(f"Playbook run {self.playbook_run_id} done")

except playbook_verifier.PlaybookValidationError as err:
self.abort(
f"Playbook failed signature validation: {err}",
error_key="validation_error",
)
finally:
await run_monitor.done(self)
await self.satellite_api.close_session()
Expand Down Expand Up @@ -144,14 +152,16 @@ def update_hosts(self, hosts):
else:
self.running[host.id] = host

def abort(self, error, running=False):
def abort(self, error, running=False, error_key="connection_error"):
error = str(error)
self.logger.error(
f"Playbook run {self.playbook_run_id} encountered error '{error}', aborting."
)
hosts = [host for id, host in self.running.items()] if running else self.hosts
for host in hosts:
host.mark_as_failed(error)
result = {}
result[error_key] = error
self.queue.playbook_run_completed(
self.playbook_run_id, constants.RESULT_FAILURE, connection_error=error
self.playbook_run_id, constants.RESULT_FAILURE, **result
)
3 changes: 2 additions & 1 deletion receptor_satellite/satellite_api.py
Expand Up @@ -198,7 +198,8 @@ async def init_session(self):
self.session = aiohttp.ClientSession(auth=auth)

async def close_session(self):
await self.session.close()
if self.session:
await self.session.close()
self.session = None


Expand Down
4 changes: 3 additions & 1 deletion setup.py
Expand Up @@ -17,7 +17,9 @@
packages=find_packages(),
long_description=long_description,
long_description_content_type="text/markdown",
install_requires=["aiohttp"],
# TODO: Require minimal version of insights-core once a version with
# playbook verifier is released
install_requires=["aiohttp", "insights-core"],
zip_safe=False,
entry_points={"receptor.worker": "receptor_satellite = receptor_satellite.worker"},
classifiers=["Programming Language :: Python :: 3"],
Expand Down
51 changes: 50 additions & 1 deletion tests/test_run.py
@@ -1,4 +1,5 @@
import pytest
import os

from test_helper import base_scenario # noqa: F401
from receptor_satellite.run_monitor import run_monitor # noqa: E402
Expand Down Expand Up @@ -42,7 +43,7 @@ def test_hostname_sanity():


RUN_TEST_CASES = [
# (api_responses, expected_api_requests, expected_queue_messages, expected_logger_messages)
# (api_responses, expected_api_requests, expected_queue_messages, expected_logger_messages, playbook_valid)
(
[{"error": "Something broke"}],
[("trigger", ({"playbook": "playbook"}, ["host1"]))],
Expand All @@ -63,6 +64,7 @@ def test_hostname_sanity():
FakeLogger()
.error("Playbook run play_id encountered error 'Something broke', aborting.")
.messages,
True,
),
(
[
Expand Down Expand Up @@ -102,6 +104,7 @@ def test_hostname_sanity():
.info("Playbook run play_id running as job invocation 123")
.info("Playbook run play_id done")
.messages,
True,
),
(
[
Expand Down Expand Up @@ -152,6 +155,7 @@ def test_hostname_sanity():
.info("Playbook run play_id running as job invocation 123")
.info("Playbook run play_id done")
.messages,
True,
),
(
[
Expand Down Expand Up @@ -238,6 +242,37 @@ def test_hostname_sanity():
.info("Playbook run play_id running as job invocation 123")
.info("Playbook run play_id done")
.messages,
True,
),
(
[],
[],
[
messages.ack("play_id"),
messages.playbook_run_update(
"host1",
"play_id",
"Playbook failed signature validation: PLAYBOOK VALIDATION FAILED",
0,
),
messages.playbook_run_finished(
"host1", "play_id", constants.RESULT_FAILURE
),
messages.playbook_run_completed(
"play_id",
constants.RESULT_FAILURE,
validation_code=1,
validation_error="Playbook failed signature validation: PLAYBOOK VALIDATION FAILED",
connection_code=None,
infrastructure_code=None,
),
],
FakeLogger()
.error(
"Playbook run play_id encountered error 'Playbook failed signature validation: PLAYBOOK VALIDATION FAILED', aborting."
)
.messages,
False,
),
(
[
Expand Down Expand Up @@ -281,6 +316,7 @@ def test_hostname_sanity():
)
.info("Playbook run play_id done")
.messages,
True,
),
(
[
Expand Down Expand Up @@ -317,6 +353,7 @@ def test_hostname_sanity():
.info("Playbook run play_id running as job invocation 123")
.info("Playbook run play_id done")
.messages,
True,
),
]

Expand All @@ -338,9 +375,21 @@ async def test_run(run_scenario):
expected_api_requests,
expected_queue_messages,
expected_logger_messages,
playbook_signature_valid,
) = case
satellite_api.responses = api_responses
ansible_env_key = "ANSIBLE_PLAYBOOK_VERIFIER_THROW_ERROR"
old = os.getenv(ansible_env_key)
if not playbook_signature_valid:
os.environ[ansible_env_key] = "1"
await run.run()
if old:
os.environ[ansible_env_key] = old
else:
os.environ[ansible_env_key] = ""
print(satellite_api.requests)
print(logger.messages)
print(queue.messages)
assert satellite_api.requests == expected_api_requests
assert logger.messages == expected_logger_messages
assert queue.messages == expected_queue_messages

0 comments on commit 1c0c8e3

Please sign in to comment.