Files
safetynet/apps/network-broker/dns-policy-route.cjs
T

25 lines
1.7 KiB
JavaScript

'use strict';
const {isIP}=require('node:net');
const dns=require('node:dns').promises;
// Resolve once in the broker and bind the approved numeric address to the dial.
// Active IP policy uses this approved tuple for numeric proxy CONNECT; ordinary
// hostname CONNECT remains supported when that origin IP policy is inactive.
function createDnsPolicyRoute({lookup=dns.lookup,blocked,current}={}){
if(typeof lookup!=='function'||typeof blocked!=='function'||typeof current!=='function')throw Error('broker-route-denied');
return async({hostname,port,signal})=>{
const check=()=>{if(signal?.aborted||current()!==true)throw Error('broker-route-denied');};
try{
check();if(typeof hostname!=='string'||!hostname||hostname.length>253||!Number.isSafeInteger(port)||port<1||port>65535)throw Error();
const literal=hostname.replace(/^\[|\]$/g,'');
if(isIP(literal)){if(blocked(literal))throw Error();check();return Object.freeze({approved:true,address:literal,port});}
const records=await lookup(hostname,{all:true,verbatim:true});check();
if(!Array.isArray(records)||!records.length||records.length>64||records.some(r=>!r||typeof r.address!=='string'||!isIP(r.address)||blocked(r.address)))throw Error();
const address=records[0].address;check();return Object.freeze({approved:true,address,port});
}catch{throw Error('broker-route-denied');}
};
}
function assertSupportedDns(policy,network,{numericOriginSupported=false}={}){
if(policy.enterprise?.dns?.mode==='secure'||!numericOriginSupported&&network.mode==='fixed_servers'&&policy.security?.dns?.enabled!==false&&policy.security?.dns?.blockedIPs?.length)throw Error('broker-route-unsupported');
}
module.exports={createDnsPolicyRoute,assertSupportedDns};