Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add snapd device information to ComputerInfo message #205

Merged
merged 1 commit into from
Jan 31, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
33 changes: 33 additions & 0 deletions landscape/client/monitor/computerinfo.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
from twisted.internet.defer import returnValue

from landscape.client.monitor.plugin import MonitorPlugin
from landscape.client.snap_utils import get_assertions
from landscape.lib.cloud import fetch_ec2_meta_data
from landscape.lib.fetch import fetch_async
from landscape.lib.fs import read_text_file
Expand Down Expand Up @@ -59,6 +60,11 @@ def register(self, registry):
self.send_cloud_instance_metadata_message,
True,
)
self.call_on_accepted(
"snap-info",
self.send_snap_message,
True,
)

def send_computer_message(self, urgent=False):
message = self._create_computer_info_message()
Expand Down Expand Up @@ -96,6 +102,17 @@ def send_cloud_instance_metadata_message(self, urgent=False):
urgent=urgent,
)

def send_snap_message(self, urgent=False):
message = self._create_snap_info_message()
if message:
message["type"] = "snap-info"
logging.info("Queueing message with updated snap info.")
self.registry.broker.send_message(
message,
self._session_id,
urgent=urgent,
)

def exchange(self, urgent=False):
broker = self.registry.broker
broker.call_if_accepted(
Expand All @@ -113,6 +130,11 @@ def exchange(self, urgent=False):
self.send_cloud_instance_metadata_message,
urgent,
)
broker.call_if_accepted(
"snap-info",
self.send_snap_message,
urgent,
)

def _create_computer_info_message(self):
message = {}
Expand Down Expand Up @@ -195,3 +217,14 @@ def log_success(result):
deferred.addCallback(log_success)
deferred.addErrback(log_no_meta_data_found)
return deferred

def _create_snap_info_message(self):
"""Create message with the snapd serial metadata."""
message = {}
assertions = get_assertions("serial")
if assertions:
assertion = assertions[0]
self._add_if_new(message, "brand", assertion["brand-id"])
self._add_if_new(message, "model", assertion["model"])
self._add_if_new(message, "serial", assertion["serial"])
return message
43 changes: 43 additions & 0 deletions landscape/client/monitor/tests/test_computerinfo.py
Original file line number Diff line number Diff line change
Expand Up @@ -559,3 +559,46 @@ def test_fetch_ec2_meta_data_bad_result_retry(self):
},
result,
)

@mock.patch("landscape.client.monitor.computerinfo.get_assertions")
def test_snap_info(self, mock_get_assertions):
"""Test getting the snap info message."""
mock_get_assertions.return_value = [
{
"authority-id": "canonical",
"brand-id": "canonical",
"model": "pc-amd64",
"serial": "03961d5d-26e5-443f-838d-6db046126bea",
},
]

self.mstore.set_accepted_types(["snap-info"])
plugin = ComputerInfo(fetch_async=self.fetch_func)
self.monitor.add(plugin)
plugin.exchange()
messages = self.mstore.get_pending_messages()
self.assertEqual(len(messages), 1)
self.assertEqual(messages[0]["type"], "snap-info")
self.assertEqual(messages[0]["brand"], "canonical")
self.assertEqual(messages[0]["model"], "pc-amd64")
self.assertEqual(
messages[0]["serial"],
"03961d5d-26e5-443f-838d-6db046126bea",
)

@mock.patch("landscape.client.monitor.computerinfo.get_assertions")
def test_snap_info_no_results(self, mock_get_assertions):
"""Test getting the snap info message when there are no results.

No results can happen when:
- A SnapdHttpException occurs
- No serial assertion is found
"""
mock_get_assertions.return_value = None

self.mstore.set_accepted_types(["snap-info"])
plugin = ComputerInfo(fetch_async=self.fetch_func)
self.monitor.add(plugin)
plugin.exchange()
messages = self.mstore.get_pending_messages()
self.assertEqual(len(messages), 0)
38 changes: 38 additions & 0 deletions landscape/client/snap_utils.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
import yaml

from landscape.client import snap_http
from landscape.client.snap_http import SnapdHttpException


def get_assertions(assertion_type: str):
"""Get and parse assertions."""
try:
response = snap_http.get_assertions(assertion_type)
except SnapdHttpException:
return

