first commit
This commit is contained in:
commit
ff482ebe23
251 files changed
+19126
No files matched your search
@@ -0,0 +1,61 @@
|
||||
import { DatabaseSync } from 'node:sqlite';
|
||||
import { mkdirSync } from 'node:fs';
|
||||
import { join } from 'node:path';
|
||||
import { randomBytes, createHash, generateKeyPairSync } from 'node:crypto';
|
||||
import { defaultPolicy, signPolicy, PolicySchema } from '../../packages/core/policy.mjs';
|
||||
import { recordSecurityEvent } from './telemetry.mjs';
|
||||
import { evaluateSigma } from '../../packages/core/security.mjs';
|
||||
import { signBookmarkSnapshot } from '../../packages/core/bookmarks.mjs';
|
||||
import {applyUserProfiles} from '../../packages/core/user-profiles.mjs';
|
||||
import {applyPolicyGroups} from '../../packages/core/policy-groups.mjs';
|
||||
export const token = () => randomBytes(32).toString('base64url');
|
||||
export const digest = value => createHash('sha256').update(value).digest('hex');
|
||||
export function openStore(directory) {
|
||||
mkdirSync(directory,{recursive:true,mode:0o700});
|
||||
const db = new DatabaseSync(join(directory,'safetynet.sqlite'));
|
||||
db.exec(`PRAGMA journal_mode=WAL; PRAGMA foreign_keys=ON;
|
||||
CREATE TABLE IF NOT EXISTS settings(key TEXT PRIMARY KEY,value TEXT NOT NULL);
|
||||
CREATE TABLE IF NOT EXISTS devices(id TEXT PRIMARY KEY,name TEXT,platform TEXT,user TEXT,token_hash TEXT UNIQUE,revoked INTEGER DEFAULT 0,last_seen TEXT,policy_version INTEGER DEFAULT 0);
|
||||
CREATE TABLE IF NOT EXISTS enrollments(hash TEXT PRIMARY KEY,expires INTEGER,user TEXT);
|
||||
CREATE TABLE IF NOT EXISTS events(id INTEGER PRIMARY KEY AUTOINCREMENT,time TEXT,device_id TEXT,kind TEXT,metadata TEXT);
|
||||
CREATE TABLE IF NOT EXISTS sessions(hash TEXT PRIMARY KEY,expires INTEGER,principal TEXT);
|
||||
CREATE TABLE IF NOT EXISTS management_session_handles(handle TEXT PRIMARY KEY,session_hash TEXT UNIQUE REFERENCES sessions(hash) ON DELETE CASCADE);
|
||||
CREATE TABLE IF NOT EXISTS logins(state TEXT PRIMARY KEY,expires INTEGER,verifier TEXT,nonce TEXT,ticket TEXT);
|
||||
CREATE TABLE IF NOT EXISTS tickets(id TEXT PRIMARY KEY,secret_hash TEXT,expires INTEGER,credential TEXT);
|
||||
CREATE TABLE IF NOT EXISTS policies(version INTEGER PRIMARY KEY,time TEXT,actor TEXT,policy TEXT);
|
||||
CREATE TABLE IF NOT EXISTS policy_group_history(version INTEGER PRIMARY KEY REFERENCES policies(version),groups TEXT NOT NULL);
|
||||
CREATE TABLE IF NOT EXISTS device_events(device_id TEXT,event_id TEXT,PRIMARY KEY(device_id,event_id));
|
||||
CREATE TABLE IF NOT EXISTS teaching(id TEXT PRIMARY KEY,time TEXT,actor TEXT,label TEXT,status TEXT,reference TEXT,reviewer TEXT,policy_version INTEGER);
|
||||
CREATE TABLE IF NOT EXISTS bookmarks(device_id TEXT PRIMARY KEY REFERENCES devices(id),revision INTEGER NOT NULL,items TEXT NOT NULL);
|
||||
CREATE TABLE IF NOT EXISTS alerts(id INTEGER PRIMARY KEY AUTOINCREMENT,event_id INTEGER,rule_id TEXT,level TEXT,time TEXT);
|
||||
`);
|
||||
if(!db.prepare('PRAGMA table_info(devices)').all().some(c=>c.name==='document_analysis_enabled'))db.exec('ALTER TABLE devices ADD COLUMN document_analysis_enabled INTEGER');
|
||||
if(!db.prepare('PRAGMA table_info(devices)').all().some(c=>c.name==='verified_subject'))db.exec('ALTER TABLE devices ADD COLUMN verified_subject TEXT');
|
||||
db.exec('CREATE TABLE IF NOT EXISTS user_profile_history(version INTEGER PRIMARY KEY REFERENCES policies(version),configuration TEXT NOT NULL)');
|
||||
const getRaw=key=>{const r=db.prepare('SELECT value FROM settings WHERE key=?').get(key);return r?JSON.parse(r.value):null;};
|
||||
const get = key => {const value=getRaw(key);return key==='policy'&&value?PolicySchema.parse(value):value;};
|
||||
const put = (key,value) => db.prepare('INSERT INTO settings VALUES(?,?) ON CONFLICT(key) DO UPDATE SET value=excluded.value').run(key,JSON.stringify(value));
|
||||
if(!get('keys')) {const k=generateKeyPairSync('ed25519'); put('keys',{privateKey:k.privateKey.export({type:'pkcs8',format:'pem'}),publicKey:k.publicKey.export({type:'spki',format:'pem'})});}
|
||||
if(!getRaw('policy')) {const p=defaultPolicy();put('policy',p);db.prepare('INSERT INTO policies VALUES(?,?,?,?)').run(p.version,new Date().toISOString(),'bootstrap',JSON.stringify(p));}
|
||||
db.prepare('INSERT OR IGNORE INTO policy_group_history(version,groups) VALUES(?,?)').run(getRaw('policy').version,JSON.stringify(getRaw('policyGroups')||[]));
|
||||
db.prepare('INSERT OR IGNORE INTO user_profile_history(version,configuration) VALUES(?,?)').run(getRaw('policy').version,JSON.stringify(getRaw('userProfiles')||{profiles:[],categories:[]}));
|
||||
if(!get('installationId'))put('installationId',token());
|
||||
function event(device,kind,metadata) {
|
||||
const time=new Date().toISOString(),records=[];db.exec('SAVEPOINT security_event');
|
||||
try {
|
||||
const insert=(eventKind,eventMetadata)=>{const result=db.prepare('INSERT INTO events(time,device_id,kind,metadata) VALUES(?,?,?,?)').run(time,device,eventKind,JSON.stringify(eventMetadata));records.push({kind:eventKind,metadata:eventMetadata,id:String(result.lastInsertRowid)});return Number(result.lastInsertRowid);};
|
||||
const sourceEventId=insert(kind,metadata);
|
||||
for(const alert of evaluateSigma({kind,metadata},getRaw('policy')?.security?.sigma?.rules||[])) {
|
||||
db.prepare('INSERT INTO alerts(event_id,rule_id,level,time) VALUES(?,?,?,?)').run(sourceEventId,alert.ruleId,alert.level,time);
|
||||
insert('sigma.alert',{ruleId:alert.ruleId,level:alert.level,sourceEventId});
|
||||
}
|
||||
db.exec('RELEASE security_event');
|
||||
}catch(error){db.exec('ROLLBACK TO security_event');db.exec('RELEASE security_event');throw error;}
|
||||
for(const record of records)recordSecurityEvent(record.kind,record.metadata,record.id);
|
||||
}
|
||||
function personalBookmarks(deviceId){const row=db.prepare('SELECT revision,items FROM bookmarks WHERE device_id=?').get(deviceId);return row?{revision:row.revision,items:JSON.parse(row.items)}:{revision:0,items:[]};}
|
||||
function effectivePolicy(deviceId){let p=get('policy');if(deviceId){const d=db.prepare('SELECT document_analysis_enabled FROM devices WHERE id=?').get(deviceId);if(d?.document_analysis_enabled!==null&&d?.document_analysis_enabled!==undefined)p.inspection.documentAnalysisEnabled=!!d.document_analysis_enabled;p=PolicySchema.parse(applyPolicyGroups(applyUserProfiles(p,getRaw('userProfiles')||{},db.prepare('SELECT * FROM devices WHERE id=?').get(deviceId)||{id:deviceId}),getRaw('policyGroups')||[],deviceId));}return p;}
|
||||
return {db,get,getRaw,put,event,effectivePolicy,personalBookmarks,envelope:deviceId=>{const p=effectivePolicy(deviceId),keys=get('keys'),envelope=signPolicy(p,keys.privateKey);if(deviceId)envelope.bookmarks=signBookmarkSnapshot({deviceId,policyVersion:p.version,...personalBookmarks(deviceId),expiresAt:p.expiresAt},keys.privateKey);return envelope;},publicKey:()=>get('keys').publicKey};
|
||||
}
|
||||
|
||||
|
||||
Reference in new issue
Block a user