refactor: store temp keys in /run/zerobyte subfolders
This commit is contained in:
parent
481983a75b
commit
3b1b3ff5fe
5 changed files with 111 additions and 45 deletions
|
|
@ -16,6 +16,27 @@ const getMountPathHash = (mountPath: string) => createHash("sha256").update(moun
|
||||||
const getPrivateKeyPath = (mountPath: string) => path.join(SSH_KEYS_DIR, `${getMountPathHash(mountPath)}.key`);
|
const getPrivateKeyPath = (mountPath: string) => path.join(SSH_KEYS_DIR, `${getMountPathHash(mountPath)}.key`);
|
||||||
const getKnownHostsPath = (mountPath: string) => path.join(SSH_KEYS_DIR, `${getMountPathHash(mountPath)}.known_hosts`);
|
const getKnownHostsPath = (mountPath: string) => path.join(SSH_KEYS_DIR, `${getMountPathHash(mountPath)}.known_hosts`);
|
||||||
|
|
||||||
|
const ensurePrivateSshKeysDir = async () => {
|
||||||
|
const { uid } = os.userInfo();
|
||||||
|
|
||||||
|
try {
|
||||||
|
await fs.mkdir(SSH_KEYS_DIR, { mode: 0o700 });
|
||||||
|
} catch (error) {
|
||||||
|
if (!isNodeErrnoException(error) || error.code !== "EEXIST") throw error;
|
||||||
|
|
||||||
|
const stats = await fs.lstat(SSH_KEYS_DIR);
|
||||||
|
if (!stats.isDirectory() || stats.isSymbolicLink() || stats.uid !== uid) {
|
||||||
|
throw new Error(`Refusing to use unsafe SSH keys directory: ${SSH_KEYS_DIR}`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
await fs.chmod(SSH_KEYS_DIR, 0o700);
|
||||||
|
};
|
||||||
|
|
||||||
|
const isNodeErrnoException = (error: unknown): error is NodeJS.ErrnoException => {
|
||||||
|
return error instanceof Error && "code" in error;
|
||||||
|
};
|
||||||
|
|
||||||
const runSshfs = async (args: string[], password?: string) =>
|
const runSshfs = async (args: string[], password?: string) =>
|
||||||
new Promise<void>((resolve, reject) => {
|
new Promise<void>((resolve, reject) => {
|
||||||
const child = spawn("sshfs", args, { stdio: ["pipe", "pipe", "pipe"] });
|
const child = spawn("sshfs", args, { stdio: ["pipe", "pipe", "pipe"] });
|
||||||
|
|
@ -110,7 +131,7 @@ const mount = async (config: BackendConfig, mountPath: string) => {
|
||||||
|
|
||||||
const run = async () => {
|
const run = async () => {
|
||||||
await fs.mkdir(mountPath, { recursive: true });
|
await fs.mkdir(mountPath, { recursive: true });
|
||||||
await fs.mkdir(SSH_KEYS_DIR, { recursive: true });
|
await ensurePrivateSshKeysDir();
|
||||||
const { uid, gid } = os.userInfo();
|
const { uid, gid } = os.userInfo();
|
||||||
const options = [
|
const options = [
|
||||||
"reconnect",
|
"reconnect",
|
||||||
|
|
|
||||||
|
|
@ -1,8 +1,9 @@
|
||||||
import * as path from "node:path";
|
import * as path from "node:path";
|
||||||
|
import * as os from "node:os";
|
||||||
|
|
||||||
export const OPERATION_TIMEOUT = 5000;
|
export const OPERATION_TIMEOUT = 5000;
|
||||||
export const VOLUME_MOUNT_BASE = process.env.ZEROBYTE_VOLUMES_DIR || "/var/lib/zerobyte/volumes";
|
export const VOLUME_MOUNT_BASE = process.env.ZEROBYTE_VOLUMES_DIR || "/var/lib/zerobyte/volumes";
|
||||||
export const SSH_KEYS_DIR = "/tmp/zerobyte-ssh";
|
export const SSH_KEYS_DIR = path.join(os.tmpdir(), "zerobyte-ssh");
|
||||||
export const RCLONE_CONFIG_DIR = process.env.RCLONE_CONFIG_DIR || "/root/.config/rclone";
|
export const RCLONE_CONFIG_DIR = process.env.RCLONE_CONFIG_DIR || "/root/.config/rclone";
|
||||||
export const RCLONE_CONFIG_FILE = path.join(RCLONE_CONFIG_DIR, "rclone.conf");
|
export const RCLONE_CONFIG_FILE = path.join(RCLONE_CONFIG_DIR, "rclone.conf");
|
||||||
const serverIdleTimeout = Number(process.env.SERVER_IDLE_TIMEOUT ?? 60);
|
const serverIdleTimeout = Number(process.env.SERVER_IDLE_TIMEOUT ?? 60);
|
||||||
|
|
|
||||||
|
|
@ -24,14 +24,7 @@ const withCustomPassword = <T extends object>(config: T) => ({
|
||||||
|
|
||||||
const PLAIN_PRIVATE_KEY =
|
const PLAIN_PRIVATE_KEY =
|
||||||
"-----BEGIN OPENSSH PRIVATE KEY-----\nb3BlbnNzaC1rZXktdjEAAAAA\n-----END OPENSSH PRIVATE KEY-----";
|
"-----BEGIN OPENSSH PRIVATE KEY-----\nb3BlbnNzaC1rZXktdjEAAAAA\n-----END OPENSSH PRIVATE KEY-----";
|
||||||
|
const tempDirs = new Set<string>();
|
||||||
const tempFiles = new Set<string>();
|
|
||||||
|
|
||||||
const trackTempFile = (filePath: string | undefined) => {
|
|
||||||
if (filePath) {
|
|
||||||
tempFiles.add(filePath);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
const buildEnvForTest = async (
|
const buildEnvForTest = async (
|
||||||
config: Parameters<typeof buildEnv>[0],
|
config: Parameters<typeof buildEnv>[0],
|
||||||
|
|
@ -39,36 +32,31 @@ const buildEnvForTest = async (
|
||||||
deps: ResticDeps = makeDeps(),
|
deps: ResticDeps = makeDeps(),
|
||||||
) => {
|
) => {
|
||||||
const env = await buildEnv(config, organizationId, deps);
|
const env = await buildEnv(config, organizationId, deps);
|
||||||
|
if (env._RUNTIME_SECRETS_DIR) tempDirs.add(env._RUNTIME_SECRETS_DIR);
|
||||||
// Automatically track all temp file paths created by buildEnv
|
|
||||||
trackTempFile(env.RESTIC_PASSWORD_FILE);
|
|
||||||
trackTempFile(env.GOOGLE_APPLICATION_CREDENTIALS);
|
|
||||||
trackTempFile(env._SFTP_KEY_PATH);
|
|
||||||
trackTempFile(env._SFTP_KNOWN_HOSTS_PATH);
|
|
||||||
trackTempFile(env.RESTIC_CACERT);
|
|
||||||
|
|
||||||
return env;
|
return env;
|
||||||
};
|
};
|
||||||
|
|
||||||
afterEach(async () => {
|
afterEach(async () => {
|
||||||
await Promise.all(
|
await Promise.all([...tempDirs].map((dir) => fs.rm(dir, { recursive: true, force: true })));
|
||||||
[...tempFiles].map(async (filePath) => {
|
tempDirs.clear();
|
||||||
await fs.rm(filePath, { force: true });
|
|
||||||
}),
|
|
||||||
);
|
|
||||||
tempFiles.clear();
|
|
||||||
});
|
});
|
||||||
|
|
||||||
describe("buildEnv", () => {
|
describe("buildEnv", () => {
|
||||||
describe("base environment", () => {
|
describe("base environment", () => {
|
||||||
test("always sets RESTIC_CACHE_DIR", async () => {
|
test("always sets RESTIC_CACHE_DIR", async () => {
|
||||||
const env = await buildEnvForTest(withCustomPassword({ backend: "local" as const, path: "/tmp/repo" }), "org-1");
|
const env = await buildEnvForTest(
|
||||||
|
withCustomPassword({ backend: "local" as const, path: "/tmp/repo" }),
|
||||||
|
"org-1",
|
||||||
|
);
|
||||||
|
|
||||||
expect(env.RESTIC_CACHE_DIR).toBe(RESTIC_CACHE_DIR);
|
expect(env.RESTIC_CACHE_DIR).toBe(RESTIC_CACHE_DIR);
|
||||||
});
|
});
|
||||||
|
|
||||||
test("always sets PATH", async () => {
|
test("always sets PATH", async () => {
|
||||||
const env = await buildEnvForTest(withCustomPassword({ backend: "local" as const, path: "/tmp/repo" }), "org-1");
|
const env = await buildEnvForTest(
|
||||||
|
withCustomPassword({ backend: "local" as const, path: "/tmp/repo" }),
|
||||||
|
"org-1",
|
||||||
|
);
|
||||||
|
|
||||||
expect(env.PATH).toBeTruthy();
|
expect(env.PATH).toBeTruthy();
|
||||||
});
|
});
|
||||||
|
|
@ -77,7 +65,12 @@ describe("buildEnv", () => {
|
||||||
describe("password resolution", () => {
|
describe("password resolution", () => {
|
||||||
test("writes a password file when using customPassword on an existing repository", async () => {
|
test("writes a password file when using customPassword on an existing repository", async () => {
|
||||||
const env = await buildEnvForTest(
|
const env = await buildEnvForTest(
|
||||||
{ backend: "local" as const, path: "/tmp/repo", isExistingRepository: true, customPassword: "my-secret" },
|
{
|
||||||
|
backend: "local" as const,
|
||||||
|
path: "/tmp/repo",
|
||||||
|
isExistingRepository: true,
|
||||||
|
customPassword: "my-secret",
|
||||||
|
},
|
||||||
"org-1",
|
"org-1",
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|
@ -127,9 +120,9 @@ describe("buildEnv", () => {
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
await expect(buildEnv({ backend: "local" as const, path: "/tmp/repo" }, "org-no-pass", deps)).rejects.toThrow(
|
await expect(
|
||||||
"Restic password not configured for organization org-no-pass",
|
buildEnv({ backend: "local" as const, path: "/tmp/repo" }, "org-no-pass", deps),
|
||||||
);
|
).rejects.toThrow("Restic password not configured for organization org-no-pass");
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
@ -150,7 +143,10 @@ describe("buildEnv", () => {
|
||||||
});
|
});
|
||||||
|
|
||||||
test("sets AWS_S3_BUCKET_LOOKUP=dns for Huawei Cloud endpoints", async () => {
|
test("sets AWS_S3_BUCKET_LOOKUP=dns for Huawei Cloud endpoints", async () => {
|
||||||
const env = await buildEnvForTest({ ...base, endpoint: "https://obs.ap-southeast-1.myhuaweicloud.com" }, "org-1");
|
const env = await buildEnvForTest(
|
||||||
|
{ ...base, endpoint: "https://obs.ap-southeast-1.myhuaweicloud.com" },
|
||||||
|
"org-1",
|
||||||
|
);
|
||||||
|
|
||||||
expect(env.AWS_S3_BUCKET_LOOKUP).toBe("dns");
|
expect(env.AWS_S3_BUCKET_LOOKUP).toBe("dns");
|
||||||
});
|
});
|
||||||
|
|
@ -296,7 +292,8 @@ describe("buildEnv", () => {
|
||||||
buildEnv(
|
buildEnv(
|
||||||
{
|
{
|
||||||
...baseSftpConfig,
|
...baseSftpConfig,
|
||||||
privateKey: "-----BEGIN RSA PRIVATE KEY-----\nProc-Type: 4,ENCRYPTED\n-----END RSA PRIVATE KEY-----",
|
privateKey:
|
||||||
|
"-----BEGIN RSA PRIVATE KEY-----\nProc-Type: 4,ENCRYPTED\n-----END RSA PRIVATE KEY-----",
|
||||||
},
|
},
|
||||||
"org-1",
|
"org-1",
|
||||||
deps,
|
deps,
|
||||||
|
|
@ -319,7 +316,10 @@ describe("buildEnv", () => {
|
||||||
});
|
});
|
||||||
|
|
||||||
test("uses StrictHostKeyChecking=yes when knownHosts is absent", async () => {
|
test("uses StrictHostKeyChecking=yes when knownHosts is absent", async () => {
|
||||||
const env = await buildEnvForTest({ ...baseSftpConfig, skipHostKeyCheck: false, knownHosts: undefined }, "org-1");
|
const env = await buildEnvForTest(
|
||||||
|
{ ...baseSftpConfig, skipHostKeyCheck: false, knownHosts: undefined },
|
||||||
|
"org-1",
|
||||||
|
);
|
||||||
|
|
||||||
expect(env._SFTP_SSH_ARGS).toContain("StrictHostKeyChecking=yes");
|
expect(env._SFTP_SSH_ARGS).toContain("StrictHostKeyChecking=yes");
|
||||||
expect(env._SFTP_SSH_ARGS).not.toContain("UserKnownHostsFile=/dev/null");
|
expect(env._SFTP_SSH_ARGS).not.toContain("UserKnownHostsFile=/dev/null");
|
||||||
|
|
@ -336,8 +336,8 @@ describe("buildEnv", () => {
|
||||||
);
|
);
|
||||||
|
|
||||||
expect(env._SFTP_SSH_ARGS).toContain("StrictHostKeyChecking=yes");
|
expect(env._SFTP_SSH_ARGS).toContain("StrictHostKeyChecking=yes");
|
||||||
expect(env._SFTP_SSH_ARGS).toContain("UserKnownHostsFile=/tmp/zerobyte-known-hosts-");
|
expect(env._SFTP_SSH_ARGS).toContain("/zerobyte-known-hosts-");
|
||||||
expect(env._SFTP_KNOWN_HOSTS_PATH).toMatch(/^\/tmp\/zerobyte-known-hosts-/);
|
expect(env._SFTP_KNOWN_HOSTS_PATH).toMatch(/\/zerobyte-restic-[^/]+\/zerobyte-known-hosts-/);
|
||||||
});
|
});
|
||||||
|
|
||||||
test("adds -p flag for non-default ports", async () => {
|
test("adds -p flag for non-default ports", async () => {
|
||||||
|
|
@ -355,7 +355,7 @@ describe("buildEnv", () => {
|
||||||
test("sets the key path in both _SFTP_KEY_PATH and SSH args -i flag", async () => {
|
test("sets the key path in both _SFTP_KEY_PATH and SSH args -i flag", async () => {
|
||||||
const env = await buildEnvForTest(baseSftpConfig, "org-1");
|
const env = await buildEnvForTest(baseSftpConfig, "org-1");
|
||||||
|
|
||||||
expect(env._SFTP_KEY_PATH).toMatch(/^\/tmp\/zerobyte-ssh-/);
|
expect(env._SFTP_KEY_PATH).toMatch(/\/zerobyte-restic-[^/]+\/zerobyte-ssh-/);
|
||||||
expect(env._SFTP_SSH_ARGS).toContain(`-i ${env._SFTP_KEY_PATH}`);
|
expect(env._SFTP_SSH_ARGS).toContain(`-i ${env._SFTP_KEY_PATH}`);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
@ -382,7 +382,10 @@ describe("buildEnv", () => {
|
||||||
});
|
});
|
||||||
|
|
||||||
test("does not set RESTIC_CACERT when cacert is absent", async () => {
|
test("does not set RESTIC_CACERT when cacert is absent", async () => {
|
||||||
const env = await buildEnvForTest(withCustomPassword({ backend: "local" as const, path: "/tmp/repo" }), "org-1");
|
const env = await buildEnvForTest(
|
||||||
|
withCustomPassword({ backend: "local" as const, path: "/tmp/repo" }),
|
||||||
|
"org-1",
|
||||||
|
);
|
||||||
|
|
||||||
expect(env.RESTIC_CACERT).toBeUndefined();
|
expect(env.RESTIC_CACERT).toBeUndefined();
|
||||||
});
|
});
|
||||||
|
|
@ -399,7 +402,10 @@ describe("buildEnv", () => {
|
||||||
});
|
});
|
||||||
|
|
||||||
test("does not set _INSECURE_TLS when insecureTls is absent", async () => {
|
test("does not set _INSECURE_TLS when insecureTls is absent", async () => {
|
||||||
const env = await buildEnvForTest(withCustomPassword({ backend: "local" as const, path: "/tmp/repo" }), "org-1");
|
const env = await buildEnvForTest(
|
||||||
|
withCustomPassword({ backend: "local" as const, path: "/tmp/repo" }),
|
||||||
|
"org-1",
|
||||||
|
);
|
||||||
|
|
||||||
expect(env._INSECURE_TLS).toBeUndefined();
|
expect(env._INSECURE_TLS).toBeUndefined();
|
||||||
});
|
});
|
||||||
|
|
|
||||||
|
|
@ -1,10 +1,22 @@
|
||||||
import crypto from "node:crypto";
|
import crypto from "node:crypto";
|
||||||
|
import fs from "node:fs/promises";
|
||||||
|
import os from "node:os";
|
||||||
import path from "node:path";
|
import path from "node:path";
|
||||||
import type { ResticDeps, ResticEnv } from "../types";
|
import type { ResticDeps, ResticEnv } from "../types";
|
||||||
import type { RepositoryConfig } from "../schemas";
|
import type { RepositoryConfig } from "../schemas";
|
||||||
import { logger } from "../../node";
|
import { logger } from "../../node";
|
||||||
import { FILE_MODES, writeFileWithMode } from "../../node/fs.js";
|
import { FILE_MODES, writeFileWithMode } from "../../node/fs.js";
|
||||||
|
|
||||||
|
const createRuntimeSecretsDir = async () => {
|
||||||
|
const runtimeSecretsDir = await fs.mkdtemp(path.join(os.tmpdir(), "zerobyte-restic-"));
|
||||||
|
await fs.chmod(runtimeSecretsDir, 0o700);
|
||||||
|
return runtimeSecretsDir;
|
||||||
|
};
|
||||||
|
|
||||||
|
const runtimeSecretPath = (runtimeSecretsDir: string, filename: string) => {
|
||||||
|
return path.join(runtimeSecretsDir, filename);
|
||||||
|
};
|
||||||
|
|
||||||
export const buildEnv = async (
|
export const buildEnv = async (
|
||||||
config: RepositoryConfig,
|
config: RepositoryConfig,
|
||||||
organizationId: string,
|
organizationId: string,
|
||||||
|
|
@ -14,17 +26,25 @@ export const buildEnv = async (
|
||||||
RESTIC_CACHE_DIR: deps.resticCacheDir,
|
RESTIC_CACHE_DIR: deps.resticCacheDir,
|
||||||
PATH: process.env.PATH || "/usr/local/bin:/usr/bin:/bin",
|
PATH: process.env.PATH || "/usr/local/bin:/usr/bin:/bin",
|
||||||
};
|
};
|
||||||
|
const runtimeSecretsDir = await createRuntimeSecretsDir();
|
||||||
|
env._RUNTIME_SECRETS_DIR = runtimeSecretsDir;
|
||||||
|
|
||||||
if (config.isExistingRepository && config.customPassword) {
|
if (config.isExistingRepository && config.customPassword) {
|
||||||
const decryptedPassword = await deps.resolveSecret(config.customPassword);
|
const decryptedPassword = await deps.resolveSecret(config.customPassword);
|
||||||
const passwordFilePath = path.join("/tmp", `zerobyte-pass-${crypto.randomBytes(8).toString("hex")}.txt`);
|
const passwordFilePath = runtimeSecretPath(
|
||||||
|
runtimeSecretsDir,
|
||||||
|
`zerobyte-pass-${crypto.randomBytes(8).toString("hex")}.txt`,
|
||||||
|
);
|
||||||
|
|
||||||
await writeFileWithMode(passwordFilePath, decryptedPassword, FILE_MODES.ownerReadWrite);
|
await writeFileWithMode(passwordFilePath, decryptedPassword, FILE_MODES.ownerReadWrite);
|
||||||
env.RESTIC_PASSWORD_FILE = passwordFilePath;
|
env.RESTIC_PASSWORD_FILE = passwordFilePath;
|
||||||
} else {
|
} else {
|
||||||
const encryptedPassword = await deps.getOrganizationResticPassword(organizationId);
|
const encryptedPassword = await deps.getOrganizationResticPassword(organizationId);
|
||||||
const decryptedPassword = await deps.resolveSecret(encryptedPassword);
|
const decryptedPassword = await deps.resolveSecret(encryptedPassword);
|
||||||
const passwordFilePath = path.join("/tmp", `zerobyte-pass-${crypto.randomBytes(8).toString("hex")}.txt`);
|
const passwordFilePath = runtimeSecretPath(
|
||||||
|
runtimeSecretsDir,
|
||||||
|
`zerobyte-pass-${crypto.randomBytes(8).toString("hex")}.txt`,
|
||||||
|
);
|
||||||
await writeFileWithMode(passwordFilePath, decryptedPassword, FILE_MODES.ownerReadWrite);
|
await writeFileWithMode(passwordFilePath, decryptedPassword, FILE_MODES.ownerReadWrite);
|
||||||
env.RESTIC_PASSWORD_FILE = passwordFilePath;
|
env.RESTIC_PASSWORD_FILE = passwordFilePath;
|
||||||
}
|
}
|
||||||
|
|
@ -46,7 +66,10 @@ export const buildEnv = async (
|
||||||
break;
|
break;
|
||||||
case "gcs": {
|
case "gcs": {
|
||||||
const decryptedCredentials = await deps.resolveSecret(config.credentialsJson);
|
const decryptedCredentials = await deps.resolveSecret(config.credentialsJson);
|
||||||
const credentialsPath = path.join("/tmp", `zerobyte-gcs-${crypto.randomBytes(8).toString("hex")}.json`);
|
const credentialsPath = runtimeSecretPath(
|
||||||
|
runtimeSecretsDir,
|
||||||
|
`zerobyte-gcs-${crypto.randomBytes(8).toString("hex")}.json`,
|
||||||
|
);
|
||||||
await writeFileWithMode(credentialsPath, decryptedCredentials, FILE_MODES.ownerReadWrite);
|
await writeFileWithMode(credentialsPath, decryptedCredentials, FILE_MODES.ownerReadWrite);
|
||||||
env.GOOGLE_PROJECT_ID = config.projectId;
|
env.GOOGLE_PROJECT_ID = config.projectId;
|
||||||
env.GOOGLE_APPLICATION_CREDENTIALS = credentialsPath;
|
env.GOOGLE_APPLICATION_CREDENTIALS = credentialsPath;
|
||||||
|
|
@ -74,7 +97,10 @@ export const buildEnv = async (
|
||||||
break;
|
break;
|
||||||
case "sftp": {
|
case "sftp": {
|
||||||
const decryptedKey = await deps.resolveSecret(config.privateKey);
|
const decryptedKey = await deps.resolveSecret(config.privateKey);
|
||||||
const keyPath = path.join("/tmp", `zerobyte-ssh-${crypto.randomBytes(8).toString("hex")}`);
|
const keyPath = runtimeSecretPath(
|
||||||
|
runtimeSecretsDir,
|
||||||
|
`zerobyte-ssh-${crypto.randomBytes(8).toString("hex")}`,
|
||||||
|
);
|
||||||
|
|
||||||
let normalizedKey = decryptedKey.replace(/\r\n/g, "\n");
|
let normalizedKey = decryptedKey.replace(/\r\n/g, "\n");
|
||||||
if (!normalizedKey.endsWith("\n")) {
|
if (!normalizedKey.endsWith("\n")) {
|
||||||
|
|
@ -83,7 +109,9 @@ export const buildEnv = async (
|
||||||
|
|
||||||
if (normalizedKey.includes("ENCRYPTED")) {
|
if (normalizedKey.includes("ENCRYPTED")) {
|
||||||
logger.error("SFTP: Private key appears to be passphrase-protected. Please use an unencrypted key.");
|
logger.error("SFTP: Private key appears to be passphrase-protected. Please use an unencrypted key.");
|
||||||
throw new Error("Passphrase-protected SSH keys are not supported. Please provide an unencrypted private key.");
|
throw new Error(
|
||||||
|
"Passphrase-protected SSH keys are not supported. Please provide an unencrypted private key.",
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
await writeFileWithMode(keyPath, normalizedKey, FILE_MODES.ownerReadWrite);
|
await writeFileWithMode(keyPath, normalizedKey, FILE_MODES.ownerReadWrite);
|
||||||
|
|
@ -120,7 +148,10 @@ export const buildEnv = async (
|
||||||
if (config.skipHostKeyCheck) {
|
if (config.skipHostKeyCheck) {
|
||||||
sshArgs.push("-o", "StrictHostKeyChecking=no", "-o", "UserKnownHostsFile=/dev/null");
|
sshArgs.push("-o", "StrictHostKeyChecking=no", "-o", "UserKnownHostsFile=/dev/null");
|
||||||
} else if (config.knownHosts) {
|
} else if (config.knownHosts) {
|
||||||
const knownHostsPath = path.join("/tmp", `zerobyte-known-hosts-${crypto.randomBytes(8).toString("hex")}`);
|
const knownHostsPath = runtimeSecretPath(
|
||||||
|
runtimeSecretsDir,
|
||||||
|
`zerobyte-known-hosts-${crypto.randomBytes(8).toString("hex")}`,
|
||||||
|
);
|
||||||
await writeFileWithMode(knownHostsPath, config.knownHosts, FILE_MODES.ownerReadWrite);
|
await writeFileWithMode(knownHostsPath, config.knownHosts, FILE_MODES.ownerReadWrite);
|
||||||
env._SFTP_KNOWN_HOSTS_PATH = knownHostsPath;
|
env._SFTP_KNOWN_HOSTS_PATH = knownHostsPath;
|
||||||
sshArgs.push("-o", "StrictHostKeyChecking=yes", "-o", `UserKnownHostsFile=${knownHostsPath}`);
|
sshArgs.push("-o", "StrictHostKeyChecking=yes", "-o", `UserKnownHostsFile=${knownHostsPath}`);
|
||||||
|
|
@ -140,7 +171,10 @@ export const buildEnv = async (
|
||||||
|
|
||||||
if (config.cacert) {
|
if (config.cacert) {
|
||||||
const decryptedCert = await deps.resolveSecret(config.cacert);
|
const decryptedCert = await deps.resolveSecret(config.cacert);
|
||||||
const certPath = path.join("/tmp", `zerobyte-cacert-${crypto.randomBytes(8).toString("hex")}.pem`);
|
const certPath = runtimeSecretPath(
|
||||||
|
runtimeSecretsDir,
|
||||||
|
`zerobyte-cacert-${crypto.randomBytes(8).toString("hex")}.pem`,
|
||||||
|
);
|
||||||
await writeFileWithMode(certPath, decryptedCert, FILE_MODES.ownerReadWrite);
|
await writeFileWithMode(certPath, decryptedCert, FILE_MODES.ownerReadWrite);
|
||||||
env.RESTIC_CACERT = certPath;
|
env.RESTIC_CACERT = certPath;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -13,4 +13,8 @@ export const cleanupTemporaryKeys = async (env: ResticEnv, deps: ResticDeps) =>
|
||||||
if (env.RESTIC_PASSWORD_FILE && env.RESTIC_PASSWORD_FILE !== deps.resticPassFile) {
|
if (env.RESTIC_PASSWORD_FILE && env.RESTIC_PASSWORD_FILE !== deps.resticPassFile) {
|
||||||
await fs.unlink(env.RESTIC_PASSWORD_FILE).catch(() => {});
|
await fs.unlink(env.RESTIC_PASSWORD_FILE).catch(() => {});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (env._RUNTIME_SECRETS_DIR) {
|
||||||
|
await fs.rm(env._RUNTIME_SECRETS_DIR, { recursive: true, force: true }).catch(() => {});
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue