96 lines
4.1 KiB
TypeScript
96 lines
4.1 KiB
TypeScript
import { createHash } from 'node:crypto';
|
|
import type { GpoArtifactInventory } from '../../src/domain/gpo-ingestion';
|
|
import { gpoArtifactInventoryValidationErrors } from '../../src/domain/gpo-ingestion';
|
|
import { classifyGpoArtifactPath, isGpoSysvolPath } from '../../src/gpo/artifact-classifier';
|
|
import type { ImportResourcePolicy } from '../../src/importers/resource-policy';
|
|
import { inventoryZipArchive, type SafeZipInventory } from './zip-inventory';
|
|
|
|
export type GpoContainerRecognition =
|
|
| { status: 'gpo-backup-candidate'; evidenceCodes: string[] }
|
|
| { status: 'unrecognized-zip'; evidenceCodes: string[] };
|
|
|
|
export interface GpoContainerInventoryResult {
|
|
version: 1;
|
|
recognition: GpoContainerRecognition;
|
|
archive: SafeZipInventory;
|
|
inventory: GpoArtifactInventory;
|
|
}
|
|
|
|
export interface GpoContainerRecognitionResult {
|
|
version: 1;
|
|
recognition: GpoContainerRecognition;
|
|
}
|
|
|
|
function artifactId(containerHash: string, path: string, contentHash: string): string {
|
|
return `gpo-entry-${createHash('sha256').update(`${containerHash}\0${path}\0${contentHash}`).digest('hex').slice(0, 32)}`;
|
|
}
|
|
|
|
function recognizeFiles(files: SafeZipInventory['entries']): GpoContainerRecognition {
|
|
const metadataRoots = files
|
|
.filter(entry => entry.kind === 'file' && classifyGpoArtifactPath(entry.relativePath).role === 'backup-metadata')
|
|
.map(entry => entry.relativePath.includes('/') ? entry.relativePath.slice(0, entry.relativePath.lastIndexOf('/')) : '');
|
|
const hasMetadata = metadataRoots.length > 0;
|
|
const hasSysvolStructure = files.some(entry => entry.kind === 'file' && isGpoSysvolPath(entry.relativePath));
|
|
const recognized = metadataRoots.some(root => files.some(entry =>
|
|
entry.kind === 'file'
|
|
&& isGpoSysvolPath(entry.relativePath)
|
|
&& (!root || entry.relativePath.startsWith(root + '/'))
|
|
));
|
|
const evidenceCodes = [
|
|
...(hasMetadata ? ['gpo-backup-metadata-path-observed'] : []),
|
|
...(hasSysvolStructure ? ['gpo-domain-sysvol-structure-observed'] : []),
|
|
...(recognized ? [] : ['gpo-container-structure-unconfirmed'])
|
|
].sort();
|
|
return recognized
|
|
? { status: 'gpo-backup-candidate', evidenceCodes }
|
|
: { status: 'unrecognized-zip', evidenceCodes };
|
|
}
|
|
|
|
export function recognizeGpoContainer(
|
|
bytes: Uint8Array,
|
|
resourcePolicy?: Partial<ImportResourcePolicy>
|
|
): GpoContainerRecognitionResult {
|
|
const archive = inventoryZipArchive(bytes, resourcePolicy);
|
|
return { version: 1, recognition: recognizeFiles(archive.entries) };
|
|
}
|
|
export function inventoryGpoContainer(
|
|
bytes: Uint8Array,
|
|
resourcePolicy?: Partial<ImportResourcePolicy>
|
|
): GpoContainerInventoryResult {
|
|
const archive = inventoryZipArchive(bytes, resourcePolicy);
|
|
const files = archive.entries.filter(entry => entry.kind === 'file');
|
|
const classified = files.map(entry => ({ entry, classification: classifyGpoArtifactPath(entry.relativePath) }));
|
|
const recognition = recognizeFiles(archive.entries);
|
|
const evidenceCodes = recognition.evidenceCodes;
|
|
const diagnosticCodes = new Set(evidenceCodes);
|
|
const entries = classified.map(({ entry, classification }) => {
|
|
const disposition = classification.role === 'unknown' ? 'unsupported' as const : 'inventory-only' as const;
|
|
if (classification.role === 'unknown') diagnosticCodes.add('gpo-artifact-unsupported');
|
|
else diagnosticCodes.add('gpo-artifact-parser-pending');
|
|
if (classification.role === 'script') diagnosticCodes.add('gpo-script-inventory-only');
|
|
return {
|
|
version: 1 as const,
|
|
artifactId: artifactId(archive.containerSha256, entry.relativePath, entry.sha256 ?? ''),
|
|
relativePath: entry.relativePath,
|
|
role: classification.role,
|
|
scope: classification.scope,
|
|
disposition,
|
|
secretIndicatorStatus: 'not-inspected' as const
|
|
};
|
|
});
|
|
const inventory: GpoArtifactInventory = {
|
|
version: 1,
|
|
status: 'complete',
|
|
entries,
|
|
diagnosticCodes: [...diagnosticCodes].sort()
|
|
};
|
|
const validationErrors = gpoArtifactInventoryValidationErrors(inventory);
|
|
if (validationErrors.length) throw new Error(`Intern ungültiges GPO-Artefaktinventar: ${validationErrors.join(' ')}`);
|
|
return {
|
|
version: 1,
|
|
recognition,
|
|
archive,
|
|
inventory
|
|
};
|
|
}
|