mirror of
https://github.com/awatertrevi/infisical.git
synced 2026-09-22 13:39:35 +00:00
misc: key usages setup
This commit is contained in:
@@ -0,0 +1,40 @@
|
||||
import { Knex } from "knex";
|
||||
|
||||
import { CertKeyUsage } from "@app/services/certificate/certificate-types";
|
||||
|
||||
import { TableName } from "../schemas";
|
||||
|
||||
export async function up(knex: Knex): Promise<void> {
|
||||
const hasKeyUsagesCol = await knex.schema.hasColumn(TableName.CertificateTemplate, "keyUsages");
|
||||
const hasExtendedKeyUsagesCol = await knex.schema.hasColumn(TableName.CertificateTemplate, "extendedKeyUsages");
|
||||
|
||||
await knex.schema.alterTable(TableName.CertificateTemplate, (tb) => {
|
||||
if (!hasKeyUsagesCol) {
|
||||
tb.specificType("keyUsages", "text[]");
|
||||
}
|
||||
|
||||
if (!hasExtendedKeyUsagesCol) {
|
||||
tb.specificType("extendedKeyUsages", "text[]");
|
||||
}
|
||||
});
|
||||
|
||||
if (!hasKeyUsagesCol) {
|
||||
await knex(TableName.CertificateTemplate).update({
|
||||
keyUsages: [CertKeyUsage.DIGITAL_SIGNATURE, CertKeyUsage.KEY_ENCIPHERMENT]
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
export async function down(knex: Knex): Promise<void> {
|
||||
const hasKeyUsagesCol = await knex.schema.hasColumn(TableName.CertificateTemplate, "keyUsages");
|
||||
const hasExtendedKeyUsagesCol = await knex.schema.hasColumn(TableName.CertificateTemplate, "extendedKeyUsages");
|
||||
|
||||
await knex.schema.alterTable(TableName.CertificateTemplate, (t) => {
|
||||
if (hasKeyUsagesCol) {
|
||||
t.dropColumn("keyUsages");
|
||||
}
|
||||
if (hasExtendedKeyUsagesCol) {
|
||||
t.dropColumn("extendedKeyUsages");
|
||||
}
|
||||
});
|
||||
}
|
||||
@@ -16,7 +16,9 @@ export const CertificateTemplatesSchema = z.object({
|
||||
subjectAlternativeName: z.string(),
|
||||
ttl: z.string(),
|
||||
createdAt: z.date(),
|
||||
updatedAt: z.date()
|
||||
updatedAt: z.date(),
|
||||
keyUsages: z.string().array().nullable().optional(),
|
||||
extendedKeyUsages: z.string().array().nullable().optional()
|
||||
});
|
||||
|
||||
export type TCertificateTemplates = z.infer<typeof CertificateTemplatesSchema>;
|
||||
|
||||
@@ -7,6 +7,7 @@ import { CERTIFICATE_TEMPLATES } from "@app/lib/api-docs";
|
||||
import { readLimit, writeLimit } from "@app/server/config/rateLimiter";
|
||||
import { verifyAuth } from "@app/server/plugins/auth/verify-auth";
|
||||
import { AuthMode } from "@app/services/auth/auth-type";
|
||||
import { CertKeyUsage } from "@app/services/certificate/certificate-types";
|
||||
import { sanitizedCertificateTemplate } from "@app/services/certificate-template/certificate-template-schema";
|
||||
import { validateTemplateRegexField } from "@app/services/certificate-template/certificate-template-validators";
|
||||
|
||||
@@ -74,7 +75,12 @@ export const registerCertificateTemplateRouter = async (server: FastifyZodProvid
|
||||
ttl: z
|
||||
.string()
|
||||
.refine((val) => ms(val) > 0, "TTL must be a positive number")
|
||||
.describe(CERTIFICATE_TEMPLATES.CREATE.ttl)
|
||||
.describe(CERTIFICATE_TEMPLATES.CREATE.ttl),
|
||||
keyUsages: z
|
||||
.nativeEnum(CertKeyUsage)
|
||||
.array()
|
||||
.optional()
|
||||
.default([CertKeyUsage.DIGITAL_SIGNATURE, CertKeyUsage.KEY_ENCIPHERMENT])
|
||||
}),
|
||||
response: {
|
||||
200: sanitizedCertificateTemplate
|
||||
@@ -130,7 +136,8 @@ export const registerCertificateTemplateRouter = async (server: FastifyZodProvid
|
||||
.string()
|
||||
.refine((val) => ms(val) > 0, "TTL must be a positive number")
|
||||
.optional()
|
||||
.describe(CERTIFICATE_TEMPLATES.UPDATE.ttl)
|
||||
.describe(CERTIFICATE_TEMPLATES.UPDATE.ttl),
|
||||
keyUsages: z.nativeEnum(CertKeyUsage).array().optional()
|
||||
}),
|
||||
params: z.object({
|
||||
certificateTemplateId: z.string().describe(CERTIFICATE_TEMPLATES.UPDATE.certificateTemplateId)
|
||||
|
||||
@@ -50,7 +50,9 @@ export const certificateTemplateDALFactory = (db: TDbClient) => {
|
||||
)
|
||||
.first();
|
||||
|
||||
return certTemplate;
|
||||
if (certTemplate) {
|
||||
return { ...certTemplate, keyUsages: certTemplate.keyUsages || [] };
|
||||
}
|
||||
} catch (error) {
|
||||
throw new DatabaseError({ error, name: "Get certificate template by ID" });
|
||||
}
|
||||
|
||||
@@ -13,6 +13,7 @@ export const sanitizedCertificateTemplate = CertificateTemplatesSchema.pick({
|
||||
}).merge(
|
||||
z.object({
|
||||
projectId: z.string(),
|
||||
caName: z.string()
|
||||
caName: z.string(),
|
||||
keyUsages: z.string().array()
|
||||
})
|
||||
);
|
||||
|
||||
@@ -57,7 +57,8 @@ export const certificateTemplateServiceFactory = ({
|
||||
actorId,
|
||||
actorAuthMethod,
|
||||
actor,
|
||||
actorOrgId
|
||||
actorOrgId,
|
||||
keyUsages
|
||||
}: TCreateCertTemplateDTO) => {
|
||||
const ca = await certificateAuthorityDAL.findById(caId);
|
||||
if (!ca) {
|
||||
@@ -86,7 +87,8 @@ export const certificateTemplateServiceFactory = ({
|
||||
name,
|
||||
commonName,
|
||||
subjectAlternativeName,
|
||||
ttl
|
||||
ttl,
|
||||
keyUsages
|
||||
},
|
||||
tx
|
||||
);
|
||||
@@ -113,7 +115,8 @@ export const certificateTemplateServiceFactory = ({
|
||||
actorId,
|
||||
actorAuthMethod,
|
||||
actor,
|
||||
actorOrgId
|
||||
actorOrgId,
|
||||
keyUsages
|
||||
}: TUpdateCertTemplateDTO) => {
|
||||
const certTemplate = await certificateTemplateDAL.getById(id);
|
||||
if (!certTemplate) {
|
||||
@@ -153,7 +156,8 @@ export const certificateTemplateServiceFactory = ({
|
||||
commonName,
|
||||
subjectAlternativeName,
|
||||
name,
|
||||
ttl
|
||||
ttl,
|
||||
keyUsages
|
||||
},
|
||||
tx
|
||||
);
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import { TProjectPermission } from "@app/lib/types";
|
||||
import { CertKeyUsage } from "@app/services/certificate/certificate-types";
|
||||
|
||||
export type TCreateCertTemplateDTO = {
|
||||
caId: string;
|
||||
@@ -7,6 +8,7 @@ export type TCreateCertTemplateDTO = {
|
||||
commonName: string;
|
||||
subjectAlternativeName: string;
|
||||
ttl: string;
|
||||
keyUsages: CertKeyUsage[];
|
||||
} & Omit<TProjectPermission, "projectId">;
|
||||
|
||||
export type TUpdateCertTemplateDTO = {
|
||||
@@ -17,6 +19,7 @@ export type TUpdateCertTemplateDTO = {
|
||||
commonName?: string;
|
||||
subjectAlternativeName?: string;
|
||||
ttl?: string;
|
||||
keyUsages?: CertKeyUsage[];
|
||||
} & Omit<TProjectPermission, "projectId">;
|
||||
|
||||
export type TGetCertTemplateDTO = {
|
||||
|
||||
@@ -12,6 +12,18 @@ export enum CertKeyAlgorithm {
|
||||
ECDSA_P384 = "EC_secp384r1"
|
||||
}
|
||||
|
||||
export enum CertKeyUsage {
|
||||
DIGITAL_SIGNATURE = "digitalSignature",
|
||||
KEY_ENCIPHERMENT = "keyEncipherment",
|
||||
NON_REPUDIATION = "nonRepudiation",
|
||||
DATA_ENCIPHERMENT = "dataEncipherment",
|
||||
KEY_AGREEMENT = "keyAgreement",
|
||||
KEY_CERT_SIGN = "keyCertSign",
|
||||
CRL_SIGN = "cRLSign",
|
||||
ENCIPHER_ONLY = "encipherOnly",
|
||||
DECIPHER_ONLY = "decipherOnly"
|
||||
}
|
||||
|
||||
export enum CrlReason {
|
||||
UNSPECIFIED = "UNSPECIFIED",
|
||||
KEY_COMPROMISE = "KEY_COMPROMISE",
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
import { CertKeyUsage } from "../certificates/types";
|
||||
|
||||
export type TCertificateTemplate = {
|
||||
id: string;
|
||||
caId: string;
|
||||
@@ -8,6 +10,7 @@ export type TCertificateTemplate = {
|
||||
commonName: string;
|
||||
subjectAlternativeName: string;
|
||||
ttl: string;
|
||||
keyUsages: CertKeyUsage[];
|
||||
};
|
||||
|
||||
export type TCreateCertificateTemplateDTO = {
|
||||
@@ -18,6 +21,7 @@ export type TCreateCertificateTemplateDTO = {
|
||||
subjectAlternativeName: string;
|
||||
ttl: string;
|
||||
projectId: string;
|
||||
keyUsages: CertKeyUsage[];
|
||||
};
|
||||
|
||||
export type TUpdateCertificateTemplateDTO = {
|
||||
@@ -29,6 +33,7 @@ export type TUpdateCertificateTemplateDTO = {
|
||||
subjectAlternativeName?: string;
|
||||
ttl?: string;
|
||||
projectId: string;
|
||||
keyUsages?: CertKeyUsage[];
|
||||
};
|
||||
|
||||
export type TDeleteCertificateTemplateDTO = {
|
||||
|
||||
@@ -23,3 +23,15 @@ export type TRevokeCertDTO = {
|
||||
serialNumber: string;
|
||||
revocationReason: string;
|
||||
};
|
||||
|
||||
export enum CertKeyUsage {
|
||||
DIGITAL_SIGNATURE = "digitalSignature",
|
||||
KEY_ENCIPHERMENT = "keyEncipherment",
|
||||
NON_REPUDIATION = "nonRepudiation",
|
||||
DATA_ENCIPHERMENT = "dataEncipherment",
|
||||
KEY_AGREEMENT = "keyAgreement",
|
||||
KEY_CERT_SIGN = "keyCertSign",
|
||||
CRL_SIGN = "cRLSign",
|
||||
ENCIPHER_ONLY = "encipherOnly",
|
||||
DECIPHER_ONLY = "decipherOnly"
|
||||
}
|
||||
|
||||
@@ -7,7 +7,12 @@ import { z } from "zod";
|
||||
|
||||
import { createNotification } from "@app/components/notifications";
|
||||
import {
|
||||
Accordion,
|
||||
AccordionContent,
|
||||
AccordionItem,
|
||||
AccordionTrigger,
|
||||
Button,
|
||||
Checkbox,
|
||||
FormControl,
|
||||
FormLabel,
|
||||
Input,
|
||||
@@ -25,8 +30,10 @@ import {
|
||||
useGetCertTemplate,
|
||||
useListWorkspaceCas,
|
||||
useListWorkspacePkiCollections,
|
||||
useUpdateCertTemplate} from "@app/hooks/api";
|
||||
useUpdateCertTemplate
|
||||
} from "@app/hooks/api";
|
||||
import { caTypeToNameMap } from "@app/hooks/api/ca/constants";
|
||||
import { CertKeyUsage } from "@app/hooks/api/certificates/types";
|
||||
import { UsePopUpState } from "@app/hooks/usePopUp";
|
||||
|
||||
const validateTemplateRegexField = z
|
||||
@@ -45,7 +52,18 @@ const schema = z.object({
|
||||
name: z.string().min(1),
|
||||
commonName: validateTemplateRegexField,
|
||||
subjectAlternativeName: validateTemplateRegexField,
|
||||
ttl: z.string().trim().min(1)
|
||||
ttl: z.string().trim().min(1),
|
||||
keyUsages: z.object({
|
||||
[CertKeyUsage.DIGITAL_SIGNATURE]: z.boolean().optional(),
|
||||
[CertKeyUsage.KEY_ENCIPHERMENT]: z.boolean().optional(),
|
||||
[CertKeyUsage.NON_REPUDIATION]: z.boolean().optional(),
|
||||
[CertKeyUsage.DATA_ENCIPHERMENT]: z.boolean().optional(),
|
||||
[CertKeyUsage.KEY_AGREEMENT]: z.boolean().optional(),
|
||||
[CertKeyUsage.KEY_CERT_SIGN]: z.boolean().optional(),
|
||||
[CertKeyUsage.CRL_SIGN]: z.boolean().optional(),
|
||||
[CertKeyUsage.ENCIPHER_ONLY]: z.boolean().optional(),
|
||||
[CertKeyUsage.DECIPHER_ONLY]: z.boolean().optional()
|
||||
})
|
||||
});
|
||||
|
||||
export type FormData = z.infer<typeof schema>;
|
||||
@@ -59,11 +77,23 @@ type Props = {
|
||||
) => void;
|
||||
};
|
||||
|
||||
const KEY_USAGES_OPTIONS = [
|
||||
{ value: CertKeyUsage.DIGITAL_SIGNATURE, label: "Digital Signature" },
|
||||
{ value: CertKeyUsage.KEY_ENCIPHERMENT, label: "Key Encipherment" },
|
||||
{ value: CertKeyUsage.NON_REPUDIATION, label: "Non Repudiation" },
|
||||
{ value: CertKeyUsage.DATA_ENCIPHERMENT, label: "Data Encipherment" },
|
||||
{ value: CertKeyUsage.KEY_AGREEMENT, label: "Key Agreement" },
|
||||
{ value: CertKeyUsage.KEY_CERT_SIGN, label: "Key Certification Sign" },
|
||||
{ value: CertKeyUsage.CRL_SIGN, label: "CRL Sign" },
|
||||
{ value: CertKeyUsage.ENCIPHER_ONLY, label: "Encipher Only" },
|
||||
{ value: CertKeyUsage.DECIPHER_ONLY, label: "Decipher Only" }
|
||||
] as const;
|
||||
|
||||
export const CertificateTemplateModal = ({ popUp, handlePopUpToggle, caId }: Props) => {
|
||||
const { currentWorkspace } = useWorkspace();
|
||||
|
||||
|
||||
const { data: ca } = useGetCaById(caId);
|
||||
|
||||
|
||||
const { data: certTemplate } = useGetCertTemplate(
|
||||
(popUp?.certificateTemplate?.data as { id: string })?.id || ""
|
||||
);
|
||||
@@ -86,7 +116,13 @@ export const CertificateTemplateModal = ({ popUp, handlePopUpToggle, caId }: Pro
|
||||
reset,
|
||||
formState: { isSubmitting }
|
||||
} = useForm<FormData>({
|
||||
resolver: zodResolver(schema)
|
||||
resolver: zodResolver(schema),
|
||||
defaultValues: {
|
||||
keyUsages: {
|
||||
[CertKeyUsage.DIGITAL_SIGNATURE]: true,
|
||||
[CertKeyUsage.KEY_ENCIPHERMENT]: true
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
useEffect(() => {
|
||||
@@ -97,14 +133,19 @@ export const CertificateTemplateModal = ({ popUp, handlePopUpToggle, caId }: Pro
|
||||
commonName: certTemplate.commonName,
|
||||
subjectAlternativeName: certTemplate.subjectAlternativeName,
|
||||
collectionId: certTemplate.pkiCollectionId ?? undefined,
|
||||
ttl: certTemplate.ttl
|
||||
ttl: certTemplate.ttl,
|
||||
keyUsages: Object.fromEntries(certTemplate.keyUsages.map((name) => [name, true]))
|
||||
});
|
||||
} else {
|
||||
reset({
|
||||
caId,
|
||||
name: "",
|
||||
commonName: "",
|
||||
ttl: ""
|
||||
ttl: "",
|
||||
keyUsages: {
|
||||
[CertKeyUsage.DIGITAL_SIGNATURE]: true,
|
||||
[CertKeyUsage.KEY_ENCIPHERMENT]: true
|
||||
}
|
||||
});
|
||||
}
|
||||
}, [certTemplate, ca]);
|
||||
@@ -114,7 +155,8 @@ export const CertificateTemplateModal = ({ popUp, handlePopUpToggle, caId }: Pro
|
||||
name,
|
||||
commonName,
|
||||
subjectAlternativeName,
|
||||
ttl
|
||||
ttl,
|
||||
keyUsages
|
||||
}: FormData) => {
|
||||
if (!currentWorkspace?.id) {
|
||||
return;
|
||||
@@ -130,7 +172,10 @@ export const CertificateTemplateModal = ({ popUp, handlePopUpToggle, caId }: Pro
|
||||
name,
|
||||
commonName,
|
||||
subjectAlternativeName,
|
||||
ttl
|
||||
ttl,
|
||||
keyUsages: Object.entries(keyUsages)
|
||||
.filter(([, value]) => value)
|
||||
.map(([key]) => key as CertKeyUsage)
|
||||
});
|
||||
|
||||
createNotification({
|
||||
@@ -145,7 +190,10 @@ export const CertificateTemplateModal = ({ popUp, handlePopUpToggle, caId }: Pro
|
||||
name,
|
||||
commonName,
|
||||
subjectAlternativeName,
|
||||
ttl
|
||||
ttl,
|
||||
keyUsages: Object.entries(keyUsages)
|
||||
.filter(([, value]) => value)
|
||||
.map(([key]) => key as CertKeyUsage)
|
||||
});
|
||||
|
||||
createNotification({
|
||||
@@ -332,7 +380,50 @@ export const CertificateTemplateModal = ({ popUp, handlePopUpToggle, caId }: Pro
|
||||
</FormControl>
|
||||
)}
|
||||
/>
|
||||
<div className="flex items-center">
|
||||
<Accordion type="single" collapsible className="w-full">
|
||||
<AccordionItem value="key-usages" className="data-[state=open]:border-none">
|
||||
<AccordionTrigger className="h-fit flex-none pl-1 text-sm">
|
||||
<div className="order-1 ml-3">Key Usages</div>
|
||||
</AccordionTrigger>
|
||||
<AccordionContent>
|
||||
<Controller
|
||||
control={control}
|
||||
name="keyUsages"
|
||||
render={({ field: { onChange, value }, fieldState: { error } }) => {
|
||||
return (
|
||||
<FormControl
|
||||
label="Key Usages"
|
||||
errorText={error?.message}
|
||||
isError={Boolean(error)}
|
||||
>
|
||||
<div className="mt-2 mb-7 grid grid-cols-2 gap-2">
|
||||
{KEY_USAGES_OPTIONS.map(({ label, value: optionValue }) => {
|
||||
return (
|
||||
<Checkbox
|
||||
id={optionValue}
|
||||
key={optionValue}
|
||||
className="data-[state=checked]:bg-primary"
|
||||
isChecked={value[optionValue]}
|
||||
onCheckedChange={(state) => {
|
||||
onChange({
|
||||
...value,
|
||||
[optionValue]: state
|
||||
});
|
||||
}}
|
||||
>
|
||||
{label}
|
||||
</Checkbox>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
</FormControl>
|
||||
);
|
||||
}}
|
||||
/>
|
||||
</AccordionContent>
|
||||
</AccordionItem>
|
||||
</Accordion>
|
||||
<div className="mt-4 flex items-center">
|
||||
<Button
|
||||
className="mr-4"
|
||||
size="sm"
|
||||
|
||||
Reference in New Issue
Block a user