fix: require opt-in for unverified apt sources
This commit is contained in:
parent
9f34641545
commit
a89eafaebc
4 changed files with 76 additions and 12 deletions
21
README.md
21
README.md
|
|
@ -65,7 +65,26 @@ sudo apt-get update && sudo apt-get install stoke
|
|||
|
||||
Upgrades then arrive through regular `apt-get upgrade`. `install-apt.sh` performs all of the above, adding the NodeSource repository only when no already-configured apt source offers a new-enough nodejs.
|
||||
|
||||
Note: apt releases that verify OpenPGP with `sqv` (Debian 13+, apt >= 2.9) may reject signatures produced by affected Forgejo versions. `install-apt.sh` permits its `[trusted=yes]` compatibility fallback only when apt reports an explicit signature failure. Authentication, network, and all other update failures are fatal and leave the `signed-by=` source unchanged, so a transient error cannot silently disable verification.
|
||||
Note: apt releases that verify OpenPGP with `sqv` (Debian 13+, apt >= 2.9)
|
||||
may reject signatures produced by affected Forgejo versions. By default,
|
||||
`install-apt.sh` refuses that signature failure and removes the Forge source;
|
||||
authentication, network, and all other update failures are also fatal and never
|
||||
disable verification.
|
||||
|
||||
If the installer reports the known `sqv` parsing failure and you deliberately
|
||||
accept HTTPS-only integrity without OpenPGP verification, opt in on a second
|
||||
run:
|
||||
|
||||
```bash
|
||||
export STOKE_ALLOW_UNVERIFIED_APT=1
|
||||
sudo --preserve-env=FORGE_USER,FORGE_TOKEN,STOKE_ALLOW_UNVERIFIED_APT \
|
||||
bash /tmp/stoke-install-apt.sh
|
||||
unset STOKE_ALLOW_UNVERIFIED_APT
|
||||
```
|
||||
|
||||
This exact opt-in is the only path in the installer that writes a
|
||||
`[trusted=yes]` source. The installer prints the security trade-off again when
|
||||
it takes that path.
|
||||
|
||||
As a fallback, each release also has the `.deb` attached for direct install: `sudo dpkg -i stoke_<version>_all.deb`.
|
||||
|
||||
|
|
|
|||
|
|
@ -1 +1 @@
|
|||
- Private apt installs now keep credentials out of source URLs and retain signature verification after non-signature update failures. (#1).
|
||||
- Private apt installs keep credentials out of source URLs, refuse unverifiable registries by default, and require an explicit HTTPS-only opt-in to disable signature checks. (#1).
|
||||
|
|
|
|||
|
|
@ -9,6 +9,7 @@
|
|||
# Usage:
|
||||
# ./scripts/install-apt.sh
|
||||
# FORGE_URL=... OWNER=... ./scripts/install-apt.sh # non-default instance
|
||||
# STOKE_ALLOW_UNVERIFIED_APT=1 ./scripts/install-apt.sh # explicit HTTPS-only opt-in
|
||||
#
|
||||
# Run as root or as a user with sudo.
|
||||
|
||||
|
|
@ -20,6 +21,7 @@ DISTRIBUTION="${DISTRIBUTION:-stable}"
|
|||
COMPONENT="${COMPONENT:-main}"
|
||||
FORGE_USER="${FORGE_USER:-}"
|
||||
FORGE_TOKEN="${FORGE_TOKEN:-}"
|
||||
ALLOW_UNVERIFIED="${STOKE_ALLOW_UNVERIFIED_APT:-}"
|
||||
# Where apt configuration lives; overridable so tests can run against a
|
||||
# throwaway directory instead of the real /etc/apt.
|
||||
APT_ETC="${STOKE_APT_ETC:-/etc/apt}"
|
||||
|
|
@ -34,6 +36,11 @@ if { [ -n "$FORGE_USER" ] && [ -z "$FORGE_TOKEN" ]; } \
|
|||
exit 1
|
||||
fi
|
||||
|
||||
if [ -n "$ALLOW_UNVERIFIED" ] && [ "$ALLOW_UNVERIFIED" != "1" ]; then
|
||||
echo "error: STOKE_ALLOW_UNVERIFIED_APT must be unset or exactly 1" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
SUDO=""
|
||||
if [ "$(id -u)" -ne 0 ]; then
|
||||
command -v sudo >/dev/null 2>&1 || { echo "error: run as root or install sudo" >&2; exit 1; }
|
||||
|
|
@ -127,9 +134,9 @@ fi
|
|||
# is not the key algorithm, because the registry serves an RSA-2048 signing
|
||||
# key (`gpg --list-packets` on repository.key reports `algo 1` with a
|
||||
# 2048-bit pkey[0]). Try the properly signed source first so this heals
|
||||
# automatically once the forge is fixed. Only that signature-error
|
||||
# class permits the compatibility fallback; auth, network, and other failures
|
||||
# must leave verification enabled and retain apt's original diagnostic.
|
||||
# automatically once the forge is fixed. Only that signature-error class, plus
|
||||
# the user's exact opt-in, permits an unverified source; auth, network, and
|
||||
# other failures must leave verification enabled and retain apt's diagnostic.
|
||||
if update_output="$(update_only_source "$LIST" 2>&1)"; then
|
||||
printf '%s\n' "$update_output"
|
||||
else
|
||||
|
|
@ -140,11 +147,21 @@ else
|
|||
printf '%s\n' "$update_output" >&2
|
||||
exit "$update_status"
|
||||
fi
|
||||
echo
|
||||
echo "WARNING: signature verification failed (known Forgejo registry issue" >&2
|
||||
echo "with sqv-based apt). Falling back to [trusted=yes]; transport" >&2
|
||||
echo "security is provided by HTTPS to $FORGE_URL." >&2
|
||||
echo
|
||||
if [ "$ALLOW_UNVERIFIED" != "1" ]; then
|
||||
$SUDO rm -f "$LIST"
|
||||
echo "error: apt could not verify the Forgejo registry signature." >&2
|
||||
echo "On sqv-based apt, the known cause is that sqv-based apt cannot parse" >&2
|
||||
echo "the Forgejo registry signature, although gpgv-based apt accepts it." >&2
|
||||
echo "No apt source was left behind." >&2
|
||||
echo "If you knowingly accept HTTPS-only integrity, re-run with" >&2
|
||||
echo "STOKE_ALLOW_UNVERIFIED_APT=1 to disable OpenPGP verification." >&2
|
||||
exit "$update_status"
|
||||
fi
|
||||
echo >&2
|
||||
echo "WARNING: OpenPGP signature verification is disabled for the Forgejo" >&2
|
||||
echo "registry at $FORGE_URL. You explicitly accepted HTTPS-only integrity" >&2
|
||||
echo "by setting STOKE_ALLOW_UNVERIFIED_APT=1." >&2
|
||||
echo >&2
|
||||
echo "deb [trusted=yes] $FORGE_URL/api/packages/$OWNER/debian $DISTRIBUTION $COMPONENT" \
|
||||
| $SUDO tee "$LIST" >/dev/null
|
||||
$SUDO chmod 0644 "$LIST"
|
||||
|
|
|
|||
|
|
@ -15,12 +15,13 @@ const SCRIPT = path.join(__dirname, '..', 'scripts', 'install-apt.sh');
|
|||
// releaseStatus HTTP status curl reports for the registry Release file
|
||||
// sourceUpdateError stderr and exit 100 for the first signed stoke update
|
||||
// forgeUser/token private-registry credentials
|
||||
// allowUnverified explicit HTTPS-only integrity opt-in
|
||||
// The apt-cache stub localizes the "Candidate:" label unless LC_ALL=C is set,
|
||||
// so every scenario doubles as a regression test for locale-safe parsing.
|
||||
const cleanups = [];
|
||||
process.on('exit', () => { for (const dir of cleanups) fs.rmSync(dir, { recursive: true, force: true }); });
|
||||
|
||||
function runScenario({ candInitial, candAfterUpdate, candAfterNodesource, preexistingNodesourceList, releaseStatus, sourceUpdateError, forgeUser, forgeToken }) {
|
||||
function runScenario({ candInitial, candAfterUpdate, candAfterNodesource, preexistingNodesourceList, releaseStatus, sourceUpdateError, forgeUser, forgeToken, allowUnverified }) {
|
||||
const root = fs.mkdtempSync(path.join(os.tmpdir(), 'stoke-apt-test-'));
|
||||
cleanups.push(root);
|
||||
const bin = path.join(root, 'bin');
|
||||
|
|
@ -101,6 +102,7 @@ function runScenario({ candInitial, candAfterUpdate, candAfterNodesource, preexi
|
|||
SOURCE_UPDATE_ERROR: sourceUpdateError || '',
|
||||
FORGE_USER: forgeUser || '',
|
||||
FORGE_TOKEN: forgeToken || '',
|
||||
STOKE_ALLOW_UNVERIFIED_APT: allowUnverified || '',
|
||||
LC_ALL: 'es_ES.UTF-8', // localized environment; the script must force C
|
||||
},
|
||||
});
|
||||
|
|
@ -194,16 +196,42 @@ test('registry Release file present: proceeds with the install', () => {
|
|||
assert.match(s.aptGetLog, /install -y stoke/);
|
||||
});
|
||||
|
||||
test('signature verification failure alone may use the trusted compatibility fallback', () => {
|
||||
test('signature verification failure refuses by default and removes the forge source', () => {
|
||||
const s = runScenario({
|
||||
candInitial: '22.23.1-1nodesource1',
|
||||
sourceUpdateError: 'W: GPG error: signatures could not be verified: NO_PUBKEY DEADBEEF\nE: The repository is not signed.',
|
||||
});
|
||||
assert.notEqual(s.res.status, 0);
|
||||
assert.equal(s.forgeList, null);
|
||||
assert.match(s.res.stderr, /sqv-based apt cannot parse\s+the Forgejo registry signature/);
|
||||
assert.match(s.res.stderr, /STOKE_ALLOW_UNVERIFIED_APT=1/);
|
||||
assert.doesNotMatch(s.aptGetLog, /install -y stoke/);
|
||||
});
|
||||
|
||||
test('exact opt-in permits an HTTPS-only forge source after signature failure', () => {
|
||||
const s = runScenario({
|
||||
candInitial: '22.23.1-1nodesource1',
|
||||
sourceUpdateError: 'W: GPG error: signatures could not be verified: NO_PUBKEY DEADBEEF\nE: The repository is not signed.',
|
||||
allowUnverified: '1',
|
||||
});
|
||||
assert.equal(s.res.status, 0, s.res.stderr);
|
||||
assert.match(s.forgeList, /\[trusted=yes\]/);
|
||||
assert.match(s.res.stderr, /OpenPGP signature verification is disabled/);
|
||||
assert.match(s.res.stderr, /HTTPS-only integrity/);
|
||||
assert.match(s.aptGetLog, /install -y stoke/);
|
||||
});
|
||||
|
||||
test('unrecognized opt-in value is rejected before configuring apt', () => {
|
||||
const s = runScenario({
|
||||
candInitial: '22.23.1-1nodesource1',
|
||||
allowUnverified: 'yes',
|
||||
});
|
||||
assert.notEqual(s.res.status, 0);
|
||||
assert.match(s.res.stderr, /STOKE_ALLOW_UNVERIFIED_APT must be unset or exactly 1/);
|
||||
assert.equal(s.forgeList, null);
|
||||
assert.equal(s.aptGetLog, '');
|
||||
});
|
||||
|
||||
test('network update failure stays fatal and never disables signature verification', () => {
|
||||
const failure = 'Temporary failure resolving forgejo.heavyduty.builders';
|
||||
const s = runScenario({
|
||||
|
|
|
|||
Loading…
Reference in a new issue