Skip to content

Commit

Permalink
Add spec and parser for luksmeta command (#3525)
Browse files Browse the repository at this point in the history
* Add spec and parser for luksmeta command

Signed-off-by: daniel.zatovic <[email protected]>

* Add LuksDump parser to collect.py

Signed-off-by: daniel.zatovic <[email protected]>

* Change luksmeta command documentation

Signed-off-by: daniel.zatovic <[email protected]>

* Improve error handling in the luksmeta parser

Signed-off-by: daniel.zatovic <[email protected]>

* Change LuksMeta parser to inherit from dict.

Signed-off-by: daniel.zatovic <[email protected]>

* Extend test coverage

Signed-off-by: daniel.zatovic <[email protected]>

* Fix luksmeta parser documentation

Signed-off-by: daniel.zatovic <[email protected]>

* Specs class cannot be decorated using datasource

Otherwise TypeError: LocalSpecs() takes no arguments
is emitted.

Signed-off-by: daniel.zatovic <[email protected]>

* Change luks1_block_devices to a combinder

Signed-off-by: daniel.zatovic <[email protected]>

* Add device UUID to LuksMeta parser output.

To make a combiner which combines LuksMeta and cryptsetup luksDump
outputs, we need to include UUID information in LuksMeta parser.

Signed-off-by: daniel.zatovic <[email protected]>

* Convert LuksMeta to CommandParser

Signed-off-by: daniel.zatovic <[email protected]>

* Add LuksDevices combiner

We match LuksDump and the associated LuksMeta based on the UUID of the
device they were obtained from.

Signed-off-by: daniel.zatovic <[email protected]>

* Don't run luks1_block_devices during collection

Instead we run luksmeta against all devices in /dev/disk/by-uuid

Signed-off-by: daniel.zatovic <[email protected]>

* Fix cryptsetup LocalSpecs to use full path

Signed-off-by: daniel.zatovic <[email protected]>

* Remove luks1_block_devices combiner

Signed-off-by: daniel.zatovic <[email protected]>

* Skip LuksDevices combiner if there are no results

Signed-off-by: daniel.zatovic <[email protected]>
(cherry picked from commit d5b0862)
  • Loading branch information
danzatt authored and xiangce committed Oct 13, 2022
1 parent 548e180 commit 03ab8e0
Show file tree
Hide file tree
Showing 9 changed files with 430 additions and 3 deletions.
3 changes: 3 additions & 0 deletions docs/shared_combiners_catalog/cryptsetup.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
.. automodule:: insights.combiners.cryptsetup
:members: LuksDevices
:show-inheritance:
3 changes: 3 additions & 0 deletions docs/shared_parsers_catalog/luksmeta.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
.. automodule:: insights.parsers.luksmeta
:members:
:show-inheritance:
56 changes: 56 additions & 0 deletions insights/combiners/cryptsetup.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
"""
Cryptsetup - combine metadata about LUKS devices
================================================
Combine outputs of LuksDump and LuksMeta parsers (with the same UUID) into a
single dictionary.
"""

import copy

from insights import SkipComponent
from insights.core.plugins import combiner
from insights.parsers.cryptsetup_luksDump import LuksDump
from insights.parsers.luksmeta import LuksMeta


@combiner(LuksDump, optional=[LuksMeta])
class LuksDevices(list):
"""
Combiner for LUKS encrypted devices information. It uses the results of
the ``LuksDump`` and ``LuksMeta`` parser (they are matched based UUID of
the device they were collected from).
Examples:
>>> luks_devices[0]["header"]["Version"]
'1'
>>> "luksmeta" in luks_devices[0]
True
>>> "luksmeta" in luks_devices[1]
False
>>> luks_devices[0]["luksmeta"][0]
Keyslot on index 0 is 'active' with no embedded metadata
"""

def __init__(self, luks_dumps, luks_metas):
luksmeta_by_uuid = {}

if luks_metas:
for luks_meta in luks_metas:
if "device_uuid" not in luks_meta:
continue

luksmeta_by_uuid[luks_meta["device_uuid"].lower()] = luks_meta

for luks_dump in luks_dumps:
uuid = luks_dump.dump["header"]["UUID"].lower()
luks_dump_copy = copy.deepcopy(luks_dump.dump)

if luks_metas and uuid in luksmeta_by_uuid:
luks_dump_copy["luksmeta"] = luksmeta_by_uuid[uuid]

self.append(luks_dump_copy)

if not self:
raise SkipComponent
109 changes: 109 additions & 0 deletions insights/parsers/luksmeta.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,109 @@
"""
luksmeta - command ``luksmeta show -d <device_name>``
=====================================================
This class provides parsing for the output of luksmeta <device_name>.
"""

from insights import parser, CommandParser
from insights.specs import Specs


class KeyslotSpecification:
"""
Class ``KeyslotSpecification`` describes information about a keyslot
collected by the ``luksmeta show`` command.
Attributes:
index (int): the index of the described keyslot
state (str): the state of the described keyslot
metadata (str): the UUID of the application that stored metadata into
the described keyslot
"""

def __init__(self, index, state, metadata):
self.index = index
self.state = state
self.metadata = metadata

def __repr__(self):
ret = "Keyslot on index " + str(self.index) + " is '" + self.state + "' "
if self.metadata:
ret += "with metadata stored by application with UUID '" + self.metadata + "'"
else:
ret += "with no embedded metadata"

return ret


@parser(Specs.luksmeta)
class LuksMeta(CommandParser, dict):
"""
Class ``LuksMeta`` parses the output of the ``luksmeta show -d <device>`` command.
This command prints information if the device has custom user-defined
metadata embedded in the keyslots (used e.g. by clevis). If the device was
not initialized using ``luksmeta``, the parser raises SkipComponent.
The parser can be indexed by the keyslot index (in the range 0-7).
A KeyslotSpecification object is returned, which describes every LUKS
keyslot. The KeyslotSpecification contains the ``index``, ``state`` and
``metadata`` fileds. Metadata field stores the UUID of the application that
has stored metadata in the keyslot.
Sample input data is in the format::
0 active empty
1 active cb6e8904-81ff-40da-a84a-07ab9ab5715e
2 active empty
3 active empty
4 inactive empty
5 active empty
6 active cb6e8904-81ff-40da-a84a-07ab9ab5715e
7 active cb6e8904-81ff-40da-a84a-07ab9ab5715e
Examples:
>>> type(parsed_result)
<class 'insights.parsers.luksmeta.LuksMeta'>
>>> parsed_result[0].index
0
>>> parsed_result[0].state
'active'
>>> parsed_result[4].state
'inactive'
>>> parsed_result[0].metadata is None
True
>>> parsed_result[1].metadata
'cb6e8904-81ff-40da-a84a-07ab9ab5715e'
""" # noqa

BAD_LINES = [
"device is not initialized",
"luksmeta data appears corrupt",
"unknown error",
"invalid slot",
"is not a luksv1 device",
"invalid argument",
"unable to read luksv1 header"
]

def __init__(self, context):
super(LuksMeta, self).__init__(context, LuksMeta.BAD_LINES)

def parse_content(self, content):
filename_split = self.file_name.split(".")

if len(filename_split) >= 4 and filename_split[-4] == "dev" and filename_split[-3] == "disk" and filename_split[-2] == "by-uuid":
self["device_uuid"] = self.file_name.split(".")[-1] if self.file_name else None

for line in content:
index, state, metadata = line.split()
index = int(index)
metadata = None if metadata == "empty" else metadata
self[index] = KeyslotSpecification(index, state, metadata)
1 change: 1 addition & 0 deletions insights/specs/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -363,6 +363,7 @@ class Specs(SpecSet):
lssap = RegistryPoint()
lsscsi = RegistryPoint()
lsvmbus = RegistryPoint()
luksmeta = RegistryPoint(multi_output=True)
lvdisplay = RegistryPoint()
lvm_conf = RegistryPoint(filterable=True)
lvm_system_devices = RegistryPoint()
Expand Down
5 changes: 2 additions & 3 deletions insights/specs/datasources/luks_devices.py
Original file line number Diff line number Diff line change
Expand Up @@ -37,11 +37,10 @@ def luks_block_devices(broker):
raise SkipComponent


@datasource(luks_block_devices)
class LocalSpecs(Specs):
""" Local specs used only by LUKS_data_sources datasource. """
cryptsetup_luks_dump_token_commands = foreach_execute(luks_block_devices, "cryptsetup luksDump --disable-external-tokens %s", deps=[HasCryptsetupWithTokens])
cryptsetup_luks_dump_commands = foreach_execute(luks_block_devices, "cryptsetup luksDump %s", deps=[HasCryptsetupWithoutTokens])
cryptsetup_luks_dump_token_commands = foreach_execute(luks_block_devices, "/usr/sbin/cryptsetup luksDump --disable-external-tokens %s", deps=[luks_block_devices, HasCryptsetupWithTokens])
cryptsetup_luks_dump_commands = foreach_execute(luks_block_devices, "/usr/sbin/cryptsetup luksDump %s", deps=[luks_block_devices, HasCryptsetupWithoutTokens])


def line_indentation(line):
Expand Down
2 changes: 2 additions & 0 deletions insights/specs/default.py
Original file line number Diff line number Diff line change
Expand Up @@ -372,6 +372,8 @@ class DefaultSpecs(Specs):
lspci_vmmkn = simple_command("/sbin/lspci -vmmkn")
lsscsi = simple_command("/usr/bin/lsscsi")
lsvmbus = simple_command("/usr/sbin/lsvmbus -vv")
block_devices_by_uuid = listdir("/dev/disk/by-uuid/", context=HostContext)
luksmeta = foreach_execute(block_devices_by_uuid, "/usr/bin/luksmeta show -d /dev/disk/by-uuid/%s", keep_rc=True)
lvm_conf = simple_file("/etc/lvm/lvm.conf")
lvmconfig = first_of([
simple_command("/usr/sbin/lvmconfig --type full"),
Expand Down
191 changes: 191 additions & 0 deletions insights/tests/combiners/test_cryptsetup.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,191 @@
import doctest
import pytest

from insights import SkipComponent
from insights.parsers.cryptsetup_luksDump import LuksDump
from insights.parsers import luksmeta
from insights.combiners.cryptsetup import LuksDevices
import insights.combiners.cryptsetup
from insights.tests import context_wrap

LUKS1_DUMP = """LUKS header information for luks1
Version: 1
Cipher name: aes
Cipher mode: xts-plain64
Hash spec: sha256
Payload offset: 4096
MK bits: 512
MK digest: ca fe ba be df 8c c4 b4 b8 0a cc dd 98 b5 d8 64 3a 95 3e 9e
MK salt: ca fe ba be 04 3b 77 d8 ff 08 1e 0a 41 68 45 a5
ca fe ba be 7b 3f a9 69 9c 9b 51 24 58 47 8d a2
ca fe ba be 7b 3f a9 69 9c 9b 51 24 58 47 8d a2
ca fe ba be 7b 3f a9 69 9c 9b 51 24 58 47 8d a2
ca fe ba be 7b 3f a9 69 9c 9b 51 24 58 47 8d a2
ca fe ba be 7b 3f a9 69 9c de ad be ef
MK iterations: 106562
UUID: 263902da-5f0c-43a9-82eb-cc6f14d90448
Key Slot 0: ENABLED
Iterations: 2099250
Salt: de ad be ef
Salt: ca fe ba be a1 f3 ae cb 4a 3f f0 2d de ad be ef
de ad be ef
Key material offset: 8
AF stripes: 4000
Key Slot 1: ENABLED
Iterations: 1987820
Salt: ca fe ba be f2 b7 7d f3 29 c2 c8 80 de ad be ef
ca fe ba be 9f a1 87 07 c6 4f aa cd de ad be ef
ca fe ba be 9f a1 87 07 c6 4f aa de ad be ef
Key material offset: 512
AF stripes: 4000
Key Slot 2: ENABLED
Iterations: 2052006
Salt: ca fe ba be 47 94 e7 40 22 c1 bb 4a de ad be ef
ca fe ba be 52 e8 8d 70 b2 1e 9d 47 de ad be ef
Key material offset: 1016
AF stripes: 4000
Key Slot 3: DISABLED
Key Slot 4: DISABLED
Key Slot 5: DISABLED
Key Slot 6: DISABLED
Key Slot 7: DISABLED
""" # noqa

LUKS2_DUMP = """LUKS header information
Version: 2
Epoch: 6
Metadata area: 16384 [bytes]
Keyslots area: 16744448 [bytes]
UUID: cfbcc942-e06b-4c4a-952f-e9c9b2011c27
Label: (no label)
Subsystem: (no subsystem)
Flags: (no flags)
Data segments:
0: crypt
offset: 16777216 [bytes]
length: (whole device)
cipher: aes-xts-plain64
sector: 4096 [bytes]
Keyslots:
0: luks2
Key: 512 bits
Priority: normal
Cipher: aes-xts-plain64
Cipher key: 512 bits
PBKDF: argon2id
Time cost: 7
Memory: 1048576
Threads: 4
Salt: ca fe ba be fe 1c 90 d8 2a 35 b2 b2 de ad be ef
ca fe ba be b2 dd 45 9e ed 9a 33 b2 de ad be ef
de ad be ef
AF stripes: 4000
AF hash: sha256
Area offset:32768 [bytes]
Area length:258048 [bytes]
Digest ID: 0
1: luks2
Key: 512 bits
Priority: normal
Cipher: aes-xts-plain64
Cipher key: 512 bits
PBKDF: argon2id
Time cost: 7
Memory: 1048576
Threads: 4
Salt: ca fe ba be c1 94 15 86 2a e9 26 f8 de ad be ef
ca fe ba be 05 2d 80 c9 56 e8 4d 6f de ad be ef
AF stripes: 4000
AF hash: sha256
Area offset:290816 [bytes]
Area length:258048 [bytes]
Digest ID: 0
2: luks2
Key: 512 bits
Priority: normal
Cipher: aes-xts-plain64
Cipher key: 512 bits
PBKDF: pbkdf2
Hash: sha512
Iterations: 1000
Salt: ca fe ba be d7 8f a6 de a0 cb a4 d1 de ad be ef
ca fe ba be fb 53 43 06 e8 83 90 93 de ad be ef
AF stripes: 4000
AF hash: sha512
Area offset:548864 [bytes]
Area length:258048 [bytes]
Digest ID: 0
Tokens:
0: systemd-tpm2
tpm2-pcrs: 7
tpm2-bank: sha256
tpm2-primary-alg: ecc
tpm2-blob: 00 9e 00 20 bd 97 78 70 3f 3a 5b 93 d4 8f dc ed
10 16 b2 ce f5 f7 a2 c8 63 f6 19 12 63 7a f2 94
26 f1 b6 1b 00 10 2e 36 26 c1 3b f7 1e 8d 86 55
tpm2-policy-hash:
df 06 80 28 e7 67 b1 d0 34 f4 de 1b 8e ac 33 5a
df 06 80 28 e7 67 b1 d0 34 f4 de 1b 8e ac 33 5a
Keyslot: 2
Digests:
0: pbkdf2
Hash: sha256
Iterations: 129774
Salt: ca fe ba be e0 65 83 82 35 03 29 56 de ad be ef
ca fe ba be de 69 39 97 d5 b3 ac c4 de ad be ef
de ad be ef
Digest: ca fe ba be 9d 46 9b 0f 3a 0f 57 13 de ad be ef
ca fe ba be ed 7d 09 2c 3d b6 fa f4 de ad be ef
""" # noqa

LUKSMETA_OUTPUT = """0 active empty
1 active cb6e8904-81ff-40da-a84a-07ab9ab5715e
2 active empty
3 active empty
4 inactive empty
5 active empty
6 active cb6e8904-81ff-40da-a84a-07ab9ab5715e
7 active cb6e8904-81ff-40da-a84a-07ab9ab5715e
""" # noqa

luks1_device = LuksDump(context_wrap(LUKS1_DUMP))
luks2_device = LuksDump(context_wrap(LUKS2_DUMP))
uuid = luks1_device.dump["header"]["UUID"]
luksmeta_parsed = luksmeta.LuksMeta(context_wrap(LUKSMETA_OUTPUT, path="/insights_commands/cryptsetup_luksDump_--disable-external-tokens_.dev.disk.by-uuid." + uuid))
luksmeta_parsed_no_uuid = luksmeta.LuksMeta(context_wrap(LUKSMETA_OUTPUT))


def test_luks_devices_combiner():
with pytest.raises(SkipComponent):
luks_devices = LuksDevices([], None)

luks_devices = LuksDevices([luks1_device, luks2_device], None)
for device in luks_devices:
assert "luksmeta" not in device

luks_devices = LuksDevices([luks1_device, luks2_device], [])
for device in luks_devices:
assert "luksmeta" not in device

luks_devices = LuksDevices([luks1_device, luks2_device], [luksmeta_parsed_no_uuid])
for device in luks_devices:
assert "luksmeta" not in device

luks_devices = LuksDevices([luks1_device, luks2_device], [luksmeta_parsed])
for device in luks_devices:
if device["header"]["UUID"] == uuid:
assert "luksmeta" in device
else:
assert "luksmeta" not in device


def test_doc_examples():
env = {
'luks_devices': LuksDevices([luks1_device, luks2_device], [luksmeta_parsed])
}
failed, total = doctest.testmod(insights.combiners.cryptsetup, globs=env)
assert failed == 0
Loading

0 comments on commit 03ab8e0

Please sign in to comment.