# the snapd API returns multiple assertions as a stream of
# bytes separated by double newlines, something like this:
# <assertion1-headers>: <value>
# <assertion1-headers>: <value>
#
# signature
#
# <assertion2-headers>: <value>
# <assertion2-headers>: <value>
#
# signature

# extract the assertion headers + their signatures as separate assertions
assertions = []
result = response.result.decode()
if result:
sections = result.split("\n\n")
rest = sections
while rest:
headers, signature, *rest = rest
assertion = yaml.safe_load(headers)
assertion["signature"] = signature
assertions.append(assertion)

return assertions
175 changes: 175 additions & 0 deletions landscape/client/tests/test_snap_utils.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,175 @@
from unittest import mock
from unittest import TestCase

import yaml

from landscape.client.snap_http import SnapdHttpException
from landscape.client.snap_http import SnapdResponse
from landscape.client.snap_utils import get_assertions


TEST_SERIAL_ASSERTION = """type: serial
authority-id: canonical
brand-id: canonical
model: pc-amd64
serial: 03961d5d-26e5-443f-838d-6db046126bea
device-key:
AcbBTQRWhcGAARAA0y/BXkBJjPOl24qPKOZWy7H+6+piDPtyKIGfU9TDDrFjFnv3R8EMTz1WNW8
5nLR8gjDXNh3z7dLIbSPeC54bvQ7LlaO2VYICGdzHT5+68Rod9h5NYdTKgaWDyHdm2K1v2oOzmM
Z+MmL15TvP9lX1U8OIVkmHhCO7FeDGsPlsTX2Wz++SrOqG4PsvpYsaYUTHE+oZ+Eo8oySW/OxTm
rQIEUoDEWNbFR5/+33tHRDxKSjeErCVuVetZxlZW/gpCx5tmCyAcBgKoEKsPqrgzW4wUAONaSOG
Zuo35DxwqeGHOx3C118rYrGvqA2mCn3fFz/mqnciK3JzLemLjw4HyVd1DyaKUgGjR6VYBcadL72
YN6gPiMMmlaAPtkdFIkqIp1OpvUFEEEHwNI88klM/N8+t3JE8cFpG6n4WBdHUAwtMmmVxXm5IsM
uNwrZdIBUu4WOAAgu2ZioeHLIQlDGw6dvVTaK+dTe0EXo5j+mH5DFnn0W1L7IAj6rX8HdiM5X5f
4kwiezSfYXJgctdi0gizdGB7wcH0/JynaXA/tI3fEVDu45X7dA/XnCEzYkBxpidNfDkmXxSWt5N
NMuHZqqmNHNfLeKAo1yQ/SH702nth6vJYJaIX4Pgv5cVrX5L429U5SHV+8HaE0lPCfFo/rKRJa9
rvnJ5OGR4TeRTLsAEQEAAQ==
device-key-sha3-384: _4U3nReiiIMIaHcl6zSdRzcu75Tz37FW8b7NHhxXjNaPaZzyGooMFqur0E
timestamp: 2016-11-08T18:16:12.977431Z
sign-key-sha3-384: BWDEoaqyr25nF5SNCvEv2v7QnM9QsfCc0PBMYD_i2NGSQ32EF2d4D0hqUel3

AcLBUgQAAQoABgUCWCIWcgAARegQAB4/UsBpzqLOYOpmR/j9BX5XNyEWxOWgFg5QLaY+0bIz/nbU
avFH4EwV7YKQxX5nGmt7vfFoUPsRrWO4E6RtXQ1x5kYr8sSltLIYEkUjHO7sqB6gzomQYkMnS2fI
xOZwJs9ev2sCnqr9pwPC8MDS5KW5iwXYvdBP1CIwNfQO48Ys8SC9MdYH0t3DbnuG/w+EceOIyI3o
ilkB427DiueGwlBpjNRSE4B8cvglXW9rcYW72bnNs1DSnCq8tNHHybBtOYm/Y/jmk7UGXwqYUGQQ
Iwu1W+SgloJdXLLgM80bPzLy+cYiIe1W1FSMzVdOforTkG5mVFHTL/0l4eceWequfcxU3DW9ggcN
YJx8MPW9ab5gPibx8FeVb6cMWEvm8S7wXIRSff/bkHMhpjAagp+A6dyYsuUwPXFxCvHSpT0vUwFS
CCPHkPUwj54GjKAGEkKMx+s0psQ3V+fcZgW5TBxk/+J83S/+6AiQ06W8rkabWCRyl2fX81vMBynQ
nu147uRGWTXfa31Mys9lAGNHMtEcMmA106f2XfATqNK99GlIIjOxqEe5zH3j51JtY+5kyJd9cqvl
Pb0rZnPySeGxnV4Q2403As67AJrIExRrcrK2yXZjEW3G2zTsFNzBSSZr0U8id1UJ/EZLB/em2EHw
D2FXTwfDiwGroHYUFAEu1DkHx7Sy
"""

