Merge pull request #4777 from Infisical/feat/pki-sync-v2

PKI Syncs: Certificate Syncs and AWS ARN option
This commit is contained in:
carlosmonastyrski
2025-11-03 11:55:47 -03:00
committed by GitHub
102 changed files with 4150 additions and 1183 deletions
+11 -2
View File
@@ -172,6 +172,7 @@ import { internalCertificateAuthorityServiceFactory } from "@app/services/certif
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 { certificateSyncDALFactory } from "@app/services/certificate-sync/certificate-sync-dal";
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";
@@ -1064,6 +1065,7 @@ export const registerRoutes = async (
const certificateDAL = certificateDALFactory(db);
const certificateBodyDAL = certificateBodyDALFactory(db);
const certificateSecretDAL = certificateSecretDALFactory(db);
const certificateSyncDAL = certificateSyncDALFactory(db);
const pkiAlertDAL = pkiAlertDALFactory(db);
const pkiCollectionDAL = pkiCollectionDALFactory(db);
@@ -2027,7 +2029,8 @@ export const registerRoutes = async (
certificateBodyDAL,
certificateSecretDAL,
certificateAuthorityDAL,
certificateAuthorityCertDAL
certificateAuthorityCertDAL,
certificateSyncDAL
});
const pkiSyncCleanup = pkiSyncCleanupQueueServiceFactory({
@@ -2138,6 +2141,7 @@ export const registerRoutes = async (
permissionService,
pkiCollectionDAL,
pkiCollectionItemDAL,
certificateSyncDAL,
pkiSyncDAL,
pkiSyncQueue
});
@@ -2149,7 +2153,10 @@ export const registerRoutes = async (
certificateProfileDAL,
certificateTemplateV2Service,
internalCaService: internalCertificateAuthorityService,
permissionService
permissionService,
certificateSyncDAL,
pkiSyncDAL,
pkiSyncQueue
});
const certificateV3Queue = certificateV3QueueServiceFactory({
@@ -2191,6 +2198,8 @@ export const registerRoutes = async (
const pkiSyncService = pkiSyncServiceFactory({
pkiSyncDAL,
certificateDAL,
certificateSyncDAL,
pkiSubscriberDAL,
appConnectionService,
permissionService,
@@ -121,9 +121,7 @@ export const registerCertificateProfilesRouter = async (server: FastifyZodProvid
limit: z.coerce.number().min(1).max(100).default(20),
search: z.string().optional(),
enrollmentType: z.nativeEnum(EnrollmentType).optional(),
caId: z.string().uuid().optional(),
includeMetrics: z.coerce.boolean().optional().default(false),
expiringDays: z.coerce.number().min(1).max(365).optional().default(7)
caId: z.string().uuid().optional()
}),
response: {
200: z.object({
@@ -195,10 +193,6 @@ export const registerCertificateProfilesRouter = async (server: FastifyZodProvid
params: z.object({
id: z.string().uuid()
}),
querystring: z.object({
includeMetrics: z.coerce.boolean().optional().default(false),
expiringDays: z.coerce.number().min(1).max(365).optional().default(7)
}),
response: {
200: z.object({
certificateProfile: PkiCertificateProfilesSchema.extend({
@@ -232,16 +226,6 @@ export const registerCertificateProfilesRouter = async (server: FastifyZodProvid
autoRenew: z.boolean(),
renewBeforeDays: z.number().optional()
})
.optional(),
metrics: z
.object({
profileId: z.string(),
totalCertificates: z.number(),
activeCertificates: z.number(),
expiredCertificates: z.number(),
expiringCertificates: z.number(),
revokedCertificates: z.number()
})
.optional()
})
})
@@ -257,20 +241,6 @@ export const registerCertificateProfilesRouter = async (server: FastifyZodProvid
profileId: req.params.id
});
let result = certificateProfile;
if (req.query.includeMetrics) {
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
});
result = { ...certificateProfile, metrics };
}
await server.services.auditLog.createAuditLog({
...req.auditLogInfo,
projectId: certificateProfile.projectId,
@@ -283,7 +253,7 @@ export const registerCertificateProfilesRouter = async (server: FastifyZodProvid
}
});
return { certificateProfile: result };
return { certificateProfile };
}
});
@@ -26,7 +26,7 @@ export const registerSyncPkiEndpoints = ({
syncOptions?: Record<string, unknown>;
description?: string;
isAutoSyncEnabled?: boolean;
subscriberId?: string;
subscriberId?: string | null;
}>;
updateSchema: z.ZodType<{
connectionId?: string;
@@ -35,7 +35,7 @@ export const registerSyncPkiEndpoints = ({
syncOptions?: Record<string, unknown>;
description?: string;
isAutoSyncEnabled?: boolean;
subscriberId?: string;
subscriberId?: string | null;
}>;
responseSchema: z.ZodTypeAny;
syncOptions: {
@@ -2,10 +2,11 @@ import { z } from "zod";
import { EventType } from "@app/ee/services/audit-log/audit-log-types";
import { ApiDocsTags } from "@app/lib/api-docs";
import { readLimit } from "@app/server/config/rateLimiter";
import { readLimit, writeLimit } from "@app/server/config/rateLimiter";
import { verifyAuth } from "@app/server/plugins/auth/verify-auth";
import { AppConnection } from "@app/services/app-connection/app-connection-enums";
import { AuthMode } from "@app/services/auth/auth-type";
import { CertificateSyncStatus } from "@app/services/certificate-sync/certificate-sync-enums";
import { PkiSync } from "@app/services/pki-sync/pki-sync-enums";
const PkiSyncSchema = z.object({
@@ -60,7 +61,8 @@ const PkiSyncSchema = z.object({
name: z.string()
})
.nullable()
.optional()
.optional(),
hasCertificate: z.boolean().optional()
});
const PkiSyncOptionsSchema = z.object({
@@ -76,6 +78,27 @@ const PkiSyncOptionsSchema = z.object({
minCertificateNameLength: z.number().optional()
});
const PkiSyncCertificateSchema = z.object({
id: z.string().uuid(),
pkiSyncId: z.string().uuid(),
certificateId: z.string().uuid(),
syncStatus: z.nativeEnum(CertificateSyncStatus),
lastSyncMessage: z.string().nullable().optional(),
lastSyncedAt: z.date().nullable().optional(),
createdAt: z.date(),
updatedAt: z.date(),
certificateSerialNumber: z.string().optional(),
certificateCommonName: z.string().optional(),
certificateAltNames: z.string().optional(),
certificateStatus: z.string().optional(),
certificateNotBefore: z.date().optional(),
certificateNotAfter: z.date().optional(),
certificateRenewBeforeDays: z.number().nullish(),
certificateRenewalError: z.string().nullish(),
pkiSyncName: z.string().optional(),
pkiSyncDestination: z.string().optional()
});
export const registerPkiSyncRouter = async (server: FastifyZodProvider) => {
server.route({
method: "GET",
@@ -111,7 +134,8 @@ export const registerPkiSyncRouter = async (server: FastifyZodProvider) => {
tags: [ApiDocsTags.PkiSyncs],
description: "List all the PKI Syncs for the specified project.",
querystring: z.object({
projectId: z.string().trim().min(1)
projectId: z.string().trim().min(1),
certificateId: z.string().uuid().optional()
}),
response: {
200: z.object({ pkiSyncs: PkiSyncSchema.array() })
@@ -120,11 +144,11 @@ export const registerPkiSyncRouter = async (server: FastifyZodProvider) => {
onRequest: verifyAuth([AuthMode.JWT, AuthMode.IDENTITY_ACCESS_TOKEN]),
handler: async (req) => {
const {
query: { projectId },
query: { projectId, certificateId },
permission
} = req;
const pkiSyncs = await server.services.pkiSync.listPkiSyncsByProjectId({ projectId }, permission);
const pkiSyncs = await server.services.pkiSync.listPkiSyncsByProjectId({ projectId, certificateId }, permission);
await server.services.auditLog.createAuditLog({
...req.auditLogInfo,
@@ -179,4 +203,163 @@ export const registerPkiSyncRouter = async (server: FastifyZodProvider) => {
return pkiSync;
}
});
server.route({
method: "GET",
url: "/:pkiSyncId/certificates",
config: {
rateLimit: readLimit
},
schema: {
hide: false,
tags: [ApiDocsTags.PkiSyncs],
description: "List all certificates associated with a PKI Sync.",
params: z.object({
pkiSyncId: z.string().uuid()
}),
querystring: z.object({
offset: z.coerce.number().min(0).default(0),
limit: z.coerce.number().min(1).max(100).default(20)
}),
response: {
200: z.object({
certificates: PkiSyncCertificateSchema.array(),
totalCount: z.number()
})
}
},
onRequest: verifyAuth([AuthMode.JWT, AuthMode.IDENTITY_ACCESS_TOKEN]),
handler: async (req) => {
const { pkiSyncId } = req.params;
const { offset, limit } = req.query;
const { certificates, totalCount, pkiSyncInfo } = await server.services.pkiSync.listPkiSyncCertificates(
{ pkiSyncId, offset, limit },
req.permission
);
await server.services.auditLog.createAuditLog({
...req.auditLogInfo,
projectId: pkiSyncInfo.projectId,
event: {
type: EventType.GET_PKI_SYNC_CERTIFICATES,
metadata: {
syncId: pkiSyncId,
destination: pkiSyncInfo.destination,
count: certificates.length,
certificateIds: certificates.map((c) => c.certificateId)
}
}
});
return { certificates, totalCount };
}
});
server.route({
method: "POST",
url: "/:pkiSyncId/certificates",
config: {
rateLimit: writeLimit
},
schema: {
hide: false,
tags: [ApiDocsTags.PkiSyncs],
description: "Add certificates to a PKI Sync.",
params: z.object({
pkiSyncId: z.string().uuid()
}),
body: z.object({
certificateIds: z.array(z.string().uuid()).min(1, "At least one certificate ID is required")
}),
response: {
200: z.object({
addedCertificates: z.array(
z.object({
id: z.string().uuid(),
pkiSyncId: z.string().uuid(),
certificateId: z.string().uuid(),
syncStatus: z.string().default("pending").optional().nullable(),
lastSyncMessage: z.string().optional().nullable(),
lastSyncedAt: z.date().optional().nullable(),
createdAt: z.date(),
updatedAt: z.date()
})
)
})
}
},
onRequest: verifyAuth([AuthMode.JWT, AuthMode.IDENTITY_ACCESS_TOKEN]),
handler: async (req) => {
const { pkiSyncId } = req.params;
const { certificateIds } = req.body;
const { addedCertificates, pkiSyncInfo } = await server.services.pkiSync.addCertificatesToPkiSync(
{ pkiSyncId, certificateIds },
req.permission
);
await server.services.auditLog.createAuditLog({
...req.auditLogInfo,
projectId: pkiSyncInfo.projectId,
event: {
type: EventType.UPDATE_PKI_SYNC,
metadata: {
pkiSyncId,
name: pkiSyncInfo.name
}
}
});
return { addedCertificates };
}
});
server.route({
method: "DELETE",
url: "/:pkiSyncId/certificates",
config: {
rateLimit: writeLimit
},
schema: {
hide: false,
tags: [ApiDocsTags.PkiSyncs],
description: "Remove certificates from a PKI Sync.",
params: z.object({
pkiSyncId: z.string().uuid()
}),
body: z.object({
certificateIds: z.array(z.string().uuid()).min(1, "At least one certificate ID is required")
}),
response: {
200: z.object({
removedCount: z.number()
})
}
},
onRequest: verifyAuth([AuthMode.JWT, AuthMode.IDENTITY_ACCESS_TOKEN]),
handler: async (req) => {
const { pkiSyncId } = req.params;
const { certificateIds } = req.body;
const { removedCount, pkiSyncInfo } = await server.services.pkiSync.removeCertificatesFromPkiSync(
{ pkiSyncId, certificateIds },
req.permission
);
await server.services.auditLog.createAuditLog({
...req.auditLogInfo,
projectId: pkiSyncInfo.projectId,
event: {
type: EventType.UPDATE_PKI_SYNC,
metadata: {
pkiSyncId,
name: pkiSyncInfo.name
}
}
});
return { removedCount };
}
});
};
@@ -1195,8 +1195,13 @@ export const registerProjectRouter = async (server: FastifyZodProvider) => {
querystring: z.object({
friendlyName: z.string().optional().describe(PROJECTS.LIST_CERTIFICATES.friendlyName),
commonName: z.string().optional().describe(PROJECTS.LIST_CERTIFICATES.commonName),
offset: z.coerce.number().min(0).max(100).default(0).describe(PROJECTS.LIST_CERTIFICATES.offset),
limit: z.coerce.number().min(1).max(100).default(25).describe(PROJECTS.LIST_CERTIFICATES.limit)
offset: z.coerce.number().min(0).default(0).describe(PROJECTS.LIST_CERTIFICATES.offset),
limit: z.coerce.number().min(1).max(100).default(25).describe(PROJECTS.LIST_CERTIFICATES.limit),
forPkiSync: z.coerce
.boolean()
.default(false)
.optional()
.describe("Retrieve only certificates available for PKI sync")
}),
response: {
200: z.object({