feat: added collection selection for cert template

This commit is contained in:
Sheen Capadngan
2024-08-16 19:40:23 +08:00
parent 69925721cc
commit 97f5c33aea
11 changed files with 116 additions and 59 deletions
@@ -10,6 +10,8 @@ export async function up(knex: Knex): Promise<void> {
tb.uuid("id", { primaryKey: true }).defaultTo(knex.fn.uuid()); tb.uuid("id", { primaryKey: true }).defaultTo(knex.fn.uuid());
tb.uuid("caId").notNullable(); tb.uuid("caId").notNullable();
tb.foreign("caId").references("id").inTable(TableName.CertificateAuthority).onDelete("CASCADE"); tb.foreign("caId").references("id").inTable(TableName.CertificateAuthority).onDelete("CASCADE");
tb.uuid("pkiCollectionId");
tb.foreign("pkiCollectionId").references("id").inTable(TableName.PkiCollection).onDelete("SET NULL");
tb.string("name").notNullable(); tb.string("name").notNullable();
tb.string("commonName").notNullable(); tb.string("commonName").notNullable();
tb.string("subjectAlternativeName").notNullable(); tb.string("subjectAlternativeName").notNullable();
@@ -10,6 +10,7 @@ import { TImmutableDBKeys } from "./models";
export const CertificateTemplatesSchema = z.object({ export const CertificateTemplatesSchema = z.object({
id: z.string().uuid(), id: z.string().uuid(),
caId: z.string().uuid(), caId: z.string().uuid(),
pkiCollectionId: z.string().uuid().nullable().optional(),
name: z.string(), name: z.string(),
commonName: z.string(), commonName: z.string(),
subjectAlternativeName: z.string(), subjectAlternativeName: z.string(),
@@ -77,6 +77,7 @@ export const registerCertRouter = async (server: FastifyZodProvider) => {
.trim() .trim()
.optional() .optional()
.describe(CERTIFICATE_AUTHORITIES.ISSUE_CERT.certificateTemplateId), .describe(CERTIFICATE_AUTHORITIES.ISSUE_CERT.certificateTemplateId),
pkiCollectionId: z.string().trim().optional().describe(CERTIFICATE_AUTHORITIES.SIGN_CERT.pkiCollectionId),
friendlyName: z.string().trim().optional().describe(CERTIFICATE_AUTHORITIES.ISSUE_CERT.friendlyName), friendlyName: z.string().trim().optional().describe(CERTIFICATE_AUTHORITIES.ISSUE_CERT.friendlyName),
commonName: z.string().trim().min(1).describe(CERTIFICATE_AUTHORITIES.ISSUE_CERT.commonName), commonName: z.string().trim().min(1).describe(CERTIFICATE_AUTHORITIES.ISSUE_CERT.commonName),
altNames: validateAltNamesField.describe(CERTIFICATE_AUTHORITIES.ISSUE_CERT.altNames), altNames: validateAltNamesField.describe(CERTIFICATE_AUTHORITIES.ISSUE_CERT.altNames),
@@ -166,6 +167,7 @@ export const registerCertRouter = async (server: FastifyZodProvider) => {
.trim() .trim()
.optional() .optional()
.describe(CERTIFICATE_AUTHORITIES.ISSUE_CERT.certificateTemplateId), .describe(CERTIFICATE_AUTHORITIES.ISSUE_CERT.certificateTemplateId),
pkiCollectionId: z.string().trim().optional().describe(CERTIFICATE_AUTHORITIES.SIGN_CERT.pkiCollectionId),
csr: z.string().trim().min(1).describe(CERTIFICATE_AUTHORITIES.SIGN_CERT.csr), csr: z.string().trim().min(1).describe(CERTIFICATE_AUTHORITIES.SIGN_CERT.csr),
friendlyName: z.string().trim().optional().describe(CERTIFICATE_AUTHORITIES.SIGN_CERT.friendlyName), friendlyName: z.string().trim().optional().describe(CERTIFICATE_AUTHORITIES.SIGN_CERT.friendlyName),
commonName: z.string().trim().min(1).optional().describe(CERTIFICATE_AUTHORITIES.SIGN_CERT.commonName), commonName: z.string().trim().min(1).optional().describe(CERTIFICATE_AUTHORITIES.SIGN_CERT.commonName),
@@ -13,6 +13,7 @@ const sanitizedCertificateTemplate = CertificateTemplatesSchema.pick({
name: true, name: true,
commonName: true, commonName: true,
subjectAlternativeName: true, subjectAlternativeName: true,
pkiCollectionId: true,
ttl: true ttl: true
}); });
@@ -61,6 +62,7 @@ export const registerCertificateTemplateRouter = async (server: FastifyZodProvid
schema: { schema: {
body: z.object({ body: z.object({
caId: z.string(), caId: z.string(),
pkiCollectionId: z.string().optional(),
name: z.string().min(1), name: z.string().min(1),
commonName: validateTemplateRegexField, commonName: validateTemplateRegexField,
subjectAlternativeName: validateTemplateRegexField, subjectAlternativeName: validateTemplateRegexField,
@@ -95,6 +97,7 @@ export const registerCertificateTemplateRouter = async (server: FastifyZodProvid
schema: { schema: {
body: z.object({ body: z.object({
caId: z.string().optional(), caId: z.string().optional(),
pkiCollectionId: z.string().optional(),
name: z.string().min(1).optional(), name: z.string().min(1).optional(),
commonName: validateTemplateRegexField.optional(), commonName: validateTemplateRegexField.optional(),
subjectAlternativeName: validateTemplateRegexField.optional(), subjectAlternativeName: validateTemplateRegexField.optional(),
@@ -1033,6 +1033,7 @@ export const certificateAuthorityServiceFactory = ({
}: TIssueCertFromCaDTO) => { }: TIssueCertFromCaDTO) => {
let ca: TCertificateAuthorities | undefined; let ca: TCertificateAuthorities | undefined;
let certificateTemplate: TCertificateTemplates | undefined; let certificateTemplate: TCertificateTemplates | undefined;
let collectionId = pkiCollectionId;
if (caId) { if (caId) {
ca = await certificateAuthorityDAL.findById(caId); ca = await certificateAuthorityDAL.findById(caId);
@@ -1044,6 +1045,7 @@ export const certificateAuthorityServiceFactory = ({
}); });
} }
collectionId = certificateTemplate.pkiCollectionId as string;
ca = await certificateAuthorityDAL.findById(certificateTemplate.caId); ca = await certificateAuthorityDAL.findById(certificateTemplate.caId);
} }
@@ -1070,8 +1072,8 @@ export const certificateAuthorityServiceFactory = ({
} }
// check PKI collection // check PKI collection
if (pkiCollectionId) { if (collectionId) {
const pkiCollection = await pkiCollectionDAL.findById(pkiCollectionId); const pkiCollection = await pkiCollectionDAL.findById(collectionId);
if (!pkiCollection) throw new NotFoundError({ message: "PKI collection not found" }); if (!pkiCollection) throw new NotFoundError({ message: "PKI collection not found" });
if (pkiCollection.projectId !== ca.projectId) throw new BadRequestError({ message: "Invalid PKI collection" }); if (pkiCollection.projectId !== ca.projectId) throw new BadRequestError({ message: "Invalid PKI collection" });
} }
@@ -1237,10 +1239,10 @@ export const certificateAuthorityServiceFactory = ({
tx tx
); );
if (pkiCollectionId) { if (collectionId) {
await pkiCollectionItemDAL.create( await pkiCollectionItemDAL.create(
{ {
pkiCollectionId, pkiCollectionId: collectionId,
certId: cert.id certId: cert.id
}, },
tx tx
@@ -1290,6 +1292,7 @@ export const certificateAuthorityServiceFactory = ({
}: TSignCertFromCaDTO) => { }: TSignCertFromCaDTO) => {
let ca: TCertificateAuthorities | undefined; let ca: TCertificateAuthorities | undefined;
let certificateTemplate: TCertificateTemplates | undefined; let certificateTemplate: TCertificateTemplates | undefined;
let collectionId = pkiCollectionId;
if (caId) { if (caId) {
ca = await certificateAuthorityDAL.findById(caId); ca = await certificateAuthorityDAL.findById(caId);
@@ -1301,6 +1304,7 @@ export const certificateAuthorityServiceFactory = ({
}); });
} }
collectionId = certificateTemplate.pkiCollectionId as string;
ca = await certificateAuthorityDAL.findById(certificateTemplate.caId); ca = await certificateAuthorityDAL.findById(certificateTemplate.caId);
} }
@@ -1490,10 +1494,10 @@ export const certificateAuthorityServiceFactory = ({
tx tx
); );
if (pkiCollectionId) { if (collectionId) {
await pkiCollectionItemDAL.create( await pkiCollectionItemDAL.create(
{ {
pkiCollectionId, pkiCollectionId: collectionId,
certId: cert.id certId: cert.id
}, },
tx tx
@@ -28,6 +28,7 @@ export const certificateTemplateServiceFactory = ({
}: TCertificateTemplateServiceFactoryDep) => { }: TCertificateTemplateServiceFactoryDep) => {
const createCertTemplate = async ({ const createCertTemplate = async ({
caId, caId,
pkiCollectionId,
name, name,
commonName, commonName,
subjectAlternativeName, subjectAlternativeName,
@@ -58,6 +59,7 @@ export const certificateTemplateServiceFactory = ({
const certificateTemplate = await certificateTemplateDAL.create({ const certificateTemplate = await certificateTemplateDAL.create({
caId, caId,
pkiCollectionId,
name, name,
commonName, commonName,
subjectAlternativeName, subjectAlternativeName,
@@ -70,6 +72,7 @@ export const certificateTemplateServiceFactory = ({
const updateCertTemplate = async ({ const updateCertTemplate = async ({
id, id,
caId, caId,
pkiCollectionId,
name, name,
commonName, commonName,
subjectAlternativeName, subjectAlternativeName,
@@ -110,6 +113,7 @@ export const certificateTemplateServiceFactory = ({
const updatedCertTemplate = await certificateTemplateDAL.updateById(certTemplate.id, { const updatedCertTemplate = await certificateTemplateDAL.updateById(certTemplate.id, {
caId, caId,
pkiCollectionId,
commonName, commonName,
subjectAlternativeName, subjectAlternativeName,
name, name,
@@ -2,6 +2,7 @@ import { TProjectPermission } from "@app/lib/types";
export type TCreateCertTemplateDTO = { export type TCreateCertTemplateDTO = {
caId: string; caId: string;
pkiCollectionId?: string;
name: string; name: string;
commonName: string; commonName: string;
subjectAlternativeName: string; subjectAlternativeName: string;
@@ -11,6 +12,7 @@ export type TCreateCertTemplateDTO = {
export type TUpdateCertTemplateDTO = { export type TUpdateCertTemplateDTO = {
id: string; id: string;
caId?: string; caId?: string;
pkiCollectionId?: string;
name?: string; name?: string;
commonName?: string; commonName?: string;
subjectAlternativeName?: string; subjectAlternativeName?: string;
@@ -8,6 +8,7 @@ export type TCertificateTemplateListEntry = {
export type TCertificateTemplate = { export type TCertificateTemplate = {
id: string; id: string;
caId: string; caId: string;
pkiCollectionId?: string;
name: string; name: string;
commonName: string; commonName: string;
subjectAlternativeName: string; subjectAlternativeName: string;
@@ -16,6 +17,7 @@ export type TCertificateTemplate = {
export type TCreateCertificateTemplateDTO = { export type TCreateCertificateTemplateDTO = {
caId: string; caId: string;
pkiCollectionId?: string;
name: string; name: string;
commonName: string; commonName: string;
subjectAlternativeName: string; subjectAlternativeName: string;
@@ -26,6 +28,7 @@ export type TCreateCertificateTemplateDTO = {
export type TUpdateCertificateTemplateDTO = { export type TUpdateCertificateTemplateDTO = {
id: string; id: string;
caId?: string; caId?: string;
pkiCollectionId?: string;
name?: string; name?: string;
commonName?: string; commonName?: string;
subjectAlternativeName?: string; subjectAlternativeName?: string;
@@ -216,60 +216,61 @@ export const CertificateModal = ({ popUp, handlePopUpToggle }: Props) => {
)} )}
/> />
{(!selectedCertTemplateId || selectedCertTemplateId === CERT_TEMPLATE_NONE_VALUE) && ( {(!selectedCertTemplateId || selectedCertTemplateId === CERT_TEMPLATE_NONE_VALUE) && (
<Controller <Controller
control={control} control={control}
name="caId" name="caId"
render={({ field: { onChange, ...field }, fieldState: { error } }) => ( render={({ field: { onChange, ...field }, fieldState: { error } }) => (
<FormControl <FormControl
label="Issuing CA" label="Issuing CA"
errorText={error?.message} errorText={error?.message}
isError={Boolean(error)} isError={Boolean(error)}
className="mt-4" className="mt-4"
isRequired isRequired
>
<Select
defaultValue={field.value}
{...field}
onValueChange={(e) => onChange(e)}
className="w-full"
isDisabled={Boolean(cert)}
> >
{(cas || []).map(({ id, type, dn }) => ( <Select
<SelectItem value={id} key={`ca-${id}`}> defaultValue={field.value}
{`${caTypeToNameMap[type]}: ${dn}`} {...field}
</SelectItem> onValueChange={(e) => onChange(e)}
))} className="w-full"
</Select> isDisabled={Boolean(cert)}
</FormControl> >
)} {(cas || []).map(({ id, type, dn }) => (
/> <SelectItem value={id} key={`ca-${id}`}>
)} {`${caTypeToNameMap[type]}: ${dn}`}
<Controller </SelectItem>
control={control} ))}
name="collectionId" </Select>
render={({ field: { onChange, ...field }, fieldState: { error } }) => ( </FormControl>
<FormControl )}
label="Certificate Collection (Optional)" />
errorText={error?.message} ) && (
isError={Boolean(error)} <Controller
className="mt-4" control={control}
> name="collectionId"
<Select render={({ field: { onChange, ...field }, fieldState: { error } }) => (
defaultValue={field.value} <FormControl
{...field} label="Certificate Collection (Optional)"
onValueChange={(e) => onChange(e)} errorText={error?.message}
className="w-full" isError={Boolean(error)}
isDisabled={Boolean(cert)} className="mt-4"
> >
{(data?.collections || []).map(({ id, name }) => ( <Select
<SelectItem value={id} key={`pki-collection-${id}`}> defaultValue={field.value}
{name} {...field}
</SelectItem> onValueChange={(e) => onChange(e)}
))} className="w-full"
</Select> isDisabled={Boolean(cert)}
</FormControl> >
{(data?.collections || []).map(({ id, name }) => (
<SelectItem value={id} key={`pki-collection-${id}`}>
{name}
</SelectItem>
))}
</Select>
</FormControl>
)}
/>
)} )}
/>
<Controller <Controller
control={control} control={control}
defaultValue="" defaultValue=""
@@ -19,6 +19,7 @@ import {
useCreateCertTemplate, useCreateCertTemplate,
useGetCertTemplate, useGetCertTemplate,
useListWorkspaceCas, useListWorkspaceCas,
useListWorkspacePkiCollections,
useUpdateCertTemplate useUpdateCertTemplate
} from "@app/hooks/api"; } from "@app/hooks/api";
import { caTypeToNameMap } from "@app/hooks/api/ca/constants"; import { caTypeToNameMap } from "@app/hooks/api/ca/constants";
@@ -36,6 +37,7 @@ const validateTemplateRegexField = z
const schema = z.object({ const schema = z.object({
caId: z.string(), caId: z.string(),
collectionId: z.string().optional(),
name: z.string().min(1), name: z.string().min(1),
commonName: validateTemplateRegexField, commonName: validateTemplateRegexField,
subjectAlternativeName: validateTemplateRegexField, subjectAlternativeName: validateTemplateRegexField,
@@ -63,6 +65,10 @@ export const CertificateTemplateModal = ({ popUp, handlePopUpToggle }: Props) =>
status: CaStatus.ACTIVE status: CaStatus.ACTIVE
}); });
const { data: collectionsData } = useListWorkspacePkiCollections({
workspaceId: currentWorkspace?.id || ""
});
const { mutateAsync: createCertTemplate } = useCreateCertTemplate(); const { mutateAsync: createCertTemplate } = useCreateCertTemplate();
const { mutateAsync: updateCertTemplate } = useUpdateCertTemplate(); const { mutateAsync: updateCertTemplate } = useUpdateCertTemplate();
@@ -82,6 +88,7 @@ export const CertificateTemplateModal = ({ popUp, handlePopUpToggle }: Props) =>
name: certTemplate.name, name: certTemplate.name,
commonName: certTemplate.commonName, commonName: certTemplate.commonName,
subjectAlternativeName: certTemplate.subjectAlternativeName, subjectAlternativeName: certTemplate.subjectAlternativeName,
collectionId: certTemplate.pkiCollectionId ?? undefined,
ttl: certTemplate.ttl ttl: certTemplate.ttl
}); });
} else { } else {
@@ -96,6 +103,7 @@ export const CertificateTemplateModal = ({ popUp, handlePopUpToggle }: Props) =>
const onFormSubmit = async ({ const onFormSubmit = async ({
caId, caId,
collectionId,
name, name,
commonName, commonName,
subjectAlternativeName, subjectAlternativeName,
@@ -110,6 +118,7 @@ export const CertificateTemplateModal = ({ popUp, handlePopUpToggle }: Props) =>
await updateCertTemplate({ await updateCertTemplate({
id: certTemplate.id, id: certTemplate.id,
projectId: currentWorkspace.id, projectId: currentWorkspace.id,
pkiCollectionId: collectionId,
caId, caId,
name, name,
commonName, commonName,
@@ -124,6 +133,7 @@ export const CertificateTemplateModal = ({ popUp, handlePopUpToggle }: Props) =>
} else { } else {
await createCertTemplate({ await createCertTemplate({
projectId: currentWorkspace.id, projectId: currentWorkspace.id,
pkiCollectionId: collectionId,
caId, caId,
name, name,
commonName, commonName,
@@ -200,6 +210,31 @@ export const CertificateTemplateModal = ({ popUp, handlePopUpToggle }: Props) =>
</FormControl> </FormControl>
)} )}
/> />
<Controller
control={control}
name="collectionId"
render={({ field: { onChange, ...field }, fieldState: { error } }) => (
<FormControl
label="Certificate Collection (Optional)"
errorText={error?.message}
isError={Boolean(error)}
className="mt-4"
>
<Select
defaultValue={field.value}
{...field}
onValueChange={(e) => onChange(e)}
className="w-full"
>
{(collectionsData?.collections || []).map(({ id, name }) => (
<SelectItem value={id} key={`pki-collection-${id}`}>
{name}
</SelectItem>
))}
</Select>
</FormControl>
)}
/>
<Controller <Controller
control={control} control={control}
defaultValue="" defaultValue=""
@@ -76,7 +76,7 @@ export const CertificateTemplatesTable = ({ handlePopUpOpen }: Props) => {
} }
icon={<FontAwesomeIcon icon={faGear} />} icon={<FontAwesomeIcon icon={faGear} />}
> >
Manage Manage Policies
</DropdownMenuItem> </DropdownMenuItem>
<ProjectPermissionCan <ProjectPermissionCan
I={ProjectPermissionActions.Delete} I={ProjectPermissionActions.Delete}