stages: add new stage org.osbuild.hmac
The new org.osbuild.hmac stage can be used to calculate hmac digests to be stored alongside files for verification.
This commit is contained in:
parent
7b843dc83e
commit
5b77ff6f65
3 changed files with 170 additions and 0 deletions
34
stages/org.osbuild.hmac
Executable file
34
stages/org.osbuild.hmac
Executable file
|
|
@ -0,0 +1,34 @@
|
|||
#!/usr/bin/python3
|
||||
import os
|
||||
import subprocess
|
||||
import sys
|
||||
|
||||
import osbuild.api
|
||||
|
||||
|
||||
def hmac_filepath(path):
|
||||
directory = os.path.dirname(path)
|
||||
basename = os.path.basename(path)
|
||||
return os.path.join(directory, f".{basename}.hmac")
|
||||
|
||||
|
||||
def main(tree, options):
|
||||
paths = options["paths"]
|
||||
algorithm = options["algorithm"]
|
||||
|
||||
hmac_cmd = f"{algorithm}hmac"
|
||||
for path in paths:
|
||||
real_path = os.path.join(tree, path.lstrip("/"))
|
||||
with open(hmac_filepath(real_path), "w", encoding="utf-8") as hmac_file:
|
||||
# run from the directory of the target file to create the hmac file with just the basename
|
||||
cwd = os.path.dirname(real_path)
|
||||
base = os.path.basename(real_path)
|
||||
subprocess.run([hmac_cmd, base], cwd=cwd, encoding="utf-8", stdout=hmac_file, check=True)
|
||||
|
||||
return 0
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
args = osbuild.api.arguments()
|
||||
r = main(args["tree"], args["options"])
|
||||
sys.exit(r)
|
||||
45
stages/org.osbuild.hmac.meta.json
Normal file
45
stages/org.osbuild.hmac.meta.json
Normal file
|
|
@ -0,0 +1,45 @@
|
|||
{
|
||||
"summary": "Generates .hmac checksum files",
|
||||
"description": [
|
||||
"Generates HMAC values for given files and stores them alongside the original",
|
||||
"for later verification.",
|
||||
"The stage uses the <algorithm>hmac commands (e.g. sha512hmac) which use a",
|
||||
"built-in key when none is provided. Future extensions of this stage may",
|
||||
"add a key parameter if and when it becomes a requirement. ",
|
||||
"In its current state, the stage can be used to replicate the generation of",
|
||||
"kernel hmac files [1] which are used for integrity verification when booting",
|
||||
"in FIPS mode.",
|
||||
"Notes:",
|
||||
" - Requires hmac calc in the build root (libkcapi-hmaccalc)",
|
||||
"Links:",
|
||||
"[1] https://gitlab.com/redhat/centos-stream/rpms/kernel/-/blob/f5b2a5f2ae8040c6072382545d302a4a936cb53c/kernel.spec?page=3#L2370"
|
||||
],
|
||||
"schema_2": {
|
||||
"options": {
|
||||
"additionalProperties": false,
|
||||
"required": [
|
||||
"paths",
|
||||
"algorithm"
|
||||
],
|
||||
"properties": {
|
||||
"paths": {
|
||||
"type": "array",
|
||||
"minItems": 1,
|
||||
"items": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"algorithm": {
|
||||
"type": "string",
|
||||
"enum": [
|
||||
"sha1",
|
||||
"sha224",
|
||||
"sha256",
|
||||
"sha384",
|
||||
"sha512"
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
91
stages/test/test_hmac.py
Normal file
91
stages/test/test_hmac.py
Normal file
|
|
@ -0,0 +1,91 @@
|
|||
import os.path
|
||||
import re
|
||||
from unittest import mock
|
||||
|
||||
import pytest
|
||||
|
||||
from osbuild import testutil
|
||||
|
||||
STAGE_NAME = "org.osbuild.hmac"
|
||||
|
||||
|
||||
@pytest.mark.parametrize("test_data,expected_errs", [
|
||||
# bad
|
||||
({}, [r"'paths' is a required property", r"'algorithm' is a required property"]),
|
||||
({"algorithm": "sha512"}, [r"'paths' is a required property"]),
|
||||
({"paths": ["/somefile"]}, [r"'algorithm' is a required property"]),
|
||||
({"paths": [], "algorithm": "sha1"}, [r"(\[\] is too short|\[\] should be non-empty)"]),
|
||||
|
||||
({"paths": ["/somefiles"], "algorithm": "md5"},
|
||||
[r"'md5' is not one of \['sha1', 'sha224', 'sha256', 'sha384', 'sha512'\]"]),
|
||||
|
||||
# good
|
||||
(
|
||||
{
|
||||
"paths": [
|
||||
"/greet1.txt",
|
||||
"/greet2.txt"
|
||||
],
|
||||
"algorithm": "sha256"
|
||||
},
|
||||
""
|
||||
),
|
||||
(
|
||||
{
|
||||
"paths": [
|
||||
"/path/to/some/file",
|
||||
"/boot/efi/EFI/Linux/vmlinuz-linux"
|
||||
],
|
||||
"algorithm": "sha512"
|
||||
},
|
||||
""
|
||||
),
|
||||
])
|
||||
def test_schema_validation_hmac(stage_schema, test_data, expected_errs):
|
||||
test_input = {
|
||||
"type": STAGE_NAME,
|
||||
"options": {
|
||||
}
|
||||
}
|
||||
test_input["options"].update(test_data)
|
||||
res = stage_schema.validate(test_input)
|
||||
|
||||
if not expected_errs:
|
||||
assert res.valid is True, f"err: {[e.as_dict() for e in res.errors]}"
|
||||
else:
|
||||
assert res.valid is False
|
||||
for exp_err in expected_errs:
|
||||
testutil.assert_jsonschema_error_contains(res, re.compile(exp_err), expected_num_errs=len(expected_errs))
|
||||
|
||||
|
||||
@mock.patch("subprocess.run")
|
||||
@pytest.mark.parametrize("options", [
|
||||
({"paths": ["/a/file/path"], "algorithm": "sha512"}),
|
||||
({"paths": ["/b/file/path"], "algorithm": "sha512"}),
|
||||
({"paths": ["/a/file/path", "/b/file/path"], "algorithm": "sha256"}),
|
||||
])
|
||||
def test_hmac_cmdline(mock_run, tmp_path, stage_module, options):
|
||||
algorithm = options["algorithm"]
|
||||
|
||||
expected_cmds = []
|
||||
expected_wds = []
|
||||
hmac_paths = []
|
||||
for path in options["paths"]:
|
||||
# create parent directories because the stage opens a file to write the output to it even when mocking sp.run()
|
||||
basename = os.path.basename(path)
|
||||
real_path = os.path.join(tmp_path, path.lstrip("/"))
|
||||
parent = os.path.dirname(real_path)
|
||||
hmac_paths.append(os.path.join(parent, f".{basename}.hmac"))
|
||||
os.makedirs(parent, exist_ok=True)
|
||||
expected_cmds.append([f"{algorithm}hmac", basename])
|
||||
expected_wds.append(parent)
|
||||
|
||||
stage_module.main(tmp_path, options)
|
||||
for exp, cwd, actual in zip(expected_cmds, expected_wds, mock_run.call_args_list):
|
||||
assert exp == actual[0][0]
|
||||
assert cwd == actual[1]["cwd"]
|
||||
|
||||
# the file should have been created by the open() call, but should be empty because we mocked sp.run()
|
||||
for path in hmac_paths:
|
||||
info = os.stat(path)
|
||||
assert info.st_size == 0
|
||||
Loading…
Add table
Add a link
Reference in a new issue