misc: developed create kms backup feature

This commit is contained in:
Sheen Capadngan
2024-07-30 23:03:53 +05:30
committed by =
parent 4d032cfbfa
commit 80be054425
5 changed files with 127 additions and 6 deletions
@@ -254,4 +254,36 @@ export const registerProjectRouter = async (server: FastifyZodProvider) => {
}; };
} }
}); });
server.route({
method: "GET",
url: "/:workspaceId/kms/backup",
config: {
rateLimit: readLimit
},
schema: {
params: z.object({
workspaceId: z.string().trim()
}),
response: {
200: z.object({
secretManager: z.string()
})
}
},
onRequest: verifyAuth([AuthMode.JWT]),
handler: async (req) => {
const backup = await server.services.project.getProjectKmsBackup({
actor: req.permission.type,
actorId: req.permission.id,
actorAuthMethod: req.permission.authMethod,
actorOrgId: req.permission.orgId,
projectId: req.params.workspaceId
});
// TODO: add audit log
return backup;
}
});
}; };
+25 -1
View File
@@ -585,6 +585,29 @@ export const kmsServiceFactory = ({
}); });
}; };
const getProjectKeyBackup = async (projectId: string) => {
const project = await projectDAL.findById(projectId);
if (!project) {
throw new NotFoundError({
message: "Project not found"
});
}
const secretManagerDataKey = await getProjectSecretManagerKmsDataKey(projectId);
const kmsKeyIdForEncrypt = await getOrgKmsKeyId(project.orgId);
const kmsEncryptor = await encryptWithKmsKey({ kmsId: kmsKeyIdForEncrypt });
const { cipherTextBlob: encryptedSecretManagerDataKey } = await kmsEncryptor({ plainText: secretManagerDataKey });
// format: projectId.kmsFunction.kmsId.Base64(encryptedDataKey)
const secretManagerBackup = `${projectId}.secretManager.${kmsKeyIdForEncrypt}.${encryptedSecretManagerDataKey.toString(
"base64"
)}`;
return {
secretManager: secretManagerBackup
};
};
const startService = async () => { const startService = async () => {
const appCfg = getConfig(); const appCfg = getConfig();
// This will switch to a seal process and HMS flow in future // This will switch to a seal process and HMS flow in future
@@ -642,6 +665,7 @@ export const kmsServiceFactory = ({
getOrgKmsDataKey, getOrgKmsDataKey,
getProjectSecretManagerKmsDataKey, getProjectSecretManagerKmsDataKey,
getProjectSecretManagerKmsKey, getProjectSecretManagerKmsKey,
updateProjectSecretManagerKmsKey updateProjectSecretManagerKmsKey,
getProjectKeyBackup
}; };
}; };
@@ -78,7 +78,7 @@ type TProjectServiceFactoryDep = {
licenseService: Pick<TLicenseServiceFactory, "getPlan">; licenseService: Pick<TLicenseServiceFactory, "getPlan">;
orgDAL: Pick<TOrgDALFactory, "findOne">; orgDAL: Pick<TOrgDALFactory, "findOne">;
keyStore: Pick<TKeyStoreFactory, "deleteItem">; keyStore: Pick<TKeyStoreFactory, "deleteItem">;
kmsService: Pick<TKmsServiceFactory, "updateProjectSecretManagerKmsKey">; kmsService: Pick<TKmsServiceFactory, "updateProjectSecretManagerKmsKey" | "getProjectKeyBackup">;
}; };
export type TProjectServiceFactory = ReturnType<typeof projectServiceFactory>; export type TProjectServiceFactory = ReturnType<typeof projectServiceFactory>;
@@ -693,6 +693,34 @@ export const projectServiceFactory = ({
}; };
}; };
const getProjectKmsBackup = async ({
projectId,
actor,
actorId,
actorAuthMethod,
actorOrgId
}: TProjectPermission) => {
const { permission } = await permissionService.getProjectPermission(
actor,
actorId,
projectId,
actorAuthMethod,
actorOrgId
);
ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionActions.Edit, ProjectPermissionSub.Kms);
const plan = await licenseService.getPlan(actorOrgId);
if (!plan.externalKms) {
throw new BadRequestError({
message: "Failed to create KMS backup due to plan restriction. Upgrade to the enterprise plan."
});
}
const kmsBackup = await kmsService.getProjectKeyBackup(projectId);
return kmsBackup;
};
return { return {
createProject, createProject,
deleteProject, deleteProject,
@@ -707,6 +735,7 @@ export const projectServiceFactory = ({
listProjectCertificates, listProjectCertificates,
updateVersionLimit, updateVersionLimit,
updateAuditLogsRetention, updateAuditLogsRetention,
updateProjectKmsKey updateProjectKmsKey,
getProjectKmsBackup
}; };
}; };
+8
View File
@@ -53,3 +53,11 @@ export const useGetActiveProjectKms = (projectId: string) => {
} }
}); });
}; };
export const fetchProjectKmsBackup = async (projectId: string) => {
const { data } = await apiRequest.get<{
secretManager: string;
}>(`/api/v1/workspace/${projectId}/kms/backup`);
return data;
};
@@ -1,6 +1,7 @@
import { useEffect, useState } from "react"; import { useEffect, useState } from "react";
import { Controller, useForm } from "react-hook-form"; import { Controller, useForm } from "react-hook-form";
import { zodResolver } from "@hookform/resolvers/zod"; import { zodResolver } from "@hookform/resolvers/zod";
import FileSaver from "file-saver";
import { z } from "zod"; import { z } from "zod";
import { createNotification } from "@app/components/notifications"; import { createNotification } from "@app/components/notifications";
@@ -13,6 +14,7 @@ import {
useWorkspace useWorkspace
} from "@app/context"; } from "@app/context";
import { useGetActiveProjectKms, useGetExternalKmsList, useUpdateProjectKms } from "@app/hooks/api"; import { useGetActiveProjectKms, useGetExternalKmsList, useUpdateProjectKms } from "@app/hooks/api";
import { fetchProjectKmsBackup } from "@app/hooks/api/kms/queries";
const formSchema = z.object({ const formSchema = z.object({
kmsKeyId: z.string() kmsKeyId: z.string()
@@ -55,7 +57,7 @@ export const EncryptionTab = () => {
} }
}, [kmsKeyId]); }, [kmsKeyId]);
const onFormSubmit = async (data: TForm) => { const onUpdateProjectKms = async (data: TForm) => {
try { try {
await updateProjectKms({ await updateProjectKms({
secretManagerKmsKeyId: data.kmsKeyId secretManagerKmsKeyId: data.kmsKeyId
@@ -70,12 +72,38 @@ export const EncryptionTab = () => {
} }
}; };
const downloadKmsBackup = async () => {
if (!currentWorkspace || !currentOrg) {
return;
}
const { secretManager } = await fetchProjectKmsBackup(currentWorkspace.id);
const [, kmsFunction] = secretManager.split(".");
const file = secretManager;
const blob = new Blob([file], { type: "text/plain;charset=utf-8" });
FileSaver.saveAs(
blob,
`kms-backup-${currentOrg.slug}-${currentWorkspace.slug}-${kmsFunction}.infisical.txt`
);
};
return ( return (
<form <form
onSubmit={handleSubmit(onFormSubmit)} onSubmit={handleSubmit(onUpdateProjectKms)}
className="mb-6 rounded-lg border border-mineshaft-600 bg-mineshaft-900 p-4" className="mb-6 rounded-lg border border-mineshaft-600 bg-mineshaft-900 p-4"
> >
<div className="flex justify-between">
<h2 className="mb-2 flex-1 text-xl font-semibold text-mineshaft-100">Key Management</h2> <h2 className="mb-2 flex-1 text-xl font-semibold text-mineshaft-100">Key Management</h2>
<div className="space-x-2">
<Button colorSchema="secondary">Load Backup</Button>
<Button colorSchema="secondary" onClick={downloadKmsBackup}>
Create Backup
</Button>
</div>
</div>
<p className="mb-4 text-gray-400"> <p className="mb-4 text-gray-400">
Select which Key Management System to use for encrypting your project data Select which Key Management System to use for encrypting your project data
</p> </p>