Assessor_2.0/server/index.ts

204 lines
12 KiB
TypeScript

import { randomUUID } from 'node:crypto';
import { join, resolve } from 'node:path';
import { defaultImportResourcePolicy } from '../src/importers/resource-policy';
import { createAssessorHttpServer } from './assessor-http-server';
import { NodeImportWorkerExecutor } from './import-worker-client';
import { createRepositoryRuntime, type RepositoryEnvironment } from './repository-factory';
import { assertArtifactStoreOutsidePublicRoot, FileSystemBackendArtifactStore } from './artifacts/backend-artifact-store';
import { GpoBackupSubmissionService } from './gpo/gpo-backup-submission-service';
import { GpoBackupJobProcessor, GpoBackupJobRunner } from './gpo/gpo-backup-job-processor';
import { GpoArtifactReconciler, GpoArtifactReconciliationRunner } from './gpo/gpo-artifact-reconciler';
import { GpoBackupManagementService } from './gpo/gpo-backup-management-service';
import { NodeGpoInventoryWorkerExecutor } from './gpo/gpo-inventory-worker-client';
import { GpoAutomaticAnalysisService } from './gpo/gpo-automatic-analysis-service';
import { MicrosoftSecurityBaselineService } from './gpo/microsoft-security-baseline-service';
import { GpoPolicySnapshotService } from './gpo/gpo-policy-snapshot-service';
import { GpoPolicyEvaluationService } from './gpo/gpo-policy-evaluation-service';
import { GpoPolicyFindingService } from './gpo/gpo-policy-finding-service';
import { GpoPolicyAnalysisOverviewService } from './gpo/gpo-policy-analysis-overview-service';
import { loadPolicyEvaluationRuleCatalog } from './gpo/policy-evaluation-rule-catalog-loader';
import { MitreCatalogService } from './reference-data/mitre-catalog-service';
import { FileSystemReportArchive, resolveReportArchiveRoot, type ReportArchiveEnvironment } from './reporting/report-archive';
import { createPostgresReportReadRuntime, memoryReportReadRuntime, type ReportReadRuntime } from './reporting/report-read-runtime';
import { createRemediationRuntime } from './remediation/remediation-runtime';
import { createLiteLlmClientFromEnvironment } from './ai/litellm-client';
import { createComplianceEngineRuntime } from './compliance/compliance-engine-runtime';
function boundedInteger(value: string | undefined, fallback: number, minimum: number, maximum: number, name: string): number {
if (value === undefined || value.trim() === '') return fallback;
const parsed = Number(value);
if (!Number.isInteger(parsed) || parsed < minimum || parsed > maximum) {
throw new Error(`${name} muss eine Ganzzahl zwischen ${minimum} und ${maximum} sein.`);
}
return parsed;
}
async function main(): Promise<void> {
const host = process.env.ASSESSOR_HOST?.trim() || '127.0.0.1';
const parsedPort = Number(process.env.ASSESSOR_PORT ?? 4173);
if (!Number.isInteger(parsedPort) || parsedPort < 1 || parsedPort > 65535) {
throw new Error('ASSESSOR_PORT muss eine Ganzzahl zwischen 1 und 65535 sein.');
}
const importExecutor = new NodeImportWorkerExecutor({
workerUrl: new URL('./import-worker.mjs', import.meta.url),
timeoutMs: boundedInteger(process.env.ASSESSOR_PARSER_TIMEOUT_MS, 30_000, 1_000, 300_000, 'ASSESSOR_PARSER_TIMEOUT_MS'),
maximumConcurrent: boundedInteger(process.env.ASSESSOR_PARSER_MAX_CONCURRENT, 2, 1, 8, 'ASSESSOR_PARSER_MAX_CONCURRENT'),
maximumQueued: boundedInteger(process.env.ASSESSOR_PARSER_MAX_QUEUE, 8, 0, 100, 'ASSESSOR_PARSER_MAX_QUEUE'),
maximumQueuedBytes: boundedInteger(process.env.ASSESSOR_PARSER_MAX_QUEUE_MB, 256, 16, 2_048, 'ASSESSOR_PARSER_MAX_QUEUE_MB') * 1024 * 1024,
maximumOldGenerationMb: boundedInteger(process.env.ASSESSOR_PARSER_MEMORY_MB, 512, 128, 2_048, 'ASSESSOR_PARSER_MEMORY_MB')
});
const staticRoot = resolve(process.cwd(), 'dist');
const artifactRoot = resolve(process.env.ASSESSOR_ARTIFACT_ROOT?.trim() || resolve(process.cwd(), '.assessor-data', 'artifacts'));
assertArtifactStoreOutsidePublicRoot(artifactRoot, staticRoot);
const gpoInventoryExecutor = new NodeGpoInventoryWorkerExecutor({
workerUrl: new URL('./gpo-inventory-worker.mjs', import.meta.url),
timeoutMs: boundedInteger(process.env.ASSESSOR_GPO_TIMEOUT_MS, 30_000, 1_000, 300_000, 'ASSESSOR_GPO_TIMEOUT_MS'),
maximumConcurrent: boundedInteger(process.env.ASSESSOR_GPO_MAX_CONCURRENT, 1, 1, 4, 'ASSESSOR_GPO_MAX_CONCURRENT'),
maximumSourceBytes: defaultImportResourcePolicy.maximumFileBytes,
maximumOldGenerationMb: boundedInteger(process.env.ASSESSOR_GPO_MEMORY_MB, 1_024, 256, 2_048, 'ASSESSOR_GPO_MEMORY_MB'),
maximumYoungGenerationMb: boundedInteger(process.env.ASSESSOR_GPO_YOUNG_MEMORY_MB, 128, 64, 512, 'ASSESSOR_GPO_YOUNG_MEMORY_MB')
});
const policyEvaluationRuleCatalog = await loadPolicyEvaluationRuleCatalog({
path: process.env.ASSESSOR_POLICY_RULE_CATALOG_PATH,
expectedSha256: process.env.ASSESSOR_POLICY_RULE_CATALOG_SHA256
});
const runtime = await createRepositoryRuntime();
let remediationRuntime: Awaited<ReturnType<typeof createRemediationRuntime>> | undefined;
try { remediationRuntime = await createRemediationRuntime(process.env as RepositoryEnvironment, runtime.repository); }
catch (error) { console.warn('Remediation Engine not available:', error instanceof Error ? error.message : 'Unknown error'); }
// The Report Engine runs beside ASSESSOR with its own read-only database reader and archive.
// If it cannot start, ASSESSOR keeps running and only report runs are refused.
let reportRuntime: ReportReadRuntime | undefined;
let reportArchive: FileSystemReportArchive | undefined;
try {
reportArchive = new FileSystemReportArchive(resolveReportArchiveRoot(process.env as ReportArchiveEnvironment, [process.cwd(), staticRoot]));
reportRuntime = runtime.persistence === 'postgresql'
? await createPostgresReportReadRuntime(process.env as RepositoryEnvironment)
: memoryReportReadRuntime(runtime.repository);
} catch (error) {
reportArchive = undefined;
console.warn('Report Engine nicht verfügbar:', error instanceof Error ? error.message : 'Unbekannter Fehler');
}
const aiClient = createLiteLlmClientFromEnvironment();
// The Compliance Engine runs beside ASSESSOR with its own store and reads the Core only through its read-only port.
const complianceEngine = await createComplianceEngineRuntime({
repository: runtime.repository,
persistence: runtime.persistence,
aiClient,
artifactRoot,
aiOverview: {
maximumChunkCharacters: boundedInteger(process.env.ASSESSOR_AI_CHUNK_CHARS, 80_000, 10_000, 500_000, 'ASSESSOR_AI_CHUNK_CHARS'),
maximumCalls: boundedInteger(process.env.ASSESSOR_AI_MAX_CALLS, 64, 1, 64, 'ASSESSOR_AI_MAX_CALLS'),
leaseDurationMs: boundedInteger(process.env.ASSESSOR_AI_JOB_LEASE_MS, 5 * 60_000, 10_000, 30 * 60_000, 'ASSESSOR_AI_JOB_LEASE_MS')
}
});
const artifactStore = new FileSystemBackendArtifactStore(artifactRoot);
const mitreCatalogService = new MitreCatalogService(join(artifactRoot, 'reference-data', 'mitre-enterprise.json'));
const gpoBackupSubmissionService = new GpoBackupSubmissionService(
runtime.repository,
artifactStore,
gpoInventoryExecutor,
defaultImportResourcePolicy.maximumFileBytes
);
const gpoBackupManagementService = new GpoBackupManagementService(runtime.repository, artifactStore);
const microsoftSecurityBaselineService = new MicrosoftSecurityBaselineService(
runtime.repository, artifactStore, defaultImportResourcePolicy.maximumFileBytes, gpoInventoryExecutor
);
const gpoPolicySnapshotService = new GpoPolicySnapshotService(runtime.repository);
const gpoPolicyEvaluationService = new GpoPolicyEvaluationService(runtime.repository, {
ruleCatalog: policyEvaluationRuleCatalog
});
const gpoPolicyFindingService = new GpoPolicyFindingService(runtime.repository);
const gpoPolicyAnalysisOverviewService = new GpoPolicyAnalysisOverviewService(runtime.repository);
const automaticGpoAnalysis = new GpoAutomaticAnalysisService(runtime.repository,
gpoPolicySnapshotService, gpoPolicyEvaluationService, gpoPolicyFindingService);
await microsoftSecurityBaselineService.refreshDetectedMetadata();
const baselineBackfill = await microsoftSecurityBaselineService.reprocessPending();
if (baselineBackfill.failed) console.warn('Microsoft-Baseline-Backfill:', baselineBackfill.analyzed,
'analysiert,', baselineBackfill.failed, 'fehlgeschlagen.');
const gpoJobProcessor = new GpoBackupJobProcessor(runtime.repository, artifactStore, gpoInventoryExecutor, {
afterAnalysis: (projectId, runId) => automaticGpoAnalysis.run(projectId, runId),
maximumArtifactBytes: defaultImportResourcePolicy.maximumFileBytes,
leaseDurationMs: boundedInteger(process.env.ASSESSOR_GPO_JOB_LEASE_MS, 60_000, 1_000, 3_600_000, 'ASSESSOR_GPO_JOB_LEASE_MS'),
retryDelayMs: boundedInteger(process.env.ASSESSOR_GPO_JOB_RETRY_MS, 5_000, 0, 300_000, 'ASSESSOR_GPO_JOB_RETRY_MS')
});
const gpoJobRunner = new GpoBackupJobRunner(
gpoJobProcessor,
`gpo-worker-${randomUUID()}`,
boundedInteger(process.env.ASSESSOR_GPO_JOB_POLL_MS, 1_000, 100, 60_000, 'ASSESSOR_GPO_JOB_POLL_MS')
);
const gpoArtifactReconciliationRunner = new GpoArtifactReconciliationRunner(
new GpoArtifactReconciler(runtime.repository, artifactStore, {
gracePeriodMs: boundedInteger(process.env.ASSESSOR_ARTIFACT_ORPHAN_GRACE_MS, 24 * 60 * 60 * 1_000, 60_000, 7 * 24 * 60 * 60 * 1_000, 'ASSESSOR_ARTIFACT_ORPHAN_GRACE_MS'),
maximumRemovals: boundedInteger(process.env.ASSESSOR_ARTIFACT_ORPHAN_MAX_REMOVALS, 100, 1, 10_000, 'ASSESSOR_ARTIFACT_ORPHAN_MAX_REMOVALS')
}),
boundedInteger(process.env.ASSESSOR_ARTIFACT_RECONCILE_INTERVAL_MS, 60 * 60 * 1_000, 60_000, 24 * 60 * 60 * 1_000, 'ASSESSOR_ARTIFACT_RECONCILE_INTERVAL_MS'),
error => console.error('GPO-Artefakt-Reconciliation fehlgeschlagen.', error instanceof Error ? error.message : 'Unbekannter Fehler')
);
const server = createAssessorHttpServer({
artifactStore,
mitreCatalogService,
repository: runtime.repository,
persistence: runtime.persistence,
reportPort: reportRuntime?.port ?? null,
reportArchive,
remediationStore: remediationRuntime?.store ?? null,
remediationPort: remediationRuntime?.port ?? null,
staticRoot,
releaseId: process.env.ASSESSOR_RELEASE_ID?.trim() || undefined,
developmentInstanceId: process.env.ASSESSOR_DEVELOPMENT_INSTANCE_ID?.trim() || undefined,
gpoBackupSubmissionService,
gpoBackupManagementService,
microsoftSecurityBaselineService,
gpoPolicySnapshotService,
gpoPolicyEvaluationService,
gpoPolicyFindingService,
gpoPolicyAnalysisOverviewService,
aiOverviewService: complianceEngine.aiOverviewService,
regulatoryMappingService: complianceEngine.mappingService,
regulatoryCorpusUploadService: complianceEngine.corpusUploadService,
complianceExport: complianceEngine.store,
isoUploadToken: process.env.ASSESSOR_ISO_UPLOAD_TOKEN?.trim(),
automaticGpoAnalysis,
importExecutor
});
server.listen(parsedPort, host, () => {
gpoArtifactReconciliationRunner.start();
gpoJobRunner.start();
console.log(`ASSESSOR läuft lokal unter http://${host}:${parsedPort}`);
console.log(`Persistenz: ${runtime.persistence === 'postgresql' ? 'PostgreSQL' : 'Arbeitsspeicher'}`);
});
let shuttingDown = false;
const shutdown = (signal: string) => {
if (shuttingDown) return;
shuttingDown = true;
console.log(`${signal}: ASSESSOR wird beendet.`);
void Promise.all([gpoJobRunner.stop(), gpoArtifactReconciliationRunner.stop(), complianceEngine.stop()]).then(() => {
server.close(async error => {
try {
await reportRuntime?.close();
await remediationRuntime?.close();
await complianceEngine.close();
await runtime.close();
} catch (closeError) {
console.error(closeError);
process.exitCode = 1;
}
if (error) {
console.error(error);
process.exitCode = 1;
}
});
});
};
process.on('SIGINT', () => shutdown('SIGINT'));
process.on('SIGTERM', () => shutdown('SIGTERM'));
}
void main().catch(error => {
console.error('ASSESSOR konnte nicht gestartet werden.');
console.error(error instanceof Error ? error.message : error);
process.exitCode = 1;
});