147 lines
8.4 KiB
TypeScript
147 lines
8.4 KiB
TypeScript
import { randomUUID } from 'node:crypto';
|
|
import { 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 } 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 { 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 { createRegulationMapping } from './regmap/regulation-mapping-factory';
|
|
|
|
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 runtime = await createRepositoryRuntime();
|
|
const artifactStore = new FileSystemBackendArtifactStore(artifactRoot);
|
|
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);
|
|
const gpoPolicyFindingService = new GpoPolicyFindingService(runtime.repository);
|
|
const gpoPolicyAnalysisOverviewService = new GpoPolicyAnalysisOverviewService(runtime.repository);
|
|
const regulationMapping = createRegulationMapping();
|
|
// Best-effort startup validation so /api/v1/health reports a meaningful capability.
|
|
await regulationMapping.refreshCapability().catch(() => undefined);
|
|
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, {
|
|
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({
|
|
repository: runtime.repository,
|
|
persistence: runtime.persistence,
|
|
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,
|
|
regulationMapping,
|
|
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()]).then(() => {
|
|
server.close(async error => {
|
|
try {
|
|
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;
|
|
});
|