feat(proxy): apt proxy audit + doctor mode; reinstall-from-clone = update
apt-audit.js scans EVERY file in apt.conf.d for Acquire::*::Proxy, classifying: - creds: embedded userinfo (plaintext leak + stale-cred hammering risk) - overrides: sorts after 80proxy-bridge so apt last-wins REPLACES the bridge URL - external: points at a non-bridge proxy Installer Legacy section alerts + fixes (backup kept: *.disabled-by-proxy-bridge). New 'install.js doctor [--fix]': audits apt, bashrc, snap, service drop-ins, service state, keyring presence; exit 1 when issues found. Update semantics: deploy writes .installed-from (git commit + file hashes); runtime JSONs are never created if present and never overwritten; tests prove 3 consecutive installs leave runtime state byte-identical. Downgrade protection comes from the visible marker + diff reporting, never silent clobbering. Tests: 6 audit + 2 installer (update-semantics, doctor) — suite now 23/23.
This commit is contained in:
1 parent
a626392eb1
commit
a23fa00dfb
4 files changed
+339
-15
No files matched your search
@@ -34,7 +34,10 @@ function envFor(dirs) {
|
||||
}
|
||||
|
||||
function runInstaller(t, dirs, extraArgs = []) {
|
||||
const r = spawnSync('node', [INSTALLER, '--no-systemd', ...extraArgs], {
|
||||
// Subcommand (e.g. 'doctor') must come first, flags after — install.js parses
|
||||
// args[0] as the command.
|
||||
extraArgs.sort((a, b) => (a === 'doctor' ? -1 : b === 'doctor' ? 1 : 0));
|
||||
const r = spawnSync('node', [INSTALLER, ...extraArgs, '--no-systemd'], {
|
||||
env: envFor(dirs),
|
||||
encoding: 'utf8',
|
||||
timeout: 60000,
|
||||
@@ -155,3 +158,68 @@ test('credential verification: 407 from upstream does NOT store and reports FAIL
|
||||
assert.match(r.stdout, /\[FAILED\] Credentials/);
|
||||
assert.match(r.stdout, /nothing stored/i);
|
||||
});
|
||||
|
||||
test('reinstall from a clone behaves as an update: runtime state never clobbered', { timeout: 60000 }, async t => {
|
||||
const dirs = mkTmp(t);
|
||||
// First install: fresh, upstream disabled to skip creds entirely.
|
||||
const configPath = path.join(dirs.bridge, 'config.json');
|
||||
fs.writeFileSync(configPath, JSON.stringify({ enabled: false }));
|
||||
fs.writeFileSync(path.join(dirs.home, '.bashrc'), '');
|
||||
|
||||
const r1 = runInstaller(t, dirs, ['--non-interactive']);
|
||||
assert.match(r1.stdout, /Deploy code — code deployed/);
|
||||
assert.ok(fs.readFileSync(path.join(dirs.bridge, 'bridge.js')).length > 1000);
|
||||
|
||||
// Simulate the RUNNING SYSTEM writing runtime state after install 1:
|
||||
fs.writeFileSync(path.join(dirs.bridge, 'user.json'), JSON.stringify({ username: 'live-user' }));
|
||||
fs.writeFileSync(path.join(dirs.bridge, 'profile.json'), JSON.stringify({ profile: 'proxy-tke' }));
|
||||
fs.writeFileSync(path.join(dirs.bridge, 'overrides.json'), JSON.stringify({ rules: [{ match: 'x', profile: 'direct' }] }));
|
||||
const configAfter = fs.readFileSync(configPath, 'utf8');
|
||||
|
||||
// Second install (same source): must be a no-op for state files.
|
||||
const r2 = runInstaller(t, dirs, ['--non-interactive']);
|
||||
assert.ok(!/\[CHANGED\] Runtime config/.test(r2.stdout), 'runtime config import must not touch existing files');
|
||||
assert.equal(fs.readFileSync(configPath, 'utf8'), configAfter, 'config.json untouched');
|
||||
assert.deepEqual(JSON.parse(fs.readFileSync(path.join(dirs.bridge, 'profile.json'), 'utf8')), { profile: 'proxy-tke' });
|
||||
assert.deepEqual(JSON.parse(fs.readFileSync(path.join(dirs.bridge, 'overrides.json'), 'utf8')), { rules: [{ match: 'x', profile: 'direct' }] });
|
||||
// Version marker exists and records source hashes.
|
||||
const marker = fs.readFileSync(path.join(dirs.bridge, '.installed-from'), 'utf8');
|
||||
assert.ok(marker.includes('bridge.js:'), 'version marker records source hashes');
|
||||
|
||||
// Third install simulating an UPDATE: installed copy older than source → redeploy, state untouched.
|
||||
fs.appendFileSync(path.join(dirs.bridge, 'bridge.js'), '\n// pretend installed copy is older\n');
|
||||
const r3 = runInstaller(t, dirs, ['--non-interactive']);
|
||||
assert.match(r3.stdout, /\[CHANGED\] Deploy code/, 'newer source must redeploy');
|
||||
// Marker identifies the SOURCE build (git commit + source file hashes), so it is
|
||||
// stable across identical sources — and present; the installed bridge.js must now
|
||||
// equal the source again (the fake "older" line gone).
|
||||
assert.equal(fs.readFileSync(path.join(dirs.bridge, '.installed-from'), 'utf8'), marker);
|
||||
assert.ok(!fs.readFileSync(path.join(dirs.bridge, 'bridge.js'), 'utf8').includes('pretend installed copy is older'));
|
||||
assert.equal(fs.readFileSync(configPath, 'utf8'), configAfter, 'update must not touch runtime config');
|
||||
});
|
||||
|
||||
test('doctor reports apt threats and exits non-zero; clean tree exits zero', { timeout: 30000 }, async t => {
|
||||
const dirs = mkTmp(t);
|
||||
// Threat: file sorting after 80proxy-bridge with creds
|
||||
fs.writeFileSync(path.join(dirs.apt, '80proxy-bridge'),
|
||||
'Acquire::http::Proxy "http://127.0.0.1:8888/";\nAcquire::https::Proxy "http://127.0.0.1:8888/";\n');
|
||||
fs.writeFileSync(path.join(dirs.apt, '99bad'), 'Acquire::http::Proxy "http://u:p@proxy.corp:8080/";\n');
|
||||
fs.writeFileSync(path.join(dirs.home, '.bashrc'),
|
||||
'\n# Local Proxy Bridge Configuration #PROXY_BRIDGE_CONFIG\nexport http_proxy="http://127.0.0.1:8888/"\n');
|
||||
fs.writeFileSync(path.join(dirs.bridge, 'user.json'), JSON.stringify({ username: `doc-test-${process.pid}` }));
|
||||
|
||||
const r = runInstaller(t, dirs, ['doctor', '--no-systemd']);
|
||||
assert.equal(r.status, 1);
|
||||
assert.match(r.stdout, /99bad.*CREDENTIALS EMBEDDED/);
|
||||
assert.match(r.stdout, /NO password entry/);
|
||||
|
||||
// Clean tree: doctor passes
|
||||
const dirs2 = mkTmp(t);
|
||||
fs.writeFileSync(path.join(dirs2.apt, '80proxy-bridge'),
|
||||
'Acquire::http::Proxy "http://127.0.0.1:8888/";\nAcquire::https::Proxy "http://127.0.0.1:8888/";\n');
|
||||
fs.writeFileSync(path.join(dirs2.home, '.bashrc'),
|
||||
'\n# Local Proxy Bridge Configuration #PROXY_BRIDGE_CONFIG\nexport http_proxy="http://127.0.0.1:8888/"\n');
|
||||
const r2 = runInstaller(t, dirs2, ['doctor', '--no-systemd']);
|
||||
assert.equal(r2.status, 0, JSON.stringify(r2.stdout + r2.stderr));
|
||||
assert.match(r2.stdout, /All checks passed\./);
|
||||
});
|
||||
Reference in new issue
Block a user