mirror of
https://github.com/MBombeck/HealthLog.git
synced 2026-11-10 21:32:18 +00:00
fix(backup): bind new off-host objects to their key and account, and cap what an uploaded backup may inflate to
Off-host objects are now written as envelope version 4: the header names the off-host key by fingerprint, and the object's key in the bucket (<date>/user-<id>.json.enc) is authenticated as GCM associated data, so a copy moved to another account or another night no longer opens. Objects of versions 1 to 3 still read. BACKUP_ENCRYPTION_PREVIOUS_KEYS (comma-separated, on the compose whitelist and in the env manifest) keeps retired off-host keys readable, which is the rotation path the off-host key did not have. The admin backup upload counted the bytes that arrived and none after the gunzip, so a small gzip could expand without limit into the JSON scanner and take the app down. The inflated bytes are now held to 100 times the compressed bytes read (at least 256 MB, at most 4 GB) and a file past that is refused with 413 before anything is stored.
This commit is contained in:
1 parent
1b4594e69f
commit
849748c4cd
14 files changed
+463
-90
No files matched your search
@@ -293,6 +293,11 @@
|
||||
"purpose": "Trusted reverse-proxy hop count; the client IP is read that many entries from the right of X-Forwarded-For.",
|
||||
"example": "1"
|
||||
},
|
||||
{
|
||||
"name": "BACKUP_ENCRYPTION_PREVIOUS_KEYS",
|
||||
"purpose": "Comma-separated off-host backup keys that are no longer written with but may still be needed to read older objects. Set during a rotation of BACKUP_ENCRYPTION_KEY until the bucket's lifecycle rule has retired every object written under the old key.",
|
||||
"example": ""
|
||||
},
|
||||
{
|
||||
"name": "BACKUP_MAX_STORED_MB",
|
||||
"purpose": "Largest stored backup of one account the app keeps, in megabytes of encrypted copy (2048 by default). A storage limit, independent of the app's memory: a backup past it fails with a message naming this variable, and the previous copy stays.",
|
||||
|
||||
@@ -32,6 +32,7 @@ import {
|
||||
loadOffhostConfig,
|
||||
getS3Client,
|
||||
openBackupObject,
|
||||
offhostKeyRing,
|
||||
OffhostBackupNotConfiguredError,
|
||||
} from "@/lib/jobs/offhost-backup";
|
||||
|
||||
@@ -109,7 +110,7 @@ async function main() {
|
||||
`Downloading s3://${cfg.bucket}/${key} from ${cfg.endpoint} (region=${cfg.region})`,
|
||||
);
|
||||
const ciphertext = await s3.getObject(key);
|
||||
const source = openBackupObject(ciphertext, cfg.encryptionKey);
|
||||
const source = openBackupObject(ciphertext, offhostKeyRing(cfg), key);
|
||||
|
||||
// Checked first, in one streamed pass that keeps only the small sections,
|
||||
// so nothing is written for an object that is not a backup.
|
||||
|
||||
Reference in new issue
Block a user