mirror of
https://github.com/awatertrevi/infisical.git
synced 2026-10-14 19:44:52 +00:00
feat: finished up client cert generation
This commit is contained in:
@@ -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 {
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
@@ -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>
|
||||
);
|
||||
|
||||
+11
-1
@@ -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
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user