feat: add AES-256-GCM encryption at rest for sensitive settings

This commit is contained in:
SnapOtter
2026-06-13 16:35:28 +08:00
parent 36f083ba64
commit 2520cdd556
4 changed files with 158 additions and 5 deletions
+29 -5
View File
@@ -11,6 +11,8 @@ import type { FastifyInstance, FastifyReply, FastifyRequest } from "fastify";
import { z } from "zod";
import { db, schema } from "../db/index.js";
import { auditLog } from "../lib/audit.js";
import { env } from "../config.js";
import { encrypt, decrypt, isEncrypted } from "../lib/encryption.js";
import { requirePermission } from "../permissions.js";
import { requireAuth } from "../plugins/auth.js";
@@ -18,7 +20,27 @@ const settingsBodySchema = z.record(z.string().min(1), z.unknown());
const HTML_TAG_PATTERN = /<[a-z/!?][^>]*>/i;
const SENSITIVE_KEYS = new Set(["cookie_secret", "instance_id"]);
const SENSITIVE_KEYS = new Set([
"cookie_secret",
"instance_id",
"oidc_client_secret",
"saml_idp_certificate",
"siem_webhook_auth",
]);
async function encryptIfSensitive(key: string, value: string): Promise<string> {
if (!env.DATA_ENCRYPTION_KEY || !SENSITIVE_KEYS.has(key)) return value;
return encrypt(value, env.DATA_ENCRYPTION_KEY);
}
async function decryptIfNeeded(value: string): Promise<string> {
if (!isEncrypted(value)) return value;
if (!env.DATA_ENCRYPTION_KEY) return value;
return (
(await decrypt(value, env.DATA_ENCRYPTION_KEY, env.DATA_ENCRYPTION_KEY_PREVIOUS || undefined)) ??
value
);
}
export async function settingsRoutes(app: FastifyInstance): Promise<void> {
// GET /api/v1/settings — Get all settings as a key-value object
@@ -32,7 +54,7 @@ export async function settingsRoutes(app: FastifyInstance): Promise<void> {
const settings: Record<string, string> = {};
for (const row of rows) {
if (!isAdmin && SENSITIVE_KEYS.has(row.key)) continue;
settings[row.key] = row.value;
settings[row.key] = await decryptIfNeeded(row.value);
}
return reply.send({ settings });
@@ -74,6 +96,8 @@ export async function settingsRoutes(app: FastifyInstance): Promise<void> {
const now = new Date();
for (const { key, strValue } of entries) {
const storedValue = await encryptIfSensitive(key, strValue);
// Upsert: insert or update on conflict
const [existing] = await db
.select()
@@ -83,10 +107,10 @@ export async function settingsRoutes(app: FastifyInstance): Promise<void> {
if (existing) {
await db
.update(schema.settings)
.set({ value: strValue, updatedAt: now })
.set({ value: storedValue, updatedAt: now })
.where(eq(schema.settings.key, key));
} else {
await db.insert(schema.settings).values({ key, value: strValue });
await db.insert(schema.settings).values({ key, value: storedValue });
}
}
@@ -125,7 +149,7 @@ export async function settingsRoutes(app: FastifyInstance): Promise<void> {
return reply.send({
key: row.key,
value: row.value,
value: await decryptIfNeeded(row.value),
updatedAt: row.updatedAt.toISOString(),
});
},