PKI revamp - backend initial changes

This commit is contained in:
Carlos Monastyrski
2025-10-09 00:29:55 -03:00
parent c1833b5cbd
commit c4dcc3dd83
47 changed files with 9624 additions and 26 deletions
+50
View File
@@ -170,10 +170,18 @@ import { externalCertificateAuthorityDALFactory } from "@app/services/certificat
import { internalCertificateAuthorityDALFactory } from "@app/services/certificate-authority/internal/internal-certificate-authority-dal";
import { InternalCertificateAuthorityFns } from "@app/services/certificate-authority/internal/internal-certificate-authority-fns";
import { internalCertificateAuthorityServiceFactory } from "@app/services/certificate-authority/internal/internal-certificate-authority-service";
import { certificateEstV3ServiceFactory } from "@app/services/certificate-est-v3/certificate-est-v3-service";
import { certificateProfileDALFactory } from "@app/services/certificate-profile/certificate-profile-dal";
import { certificateProfileServiceFactory } from "@app/services/certificate-profile/certificate-profile-service";
import { certificateTemplateDALFactory } from "@app/services/certificate-template/certificate-template-dal";
import { certificateTemplateEstConfigDALFactory } from "@app/services/certificate-template/certificate-template-est-config-dal";
import { certificateTemplateServiceFactory } from "@app/services/certificate-template/certificate-template-service";
import { certificateTemplateV2DALFactory } from "@app/services/certificate-template-v2/certificate-template-v2-dal";
import { certificateTemplateV2ServiceFactory } from "@app/services/certificate-template-v2/certificate-template-v2-service";
import { certificateV3ServiceFactory } from "@app/services/certificate-v3/certificate-v3-service";
import { cmekServiceFactory } from "@app/services/cmek/cmek-service";
import { apiEnrollmentConfigDALFactory } from "@app/services/enrollment-config/api-enrollment-config-dal";
import { estEnrollmentConfigDALFactory } from "@app/services/enrollment-config/est-enrollment-config-dal";
import { externalGroupOrgRoleMappingDALFactory } from "@app/services/external-group-org-role-mapping/external-group-org-role-mapping-dal";
import { externalGroupOrgRoleMappingServiceFactory } from "@app/services/external-group-org-role-mapping/external-group-org-role-mapping-service";
import { externalMigrationQueueFactory } from "@app/services/external-migration/external-migration-queue";
@@ -993,6 +1001,10 @@ export const registerRoutes = async (
const certificateAuthorityCrlDAL = certificateAuthorityCrlDALFactory(db);
const certificateTemplateDAL = certificateTemplateDALFactory(db);
const certificateTemplateEstConfigDAL = certificateTemplateEstConfigDALFactory(db);
const certificateTemplateV2DAL = certificateTemplateV2DALFactory(db);
const certificateProfileDAL = certificateProfileDALFactory(db);
const apiEnrollmentConfigDAL = apiEnrollmentConfigDALFactory(db);
const estEnrollmentConfigDAL = estEnrollmentConfigDALFactory(db);
const certificateDAL = certificateDALFactory(db);
const certificateBodyDAL = certificateBodyDALFactory(db);
@@ -1077,6 +1089,19 @@ export const registerRoutes = async (
licenseService
});
const certificateTemplateV2Service = certificateTemplateV2ServiceFactory({
certificateTemplateV2DAL,
permissionService
});
const certificateProfileService = certificateProfileServiceFactory({
certificateProfileDAL,
certificateTemplateV2DAL,
apiEnrollmentConfigDAL,
estEnrollmentConfigDAL,
permissionService
});
const pkiAlertService = pkiAlertServiceFactory({
pkiAlertDAL,
pkiCollectionDAL,
@@ -2043,6 +2068,27 @@ export const registerRoutes = async (
pkiSyncQueue
});
const certificateV3Service = certificateV3ServiceFactory({
certificateDAL,
certificateAuthorityDAL,
certificateProfileDAL,
certificateTemplateV2Service,
internalCaService: internalCertificateAuthorityService,
permissionService
});
const certificateEstV3Service = certificateEstV3ServiceFactory({
internalCertificateAuthorityService,
certificateTemplateDAL,
certificateAuthorityDAL,
certificateAuthorityCertDAL,
projectDAL,
kmsService,
licenseService,
certificateProfileDAL,
estEnrollmentConfigDAL
});
const pkiSubscriberService = pkiSubscriberServiceFactory({
pkiSubscriberDAL,
certificateAuthorityDAL,
@@ -2243,6 +2289,8 @@ export const registerRoutes = async (
auditLog: auditLogService,
auditLogStream: auditLogStreamService,
certificate: certificateService,
certificateV3: certificateV3Service,
certificateEstV3: certificateEstV3Service,
sshCertificateAuthority: sshCertificateAuthorityService,
sshCertificateTemplate: sshCertificateTemplateService,
sshHost: sshHostService,
@@ -2250,6 +2298,8 @@ export const registerRoutes = async (
certificateAuthority: certificateAuthorityService,
internalCertificateAuthority: internalCertificateAuthorityService,
certificateTemplate: certificateTemplateService,
certificateTemplateV2: certificateTemplateV2Service,
certificateProfile: certificateProfileService,
certificateAuthorityCrl: certificateAuthorityCrlService,
certificateEst: certificateEstService,
pit: pitService,
@@ -0,0 +1,387 @@
import { z } from "zod";
import { CertificateProfilesSchema } from "@app/db/schemas";
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 {
createCertificateProfileSchema,
deleteCertificateProfileSchema,
getCertificateProfileByIdSchema,
listCertificateProfilesSchema,
updateCertificateProfileSchema
} from "@app/services/certificate-profile/certificate-profile-schemas";
export const registerCertificateProfilesRouter = async (server: FastifyZodProvider) => {
server.route({
method: "POST",
url: "/",
config: {
rateLimit: writeLimit
},
schema: {
hide: false,
tags: [ApiDocsTags.PkiCertificateProfiles],
body: createCertificateProfileSchema,
response: {
200: z.object({
certificateProfile: CertificateProfilesSchema
})
}
},
onRequest: verifyAuth([AuthMode.JWT, AuthMode.IDENTITY_ACCESS_TOKEN]),
handler: async (req) => {
const certificateProfile = await server.services.certificateProfile.createProfile({
actor: req.permission.type,
actorId: req.permission.id,
actorAuthMethod: req.permission.authMethod,
actorOrgId: req.permission.orgId,
projectId: req.body.projectId,
data: req.body
});
await server.services.auditLog.createAuditLog({
...req.auditLogInfo,
projectId: req.body.projectId,
event: {
type: EventType.CREATE_CERTIFICATE_PROFILE,
metadata: {
certificateProfileId: certificateProfile.id,
name: certificateProfile.name,
projectId: certificateProfile.projectId,
enrollmentType: certificateProfile.enrollmentType
}
}
});
return { certificateProfile };
}
});
server.route({
method: "GET",
url: "/",
config: {
rateLimit: readLimit
},
schema: {
hide: false,
tags: [ApiDocsTags.PkiCertificateProfiles],
querystring: listCertificateProfilesSchema,
response: {
200: z.object({
certificateProfiles: CertificateProfilesSchema.array(),
totalCount: z.number()
})
}
},
onRequest: verifyAuth([AuthMode.JWT, AuthMode.IDENTITY_ACCESS_TOKEN]),
handler: async (req) => {
const { profiles, totalCount } = await server.services.certificateProfile.listProfiles({
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: req.query.projectId,
event: {
type: EventType.LIST_CERTIFICATE_PROFILES,
metadata: {
projectId: req.query.projectId
}
}
});
return { certificateProfiles: profiles, totalCount };
}
});
server.route({
method: "GET",
url: "/:id",
config: {
rateLimit: readLimit
},
schema: {
hide: false,
tags: [ApiDocsTags.PkiCertificateProfiles],
params: getCertificateProfileByIdSchema,
response: {
200: z.object({
certificateProfile: CertificateProfilesSchema.extend({
certificateAuthority: z
.object({
id: z.string(),
projectId: z.string(),
status: z.string(),
name: z.string()
})
.optional(),
certificateTemplate: z
.object({
id: z.string(),
projectId: z.string(),
name: z.string(),
description: z.string().optional()
})
.optional(),
estConfig: z
.object({
id: z.string(),
disableBootstrapCaValidation: z.boolean(),
hashedPassphrase: z.string(),
encryptedCaChain: z.any()
})
.optional(),
apiConfig: z
.object({
id: z.string(),
autoRenew: z.boolean(),
autoRenewDays: z.number().optional()
})
.optional()
})
})
}
},
onRequest: verifyAuth([AuthMode.JWT, AuthMode.IDENTITY_ACCESS_TOKEN]),
handler: async (req) => {
const certificateProfile = await server.services.certificateProfile.getProfileByIdWithConfigs({
actor: req.permission.type,
actorId: req.permission.id,
actorAuthMethod: req.permission.authMethod,
actorOrgId: req.permission.orgId,
profileId: req.params.id
});
await server.services.auditLog.createAuditLog({
...req.auditLogInfo,
projectId: certificateProfile.projectId,
event: {
type: EventType.GET_CERTIFICATE_PROFILE,
metadata: {
certificateProfileId: certificateProfile.id
}
}
});
return { certificateProfile };
}
});
server.route({
method: "GET",
url: "/slug/:slug",
config: {
rateLimit: readLimit
},
schema: {
hide: false,
tags: [ApiDocsTags.PkiCertificateProfiles],
params: z.object({
slug: z.string().min(1)
}),
querystring: z.object({
projectId: z.string().min(1)
}),
response: {
200: z.object({
certificateProfile: CertificateProfilesSchema
})
}
},
onRequest: verifyAuth([AuthMode.JWT, AuthMode.IDENTITY_ACCESS_TOKEN]),
handler: async (req) => {
const certificateProfile = await server.services.certificateProfile.getProfileBySlug({
actor: req.permission.type,
actorId: req.permission.id,
actorAuthMethod: req.permission.authMethod,
actorOrgId: req.permission.orgId,
projectId: req.query.projectId,
slug: req.params.slug
});
return { certificateProfile };
}
});
server.route({
method: "PATCH",
url: "/:id",
config: {
rateLimit: writeLimit
},
schema: {
hide: false,
tags: [ApiDocsTags.PkiCertificateProfiles],
params: getCertificateProfileByIdSchema,
body: updateCertificateProfileSchema,
response: {
200: z.object({
certificateProfile: CertificateProfilesSchema
})
}
},
onRequest: verifyAuth([AuthMode.JWT, AuthMode.IDENTITY_ACCESS_TOKEN]),
handler: async (req) => {
const certificateProfile = await server.services.certificateProfile.updateProfile({
actor: req.permission.type,
actorId: req.permission.id,
actorAuthMethod: req.permission.authMethod,
actorOrgId: req.permission.orgId,
profileId: req.params.id,
data: req.body
});
await server.services.auditLog.createAuditLog({
...req.auditLogInfo,
projectId: certificateProfile.projectId,
event: {
type: EventType.UPDATE_CERTIFICATE_PROFILE,
metadata: {
certificateProfileId: certificateProfile.id,
name: certificateProfile.name
}
}
});
return { certificateProfile };
}
});
server.route({
method: "DELETE",
url: "/:id",
config: {
rateLimit: writeLimit
},
schema: {
hide: false,
tags: [ApiDocsTags.PkiCertificateProfiles],
params: deleteCertificateProfileSchema,
response: {
200: z.object({
certificateProfile: CertificateProfilesSchema
})
}
},
onRequest: verifyAuth([AuthMode.JWT, AuthMode.IDENTITY_ACCESS_TOKEN]),
handler: async (req) => {
const certificateProfile = await server.services.certificateProfile.deleteProfile({
actor: req.permission.type,
actorId: req.permission.id,
actorAuthMethod: req.permission.authMethod,
actorOrgId: req.permission.orgId,
profileId: req.params.id
});
await server.services.auditLog.createAuditLog({
...req.auditLogInfo,
projectId: certificateProfile.projectId,
event: {
type: EventType.DELETE_CERTIFICATE_PROFILE,
metadata: {
certificateProfileId: certificateProfile.id
}
}
});
return { certificateProfile };
}
});
server.route({
method: "GET",
url: "/:id/certificates",
config: {
rateLimit: readLimit
},
schema: {
hide: false,
tags: [ApiDocsTags.PkiCertificateProfiles],
params: getCertificateProfileByIdSchema,
querystring: z.object({
offset: z.number().min(0).default(0),
limit: z.number().min(1).max(100).default(20),
status: z.enum(["active", "expired", "revoked"]).optional(),
search: z.string().optional()
}),
response: {
200: z.object({
certificates: z.array(
z.object({
id: z.string(),
serialNumber: z.string(),
cn: z.string(),
status: z.string(),
notBefore: z.date(),
notAfter: z.date(),
isRevoked: z.boolean(),
createdAt: z.date()
})
)
})
}
},
onRequest: verifyAuth([AuthMode.JWT, AuthMode.IDENTITY_ACCESS_TOKEN]),
handler: async (req) => {
const certificates = await server.services.certificateProfile.getProfileCertificates({
actor: req.permission.type,
actorId: req.permission.id,
actorAuthMethod: req.permission.authMethod,
actorOrgId: req.permission.orgId,
profileId: req.params.id,
...req.query
});
return { certificates };
}
});
server.route({
method: "GET",
url: "/:id/metrics",
config: {
rateLimit: readLimit
},
schema: {
hide: false,
tags: [ApiDocsTags.PkiCertificateProfiles],
params: getCertificateProfileByIdSchema,
querystring: z.object({
expiringDays: z.number().min(1).max(365).default(30)
}),
response: {
200: z.object({
metrics: z.object({
profileId: z.string(),
totalCertificates: z.number(),
activeCertificates: z.number(),
expiredCertificates: z.number(),
expiringCertificates: z.number(),
revokedCertificates: z.number()
})
})
}
},
onRequest: verifyAuth([AuthMode.JWT, AuthMode.IDENTITY_ACCESS_TOKEN]),
handler: async (req) => {
const metrics = await server.services.certificateProfile.getProfileMetrics({
actor: req.permission.type,
actorId: req.permission.id,
actorAuthMethod: req.permission.authMethod,
actorOrgId: req.permission.orgId,
profileId: req.params.id,
expiringDays: req.query.expiringDays
});
return { metrics };
}
});
};
+2
View File
@@ -12,6 +12,7 @@ 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 { registerCertificateProfilesRouter } from "./certificate-profiles-router";
import { registerCertificateTemplateRouter } from "./certificate-template-router";
import { registerDeprecatedProjectEnvRouter } from "./deprecated-project-env-router";
import { registerDeprecatedProjectMembershipRouter } from "./deprecated-project-membership-router";
@@ -146,6 +147,7 @@ export const registerV1Routes = async (server: FastifyZodProvider) => {
);
await pkiRouter.register(registerCertRouter, { prefix: "/certificates" });
await pkiRouter.register(registerCertificateTemplateRouter, { prefix: "/certificate-templates" });
await pkiRouter.register(registerCertificateProfilesRouter, { prefix: "/certificate-profiles" });
await pkiRouter.register(registerPkiAlertRouter, { prefix: "/alerts" });
await pkiRouter.register(registerPkiCollectionRouter, { prefix: "/collections" });
await pkiRouter.register(registerPkiSubscriberRouter, { prefix: "/subscribers" });
@@ -0,0 +1,265 @@
import { z } from "zod";
import { CertificateTemplatesV2Schema } from "@app/db/schemas";
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 {
certificateRequestSchema,
createCertificateTemplateV2Schema,
deleteCertificateTemplateV2Schema,
getCertificateTemplateV2ByIdSchema,
listCertificateTemplatesV2Schema,
updateCertificateTemplateV2Schema
} from "@app/services/certificate-template-v2/certificate-template-v2-schemas";
export const registerCertificateTemplatesV2Router = async (server: FastifyZodProvider) => {
server.route({
method: "POST",
url: "/",
config: {
rateLimit: writeLimit
},
schema: {
hide: false,
tags: [ApiDocsTags.PkiCertificateTemplates],
body: createCertificateTemplateV2Schema,
response: {
200: z.object({
certificateTemplate: CertificateTemplatesV2Schema
})
}
},
onRequest: verifyAuth([AuthMode.JWT, AuthMode.IDENTITY_ACCESS_TOKEN]),
handler: async (req) => {
const { projectId, ...data } = req.body;
const certificateTemplate = await server.services.certificateTemplateV2.createTemplateV2({
actor: req.permission.type,
actorId: req.permission.id,
actorAuthMethod: req.permission.authMethod!,
actorOrgId: req.permission.orgId,
projectId,
data
});
await server.services.auditLog.createAuditLog({
...req.auditLogInfo,
projectId,
event: {
type: EventType.CREATE_CERTIFICATE_TEMPLATE_V2,
metadata: {
certificateTemplateId: certificateTemplate.id,
name: certificateTemplate.name,
projectId: certificateTemplate.projectId
}
}
});
return { certificateTemplate };
}
});
server.route({
method: "GET",
url: "/",
config: {
rateLimit: readLimit
},
schema: {
hide: false,
tags: [ApiDocsTags.PkiCertificateTemplates],
querystring: listCertificateTemplatesV2Schema,
response: {
200: z.object({
certificateTemplates: CertificateTemplatesV2Schema.array(),
totalCount: z.number()
})
}
},
onRequest: verifyAuth([AuthMode.JWT, AuthMode.IDENTITY_ACCESS_TOKEN]),
handler: async (req) => {
const { templates, totalCount } = await server.services.certificateTemplateV2.listTemplatesV2({
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: req.query.projectId,
event: {
type: EventType.LIST_CERTIFICATE_TEMPLATES_V2,
metadata: {
projectId: req.query.projectId
}
}
});
return { certificateTemplates: templates, totalCount };
}
});
server.route({
method: "GET",
url: "/:id",
config: {
rateLimit: readLimit
},
schema: {
hide: false,
tags: [ApiDocsTags.PkiCertificateTemplates],
params: getCertificateTemplateV2ByIdSchema,
response: {
200: z.object({
certificateTemplate: CertificateTemplatesV2Schema
})
}
},
onRequest: verifyAuth([AuthMode.JWT, AuthMode.IDENTITY_ACCESS_TOKEN]),
handler: async (req) => {
const certificateTemplate = await server.services.certificateTemplateV2.getTemplateV2ById({
actor: req.permission.type,
actorId: req.permission.id,
actorAuthMethod: req.permission.authMethod!,
actorOrgId: req.permission.orgId,
templateId: req.params.id
});
await server.services.auditLog.createAuditLog({
...req.auditLogInfo,
projectId: certificateTemplate.projectId,
event: {
type: EventType.GET_CERTIFICATE_TEMPLATE_V2,
metadata: {
certificateTemplateId: certificateTemplate.id
}
}
});
return { certificateTemplate };
}
});
server.route({
method: "PATCH",
url: "/:id",
config: {
rateLimit: writeLimit
},
schema: {
hide: false,
tags: [ApiDocsTags.PkiCertificateTemplates],
params: getCertificateTemplateV2ByIdSchema,
body: updateCertificateTemplateV2Schema,
response: {
200: z.object({
certificateTemplate: CertificateTemplatesV2Schema
})
}
},
onRequest: verifyAuth([AuthMode.JWT, AuthMode.IDENTITY_ACCESS_TOKEN]),
handler: async (req) => {
const certificateTemplate = await server.services.certificateTemplateV2.updateTemplateV2({
actor: req.permission.type,
actorId: req.permission.id,
actorAuthMethod: req.permission.authMethod!,
actorOrgId: req.permission.orgId,
templateId: req.params.id,
data: req.body
});
await server.services.auditLog.createAuditLog({
...req.auditLogInfo,
projectId: certificateTemplate.projectId,
event: {
type: EventType.UPDATE_CERTIFICATE_TEMPLATE_V2,
metadata: {
certificateTemplateId: certificateTemplate.id,
name: certificateTemplate.name
}
}
});
return { certificateTemplate };
}
});
server.route({
method: "DELETE",
url: "/:id",
config: {
rateLimit: writeLimit
},
schema: {
hide: false,
tags: [ApiDocsTags.PkiCertificateTemplates],
params: deleteCertificateTemplateV2Schema,
response: {
200: z.object({
certificateTemplate: CertificateTemplatesV2Schema
})
}
},
onRequest: verifyAuth([AuthMode.JWT, AuthMode.IDENTITY_ACCESS_TOKEN]),
handler: async (req) => {
const certificateTemplate = await server.services.certificateTemplateV2.deleteTemplateV2({
actor: req.permission.type,
actorId: req.permission.id,
actorAuthMethod: req.permission.authMethod!,
actorOrgId: req.permission.orgId,
templateId: req.params.id
});
await server.services.auditLog.createAuditLog({
...req.auditLogInfo,
projectId: certificateTemplate.projectId,
event: {
type: EventType.DELETE_CERTIFICATE_TEMPLATE_V2,
metadata: {
certificateTemplateId: certificateTemplate.id
}
}
});
return { certificateTemplate };
}
});
server.route({
method: "POST",
url: "/:id/validate",
config: {
rateLimit: readLimit
},
schema: {
hide: false,
tags: [ApiDocsTags.PkiCertificateTemplates],
params: getCertificateTemplateV2ByIdSchema,
body: z.object({
request: certificateRequestSchema
}),
response: {
200: z.object({
valid: z.boolean(),
errors: z.array(z.string()).optional()
})
}
},
onRequest: verifyAuth([AuthMode.JWT, AuthMode.IDENTITY_ACCESS_TOKEN]),
handler: async (req) => {
const result = await server.services.certificateTemplateV2.validateCertificateRequest(
req.params.id,
req.body.request
);
return {
valid: result.isValid,
errors: result.errors.length > 0 ? result.errors : undefined
};
}
});
};
+3
View File
@@ -1,4 +1,5 @@
import { registerCaRouter } from "./certificate-authority-router";
import { registerCertificateTemplatesV2Router } from "./certificate-templates-v2-router";
import { registerDeprecatedGroupProjectRouter } from "./deprecated-group-project-router";
import { registerDeprecatedIdentityProjectRouter } from "./deprecated-identity-project-router";
import { registerDeprecatedProjectMembershipRouter } from "./deprecated-project-membership-router";
@@ -19,6 +20,8 @@ export const registerV2Routes = async (server: FastifyZodProvider) => {
await server.register(registerServiceTokenRouter, { prefix: "/service-token" });
await server.register(registerPasswordRouter, { prefix: "/password" });
await server.register(registerCertificateTemplatesV2Router, { prefix: "/certificate-templates" });
await server.register(
async (pkiRouter) => {
await pkiRouter.register(registerCaRouter, { prefix: "/ca" });
@@ -0,0 +1,324 @@
import { z } from "zod";
import { EventType } from "@app/ee/services/audit-log/audit-log-types";
import { ApiDocsTags } from "@app/lib/api-docs";
import { ms } from "@app/lib/ms";
import { writeLimit } from "@app/server/config/rateLimiter";
import { verifyAuth } from "@app/server/plugins/auth/verify-auth";
import { AuthMode } from "@app/services/auth/auth-type";
import { CertExtendedKeyUsage, CertKeyUsage } from "@app/services/certificate/certificate-types";
import {
validateAltNamesField,
validateAndMapAltNameType,
validateCaDateField
} from "@app/services/certificate-authority/certificate-authority-validators";
import { validateTemplateRegexField } from "@app/services/certificate-template/certificate-template-validators";
export const registerCertificatesRouter = async (server: FastifyZodProvider) => {
server.route({
method: "POST",
url: "/issue-certificate",
config: {
rateLimit: writeLimit
},
schema: {
hide: false,
tags: [ApiDocsTags.PkiCertificates],
body: z.object({
profileId: z.string().uuid(),
commonName: validateTemplateRegexField,
ttl: z.string().refine((val) => ms(val) > 0, "TTL must be a positive number"),
keyUsages: z.nativeEnum(CertKeyUsage).array().optional(),
extendedKeyUsages: z.nativeEnum(CertExtendedKeyUsage).array().optional(),
notBefore: validateCaDateField.optional(),
notAfter: validateCaDateField.optional(),
altNames: validateAltNamesField.optional(),
organization: z.string().optional(),
organizationUnit: z.string().optional(),
locality: z.string().optional(),
state: z.string().optional(),
country: z.string().length(2).optional(),
email: z.string().email().optional(),
streetAddress: z.string().optional(),
postalCode: z.string().optional(),
signatureAlgorithm: z.string().optional(),
keyAlgorithm: z.string().optional()
}),
response: {
200: z.object({
certificate: z.string().trim(),
issuingCaCertificate: z.string().trim(),
certificateChain: z.string().trim(),
privateKey: z.string().trim().optional(),
serialNumber: z.string().trim(),
certificateId: z.string()
})
}
},
onRequest: verifyAuth([AuthMode.JWT, AuthMode.IDENTITY_ACCESS_TOKEN]),
handler: async (req) => {
const data = await server.services.certificateV3.issueCertificateFromProfile({
actor: req.permission.type,
actorId: req.permission.id,
actorAuthMethod: req.permission.authMethod,
actorOrgId: req.permission.orgId,
profileId: req.body.profileId,
certificateRequest: {
commonName: req.body.commonName,
organization: req.body.organization,
organizationUnit: req.body.organizationUnit,
locality: req.body.locality,
state: req.body.state,
country: req.body.country,
email: req.body.email,
streetAddress: req.body.streetAddress,
postalCode: req.body.postalCode,
keyUsages: req.body.keyUsages,
extendedKeyUsages: req.body.extendedKeyUsages,
subjectAlternativeNames: req.body.altNames
? req.body.altNames
.split(", ")
.map((name) => name.trim())
.map((name) => {
const mappedType = validateAndMapAltNameType(name);
if (!mappedType) return null;
const typeMapping = {
dns: "dns_name",
ip: "ip_address",
email: "email",
url: "uri"
} as const;
return {
type: typeMapping[mappedType.type] as "dns_name" | "ip_address" | "email" | "uri",
value: mappedType.value
};
})
.filter((item): item is NonNullable<typeof item> => item !== null)
: undefined,
validity: {
ttl: req.body.ttl
},
notBefore: req.body.notBefore ? new Date(req.body.notBefore) : undefined,
notAfter: req.body.notAfter ? new Date(req.body.notAfter) : undefined,
signatureAlgorithm: req.body.signatureAlgorithm,
keyAlgorithm: req.body.keyAlgorithm
}
});
const profile = await server.services.certificateProfile.getProfileById({
actor: req.permission.type,
actorId: req.permission.id,
actorAuthMethod: req.permission.authMethod,
actorOrgId: req.permission.orgId,
profileId: req.body.profileId
});
await server.services.auditLog.createAuditLog({
...req.auditLogInfo,
projectId: profile.projectId,
event: {
type: EventType.ISSUE_CERTIFICATE_FROM_PROFILE,
metadata: {
certificateProfileId: req.body.profileId,
certificateId: data.certificateId,
commonName: req.body.commonName || ""
}
}
});
return data;
}
});
server.route({
method: "POST",
url: "/sign-certificate",
config: {
rateLimit: writeLimit
},
schema: {
hide: false,
tags: [ApiDocsTags.PkiCertificates],
body: z.object({
profileId: z.string().uuid(),
csr: z.string().trim().min(1).max(4096),
ttl: z.string().refine((val) => ms(val) > 0, "TTL must be a positive number"),
notBefore: validateCaDateField.optional(),
notAfter: validateCaDateField.optional()
}),
response: {
200: z.object({
certificate: z.string().trim(),
issuingCaCertificate: z.string().trim(),
certificateChain: z.string().trim(),
serialNumber: z.string().trim(),
certificateId: z.string()
})
}
},
onRequest: verifyAuth([AuthMode.JWT, AuthMode.IDENTITY_ACCESS_TOKEN]),
handler: async (req) => {
const data = await server.services.certificateV3.signCertificateFromProfile({
actor: req.permission.type,
actorId: req.permission.id,
actorAuthMethod: req.permission.authMethod,
actorOrgId: req.permission.orgId,
profileId: req.body.profileId,
csr: req.body.csr,
validity: {
ttl: req.body.ttl
},
notBefore: req.body.notBefore ? new Date(req.body.notBefore) : undefined,
notAfter: req.body.notAfter ? new Date(req.body.notAfter) : undefined
});
const profile = await server.services.certificateProfile.getProfileById({
actor: req.permission.type,
actorId: req.permission.id,
actorAuthMethod: req.permission.authMethod,
actorOrgId: req.permission.orgId,
profileId: req.body.profileId
});
await server.services.auditLog.createAuditLog({
...req.auditLogInfo,
projectId: profile.projectId,
event: {
type: EventType.SIGN_CERTIFICATE_FROM_PROFILE,
metadata: {
certificateProfileId: req.body.profileId,
certificateId: data.certificateId
}
}
});
return data;
}
});
server.route({
method: "POST",
url: "/order-certificate",
config: {
rateLimit: writeLimit
},
schema: {
hide: false,
tags: [ApiDocsTags.PkiCertificates],
body: z.object({
profileId: z.string().uuid(),
identifiers: z
.array(
z.object({
type: z.enum(["dns", "ip"]),
value: z.string()
})
)
.min(1),
ttl: z.string().refine((val) => ms(val) > 0, "TTL must be a positive number"),
keyUsages: z.nativeEnum(CertKeyUsage).array().optional(),
extendedKeyUsages: z.nativeEnum(CertExtendedKeyUsage).array().optional(),
notBefore: validateCaDateField.optional(),
notAfter: validateCaDateField.optional(),
commonName: validateTemplateRegexField.optional(),
organization: z.string().optional(),
organizationUnit: z.string().optional(),
locality: z.string().optional(),
state: z.string().optional(),
country: z.string().length(2).optional(),
email: z.string().email().optional(),
streetAddress: z.string().optional(),
postalCode: z.string().optional(),
signatureAlgorithm: z.string().optional(),
keyAlgorithm: z.string().optional()
}),
response: {
200: z.object({
orderId: z.string(),
status: z.enum(["pending", "processing", "valid", "invalid"]),
identifiers: z.array(
z.object({
type: z.enum(["dns", "ip"]),
value: z.string(),
status: z.enum(["pending", "processing", "valid", "invalid"])
})
),
authorizations: z.array(
z.object({
identifier: z.object({
type: z.enum(["dns", "ip"]),
value: z.string()
}),
status: z.enum(["pending", "processing", "valid", "invalid"]),
expires: z.string().optional(),
challenges: z.array(
z.object({
type: z.string(),
status: z.enum(["pending", "processing", "valid", "invalid"]),
url: z.string(),
token: z.string()
})
)
})
),
finalize: z.string(),
certificate: z.string().optional()
})
}
},
onRequest: verifyAuth([AuthMode.JWT, AuthMode.IDENTITY_ACCESS_TOKEN]),
handler: async (req) => {
const data = await server.services.certificateV3.orderCertificateFromProfile({
actor: req.permission.type,
actorId: req.permission.id,
actorAuthMethod: req.permission.authMethod,
actorOrgId: req.permission.orgId,
profileId: req.body.profileId,
certificateOrder: {
identifiers: req.body.identifiers,
validity: {
ttl: req.body.ttl
},
commonName: req.body.commonName,
organization: req.body.organization,
organizationUnit: req.body.organizationUnit,
locality: req.body.locality,
state: req.body.state,
country: req.body.country,
email: req.body.email,
streetAddress: req.body.streetAddress,
postalCode: req.body.postalCode,
keyUsages: req.body.keyUsages,
extendedKeyUsages: req.body.extendedKeyUsages,
notBefore: req.body.notBefore ? new Date(req.body.notBefore) : undefined,
notAfter: req.body.notAfter ? new Date(req.body.notAfter) : undefined,
signatureAlgorithm: req.body.signatureAlgorithm,
keyAlgorithm: req.body.keyAlgorithm
}
});
const profile = await server.services.certificateProfile.getProfileById({
actor: req.permission.type,
actorId: req.permission.id,
actorAuthMethod: req.permission.authMethod,
actorOrgId: req.permission.orgId,
profileId: req.body.profileId
});
await server.services.auditLog.createAuditLog({
...req.auditLogInfo,
projectId: profile.projectId,
event: {
type: EventType.ORDER_CERTIFICATE_FROM_PROFILE,
metadata: {
certificateProfileId: req.body.profileId,
orderId: data.orderId,
identifiers: req.body.identifiers.map((id) => `${id.type}:${id.value}`)
}
}
});
return data;
}
});
};
+2
View File
@@ -1,3 +1,4 @@
import { registerCertificatesRouter } from "./certificates-router";
import { registerDeprecatedSecretRouter } from "./deprecated-secret-router";
import { registerExternalMigrationRouter } from "./external-migration-router";
import { registerLoginRouter } from "./login-router";
@@ -10,4 +11,5 @@ export const registerV3Routes = async (server: FastifyZodProvider) => {
await server.register(registerUserRouter, { prefix: "/users" });
await server.register(registerDeprecatedSecretRouter, { prefix: "/secrets" });
await server.register(registerExternalMigrationRouter, { prefix: "/external-migration" });
await server.register(registerCertificatesRouter, { prefix: "/certificates" });
};