Certificate Profiles
Certificate Templates
diff --git a/frontend/src/pages/cert-manager/PoliciesPage/components/CertificateProfilesTab/CreateProfileModal.tsx b/frontend/src/pages/cert-manager/PoliciesPage/components/CertificateProfilesTab/CreateProfileModal.tsx
index 884e663de..55262439a 100644
--- a/frontend/src/pages/cert-manager/PoliciesPage/components/CertificateProfilesTab/CreateProfileModal.tsx
+++ b/frontend/src/pages/cert-manager/PoliciesPage/components/CertificateProfilesTab/CreateProfileModal.tsx
@@ -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 } }) => (
- {profile.slug}
+ {profile.slug}
{profile.description && (
diff --git a/frontend/src/pages/cert-manager/PoliciesPage/components/CertificateTemplatesV2Tab/CreateTemplateModal.tsx b/frontend/src/pages/cert-manager/PoliciesPage/components/CertificateTemplatesV2Tab/CreateTemplateModal.tsx
index 4abe390a7..909a2e8ac 100644
--- a/frontend/src/pages/cert-manager/PoliciesPage/components/CertificateTemplatesV2Tab/CreateTemplateModal.tsx
+++ b/frontend/src/pages/cert-manager/PoliciesPage/components/CertificateTemplatesV2Tab/CreateTemplateModal.tsx
@@ -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 && (
{SAN_TYPE_OPTIONS.map((type) => (
@@ -719,7 +719,7 @@ export const CreateTemplateModal = ({ isOpen, onClose, template, mode = "create"
required
/>
- {watchedSans.length > 1 && (
+ {watchedSans.length > 0 && (
- Signature Algorithms
+ Allowed Signature Algorithms
- Key Algorithms
+
+ Allowed Key Algorithms
+
{
>
- {template.name}
+ {template.name}
{template.description && (
diff --git a/frontend/src/pages/cert-manager/PoliciesPage/components/CertificateTemplatesV2Tab/shared/schemas.ts b/frontend/src/pages/cert-manager/PoliciesPage/components/CertificateTemplatesV2Tab/shared/schemas.ts
index b4f6fc9eb..861f4e5d9 100644
--- a/frontend/src/pages/cert-manager/PoliciesPage/components/CertificateTemplatesV2Tab/shared/schemas.ts
+++ b/frontend/src/pages/cert-manager/PoliciesPage/components/CertificateTemplatesV2Tab/shared/schemas.ts
@@ -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()
})
diff --git a/frontend/src/pages/project/RoleDetailsBySlugPage/components/ProjectRoleModifySection.utils.tsx b/frontend/src/pages/project/RoleDetailsBySlugPage/components/ProjectRoleModifySection.utils.tsx
index ec5bfc62a..efd7b123b 100644
--- a/frontend/src/pages/project/RoleDetailsBySlugPage/components/ProjectRoleModifySection.utils.tsx
+++ b/frontend/src/pages/project/RoleDetailsBySlugPage/components/ProjectRoleModifySection.utils.tsx
@@ -393,12 +393,8 @@ export const projectRoleFormSchema = z.object({
})
.array()
.default([]),
- [ProjectPermissionSub.CertificateProfiles]: CertificateProfilePolicyActionSchema.extend({
- inverted: z.boolean().optional(),
- conditions: ConditionSchema
- })
- .array()
- .default([]),
+ [ProjectPermissionSub.CertificateProfiles]:
+ CertificateProfilePolicyActionSchema.array().default([]),
[ProjectPermissionSub.SshCertificateAuthorities]: GeneralPolicyActionSchema.array().default(
[]
),
@@ -479,7 +475,6 @@ export const isConditionalSubjects = (
subject === ProjectPermissionSub.SecretRotation ||
subject === ProjectPermissionSub.PkiSubscribers ||
subject === ProjectPermissionSub.CertificateTemplates ||
- subject === ProjectPermissionSub.CertificateProfiles ||
subject === ProjectPermissionSub.SecretSyncs ||
subject === ProjectPermissionSub.PkiSyncs ||
subject === ProjectPermissionSub.SecretEvents ||
@@ -1176,9 +1171,7 @@ export const rolePermission2Form = (permissions: TProjectPermission[] = []) => {
),
[ProjectPermissionCertificateProfileActions.IssueCert]: action.includes(
ProjectPermissionCertificateProfileActions.IssueCert
- ),
- conditions: conditions ? convertCaslConditionToFormOperator(conditions) : [],
- inverted
+ )
});
return;
@@ -2195,6 +2188,10 @@ export const RoleTemplates: Record = {
{
subject: ProjectPermissionSub.PkiSyncs,
actions: [ProjectPermissionPkiSyncActions.Read]
+ },
+ {
+ subject: ProjectPermissionSub.CertificateProfiles,
+ actions: [ProjectPermissionCertificateProfileActions.Read]
}
]
},
@@ -2226,6 +2223,10 @@ export const RoleTemplates: Record = {
{
subject: ProjectPermissionSub.PkiSyncs,
actions: Object.values(ProjectPermissionPkiSyncActions)
+ },
+ {
+ subject: ProjectPermissionSub.CertificateProfiles,
+ actions: Object.values(ProjectPermissionCertificateProfileActions)
}
]
},
| |