Add PKI Syncs docs and a few improvements on the router

This commit is contained in:
Carlos Monastyrski
2025-09-17 10:49:31 -03:00
parent bc04fc6113
commit 8130be5e2f
52 changed files with 1124 additions and 690 deletions
@@ -4,13 +4,23 @@ import { AxiosError } from "axios";
import { request } from "@app/lib/config/request";
import { logger } from "@app/lib/logger";
import { TAppConnectionDALFactory } from "@app/services/app-connection/app-connection-dal";
import { AppConnection } from "@app/services/app-connection/app-connection-enums";
import { getAzureConnectionAccessToken } from "@app/services/app-connection/azure-key-vault";
import { TKmsServiceFactory } from "@app/services/kms/kms-service";
import { TCertificateMap } from "@app/services/pki-sync/pki-sync-types";
import { PkiSync } from "../pki-sync-enums";
import { PkiSyncError } from "../pki-sync-errors";
import { GetAzureKeyVaultCertificate, TAzureKeyVaultPkiSyncWithCredentials } from "./azure-key-vault-pki-sync-types";
export const AZURE_KEY_VAULT_PKI_SYNC_LIST_OPTION = {
name: "Azure Key Vault" as const,
connection: AppConnection.AzureKeyVault,
destination: PkiSync.AzureKeyVault,
canImportCertificates: false,
canRemoveCertificates: true
};
type TAzureKeyVaultPkiSyncFactoryDeps = {
appConnectionDAL: Pick<TAppConnectionDALFactory, "findById" | "updateById">;
kmsService: Pick<TKmsServiceFactory, "createCipherPairWithDataKey">;
@@ -56,7 +66,6 @@ export const azureKeyVaultPkiSyncFactory = ({ kmsService, appConnectionDAL }: TA
lastSlashIndex = getAzureKeyVaultCertificate.id.lastIndexOf("/");
}
// Get the certificate details
const azureKeyVaultCertificate = await request.get<GetAzureKeyVaultCertificate>(
`${getAzureKeyVaultCertificate.id}?api-version=7.4`,
{
@@ -66,7 +75,6 @@ export const azureKeyVaultPkiSyncFactory = ({ kmsService, appConnectionDAL }: TA
}
);
// Convert base64 certificate to PEM format if available
let certPem = "";
if (azureKeyVaultCertificate.data.cer) {
try {
@@ -75,7 +83,6 @@ export const azureKeyVaultPkiSyncFactory = ({ kmsService, appConnectionDAL }: TA
const base64Cert = azureKeyVaultCertificate.data.cer;
certPem = `-----BEGIN CERTIFICATE-----\n${base64Cert.match(/.{1,64}/g)?.join("\n")}\n-----END CERTIFICATE-----`;
} catch (error) {
// If conversion fails, assume it's already in PEM format
certPem = azureKeyVaultCertificate.data.cer;
}
}
@@ -259,7 +266,6 @@ export const azureKeyVaultPkiSyncFactory = ({ kmsService, appConnectionDAL }: TA
{ certificateKey: key, syncId: pkiSync.id },
"Certificate exists in deleted but recoverable state in Azure Key Vault - skipping upload"
);
// Return a successful result to avoid failing the entire sync
return { key, success: false, skipped: true, reason: "Certificate in deleted but recoverable state" };
}
@@ -0,0 +1,41 @@
import { z } from "zod";
import { AppConnection } from "@app/services/app-connection/app-connection-enums";
import { PkiSync } from "@app/services/pki-sync/pki-sync-enums";
import { PkiSyncSchema } from "@app/services/pki-sync/pki-sync-schemas";
import { AzureKeyVaultPkiSyncConfigSchema } from "./azure-key-vault-pki-sync-types";
export const AzureKeyVaultPkiSyncSchema = PkiSyncSchema.extend({
destination: z.literal(PkiSync.AzureKeyVault),
destinationConfig: AzureKeyVaultPkiSyncConfigSchema
});
export const CreateAzureKeyVaultPkiSyncSchema = z.object({
name: z.string().trim().min(1).max(64),
description: z.string().optional(),
isAutoSyncEnabled: z.boolean().default(true),
destinationConfig: AzureKeyVaultPkiSyncConfigSchema,
syncOptions: z.record(z.unknown()).optional().default({}),
subscriberId: z.string().optional(),
connectionId: z.string(),
projectId: z.string().trim().min(1)
});
export const UpdateAzureKeyVaultPkiSyncSchema = z.object({
name: z.string().trim().min(1).max(64).optional(),
description: z.string().optional(),
isAutoSyncEnabled: z.boolean().optional(),
destinationConfig: AzureKeyVaultPkiSyncConfigSchema.optional(),
syncOptions: z.record(z.unknown()).optional(),
subscriberId: z.string().optional(),
connectionId: z.string().optional()
});
export const AzureKeyVaultPkiSyncListItemSchema = z.object({
name: z.literal("Azure Key Vault"),
connection: z.literal(AppConnection.AzureKeyVault),
destination: z.literal(PkiSync.AzureKeyVault),
canImportCertificates: z.literal(false),
canRemoveCertificates: z.literal(true)
});
@@ -0,0 +1,3 @@
export * from "./azure-key-vault-pki-sync-fns";
export * from "./azure-key-vault-pki-sync-schemas";
export * from "./azure-key-vault-pki-sync-types";
@@ -21,6 +21,7 @@ const basePkiSyncQuery = ({ filter, db, tx }: { db: TDbClient; filter?: PkiSyncF
db.ref("app").withSchema(TableName.AppConnection).as("appConnectionApp"),
db.ref("encryptedCredentials").withSchema(TableName.AppConnection).as("appConnectionEncryptedCredentials"),
db.ref("orgId").withSchema(TableName.AppConnection).as("appConnectionOrgId"),
db.ref("projectId").withSchema(TableName.AppConnection).as("appConnectionProjectId"),
db.ref("method").withSchema(TableName.AppConnection).as("appConnectionMethod"),
db.ref("description").withSchema(TableName.AppConnection).as("appConnectionDescription"),
db.ref("version").withSchema(TableName.AppConnection).as("appConnectionVersion"),
@@ -47,6 +48,7 @@ const expandPkiSync = (pkiSync: Awaited<ReturnType<typeof basePkiSyncQuery>>[num
appConnectionApp,
appConnectionEncryptedCredentials,
appConnectionOrgId,
appConnectionProjectId,
appConnectionMethod,
appConnectionDescription,
appConnectionVersion,
@@ -70,6 +72,7 @@ const expandPkiSync = (pkiSync: Awaited<ReturnType<typeof basePkiSyncQuery>>[num
app: appConnectionApp,
encryptedCredentials: appConnectionEncryptedCredentials,
orgId: appConnectionOrgId,
projectId: appConnectionProjectId,
method: appConnectionMethod,
description: appConnectionDescription,
version: appConnectionVersion,
@@ -5,11 +5,16 @@ import { BadRequestError } from "@app/lib/errors";
import { TAppConnectionDALFactory } from "@app/services/app-connection/app-connection-dal";
import { TKmsServiceFactory } from "@app/services/kms/kms-service";
import { AZURE_KEY_VAULT_PKI_SYNC_LIST_OPTION } from "./azure-key-vault/azure-key-vault-pki-sync-fns";
import { PkiSync } from "./pki-sync-enums";
import { TCertificateMap, TPkiSyncWithCredentials } from "./pki-sync-types";
const ENTERPRISE_PKI_SYNCS: PkiSync[] = [];
const PKI_SYNC_LIST_OPTIONS = {
[PkiSync.AzureKeyVault]: AZURE_KEY_VAULT_PKI_SYNC_LIST_OPTION
};
export const enterprisePkiSyncCheck = async (
licenseService: Pick<TLicenseServiceFactory, "getPlan">,
orgId: string,
@@ -26,7 +31,7 @@ export const enterprisePkiSyncCheck = async (
};
export const listPkiSyncOptions = () => {
return Object.values(PkiSync);
return Object.values(PKI_SYNC_LIST_OPTIONS).sort((a, b) => a.name.localeCompare(b.name));
};
export const matchesSchema = <T extends ZodSchema>(schema: T, data: unknown): data is z.infer<T> => {
@@ -216,7 +216,6 @@ export const pkiSyncQueueFactory = ({
encryptedCertificate
});
// Create certificate secret record with encrypted private key (if available)
if (certData.privateKey) {
const { cipherTextBlob: encryptedPrivateKey } = await kmsEncryptor({
plainText: Buffer.from(certData.privateKey)
@@ -231,7 +230,6 @@ export const pkiSyncQueueFactory = ({
logger.info(`Successfully created certificate ${certData.name} with ID ${createdCert.id}`);
} catch (error) {
logger.error(`Failed to create certificate ${certData.name}: ${String(error)}`);
// Continue with other certificates even if one fails
}
}
};
@@ -264,7 +262,6 @@ export const pkiSyncQueueFactory = ({
for (const certificate of certificates) {
try {
// Only sync certificates issued by Infisical (not imported ones)
// Imported certificates don't have caId and certificateTemplateId
if (!certificate.caId) {
logger.debug(
{ certificateId: certificate.id, subscriberId },
@@ -503,15 +500,14 @@ export const pkiSyncQueueFactory = ({
try {
const {
connection: { orgId, encryptedCredentials },
projectId
connection: { orgId, encryptedCredentials, projectId: appConnectionProjectId }
} = pkiSync;
const credentials = await decryptAppConnectionCredentials({
orgId,
encryptedCredentials,
kmsService,
projectId
projectId: appConnectionProjectId
});
const pkiSyncWithCredentials = {
@@ -564,7 +560,6 @@ export const pkiSyncQueueFactory = ({
if (err instanceof PkiSyncError && !err.shouldRetry) {
isFinalAttempt = true;
} else {
// re-throw so job fails
throw err;
}
} finally {
@@ -630,15 +625,14 @@ export const pkiSyncQueueFactory = ({
try {
const {
connection: { orgId, encryptedCredentials },
projectId
connection: { orgId, encryptedCredentials, projectId: appConnectionProjectId }
} = pkiSync;
const credentials = await decryptAppConnectionCredentials({
orgId,
encryptedCredentials,
kmsService,
projectId
projectId: appConnectionProjectId
});
await $importCertificates({
@@ -673,7 +667,6 @@ export const pkiSyncQueueFactory = ({
if (err instanceof PkiSyncError && !err.shouldRetry) {
isFinalAttempt = true;
} else {
// re-throw so job fails
throw err;
}
} finally {
@@ -746,14 +739,14 @@ export const pkiSyncQueueFactory = ({
try {
const {
connection: { orgId, encryptedCredentials }
connection: { orgId, encryptedCredentials, projectId: appConnectionProjectId }
} = pkiSync;
const credentials = await decryptAppConnectionCredentials({
orgId,
encryptedCredentials,
kmsService,
projectId: pkiSync.projectId
projectId: appConnectionProjectId
});
const certificateMap = await $getInfisicalCertificates(pkiSync);
@@ -797,7 +790,6 @@ export const pkiSyncQueueFactory = ({
if (err instanceof PkiSyncError && !err.shouldRetry) {
isFinalAttempt = true;
} else {
// re-throw so job fails
throw err;
}
} finally {
@@ -880,7 +872,6 @@ export const pkiSyncQueueFactory = ({
errorMessage = lastRemoveMessage || null;
}
// Log notification for now - actual email sending would require SMTP configuration
if (projectAdmins.length > 0) {
logger.info(
`PKI Sync ${action} failure notification would be sent to ${projectAdmins.length} admin(s) for sync "${name}" in project "${project.name}". Error: ${errorMessage}`
@@ -40,7 +40,6 @@ export const PkiSyncListItemSchema = PkiSyncSchema.extend({
appConnectionApp: z.string().max(255)
});
// Schema for PKI sync details (includes app connection info)
export const PkiSyncDetailsSchema = PkiSyncSchema.extend({
appConnectionName: z.string().max(255),
appConnectionApp: z.string().max(255)
@@ -103,6 +103,12 @@ export const pkiSyncServiceFactory = ({
// Validates permission to connect and app is valid for sync destination
await appConnectionService.connectAppConnectionById(destinationApp, connectionId, actor);
const defaultSyncOptions = {
canImportCertificates: false,
canRemoveCertificates: true,
...syncOptions
};
try {
const pkiSync = await pkiSyncDAL.create({
name,
@@ -110,7 +116,7 @@ export const pkiSyncServiceFactory = ({
destination,
isAutoSyncEnabled,
destinationConfig,
syncOptions,
syncOptions: defaultSyncOptions,
subscriberId,
connectionId,
projectId,