Files
safetynet/tests/broker-dns-policy-route.test.mjs
T

29 lines
3.6 KiB
JavaScript

import test from 'node:test';
import assert from 'node:assert/strict';
import {createRequire} from 'node:module';
import https from 'node:https';
import {readFile} from 'node:fs/promises';
import {dnsAddressBlocked} from '../packages/core/security.mjs';
const require=createRequire(import.meta.url),{createDnsPolicyRoute,assertSupportedDns}=require('../apps/network-broker/dns-policy-route.cjs');
const policy={security:{dns:{enabled:true,blockedIPs:['127.0.0.1','::1']}}};
test('blocked literal including mapped IPv6 is denied before any DNS lookup',async()=>{
let calls=0;const resolve=createDnsPolicyRoute({current:()=>true,blocked:a=>dnsAddressBlocked(a,policy),lookup:async()=>{calls++;throw Error();}});
for(const hostname of ['127.0.0.1','[::1]','::ffff:127.0.0.1'])await assert.rejects(resolve({hostname,port:443}),{message:'broker-route-denied'});assert.equal(calls,0);
});
test('real OS DNS blocked route produces zero TCP at controlled HTTPS receiver',async()=>{
const cert=await readFile(new URL('./fixtures/icap/cert.pem',import.meta.url)),key=await readFile(new URL('./fixtures/icap/key.pem',import.meta.url));let connections=0;
const server=https.createServer({cert,key},(_q,r)=>r.end('public'));server.on('connection',()=>connections++);await new Promise(r=>server.listen(0,'::',r));const port=server.address().port;
try{const {createHttpsForwarder}=require('../apps/network-broker/https-forwarder.cjs'),resolveRoute=createDnsPolicyRoute({current:()=>true,blocked:a=>dnsAddressBlocked(a,policy)}),forward=createHttpsForwarder({authority:'localhost:'+port,current:()=>true,resolveRoute,ca:cert});await assert.rejects(forward({request:{url:`https://localhost:${port}/`,method:'GET',headers:{}},body:Buffer.alloc(0),signal:new AbortController().signal}));assert.equal(connections,0);}finally{server.closeAllConnections();await new Promise(r=>server.close(r));}
});
test('system/VPN DNS all-address check refuses mixed blocked records and binds approved numeric route',async()=>{
let response=[{address:'192.0.2.1',family:4},{address:'127.0.0.1',family:4}];const resolve=createDnsPolicyRoute({current:()=>true,blocked:a=>dnsAddressBlocked(a,policy),lookup:async(host,options)=>{assert.equal(host,'intranet.example');assert.deepEqual(options,{all:true,verbatim:true});return response;}});
await assert.rejects(resolve({hostname:'intranet.example',port:443}),{message:'broker-route-denied'});response=[{address:'192.0.2.1',family:4}];const route=await resolve({hostname:'intranet.example',port:443});response[0].address='127.0.0.1';assert.equal(route.address,'192.0.2.1');assert.ok(Object.isFrozen(route));
});
test('revocation during original lookup and malformed results never produce approved route',async()=>{
let active=true;const resolve=createDnsPolicyRoute({current:()=>active,blocked:()=>false,lookup:async()=>{active=false;return [{address:'192.0.2.1'}];}});await assert.rejects(resolve({hostname:'example.test',port:443}),{message:'broker-route-denied'});
for(const response of [[],[{address:'hostname'}],null])await assert.rejects(createDnsPolicyRoute({current:()=>true,blocked:()=>false,lookup:async()=>response})({hostname:'example.test',port:443}),{message:'broker-route-denied'});
});
test('secure DNS and hostname proxy with active IP block policy refuse unsupported downgrade',()=>{
assert.throws(()=>assertSupportedDns({enterprise:{dns:{mode:'secure'}}},{mode:'direct'}),/broker-route-unsupported/);assert.throws(()=>assertSupportedDns(policy,{mode:'fixed_servers'}),/broker-route-unsupported/);assert.doesNotThrow(()=>assertSupportedDns(policy,{mode:'direct'}));assert.doesNotThrow(()=>assertSupportedDns({security:{dns:{enabled:false,blockedIPs:['127.0.0.1']}}},{mode:'fixed_servers'}));
});