46 lines
2.7 KiB
TypeScript
46 lines
2.7 KiB
TypeScript
import type { MicrosoftSecurityBaselineSetting } from '../domain/microsoft-security-baseline-analysis';
|
|
import type { EnvironmentProfile } from '../domain/gpo-snapshot-aggregation';
|
|
|
|
type TemplateContext = NonNullable<MicrosoftSecurityBaselineSetting['template']>['context'];
|
|
|
|
/** Deliberately narrow Microsoft template naming grammar, not a fuzzy role guess. */
|
|
export function baselineTemplateContext(names: string[]): TemplateContext {
|
|
const unique = [...new Set(names)];
|
|
if (unique.length !== 1) return undefined;
|
|
const name = unique[0].trim();
|
|
const server = /^(?:MSFT )?Windows Server (2008 R2|2012 R2|2016|2019|2022|2025) - (Member Server|Domain Controller)$/i.exec(name);
|
|
if (server) return { productFamily: 'Windows Server', productVersion: server[1].toUpperCase(),
|
|
role: server[2].toLowerCase() === 'member server' ? 'Member Server' : 'Domain Controller' };
|
|
const client = /^(?:MSFT )?Windows (10|11) (RS1|1607|1703|1709|1803|1809|1903|1909|2004|20H2|21H1|21H2|22H2|23H2|24H2|25H2) - (?:Computer|User)$/i.exec(name);
|
|
if (client) return { productFamily: `Windows ${client[1]}`,
|
|
productVersion: client[2].toUpperCase() === 'RS1' ? '1607' : client[2].toUpperCase(), role: 'Client' };
|
|
return undefined;
|
|
}
|
|
|
|
export function selectBaselineTemplates(settings: MicrosoftSecurityBaselineSetting[], profile: EnvironmentProfile) {
|
|
const scope = settings[0].observation.scope;
|
|
const contexts = profile.contexts.filter(item => item.scope === 'mixed' || item.scope === scope);
|
|
// Inventory-derived contexts and scope-only aggregation do not prove GPO applicability.
|
|
if (contexts.length !== 1 || contexts[0].sourceObservationIds !== undefined) {
|
|
return { settings, contextIds: [] as string[], status: 'unresolved' as const };
|
|
}
|
|
const context = contexts[0];
|
|
const normalize = (value: string) => value.trim().toLowerCase();
|
|
if (!['client', 'member server', 'domain controller'].includes(normalize(context.role))) {
|
|
return { settings, contextIds: [] as string[], status: 'unresolved' as const };
|
|
}
|
|
// An unknown template could still apply: never drop it to manufacture a match.
|
|
if (settings.some(item => !item.template?.context)) {
|
|
return { settings, contextIds: [] as string[], status: 'unresolved' as const };
|
|
}
|
|
const selected = settings.filter(item => {
|
|
const target = item.template!.context!;
|
|
return normalize(target.productFamily) === normalize(context.productFamily)
|
|
&& normalize(target.productVersion) === normalize(context.productVersion)
|
|
&& normalize(target.role) === normalize(context.role);
|
|
});
|
|
return selected.length
|
|
? { settings: selected, contextIds: [context.id], status: 'selected' as const }
|
|
: { settings, contextIds: [context.id], status: 'no-match' as const };
|
|
}
|