31 lines
1.8 KiB
TypeScript
31 lines
1.8 KiB
TypeScript
import { createHash } from 'node:crypto';
|
|
import type { EnvironmentObservation } from '../../src/domain/environment-context';
|
|
import type { EnvironmentProfileContext } from '../../src/domain/gpo-snapshot-aggregation';
|
|
|
|
/** Observed platform inventory is evidence for candidate contexts, not GPO applicability. */
|
|
export function inferGpoEnvironmentContexts(observations: EnvironmentObservation[]): Array<Omit<EnvironmentProfileContext, 'version'>> {
|
|
const contexts = new Map<string, Omit<EnvironmentProfileContext, 'version'>>();
|
|
for (const observation of observations) {
|
|
if (observation.state !== 'observed' || observation.property !== 'operating_system.name') continue;
|
|
const value = observation.normalizedValue ?? observation.originalValue;
|
|
if (typeof value !== 'string') continue;
|
|
const isDc = observation.subject.type === 'computer-information.domain-controllers';
|
|
const server = /^Windows(?: Server)? (2008(?: R2)?|2012(?: R2)?|2016|2019|2022|2025)(?:\b|$)/i.exec(value.trim());
|
|
// A generic server inventory row does not prove member-server role.
|
|
if (!isDc || !server) continue;
|
|
const productVersion = server[1].toUpperCase();
|
|
const key = `Windows Server:${productVersion}:Domain Controller`;
|
|
const existing = contexts.get(key);
|
|
if (existing) {
|
|
existing.sourceObservationIds = [...new Set([...(existing.sourceObservationIds ?? []), observation.id])].sort();
|
|
continue;
|
|
}
|
|
contexts.set(key, {
|
|
id: 'detected-context-' + createHash('sha256').update(key).digest('hex').slice(0, 24),
|
|
kind: 'operating-system', productFamily: 'Windows Server', productVersion,
|
|
role: 'Domain Controller', scope: 'computer', sourceObservationIds: [observation.id]
|
|
});
|
|
}
|
|
return [...contexts.values()].sort((a, b) => a.id.localeCompare(b.id));
|
|
}
|