TEST_DECLARATION_ASSERTIONS = """type: snap-declaration
format: 1
authority-id: canonical
revision: 6
series: 16
snap-id: ffnH0sJpX3NFAclH777M8BdXIWpo93af
plugs:
hardware-observe:
allow-auto-connection: true
mount-observe:
allow-auto-connection: true
network-observe:
allow-auto-connection: true
scsi-generic:
allow-auto-connection: true
shutdown:
allow-auto-connection: true
snapd-control:
allow-auto-connection: true
allow-installation: true
system-observe:
allow-auto-connection: true
publisher-id: 0N0rjFmfHsjIZMCjZ4IX5EtW2CsVd5Ky
snap-name: landscape-client
timestamp: 2023-11-08T07:46:21.082809Z
sign-key-sha3-384: BWDEoaqyr25nF5SNCvEv2v7QnM9QsfCc0PBMYD_i2NGSQ32EF2d4D0hqU

AcLBUgQAAQoABgUCZUs80QAA5QQQALZTjK8YeaJIVjcDw4w7IbLyfO28hQEy95ocZOWLtPwnSYQk
OJBtSFknwTCGKvlaOFuy9PLIABG+MoObm+MOQ8QjqqgHZ+ESqCs5sHrpYk2U9nhI6h89iDshk6Sg
T9l0tqqyV5pEen9nH/zRJy6k8xm7iMR7y1DVl4PBFJVwmugyXn1/4/kG5XKwV1p4WdvIemPzk7nm
nNvVpx+T2IAbXCcZMnvXxmCIf+KqiIub3v4Cvpy9xyxNGqLdCHiCh9bsPoTz2lwHOpgki/rlS6gd
T+GZZf+0358Xom5CeLMMVlV/1jcZs3X0BTK5m2Tx5aW4f+4pHfSi7idtaV1lzqUagM+KO/UdGi7i
dxq1/6eP0YTDb/hHhjlhDQAgwBTCvxRDor8V/NE1TkdEsbMFMgEOT/70v6mkgpXXYY9gnEg0vlSW
osDXzqN6tchJzVQfYYPRjIplX3C+fF5axCUL/ly14Up558ge53zS6frImytG/Qxeh0Ga1RyJsqMu
mbmz+uHwQA2IuXU9MbubCzO6hvVP4zU8FKbSRLrXrSLOwxuFFFRU5aor/w+kbx3l/pgfzOkpSLgD
eEQQbEjwhN14cCTq+jt78MoIAVLMHj1cre567X6ei1HufpAYRdAEL5Igi3+8ua5AyI3QTp7mQIPm
UUvllJovZRye6mF0u7VK9YRQwaoY

type: snap-declaration
authority-id: canonical
series: 16
snap-id: XaUSoE9KNKazeLO5H02NMM2cTxX8E9IH
publisher-id: f22PSauKuNkwQTM9Wz67ZCjNACuSjjhN
snap-name: pocketses
timestamp: 2024-01-03T11:01:29.918417Z
sign-key-sha3-384: BWDEoaqyr25nF5SNCvEv2v7QnM9QsfCc0PBMYD_i2NGSQ32EF2d4D0hqU

AcLBUgQAAQoABgUCZZU+iQAA8GsQADl85AbacovxCOrCb3zNDCCBulAakWiITOTd6lCPkibQq+2b
5Nj7PUl+DCqnVBr8tWx/o8Me/Q4yC+btcOZA/LIzCFHVXS/ExvKABSthgIjygkwM4BbXWpdm7Fua
CxY3GzsfBbOQncpOV/jN5UmoCPJ+/OBJleoxjboZGeMr7hEaKhMKed0+C1VXGVJnpL8MfdDjOgjz
WWSJX+KVfQhNIUZJVGMdJ6tl3/Dzs2wSnwfy8mftx0eXWB2Z9xnX17lWe/ewqG38DzYcoq2dbkNU
XItff1HdEJZANbmW1f2vRbeeShDUvW9mxHcYauI+VsBr5XiHDGO8h4l7kDrlh76j/th5y/WSOGyN
DEV/E5Q+JDUFy0k9NSgw6pCHY7EwOwyiM0zKe4wEUhuLUvIuO9n4lAUApIgxNvDgxx7ZymHggzHy
HQZEt8GwUBNgipqz1FI4u5eT2LhhqwVT6no2dmVwVTUxaPPRKxM3qApUzXzzzLJxNKf5XeM0/QFW
aYy6QoXO3X0Ze01DFq2aFqXsNi4flpONFDG3fsffXCoxyPRaXpB3bRXuRTN0oAun7IxF/m2KHZyn
Xl40RI/40APrJpUUgJu9QjXslspuX9X6JUNjdwUVs65qbfVvdjUMKcIkk/CX14YKquPXrIzwOaao
EJ67KJVjhdyWDv+AUE4FMC0hUGSh
"""


