Revoke scoped credential policies before package downgrade

This commit is contained in:
FrameBridge compatibility build committed 2026-10-27 11:23:36 -04:00
1 parent bf79a79342
commit 5682508644
8 files changed
+138 -17

No files matched your search

+1 -1
View File
@@ -42,7 +42,7 @@ fb connect
### Updating and removal
Install an official update through the normal package manager when that update has been published. Normal removal stops package-owned managed processes and can retain the user profile and encrypted connection material; it is not a credential-forgetting operation. On systemd 255, package removal also revokes the administrative boot-credential grant. After reinstalling, run fb connect interactively to provision that grant again; retained files alone do not restore startup. Use the explicit credentials forget action first when that is your intended outcome. Do not assume purge erases user-profile data.
Install an official update through the normal package manager when that update has been published. Normal removal stops package-owned managed processes and can retain the user profile and encrypted connection material; it is not a credential-forgetting operation. On systemd 255, package removal also revokes the administrative boot-credential grant. After reinstalling, run fb connect interactively to provision that grant again; retained files alone do not restore startup. Use the explicit credentials forget action first when that is your intended outcome. Do not assume purge erases user-profile data. An explicit downgrade also revokes administrative helper grants before the older package files replace the helper. Helper-backed profiles require a new interactive connect with a compatible package before managed startup can be restored.
~~~sh
sudo dpkg --remove framebridge-original-expanded
+3 -3
View File
@@ -30,7 +30,7 @@
},
{
"heading": "Updating and removal",
"body": "Install an official update through the normal package manager when that update has been published. Normal removal stops package-owned managed processes and can retain the user profile and encrypted connection material; it is not a credential-forgetting operation. On systemd 255, package removal also revokes the administrative boot-credential grant. After reinstalling, run fb connect interactively to provision that grant again; retained files alone do not restore startup. Use the explicit credentials forget action first when that is your intended outcome. Do not assume purge erases user-profile data.",
"body": "Install an official update through the normal package manager when that update has been published. Normal removal stops package-owned managed processes and can retain the user profile and encrypted connection material; it is not a credential-forgetting operation. On systemd 255, package removal also revokes the administrative boot-credential grant. After reinstalling, run fb connect interactively to provision that grant again; retained files alone do not restore startup. Use the explicit credentials forget action first when that is your intended outcome. Do not assume purge erases user-profile data. An explicit downgrade also revokes administrative helper grants before the older package files replace the helper. Helper-backed profiles require a new interactive connect with a compatible package before managed startup can be restored.",
"commands": [
"sudo dpkg --remove framebridge-original-expanded"
]
@@ -64,7 +64,7 @@
],
"read_only": true,
"capabilities_verified": false,
"guide_sha256": "ce03094ef55168ab22d393ce1f9193aa0a26bc3524eaaecfc8f48bed1c788cf1"
"guide_sha256": "d6ef35f7879fa290843fbfcadf403270ec0e70e403b0230b3469659492385928"
},
"commands": {
"ok": true,
@@ -567,5 +567,5 @@
"guide_sha256": "54b70093617e6992de04d41ddd367ffdd9a6bd6c317c99a886d6391277c55f6b"
}
},
"guide_sha256": "b6060e0572a612d8d7d8a988bc9b0bca7aa8db8ef267004e6a1495e9ab5f34a4"
"guide_sha256": "ab3cce8d01d63a34fa2634738a9e0b37c89f5e1f51ba88d2b33abf9a872ad7ab"
}
+1 -1
View File
@@ -42,7 +42,7 @@ fb connect
### Updating and removal
Install an official update through the normal package manager when that update has been published. Normal removal stops package-owned managed processes and can retain the user profile and encrypted connection material; it is not a credential-forgetting operation. On systemd 255, package removal also revokes the administrative boot-credential grant. After reinstalling, run fb connect interactively to provision that grant again; retained files alone do not restore startup. Use the explicit credentials forget action first when that is your intended outcome. Do not assume purge erases user-profile data.
Install an official update through the normal package manager when that update has been published. Normal removal stops package-owned managed processes and can retain the user profile and encrypted connection material; it is not a credential-forgetting operation. On systemd 255, package removal also revokes the administrative boot-credential grant. After reinstalling, run fb connect interactively to provision that grant again; retained files alone do not restore startup. Use the explicit credentials forget action first when that is your intended outcome. Do not assume purge erases user-profile data. An explicit downgrade also revokes administrative helper grants before the older package files replace the helper. Helper-backed profiles require a new interactive connect with a compatible package before managed startup can be restored.
~~~sh
sudo dpkg --remove framebridge-original-expanded
+1 -1
View File
@@ -42,7 +42,7 @@ fb connect
### Updating and removal
Install an official update through the normal package manager when that update has been published. Normal removal stops package-owned managed processes and can retain the user profile and encrypted connection material; it is not a credential-forgetting operation. On systemd 255, package removal also revokes the administrative boot-credential grant. After reinstalling, run fb connect interactively to provision that grant again; retained files alone do not restore startup. Use the explicit credentials forget action first when that is your intended outcome. Do not assume purge erases user-profile data.
Install an official update through the normal package manager when that update has been published. Normal removal stops package-owned managed processes and can retain the user profile and encrypted connection material; it is not a credential-forgetting operation. On systemd 255, package removal also revokes the administrative boot-credential grant. After reinstalling, run fb connect interactively to provision that grant again; retained files alone do not restore startup. Use the explicit credentials forget action first when that is your intended outcome. Do not assume purge erases user-profile data. An explicit downgrade also revokes administrative helper grants before the older package files replace the helper. Helper-backed profiles require a new interactive connect with a compatible package before managed startup can be restored.
~~~sh
sudo dpkg --remove framebridge-original-expanded
+1 -1
View File
@@ -25,7 +25,7 @@ sudo apt install ./framebridge\-expanded_0.3.2~compat.20261027.1_amd64.deb
fb connect
.fi
.SS Updating and removal
Install an official update through the normal package manager when that update has been published. Normal removal stops package\-owned managed processes and can retain the user profile and encrypted connection material; it is not a credential\-forgetting operation. On systemd 255, package removal also revokes the administrative boot\-credential grant. After reinstalling, run fb connect interactively to provision that grant again; retained files alone do not restore startup. Use the explicit credentials forget action first when that is your intended outcome. Do not assume purge erases user\-profile data.
Install an official update through the normal package manager when that update has been published. Normal removal stops package\-owned managed processes and can retain the user profile and encrypted connection material; it is not a credential\-forgetting operation. On systemd 255, package removal also revokes the administrative boot\-credential grant. After reinstalling, run fb connect interactively to provision that grant again; retained files alone do not restore startup. Use the explicit credentials forget action first when that is your intended outcome. Do not assume purge erases user\-profile data. An explicit downgrade also revokes administrative helper grants before the older package files replace the helper. Helper\-backed profiles require a new interactive connect with a compatible package before managed startup can be restored.
.PP
.nf
sudo dpkg \-\-remove framebridge\-original\-expanded
@@ -19,7 +19,7 @@ GUIDE = {'start': {'title': 'Start with FrameBridge',
'body': 'Install the .deb built for your exact Debian 13, Ubuntu 24.04/26.04, Linux Mint 22.x or LMDE 7 amd64 profile. These compatibility candidates still require installation and lifecycle validation on each target. Select the artifact from the matching profile folder and keep the distribution system Python. Normal first connection does not require a separate setup, doctor or start sequence. Run connect in a normal interactive terminal; advanced setup is optional for central/node roles and public settings.',
'commands': ["sudo apt install ./framebridge-expanded_" + __version__ + "_amd64.deb", 'fb connect']},
{'heading': 'Updating and removal',
'body': 'Install an official update through the normal package manager when that update has been published. Normal removal stops package-owned managed processes and can retain the user profile and encrypted connection material; it is not a credential-forgetting operation. On systemd 255, package removal also revokes the administrative boot-credential grant. After reinstalling, run fb connect interactively to provision that grant again; retained files alone do not restore startup. Use the explicit credentials forget action first when that is your intended outcome. Do not assume purge erases user-profile data.',
'body': 'Install an official update through the normal package manager when that update has been published. Normal removal stops package-owned managed processes and can retain the user profile and encrypted connection material; it is not a credential-forgetting operation. On systemd 255, package removal also revokes the administrative boot-credential grant. After reinstalling, run fb connect interactively to provision that grant again; retained files alone do not restore startup. Use the explicit credentials forget action first when that is your intended outcome. Do not assume purge erases user-profile data. An explicit downgrade also revokes administrative helper grants before the older package files replace the helper. Helper-backed profiles require a new interactive connect with a compatible package before managed startup can be restored.',
'commands': ['sudo dpkg --remove framebridge-original-expanded']},
{'heading': 'Human authentication',
'body': 'The normal connection asks for an optional account label, a '
+34 -9
View File
@@ -10,6 +10,7 @@ import os
import re
from pathlib import Path
import shutil
import shlex
import subprocess
from build_profiles import CANDIDATE_VERSION, PROFILES, SOURCE_VENDOR, VENDOR_RELATIVE, target_profile, validate_vendor, vendor_versions
@@ -136,6 +137,38 @@ def install_legal_material(docs):
"dependency_source_archives": DEPENDENCY_SOURCE_ASSETS
}, indent=2) + "\n")
def maintainer_prerm(version):
"""Revoke helper grants before a downgrade removes the helper's code.
Equal-version reinstalls and forward upgrades retain managed credentials.
The fixed installed cleanup module validates the helper's root ownership,
permissions and ancestors before running its bounded purge-all command.
"""
if not re.fullmatch(r"[0-9][0-9A-Za-z.+:~\-]*", version):
raise ValueError("Invalid Debian package version for maintainer script")
helper_cleanup = (
'import runpy; runpy.run_path('
'"/usr/lib/framebridge-expanded/expanded/framebridge_expanded/uninstall.py")'
'["purge_credential_helpers"]()'
)
return (
"#!/bin/sh\nset -eu\n"
'case "$1" in\n'
" remove|deconfigure)\n"
" /usr/bin/python3 -I -S /usr/lib/framebridge-expanded/expanded/framebridge_expanded/uninstall.py --all-users\n"
" if [ -e /etc/framebridge-expanded/screen-access.json ] || [ -L /etc/framebridge-expanded/screen-access.json ]; then\n"
" /usr/lib/framebridge-expanded/bin/framebridge-screen-access disable\n"
" fi\n"
" ;;\n"
" upgrade)\n"
' /usr/bin/dpkg --validate-version "$2"\n'
' if /usr/bin/dpkg --compare-versions "$2" lt ' + shlex.quote(version) + "; then\n"
" /usr/bin/python3 -I -S -c " + shlex.quote(helper_cleanup) + "\n"
" fi\n"
" ;;\nesac\n"
)
def main(argv=None):
global VERSION, PUBLIC_FILENAME, RELEASE, STAGE, PREFIX
parser = argparse.ArgumentParser(description=__doc__)
@@ -246,15 +279,7 @@ def main(argv=None):
" Optional virtual displays are explicitly identified as virtual.\n"
" No phone or Owner Device pairing. Services start after connection setup.\n"
" Installing the package alone does not activate a connection.\n")
(control / "prerm").write_text(
"#!/bin/sh\nset -eu\n"
"case \"$1\" in\n"
" remove|deconfigure)\n"
" /usr/bin/python3 -I -S /usr/lib/framebridge-expanded/expanded/framebridge_expanded/uninstall.py --all-users\n"
" if [ -e /etc/framebridge-expanded/screen-access.json ] || [ -L /etc/framebridge-expanded/screen-access.json ]; then\n"
" /usr/lib/framebridge-expanded/bin/framebridge-screen-access disable\n"
" fi\n"
" ;;\nesac\n")
(control / "prerm").write_text(maintainer_prerm(VERSION))
(control / "prerm").chmod(0o755)
rows = []
for path in sorted(STAGE.rglob("*")):
+96
View File
@@ -0,0 +1,96 @@
"""Execute maintscript branches with cleanup commands intercepted in a tempdir."""
import json
import os
from pathlib import Path
import shlex
import subprocess
import sys
import tempfile
import unittest
sys.path.insert(0, str(Path(__file__).resolve().parents[1] / "packaging"))
from build_expanded import maintainer_prerm
from build_profiles import CANDIDATE_VERSION
class MaintainerScriptTests(unittest.TestCase):
def setUp(self):
self.tmp = tempfile.TemporaryDirectory()
self.addCleanup(self.tmp.cleanup)
self.root = Path(self.tmp.name)
self.trace = self.root / "calls.jsonl"
self.stub = self.root / "cleanup-stub"
self.stub.write_text(
"#!" + sys.executable + "\n"
"import json,os,sys\n"
"with open(os.environ['TEST_CALLS'], 'a') as stream: stream.write(json.dumps(sys.argv[1:])+'\\n')\n"
"raise SystemExit(int(os.environ.get('TEST_CLEANUP_RC','0')))\n"
)
self.stub.chmod(0o755)
self.screen = self.root / "screen-access.json"
def run_script(self, args, *, fail=0, screen=False):
script = maintainer_prerm(CANDIDATE_VERSION)
# Only side-effecting commands and config probes are redirected. The
# real dpkg comparisons are read-only and verify Debian version ordering.
script = script.replace("/usr/bin/python3", shlex.quote(str(self.stub)))
script = script.replace("/usr/lib/framebridge-expanded/bin/framebridge-screen-access", shlex.quote(str(self.stub)))
script = script.replace("/etc/framebridge-expanded/screen-access.json", shlex.quote(str(self.screen)))
if screen:
self.screen.write_text("{}")
env = dict(os.environ, TEST_CALLS=str(self.trace), TEST_CLEANUP_RC=str(fail))
result = subprocess.run(["/bin/sh", "-s", "--", *args], input=script, text=True,
capture_output=True, env=env, timeout=10)
calls = [json.loads(row) for row in self.trace.read_text().splitlines()] if self.trace.exists() else []
return result, calls
def test_downgrade_revokes_only_helper_before_replacement(self):
result, calls = self.run_script(["upgrade", "0.3.1"])
self.assertEqual(result.returncode, 0, result.stderr)
self.assertEqual(len(calls), 1)
self.assertEqual(calls[0][:3], ["-I", "-S", "-c"])
self.assertEqual(calls[0][3], 'import runpy; runpy.run_path("/usr/lib/framebridge-expanded/expanded/framebridge_expanded/uninstall.py")["purge_credential_helpers"]()')
self.assertNotIn("--all-users", calls[0])
def test_forward_upgrade_retains_helper_state(self):
result, calls = self.run_script(["upgrade", "0.3.2"])
self.assertEqual(result.returncode, 0, result.stderr)
self.assertEqual(calls, [])
def test_same_version_reinstall_retains_helper_state(self):
result, calls = self.run_script(["upgrade", CANDIDATE_VERSION])
self.assertEqual(result.returncode, 0, result.stderr)
self.assertEqual(calls, [])
def test_remove_and_deconfigure_keep_existing_cleanup(self):
for operation in ("remove", "deconfigure"):
if self.trace.exists():
self.trace.unlink()
result, calls = self.run_script([operation], screen=True)
self.assertEqual(result.returncode, 0, result.stderr)
self.assertEqual(calls, [["-I", "-S", "/usr/lib/framebridge-expanded/expanded/framebridge_expanded/uninstall.py", "--all-users"], ["disable"]])
def test_failed_helper_cleanup_blocks_downgrade(self):
result, calls = self.run_script(["upgrade", "0.3.1"], fail=17)
self.assertEqual(result.returncode, 17)
self.assertEqual(len(calls), 1)
def test_invalid_target_does_not_execute_or_interpolate(self):
marker = self.root / "must-not-exist"
result, calls = self.run_script(["upgrade", "0.3.1;$(touch " + str(marker) + ")"])
self.assertNotEqual(result.returncode, 0)
self.assertEqual(calls, [])
self.assertFalse(marker.exists())
def test_missing_target_fails_closed(self):
result, calls = self.run_script(["upgrade"])
self.assertNotEqual(result.returncode, 0)
self.assertEqual(calls, [])
def test_current_version_cannot_inject_script_paths(self):
with self.assertRaises(ValueError):
maintainer_prerm("0.3.2'; /tmp/untrusted-script; '")
if __name__ == "__main__":
unittest.main()