fix(storage): make S3 server-side-encryption optional (default off)
All checks were successful
Build & Push Docker Images / lint (push) Successful in 2m46s
Build & Push Docker Images / build-and-push (push) Successful in 7m53s

Prod MinIO has no KMS/KES, so the unconditional
`x-amz-server-side-encryption: AES256` header on every PutObject was
rejected with `NotImplemented` ("KMS not configured") — breaking ALL
server-side uploads on prod: avatars, the signed-PDF deposit on
Documenso completion, GDPR exports, the nightly DB backup, generated
EOI/contract PDFs, report renders. Reads/presigned downloads were
unaffected, so the cutover walkthrough missed it.

The SSE header is now sent only when explicitly configured via the
per-port `storage_s3_sse` setting (or the STORAGE_S3_SSE env fallback);
the default is off so a vanilla S3-compatible backend accepts uploads.
This also resolves the put()-encrypts-but-presignUpload-doesn't
asymmetry — presigned PUTs never sent SSE, so both paths now match by
default.

Extracted buildPutObjectMetadata() as a pure, unit-tested helper.

Interim fix; the planned filesystem-storage migration removes SSE from
the prod path entirely.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-06-03 22:08:41 +02:00
parent 1750e265e7
commit eff57af571
4 changed files with 83 additions and 12 deletions

View File

@@ -0,0 +1,28 @@
/**
* SSE (server-side-encryption) header policy for the S3 backend.
*
* Regression (2026-06-03 prod): MinIO with no KMS/KES rejected EVERY
* PutObject because `put()` unconditionally sent
* `x-amz-server-side-encryption: AES256`, which a backend without KMS
* answers with `NotImplemented` ("KMS not configured"). The header must
* only be sent when SSE is explicitly configured; the default is OFF so
* a vanilla S3-compatible backend accepts uploads.
*/
import { describe, expect, it } from 'vitest';
import { buildPutObjectMetadata } from '@/lib/storage/s3';
describe('buildPutObjectMetadata', () => {
it('omits the server-side-encryption header when no SSE is configured', () => {
const meta = buildPutObjectMetadata('application/pdf', undefined);
expect(meta['Content-Type']).toBe('application/pdf');
expect(meta['x-amz-server-side-encryption']).toBeUndefined();
});
it('sends the configured SSE algorithm when one is set', () => {
const meta = buildPutObjectMetadata('image/png', 'AES256');
expect(meta['Content-Type']).toBe('image/png');
expect(meta['x-amz-server-side-encryption']).toBe('AES256');
});
});