Assessor_2.0/tests/server/policy-evaluation-rule-cata...

101 lines
3.6 KiB
TypeScript

import { createHash } from 'node:crypto';
import { mkdtemp, rm, writeFile } from 'node:fs/promises';
import { tmpdir } from 'node:os';
import { join } from 'node:path';
import { afterEach, describe, expect, it } from 'vitest';
import {
loadPolicyEvaluationRuleCatalog,
MAXIMUM_POLICY_RULE_CATALOG_BYTES
} from '../../server/gpo/policy-evaluation-rule-catalog-loader';
import {
POLICY_EVALUATION_RULE_CATALOG,
RELEASED_POLICY_EVALUATION_RULE_CATALOG,
type PolicyEvaluationRuleCatalog
} from '../../src/domain/policy-evaluation-rules';
const directories: string[] = [];
function syntheticCatalog(): PolicyEvaluationRuleCatalog {
return {
version: 1,
id: 'synthetic-curated-policy-rules-1',
catalog: POLICY_EVALUATION_RULE_CATALOG,
status: 'released',
rules: [{
version: 1,
id: 'synthetic-rule-1',
identityId: 'registry:computer:hklm:software\\policies\\synthetic:value',
deliveryMechanism: 'registry-policy',
contexts: [{
version: 1,
productFamily: 'Synthetic Windows',
productVersion: '1',
roles: ['Member Server'],
scopes: ['computer']
}],
baselineDisposition: 'explicit-recommendation',
comparator: { kind: 'equality' },
riskRule: { version: 1, id: 'synthetic-risk-1', severity: 'high' },
dependencies: []
}]
};
}
async function catalogFile(content: string): Promise<{ path: string; sha256: string }> {
const directory = await mkdtemp(join(tmpdir(), 'assessor-policy-catalog-'));
directories.push(directory);
const path = join(directory, 'catalog.json');
await writeFile(path, content, 'utf8');
return { path, sha256: createHash('sha256').update(content, 'utf8').digest('hex') };
}
afterEach(async () => {
await Promise.all(directories.splice(0).map(directory => rm(directory, { recursive: true, force: true })));
});
describe('policy evaluation rule catalog loader', () => {
it('keeps the safe empty released catalog when no operator file is configured', async () => {
const loaded = await loadPolicyEvaluationRuleCatalog();
expect(loaded).toEqual(RELEASED_POLICY_EVALUATION_RULE_CATALOG);
expect(loaded).not.toBe(RELEASED_POLICY_EVALUATION_RULE_CATALOG);
});
it('loads a pinned, structurally and semantically valid curated catalog', async () => {
const expected = syntheticCatalog();
const source = `\uFEFF${JSON.stringify(expected)}`;
const file = await catalogFile(source);
await expect(loadPolicyEvaluationRuleCatalog({
path: file.path,
expectedSha256: file.sha256
})).resolves.toEqual(expected);
});
it('fails closed for incomplete configuration, hash drift, malformed structure, and oversize input', async () => {
const valid = await catalogFile(JSON.stringify(syntheticCatalog()));
await expect(loadPolicyEvaluationRuleCatalog({ path: valid.path }))
.rejects.toThrow('gemeinsam konfiguriert');
await expect(loadPolicyEvaluationRuleCatalog({
path: valid.path,
expectedSha256: '0'.repeat(64)
})).rejects.toThrow('SHA-256');
const malformed = await catalogFile(JSON.stringify({ version: 1, rules: [null] }));
await expect(loadPolicyEvaluationRuleCatalog({
path: malformed.path,
expectedSha256: malformed.sha256
})).rejects.toThrow('keine gueltige Struktur');
await expect(loadPolicyEvaluationRuleCatalog({
path: valid.path,
expectedSha256: valid.sha256,
maximumBytes: 1
})).rejects.toThrow('Groessengrenze');
await expect(loadPolicyEvaluationRuleCatalog({
path: valid.path,
expectedSha256: valid.sha256,
maximumBytes: MAXIMUM_POLICY_RULE_CATALOG_BYTES + 1
})).rejects.toThrow('Groessengrenze');
});
});