misc: finalize KMIP management

This commit is contained in:
Sheen Capadngan
2025-02-18 02:22:59 +08:00
parent 603b740bbe
commit 943c2b0e69
20 changed files with 490 additions and 317 deletions
@@ -125,6 +125,9 @@ export const eventToNameMap: { [K in EventType]: string } = {
[EventType.GET_KMIP_CLIENT]: "Get KMIP client",
[EventType.GET_KMIP_CLIENTS]: "Get KMIP clients",
[EventType.CREATE_KMIP_CLIENT_CERTIFICATE]: "Create KMIP client certificate",
[EventType.SETUP_KMIP]: "Setup KMIP configuration",
[EventType.GET_KMIP]: "Get KMIP configuration",
[EventType.REGISTER_KMIP_SERVER]: "Register KMIP server",
[EventType.KMIP_OPERATION_CREATE]: "KMIP operation create",
[EventType.KMIP_OPERATION_GET]: "KMIP operation get",
[EventType.KMIP_OPERATION_DELETE]: "KMIP operation delete",
@@ -137,6 +137,9 @@ export enum EventType {
GET_KMIP_CLIENT = "get-kmip-client",
GET_KMIP_CLIENTS = "get-kmip-clients",
CREATE_KMIP_CLIENT_CERTIFICATE = "create-kmip-client-certificate",
SETUP_KMIP = "setup-kmip",
GET_KMIP = "get-kmip",
REGISTER_KMIP_SERVER = "register-kmip-server",
KMIP_OPERATION_CREATE = "kmip-operation-create",
KMIP_OPERATION_GET = "kmip-operation-get",
KMIP_OPERATION_DELETE = "kmip-operation-delete",
-10
View File
@@ -5,11 +5,9 @@ import { apiRequest } from "@app/config/request";
import { kmipKeys } from "./queries";
import {
KmipClientCertificate,
OrgKmipServerCert,
TCreateKmipClient,
TDeleteKmipClient,
TGenerateKmipClientCertificate,
TGenerateOrgKmipServerCertDTO,
TSetupOrgKmipDTO,
TUpdateKmipClient
} from "./types";
@@ -90,11 +88,3 @@ export const useSetupOrgKmip = (orgId: string) => {
}
});
};
export const useGenerateOrgKmipServerCert = () => {
return useMutation({
mutationFn: async (payload: TGenerateOrgKmipServerCertDTO) => {
return apiRequest.post<OrgKmipServerCert>("/api/v1/kmip/server-certificates", payload);
}
});
};
-7
View File
@@ -73,13 +73,6 @@ export type TSetupOrgKmipDTO = {
caKeyAlgorithm: CertKeyAlgorithm;
};
export type TGenerateOrgKmipServerCertDTO = {
commonName: string;
keyAlgorithm: CertKeyAlgorithm;
altNames: string;
ttl: string;
};
export type OrgKmipServerCert = {
serialNumber: string;
certificate: string;
@@ -46,4 +46,5 @@ export type SubscriptionPlan = {
pkiEst: boolean;
enforceMfa: boolean;
projectTemplates: boolean;
kmip: boolean;
};
@@ -107,7 +107,7 @@ const KmipClientCertificateForm = ({
</FormControl>
)}
/>
<div className="flex items-center">
<div className="mt-8 flex items-center">
<Button
className="mr-4"
size="sm"
@@ -13,6 +13,7 @@ import {
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
import { motion } from "framer-motion";
import { UpgradePlanModal } from "@app/components/license/UpgradePlanModal";
import { ProjectPermissionCan } from "@app/components/permissions";
import {
Button,
@@ -39,6 +40,7 @@ import {
ProjectPermissionKmipActions,
ProjectPermissionSub,
useProjectPermission,
useSubscription,
useWorkspace
} from "@app/context";
import { usePagination, usePopUp, useResetPageHelper } from "@app/hooks";
@@ -81,6 +83,7 @@ export const KmipClientTable = () => {
});
const { permission } = useProjectPermission();
const { subscription } = useSubscription();
const { kmipClients = [], totalCount = 0 } = data ?? {};
useResetPageHelper({
@@ -93,7 +96,8 @@ export const KmipClientTable = () => {
"upsertKmipClient",
"deleteKmipClient",
"generateKmipClientCert",
"displayKmipClientCert"
"displayKmipClientCert",
"upgradePlan"
] as const);
const handleSort = () => {
@@ -152,7 +156,14 @@ export const KmipClientTable = () => {
colorSchema="primary"
type="submit"
leftIcon={<FontAwesomeIcon icon={faPlus} />}
onClick={() => handlePopUpOpen("upsertKmipClient", null)}
onClick={() => {
if (subscription && !subscription.kmip) {
handlePopUpOpen("upgradePlan");
return;
}
handlePopUpOpen("upsertKmipClient", null);
}}
isDisabled={!isAllowed}
>
Add KMIP Client
@@ -202,7 +213,7 @@ export const KmipClientTable = () => {
<Tr className="group h-10 hover:bg-mineshaft-700" key={`st-v3-${id}`}>
<Td>{name}</Td>
<Td className="max-w-80 break-all">{description}</Td>
<Td className="max-w-40 break-all">{[permissions.join(", ")]}</Td>
<Td className="max-w-40">{[permissions.join(", ")]}</Td>
<Td className="flex justify-end">
<DropdownMenu>
<DropdownMenuTrigger asChild>
@@ -316,6 +327,11 @@ export const KmipClientTable = () => {
onOpenChange={(isOpen) => handlePopUpToggle("displayKmipClientCert", isOpen)}
certificate={popUp.displayKmipClientCert.data}
/>
<UpgradePlanModal
isOpen={popUp.upgradePlan.isOpen}
onOpenChange={(isOpen) => handlePopUpToggle("upgradePlan", isOpen)}
text="KMIP requires an enterprise plan."
/>
</div>
</motion.div>
);
@@ -4,12 +4,12 @@ import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
import { zodResolver } from "@hookform/resolvers/zod";
import { z } from "zod";
import { UpgradePlanModal } from "@app/components/license/UpgradePlanModal";
import { createNotification } from "@app/components/notifications";
import {
Button,
FormControl,
IconButton,
Input,
Modal,
ModalContent,
Select,
@@ -18,18 +18,13 @@ import {
TextArea,
Tooltip
} from "@app/components/v2";
import { useOrganization } from "@app/context";
import { useOrganization, useSubscription } from "@app/context";
import { downloadTxtFile } from "@app/helpers/download";
import { usePopUp, useTimedReset } from "@app/hooks";
import { certKeyAlgorithms } from "@app/hooks/api/certificates/constants";
import { CertKeyAlgorithm } from "@app/hooks/api/certificates/enums";
import {
useGenerateOrgKmipServerCert,
useGetOrgKmipConfig,
useSetupOrgKmip
} from "@app/hooks/api/kmip";
import { useGetOrgKmipConfig, useSetupOrgKmip } from "@app/hooks/api/kmip";
import { OrgKmipConfig } from "@app/hooks/api/kmip/types";
import { CertificateContent } from "@app/pages/cert-manager/CertificatesPage/components/CertificatesTab/components/CertificateContent";
const orgConfigFormSchema = z.object({
caKeyAlgorithm: z.nativeEnum(CertKeyAlgorithm)
@@ -45,8 +40,11 @@ const OrgConfigSection = ({
isKmipConfigLoading: boolean;
}) => {
const { popUp, handlePopUpToggle, handlePopUpClose, handlePopUpOpen } = usePopUp([
"configureKmip"
"configureKmip",
"upgradePlan"
] as const);
const { subscription } = useSubscription();
const {
handleSubmit,
control,
@@ -181,6 +179,11 @@ const OrgConfigSection = ({
<Button
className="mt-2"
onClick={() => {
if (subscription && !subscription.kmip) {
handlePopUpOpen("upgradePlan");
return;
}
handlePopUpOpen("configureKmip");
}}
>
@@ -244,178 +247,15 @@ const OrgConfigSection = ({
</form>
</ModalContent>
</Modal>
<UpgradePlanModal
isOpen={popUp.upgradePlan.isOpen}
onOpenChange={(isOpen) => handlePopUpToggle("upgradePlan", isOpen)}
text="KMIP requires an enterprise plan."
/>
</>
);
};
const orgServerCertFormSchema = z.object({
commonName: z.string(),
altNames: z.string(),
keyAlgorithm: z.nativeEnum(CertKeyAlgorithm),
ttl: z.string()
});
type TOrgServerCertForm = z.infer<typeof orgServerCertFormSchema>;
export const KmipServerConfigSection = () => {
const { popUp, handlePopUpToggle, handlePopUpClose, handlePopUpOpen } = usePopUp([
"configureKmipServerCert",
"showCertificate"
] as const);
const certificateData = popUp.showCertificate?.data as {
serialNumber: string;
certificate: string;
certificateChain: string;
privateKey: string;
};
const {
handleSubmit,
control,
formState: { isSubmitting }
} = useForm<TOrgServerCertForm>({
resolver: zodResolver(orgServerCertFormSchema)
});
const { mutateAsync: generateKmipServerCert } = useGenerateOrgKmipServerCert();
const onFormSubmit = async (formData: TOrgServerCertForm) => {
const { data: certificate } = await generateKmipServerCert(formData);
handlePopUpOpen("showCertificate", certificate);
createNotification({
type: "success",
text: "Successfully created KMIP server certificate"
});
handlePopUpClose("configureKmipServerCert");
};
return (
<div className="mt-8 flex flex-col justify-start">
<div className="text-lg">KMIP Server Certificate</div>
<div className="mt-2 max-w-lg text-sm text-mineshaft-400">
These certificates should be used to configure TLS for the KMIP servers.
</div>
<Button
className="mt-2 w-fit"
onClick={() => {
handlePopUpOpen("configureKmipServerCert");
}}
>
Generate KMIP server certificate
</Button>
<Modal
isOpen={popUp.configureKmipServerCert.isOpen}
onOpenChange={(state) => handlePopUpToggle("configureKmipServerCert", state)}
>
<ModalContent title="Configure KMIP for the organization">
<form onSubmit={handleSubmit(onFormSubmit)}>
<Controller
control={control}
defaultValue=""
name="commonName"
render={({ field, fieldState: { error } }) => (
<FormControl
label="Common Name (CN)"
isError={Boolean(error)}
errorText={error?.message}
isRequired
>
<Input {...field} placeholder="service.acme.com" />
</FormControl>
)}
/>
<Controller
control={control}
defaultValue=""
name="altNames"
render={({ field, fieldState: { error } }) => (
<FormControl
label="Alternative Names (SANs)"
isError={Boolean(error)}
errorText={error?.message}
>
<Input {...field} placeholder="app1.acme.com, app2.acme.com, ..." />
</FormControl>
)}
/>
<Controller
control={control}
name="ttl"
render={({ field, fieldState: { error } }) => (
<FormControl
label="TTL"
isError={Boolean(error)}
errorText={error?.message}
isRequired
>
<Input {...field} placeholder="2 days, 1d, 2h, 1y, ..." />
</FormControl>
)}
/>
<Controller
control={control}
name="keyAlgorithm"
defaultValue={CertKeyAlgorithm.RSA_2048}
render={({ field: { onChange, ...field }, fieldState: { error } }) => (
<FormControl
label="Key Algorithm"
errorText={error?.message}
isError={Boolean(error)}
helperText="This defines the key algorithm to use for signing the server certificate."
>
<Select
defaultValue={field.value}
{...field}
onValueChange={(e) => onChange(e)}
className="w-full"
>
{certKeyAlgorithms.map(({ label, value }) => (
<SelectItem value={String(value || "")} key={label}>
{label}
</SelectItem>
))}
</Select>
</FormControl>
)}
/>
<div className="mt-6 flex w-full gap-4">
<Button
className=""
size="sm"
type="submit"
isLoading={isSubmitting}
isDisabled={isSubmitting}
>
Continue
</Button>
<Button
className=""
size="sm"
variant="outline_bg"
type="button"
onClick={() => handlePopUpClose("configureKmipServerCert")}
>
Cancel
</Button>
</div>
</form>
</ModalContent>
</Modal>
<Modal
isOpen={popUp.showCertificate.isOpen}
onOpenChange={(state) => handlePopUpToggle("showCertificate", state)}
>
<ModalContent title="Configure KMIP for the organization">
<CertificateContent {...certificateData} />
</ModalContent>
</Modal>
</div>
);
};
export const KmipTab = () => {
const { currentOrg } = useOrganization();
const { data: kmipConfig, isPending } = useGetOrgKmipConfig(currentOrg.id);
@@ -423,7 +263,6 @@ export const KmipTab = () => {
return (
<div className="mb-6 rounded-lg border border-mineshaft-600 bg-mineshaft-900 p-4">
<OrgConfigSection kmipConfig={kmipConfig} isKmipConfigLoading={isPending} />
{kmipConfig && <KmipServerConfigSection />}
</div>
);
};