mirror of
https://github.com/nicotsx/ironmount.git
synced 2025-12-10 12:10:51 +01:00
feat: implement encryption for sensitive fields in volume backends
This commit is contained in:
committed by
Nicolas Meienberger
parent
da8e9c4ada
commit
2df1fa53a0
@@ -1,6 +1,7 @@
|
||||
import * as fs from "node:fs/promises";
|
||||
import * as os from "node:os";
|
||||
import { OPERATION_TIMEOUT } from "../../../core/constants";
|
||||
import { cryptoUtils } from "../../../utils/crypto";
|
||||
import { toMessage } from "../../../utils/errors";
|
||||
import { logger } from "../../../utils/logger";
|
||||
import { getMountForPath } from "../../../utils/mountinfo";
|
||||
@@ -33,10 +34,12 @@ const mount = async (config: BackendConfig, path: string) => {
|
||||
const run = async () => {
|
||||
await fs.mkdir(path, { recursive: true });
|
||||
|
||||
const password = await cryptoUtils.decrypt(config.password);
|
||||
|
||||
const source = `//${config.server}/${config.share}`;
|
||||
const options = [
|
||||
`user=${config.username}`,
|
||||
`pass=${config.password}`,
|
||||
`pass=${password}`,
|
||||
`vers=${config.vers}`,
|
||||
`port=${config.port}`,
|
||||
"uid=1000",
|
||||
|
||||
@@ -3,6 +3,7 @@ import * as fs from "node:fs/promises";
|
||||
import * as os from "node:os";
|
||||
import { promisify } from "node:util";
|
||||
import { OPERATION_TIMEOUT } from "../../../core/constants";
|
||||
import { cryptoUtils } from "../../../utils/crypto";
|
||||
import { toMessage } from "../../../utils/errors";
|
||||
import { logger } from "../../../utils/logger";
|
||||
import { getMountForPath } from "../../../utils/mountinfo";
|
||||
@@ -49,8 +50,9 @@ const mount = async (config: BackendConfig, path: string) => {
|
||||
: ["uid=1000", "gid=1000", "file_mode=0664", "dir_mode=0775"];
|
||||
|
||||
if (config.username && config.password) {
|
||||
const password = await cryptoUtils.decrypt(config.password);
|
||||
const secretsFile = "/etc/davfs2/secrets";
|
||||
const secretsContent = `${source} ${config.username} ${config.password}\n`;
|
||||
const secretsContent = `${source} ${config.username} ${password}\n`;
|
||||
await fs.appendFile(secretsFile, secretsContent, { mode: 0o600 });
|
||||
}
|
||||
|
||||
|
||||
@@ -8,6 +8,7 @@ import slugify from "slugify";
|
||||
import { getCapabilities, parseDockerHost } from "../../core/capabilities";
|
||||
import { db } from "../../db/db";
|
||||
import { volumesTable } from "../../db/schema";
|
||||
import { cryptoUtils } from "../../utils/crypto";
|
||||
import { toMessage } from "../../utils/errors";
|
||||
import { generateShortId } from "../../utils/id";
|
||||
import { getStatFs, type StatFs } from "../../utils/mountinfo";
|
||||
@@ -19,6 +20,23 @@ import { logger } from "../../utils/logger";
|
||||
import { serverEvents } from "../../core/events";
|
||||
import type { BackendConfig } from "~/schemas/volumes";
|
||||
|
||||
async function encryptSensitiveFields(config: BackendConfig): Promise<BackendConfig> {
|
||||
switch (config.backend) {
|
||||
case "smb":
|
||||
return {
|
||||
...config,
|
||||
password: await cryptoUtils.encrypt(config.password),
|
||||
};
|
||||
case "webdav":
|
||||
return {
|
||||
...config,
|
||||
password: config.password ? await cryptoUtils.encrypt(config.password) : undefined,
|
||||
};
|
||||
default:
|
||||
return config;
|
||||
}
|
||||
}
|
||||
|
||||
const listVolumes = async () => {
|
||||
const volumes = await db.query.volumesTable.findMany({});
|
||||
|
||||
@@ -37,13 +55,14 @@ const createVolume = async (name: string, backendConfig: BackendConfig) => {
|
||||
}
|
||||
|
||||
const shortId = generateShortId();
|
||||
const encryptedConfig = await encryptSensitiveFields(backendConfig);
|
||||
|
||||
const [created] = await db
|
||||
.insert(volumesTable)
|
||||
.values({
|
||||
shortId,
|
||||
name: slug,
|
||||
config: backendConfig,
|
||||
config: encryptedConfig,
|
||||
type: backendConfig.backend,
|
||||
})
|
||||
.returning();
|
||||
@@ -175,11 +194,13 @@ const updateVolume = async (name: string, volumeData: UpdateVolumeBody) => {
|
||||
await backend.unmount();
|
||||
}
|
||||
|
||||
const encryptedConfig = volumeData.config ? await encryptSensitiveFields(volumeData.config) : undefined;
|
||||
|
||||
const [updated] = await db
|
||||
.update(volumesTable)
|
||||
.set({
|
||||
name: newName,
|
||||
config: volumeData.config,
|
||||
config: encryptedConfig,
|
||||
type: volumeData.config?.backend,
|
||||
autoRemount: volumeData.autoRemount,
|
||||
updatedAt: Date.now(),
|
||||
|
||||
@@ -5,6 +5,10 @@ const algorithm = "aes-256-gcm" as const;
|
||||
const keyLength = 32;
|
||||
const encryptionPrefix = "encv1";
|
||||
|
||||
const isEncrypted = (val?: string): boolean => {
|
||||
return typeof val === "string" && val.startsWith(encryptionPrefix);
|
||||
};
|
||||
|
||||
/**
|
||||
* Given a string, encrypts it using a randomly generated salt
|
||||
*/
|
||||
@@ -13,7 +17,7 @@ const encrypt = async (data: string) => {
|
||||
return data;
|
||||
}
|
||||
|
||||
if (data.startsWith(encryptionPrefix)) {
|
||||
if (isEncrypted(data)) {
|
||||
return data;
|
||||
}
|
||||
|
||||
@@ -34,6 +38,10 @@ const encrypt = async (data: string) => {
|
||||
* Given an encrypted string, decrypts it using the salt stored in the string
|
||||
*/
|
||||
const decrypt = async (encryptedData: string) => {
|
||||
if (!isEncrypted(encryptedData)) {
|
||||
return encryptedData;
|
||||
}
|
||||
|
||||
const secret = await Bun.file(RESTIC_PASS_FILE).text();
|
||||
|
||||
const parts = encryptedData.split(":").slice(1); // Remove prefix
|
||||
@@ -58,4 +66,5 @@ const decrypt = async (encryptedData: string) => {
|
||||
export const cryptoUtils = {
|
||||
encrypt,
|
||||
decrypt,
|
||||
isEncrypted,
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user