feat: finished up client cert generation

This commit is contained in:
Sheen Capadngan
2025-02-07 01:24:32 +08:00
parent 0269f57768
commit a3acfa65a2
21 changed files with 593 additions and 14 deletions
@@ -28,7 +28,8 @@ export enum ProjectPermissionKmipActions {
CreateClients = "create-clients",
UpdateClients = "update-clients",
DeleteClients = "delete-clients",
ReadClients = "read-clients"
ReadClients = "read-clients",
GenerateClientCertificates = "generate-client-certificates"
}
export enum ProjectPermissionSecretSyncActions {
+20 -1
View File
@@ -3,7 +3,13 @@ import { useMutation, useQueryClient } from "@tanstack/react-query";
import { apiRequest } from "@app/config/request";
import { kmipKeys } from "./queries";
import { TCreateKmipClient, TDeleteKmipClient, TUpdateKmipClient } from "./types";
import {
KmipClientCertificate,
TCreateKmipClient,
TDeleteKmipClient,
TGenerateKmipClientCertificate,
TUpdateKmipClient
} from "./types";
export const useCreateKmipClient = () => {
const queryClient = useQueryClient();
@@ -56,3 +62,16 @@ export const useDeleteKmipClients = () => {
}
});
};
export const useGenerateKmipClientCertificate = () => {
return useMutation({
mutationFn: async (payload: TGenerateKmipClientCertificate) => {
const { data } = await apiRequest.post<KmipClientCertificate>(
`/api/v1/kmip/clients/${payload.clientId}/certificates`,
payload
);
return data;
}
});
};
+14
View File
@@ -1,3 +1,4 @@
import { CertKeyAlgorithm } from "../certificates/enums";
import { OrderByDirection } from "../generic/types";
export enum KmipPermission {
@@ -30,6 +31,19 @@ export type TProjectKmipClientList = {
totalCount: number;
};
export type TGenerateKmipClientCertificate = {
keyAlgorithm: CertKeyAlgorithm;
ttl: string;
clientId: string;
};
export type KmipClientCertificate = {
serialNumber: string;
certificate: string;
certificateChain: string;
privateKey: string;
};
export type TDeleteKmipClient = KeyRef & ProjectRef;
export type TListProjectKmipClientsDTO = {
@@ -0,0 +1,147 @@
import { Controller, useForm } from "react-hook-form";
import { zodResolver } from "@hookform/resolvers/zod";
import { z } from "zod";
import { createNotification } from "@app/components/notifications";
import {
Button,
FormControl,
Input,
Modal,
ModalClose,
ModalContent,
Select,
SelectItem
} from "@app/components/v2";
import { certKeyAlgorithms } from "@app/hooks/api/certificates/constants";
import { CertKeyAlgorithm } from "@app/hooks/api/certificates/enums";
import { useGenerateKmipClientCertificate } from "@app/hooks/api/kmip";
import { KmipClientCertificate, TKmipClient } from "@app/hooks/api/kmip/types";
const formSchema = z.object({
keyAlgorithm: z.nativeEnum(CertKeyAlgorithm),
ttl: z.string()
});
export type FormData = z.infer<typeof formSchema>;
type Props = {
isOpen: boolean;
onOpenChange: (isOpen: boolean) => void;
kmipClient?: TKmipClient | null;
displayNewClientCertificate: (certificate: KmipClientCertificate) => void;
};
type FormProps = Pick<Props, "kmipClient" | "displayNewClientCertificate"> & {
onComplete: () => void;
};
const KmipClientCertificateForm = ({
displayNewClientCertificate,
kmipClient,
onComplete
}: FormProps) => {
const { mutateAsync: createKmipClientCertificate } = useGenerateKmipClientCertificate();
const {
control,
handleSubmit,
formState: { isSubmitting }
} = useForm<FormData>({
resolver: zodResolver(formSchema)
});
const handleKmipClientSubmit = async (payload: FormData) => {
if (!kmipClient) {
return;
}
const certificate = await createKmipClientCertificate({
...payload,
clientId: kmipClient?.id
});
createNotification({
text: "Successfully created KMIP client certificate",
type: "success"
});
displayNewClientCertificate(certificate);
onComplete();
};
return (
<form onSubmit={handleSubmit(handleKmipClientSubmit)}>
<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 client 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="flex items-center">
<Button
className="mr-4"
size="sm"
type="submit"
isLoading={isSubmitting}
isDisabled={isSubmitting}
>
Generate Client Certificate
</Button>
<ModalClose asChild>
<Button colorSchema="secondary" variant="plain">
Cancel
</Button>
</ModalClose>
</div>
</form>
);
};
export const CreateKmipClientCertificateModal = ({
isOpen,
onOpenChange,
kmipClient,
displayNewClientCertificate
}: Props) => {
return (
<Modal isOpen={isOpen} onOpenChange={onOpenChange}>
<ModalContent title="Generate KMIP client certificate">
<KmipClientCertificateForm
onComplete={() => onOpenChange(false)}
displayNewClientCertificate={displayNewClientCertificate}
kmipClient={kmipClient}
/>
</ModalContent>
</Modal>
);
};
@@ -0,0 +1,19 @@
import { Modal, ModalContent } from "@app/components/v2";
import { KmipClientCertificate } from "@app/hooks/api/kmip/types";
import { CertificateContent } from "@app/pages/cert-manager/CertificatesPage/components/CertificatesTab/components/CertificateContent";
type Props = {
isOpen: boolean;
onOpenChange: (isOpen: boolean) => void;
certificate: KmipClientCertificate;
};
export const KmipClientCertificateModal = ({ isOpen, onOpenChange, certificate }: Props) => {
return (
<Modal isOpen={isOpen} onOpenChange={onOpenChange}>
<ModalContent title="KMIP client certificate">
<CertificateContent {...certificate} />
</ModalContent>
</Modal>
);
};
@@ -2,6 +2,7 @@ import {
faArrowDown,
faArrowUp,
faArrowUpRightFromSquare,
faCertificate,
faEdit,
faEllipsis,
faMagnifyingGlass,
@@ -45,7 +46,9 @@ import { OrderByDirection } from "@app/hooks/api/generic/types";
import { useGetKmipClientsByProjectId } from "@app/hooks/api/kmip";
import { KmipClientOrderBy, TKmipClient } from "@app/hooks/api/kmip/types";
import { CreateKmipClientCertificateModal } from "./CreateKmipClientCertificateModal";
import { DeleteKmipClientModal } from "./DeleteKmipClientModal";
import { KmipClientCertificateModal } from "./KmipClientCertificateModal";
import { KmipClientModal } from "./KmipClientModal";
export const KmipClientTable = () => {
@@ -88,7 +91,9 @@ export const KmipClientTable = () => {
const { popUp, handlePopUpOpen, handlePopUpToggle } = usePopUp([
"upsertKmipClient",
"deleteKmipClient"
"deleteKmipClient",
"generateKmipClientCert",
"displayKmipClientCert"
] as const);
const handleSort = () => {
@@ -107,6 +112,11 @@ export const KmipClientTable = () => {
ProjectPermissionSub.Kmip
);
const cannotGenerateKmipClientCertificate = permission.cannot(
ProjectPermissionKmipActions.GenerateClientCertificates,
ProjectPermissionSub.Kmip
);
return (
<motion.div
key="kmip-clients-tab"
@@ -206,6 +216,25 @@ export const KmipClientTable = () => {
</IconButton>
</DropdownMenuTrigger>
<DropdownMenuContent className="min-w-[160px]">
<Tooltip
content={
cannotGenerateKmipClientCertificate ? "Access Restricted" : ""
}
position="left"
>
<div>
<DropdownMenuItem
onClick={() =>
handlePopUpOpen("generateKmipClientCert", kmipClient)
}
icon={<FontAwesomeIcon icon={faCertificate} />}
iconPos="left"
isDisabled={cannotGenerateKmipClientCertificate}
>
Generate Certificate
</DropdownMenuItem>
</div>
</Tooltip>
<Tooltip
content={cannotEditKmipClient ? "Access Restricted" : ""}
position="left"
@@ -274,6 +303,19 @@ export const KmipClientTable = () => {
onOpenChange={(isOpen) => handlePopUpToggle("upsertKmipClient", isOpen)}
kmipClient={popUp.upsertKmipClient.data as TKmipClient | null}
/>
<CreateKmipClientCertificateModal
isOpen={popUp.generateKmipClientCert.isOpen}
onOpenChange={(isOpen) => handlePopUpToggle("generateKmipClientCert", isOpen)}
kmipClient={popUp.generateKmipClientCert.data as TKmipClient | null}
displayNewClientCertificate={(certificate) =>
handlePopUpOpen("displayKmipClientCert", certificate)
}
/>
<KmipClientCertificateModal
isOpen={popUp.displayKmipClientCert.isOpen}
onOpenChange={(isOpen) => handlePopUpToggle("displayKmipClientCert", isOpen)}
certificate={popUp.displayKmipClientCert.data}
/>
</div>
</motion.div>
);
@@ -53,7 +53,8 @@ const KmipPolicyActionSchema = z.object({
[ProjectPermissionKmipActions.ReadClients]: z.boolean().optional(),
[ProjectPermissionKmipActions.CreateClients]: z.boolean().optional(),
[ProjectPermissionKmipActions.UpdateClients]: z.boolean().optional(),
[ProjectPermissionKmipActions.DeleteClients]: z.boolean().optional()
[ProjectPermissionKmipActions.DeleteClients]: z.boolean().optional(),
[ProjectPermissionKmipActions.GenerateClientCertificates]: z.boolean().optional()
});
const SecretRollbackPolicyActionSchema = z.object({
@@ -373,6 +374,9 @@ export const rolePermission2Form = (permissions: TProjectPermission[] = []) => {
const canEditClients = action.includes(ProjectPermissionKmipActions.UpdateClients);
const canDeleteClients = action.includes(ProjectPermissionKmipActions.DeleteClients);
const canCreateClients = action.includes(ProjectPermissionKmipActions.CreateClients);
const canGenerateClientCerts = action.includes(
ProjectPermissionKmipActions.GenerateClientCertificates
);
if (!formVal[subject]) formVal[subject] = [{}];
@@ -381,6 +385,8 @@ export const rolePermission2Form = (permissions: TProjectPermission[] = []) => {
if (canEditClients) formVal[subject]![0][ProjectPermissionKmipActions.UpdateClients] = true;
if (canCreateClients) formVal[subject]![0][ProjectPermissionKmipActions.CreateClients] = true;
if (canDeleteClients) formVal[subject]![0][ProjectPermissionKmipActions.DeleteClients] = true;
if (canGenerateClientCerts)
formVal[subject]![0][ProjectPermissionKmipActions.GenerateClientCertificates] = true;
return;
}
@@ -783,6 +789,10 @@ export const PROJECT_PERMISSION_OBJECT: TProjectPermissionObject = {
{
label: "Delete clients",
value: ProjectPermissionKmipActions.DeleteClients
},
{
label: "Generate client certificates",
value: ProjectPermissionKmipActions.GenerateClientCertificates
}
]
}