PKI revamp: general improvements

This commit is contained in:
Carlos Monastyrski
2025-10-17 15:47:41 -03:00
parent 4f23e6dc53
commit e195afba11
34 changed files with 601 additions and 579 deletions
@@ -52,7 +52,7 @@ export const PoliciesPage = () => {
/>
<Tabs value={activeTab} onValueChange={(value) => setActiveTab(value as TabSections)}>
<TabList className="mb-6 w-full">
<TabList className="w-full">
<div className="flex w-full border-b border-mineshaft-600">
<Tab value={TabSections.CertificateProfiles}>Certificate Profiles</Tab>
<Tab value={TabSections.CertificateTemplatesV2}>Certificate Templates</Tab>
@@ -27,8 +27,20 @@ import { useListCertificateTemplatesV2 } from "@app/hooks/api/certificateTemplat
const createSchema = z
.object({
slug: z.string().trim().min(1, "Profile slug is required"),
description: z.string().optional(),
slug: z
.string()
.trim()
.min(1, "Profile slug is required")
.max(255, "Profile slug must be less than 255 characters")
.regex(
/^[a-zA-Z0-9-_]+$/,
"Profile slug must contain only letters, numbers, hyphens, and underscores"
),
description: z
.string()
.trim()
.max(1000, "Description must be less than 1000 characters")
.optional(),
enrollmentType: z.enum(["api", "est"]),
certificateAuthorityId: z.string().min(1, "Certificate Authority is required"),
certificateTemplateId: z.string().min(1, "Certificate Template is required"),
@@ -36,8 +48,19 @@ const createSchema = z
.object({
disableBootstrapCaValidation: z.boolean().optional(),
passphrase: z.string().min(1, "EST passphrase is required"),
caChain: z.string().min(1, "EST CA chain is required")
caChain: z.string().min(1, "EST CA chain is required").optional()
})
.refine(
(data) => {
if (!data.disableBootstrapCaValidation && !data.caChain) {
return false;
}
return true;
},
{
message: "EST CA chain is required"
}
)
.optional(),
apiConfig: z
.object({
@@ -63,8 +86,20 @@ const createSchema = z
const editSchema = z
.object({
slug: z.string().trim().min(1, "Profile slug is required"),
description: z.string().optional(),
slug: z
.string()
.trim()
.min(1, "Profile slug is required")
.max(255, "Profile slug must be less than 255 characters")
.regex(
/^[a-zA-Z0-9-_]+$/,
"Profile slug must contain only letters, numbers, hyphens, and underscores"
),
description: z
.string()
.trim()
.max(1000, "Description must be less than 1000 characters")
.optional(),
enrollmentType: z.enum(["api", "est"]),
certificateAuthorityId: z.string().optional(),
certificateTemplateId: z.string().optional(),
@@ -136,7 +171,7 @@ export const CreateProfileModal = ({ isOpen, onClose, profile, mode = "create" }
estConfig: {
disableBootstrapCaValidation: profile.estConfig?.disableBootstrapCaValidation || false,
passphrase: "",
caChain: ""
caChain: undefined
},
apiConfig: {
autoRenew: profile.apiConfig?.autoRenew || false,
@@ -193,7 +228,11 @@ export const CreateProfileModal = ({ isOpen, onClose, profile, mode = "create" }
};
if (data.enrollmentType === "est" && data.estConfig) {
createData.estConfig = data.estConfig;
createData.estConfig = {
passphrase: data.estConfig.passphrase,
caChain: data.estConfig.caChain || "",
disableBootstrapCaValidation: data.estConfig.disableBootstrapCaValidation
};
} else if (data.enrollmentType === "api" && data.apiConfig) {
createData.apiConfig = data.apiConfig;
}
@@ -241,7 +280,7 @@ export const CreateProfileModal = ({ isOpen, onClose, profile, mode = "create" }
name="slug"
render={({ field, fieldState: { error } }) => (
<FormControl
label="Profile Slug"
label="Name"
isRequired
isError={Boolean(error)}
errorText={error?.message}
@@ -150,7 +150,7 @@ export const ProfileRow = ({ profile, onEditProfile, onDeleteProfile }: Props) =
<Tr key={profile.id} className="h-10 transition-colors duration-100 hover:bg-mineshaft-700">
<Td>
<div className="flex items-center gap-2">
<div className="font-medium text-mineshaft-100">{profile.slug}</div>
<div className="text-mineshaft-300">{profile.slug}</div>
{profile.description && (
<Tooltip content={profile.description}>
<FontAwesomeIcon icon={faCircleInfo} className="text-mineshaft-400" />
@@ -362,8 +362,8 @@ export const CreateTemplateModal = ({ isOpen, onClose, template, mode = "create"
return {
name: data.name,
description: data.description,
subject: subject.length > 0 ? subject : undefined,
sans: sans.length > 0 ? sans : undefined,
subject,
sans,
keyUsages: Object.keys(keyUsages).length > 0 ? keyUsages : undefined,
extendedKeyUsages: Object.keys(extendedKeyUsages).length > 0 ? extendedKeyUsages : undefined,
algorithms: Object.keys(algorithms).length > 0 ? algorithms : undefined,
@@ -614,7 +614,7 @@ export const CreateTemplateModal = ({ isOpen, onClose, template, mode = "create"
required
/>
{watchedAttributes.length > 1 && (
{watchedAttributes.length > 0 && (
<IconButton
ariaLabel="Remove Attribute"
variant="plain"
@@ -672,7 +672,7 @@ export const CreateTemplateModal = ({ isOpen, onClose, template, mode = "create"
};
setValue("subjectAlternativeNames", newSans);
}}
className="w-24"
className="w-36"
>
{SAN_TYPE_OPTIONS.map((type) => (
<SelectItem key={type} value={type}>
@@ -719,7 +719,7 @@ export const CreateTemplateModal = ({ isOpen, onClose, template, mode = "create"
required
/>
{watchedSans.length > 1 && (
{watchedSans.length > 0 && (
<IconButton
ariaLabel="Remove SAN"
variant="plain"
@@ -755,7 +755,7 @@ export const CreateTemplateModal = ({ isOpen, onClose, template, mode = "create"
<div className="space-y-6">
<div>
<h4 className="mb-3 text-sm font-medium text-mineshaft-200">
Signature Algorithms
Allowed Signature Algorithms
</h4>
<Controller
control={control}
@@ -799,7 +799,9 @@ export const CreateTemplateModal = ({ isOpen, onClose, template, mode = "create"
</div>
<div>
<h4 className="mb-3 text-sm font-medium text-mineshaft-200">Key Algorithms</h4>
<h4 className="mb-3 text-sm font-medium text-mineshaft-200">
Allowed Key Algorithms
</h4>
<Controller
control={control}
name="keyAlgorithm.allowedKeyTypes"
@@ -89,7 +89,7 @@ export const TemplateList = ({ onEditTemplate, onDeleteTemplate }: Props) => {
>
<Td>
<div className="flex items-center gap-2">
<div className="font-medium">{template.name}</div>
<div className="text-mineshaft-300">{template.name}</div>
{template.description && (
<Tooltip content={template.description}>
<FontAwesomeIcon icon={faCircleInfo} className="text-mineshaft-400" />
@@ -50,8 +50,20 @@ export const uiKeyAlgorithmSchema = z.object({
});
export const templateSchema = z.object({
name: z.string().trim().min(1, "Template name is required"),
description: z.string().optional(),
name: z
.string()
.trim()
.min(1, "Template name is required")
.max(255, "Template name must be less than 255 characters")
.regex(
/^[a-zA-Z0-9-_]+$/,
"Template name must contain only letters, numbers, hyphens, and underscores"
),
description: z
.string()
.trim()
.max(1000, "Description must be less than 1000 characters")
.optional(),
attributes: z.array(uiAttributeSchema).optional(),
subjectAlternativeNames: z.array(uiSanSchema).optional(),
keyUsages: uiKeyUsagesSchema.optional(),
@@ -86,8 +98,20 @@ export const apiSanSchema = z
});
export const apiTemplateSchema = z.object({
name: z.string().trim().min(1, "Template name is required"),
description: z.string().optional(),
name: z
.string()
.trim()
.min(1, "Template name is required")
.max(255, "Template name must be less than 255 characters")
.regex(
/^[a-zA-Z0-9-_]+$/,
"Template name must contain only letters, numbers, hyphens, and underscores"
),
description: z
.string()
.trim()
.max(1000, "Description must be less than 1000 characters")
.optional(),
subject: z.array(apiSubjectSchema).optional(),
sans: z.array(apiSanSchema).optional(),
keyUsages: z
@@ -106,14 +130,15 @@ export const apiTemplateSchema = z.object({
.optional(),
algorithms: z
.object({
signature: z.array(z.string()).optional(),
keyAlgorithm: z.array(z.string()).optional()
signature: z.array(z.string().trim().min(1, "Algorithm cannot be empty")).optional(),
keyAlgorithm: z.array(z.string().trim().min(1, "Algorithm cannot be empty")).optional()
})
.optional(),
validity: z
.object({
max: z
.string()
.trim()
.regex(/^[1-9]\d*[dhmy]$/, "Must be in format like '365d', '12m', '1y', or '24h'")
.optional()
})