Protect private bank configuration from drift reconciliation (Refs #152)
This commit is contained in:
@@ -1,3 +1,9 @@
|
||||
import {
|
||||
assertPublicFilePayload,
|
||||
BANK_CONFIG_FILE,
|
||||
PrivateFileOperationError,
|
||||
isPrivateConfigPath
|
||||
} from '../fleet/private-config.js';
|
||||
import { packFileJson, unpackFileJson, referenceBlobs } from '../db/file-blobs.js';
|
||||
import { pruneHistory } from '../retention.js';
|
||||
import { recordAgentReceipt } from '../provisioning/agent-events.js';
|
||||
@@ -1248,7 +1254,9 @@ function enqueueHostOperation(
|
||||
)
|
||||
throw new Error('Host is disabled. Enable it before requesting operations.');
|
||||
const requestHash = createHash('sha256').update(payload).digest('hex');
|
||||
const packed = packFileJson(JSON.parse(payload.toString()));
|
||||
const parsedPayload = JSON.parse(payload.toString());
|
||||
assertPublicFilePayload(parsedPayload);
|
||||
const packed = packFileJson(parsedPayload);
|
||||
payload = JSON.stringify(packed.value);
|
||||
const id = options.id ?? randomUUID();
|
||||
const timestamp = now();
|
||||
@@ -1405,6 +1413,15 @@ export function pendingHostOperationRows(
|
||||
)
|
||||
: new Set<string>();
|
||||
const admitted = rows.filter((operation) => {
|
||||
// Withhold unsafe legacy commands without breaking the entire delivery stream.
|
||||
if (operation.kind.startsWith('files.') || operation.kind === 'logs.read') {
|
||||
try {
|
||||
getHostOperationPayload(hostId, operation.id);
|
||||
} catch (error) {
|
||||
if (error instanceof PrivateFileOperationError) return false;
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
if (
|
||||
!libraryKinds.includes(operation.kind) ||
|
||||
preparing.has(hostId) ||
|
||||
@@ -1431,6 +1448,9 @@ export function getHostOperationPayload(hostId: string, id: string): Buffer {
|
||||
)
|
||||
.get(id, hostId) as { payload: Buffer } | undefined;
|
||||
if (!row) throw new Error('Host operation was not found for this host.');
|
||||
// Block old private-file commands before delivery to agents predating the guard.
|
||||
if (row.payload.includes(BANK_CONFIG_FILE))
|
||||
assertPublicFilePayload(JSON.parse(row.payload.toString()));
|
||||
if (
|
||||
!row.payload.includes('"contentSha256"') ||
|
||||
getHost(hostId)?.inventory.capabilities?.includes('files.blobs')
|
||||
@@ -1468,8 +1488,18 @@ export function reportHostOperationStatus(
|
||||
if (result !== null && Buffer.byteLength(result) > 128 * 1024 * 1024)
|
||||
throw new Error('Operation result exceeds the limit.');
|
||||
if (result !== null) {
|
||||
const target =
|
||||
operation.kind === 'files.read'
|
||||
? (database()
|
||||
.prepare('SELECT target_path FROM ec_host_operations WHERE id=?')
|
||||
.get(id) as { target_path: string | null })
|
||||
: null;
|
||||
const packed = packFileJson(
|
||||
operation.kind === 'logs.read' ? logResultMetadata(input.result) : input.result
|
||||
target?.target_path && isPrivateConfigPath(target.target_path)
|
||||
? { redacted: true }
|
||||
: operation.kind === 'logs.read'
|
||||
? logResultMetadata(input.result)
|
||||
: input.result
|
||||
);
|
||||
result = JSON.stringify(packed.value);
|
||||
referenceBlobs('operation-result', id, packed.hashes);
|
||||
|
||||
Reference in New Issue
Block a user