Assessor_2.0/server/gpo/gpo-policy-evaluation-rule-...

140 lines
6.4 KiB
TypeScript

import { canonicalJson } from '../../src/shared/canonical-json';
import type { AggregatedPolicyState, EnvironmentProfile, StoredPolicySnapshotAggregation } from '../../src/domain/gpo-snapshot-aggregation';
import type { PolicyBaselineState, PolicyComparatorDefinition } from '../../src/domain/policy-evaluation';
import {
policyEvaluationRulesForContext,
type PolicyDependencyRule,
type PolicyEvaluationRule,
type PolicyEvaluationRuleCatalog
} from '../../src/domain/policy-evaluation-rules';
export interface PolicyRuleResolution {
baselineState: PolicyBaselineState;
comparator: PolicyComparatorDefinition;
source: 'baseline-setting' | 'rule-catalog';
ruleIds: string[];
dependencyRuleIds: string[];
riskRuleIds: string[];
notApplicable: boolean;
diagnosticCodes: string[];
}
function sortedUnique(values: string[]): string[] {
return [...new Set(values)].sort((left, right) => left.localeCompare(right));
}
function dependencyTarget(
aggregation: StoredPolicySnapshotAggregation,
dependency: PolicyDependencyRule
): AggregatedPolicyState | undefined {
return aggregation.states.find(state => state.identity.id === dependency.identityId
&& state.deliveryMechanism === dependency.deliveryMechanism);
}
function targetIsConfigured(target: AggregatedPolicyState): boolean {
return target.status === 'configured' && target.completeness === 'complete' && target.targeting === 'none';
}
function dependencyResolution(
aggregation: StoredPolicySnapshotAggregation,
dependencies: PolicyDependencyRule[]
): { notApplicable: boolean; diagnosticCodes: string[] } {
const diagnostics = new Set<string>();
let notApplicable = false;
for (const dependency of dependencies) {
const target = dependencyTarget(aggregation, dependency);
if (!target) {
if (!aggregation.snapshot.permitsSetAbsenceConclusions) {
diagnostics.add('policy-dependency-target-absence-unknown');
} else if (dependency.kind === 'prerequisite') {
notApplicable = true;
}
continue;
}
if (target.status === 'conflicted' || target.status === 'indeterminate'
|| target.completeness !== 'complete' || target.targeting !== 'none') {
diagnostics.add('policy-dependency-target-indeterminate');
continue;
}
const configured = targetIsConfigured(target);
if (dependency.kind === 'prerequisite' && !configured) notApplicable = true;
if (dependency.kind === 'override' && configured) notApplicable = true;
if (dependency.kind === 'mutual-exclusion' && configured) {
diagnostics.add('policy-dependency-mutual-exclusion-active');
}
}
return { notApplicable, diagnosticCodes: [...diagnostics].sort() };
}
export function resolvePolicyEvaluationRule(
catalog: PolicyEvaluationRuleCatalog,
profile: EnvironmentProfile,
aggregation: StoredPolicySnapshotAggregation,
state: AggregatedPolicyState
): PolicyRuleResolution {
const diagnostics = new Set<string>();
const selected: PolicyEvaluationRule[] = [];
let missingContextRules = 0;
for (const contextId of state.applicableContextIds) {
const context = profile.contexts.find(candidate => candidate.id === contextId);
if (!context) {
diagnostics.add('policy-evaluation-context-missing');
continue;
}
const candidates = policyEvaluationRulesForContext(
catalog, state.identity.id, state.deliveryMechanism, context, state.identity.scope
);
if (candidates.length !== 1) {
if (candidates.length) diagnostics.add('policy-evaluation-rule-ambiguous');
else missingContextRules += 1;
continue;
}
selected.push(candidates[0]);
}
const rules = [...new Map(selected.map(rule => [rule.id, rule])).values()]
.sort((left, right) => left.id.localeCompare(right.id));
const useBaselineSetting = rules.length === 0
&& missingContextRules === state.applicableContextIds.length
&& !diagnostics.size;
if (!useBaselineSetting && missingContextRules > 0) {
diagnostics.add('policy-evaluation-rule-catalog-gap');
}
const dispositions = new Set(rules.map(rule => rule.baselineDisposition));
if (dispositions.size > 1) diagnostics.add('policy-evaluation-rule-context-conflict');
let baselineState: PolicyBaselineState = dispositions.size === 1
? rules[0].baselineDisposition
: useBaselineSetting ? 'explicit-recommendation' : 'catalog-gap';
const recommendationRules = rules.filter(rule => rule.baselineDisposition === 'explicit-recommendation');
const comparatorSignatures = new Set(recommendationRules.map(rule => canonicalJson(rule.comparator)));
if (comparatorSignatures.size > 1) diagnostics.add('policy-evaluation-comparator-consensus-conflict');
const riskSignatures = new Set(recommendationRules.map(rule => canonicalJson(rule.riskRule)));
if (riskSignatures.size > 1) diagnostics.add('policy-evaluation-risk-consensus-conflict');
if (baselineState === 'no-recommendation') diagnostics.add('policy-evaluation-no-recommendation');
const dependencies = [...new Map(rules.flatMap(rule => rule.dependencies).map(rule => [rule.id, rule])).values()]
.sort((left, right) => left.id.localeCompare(right.id));
const conflictingDependencyIds = new Set<string>();
for (const rule of rules) {
for (const dependency of rule.dependencies) {
const sameId = dependencies.find(candidate => candidate.id === dependency.id);
if (sameId && canonicalJson(sameId) !== canonicalJson(dependency)) conflictingDependencyIds.add(dependency.id);
}
}
if (conflictingDependencyIds.size) diagnostics.add('policy-evaluation-dependency-definition-conflict');
const dependency = dependencyResolution(aggregation, dependencies);
dependency.diagnosticCodes.forEach(code => diagnostics.add(code));
let notApplicable = baselineState === 'explicitly-not-applicable' || dependency.notApplicable;
if (dependency.notApplicable) baselineState = 'explicitly-not-applicable';
if (diagnostics.size) notApplicable = false;
const comparator = recommendationRules[0]?.comparator ?? { kind: 'equality' as const };
return {
baselineState,
comparator: useBaselineSetting ? { kind: 'equality' } : comparator,
source: useBaselineSetting ? 'baseline-setting' : 'rule-catalog',
ruleIds: rules.map(rule => rule.id),
dependencyRuleIds: dependencies.map(rule => rule.id),
riskRuleIds: sortedUnique(recommendationRules.flatMap(rule => rule.riskRule ? [rule.riskRule.id] : [])),
notApplicable,
diagnosticCodes: [...diagnostics].sort()
};
}