169 lines
6.9 KiB
TypeScript
169 lines
6.9 KiB
TypeScript
import type { GpoPolicyScope } from './gpo-ingestion';
|
|
|
|
export const GPO_POLICY_IDENTITY_RESOLVER = {
|
|
id: 'gpo-technical-registry-target',
|
|
version: '1.0.0'
|
|
} as const;
|
|
|
|
export const GPO_NATIVE_POLICY_IDENTITY_RESOLVER = {
|
|
id: 'gpo-native-policy-target', version: '1.0.0'
|
|
} as const;
|
|
|
|
export const ADMX_POLICY_CATALOG_PARSER = {
|
|
id: 'admx-policy-metadata-catalog',
|
|
version: '1.0.0'
|
|
} as const;
|
|
|
|
export type ResolvableGpoPolicyScope = Exclude<GpoPolicyScope, 'shared' | 'unknown'>;
|
|
|
|
export interface GpoRegistryTechnicalTarget {
|
|
kind: 'registry-key' | 'registry-value';
|
|
hive: 'HKEY_LOCAL_MACHINE' | 'HKEY_CURRENT_USER';
|
|
key: string;
|
|
valueName?: string;
|
|
}
|
|
|
|
export interface AdmxPolicyRegistryTarget {
|
|
version: 1;
|
|
scope: ResolvableGpoPolicyScope;
|
|
key: string;
|
|
match: 'exact-value' | 'key-values';
|
|
valueName?: string;
|
|
role: 'policy-state' | 'element' | 'enabled-list' | 'disabled-list';
|
|
elementId?: string;
|
|
}
|
|
|
|
export interface AdmxPolicyDefinition {
|
|
version: 1;
|
|
technicalId: string;
|
|
targetNamespace: string;
|
|
name: string;
|
|
class: 'machine' | 'user' | 'both';
|
|
displayNameResourceId?: string;
|
|
explainTextResourceId?: string;
|
|
sourcePath: string;
|
|
sourceSha256: string;
|
|
registryTargets: AdmxPolicyRegistryTarget[];
|
|
}
|
|
|
|
export interface AdmxLanguageResource {
|
|
version: 1;
|
|
sourcePath: string;
|
|
sourceSha256: string;
|
|
admxFileName: string;
|
|
locale: string;
|
|
strings: Record<string, string>;
|
|
}
|
|
|
|
export interface AdmxPolicyCatalog {
|
|
version: 1;
|
|
id: string;
|
|
parser: typeof ADMX_POLICY_CATALOG_PARSER;
|
|
source: { kind: 'microsoft-security-baseline'; baselineId: string; packageSha256: string };
|
|
status: 'complete' | 'partial';
|
|
policies: AdmxPolicyDefinition[];
|
|
languageResources: AdmxLanguageResource[];
|
|
diagnosticCodes: string[];
|
|
}
|
|
|
|
export interface AdmxPolicyReference {
|
|
technicalId: string;
|
|
targetNamespace: string;
|
|
policyName: string;
|
|
settingId: string;
|
|
targetRole: AdmxPolicyRegistryTarget['role'];
|
|
}
|
|
|
|
export type AdmxPolicyResolution =
|
|
| { status: 'matched'; catalogId: string; reference: AdmxPolicyReference }
|
|
| { status: 'not-found'; catalogId?: string }
|
|
| { status: 'ambiguous'; catalogId: string; candidates: AdmxPolicyReference[] };
|
|
|
|
export interface GpoTechnicalPolicyIdentity {
|
|
version: 1;
|
|
id: string;
|
|
resolver: typeof GPO_POLICY_IDENTITY_RESOLVER | typeof GPO_NATIVE_POLICY_IDENTITY_RESOLVER;
|
|
scope: ResolvableGpoPolicyScope;
|
|
target: GpoRegistryTechnicalTarget | {
|
|
kind: 'security-template' | 'advanced-audit-policy';
|
|
section: string;
|
|
key: string;
|
|
hive?: never;
|
|
valueName?: never;
|
|
};
|
|
admx: AdmxPolicyResolution;
|
|
}
|
|
|
|
export type GpoPolicyIdentityResolution =
|
|
| { version: 1; status: 'resolved'; identity: GpoTechnicalPolicyIdentity }
|
|
| { version: 1; status: 'unresolved'; reasonCode: string };
|
|
|
|
function safeTechnicalText(value: string): boolean {
|
|
return Boolean(value.trim()) && value.length <= 32_767 && !/[\u0000-\u001f]/.test(value);
|
|
}
|
|
|
|
export function admxPolicyCatalogValidationErrors(catalog: AdmxPolicyCatalog): string[] {
|
|
const errors: string[] = [];
|
|
if (catalog.version !== 1 || !catalog.id.trim() || !/^[a-f0-9]{64}$/.test(catalog.source.packageSha256)) {
|
|
errors.push('Der ADMX-Katalog besitzt keine gültige Version, ID oder Paketprüfsumme.');
|
|
}
|
|
if (!catalog.source.baselineId.trim() || catalog.parser.id !== ADMX_POLICY_CATALOG_PARSER.id
|
|
|| catalog.parser.version !== ADMX_POLICY_CATALOG_PARSER.version) {
|
|
errors.push('Der ADMX-Katalog besitzt keine gültige Quelle oder Parserprovenienz.');
|
|
}
|
|
const policyIds = new Set<string>();
|
|
for (const policy of catalog.policies) {
|
|
if (policy.version !== 1 || !safeTechnicalText(policy.technicalId) || !safeTechnicalText(policy.targetNamespace)
|
|
|| !safeTechnicalText(policy.name) || !safeTechnicalText(policy.sourcePath) || !/^[a-f0-9]{64}$/.test(policy.sourceSha256)
|
|
|| policyIds.has(policy.technicalId)) {
|
|
errors.push('Der ADMX-Katalog enthält eine ungültige oder doppelte Policydefinition.');
|
|
continue;
|
|
}
|
|
policyIds.add(policy.technicalId);
|
|
for (const target of policy.registryTargets) {
|
|
if (target.version !== 1 || !safeTechnicalText(target.key)
|
|
|| (target.match === 'exact-value' && target.valueName === undefined)
|
|
|| (target.match === 'key-values' && target.valueName !== undefined)
|
|
|| (target.role === 'element' && !target.elementId?.trim())) {
|
|
errors.push('Eine ADMX-Registryzuordnung ist ungültig.');
|
|
}
|
|
}
|
|
}
|
|
for (const resource of catalog.languageResources) {
|
|
if (resource.version !== 1 || !safeTechnicalText(resource.sourcePath) || !/^[a-f0-9]{64}$/.test(resource.sourceSha256)
|
|
|| !safeTechnicalText(resource.admxFileName) || !safeTechnicalText(resource.locale)
|
|
|| Object.entries(resource.strings).some(([id, value]) => !safeTechnicalText(id) || typeof value !== 'string')) {
|
|
errors.push('Der ADML-Ressourcenkatalog ist ungültig.');
|
|
}
|
|
}
|
|
return [...new Set(errors)];
|
|
}
|
|
|
|
export function gpoTechnicalPolicyIdentityValidationErrors(identity: GpoTechnicalPolicyIdentity): string[] {
|
|
const errors: string[] = [];
|
|
if (identity.version !== 1 || !/^gpo-policy-identity-[a-f0-9]{32}$/.test(identity.id)
|
|
|| ![GPO_POLICY_IDENTITY_RESOLVER.id, GPO_NATIVE_POLICY_IDENTITY_RESOLVER.id].includes(identity.resolver.id)
|
|
|| identity.resolver.version !== GPO_POLICY_IDENTITY_RESOLVER.version) {
|
|
errors.push('Die technische Policy-Identität besitzt eine ungültige Version oder Resolverprovenienz.');
|
|
}
|
|
const expectedHive = identity.scope === 'computer' ? 'HKEY_LOCAL_MACHINE' : 'HKEY_CURRENT_USER';
|
|
const registry = identity.target.kind === 'registry-key' || identity.target.kind === 'registry-value';
|
|
if (!['computer', 'user'].includes(identity.scope)
|
|
|| identity.resolver.id !== (registry ? GPO_POLICY_IDENTITY_RESOLVER.id : GPO_NATIVE_POLICY_IDENTITY_RESOLVER.id)
|
|
|| !['registry-key', 'registry-value', 'security-template', 'advanced-audit-policy'].includes(identity.target.kind)
|
|
|| (registry && identity.target.hive !== expectedHive) || !safeTechnicalText(identity.target.key)
|
|
|| (!registry && (!('section' in identity.target) || !safeTechnicalText(identity.target.section) || identity.target.hive !== undefined))
|
|
|| (identity.target.kind === 'registry-value' && identity.target.valueName === undefined)
|
|
|| (identity.target.kind === 'registry-key' && identity.target.valueName !== undefined)) {
|
|
errors.push('Die technische Registry-Zielidentität ist ungültig.');
|
|
}
|
|
if (identity.admx.status === 'matched' && (!identity.admx.catalogId.trim()
|
|
|| !identity.admx.reference.technicalId.trim() || !identity.admx.reference.settingId.trim())) {
|
|
errors.push('Die ADMX-Zuordnung der Policy-Identität ist ungültig.');
|
|
}
|
|
if (identity.admx.status === 'ambiguous' && (!identity.admx.catalogId.trim() || identity.admx.candidates.length < 2)) {
|
|
errors.push('Eine mehrdeutige ADMX-Zuordnung benötigt mindestens zwei Kandidaten.');
|
|
}
|
|
return errors;
|
|
}
|