P7 live probes: real-HTTP transcripts for forward_auth (inactive 503; active: allow/deny/unknown-device/step-up) via seeded real stores + registry stub provider
This commit is contained in:
1 file changed
+111
@@ -0,0 +1,111 @@
|
|||||||
|
/**
|
||||||
|
* Live transcript, phase 2: ACTIVE-provider outcomes over a real socket.
|
||||||
|
* Strategy: seed the REAL data files (/etc/dashcaddy/network-*.json — none
|
||||||
|
* exist on this fresh install) in the exact on-disk format the stores read,
|
||||||
|
* register a stub provider through the REAL registry (setActiveProvider),
|
||||||
|
* boot the REAL app, and hit the REAL mounted route over HTTP. Only the
|
||||||
|
* network edge (whois) is stubbed. Leaves no state behind that matters
|
||||||
|
* (fresh-install files; removed on exit).
|
||||||
|
*/
|
||||||
|
'use strict';
|
||||||
|
|
||||||
|
const http = require('http');
|
||||||
|
const fs = require('fs');
|
||||||
|
const { createApp } = require('../src/app');
|
||||||
|
const registry = require('../src/network/registry');
|
||||||
|
const DATA = '/etc/dashcaddy';
|
||||||
|
|
||||||
|
function req(port, method, reqPath, headers = {}) {
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
const r = http.request({ host: '127.0.0.1', port, method, path: reqPath, headers }, (res) => {
|
||||||
|
let body = '';
|
||||||
|
res.on('data', (c) => { body += c; });
|
||||||
|
res.on('end', () => resolve({ status: res.statusCode, headers: res.headers, body }));
|
||||||
|
});
|
||||||
|
r.on('error', reject);
|
||||||
|
r.end();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
const CREATED = [];
|
||||||
|
function writeIfAbsent(name, obj) {
|
||||||
|
const p = `${DATA}/${name}`;
|
||||||
|
if (!fs.existsSync(p)) {
|
||||||
|
fs.writeFileSync(p, JSON.stringify(obj, null, 2));
|
||||||
|
CREATED.push(p);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async function main() {
|
||||||
|
// ── seed the real store files (fresh install: none exist) ──
|
||||||
|
const now = new Date().toISOString();
|
||||||
|
writeIfAbsent('network-people.json', {
|
||||||
|
people: {
|
||||||
|
'p-alice': { id: 'p-alice', displayName: 'Alice', providerUserId: '1001', groups: ['Admin'], status: 'active', devices: [], createdAt: now, updatedAt: now },
|
||||||
|
'p-bob': { id: 'p-bob', displayName: 'Bob', providerUserId: '1002', groups: ['Family'], status: 'active', devices: [], createdAt: now, updatedAt: now },
|
||||||
|
},
|
||||||
|
});
|
||||||
|
writeIfAbsent('network-resources.json', {
|
||||||
|
resources: [
|
||||||
|
{ id: 'r-plex', name: 'plex', host: 'plex.internal', ports: ['*'], sensitive: false, createdAt: now, updatedAt: now },
|
||||||
|
{ id: 'r-vault', name: 'vault', host: 'vault.internal', ports: ['*'], sensitive: true, createdAt: now, updatedAt: now },
|
||||||
|
],
|
||||||
|
});
|
||||||
|
writeIfAbsent('network-access.json', {
|
||||||
|
grants: [
|
||||||
|
{ principal: { type: 'person', id: 'p-alice' }, resource: 'plex', allowed: true, updatedAt: now },
|
||||||
|
{ principal: { type: 'person', id: 'p-alice' }, resource: 'vault', allowed: true, updatedAt: now },
|
||||||
|
],
|
||||||
|
updatedAt: now,
|
||||||
|
});
|
||||||
|
|
||||||
|
// ── register + activate a stub provider through the REAL registry ──
|
||||||
|
const DEVICE_IP = '10.53.0.7';
|
||||||
|
const stub = {
|
||||||
|
id: () => 'stub-live',
|
||||||
|
whois: async (ip) => (ip === DEVICE_IP
|
||||||
|
? { personId: 'p-alice', deviceId: 'dev-1' }
|
||||||
|
: (ip === '10.53.0.9' ? { personId: 'p-bob', deviceId: 'dev-2' } : { personId: null, deviceId: null })),
|
||||||
|
listDevices: async () => [],
|
||||||
|
listPeople: async () => [],
|
||||||
|
capabilities: () => ({ id: 'stub-live' }),
|
||||||
|
getStatus: async () => ({ ok: true }),
|
||||||
|
createInvite: async () => { throw new Error('not needed for probe'); },
|
||||||
|
revokeInvite: async () => { throw new Error('not needed for probe'); },
|
||||||
|
blockPerson: async () => { throw new Error('not needed for probe'); },
|
||||||
|
unblockPerson: async () => { throw new Error('not needed for probe'); },
|
||||||
|
removePerson: async () => { throw new Error('not needed for probe'); },
|
||||||
|
revokeDevice: async () => { throw new Error('not needed for probe'); },
|
||||||
|
syncAccess: async () => { throw new Error('not needed for probe'); },
|
||||||
|
configureDns: async () => { throw new Error('not needed for probe'); },
|
||||||
|
};
|
||||||
|
registry.registerProvider('stub-live', () => stub);
|
||||||
|
registry.setActiveProvider('stub-live');
|
||||||
|
|
||||||
|
const { app } = await createApp();
|
||||||
|
const server = app.listen(0, '127.0.0.1');
|
||||||
|
await new Promise((r) => server.on('listening', r));
|
||||||
|
const port = server.address().port;
|
||||||
|
const FA = '/api/v1/network/forward-auth/';
|
||||||
|
|
||||||
|
const show = (label, res) => {
|
||||||
|
console.log(`### ${label}`);
|
||||||
|
console.log(`< HTTP ${res.status}`);
|
||||||
|
if (res.headers['www-authenticate']) console.log(`< www-authenticate: ${res.headers['www-authenticate']}`);
|
||||||
|
if (res.headers['x-forwarded-user']) console.log(`< x-forwarded-user: ${res.headers['x-forwarded-user']}`);
|
||||||
|
console.log(`< body: ${res.body.slice(0, 240)}`);
|
||||||
|
console.log('');
|
||||||
|
};
|
||||||
|
|
||||||
|
// loopback is in the default trustedProxies → forwarded headers honored
|
||||||
|
show('ALLOWED non-sensitive (alice device → plex.internal)', await req(port, 'GET', FA, { 'X-Forwarded-For': DEVICE_IP, 'X-Forwarded-Host': 'plex.internal' }));
|
||||||
|
show('DENIED (bob device → plex.internal, no grant)', await req(port, 'GET', FA, { 'X-Forwarded-For': '10.53.0.9', 'X-Forwarded-Host': 'plex.internal' }));
|
||||||
|
show('UNKNOWN-DEVICE (unrecognized tailnet ip)', await req(port, 'GET', FA, { 'X-Forwarded-For': '10.99.99.99', 'X-Forwarded-Host': 'plex.internal' }));
|
||||||
|
show('SENSITIVE step-up REQUIRED (alice → vault.internal, no session)', await req(port, 'GET', FA, { 'X-Forwarded-For': DEVICE_IP, 'X-Forwarded-Host': 'vault.internal' }));
|
||||||
|
|
||||||
|
server.close();
|
||||||
|
console.log('LIVE-PROBE-PHASE2-DONE');
|
||||||
|
process.exit(0);
|
||||||
|
}
|
||||||
|
|
||||||
|
main().catch((e) => { console.error('FATAL', e && e.stack || e); process.exit(1); });
|
||||||
Reference in new issue
Block a user