fix: force user to provide known hosts values in sftp volume / repository creation
Some checks failed
Release Workflow / determine-release-type (push) Has been cancelled
Release Workflow / checks (push) Has been cancelled
Release Workflow / e2e-tests (push) Has been cancelled
Release Workflow / build-images (push) Has been cancelled
Release Workflow / publish-release (push) Has been cancelled
Some checks failed
Release Workflow / determine-release-type (push) Has been cancelled
Release Workflow / checks (push) Has been cancelled
Release Workflow / e2e-tests (push) Has been cancelled
Release Workflow / build-images (push) Has been cancelled
Release Workflow / publish-release (push) Has been cancelled
This commit is contained in:
parent
aa7eaa0929
commit
3acf565ccd
7 changed files with 49 additions and 25 deletions
|
|
@ -52,16 +52,26 @@ const formBaseFields = {
|
||||||
compressionMode: z.enum(COMPRESSION_MODES).optional(),
|
compressionMode: z.enum(COMPRESSION_MODES).optional(),
|
||||||
};
|
};
|
||||||
|
|
||||||
export const formSchema = z.discriminatedUnion("backend", [
|
export const formSchema = z
|
||||||
localRepositoryConfigSchema.extend(formBaseFields),
|
.discriminatedUnion("backend", [
|
||||||
s3RepositoryConfigSchema.extend(formBaseFields),
|
localRepositoryConfigSchema.extend(formBaseFields),
|
||||||
r2RepositoryConfigSchema.extend(formBaseFields),
|
s3RepositoryConfigSchema.extend(formBaseFields),
|
||||||
gcsRepositoryConfigSchema.extend(formBaseFields),
|
r2RepositoryConfigSchema.extend(formBaseFields),
|
||||||
azureRepositoryConfigSchema.extend(formBaseFields),
|
gcsRepositoryConfigSchema.extend(formBaseFields),
|
||||||
rcloneRepositoryConfigSchema.extend(formBaseFields),
|
azureRepositoryConfigSchema.extend(formBaseFields),
|
||||||
restRepositoryConfigSchema.extend(formBaseFields),
|
rcloneRepositoryConfigSchema.extend(formBaseFields),
|
||||||
sftpRepositoryConfigSchema.extend(formBaseFields),
|
restRepositoryConfigSchema.extend(formBaseFields),
|
||||||
]);
|
sftpRepositoryConfigSchema.extend(formBaseFields),
|
||||||
|
])
|
||||||
|
.superRefine((value, ctx) => {
|
||||||
|
if (value.backend === "sftp" && !value.skipHostKeyCheck && !value.knownHosts?.trim()) {
|
||||||
|
ctx.addIssue({
|
||||||
|
code: "custom",
|
||||||
|
message: "Known hosts are required unless host key verification is skipped",
|
||||||
|
path: ["knownHosts"],
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
export type RepositoryFormValues = z.input<typeof formSchema>;
|
export type RepositoryFormValues = z.input<typeof formSchema>;
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -32,14 +32,24 @@ import { useSystemInfo } from "~/client/hooks/use-system-info";
|
||||||
import { useScrollToFormError } from "~/client/hooks/use-scroll-to-form-error";
|
import { useScrollToFormError } from "~/client/hooks/use-scroll-to-form-error";
|
||||||
import { DirectoryForm, NFSForm, SMBForm, WebDAVForm, RcloneForm, SFTPForm } from "./volume-forms";
|
import { DirectoryForm, NFSForm, SMBForm, WebDAVForm, RcloneForm, SFTPForm } from "./volume-forms";
|
||||||
|
|
||||||
export const formSchema = z.discriminatedUnion("backend", [
|
export const formSchema = z
|
||||||
directoryConfigSchema.extend({ name: z.string().min(2).max(32) }),
|
.discriminatedUnion("backend", [
|
||||||
nfsConfigSchema.extend({ name: z.string().min(2).max(32) }),
|
directoryConfigSchema.extend({ name: z.string().min(2).max(32) }),
|
||||||
smbConfigSchema.extend({ name: z.string().min(2).max(32) }),
|
nfsConfigSchema.extend({ name: z.string().min(2).max(32) }),
|
||||||
webdavConfigSchema.extend({ name: z.string().min(2).max(32) }),
|
smbConfigSchema.extend({ name: z.string().min(2).max(32) }),
|
||||||
rcloneConfigSchema.extend({ name: z.string().min(2).max(32) }),
|
webdavConfigSchema.extend({ name: z.string().min(2).max(32) }),
|
||||||
sftpConfigSchema.extend({ name: z.string().min(2).max(32) }),
|
rcloneConfigSchema.extend({ name: z.string().min(2).max(32) }),
|
||||||
]);
|
sftpConfigSchema.extend({ name: z.string().min(2).max(32) }),
|
||||||
|
])
|
||||||
|
.superRefine((value, ctx) => {
|
||||||
|
if (value.backend === "sftp" && !value.skipHostKeyCheck && !value.knownHosts?.trim()) {
|
||||||
|
ctx.addIssue({
|
||||||
|
code: "custom",
|
||||||
|
message: "Known hosts are required unless host key verification is skipped",
|
||||||
|
path: ["knownHosts"],
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
export type FormValues = z.input<typeof formSchema>;
|
export type FormValues = z.input<typeof formSchema>;
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -82,7 +82,7 @@ export const sftpConfigSchema = z.object({
|
||||||
privateKey: z.string().optional(),
|
privateKey: z.string().optional(),
|
||||||
path: z.string().min(1),
|
path: z.string().min(1),
|
||||||
readOnly: z.boolean().optional(),
|
readOnly: z.boolean().optional(),
|
||||||
skipHostKeyCheck: z.boolean().default(true),
|
skipHostKeyCheck: z.boolean().default(false),
|
||||||
knownHosts: z.string().optional(),
|
knownHosts: z.string().optional(),
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -60,12 +60,14 @@ const mount = async (config: BackendConfig, mountPath: string) => {
|
||||||
`gid=${gid}`,
|
`gid=${gid}`,
|
||||||
];
|
];
|
||||||
|
|
||||||
if (config.skipHostKeyCheck || !config.knownHosts) {
|
if (config.skipHostKeyCheck) {
|
||||||
options.push("StrictHostKeyChecking=no", "UserKnownHostsFile=/dev/null");
|
options.push("StrictHostKeyChecking=no", "UserKnownHostsFile=/dev/null");
|
||||||
} else if (config.knownHosts) {
|
} else if (config.knownHosts) {
|
||||||
const knownHostsPath = getKnownHostsPath(mountPath);
|
const knownHostsPath = getKnownHostsPath(mountPath);
|
||||||
await writeFileWithMode(knownHostsPath, config.knownHosts, FILE_MODES.ownerReadWrite);
|
await writeFileWithMode(knownHostsPath, config.knownHosts, FILE_MODES.ownerReadWrite);
|
||||||
options.push(`UserKnownHostsFile=${knownHostsPath}`, "StrictHostKeyChecking=yes");
|
options.push(`UserKnownHostsFile=${knownHostsPath}`, "StrictHostKeyChecking=yes");
|
||||||
|
} else {
|
||||||
|
options.push("StrictHostKeyChecking=yes");
|
||||||
}
|
}
|
||||||
|
|
||||||
if (config.readOnly) {
|
if (config.readOnly) {
|
||||||
|
|
|
||||||
|
|
@ -317,11 +317,11 @@ describe("buildEnv", () => {
|
||||||
expect(env._SFTP_SSH_ARGS).toContain("UserKnownHostsFile=/dev/null");
|
expect(env._SFTP_SSH_ARGS).toContain("UserKnownHostsFile=/dev/null");
|
||||||
});
|
});
|
||||||
|
|
||||||
test("uses StrictHostKeyChecking=no 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=no");
|
expect(env._SFTP_SSH_ARGS).toContain("StrictHostKeyChecking=yes");
|
||||||
expect(env._SFTP_SSH_ARGS).toContain("UserKnownHostsFile=/dev/null");
|
expect(env._SFTP_SSH_ARGS).not.toContain("UserKnownHostsFile=/dev/null");
|
||||||
});
|
});
|
||||||
|
|
||||||
test("uses StrictHostKeyChecking=yes and a known hosts file when knownHosts is provided", async () => {
|
test("uses StrictHostKeyChecking=yes and a known hosts file when knownHosts is provided", async () => {
|
||||||
|
|
|
||||||
|
|
@ -114,13 +114,15 @@ export const buildEnv = async (
|
||||||
keyPath,
|
keyPath,
|
||||||
];
|
];
|
||||||
|
|
||||||
if (config.skipHostKeyCheck || !config.knownHosts) {
|
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 = path.join("/tmp", `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}`);
|
||||||
|
} else {
|
||||||
|
sshArgs.push("-o", "StrictHostKeyChecking=yes");
|
||||||
}
|
}
|
||||||
|
|
||||||
if (config.port && config.port !== 22) {
|
if (config.port && config.port !== 22) {
|
||||||
|
|
|
||||||
|
|
@ -116,7 +116,7 @@ export const sftpRepositoryConfigSchema = z
|
||||||
user: z.string().min(1),
|
user: z.string().min(1),
|
||||||
path: z.string().min(1),
|
path: z.string().min(1),
|
||||||
privateKey: z.string().min(1),
|
privateKey: z.string().min(1),
|
||||||
skipHostKeyCheck: z.boolean().default(true),
|
skipHostKeyCheck: z.boolean().default(false),
|
||||||
knownHosts: z.string().optional(),
|
knownHosts: z.string().optional(),
|
||||||
})
|
})
|
||||||
.extend(baseRepositoryConfigSchema.shape);
|
.extend(baseRepositoryConfigSchema.shape);
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue