diff --git a/backend/src/lib/api-docs/constants.ts b/backend/src/lib/api-docs/constants.ts index 1637b266a..ef5c22a23 100644 --- a/backend/src/lib/api-docs/constants.ts +++ b/backend/src/lib/api-docs/constants.ts @@ -728,6 +728,85 @@ export const AUDIT_LOG_STREAMS = { } }; +// TODO +export const CERTIFICATE_AUTHORITIES = { + CREATE: { + projectSlug: "Slug of the project to create the CA in.", + type: "The type of CA to create (root or intermediate)", + 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. This should be one of RSA_2048, RSA_4096, EC_prime256v1, or EC_secp384r1." + }, + GET: { + caId: "The ID of the CA to get" + }, + UPDATE: { + caId: "The ID of the CA to get", + status: "The status of the CA to update to. This can be one of active or disabled" + }, + DELETE: { + caId: "The ID of the CA to delete" + }, + GET_CSR: { + caId: "The ID of the CA to generate CSR from" + }, + GET_CERT: { + caId: "The ID of the CA to get the certificate body and certificate chain from" + }, + SIGN_INTERMEDIATE: { + caId: "The ID of the CA to sign the intermediate certificate with", + csr: "The CSR to sign with the CA", + notBefore: "The date and time when the intermediate CA becomes valid in YYYY-MM-DDTHH:mm:ss.sssZ format", + notAfter: "The date and time when the intermediate 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." + }, + IMPORT_CERT: { + caId: "The ID of the CA to import the certificate for", + certificate: "The certificate body to import", + certificateChain: "The certificate chain to import" + }, + ISSUE_CERT: { + caId: "The ID of the CA to issue the certificate from", + friendlyName: "A friendly name for the certificate", + commonName: "The common name (CN) for the certificate", + ttl: "The time to live for the certificate such as 1m, 1h, 1d, 1y, ...", + notBefore: "The date and time when the certificate becomes valid in YYYY-MM-DDTHH:mm:ss.sssZ format", + notAfter: "The date and time when the certificate expires in YYYY-MM-DDTHH:mm:ss.sssZ format" + }, + GET_CRL: { + caId: "The ID of the CA to get the certificate revocation list (CRL) for" + } +}; + +export const CERTIFICATES = { + GET: { + serialNumber: "The serial number of the certificate to get" + }, + REVOKE: { + serialNumber: + "The serial number of the certificate to revoke. The revoked certificate will be added to the certificate revocation list (CRL) of the CA.", + revocationReason: + "The reason for revoking the certificate. This can be one of UNSPECIFIED, KEY_COMPROMISE, CA_COMPROMISE, AFFILIATION_CHANGED, SUPERSEDED, CESSATION_OF_OPERATION, CERTIFICATE_HOLD, PRIVILEGE_WITHDRAWN, or A_A_COMPROMISE." + }, + DELETE: { + serialNumber: "The serial number of the certificate to delete" + }, + GET_CERT: { + serialNumber: "The serial number of the certificate to get the certificate body and certificate chain for" + } +}; + export const PROJECT_ROLE = { CREATE: { projectSlug: "Slug of the project to create the role for.", diff --git a/backend/src/server/routes/v1/certificate-authority-router.ts b/backend/src/server/routes/v1/certificate-authority-router.ts index d013bfdad..7bbaa8373 100644 --- a/backend/src/server/routes/v1/certificate-authority-router.ts +++ b/backend/src/server/routes/v1/certificate-authority-router.ts @@ -3,6 +3,7 @@ import { z } from "zod"; import { CertificateAuthoritiesSchema } from "@app/db/schemas"; import { EventType } from "@app/ee/services/audit-log/audit-log-types"; +import { CERTIFICATE_AUTHORITIES } 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"; @@ -22,20 +23,23 @@ export const registerCaRouter = async (server: FastifyZodProvider) => { description: "Create CA", body: z .object({ - projectSlug: z.string().trim(), - type: z.nativeEnum(CaType), - friendlyName: z.string().optional(), - commonName: z.string().trim(), - organization: z.string().trim(), - ou: z.string().trim(), - country: z.string().trim(), - province: z.string().trim(), - locality: z.string().trim(), + projectSlug: z.string().trim().describe(CERTIFICATE_AUTHORITIES.CREATE.projectSlug), + type: z.nativeEnum(CaType).describe(CERTIFICATE_AUTHORITIES.CREATE.type), + friendlyName: z.string().optional().describe(CERTIFICATE_AUTHORITIES.CREATE.friendlyName), + commonName: z.string().trim().describe(CERTIFICATE_AUTHORITIES.CREATE.commonName), + organization: z.string().trim().describe(CERTIFICATE_AUTHORITIES.CREATE.organization), + ou: z.string().trim().describe(CERTIFICATE_AUTHORITIES.CREATE.ou), + country: z.string().trim().describe(CERTIFICATE_AUTHORITIES.CREATE.country), + province: z.string().trim().describe(CERTIFICATE_AUTHORITIES.CREATE.province), + locality: z.string().trim().describe(CERTIFICATE_AUTHORITIES.CREATE.locality), // format: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Date#date_time_string_format - notBefore: validateCaDateField.optional(), - notAfter: validateCaDateField.optional(), - maxPathLength: z.number().min(-1).default(-1), - keyAlgorithm: z.nativeEnum(CertKeyAlgorithm).default(CertKeyAlgorithm.RSA_2048) + notBefore: validateCaDateField.optional().describe(CERTIFICATE_AUTHORITIES.CREATE.notBefore), + notAfter: validateCaDateField.optional().describe(CERTIFICATE_AUTHORITIES.CREATE.notAfter), + maxPathLength: z.number().min(-1).default(-1).describe(CERTIFICATE_AUTHORITIES.CREATE.maxPathLength), + keyAlgorithm: z + .nativeEnum(CertKeyAlgorithm) + .default(CertKeyAlgorithm.RSA_2048) + .describe(CERTIFICATE_AUTHORITIES.CREATE.keyAlgorithm) }) .refine( (data) => { @@ -93,7 +97,7 @@ export const registerCaRouter = async (server: FastifyZodProvider) => { schema: { description: "Get CA", params: z.object({ - caId: z.string().trim() + caId: z.string().trim().describe(CERTIFICATE_AUTHORITIES.GET.caId) }), response: { 200: z.object({ @@ -138,10 +142,10 @@ export const registerCaRouter = async (server: FastifyZodProvider) => { schema: { description: "Update CA", params: z.object({ - caId: z.string().trim() + caId: z.string().trim().describe(CERTIFICATE_AUTHORITIES.UPDATE.caId) }), body: z.object({ - status: z.enum([CaStatus.ACTIVE, CaStatus.DISABLED]).optional() + status: z.enum([CaStatus.ACTIVE, CaStatus.DISABLED]).optional().describe(CERTIFICATE_AUTHORITIES.UPDATE.status) }), response: { 200: z.object({ @@ -188,7 +192,7 @@ export const registerCaRouter = async (server: FastifyZodProvider) => { schema: { description: "Delete CA", params: z.object({ - caId: z.string().trim() + caId: z.string().trim().describe(CERTIFICATE_AUTHORITIES.DELETE.caId) }), response: { 200: z.object({ @@ -278,7 +282,7 @@ export const registerCaRouter = async (server: FastifyZodProvider) => { schema: { description: "Get cert and cert chain of a CA", params: z.object({ - caId: z.string().trim() + caId: z.string().trim().describe(CERTIFICATE_AUTHORITIES.GET_CERT.caId) }), response: { 200: z.object({ @@ -327,13 +331,13 @@ export const registerCaRouter = async (server: FastifyZodProvider) => { schema: { description: "Create intermediate CA certificate from parent CA", params: z.object({ - caId: z.string().trim() + caId: z.string().trim().describe(CERTIFICATE_AUTHORITIES.SIGN_INTERMEDIATE.caId) }), body: z.object({ - csr: z.string().trim(), - notBefore: validateCaDateField.optional(), - notAfter: validateCaDateField, - maxPathLength: z.number().min(-1).default(-1) + csr: z.string().trim().describe(CERTIFICATE_AUTHORITIES.SIGN_INTERMEDIATE.csr), + notBefore: validateCaDateField.optional().describe(CERTIFICATE_AUTHORITIES.SIGN_INTERMEDIATE.notBefore), + notAfter: validateCaDateField.describe(CERTIFICATE_AUTHORITIES.SIGN_INTERMEDIATE.notAfter), + maxPathLength: z.number().min(-1).default(-1).describe(CERTIFICATE_AUTHORITIES.SIGN_INTERMEDIATE.maxPathLength) }), response: { 200: z.object({ @@ -387,11 +391,11 @@ export const registerCaRouter = async (server: FastifyZodProvider) => { schema: { description: "Import certificate and chain to CA", params: z.object({ - caId: z.string().trim() + caId: z.string().trim().describe(CERTIFICATE_AUTHORITIES.IMPORT_CERT.caId) }), body: z.object({ - certificate: z.string().trim(), - certificateChain: z.string().trim() + certificate: z.string().trim().describe(CERTIFICATE_AUTHORITIES.IMPORT_CERT.certificate), + certificateChain: z.string().trim().describe(CERTIFICATE_AUTHORITIES.IMPORT_CERT.certificateChain) }), response: { 200: z.object({ @@ -439,15 +443,18 @@ export const registerCaRouter = async (server: FastifyZodProvider) => { schema: { description: "Issue certificate from CA", params: z.object({ - caId: z.string().trim() + caId: z.string().trim().describe(CERTIFICATE_AUTHORITIES.ISSUE_CERT.caId) }), body: z .object({ - friendlyName: z.string().optional(), - commonName: z.string().trim().min(1), - ttl: z.string().refine((val) => ms(val) > 0, "TTL must be a positive number"), - notBefore: validateCaDateField.optional(), - notAfter: validateCaDateField.optional() + friendlyName: z.string().optional().describe(CERTIFICATE_AUTHORITIES.ISSUE_CERT.friendlyName), + commonName: z.string().trim().min(1).describe(CERTIFICATE_AUTHORITIES.ISSUE_CERT.commonName), + ttl: z + .string() + .refine((val) => ms(val) > 0, "TTL must be a positive number") + .describe(CERTIFICATE_AUTHORITIES.ISSUE_CERT.ttl), + notBefore: validateCaDateField.optional().describe(CERTIFICATE_AUTHORITIES.ISSUE_CERT.notBefore), + notAfter: validateCaDateField.optional().describe(CERTIFICATE_AUTHORITIES.ISSUE_CERT.notAfter) }) .refine( (data) => { @@ -513,7 +520,7 @@ export const registerCaRouter = async (server: FastifyZodProvider) => { schema: { description: "Get CRL of the CA", params: z.object({ - caId: z.string().trim() + caId: z.string().trim().describe(CERTIFICATE_AUTHORITIES.GET_CRL.caId) }), response: { 200: z.object({ diff --git a/backend/src/server/routes/v1/certificate-router.ts b/backend/src/server/routes/v1/certificate-router.ts index 27da18431..056cc74b9 100644 --- a/backend/src/server/routes/v1/certificate-router.ts +++ b/backend/src/server/routes/v1/certificate-router.ts @@ -2,6 +2,7 @@ import { z } from "zod"; import { CertificatesSchema } from "@app/db/schemas"; import { EventType } from "@app/ee/services/audit-log/audit-log-types"; +import { CERTIFICATES } 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"; @@ -18,7 +19,7 @@ export const registerCertRouter = async (server: FastifyZodProvider) => { schema: { description: "Get certificate", params: z.object({ - serialNumber: z.string().trim() + serialNumber: z.string().trim().describe(CERTIFICATES.GET.serialNumber) }), response: { 200: z.object({ @@ -64,10 +65,10 @@ export const registerCertRouter = async (server: FastifyZodProvider) => { schema: { description: "Revoke", params: z.object({ - serialNumber: z.string().trim() + serialNumber: z.string().trim().describe(CERTIFICATES.REVOKE.serialNumber) }), body: z.object({ - revocationReason: z.nativeEnum(CrlReason) + revocationReason: z.nativeEnum(CrlReason).describe(CERTIFICATES.REVOKE.revocationReason) }), response: { 200: z.object({ @@ -118,7 +119,7 @@ export const registerCertRouter = async (server: FastifyZodProvider) => { schema: { description: "Delete certificate", params: z.object({ - serialNumber: z.string().trim() + serialNumber: z.string().trim().describe(CERTIFICATES.DELETE.serialNumber) }), response: { 200: z.object({ @@ -162,9 +163,9 @@ export const registerCertRouter = async (server: FastifyZodProvider) => { }, onRequest: verifyAuth([AuthMode.JWT, AuthMode.IDENTITY_ACCESS_TOKEN]), schema: { - description: "Get certificate of certificate", + description: "Get certificate body of certificate", params: z.object({ - serialNumber: z.string().trim() + serialNumber: z.string().trim().describe(CERTIFICATES.GET_CERT.serialNumber) }), response: { 200: z.object({ diff --git a/docs/api-reference/endpoints/certificate-authority/cert.mdx b/docs/api-reference/endpoints/certificate-authority/cert.mdx new file mode 100644 index 000000000..3706e0b11 --- /dev/null +++ b/docs/api-reference/endpoints/certificate-authority/cert.mdx @@ -0,0 +1,4 @@ +--- +title: "Retrieve certificate / chain" +openapi: "GET /api/v1/pki/ca/{caId}/certificate" +--- diff --git a/docs/api-reference/endpoints/certificate-authority/create.mdx b/docs/api-reference/endpoints/certificate-authority/create.mdx new file mode 100644 index 000000000..35e758e4b --- /dev/null +++ b/docs/api-reference/endpoints/certificate-authority/create.mdx @@ -0,0 +1,4 @@ +--- +title: "Create" +openapi: "POST /api/v1/pki/ca" +--- diff --git a/docs/api-reference/endpoints/certificate-authority/crl.mdx b/docs/api-reference/endpoints/certificate-authority/crl.mdx new file mode 100644 index 000000000..a7b7755de --- /dev/null +++ b/docs/api-reference/endpoints/certificate-authority/crl.mdx @@ -0,0 +1,4 @@ +--- +title: "Retrieve CRL" +openapi: "GET /api/v1/pki/ca/{caId}/crl" +--- diff --git a/docs/api-reference/endpoints/certificate-authority/csr.mdx b/docs/api-reference/endpoints/certificate-authority/csr.mdx new file mode 100644 index 000000000..2477a629e --- /dev/null +++ b/docs/api-reference/endpoints/certificate-authority/csr.mdx @@ -0,0 +1,4 @@ +--- +title: "Get CSR" +openapi: "GET /api/v1/pki/ca/{caId}/csr" +--- diff --git a/docs/api-reference/endpoints/certificate-authority/delete.mdx b/docs/api-reference/endpoints/certificate-authority/delete.mdx new file mode 100644 index 000000000..f79b8f458 --- /dev/null +++ b/docs/api-reference/endpoints/certificate-authority/delete.mdx @@ -0,0 +1,4 @@ +--- +title: "Delete" +openapi: "DELETE /api/v1/pki/ca/{caId}" +--- diff --git a/docs/api-reference/endpoints/certificate-authority/import-cert.mdx b/docs/api-reference/endpoints/certificate-authority/import-cert.mdx new file mode 100644 index 000000000..7f0e40f95 --- /dev/null +++ b/docs/api-reference/endpoints/certificate-authority/import-cert.mdx @@ -0,0 +1,4 @@ +--- +title: "Import certificate" +openapi: "POST /api/v1/pki/ca/{caId}/import-certificate" +--- diff --git a/docs/api-reference/endpoints/certificate-authority/issue-cert.mdx b/docs/api-reference/endpoints/certificate-authority/issue-cert.mdx new file mode 100644 index 000000000..045cada58 --- /dev/null +++ b/docs/api-reference/endpoints/certificate-authority/issue-cert.mdx @@ -0,0 +1,4 @@ +--- +title: "Issue certificate" +openapi: "POST /api/v1/pki/ca/{caId}/issue-certificate" +--- diff --git a/docs/api-reference/endpoints/certificate-authority/read.mdx b/docs/api-reference/endpoints/certificate-authority/read.mdx new file mode 100644 index 000000000..54dc26392 --- /dev/null +++ b/docs/api-reference/endpoints/certificate-authority/read.mdx @@ -0,0 +1,4 @@ +--- +title: "Retrieve" +openapi: "GET /api/v1/pki/ca/{caId}" +--- diff --git a/docs/api-reference/endpoints/certificate-authority/sign-intermediate.mdx b/docs/api-reference/endpoints/certificate-authority/sign-intermediate.mdx new file mode 100644 index 000000000..310bbea26 --- /dev/null +++ b/docs/api-reference/endpoints/certificate-authority/sign-intermediate.mdx @@ -0,0 +1,4 @@ +--- +title: "Sign intermediate certificate" +openapi: "POST /api/v1/pki/ca/{caId}/sign-intermediate" +--- diff --git a/docs/api-reference/endpoints/certificate-authority/update.mdx b/docs/api-reference/endpoints/certificate-authority/update.mdx new file mode 100644 index 000000000..d18a728bf --- /dev/null +++ b/docs/api-reference/endpoints/certificate-authority/update.mdx @@ -0,0 +1,4 @@ +--- +title: "Update" +openapi: "PATCH /api/v1/pki/ca/{caId}" +---