import { appendFileSync,readFileSync } from 'node:fs'; import { join } from 'node:path'; import { request } from 'node:https'; import { toECS,toCEF } from '../../packages/core/siem.mjs'; import { digest } from './store.mjs'; export function startSIEM(store,directory,env=process.env) { let stopped=false,running=false,failures=0,nextAttempt=0; const url=env.SIEM_HTTP_URL?new URL(env.SIEM_HTTP_URL):null; if(url&&(url.protocol!=='https:'||url.username||url.password))throw Error('SIEM push requires an HTTPS URL without embedded credentials'); const tlsOptions={...(env.SIEM_CA_FILE?{ca:readFileSync(env.SIEM_CA_FILE)}:{}),...(env.SIEM_CERT_FILE?{cert:readFileSync(env.SIEM_CERT_FILE)}:{}),...(env.SIEM_KEY_FILE?{key:readFileSync(env.SIEM_KEY_FILE)}:{})}; const format=env.SIEM_FORMAT||'ecs';if(!['ecs','cef'].includes(format))throw Error('SIEM_FORMAT must be ecs or cef'); const cursorKey=url?`siem.remote.${digest(url.href+':'+format)}`:`siem.file.${format}`; async function tick(){ if(stopped||running||Date.now()? ORDER BY id LIMIT 100').all(after).map(e=>({...e,metadata:JSON.parse(e.metadata)})); if(!rows.length)return; const body=rows.map(e=>format==='ecs'?JSON.stringify(toECS(e,store.get('installationId'))):toCEF(e,store.get('installationId'))).join('\n')+'\n'; if(url)await push(url,body,format,env.SIEM_HTTP_TOKEN,tlsOptions); else appendFileSync(join(directory,`security.${format==='ecs'?'ndjson':'cef'}`),body,{mode:0o600,flush:true}); if(!stopped)store.put(cursorKey,rows.at(-1).id);failures=0;nextAttempt=0; }catch{failures++;nextAttempt=Date.now()+Math.min(300000,1000*2**Math.min(failures,8));} finally{running=false;} } const timer=setInterval(tick,2000);timer.unref();tick(); return {stop:()=>{stopped=true;clearInterval(timer);},flush:tick,status:()=>({configured:!!url,format,failures,cursor:store.get(cursorKey)||0,pending:store.db.prepare('SELECT count(*) AS n FROM events WHERE id>?').get(store.get(cursorKey)||0).n})}; } function push(url,body,format,credential,tlsOptions) { return new Promise((resolve,reject)=>{ const req=request(url,{method:'POST',...tlsOptions,headers:{'content-type':format==='ecs'?'application/x-ndjson':'text/plain','content-length':Buffer.byteLength(body),...(credential?{authorization:`Bearer ${credential}`}:{})}},res=>{ res.resume();res.on('error',reject);res.on('end',()=>res.statusCode>=200&&res.statusCode<300?resolve():reject(Error('SIEM rejected batch'))); });req.setTimeout(10000,()=>req.destroy(Error('SIEM timeout')));req.on('error',reject);req.end(body); }); }