class AssertionTest(TestCase):
def setUp(self):
super().setUp()

self.snap_http = mock.patch(
"landscape.client.snap_utils.snap_http",
).start()

def tearDown(self):
mock.patch.stopall()

def test_get_assertions_one_result(self):
self.snap_http.get_assertions.return_value = SnapdResponse(
"sync",
200,
"OK",
result=TEST_SERIAL_ASSERTION.encode(),
)

assertions = get_assertions("serial")
self.assertEqual(len(assertions), 1)
self.assertEqual(assertions[0]["type"], "serial")
self.assertEqual(
assertions[0]["serial"],
"03961d5d-26e5-443f-838d-6db046126bea",
)
self.assertEqual(assertions[0]["model"], "pc-amd64")
self.assertEqual(
assertions[0]["sign-key-sha3-384"],
"BWDEoaqyr25nF5SNCvEv2v7QnM9QsfCc0PBMYD_i2NGSQ32EF2d4D0hqUel3",
)

def test_get_assertions_multiple_results(self):
self.snap_http.get_assertions.return_value = SnapdResponse(
"sync",
200,
"OK",
result=TEST_DECLARATION_ASSERTIONS.encode(),
)

assertions = get_assertions("snap-declaration")
self.assertEqual(len(assertions), 2)
self.assertEqual(assertions[0]["snap-name"], "landscape-client")
self.assertEqual(assertions[1]["snap-name"], "pocketses")

def test_get_assertions_no_result(self):
self.snap_http.get_assertions.return_value = SnapdResponse(
"sync",
200,
"OK",
result=b"",
)

assertions = get_assertions("serial-request")
self.assertEqual(assertions, [])

def test_get_assertions_exception(self):
self.snap_http.get_assertions.side_effect = SnapdHttpException()

assertions = get_assertions("unknown")
self.assertIsNone(assertions)

def test_get_assertions_invalid_assertion(self):
bad_assertion = "assertion-header: {{ bad-val }}\n\nsignature"
self.snap_http.get_assertions.return_value = SnapdResponse(
"sync",
200,
"OK",
result=bad_assertion.encode(),
)

with self.assertRaises(yaml.constructor.ConstructorError):
get_assertions("serial")
11 changes: 11 additions & 0 deletions landscape/message_schemas/server_bound.py
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,7 @@
"LIVEPATCH",
"UBUNTU_PRO_REBOOT_REQUIRED",
"SNAPS",
"SNAP_INFO",
]


Expand Down Expand Up @@ -163,6 +164,15 @@
},
)

SNAP_INFO = Message(
"snap-info",
{
"brand": Unicode(),
"model": Unicode(),
"serial": Unicode(),
},
)


hal_data = Dict(
Unicode(),
Expand Down Expand Up @@ -845,4 +855,5 @@
LIVEPATCH,
UBUNTU_PRO_REBOOT_REQUIRED,
SNAPS,
SNAP_INFO,
)
1 change: 1 addition & 0 deletions setup_client.py
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@
"landscape.client.patch",
"landscape.client.reactor",
"landscape.client.service",
"landscape.client.snap_utils",
"landscape.client.sysvconfig",
"landscape.client.watchdog",
]
Expand Down
2 changes: 1 addition & 1 deletion snap-http
Loading