227 lines
14 KiB
TypeScript
227 lines
14 KiB
TypeScript
import { describe, expect, it } from 'vitest';
|
|
import { buildPolicyEvaluation } from '../../server/gpo/gpo-policy-evaluation-service';
|
|
import { buildPolicyFindingRun } from '../../server/gpo/gpo-policy-finding-service';
|
|
import type { MicrosoftSecurityBaselineRecord } from '../../src/domain/microsoft-security-baseline';
|
|
import type { StoredMicrosoftSecurityBaselineAnalysis } from '../../src/domain/microsoft-security-baseline-analysis';
|
|
import type { GpoRegistryPolicyValue } from '../../src/domain/gpo-policy';
|
|
import type { GpoTechnicalPolicyIdentity } from '../../src/domain/gpo-policy-identity';
|
|
import type { EnvironmentProfile, StoredPolicySnapshotAggregation } from '../../src/domain/gpo-snapshot-aggregation';
|
|
import type { PolicyEvaluationRuleCatalog } from '../../src/domain/policy-evaluation-rules';
|
|
|
|
const timestamp = '2026-09-06T10:00:00.000Z';
|
|
const projectId = 'synthetic-policy-project';
|
|
const identity: GpoTechnicalPolicyIdentity = {
|
|
version: 1, id: 'gpo-policy-identity-' + '1'.repeat(32),
|
|
resolver: { id: 'gpo-technical-registry-target', version: '1.0.0' }, scope: 'computer',
|
|
target: { kind: 'registry-value', hive: 'HKEY_LOCAL_MACHINE', key: 'Software\\Policies\\Synthetic', valueName: 'Mode' },
|
|
admx: { status: 'not-found' }
|
|
};
|
|
const profile: EnvironmentProfile = {
|
|
version: 1, id: 'synthetic-profile', projectId, name: 'Synthetic', revision: 1,
|
|
contexts: [{ version: 1, id: 'all', kind: 'operating-system', productFamily: 'Group Policy',
|
|
productVersion: 'All imported baselines', role: 'Any', scope: 'mixed' }], createdAt: timestamp
|
|
};
|
|
const ruleCatalog: PolicyEvaluationRuleCatalog = {
|
|
version: 1, id: 'synthetic-empty-catalog',
|
|
catalog: { id: 'policy-evaluation-rule-catalog', version: '1.0.0' }, status: 'released', rules: []
|
|
};
|
|
|
|
function value(number: number): GpoRegistryPolicyValue {
|
|
return { kind: 'registry-policy', operation: 'set-value', key: 'Software\\Policies\\Synthetic',
|
|
valueName: 'Mode', typeCode: 4, data: { kind: 'uint32', value: number } };
|
|
}
|
|
|
|
function aggregation(actual?: number, options: { partial?: boolean; conflicted?: boolean } = {}): StoredPolicySnapshotAggregation {
|
|
const states = actual === undefined ? [] : [{
|
|
version: 1 as const, id: 'synthetic-state', aggregationRunId: 'synthetic-aggregation', identity,
|
|
deliveryMechanism: 'registry-policy' as const, status: options.conflicted ? 'conflicted' as const : 'configured' as const,
|
|
values: options.conflicted ? [value(actual), value(actual + 1)] : [value(actual)],
|
|
observationIds: ['synthetic-observation'], backupInstanceIds: ['synthetic-backup'], applicableContextIds: ['all'],
|
|
ordering: options.conflicted ? 'cross-gpo-conflict' as const : 'single-gpo' as const,
|
|
targeting: 'none' as const, completeness: 'complete' as const, effectiveState: 'unknown' as const, diagnosticCodes: []
|
|
}];
|
|
return {
|
|
version: 1,
|
|
run: { version: 1, id: 'synthetic-aggregation', projectId, environmentProfileId: profile.id,
|
|
sourceAnalysisRunIds: ['synthetic-analysis'], snapshotId: 'synthetic-snapshot',
|
|
aggregator: { id: 'gpo-policy-snapshot-aggregation', version: '1.1.0' },
|
|
status: options.partial ? 'partial' : 'complete', stateCount: states.length,
|
|
conflictCount: options.conflicted ? 1 : 0, unresolvedIdentityCount: 0, reviewHintCount: 0,
|
|
permitsFindingGeneration: false, diagnosticCodes: options.partial ? ['policy-aggregation-snapshot-incomplete'] : [],
|
|
startedAt: timestamp, completedAt: timestamp },
|
|
snapshot: { version: 1, id: 'synthetic-snapshot', projectId,
|
|
domain: { status: 'resolved', key: 'synthetic.example', evidence: 'backup-metadata' },
|
|
backupSetId: 'synthetic-set', instanceIds: ['synthetic-backup'], status: 'consistent',
|
|
completeness: options.partial ? 'selected-subset' : 'complete', permitsSetAbsenceConclusions: !options.partial,
|
|
diagnosticCodes: [], createdAt: timestamp },
|
|
states,
|
|
reviewHints: []
|
|
};
|
|
}
|
|
|
|
function baseline(name: string, recommended: number): {
|
|
record: MicrosoftSecurityBaselineRecord;
|
|
analysis: StoredMicrosoftSecurityBaselineAnalysis;
|
|
} {
|
|
const baselineId = `baseline-${name}`;
|
|
const analysisId = `baseline-analysis-${name}`;
|
|
const sha256 = name.padEnd(64, 'a').slice(0, 64).replace(/[^a-f0-9]/g, 'a');
|
|
return {
|
|
record: { version: 1, id: baselineId, fileName: `${name}.zip`, sha256, sizeBytes: 100,
|
|
storageKey: `baseline/${name}`, importedAt: timestamp, status: 'eligible',
|
|
metadata: { roles: ['Any'], scopes: ['mixed'], source: 'confirmed' } },
|
|
analysis: { version: 1, analysis: { version: 1, id: analysisId, baselineId, packageSha256: sha256,
|
|
analyzer: { id: 'microsoft-baseline-gpo-settings', version: '1.1.0' }, analyzedAt: timestamp,
|
|
status: 'complete', admxCatalog: { version: 1, id: `catalog-${name}`,
|
|
parser: { id: 'admx-policy-metadata-catalog', version: '1.0.0' },
|
|
source: { kind: 'microsoft-security-baseline', baselineId, packageSha256: sha256 },
|
|
status: 'complete', policies: [], languageResources: [], diagnosticCodes: [] },
|
|
backupCount: 1, registryPolicyFileCount: 1, settings: [{ version: 1, id: `setting-${name}`,
|
|
baselineId, backupId: `backup-${name}`, observation: { sourcePath: 'synthetic/Registry.pol',
|
|
sourceSha256: 'b'.repeat(64), scope: 'computer', deliveryMechanism: 'registry-policy',
|
|
parser: { id: 'registry-policy-preg', version: '1.0.0' },
|
|
reducer: { id: 'registry-policy-reducer', version: '1.0.0' }, state: 'instruction',
|
|
values: [value(recommended)], registryPolicy: { sequence: 0, operation: 'set-value' },
|
|
effectiveState: 'unknown', diagnosticCodes: [] },
|
|
identity: { version: 1, status: 'resolved', identity } }], diagnosticCodes: [] } }
|
|
};
|
|
}
|
|
|
|
function evaluate(actual: number | undefined, sources = [baseline('primary', 1)], options: { partial?: boolean; conflicted?: boolean } = {}) {
|
|
return buildPolicyEvaluation({ profile, aggregation: aggregation(actual, options),
|
|
baselineRecords: sources.map(source => source.record), baselineAnalyses: sources.map(source => source.analysis),
|
|
ruleCatalog, startedAt: timestamp, completedAt: timestamp });
|
|
}
|
|
|
|
describe('baseline-driven policy evaluation', () => {
|
|
function templates() {
|
|
const source = baseline('roles', 1);
|
|
const original = source.analysis.analysis.settings[0];
|
|
source.analysis.analysis.settings = ['Client', 'Member Server', 'Domain Controller'].map((role, index) => ({
|
|
...structuredClone(original), id: `role-setting-${index}`, backupId: `role-backup-${index}`,
|
|
observation: { ...structuredClone(original.observation), values: [value(index === 2 ? 1 : 0)] },
|
|
template: { names: [{ value: `Synthetic ${role}`, sourcePath: `Synthetic/${index}/Backup.xml`, sourceSha256: 'a'.repeat(64) }],
|
|
context: { productFamily: index === 0 ? 'Windows 10' : 'Windows Server', productVersion: index === 0 ? '1607' : '2016',
|
|
role: role as 'Client' | 'Member Server' | 'Domain Controller' } }
|
|
}));
|
|
return source;
|
|
}
|
|
function evaluateTemplates(source = templates(), target?: Partial<EnvironmentProfile['contexts'][number]>) {
|
|
const selectedProfile = target ? { ...profile, contexts: [{ ...profile.contexts[0], productFamily: 'Windows Server',
|
|
productVersion: '2016', role: 'Domain Controller', ...target }] } : profile;
|
|
return buildPolicyEvaluation({ profile: selectedProfile, aggregation: aggregation(1),
|
|
baselineRecords: [source.record], baselineAnalyses: [source.analysis], ruleCatalog,
|
|
startedAt: timestamp, completedAt: timestamp });
|
|
}
|
|
it('selects only the explicit comparison role and retains all candidate evidence', () => {
|
|
const source = templates();
|
|
const before = JSON.stringify(source);
|
|
const output = evaluateTemplates(source, {});
|
|
expect(output.results[0]).toMatchObject({ classification: 'configured_match', baselineSettingIds: ['role-setting-2'],
|
|
baselineTemplateSelection: { status: 'selected', candidateSettingIds: ['role-setting-0', 'role-setting-1', 'role-setting-2'] } });
|
|
expect(output.results[0].diagnosticCodes).not.toContain('policy-evaluation-baseline-consensus-conflict');
|
|
expect(output.evidence.filter(item => item.kind === 'baseline-setting')).toHaveLength(3);
|
|
expect(JSON.stringify(source)).toBe(before);
|
|
expect(evaluateTemplates(source, { role: 'Member Server' }).results[0].classification).toBe('confirmed_configured_deviation');
|
|
});
|
|
it('does not use observed inventory or a generic profile to choose the matching variant', () => {
|
|
for (const output of [evaluateTemplates(), evaluateTemplates(templates(), { sourceObservationIds: ['synthetic-os'] })]) {
|
|
expect(output.results[0].classification).toBe('review_required');
|
|
expect(output.results[0].diagnosticCodes).toContain('policy-evaluation-template-context-required');
|
|
expect(output.results[0].baselineSettingIds).toHaveLength(3);
|
|
}
|
|
});
|
|
it('keeps unanimous values comparable without a target-role decision', () => {
|
|
const source = templates();
|
|
source.analysis.analysis.settings.forEach(item => { item.observation.values = [value(1)]; });
|
|
expect(evaluateTemplates(source).results[0].classification).toBe('configured_match');
|
|
});
|
|
it('does not ignore unknown templates or conflicting values in the selected role', () => {
|
|
const unknown = templates();
|
|
unknown.analysis.analysis.settings[0].template = undefined;
|
|
expect(evaluateTemplates(unknown, {}).results[0].classification).toBe('review_required');
|
|
const conflict = templates();
|
|
const copy = structuredClone(conflict.analysis.analysis.settings[2]);
|
|
copy.id = 'role-setting-other'; copy.observation.values = [value(0)];
|
|
conflict.analysis.analysis.settings.push(copy);
|
|
expect(evaluateTemplates(conflict, {}).results[0].diagnosticCodes).toContain('policy-evaluation-baseline-consensus-conflict');
|
|
});
|
|
it('does not produce Pass or Fail for a mismatching OS version', () => {
|
|
expect(evaluateTemplates(templates(), { productVersion: '2025' }).results[0]).toMatchObject({
|
|
classification: 'review_required', baselineTemplateSelection: { status: 'no-match' }
|
|
});
|
|
});
|
|
it('reports exact values as Passed and different values as deviations', () => {
|
|
expect(evaluate(1)).toMatchObject({ run: { status: 'complete', matchCount: 1, deviationCount: 0 },
|
|
results: [{ customerState: 'configured', classification: 'configured_match',
|
|
comparator: { relation: 'match' }, findingEligibility: 'ineligible' }] });
|
|
expect(evaluate(2)).toMatchObject({ run: { status: 'complete', matchCount: 0, deviationCount: 1 },
|
|
results: [{ customerState: 'configured', classification: 'confirmed_configured_deviation',
|
|
comparator: { relation: 'different' }, findingEligibility: 'ineligible' }] });
|
|
});
|
|
|
|
it('creates a deviation when a recommended setting is missing from the customer backup', () => {
|
|
expect(evaluate(undefined)).toMatchObject({ run: { status: 'complete', deviationCount: 1 }, results: [{
|
|
aggregationStateId: undefined, customerState: 'missing', observationIds: [],
|
|
classification: 'confirmed_configured_deviation', comparator: { relation: 'different' }
|
|
}] });
|
|
});
|
|
|
|
it('does not emit actual-only settings without a baseline recommendation', () => {
|
|
const source = baseline('empty', 1);
|
|
source.analysis.analysis.settings = [];
|
|
expect(evaluate(2, [source])).toMatchObject({ run: { resultCount: 0, matchCount: 0, deviationCount: 0 }, results: [] });
|
|
});
|
|
|
|
it('does not emit PASS or FAIL for a structurally present baseline row without a Microsoft value', () => {
|
|
const source = baseline('blank', 1);
|
|
source.analysis.analysis.settings[0].observation.values = [];
|
|
expect(evaluate(undefined, [source])).toMatchObject({
|
|
run: { resultCount: 0, matchCount: 0, deviationCount: 0, reviewCount: 0 },
|
|
results: []
|
|
});
|
|
});
|
|
|
|
it('keeps an unsupported baseline value out of PASS and FAIL', () => {
|
|
const source = baseline('unsupported', 1);
|
|
source.analysis.analysis.settings[0].observation.values = [{
|
|
kind: 'service-preference', serviceName: 'Synthetic', serviceAction: 'start',
|
|
startupType: 'automatic', passwordAttributePresent: false
|
|
}];
|
|
expect(evaluate(undefined, [source])).toMatchObject({
|
|
run: { deviationCount: 0, matchCount: 0, reviewCount: 1 },
|
|
results: [{ classification: 'review_required', baselineState: 'unsupported-setting' }]
|
|
});
|
|
});
|
|
|
|
it('compares every baseline independently', () => {
|
|
const record = evaluate(1, [baseline('one', 1), baseline('two', 2)]);
|
|
expect(record.run).toMatchObject({ resultCount: 2, matchCount: 1, deviationCount: 1, reviewCount: 0 });
|
|
expect(new Set(record.results.map(result => result.baselineAnalysisId))).toEqual(
|
|
new Set(['baseline-analysis-one', 'baseline-analysis-two'])
|
|
);
|
|
});
|
|
|
|
it('keeps configured comparison usable for a selected-subset backup', () => {
|
|
expect(evaluate(2, undefined, { partial: true })).toMatchObject({
|
|
run: { status: 'complete', deviationCount: 1, reviewCount: 0 },
|
|
results: [{ classification: 'confirmed_configured_deviation' }]
|
|
});
|
|
});
|
|
|
|
it('keeps conflicting customer values in review', () => {
|
|
expect(evaluate(1, undefined, { conflicted: true })).toMatchObject({
|
|
run: { status: 'partial', reviewCount: 1 }, results: [{ customerState: 'conflicted',
|
|
classification: 'review_required', diagnosticCodes: expect.arrayContaining(['policy-evaluation-customer-conflict']) }]
|
|
});
|
|
});
|
|
|
|
it('does not manufacture security findings from neutral comparison rows', () => {
|
|
const evaluation = evaluate(2);
|
|
const findings = buildPolicyFindingRun({ evaluation, aggregation: aggregation(2), profile,
|
|
startedAt: timestamp, completedAt: timestamp });
|
|
expect(findings).toMatchObject({ run: { status: 'complete', eligibleResultCount: 0, findingCount: 0 },
|
|
definitions: [], occurrences: [], rejections: [] });
|
|
});
|
|
});
|