Merge pull request #3583 from Infisical/feat/acme-and-external-ca

feat: acme and external CA for PKI
This commit is contained in:
Sheen
2025-05-27 03:47:36 +08:00
committed by GitHub
128 changed files with 10602 additions and 3021 deletions
@@ -1,7 +1,7 @@
/* eslint-disable @typescript-eslint/no-floating-promises */
import { z } from "zod";
import { CertificateAuthoritiesSchema, CertificateTemplatesSchema } from "@app/db/schemas";
import { CertificateTemplatesSchema } from "@app/db/schemas";
import { EventType } from "@app/ee/services/audit-log/audit-log-types";
import { ApiDocsTags, CERTIFICATE_AUTHORITIES } from "@app/lib/api-docs";
import { ms } from "@app/lib/ms";
@@ -10,13 +10,19 @@ import { getTelemetryDistinctId } from "@app/server/lib/telemetry";
import { verifyAuth } from "@app/server/plugins/auth/verify-auth";
import { AuthMode } from "@app/services/auth/auth-type";
import { CertExtendedKeyUsage, CertKeyAlgorithm, CertKeyUsage } from "@app/services/certificate/certificate-types";
import { CaRenewalType, CaStatus, CaType } from "@app/services/certificate-authority/certificate-authority-types";
import {
CaRenewalType,
CaStatus,
InternalCaType
} from "@app/services/certificate-authority/certificate-authority-enums";
import {
validateAltNamesField,
validateCaDateField
} from "@app/services/certificate-authority/certificate-authority-validators";
import { PostHogEventTypes } from "@app/services/telemetry/telemetry-types";
import { InternalCertificateAuthorityResponseSchema } from "../sanitizedSchemas";
export const registerCaRouter = async (server: FastifyZodProvider) => {
server.route({
method: "POST",
@@ -32,7 +38,7 @@ export const registerCaRouter = async (server: FastifyZodProvider) => {
body: z
.object({
projectSlug: z.string().trim().describe(CERTIFICATE_AUTHORITIES.CREATE.projectSlug),
type: z.nativeEnum(CaType).describe(CERTIFICATE_AUTHORITIES.CREATE.type),
type: z.nativeEnum(InternalCaType).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),
@@ -68,16 +74,18 @@ export const registerCaRouter = async (server: FastifyZodProvider) => {
),
response: {
200: z.object({
ca: CertificateAuthoritiesSchema
ca: InternalCertificateAuthorityResponseSchema
})
}
},
handler: async (req) => {
const ca = await server.services.certificateAuthority.createCa({
const ca = await server.services.internalCertificateAuthority.createCa({
actor: req.permission.type,
actorId: req.permission.id,
actorAuthMethod: req.permission.authMethod,
isInternal: false,
actorOrgId: req.permission.orgId,
enableDirectIssuance: !req.body.requireTemplateForIssuance,
...req.body
});
@@ -87,6 +95,7 @@ export const registerCaRouter = async (server: FastifyZodProvider) => {
event: {
type: EventType.CREATE_CA,
metadata: {
name: ca.name,
caId: ca.id,
dn: ca.dn
}
@@ -115,12 +124,12 @@ export const registerCaRouter = async (server: FastifyZodProvider) => {
}),
response: {
200: z.object({
ca: CertificateAuthoritiesSchema
ca: InternalCertificateAuthorityResponseSchema
})
}
},
handler: async (req) => {
const ca = await server.services.certificateAuthority.getCaById({
const ca = await server.services.internalCertificateAuthority.getCaById({
caId: req.params.caId,
actor: req.permission.type,
actorId: req.permission.id,
@@ -135,6 +144,7 @@ export const registerCaRouter = async (server: FastifyZodProvider) => {
type: EventType.GET_CA,
metadata: {
caId: ca.id,
name: ca.name,
dn: ca.dn
}
}
@@ -167,7 +177,7 @@ export const registerCaRouter = async (server: FastifyZodProvider) => {
}
},
handler: async (req, res) => {
const caCert = await server.services.certificateAuthority.getCaCertById(req.params);
const caCert = await server.services.internalCertificateAuthority.getCaCertById(req.params);
res.header("Content-Type", "application/pkix-cert");
@@ -198,17 +208,19 @@ export const registerCaRouter = async (server: FastifyZodProvider) => {
}),
response: {
200: z.object({
ca: CertificateAuthoritiesSchema
ca: InternalCertificateAuthorityResponseSchema
})
}
},
handler: async (req) => {
const ca = await server.services.certificateAuthority.updateCaById({
const ca = await server.services.internalCertificateAuthority.updateCaById({
caId: req.params.caId,
actor: req.permission.type,
actorId: req.permission.id,
isInternal: false,
actorAuthMethod: req.permission.authMethod,
actorOrgId: req.permission.orgId,
enableDirectIssuance: !req.body.requireTemplateForIssuance,
...req.body
});
@@ -220,6 +232,7 @@ export const registerCaRouter = async (server: FastifyZodProvider) => {
metadata: {
caId: ca.id,
dn: ca.dn,
name: ca.name,
status: ca.status as CaStatus
}
}
@@ -247,12 +260,12 @@ export const registerCaRouter = async (server: FastifyZodProvider) => {
}),
response: {
200: z.object({
ca: CertificateAuthoritiesSchema
ca: InternalCertificateAuthorityResponseSchema
})
}
},
handler: async (req) => {
const ca = await server.services.certificateAuthority.deleteCaById({
const ca = await server.services.internalCertificateAuthority.deleteCaById({
caId: req.params.caId,
actor: req.permission.type,
actorId: req.permission.id,
@@ -266,6 +279,7 @@ export const registerCaRouter = async (server: FastifyZodProvider) => {
event: {
type: EventType.DELETE_CA,
metadata: {
name: ca.name,
caId: ca.id,
dn: ca.dn
}
@@ -299,7 +313,7 @@ export const registerCaRouter = async (server: FastifyZodProvider) => {
}
},
handler: async (req) => {
const { ca, csr } = await server.services.certificateAuthority.getCaCsr({
const { ca, csr } = await server.services.internalCertificateAuthority.getCaCsr({
caId: req.params.caId,
actor: req.permission.type,
actorId: req.permission.id,
@@ -353,7 +367,7 @@ export const registerCaRouter = async (server: FastifyZodProvider) => {
},
handler: async (req) => {
const { certificate, certificateChain, serialNumber, ca } =
await server.services.certificateAuthority.renewCaCert({
await server.services.internalCertificateAuthority.renewCaCert({
caId: req.params.caId,
actor: req.permission.type,
actorId: req.permission.id,
@@ -408,7 +422,7 @@ export const registerCaRouter = async (server: FastifyZodProvider) => {
}
},
handler: async (req) => {
const { caCerts, ca } = await server.services.certificateAuthority.getCaCerts({
const { caCerts, ca } = await server.services.internalCertificateAuthority.getCaCerts({
caId: req.params.caId,
actor: req.permission.type,
actorId: req.permission.id,
@@ -455,13 +469,14 @@ export const registerCaRouter = async (server: FastifyZodProvider) => {
}
},
handler: async (req) => {
const { certificate, certificateChain, serialNumber, ca } = await server.services.certificateAuthority.getCaCert({
caId: req.params.caId,
actor: req.permission.type,
actorId: req.permission.id,
actorAuthMethod: req.permission.authMethod,
actorOrgId: req.permission.orgId
});
const { certificate, certificateChain, serialNumber, ca } =
await server.services.internalCertificateAuthority.getCaCert({
caId: req.params.caId,
actor: req.permission.type,
actorId: req.permission.id,
actorAuthMethod: req.permission.authMethod,
actorOrgId: req.permission.orgId
});
await server.services.auditLog.createAuditLog({
...req.auditLogInfo,
@@ -517,7 +532,7 @@ export const registerCaRouter = async (server: FastifyZodProvider) => {
},
handler: async (req) => {
const { certificate, certificateChain, issuingCaCertificate, serialNumber, ca } =
await server.services.certificateAuthority.signIntermediate({
await server.services.internalCertificateAuthority.signIntermediate({
caId: req.params.caId,
actor: req.permission.type,
actorId: req.permission.id,
@@ -574,7 +589,7 @@ export const registerCaRouter = async (server: FastifyZodProvider) => {
}
},
handler: async (req) => {
const { ca } = await server.services.certificateAuthority.importCertToCa({
const { ca } = await server.services.internalCertificateAuthority.importCertToCa({
caId: req.params.caId,
actor: req.permission.type,
actorId: req.permission.id,
@@ -653,7 +668,7 @@ export const registerCaRouter = async (server: FastifyZodProvider) => {
},
handler: async (req) => {
const { certificate, certificateChain, issuingCaCertificate, privateKey, serialNumber, ca } =
await server.services.certificateAuthority.issueCertFromCa({
await server.services.internalCertificateAuthority.issueCertFromCa({
caId: req.params.caId,
actor: req.permission.type,
actorId: req.permission.id,
@@ -746,7 +761,7 @@ export const registerCaRouter = async (server: FastifyZodProvider) => {
},
handler: async (req) => {
const { certificate, certificateChain, issuingCaCertificate, serialNumber, ca, commonName } =
await server.services.certificateAuthority.signCertFromCa({
await server.services.internalCertificateAuthority.signCertFromCa({
isInternal: false,
caId: req.params.caId,
actor: req.permission.type,
@@ -809,13 +824,15 @@ export const registerCaRouter = async (server: FastifyZodProvider) => {
}
},
handler: async (req) => {
const { certificateTemplates, ca } = await server.services.certificateAuthority.getCaCertificateTemplates({
caId: req.params.caId,
actor: req.permission.type,
actorId: req.permission.id,
actorAuthMethod: req.permission.authMethod,
actorOrgId: req.permission.orgId
});
const { certificateTemplates, ca } = await server.services.internalCertificateAuthority.getCaCertificateTemplates(
{
caId: req.params.caId,
actor: req.permission.type,
actorId: req.permission.id,
actorAuthMethod: req.permission.authMethod,
actorOrgId: req.permission.orgId
}
);
await server.services.auditLog.createAuditLog({
...req.auditLogInfo,
@@ -0,0 +1,18 @@
import {
AcmeCertificateAuthoritySchema,
CreateAcmeCertificateAuthoritySchema,
UpdateAcmeCertificateAuthoritySchema
} from "@app/services/certificate-authority/acme/acme-certificate-authority-schemas";
import { CaType } from "@app/services/certificate-authority/certificate-authority-enums";
import { registerCertificateAuthorityEndpoints } from "./certificate-authority-endpoints";
export const registerAcmeCertificateAuthorityRouter = async (server: FastifyZodProvider) => {
registerCertificateAuthorityEndpoints({
caType: CaType.ACME,
server,
responseSchema: AcmeCertificateAuthoritySchema,
createSchema: CreateAcmeCertificateAuthoritySchema,
updateSchema: UpdateAcmeCertificateAuthoritySchema
});
};
@@ -0,0 +1,258 @@
import { z } from "zod";
import { EventType } from "@app/ee/services/audit-log/audit-log-types";
import { ApiDocsTags } 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 { CaStatus, CaType } from "@app/services/certificate-authority/certificate-authority-enums";
import {
TCertificateAuthority,
TCertificateAuthorityInput
} from "@app/services/certificate-authority/certificate-authority-types";
export const registerCertificateAuthorityEndpoints = <
T extends TCertificateAuthority,
I extends TCertificateAuthorityInput
>({
server,
caType,
createSchema,
updateSchema,
responseSchema
}: {
caType: CaType;
server: FastifyZodProvider;
createSchema: z.ZodType<{
name: string;
projectId: string;
status: CaStatus;
configuration: I["configuration"];
enableDirectIssuance: boolean;
}>;
updateSchema: z.ZodType<{
projectId: string;
name?: string;
status?: CaStatus;
configuration?: I["configuration"];
enableDirectIssuance?: boolean;
}>;
responseSchema: z.ZodTypeAny;
}) => {
server.route({
method: "GET",
url: `/`,
config: {
rateLimit: readLimit
},
schema: {
hide: false,
tags: [ApiDocsTags.PkiCertificateAuthorities],
querystring: z.object({
projectId: z.string().trim().min(1, "Project ID required")
}),
response: {
200: responseSchema.array()
}
},
onRequest: verifyAuth([AuthMode.JWT, AuthMode.IDENTITY_ACCESS_TOKEN]),
handler: async (req) => {
const {
query: { projectId }
} = req;
const certificateAuthorities = (await server.services.certificateAuthority.listCertificateAuthoritiesByProjectId(
{ projectId, type: caType },
req.permission
)) as T[];
await server.services.auditLog.createAuditLog({
...req.auditLogInfo,
projectId,
event: {
type: EventType.GET_CAS,
metadata: {
caIds: certificateAuthorities.map((ca) => ca.id)
}
}
});
return certificateAuthorities;
}
});
server.route({
method: "GET",
url: "/:caName",
config: {
rateLimit: readLimit
},
schema: {
hide: false,
tags: [ApiDocsTags.PkiCertificateAuthorities],
params: z.object({
caName: z.string()
}),
querystring: z.object({
projectId: z.string().uuid()
}),
response: {
200: responseSchema
}
},
onRequest: verifyAuth([AuthMode.JWT, AuthMode.IDENTITY_ACCESS_TOKEN]),
handler: async (req) => {
const { caName } = req.params;
const { projectId } = req.query;
const certificateAuthority =
(await server.services.certificateAuthority.findCertificateAuthorityByNameAndProjectId(
{ caName, type: caType, projectId },
req.permission
)) as T;
await server.services.auditLog.createAuditLog({
...req.auditLogInfo,
projectId: certificateAuthority.projectId,
event: {
type: EventType.GET_CA,
metadata: {
caId: certificateAuthority.id,
name: certificateAuthority.name
}
}
});
return certificateAuthority;
}
});
server.route({
method: "POST",
url: "/",
config: {
rateLimit: writeLimit
},
schema: {
hide: false,
tags: [ApiDocsTags.PkiCertificateAuthorities],
body: createSchema,
response: {
200: responseSchema
}
},
onRequest: verifyAuth([AuthMode.JWT, AuthMode.IDENTITY_ACCESS_TOKEN]),
handler: async (req) => {
const certificateAuthority = (await server.services.certificateAuthority.createCertificateAuthority(
{ ...req.body, type: caType },
req.permission
)) as T;
await server.services.auditLog.createAuditLog({
...req.auditLogInfo,
projectId: certificateAuthority.projectId,
event: {
type: EventType.CREATE_CA,
metadata: {
name: certificateAuthority.name,
caId: certificateAuthority.id
}
}
});
return certificateAuthority;
}
});
server.route({
method: "PATCH",
url: "/:caName",
config: {
rateLimit: writeLimit
},
schema: {
hide: false,
tags: [ApiDocsTags.PkiCertificateAuthorities],
params: z.object({
caName: z.string()
}),
body: updateSchema,
response: {
200: responseSchema
}
},
onRequest: verifyAuth([AuthMode.JWT, AuthMode.IDENTITY_ACCESS_TOKEN]),
handler: async (req) => {
const { caName } = req.params;
const certificateAuthority = (await server.services.certificateAuthority.updateCertificateAuthority(
{
...req.body,
type: caType,
caName
},
req.permission
)) as T;
await server.services.auditLog.createAuditLog({
...req.auditLogInfo,
projectId: certificateAuthority.projectId,
event: {
type: EventType.UPDATE_CA,
metadata: {
name: certificateAuthority.name,
caId: certificateAuthority.id,
status: certificateAuthority.status
}
}
});
return certificateAuthority;
}
});
server.route({
method: "DELETE",
url: "/:caName",
config: {
rateLimit: writeLimit
},
schema: {
hide: false,
tags: [ApiDocsTags.PkiCertificateAuthorities],
params: z.object({
caName: z.string()
}),
body: z.object({
projectId: z.string().uuid()
}),
response: {
200: responseSchema
}
},
onRequest: verifyAuth([AuthMode.JWT, AuthMode.IDENTITY_ACCESS_TOKEN]),
handler: async (req) => {
const { caName } = req.params;
const { projectId } = req.body;
const certificateAuthority = (await server.services.certificateAuthority.deleteCertificateAuthority(
{ caName, type: caType, projectId },
req.permission
)) as T;
await server.services.auditLog.createAuditLog({
...req.auditLogInfo,
projectId: certificateAuthority.projectId,
event: {
type: EventType.DELETE_CA,
metadata: {
name: certificateAuthority.name,
caId: certificateAuthority.id
}
}
});
return certificateAuthority;
}
});
};
@@ -0,0 +1,12 @@
import { CaType } from "@app/services/certificate-authority/certificate-authority-enums";
import { registerAcmeCertificateAuthorityRouter } from "./acme-certificate-authority-router";
import { registerInternalCertificateAuthorityRouter } from "./internal-certificate-authority-router";
export * from "./internal-certificate-authority-router";
export const CERTIFICATE_AUTHORITY_REGISTER_ROUTER_MAP: Record<CaType, (server: FastifyZodProvider) => Promise<void>> =
{
[CaType.INTERNAL]: registerInternalCertificateAuthorityRouter,
[CaType.ACME]: registerAcmeCertificateAuthorityRouter
};
@@ -0,0 +1,18 @@
import { CaType } from "@app/services/certificate-authority/certificate-authority-enums";
import {
CreateInternalCertificateAuthoritySchema,
InternalCertificateAuthoritySchema,
UpdateInternalCertificateAuthoritySchema
} from "@app/services/certificate-authority/internal/internal-certificate-authority-schemas";
import { registerCertificateAuthorityEndpoints } from "./certificate-authority-endpoints";
export const registerInternalCertificateAuthorityRouter = async (server: FastifyZodProvider) => {
registerCertificateAuthorityEndpoints({
caType: CaType.INTERNAL,
server,
responseSchema: InternalCertificateAuthoritySchema,
createSchema: CreateInternalCertificateAuthoritySchema,
updateSchema: UpdateInternalCertificateAuthoritySchema
});
};
@@ -39,7 +39,7 @@ export const registerCertRouter = async (server: FastifyZodProvider) => {
}
},
handler: async (req) => {
const { cert, ca } = await server.services.certificate.getCert({
const { cert } = await server.services.certificate.getCert({
serialNumber: req.params.serialNumber,
actor: req.permission.type,
actorId: req.permission.id,
@@ -49,7 +49,7 @@ export const registerCertRouter = async (server: FastifyZodProvider) => {
await server.services.auditLog.createAuditLog({
...req.auditLogInfo,
projectId: ca.projectId,
projectId: cert.projectId,
event: {
type: EventType.GET_CERT,
metadata: {
@@ -86,7 +86,7 @@ export const registerCertRouter = async (server: FastifyZodProvider) => {
}
},
handler: async (req, reply) => {
const { ca, cert, certPrivateKey } = await server.services.certificate.getCertPrivateKey({
const { cert, certPrivateKey } = await server.services.certificate.getCertPrivateKey({
serialNumber: req.params.serialNumber,
actor: req.permission.type,
actorId: req.permission.id,
@@ -96,7 +96,7 @@ export const registerCertRouter = async (server: FastifyZodProvider) => {
await server.services.auditLog.createAuditLog({
...req.auditLogInfo,
projectId: ca.projectId,
projectId: cert.projectId,
event: {
type: EventType.GET_CERT_PRIVATE_KEY,
metadata: {
@@ -138,7 +138,7 @@ export const registerCertRouter = async (server: FastifyZodProvider) => {
}
},
handler: async (req, reply) => {
const { certificate, certificateChain, serialNumber, cert, ca, privateKey } =
const { certificate, certificateChain, serialNumber, cert, privateKey } =
await server.services.certificate.getCertBundle({
serialNumber: req.params.serialNumber,
actor: req.permission.type,
@@ -149,7 +149,7 @@ export const registerCertRouter = async (server: FastifyZodProvider) => {
await server.services.auditLog.createAuditLog({
...req.auditLogInfo,
projectId: ca.projectId,
projectId: cert.projectId,
event: {
type: EventType.GET_CERT_BUNDLE,
metadata: {
@@ -242,7 +242,7 @@ export const registerCertRouter = async (server: FastifyZodProvider) => {
},
handler: async (req) => {
const { certificate, certificateChain, issuingCaCertificate, privateKey, serialNumber, ca } =
await server.services.certificateAuthority.issueCertFromCa({
await server.services.internalCertificateAuthority.issueCertFromCa({
actor: req.permission.type,
actorId: req.permission.id,
actorAuthMethod: req.permission.authMethod,
@@ -284,6 +284,68 @@ export const registerCertRouter = async (server: FastifyZodProvider) => {
}
});
server.route({
method: "POST",
url: "/import-certificate",
config: {
rateLimit: writeLimit
},
onRequest: verifyAuth([AuthMode.JWT, AuthMode.IDENTITY_ACCESS_TOKEN]),
schema: {
hide: false,
tags: [ApiDocsTags.PkiCertificates],
description: "Import certificate",
body: z.object({
projectSlug: z.string().trim().min(1).describe(CERTIFICATES.IMPORT.projectSlug),
certificatePem: z.string().trim().min(1).describe(CERTIFICATES.IMPORT.certificatePem),
privateKeyPem: z.string().trim().min(1).describe(CERTIFICATES.IMPORT.privateKeyPem),
chainPem: z.string().trim().min(1).describe(CERTIFICATES.IMPORT.chainPem),
friendlyName: z.string().trim().optional().describe(CERTIFICATES.IMPORT.friendlyName),
pkiCollectionId: z.string().trim().optional().describe(CERTIFICATES.IMPORT.pkiCollectionId)
}),
response: {
200: z.object({
certificate: z.string().trim().describe(CERTIFICATES.IMPORT.certificate),
certificateChain: z.string().trim().describe(CERTIFICATES.IMPORT.certificateChain),
privateKey: z.string().trim().describe(CERTIFICATES.IMPORT.privateKey),
serialNumber: z.string().trim().describe(CERTIFICATES.IMPORT.serialNumber)
})
}
},
handler: async (req) => {
const { certificate, certificateChain, privateKey, serialNumber, cert } =
await server.services.certificate.importCert({
actor: req.permission.type,
actorId: req.permission.id,
actorAuthMethod: req.permission.authMethod,
actorOrgId: req.permission.orgId,
...req.body
});
await server.services.auditLog.createAuditLog({
...req.auditLogInfo,
projectId: cert.projectId,
event: {
type: EventType.IMPORT_CERT,
metadata: {
certId: cert.id,
cn: cert.commonName,
serialNumber
}
}
});
return {
certificate,
certificateChain,
privateKey,
serialNumber
};
}
});
server.route({
method: "POST",
url: "/sign-certificate",
@@ -355,7 +417,7 @@ export const registerCertRouter = async (server: FastifyZodProvider) => {
},
handler: async (req) => {
const { certificate, certificateChain, issuingCaCertificate, serialNumber, ca, commonName } =
await server.services.certificateAuthority.signCertFromCa({
await server.services.internalCertificateAuthority.signCertFromCa({
isInternal: false,
actor: req.permission.type,
actorId: req.permission.id,
@@ -474,7 +536,7 @@ export const registerCertRouter = async (server: FastifyZodProvider) => {
}
},
handler: async (req) => {
const { deletedCert, ca } = await server.services.certificate.deleteCert({
const { deletedCert } = await server.services.certificate.deleteCert({
serialNumber: req.params.serialNumber,
actor: req.permission.type,
actorId: req.permission.id,
@@ -484,7 +546,7 @@ export const registerCertRouter = async (server: FastifyZodProvider) => {
await server.services.auditLog.createAuditLog({
...req.auditLogInfo,
projectId: ca.projectId,
projectId: deletedCert.projectId,
event: {
type: EventType.DELETE_CERT,
metadata: {
@@ -524,7 +586,7 @@ export const registerCertRouter = async (server: FastifyZodProvider) => {
}
},
handler: async (req) => {
const { certificate, certificateChain, serialNumber, cert, ca } = await server.services.certificate.getCertBody({
const { certificate, certificateChain, serialNumber, cert } = await server.services.certificate.getCertBody({
serialNumber: req.params.serialNumber,
actor: req.permission.type,
actorId: req.permission.id,
@@ -534,7 +596,7 @@ export const registerCertRouter = async (server: FastifyZodProvider) => {
await server.services.auditLog.createAuditLog({
...req.auditLogInfo,
projectId: ca.projectId,
projectId: cert.projectId,
event: {
type: EventType.GET_CERT_BODY,
metadata: {
+11
View File
@@ -10,6 +10,7 @@ import { registerAdminRouter } from "./admin-router";
import { registerAuthRoutes } from "./auth-router";
import { registerProjectBotRouter } from "./bot-router";
import { registerCaRouter } from "./certificate-authority-router";
import { CERTIFICATE_AUTHORITY_REGISTER_ROUTER_MAP } from "./certificate-authority-routers";
import { registerCertRouter } from "./certificate-router";
import { registerCertificateTemplateRouter } from "./certificate-template-router";
import { registerExternalGroupOrgRoleMappingRouter } from "./external-group-org-role-mapping-router";
@@ -104,6 +105,16 @@ export const registerV1Routes = async (server: FastifyZodProvider) => {
await server.register(
async (pkiRouter) => {
await pkiRouter.register(registerCaRouter, { prefix: "/ca" });
await pkiRouter.register(
async (caRouter) => {
for await (const [caType, router] of Object.entries(CERTIFICATE_AUTHORITY_REGISTER_ROUTER_MAP)) {
await caRouter.register(router, { prefix: `/${caType}` });
}
},
{
prefix: "/ca"
}
);
await pkiRouter.register(registerCertRouter, { prefix: "/certificates" });
await pkiRouter.register(registerCertificateTemplateRouter, { prefix: "/certificate-templates" });
await pkiRouter.register(registerPkiAlertRouter, { prefix: "/alerts" });
@@ -5,6 +5,7 @@ import { EventType } from "@app/ee/services/audit-log/audit-log-types";
import { ApiDocsTags, PKI_SUBSCRIBERS } from "@app/lib/api-docs";
import { ms } from "@app/lib/ms";
import { readLimit, writeLimit } from "@app/server/config/rateLimiter";
import { addNoCacheHeaders } from "@app/server/lib/caching";
import { slugSchema } from "@app/server/lib/schemas";
import { getTelemetryDistinctId } from "@app/server/lib/telemetry";
import { verifyAuth } from "@app/server/plugins/auth/verify-auth";
@@ -90,7 +91,8 @@ export const registerPkiSubscriberRouter = async (server: FastifyZodProvider) =>
ttl: z
.string()
.trim()
.refine((val) => ms(val) > 0, "TTL must be a positive number")
.refine((val) => !val || ms(val) > 0, "TTL must be a positive number")
.optional()
.describe(PKI_SUBSCRIBERS.CREATE.ttl),
subjectAlternativeNames: validateAltNameField
.array()
@@ -108,7 +110,9 @@ export const registerPkiSubscriberRouter = async (server: FastifyZodProvider) =>
.array()
.default([])
.transform((arr) => Array.from(new Set(arr)))
.describe(PKI_SUBSCRIBERS.CREATE.extendedKeyUsages)
.describe(PKI_SUBSCRIBERS.CREATE.extendedKeyUsages),
enableAutoRenewal: z.boolean().optional().describe(PKI_SUBSCRIBERS.CREATE.enableAutoRenewal),
autoRenewalPeriodInDays: z.number().min(1).optional().describe(PKI_SUBSCRIBERS.CREATE.autoRenewalPeriodInDays)
}),
response: {
200: sanitizedPkiSubscriber
@@ -134,7 +138,7 @@ export const registerPkiSubscriberRouter = async (server: FastifyZodProvider) =>
caId: subscriber.caId ?? undefined,
name: subscriber.name,
commonName: subscriber.commonName,
ttl: subscriber.ttl,
ttl: subscriber.ttl ?? undefined,
subjectAlternativeNames: subscriber.subjectAlternativeNames,
keyUsages: subscriber.keyUsages as CertKeyUsage[],
extendedKeyUsages: subscriber.extendedKeyUsages as CertExtendedKeyUsage[]
@@ -179,7 +183,7 @@ export const registerPkiSubscriberRouter = async (server: FastifyZodProvider) =>
ttl: z
.string()
.trim()
.refine((val) => ms(val) > 0, "TTL must be a positive number")
.refine((val) => !val || ms(val) > 0, "TTL must be a positive number")
.optional()
.describe(PKI_SUBSCRIBERS.UPDATE.ttl),
keyUsages: z
@@ -193,7 +197,9 @@ export const registerPkiSubscriberRouter = async (server: FastifyZodProvider) =>
.array()
.transform((arr) => Array.from(new Set(arr)))
.optional()
.describe(PKI_SUBSCRIBERS.UPDATE.extendedKeyUsages)
.describe(PKI_SUBSCRIBERS.UPDATE.extendedKeyUsages),
enableAutoRenewal: z.boolean().optional().describe(PKI_SUBSCRIBERS.UPDATE.enableAutoRenewal),
autoRenewalPeriodInDays: z.number().min(1).optional().describe(PKI_SUBSCRIBERS.UPDATE.autoRenewalPeriodInDays)
}),
response: {
200: sanitizedPkiSubscriber
@@ -219,7 +225,7 @@ export const registerPkiSubscriberRouter = async (server: FastifyZodProvider) =>
caId: subscriber.caId ?? undefined,
name: subscriber.name,
commonName: subscriber.commonName,
ttl: subscriber.ttl,
ttl: subscriber.ttl ?? undefined,
subjectAlternativeNames: subscriber.subjectAlternativeNames,
keyUsages: subscriber.keyUsages as CertKeyUsage[],
extendedKeyUsages: subscriber.extendedKeyUsages as CertExtendedKeyUsage[]
@@ -278,6 +284,67 @@ export const registerPkiSubscriberRouter = async (server: FastifyZodProvider) =>
}
});
server.route({
method: "POST",
url: "/:subscriberName/order-certificate",
config: {
rateLimit: writeLimit
},
onRequest: verifyAuth([AuthMode.JWT, AuthMode.IDENTITY_ACCESS_TOKEN]),
schema: {
hide: false,
tags: [ApiDocsTags.PkiSubscribers],
description: "Order certificate",
params: z.object({
subscriberName: z.string().describe(PKI_SUBSCRIBERS.ISSUE_CERT.subscriberName)
}),
body: z.object({
projectId: z.string().trim().describe(PKI_SUBSCRIBERS.ISSUE_CERT.projectId)
}),
response: {
200: z.object({
message: z.string().trim()
})
}
},
handler: async (req) => {
const subscriber = await server.services.pkiSubscriber.orderSubscriberCert({
subscriberName: req.params.subscriberName,
projectId: req.body.projectId,
actor: req.permission.type,
actorId: req.permission.id,
actorAuthMethod: req.permission.authMethod,
actorOrgId: req.permission.orgId
});
await server.services.auditLog.createAuditLog({
...req.auditLogInfo,
projectId: subscriber.projectId,
event: {
type: EventType.ISSUE_PKI_SUBSCRIBER_CERT,
metadata: {
subscriberId: subscriber.id,
name: subscriber.name
}
}
});
await server.services.telemetry.sendPostHogEvents({
event: PostHogEventTypes.IssueCert,
distinctId: getTelemetryDistinctId(req),
properties: {
subscriberId: subscriber.id,
commonName: subscriber.commonName,
...req.auditLogInfo
}
});
return {
message: "Successfully placed order for certificate"
};
}
});
server.route({
method: "POST",
url: "/:subscriberName/issue-certificate",
@@ -420,6 +487,72 @@ export const registerPkiSubscriberRouter = async (server: FastifyZodProvider) =>
}
});
server.route({
method: "GET",
url: "/:subscriberName/latest-certificate-bundle",
config: {
rateLimit: readLimit
},
schema: {
hide: false,
tags: [ApiDocsTags.PkiSubscribers],
description: "Get latest certificate bundle of a subscriber",
params: z.object({
subscriberName: z.string().describe(PKI_SUBSCRIBERS.GET_LATEST_CERT_BUNDLE.subscriberName)
}),
querystring: z.object({
projectId: z.string().trim().describe(PKI_SUBSCRIBERS.GET_LATEST_CERT_BUNDLE.projectId)
}),
response: {
200: z.object({
certificate: z.string().trim().describe(PKI_SUBSCRIBERS.GET_LATEST_CERT_BUNDLE.certificate),
certificateChain: z
.string()
.trim()
.nullable()
.describe(PKI_SUBSCRIBERS.GET_LATEST_CERT_BUNDLE.certificateChain),
privateKey: z.string().trim().describe(PKI_SUBSCRIBERS.GET_LATEST_CERT_BUNDLE.privateKey),
serialNumber: z.string().trim().describe(PKI_SUBSCRIBERS.GET_LATEST_CERT_BUNDLE.serialNumber)
})
}
},
onRequest: verifyAuth([AuthMode.JWT, AuthMode.IDENTITY_ACCESS_TOKEN]),
handler: async (req, reply) => {
const { certificate, certificateChain, serialNumber, cert, privateKey, subscriber } =
await server.services.pkiSubscriber.getSubscriberActiveCertBundle({
subscriberName: req.params.subscriberName,
actor: req.permission.type,
actorId: req.permission.id,
actorAuthMethod: req.permission.authMethod,
actorOrgId: req.permission.orgId,
...req.query
});
await server.services.auditLog.createAuditLog({
...req.auditLogInfo,
projectId: cert.projectId,
event: {
type: EventType.GET_SUBSCRIBER_ACTIVE_CERT_BUNDLE,
metadata: {
subscriberId: subscriber.id,
name: subscriber.name,
certId: cert.id,
serialNumber: cert.serialNumber
}
}
});
addNoCacheHeaders(reply);
return {
certificate,
certificateChain,
serialNumber,
privateKey
};
}
});
server.route({
method: "GET",
url: "/:subscriberName/certificates",