diff --git a/backend/src/lib/api-docs/constants.ts b/backend/src/lib/api-docs/constants.ts index 9183751b1..4ac675d17 100644 --- a/backend/src/lib/api-docs/constants.ts +++ b/backend/src/lib/api-docs/constants.ts @@ -5,6 +5,8 @@ import { } from "@app/ee/services/secret-rotation-v2/secret-rotation-v2-maps"; import { AppConnection } from "@app/services/app-connection/app-connection-enums"; import { APP_CONNECTION_NAME_MAP } from "@app/services/app-connection/app-connection-maps"; +import { CaType } from "@app/services/certificate-authority/certificate-authority-enums"; +import { CERTIFICATE_AUTHORITIES_TYPE_MAP } from "@app/services/certificate-authority/certificate-authority-maps"; import { SecretSync } from "@app/services/secret-sync/secret-sync-enums"; import { SECRET_SYNC_CONNECTION_MAP, SECRET_SYNC_NAME_MAP } from "@app/services/secret-sync/secret-sync-maps"; @@ -1966,6 +1968,46 @@ export const ProjectTemplates = { } }; +export const CertificateAuthorities = { + CREATE: (type: CaType) => ({ + name: `The name of the ${CERTIFICATE_AUTHORITIES_TYPE_MAP[type]} Certificate Authority to create. Must be slug-friendly.`, + projectId: `The ID of the project to create the Certificate Authority in.`, + disableDirectIssuance: `Whether or not to disable direct issuance of certificates for the ${CERTIFICATE_AUTHORITIES_TYPE_MAP[type]} Certificate Authority.`, + status: `The status of the ${CERTIFICATE_AUTHORITIES_TYPE_MAP[type]} Certificate Authority.` + }), + UPDATE: (type: CaType) => ({ + caId: `The ID of the ${CERTIFICATE_AUTHORITIES_TYPE_MAP[type]} Certificate Authority to update.`, + name: `The updated name of the ${CERTIFICATE_AUTHORITIES_TYPE_MAP[type]} Certificate Authority. Must be slug-friendly.`, + disableDirectIssuance: `Whether or not to disable direct issuance of certificates for the ${CERTIFICATE_AUTHORITIES_TYPE_MAP[type]} Certificate Authority.`, + status: `The updated status of the ${CERTIFICATE_AUTHORITIES_TYPE_MAP[type]} Certificate Authority.` + }), + CONFIGURATIONS: { + ACME: { + dnsAppConnectionId: `The ID of the App Connection to use for creating and managing DNS TXT records required for ACME domain validation. This connection must have permissions to create and delete TXT records in your DNS provider (e.g., Route53) for the ACME challenge process.`, + directoryUrl: `The directory URL for the ACME Certificate Authority.`, + accountEmail: `The email address for the ACME Certificate Authority.`, + provider: `The DNS provider for the ACME Certificate Authority.`, + hostedZoneId: `The hosted zone ID for the ACME Certificate Authority.` + }, + INTERNAL: { + type: "The type of CA to create.", + friendlyName: "A friendly name for the CA.", + organization: "The organization (O) for the CA.", + ou: "The organization unit (OU) for the CA.", + country: "The country name (C) for the CA.", + province: "The state of province name for the CA.", + locality: "The locality name for the CA.", + commonName: "The common name (CN) for the CA.", + notBefore: "The date and time when the CA becomes valid in YYYY-MM-DDTHH:mm:ss.sssZ format.", + notAfter: "The date and time when the CA expires in YYYY-MM-DDTHH:mm:ss.sssZ format.", + maxPathLength: + "The maximum number of intermediate CAs that may follow this CA in the certificate / CA chain. A maxPathLength of -1 implies no path limit on the chain.", + keyAlgorithm: + "The type of public key algorithm and size, in bits, of the key pair for the CA; when you create an intermediate CA, you must use a key algorithm supported by the parent CA." + } + } +}; + export const AppConnections = { GET_BY_ID: (app: AppConnection) => ({ connectionId: `The ID of the ${APP_CONNECTION_NAME_MAP[app]} Connection to retrieve.` diff --git a/backend/src/services/certificate-authority/acme/acme-certificate-authority-schemas.ts b/backend/src/services/certificate-authority/acme/acme-certificate-authority-schemas.ts index 0acc2d958..44990feb6 100644 --- a/backend/src/services/certificate-authority/acme/acme-certificate-authority-schemas.ts +++ b/backend/src/services/certificate-authority/acme/acme-certificate-authority-schemas.ts @@ -1,5 +1,7 @@ import { z } from "zod"; +import { CertificateAuthorities } from "@app/lib/api-docs/constants"; + import { CaType } from "../certificate-authority-enums"; import { BaseCertificateAuthoritySchema, @@ -9,21 +11,21 @@ import { import { AcmeDnsProvider } from "./acme-certificate-authority-enums"; export const AcmeCertificateAuthorityConfigurationSchema = z.object({ - dnsAppConnectionId: z.string().trim(), + dnsAppConnectionId: z.string().trim().describe(CertificateAuthorities.CONFIGURATIONS.ACME.dnsAppConnectionId), // soon, differentiate via the provider property dnsProviderConfig: z.object({ - provider: z.nativeEnum(AcmeDnsProvider), - hostedZoneId: z.string().trim().min(1) + provider: z.nativeEnum(AcmeDnsProvider).describe(CertificateAuthorities.CONFIGURATIONS.ACME.provider), + hostedZoneId: z.string().trim().min(1).describe(CertificateAuthorities.CONFIGURATIONS.ACME.hostedZoneId) }), - directoryUrl: z.string().trim().min(1), - accountEmail: z.string().trim().min(1) + directoryUrl: z.string().trim().min(1).describe(CertificateAuthorities.CONFIGURATIONS.ACME.directoryUrl), + accountEmail: z.string().trim().min(1).describe(CertificateAuthorities.CONFIGURATIONS.ACME.accountEmail) }); export const AcmeCertificateAuthorityCredentialsSchema = z.object({ accountKey: z.string() }); -export const AcmeCertificateAuthoritySchema = BaseCertificateAuthoritySchema(CaType.ACME).extend({ +export const AcmeCertificateAuthoritySchema = BaseCertificateAuthoritySchema.extend({ type: z.literal(CaType.ACME), configuration: AcmeCertificateAuthorityConfigurationSchema }); diff --git a/backend/src/services/certificate-authority/certificate-authority-maps.ts b/backend/src/services/certificate-authority/certificate-authority-maps.ts new file mode 100644 index 000000000..d13f65138 --- /dev/null +++ b/backend/src/services/certificate-authority/certificate-authority-maps.ts @@ -0,0 +1,6 @@ +import { CaType } from "./certificate-authority-enums"; + +export const CERTIFICATE_AUTHORITIES_TYPE_MAP: Record = { + [CaType.INTERNAL]: "Internal", + [CaType.ACME]: "ACME" +}; diff --git a/backend/src/services/certificate-authority/certificate-authority-schemas.ts b/backend/src/services/certificate-authority/certificate-authority-schemas.ts index 75c2a75af..1c7832b70 100644 --- a/backend/src/services/certificate-authority/certificate-authority-schemas.ts +++ b/backend/src/services/certificate-authority/certificate-authority-schemas.ts @@ -1,32 +1,31 @@ import z from "zod"; import { CertificateAuthoritiesSchema } from "@app/db/schemas"; +import { CertificateAuthorities } from "@app/lib/api-docs/constants"; import { slugSchema } from "@app/server/lib/schemas"; import { CaStatus, CaType } from "./certificate-authority-enums"; -// SHEEN TODO: add description mapping using type -export const BaseCertificateAuthoritySchema = (type: CaType) => - CertificateAuthoritiesSchema.pick({ - projectId: true, - disableDirectIssuance: true, - id: true - }).extend({ - name: z.string(), - status: z.nativeEnum(CaStatus) - }); +export const BaseCertificateAuthoritySchema = CertificateAuthoritiesSchema.pick({ + projectId: true, + disableDirectIssuance: true, + id: true +}).extend({ + name: z.string(), + status: z.nativeEnum(CaStatus) +}); export const GenericCreateCertificateAuthorityFieldsSchema = (type: CaType) => z.object({ - name: slugSchema({ field: "name" }), - projectId: z.string().trim().min(1, "Project ID required"), - disableDirectIssuance: z.boolean(), - status: z.nativeEnum(CaStatus) + name: slugSchema({ field: "name" }).describe(CertificateAuthorities.CREATE(type).name), + projectId: z.string().trim().min(1, "Project ID required").describe(CertificateAuthorities.CREATE(type).projectId), + disableDirectIssuance: z.boolean().describe(CertificateAuthorities.CREATE(type).disableDirectIssuance), + status: z.nativeEnum(CaStatus).describe(CertificateAuthorities.CREATE(type).status) }); export const GenericUpdateCertificateAuthorityFieldsSchema = (type: CaType) => z.object({ - name: slugSchema({ field: "name" }).optional(), - disableDirectIssuance: z.boolean().optional(), - status: z.nativeEnum(CaStatus).optional() + name: slugSchema({ field: "name" }).optional().describe(CertificateAuthorities.UPDATE(type).name), + disableDirectIssuance: z.boolean().optional().describe(CertificateAuthorities.UPDATE(type).disableDirectIssuance), + status: z.nativeEnum(CaStatus).optional().describe(CertificateAuthorities.UPDATE(type).status) }); diff --git a/backend/src/services/certificate-authority/internal/internal-certificate-authority-schemas.ts b/backend/src/services/certificate-authority/internal/internal-certificate-authority-schemas.ts index 73a6c0f0d..0979b968f 100644 --- a/backend/src/services/certificate-authority/internal/internal-certificate-authority-schemas.ts +++ b/backend/src/services/certificate-authority/internal/internal-certificate-authority-schemas.ts @@ -1,5 +1,6 @@ import { z } from "zod"; +import { CertificateAuthorities } from "@app/lib/api-docs/constants"; import { CertKeyAlgorithm } from "@app/services/certificate/certificate-types"; import { CaType, InternalCaType } from "../certificate-authority-enums"; @@ -12,22 +13,24 @@ import { validateCaDateField } from "../certificate-authority-validators"; const InternalCertificateAuthorityConfigurationSchema = z .object({ - type: z.nativeEnum(InternalCaType), - friendlyName: z.string().optional(), - commonName: z.string().trim(), - organization: z.string().trim(), - ou: z.string().trim(), + type: z.nativeEnum(InternalCaType).describe(CertificateAuthorities.CONFIGURATIONS.INTERNAL.type), + friendlyName: z.string().optional().describe(CertificateAuthorities.CONFIGURATIONS.INTERNAL.friendlyName), + commonName: z.string().trim().describe(CertificateAuthorities.CONFIGURATIONS.INTERNAL.commonName), + organization: z.string().trim().describe(CertificateAuthorities.CONFIGURATIONS.INTERNAL.organization), + ou: z.string().trim().describe(CertificateAuthorities.CONFIGURATIONS.INTERNAL.ou), + country: z.string().trim().describe(CertificateAuthorities.CONFIGURATIONS.INTERNAL.country), + province: z.string().trim().describe(CertificateAuthorities.CONFIGURATIONS.INTERNAL.province), + locality: z.string().trim().describe(CertificateAuthorities.CONFIGURATIONS.INTERNAL.locality), + notBefore: validateCaDateField.optional().describe(CertificateAuthorities.CONFIGURATIONS.INTERNAL.notBefore), + notAfter: validateCaDateField.optional().describe(CertificateAuthorities.CONFIGURATIONS.INTERNAL.notAfter), + maxPathLength: z.number().min(-1).describe(CertificateAuthorities.CONFIGURATIONS.INTERNAL.maxPathLength), + keyAlgorithm: z.nativeEnum(CertKeyAlgorithm).describe(CertificateAuthorities.CONFIGURATIONS.INTERNAL.keyAlgorithm), + + // no need for descriptions of the following fields because they are not exposed to the API user dn: z.string().trim(), parentCaId: z.string().uuid().nullish(), serialNumber: z.string().trim().optional(), - activeCaCertId: z.string().uuid().optional(), - country: z.string().trim(), - province: z.string().trim(), - locality: z.string().trim(), - notBefore: validateCaDateField.optional(), - notAfter: validateCaDateField.optional(), - maxPathLength: z.number().min(-1), - keyAlgorithm: z.nativeEnum(CertKeyAlgorithm) + activeCaCertId: z.string().uuid().optional() }) .refine( (data) => { @@ -43,7 +46,7 @@ const InternalCertificateAuthorityConfigurationSchema = z } ); -export const InternalCertificateAuthoritySchema = BaseCertificateAuthoritySchema(CaType.INTERNAL).extend({ +export const InternalCertificateAuthoritySchema = BaseCertificateAuthoritySchema.extend({ type: z.literal(CaType.INTERNAL), configuration: InternalCertificateAuthorityConfigurationSchema }); diff --git a/docs/api-reference/endpoints/certificate-authorities/acme/create.mdx b/docs/api-reference/endpoints/certificate-authorities/acme/create.mdx new file mode 100644 index 000000000..9cc42ed7f --- /dev/null +++ b/docs/api-reference/endpoints/certificate-authorities/acme/create.mdx @@ -0,0 +1,4 @@ +--- +title: "Create" +openapi: "POST /api/v1/pki/ca/acme" +--- diff --git a/docs/api-reference/endpoints/certificate-authorities/acme/delete.mdx b/docs/api-reference/endpoints/certificate-authorities/acme/delete.mdx new file mode 100644 index 000000000..666a01dfe --- /dev/null +++ b/docs/api-reference/endpoints/certificate-authorities/acme/delete.mdx @@ -0,0 +1,4 @@ +--- +title: "Delete" +openapi: "DELETE /api/v1/pki/ca/acme/{caId}" +--- diff --git a/docs/api-reference/endpoints/certificate-authorities/acme/get-by-id.mdx b/docs/api-reference/endpoints/certificate-authorities/acme/get-by-id.mdx new file mode 100644 index 000000000..4aabbfc04 --- /dev/null +++ b/docs/api-reference/endpoints/certificate-authorities/acme/get-by-id.mdx @@ -0,0 +1,4 @@ +--- +title: "Get By ID" +openapi: "GET /api/v1/pki/ca/acme/{caId}" +--- diff --git a/docs/api-reference/endpoints/certificate-authorities/acme/list.mdx b/docs/api-reference/endpoints/certificate-authorities/acme/list.mdx new file mode 100644 index 000000000..35bd70727 --- /dev/null +++ b/docs/api-reference/endpoints/certificate-authorities/acme/list.mdx @@ -0,0 +1,4 @@ +--- +title: "List" +openapi: "GET /api/v1/pki/ca/acme" +--- diff --git a/docs/api-reference/endpoints/certificate-authorities/acme/update.mdx b/docs/api-reference/endpoints/certificate-authorities/acme/update.mdx new file mode 100644 index 000000000..46f67b93b --- /dev/null +++ b/docs/api-reference/endpoints/certificate-authorities/acme/update.mdx @@ -0,0 +1,4 @@ +--- +title: "Update" +openapi: "PATCH /api/v1/pki/ca/acme/{caId}" +--- diff --git a/docs/mint.json b/docs/mint.json index 6cf89682b..ea3d3312b 100644 --- a/docs/mint.json +++ b/docs/mint.json @@ -1474,6 +1474,16 @@ { "group": "Certificate Authorities", "pages": [ + { + "group": "ACME", + "pages": [ + "api-reference/endpoints/certificate-authorities/acme/list", + "api-reference/endpoints/certificate-authorities/acme/create", + "api-reference/endpoints/certificate-authorities/acme/read", + "api-reference/endpoints/certificate-authorities/acme/update", + "api-reference/endpoints/certificate-authorities/acme/delete" + ] + }, "api-reference/endpoints/certificate-authorities/list", "api-reference/endpoints/certificate-authorities/create", "api-reference/endpoints/certificate-authorities/read", diff --git a/frontend/src/pages/cert-manager/CertificateAuthoritiesPage/components/ExternalCaModal.tsx b/frontend/src/pages/cert-manager/CertificateAuthoritiesPage/components/ExternalCaModal.tsx index 608a2c403..8bb9a872c 100644 --- a/frontend/src/pages/cert-manager/CertificateAuthoritiesPage/components/ExternalCaModal.tsx +++ b/frontend/src/pages/cert-manager/CertificateAuthoritiesPage/components/ExternalCaModal.tsx @@ -27,11 +27,14 @@ import { useUpdateUnifiedCa } from "@app/hooks/api/ca"; import { UsePopUpState } from "@app/hooks/usePopUp"; +import { slugSchema } from "@app/lib/schemas"; const schema = z .object({ type: z.nativeEnum(CaType), - name: z.string(), + name: slugSchema({ + field: "Name" + }), disableDirectIssuance: z.boolean(), status: z.nativeEnum(CaStatus), configuration: z.object({