101 lines
3.6 KiB
TypeScript
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');
|
|
});
|
|
});
|