Track online backup source IP and add detailed recovery output
CI / verify (push) Waiting to run

This commit is contained in:
Sucukdeluxe
2026-09-22 05:02:21 +02:00
parent 560966fa2a
commit 43b77401c6
15 changed files with 147 additions and 26 deletions
+10 -4
View File
@@ -1,11 +1,13 @@
const fs = require('node:fs');
const path = require('node:path');
const crypto = require('node:crypto');
const { isIP } = require('node:net');
const secretStore = require('./secret-store');
const { parseOnlineBackupKey } = require('./online-backup');
const STORED_LEGACY_ENTRY_KEYS = ['createdAt', 'encryptedKey', 'id'];
const STORED_EXPIRING_ENTRY_KEYS = ['createdAt', 'encryptedKey', 'expiresAt', 'id'];
const STORED_IP_ENTRY_KEYS = ['createdAt', 'encryptedKey', 'expiresAt', 'id', 'sourceIp'];
const STORED_V1_DOCUMENT_KEYS = ['keys', 'version'];
const STORED_GENERATED_DOCUMENT_KEYS = ['generation', 'keys', 'version'];
const KEYRING_ERROR_CODES = Object.freeze({
@@ -181,13 +183,15 @@ function createOnlineBackupKeyring({
function validateEntry(entry) {
const hasLegacyShape = hasExactKeys(entry, STORED_LEGACY_ENTRY_KEYS);
const hasExpiringShape = hasExactKeys(entry, STORED_EXPIRING_ENTRY_KEYS);
const hasIpShape = hasExactKeys(entry, STORED_IP_ENTRY_KEYS);
const hasExpiringShape = hasExactKeys(entry, STORED_EXPIRING_ENTRY_KEYS) || hasIpShape;
if (
(!hasLegacyShape && !hasExpiringShape)
|| !isCanonicalId(entry.id)
|| typeof entry.encryptedKey !== 'string'
|| !isEncrypted(entry.encryptedKey)
|| typeof entry.createdAt !== 'string'
|| (hasIpShape && (typeof entry.sourceIp !== 'string' || !isIP(entry.sourceIp)))
) {
return { issue: KEYRING_ERROR_CODES.structure, id: typeof entry?.id === 'string' ? entry.id : null };
}
@@ -228,6 +232,7 @@ function createOnlineBackupKeyring({
encryptedKey: entry.encryptedKey,
createdAt,
expiresAt,
...(hasIpShape ? { sourceIp: entry.sourceIp } : {}),
key
}
};
@@ -413,7 +418,7 @@ function createOnlineBackupKeyring({
const contents = JSON.stringify({
version: 2,
generation,
keys: entries.map(({ id, encryptedKey, createdAt, expiresAt }) => ({ id, encryptedKey, createdAt, expiresAt: expiresAt ?? null }))
keys: entries.map(({ id, encryptedKey, createdAt, expiresAt, sourceIp }) => ({ id, encryptedKey, createdAt, expiresAt: expiresAt ?? null, ...(sourceIp ? { sourceIp } : {}) }))
});
const payload = canonicalPayload(parseDocument(contents));
const stagingPath = temporaryPath('staging');
@@ -455,11 +460,12 @@ function createOnlineBackupKeyring({
}
const entries = activeEntries
.sort((left, right) => right.createdAt.localeCompare(left.createdAt))
.map(({ id, key, createdAt, expiresAt }) => Object.freeze({
.map(({ id, key, createdAt, expiresAt, sourceIp }) => Object.freeze({
id,
displayKey: `${key.slice(0, 9)}${key.slice(-4)}`,
createdAt,
expiresAt
expiresAt,
...(sourceIp ? { sourceIp } : {})
}));
return Object.freeze({
entries: Object.freeze(entries),