feat: ssh keys filesystel

This commit is contained in:
Lorenzo Migliorero
2024-07-25 20:16:49 +02:00
parent d243470029
commit 1f81ebd4fe
6 changed files with 56 additions and 26 deletions

View File

@@ -37,7 +37,7 @@ const GitProviderSchema = z.object({
}), }),
branch: z.string().min(1, "Branch required"), branch: z.string().min(1, "Branch required"),
buildPath: z.string().min(1, "Build Path required"), buildPath: z.string().min(1, "Build Path required"),
sshKey: z.string(), sshKey: z.string().optional(),
}); });
type GitProvider = z.infer<typeof GitProviderSchema>; type GitProvider = z.infer<typeof GitProviderSchema>;
@@ -63,7 +63,7 @@ export const SaveGitProvider = ({ applicationId }: Props) => {
branch: "", branch: "",
buildPath: "/", buildPath: "/",
repositoryURL: "", repositoryURL: "",
sshKey: "", sshKey: undefined,
}, },
resolver: zodResolver(GitProviderSchema), resolver: zodResolver(GitProviderSchema),
}); });
@@ -71,7 +71,7 @@ export const SaveGitProvider = ({ applicationId }: Props) => {
useEffect(() => { useEffect(() => {
if (data) { if (data) {
form.reset({ form.reset({
sshKey: data.customGitSSHKeyId || "", sshKey: data.customGitSSHKeyId || undefined,
branch: data.customGitBranch || "", branch: data.customGitBranch || "",
buildPath: data.customGitBuildPath || "/", buildPath: data.customGitBuildPath || "/",
repositoryURL: data.customGitUrl || "", repositoryURL: data.customGitUrl || "",
@@ -84,7 +84,7 @@ export const SaveGitProvider = ({ applicationId }: Props) => {
customGitBranch: values.branch, customGitBranch: values.branch,
customGitBuildPath: values.buildPath, customGitBuildPath: values.buildPath,
customGitUrl: values.repositoryURL, customGitUrl: values.repositoryURL,
customGitSSHKeyId: values.sshKey, customGitSSHKeyId: values.sshKey === "none" ? null : values.sshKey,
applicationId, applicationId,
}) })
.then(async () => { .then(async () => {
@@ -149,6 +149,7 @@ export const SaveGitProvider = ({ applicationId }: Props) => {
{sshKey.name} {sshKey.name}
</SelectItem> </SelectItem>
))} ))}
<SelectItem value="none">None</SelectItem>
<SelectLabel>Keys ({sshKeys?.length})</SelectLabel> <SelectLabel>Keys ({sshKeys?.length})</SelectLabel>
</SelectGroup> </SelectGroup>
<SelectSeparator /> <SelectSeparator />

View File

@@ -33,8 +33,8 @@ import {
} from "@/server/utils/filesystem/directory"; } from "@/server/utils/filesystem/directory";
import { import {
generateSSHKey, generateSSHKey,
readRSAFile, readSSHPublicKey,
removeRSAFiles, removeSSHKey,
} from "@/server/utils/filesystem/ssh"; } from "@/server/utils/filesystem/ssh";
import { import {
readConfig, readConfig,
@@ -130,7 +130,7 @@ export const applicationRouter = createTRPCRouter({
async () => await removeMonitoringDirectory(application?.appName), async () => await removeMonitoringDirectory(application?.appName),
async () => await removeTraefikConfig(application?.appName), async () => await removeTraefikConfig(application?.appName),
async () => await removeService(application?.appName), async () => await removeService(application?.appName),
async () => await removeRSAFiles(application?.appName), async () => await removeSSHKey(application?.appName),
]; ];
for (const operation of cleanupOperations) { for (const operation of cleanupOperations) {
@@ -248,7 +248,7 @@ export const applicationRouter = createTRPCRouter({
const application = await findApplicationById(input.applicationId); const application = await findApplicationById(input.applicationId);
try { try {
await generateSSHKey(application.appName); await generateSSHKey(application.appName);
const file = await readRSAFile(application.appName); const file = await readSSHPublicKey(application.appName);
// await updateApplication(input.applicationId, { // await updateApplication(input.applicationId, {
// customGitSSHKey: file, // customGitSSHKey: file,
@@ -261,7 +261,7 @@ export const applicationRouter = createTRPCRouter({
.input(apiFindOneApplication) .input(apiFindOneApplication)
.mutation(async ({ input }) => { .mutation(async ({ input }) => {
const application = await findApplicationById(input.applicationId); const application = await findApplicationById(input.applicationId);
await removeRSAFiles(application.appName); await removeSSHKey(application.appName);
// await updateApplication(input.applicationId, { // await updateApplication(input.applicationId, {
// customGitSSHKey: null, // customGitSSHKey: null,
// }); // });

View File

@@ -18,8 +18,8 @@ import { randomizeComposeFile } from "@/server/utils/docker/compose";
import { removeComposeDirectory } from "@/server/utils/filesystem/directory"; import { removeComposeDirectory } from "@/server/utils/filesystem/directory";
import { import {
generateSSHKey, generateSSHKey,
readRSAFile, readSSHPublicKey,
removeRSAFiles, removeSSHKey,
} from "@/server/utils/filesystem/ssh"; } from "@/server/utils/filesystem/ssh";
import { templates } from "@/templates/templates"; import { templates } from "@/templates/templates";
import type { TemplatesKeys } from "@/templates/types/templates-data.type"; import type { TemplatesKeys } from "@/templates/types/templates-data.type";
@@ -102,7 +102,7 @@ export const composeRouter = createTRPCRouter({
async () => await removeCompose(composeResult), async () => await removeCompose(composeResult),
async () => await removeDeploymentsByComposeId(composeResult), async () => await removeDeploymentsByComposeId(composeResult),
async () => await removeComposeDirectory(composeResult.appName), async () => await removeComposeDirectory(composeResult.appName),
async () => await removeRSAFiles(composeResult.appName), async () => await removeSSHKey(composeResult.appName),
]; ];
for (const operation of cleanupOperations) { for (const operation of cleanupOperations) {
@@ -187,7 +187,7 @@ export const composeRouter = createTRPCRouter({
const compose = await findComposeById(input.composeId); const compose = await findComposeById(input.composeId);
try { try {
await generateSSHKey(compose.appName); await generateSSHKey(compose.appName);
const file = await readRSAFile(compose.appName); const file = await readSSHPublicKey(compose.appName);
await updateCompose(input.composeId, { await updateCompose(input.composeId, {
customGitSSHKey: file, customGitSSHKey: file,
@@ -208,7 +208,7 @@ export const composeRouter = createTRPCRouter({
.input(apiFindCompose) .input(apiFindCompose)
.mutation(async ({ input }) => { .mutation(async ({ input }) => {
const compose = await findComposeById(input.composeId); const compose = await findComposeById(input.composeId);
await removeRSAFiles(compose.appName); await removeSSHKey(compose.appName);
await updateCompose(input.composeId, { await updateCompose(input.composeId, {
customGitSSHKey: null, customGitSSHKey: null,
}); });

View File

@@ -6,6 +6,7 @@ import {
type apiUpdateSshKey, type apiUpdateSshKey,
sshKeys, sshKeys,
} from "@/server/db/schema"; } from "@/server/db/schema";
import { removeSSHKey, saveSSHKey } from "@/server/utils/filesystem/ssh";
import { TRPCError } from "@trpc/server"; import { TRPCError } from "@trpc/server";
import { eq } from "drizzle-orm"; import { eq } from "drizzle-orm";
@@ -20,6 +21,11 @@ export const createSshKey = async ({
.returning() .returning()
.then((response) => response[0]) .then((response) => response[0])
.catch((e) => console.error(e)); .catch((e) => console.error(e));
if (sshKey) {
saveSSHKey(sshKey.sshKeyId, sshKey.publicKey, privateKey);
}
if (!sshKey) { if (!sshKey) {
throw new TRPCError({ throw new TRPCError({
code: "BAD_REQUEST", code: "BAD_REQUEST",
@@ -38,6 +44,8 @@ export const removeSSHKeyById = async (
.where(eq(sshKeys.sshKeyId, sshKeyId)) .where(eq(sshKeys.sshKeyId, sshKeyId))
.returning(); .returning();
removeSSHKey(sshKeyId);
return result[0]; return result[0];
}; };

View File

@@ -3,14 +3,35 @@ import * as path from "node:path";
import { SSH_PATH } from "@/server/constants"; import { SSH_PATH } from "@/server/constants";
import { spawnAsync } from "../process/spawnAsync"; import { spawnAsync } from "../process/spawnAsync";
export const generateSSHKey = async (appName: string) => { export const saveSSHKey = async (
id: string,
publicKey: string,
privateKey: string,
) => {
const applicationDirectory = SSH_PATH;
const privateKeyPath = path.join(applicationDirectory, `${id}_rsa`);
const publicKeyPath = path.join(applicationDirectory, `${id}_rsa.pub`);
const privateKeyStream = fs.createWriteStream(privateKeyPath, {
mode: 0o400,
});
privateKeyStream.write(privateKey);
privateKeyStream.end();
const publicKeyStream = fs.createWriteStream(publicKeyPath, { mode: 0o400 });
publicKeyStream.write(publicKey);
publicKeyStream.end();
};
export const generateSSHKey = async (id: string) => {
const applicationDirectory = SSH_PATH; const applicationDirectory = SSH_PATH;
if (!fs.existsSync(applicationDirectory)) { if (!fs.existsSync(applicationDirectory)) {
fs.mkdirSync(applicationDirectory, { recursive: true }); fs.mkdirSync(applicationDirectory, { recursive: true });
} }
const keyPath = path.join(applicationDirectory, `${appName}_rsa`); const keyPath = path.join(applicationDirectory, `${id}_rsa`);
if (fs.existsSync(`${keyPath}`)) { if (fs.existsSync(`${keyPath}`)) {
fs.unlinkSync(`${keyPath}`); fs.unlinkSync(`${keyPath}`);
@@ -37,12 +58,12 @@ export const generateSSHKey = async (appName: string) => {
throw error; throw error;
} }
}; };
export const readRSAFile = async (appName: string) => { export const readSSHPublicKey = async (id: string) => {
try { try {
if (!fs.existsSync(SSH_PATH)) { if (!fs.existsSync(SSH_PATH)) {
fs.mkdirSync(SSH_PATH, { recursive: true }); fs.mkdirSync(SSH_PATH, { recursive: true });
} }
const keyPath = path.join(SSH_PATH, `${appName}_rsa.pub`); const keyPath = path.join(SSH_PATH, `${id}_rsa.pub`);
const data = fs.readFileSync(keyPath, { encoding: "utf-8" }); const data = fs.readFileSync(keyPath, { encoding: "utf-8" });
return data; return data;
} catch (error) { } catch (error) {
@@ -50,10 +71,10 @@ export const readRSAFile = async (appName: string) => {
} }
}; };
export const removeRSAFiles = async (appName: string) => { export const removeSSHKey = async (id: string) => {
try { try {
const publicKeyPath = path.join(SSH_PATH, `${appName}_rsa.pub`); const publicKeyPath = path.join(SSH_PATH, `${id}_rsa.pub`);
const privateKeyPath = path.join(SSH_PATH, `${appName}_rsa`); const privateKeyPath = path.join(SSH_PATH, `${id}_rsa`);
await fs.promises.unlink(publicKeyPath); await fs.promises.unlink(publicKeyPath);
await fs.promises.unlink(privateKeyPath); await fs.promises.unlink(privateKeyPath);
} catch (error) { } catch (error) {

View File

@@ -11,12 +11,12 @@ export const cloneGitRepository = async (
appName: string; appName: string;
customGitUrl?: string | null; customGitUrl?: string | null;
customGitBranch?: string | null; customGitBranch?: string | null;
customGitSSHKey?: string | null; customGitSSHKeyId?: string | null;
}, },
logPath: string, logPath: string,
isCompose = false, isCompose = false,
) => { ) => {
const { appName, customGitUrl, customGitBranch, customGitSSHKey } = entity; const { appName, customGitUrl, customGitBranch, customGitSSHKeyId } = entity;
if (!customGitUrl || !customGitBranch) { if (!customGitUrl || !customGitBranch) {
throw new TRPCError({ throw new TRPCError({
@@ -26,7 +26,7 @@ export const cloneGitRepository = async (
} }
const writeStream = createWriteStream(logPath, { flags: "a" }); const writeStream = createWriteStream(logPath, { flags: "a" });
const keyPath = path.join(SSH_PATH, `${appName}_rsa`); const keyPath = path.join(SSH_PATH, `${customGitSSHKeyId}_rsa`);
const basePath = isCompose ? COMPOSE_PATH : APPLICATIONS_PATH; const basePath = isCompose ? COMPOSE_PATH : APPLICATIONS_PATH;
const outputPath = join(basePath, appName, "code"); const outputPath = join(basePath, appName, "code");
const knownHostsPath = path.join(SSH_PATH, "known_hosts"); const knownHostsPath = path.join(SSH_PATH, "known_hosts");
@@ -39,7 +39,7 @@ export const cloneGitRepository = async (
writeStream.write( writeStream.write(
`\nCloning Repo Custom ${customGitUrl} to ${outputPath}: ✅\n`, `\nCloning Repo Custom ${customGitUrl} to ${outputPath}: ✅\n`,
); );
console.log(customGitSSHKeyId);
await spawnAsync( await spawnAsync(
"git", "git",
[ [
@@ -60,7 +60,7 @@ export const cloneGitRepository = async (
{ {
env: { env: {
...process.env, ...process.env,
...(customGitSSHKey && { ...(customGitSSHKeyId && {
GIT_SSH_COMMAND: `ssh -i ${keyPath} -o UserKnownHostsFile=${knownHostsPath}`, GIT_SSH_COMMAND: `ssh -i ${keyPath} -o UserKnownHostsFile=${knownHostsPath}`,
}), }),
}, },