feat: initial enforcement of template policy

This commit is contained in:
Sheen Capadngan
2024-08-16 16:32:47 +08:00
parent 28a2a6c41a
commit b9bd518aa6
18 changed files with 427 additions and 37 deletions
+4 -3
View File
@@ -16,7 +16,8 @@ import {
TRenewCaResponse,
TSignIntermediateDTO,
TSignIntermediateResponse,
TUpdateCaDTO} from "./types";
TUpdateCaDTO
} from "./types";
export const useCreateCa = () => {
const queryClient = useQueryClient();
@@ -98,9 +99,9 @@ export const useImportCaCertificate = () => {
export const useCreateCertificate = () => {
const queryClient = useQueryClient();
return useMutation<TCreateCertificateResponse, {}, TCreateCertificateDTO>({
mutationFn: async ({ caId, ...body }) => {
mutationFn: async (body) => {
const { data } = await apiRequest.post<TCreateCertificateResponse>(
`/api/v1/pki/ca/${caId}/issue-certificate`,
"/api/v1/pki/certificates/issue-certificate",
body
);
return data;
+3 -2
View File
@@ -1,5 +1,5 @@
import { CertKeyAlgorithm } from "../certificates/enums";
import { CaRenewalType,CaStatus, CaType } from "./enums";
import { CaRenewalType, CaStatus, CaType } from "./enums";
export type TCertificateAuthority = {
id: string;
@@ -78,7 +78,8 @@ export type TImportCaCertificateResponse = {
export type TCreateCertificateDTO = {
projectSlug: string;
caId: string;
caId?: string;
certificateTemplateId?: string;
friendlyName?: string;
commonName: string;
altNames: string; // sans
@@ -2,6 +2,7 @@ export type TCertificateTemplateListEntry = {
id: string;
name: string;
caName: string;
caId: string;
};
export type TCertificateTemplate = {
@@ -14,13 +14,21 @@ import {
SelectItem
} from "@app/components/v2";
import { useWorkspace } from "@app/context";
import { CaStatus, useCreateCertificate, useGetCert, useListWorkspaceCas } from "@app/hooks/api";
import {
CaStatus,
useCreateCertificate,
useGetCert,
useGetCertTemplate,
useListWorkspaceCas,
useListWorkspaceCertificateTemplates
} from "@app/hooks/api";
import { caTypeToNameMap } from "@app/hooks/api/ca/constants";
import { UsePopUpState } from "@app/hooks/usePopUp";
import { CertificateContent } from "./CertificateContent";
const schema = z.object({
certificateTemplateId: z.string().optional(),
caId: z.string(),
friendlyName: z.string(),
commonName: z.string().trim().min(1),
@@ -42,6 +50,8 @@ type TCertificateDetails = {
privateKey: string;
};
const CERT_TEMPLATE_NONE_VALUE = "none";
export const CertificateModal = ({ popUp, handlePopUpToggle }: Props) => {
const [certificateDetails, setCertificateDetails] = useState<TCertificateDetails | null>(null);
const { currentWorkspace } = useWorkspace();
@@ -54,6 +64,10 @@ export const CertificateModal = ({ popUp, handlePopUpToggle }: Props) => {
status: CaStatus.ACTIVE
});
const { data: templatesData } = useListWorkspaceCertificateTemplates({
workspaceId: currentWorkspace?.id || ""
});
const { mutateAsync: createCertificate } = useCreateCertificate();
const {
@@ -61,11 +75,20 @@ export const CertificateModal = ({ popUp, handlePopUpToggle }: Props) => {
handleSubmit,
reset,
formState: { isSubmitting },
setValue
setValue,
watch
} = useForm<FormData>({
resolver: zodResolver(schema)
});
const selectedCertTemplateId = watch("certificateTemplateId");
const hasCertTemplateSelected =
selectedCertTemplateId !== "" && selectedCertTemplateId !== CERT_TEMPLATE_NONE_VALUE;
const { data: selectedCertTemplate } = useGetCertTemplate(
hasCertTemplateSelected ? (selectedCertTemplateId as string) : ""
);
useEffect(() => {
if (cert) {
reset({
@@ -81,7 +104,8 @@ export const CertificateModal = ({ popUp, handlePopUpToggle }: Props) => {
friendlyName: "",
commonName: "",
altNames: "",
ttl: ""
ttl: "",
certificateTemplateId: CERT_TEMPLATE_NONE_VALUE
});
}
}, [cert]);
@@ -91,8 +115,9 @@ export const CertificateModal = ({ popUp, handlePopUpToggle }: Props) => {
if (!currentWorkspace?.slug) return;
const { serialNumber, certificate, certificateChain, privateKey } = await createCertificate({
caId: !selectedCertTemplate ? caId : undefined,
certificateTemplateId: selectedCertTemplate ? selectedCertTemplateId : undefined,
projectSlug: currentWorkspace.slug,
caId,
friendlyName,
commonName,
altNames,
@@ -141,11 +166,11 @@ export const CertificateModal = ({ popUp, handlePopUpToggle }: Props) => {
<form onSubmit={handleSubmit(onFormSubmit)}>
<Controller
control={control}
name="caId"
name="certificateTemplateId"
defaultValue=""
render={({ field: { onChange, ...field }, fieldState: { error } }) => (
<FormControl
label="Issuing CA"
label="Certificate Template"
errorText={error?.message}
isError={Boolean(error)}
className="mt-4"
@@ -158,15 +183,47 @@ export const CertificateModal = ({ popUp, handlePopUpToggle }: Props) => {
className="w-full"
isDisabled={Boolean(cert)}
>
{(cas || []).map(({ id, type, dn }) => (
<SelectItem value={id} key={`ca-${id}`}>
{`${caTypeToNameMap[type]}: ${dn}`}
<SelectItem value={CERT_TEMPLATE_NONE_VALUE} key="cert-template-none">
None
</SelectItem>
{(templatesData?.certificateTemplates || []).map(({ id, name }) => (
<SelectItem value={id} key={`cert-template-${id}`}>
{name}
</SelectItem>
))}
</Select>
</FormControl>
)}
/>
{(!selectedCertTemplateId || selectedCertTemplateId === CERT_TEMPLATE_NONE_VALUE) && (
<Controller
control={control}
name="caId"
render={({ field: { onChange, ...field }, fieldState: { error } }) => (
<FormControl
label="Issuing CA"
errorText={error?.message}
isError={Boolean(error)}
className="mt-4"
isRequired
>
<Select
defaultValue={field.value}
{...field}
onValueChange={(e) => onChange(e)}
className="w-full"
isDisabled={Boolean(cert)}
>
{(cas || []).map(({ id, type, dn }) => (
<SelectItem value={id} key={`ca-${id}`}>
{`${caTypeToNameMap[type]}: ${dn}`}
</SelectItem>
))}
</Select>
</FormControl>
)}
/>
)}
<Controller
control={control}
defaultValue=""
@@ -27,7 +27,15 @@ import { UsePopUpState } from "@app/hooks/usePopUp";
const schema = z.object({
caId: z.string(),
name: z.string().min(1),
commonName: z.string().trim().min(1),
commonName: z
.string()
.trim()
.min(1)
.max(100)
.regex(/^[a-zA-Z0-9 *@\-\\.\\]+$/, {
message:
"Invalid pattern: only alphanumeric characters, spaces, *, ., @, -, and \\ are allowed."
}),
ttl: z.string().trim().min(1)
});