The tree adds the installer, service and test paths admitted by this source commit. Nothing else changes about what may cross. Source-Sha: 3e4a3aa4ce092e9e3f51bc78daf3ddc14d9638f8 Policy-Sha: 3e4a3aa4ce092e9e3f51bc78daf3ddc14d9638f8 Tree-Digest: 89d6fce3828a0e0de0c8eeb41ee6750f4462217e2a04b5190297134d7f4a50c3
151 lines
6.3 KiB
Shell
151 lines
6.3 KiB
Shell
#!/bin/bash
|
|
# inspect-deb.sh — validate a built RedFlag .deb without installing it.
|
|
# Suitable for CI: exits non-zero on any failed assertion and prints a
|
|
# component|result|detail style report.
|
|
#
|
|
# Usage: installer/linux/inspect-deb.sh dist/redflag_0.3.0_amd64.deb [--version 0.3.0]
|
|
#
|
|
# Checks: control metadata, declared runtime dependencies, required payload
|
|
# paths and modes, maintainer-script shell syntax, and that the packaged
|
|
# binaries are Linux x86-64 ELF executables. It cannot verify installed-system
|
|
# behaviour — that needs a real install on a Debian host.
|
|
|
|
set -uo pipefail
|
|
|
|
DEB="${1:-}"
|
|
[ -n "$DEB" ] || { echo "usage: $0 <package.deb> [--version X.Y.Z]" >&2; exit 2; }
|
|
shift || true
|
|
WANT_VERSION=""
|
|
while [ $# -gt 0 ]; do
|
|
case "$1" in
|
|
--version) WANT_VERSION="$2"; shift 2 ;;
|
|
*) echo "unknown option: $1" >&2; exit 2 ;;
|
|
esac
|
|
done
|
|
[ -f "$DEB" ] || { echo "no such package: $DEB" >&2; exit 2; }
|
|
command -v dpkg-deb >/dev/null 2>&1 || { echo "dpkg-deb not found" >&2; exit 2; }
|
|
|
|
FAILURES=0
|
|
ok() { echo " [ok] $1: $2"; }
|
|
bad() { echo " [FAIL] $1: $2"; FAILURES=$((FAILURES + 1)); }
|
|
|
|
TMP="$(mktemp -d)"
|
|
trap 'rm -rf "$TMP"' EXIT
|
|
|
|
dpkg-deb --control "$DEB" "$TMP/DEBIAN" >/dev/null 2>&1 || { echo "cannot read control archive" >&2; exit 2; }
|
|
dpkg-deb --contents "$DEB" > "$TMP/contents" 2>/dev/null || { echo "cannot list contents" >&2; exit 2; }
|
|
dpkg-deb --extract "$DEB" "$TMP/root" >/dev/null 2>&1 || { echo "cannot extract payload" >&2; exit 2; }
|
|
|
|
echo "=== RedFlag package inspection: $DEB ==="
|
|
|
|
# ---- control ----------------------------------------------------------------
|
|
CONTROL="$TMP/DEBIAN/control"
|
|
field() { grep -m1 -i "^$1:" "$CONTROL" | sed "s/^[^:]*:[[:space:]]*//"; }
|
|
|
|
[ "$(field Package)" = redflag ] && ok control "Package: redflag" || bad control "unexpected Package: $(field Package)"
|
|
[ "$(field Architecture)" = amd64 ] && ok control "Architecture: amd64" || bad control "unexpected Architecture: $(field Architecture)"
|
|
PKG_VERSION="$(field Version)"
|
|
if [ -z "$PKG_VERSION" ] || echo "$PKG_VERSION" | grep -q '@'; then
|
|
bad control "Version not substituted: '$PKG_VERSION'"
|
|
elif [ -n "$WANT_VERSION" ] && [ "$PKG_VERSION" != "$WANT_VERSION" ]; then
|
|
bad control "Version $PKG_VERSION != expected $WANT_VERSION"
|
|
else
|
|
ok control "Version: $PKG_VERSION"
|
|
fi
|
|
grep -qi '^Maintainer:' "$CONTROL" && ok control "Maintainer present" || bad control "Maintainer missing"
|
|
grep -qi '^Description:' "$CONTROL" && ok control "Description present" || bad control "Description missing"
|
|
|
|
# ---- dependencies -----------------------------------------------------------
|
|
DEPENDS="$(tr '\n' ' ' < "$CONTROL" | sed -n 's/.*Depends:\(.*\)/\1/p' | sed 's/[A-Z][a-zA-Z-]*:.*//')"
|
|
for dep in libc6 sudo systemd python3 python3-cryptography libqt6quick6 libqt6qml6 qml6-module-qtquick; do
|
|
case "$DEPENDS" in
|
|
*"$dep"*) ok depends "$dep declared" ;;
|
|
*) bad depends "$dep not declared" ;;
|
|
esac
|
|
done
|
|
case "$DEPENDS" in
|
|
*polkit*) ok depends "polkit declared" ;;
|
|
*) bad depends "polkit not declared (transient-unit rule would be inert)" ;;
|
|
esac
|
|
|
|
# ---- payload ----------------------------------------------------------------
|
|
check_file() {
|
|
local path="$1" mode="$2"
|
|
if [ ! -e "$TMP/root$path" ]; then
|
|
bad payload "$path missing"
|
|
return
|
|
fi
|
|
local actual; actual="$(stat -c%a "$TMP/root$path")"
|
|
if [ -n "$mode" ] && [ "$actual" != "$mode" ]; then
|
|
bad payload "$path mode $actual, expected $mode"
|
|
else
|
|
ok payload "$path ($actual)"
|
|
fi
|
|
}
|
|
|
|
check_file /usr/bin/redflag-agent 755
|
|
check_file /usr/bin/redflag-helper 755
|
|
check_file /usr/bin/redflag-desktop 755
|
|
check_file /lib/systemd/system/redflag-agent.service 644
|
|
check_file /etc/sudoers.d/redflag-agent 440
|
|
check_file /etc/polkit-1/rules.d/50-redflag-agent.rules 644
|
|
check_file /etc/xdg/autostart/redflag-desktop.desktop 644
|
|
check_file /usr/share/applications/redflag-desktop.desktop 644
|
|
check_file /usr/lib/redflag/provision-standalone-authority.sh 755
|
|
check_file /usr/share/doc/redflag/copyright 644
|
|
check_file /usr/share/doc/redflag/THIRD_PARTY_LICENSES.md 644
|
|
|
|
# Debian policy: nothing under /usr/local may be shipped in the archive.
|
|
if grep -qE ' \./usr/local/' "$TMP/contents"; then
|
|
bad policy "package ships files under /usr/local (postinst must create those symlinks instead)"
|
|
else
|
|
ok policy "no /usr/local content in the archive"
|
|
fi
|
|
|
|
for b in redflag-agent redflag-helper redflag-desktop; do
|
|
p="$TMP/root/usr/bin/$b"
|
|
[ -f "$p" ] || continue
|
|
magic="$(head -c 20 "$p" | od -An -tx1 | tr -d ' \n')"
|
|
if [ "${magic:0:8}" = "7f454c46" ] && [ "${magic:36:4}" = "3e00" ]; then
|
|
ok binary "$b is x86-64 ELF ($(stat -c%s "$p") bytes)"
|
|
else
|
|
bad binary "$b is not a Linux x86-64 ELF"
|
|
fi
|
|
done
|
|
|
|
# ---- maintainer scripts -----------------------------------------------------
|
|
for s in preinst postinst prerm postrm; do
|
|
f="$TMP/DEBIAN/$s"
|
|
if [ ! -f "$f" ]; then bad maintscript "$s missing"; continue; fi
|
|
[ -x "$f" ] || bad maintscript "$s not executable"
|
|
if sh -n "$f" 2>/dev/null; then ok maintscript "$s syntax ok"; else bad maintscript "$s syntax error"; fi
|
|
grep -q '@VERSION@' "$f" && bad maintscript "$s has unsubstituted @VERSION@"
|
|
done
|
|
|
|
# The provisioning script is the only authority logic: the package must call it,
|
|
# not reimplement it. A postinst that mints keys itself is a review failure.
|
|
if grep -q 'provision-standalone-authority.sh' "$TMP/DEBIAN/postinst"; then
|
|
ok authority "postinst delegates to the provisioning script"
|
|
else
|
|
bad authority "postinst does not call provision-standalone-authority.sh"
|
|
fi
|
|
if grep -qE 'mint --init-key|authority_local\.key' "$TMP/DEBIAN/postinst"; then
|
|
bad authority "postinst touches authority key material directly"
|
|
else
|
|
ok authority "postinst does not duplicate key handling"
|
|
fi
|
|
if grep -q 'refresh_token' "$TMP/DEBIAN/postinst"; then
|
|
ok authority "postinst checks for fleet enrollment material"
|
|
else
|
|
bad authority "postinst has no fleet-install guard"
|
|
fi
|
|
|
|
if [ -f "$TMP/DEBIAN/md5sums" ]; then ok integrity "md5sums present"; else bad integrity "md5sums missing"; fi
|
|
if [ -f "$TMP/DEBIAN/conffiles" ]; then ok integrity "conffiles present"; else bad integrity "conffiles missing"; fi
|
|
|
|
echo ""
|
|
if [ "$FAILURES" -gt 0 ]; then
|
|
echo "Result: $FAILURES check(s) failed"
|
|
exit 1
|
|
fi
|
|
echo "Result: all checks passed"
|