ci: add test for the new logic
This commit is contained in:
@@ -109,7 +109,9 @@ CERTDIR=$(mktemp -d)
|
||||
at_exit() {
|
||||
set +e
|
||||
systemctl stop fake-report-server fake-report-server-tls
|
||||
rm -rf "$CERTDIR"
|
||||
systemctl stop systemd-report.socket systemd-report-files.socket systemd-report-sign-plain.socket
|
||||
rm -f /run/systemd/report.files/testreportfile /run/systemd/report.files/binaryfile
|
||||
rm -rf "$CERTDIR" "${SIGN_WORK:-}"
|
||||
}
|
||||
trap at_exit EXIT
|
||||
|
||||
@@ -132,3 +134,108 @@ systemctl status fake-report-server-tls
|
||||
|
||||
"$REPORT" upload --url=https://localhost:8090/ --key=- --trust="$CERTDIR/server.crt" \
|
||||
--extra-header='Authorization: Bearer magic string'
|
||||
|
||||
# -----------------------------------------------------------------------------
|
||||
# Test the systemd-report-files@.service metric provider: files dropped into one
|
||||
# of the report.files directories are exposed verbatim as io.systemd.Files.*
|
||||
# metrics, keyed by their file name.
|
||||
mkdir -p /run/systemd/report.files
|
||||
REPORT_FILE_CONTENT="hello from the report files metric provider"
|
||||
printf '%s' "$REPORT_FILE_CONTENT" >/run/systemd/report.files/testreportfile
|
||||
|
||||
systemctl start systemd-report-files.socket
|
||||
varlinkctl info /run/systemd/report/io.systemd.Files
|
||||
varlinkctl list-methods /run/systemd/report/io.systemd.Files
|
||||
|
||||
# The metric value must be the exact file contents.
|
||||
files_metrics="$(varlinkctl call --more /run/systemd/report/io.systemd.Files io.systemd.Metrics.List {})"
|
||||
files_value() { echo "$files_metrics" | jq --seq -r "select(.name == \"$1\") | .value"; }
|
||||
[ "$(files_value io.systemd.Files.testreportfile)" = "$REPORT_FILE_CONTENT" ]
|
||||
|
||||
# Describe must list the very same metric, of type string.
|
||||
files_describe="$(varlinkctl call --more /run/systemd/report/io.systemd.Files io.systemd.Metrics.Describe {})"
|
||||
echo "$files_describe" | jq --seq -r 'select(.name == "io.systemd.Files.testreportfile") | .type' | grep -wx string >/dev/null
|
||||
|
||||
# Files that aren't valid UTF-8 text must be skipped silently (the value is a
|
||||
# JSON string, so only text files can be reported).
|
||||
printf '\xff\xfe binary garbage' >/run/systemd/report.files/binaryfile
|
||||
files_metrics="$(varlinkctl call --more /run/systemd/report/io.systemd.Files io.systemd.Metrics.List {})"
|
||||
[ -z "$(files_value io.systemd.Files.binaryfile)" ]
|
||||
rm -f /run/systemd/report.files/binaryfile
|
||||
|
||||
# -----------------------------------------------------------------------------
|
||||
# Test report signing through the plain software backend, driven entirely via
|
||||
# the io.systemd.Report Varlink interface (the GenerateSigned method). The plain
|
||||
# backend is only built/installed with OpenSSL support, so skip if unavailable.
|
||||
if ! systemctl cat systemd-report-sign-plain.socket &>/dev/null; then
|
||||
echo "systemd-report-sign-plain.socket is not installed, skipping report signing test."
|
||||
exit 0
|
||||
fi
|
||||
|
||||
systemctl start systemd-report.socket
|
||||
systemctl start systemd-report-sign-plain.socket
|
||||
|
||||
SIGN_WORK="$(mktemp -d)"
|
||||
|
||||
# Ask systemd-report to generate a *signed* report over Varlink. The reply
|
||||
# carries the signed report as base64-encoded JSON-SEQ data.
|
||||
varlinkctl call /run/systemd/io.systemd.Report io.systemd.Report.GenerateSigned \
|
||||
'{"matches":["io.systemd.Manager.UnitsTotal"]}' | jq -r .reportData | base64 -d >"$SIGN_WORK/report.seq"
|
||||
|
||||
# The first JSON-SEQ record is the report itself. This is exactly the byte
|
||||
# sequence that got signed, including the leading record separator (0x1e) and
|
||||
# the trailing newline, so 'head -n1' reproduces it verbatim.
|
||||
head -n1 "$SIGN_WORK/report.seq" >"$SIGN_WORK/message.bin"
|
||||
tr -d '\036' <"$SIGN_WORK/message.bin" | jq -e '.mediaType == "application/vnd.io.systemd.report"' >/dev/null
|
||||
|
||||
# The remaining record(s) are signature objects. With only the plain backend
|
||||
# enabled there is exactly one, with mechanism "plain".
|
||||
sig_json="$(tail -n +2 "$SIGN_WORK/report.seq" | tr -d '\036')"
|
||||
[ "$(echo "$sig_json" | jq -r .mechanism)" = "plain" ]
|
||||
[ "$(echo "$sig_json" | jq -r .mediaType)" = "application/vnd.io.systemd.report.signature" ]
|
||||
|
||||
# The sha256 recorded in the signature must match the digest of the report bytes.
|
||||
[ "$(echo "$sig_json" | jq -r .sha256)" = "$(sha256sum "$SIGN_WORK/message.bin" | cut -d' ' -f1)" ]
|
||||
|
||||
# Extract the embedded public key and the raw Ed25519 signature.
|
||||
echo "$sig_json" | jq -r .data.key >"$SIGN_WORK/pub.pem"
|
||||
echo "$sig_json" | jq -r .data.signature | base64 -d >"$SIGN_WORK/sig.bin"
|
||||
|
||||
# The signature is made over the SHA256 digest bytes directly (Ed25519, the
|
||||
# signer does not pre-hash), so verify the raw digest against the signature.
|
||||
openssl dgst -sha256 -binary "$SIGN_WORK/message.bin" >"$SIGN_WORK/digest.bin"
|
||||
openssl pkeyutl -verify -pubin -inkey "$SIGN_WORK/pub.pem" -rawin \
|
||||
-in "$SIGN_WORK/digest.bin" -sigfile "$SIGN_WORK/sig.bin"
|
||||
|
||||
# The public key embedded in the report must be the very key the plain backend
|
||||
# generated and persisted on disk.
|
||||
diff <(openssl pkey -pubin -in "$SIGN_WORK/pub.pem" -pubout) \
|
||||
<(openssl pkey -pubin -in /var/lib/systemd/report.sign.plain/local.public -pubout)
|
||||
|
||||
# A tampered report must no longer verify against the signature.
|
||||
printf 'x' >>"$SIGN_WORK/message.bin"
|
||||
openssl dgst -sha256 -binary "$SIGN_WORK/message.bin" >"$SIGN_WORK/digest-bad.bin"
|
||||
if openssl pkeyutl -verify -pubin -inkey "$SIGN_WORK/pub.pem" -rawin \
|
||||
-in "$SIGN_WORK/digest-bad.bin" -sigfile "$SIGN_WORK/sig.bin"; then
|
||||
echo "Signature verification unexpectedly succeeded for tampered report" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# -----------------------------------------------------------------------------
|
||||
# The plain backend also acts as a metric provider, exposing its public signing
|
||||
# key as the io.systemd.Report.SignPlain.PublicKey metric. This is served off the
|
||||
# very same socket as the signer, reachable in the metrics directory under
|
||||
# /run/systemd/report/ (the signer endpoint is just a symlink into the root-only
|
||||
# /run/systemd/report.sign/ directory pointing at the same socket).
|
||||
varlinkctl info /run/systemd/report/io.systemd.Report.SignPlain
|
||||
varlinkctl list-methods /run/systemd/report/io.systemd.Report.SignPlain
|
||||
|
||||
# The exposed public key must be the very key persisted on disk.
|
||||
sign_metrics="$(varlinkctl call --more /run/systemd/report/io.systemd.Report.SignPlain io.systemd.Metrics.List {})"
|
||||
echo "$sign_metrics" | jq --seq -r 'select(.name == "io.systemd.Report.SignPlain.PublicKey") | .value' >"$SIGN_WORK/metric-pub.pem"
|
||||
diff <(openssl pkey -pubin -in "$SIGN_WORK/metric-pub.pem" -pubout) \
|
||||
<(openssl pkey -pubin -in /var/lib/systemd/report.sign.plain/local.public -pubout)
|
||||
|
||||
# Describe must list the very same metric, of type string.
|
||||
varlinkctl call --more /run/systemd/report/io.systemd.Report.SignPlain io.systemd.Metrics.Describe {} |
|
||||
jq --seq -r 'select(.name == "io.systemd.Report.SignPlain.PublicKey") | .type' | grep -wx string >/dev/null
|
||||
|
||||
Reference in New Issue
Block a user