Add CA certs to Chef sync and make field names more dynamic

This commit is contained in:
Carlos Monastyrski
2025-11-19 11:32:12 -03:00
parent cf63266bba
commit 37ff9df999
18 changed files with 441 additions and 74 deletions
@@ -139,6 +139,7 @@ export const chefPkiSyncFactory = ({ certificateDAL, certificateSyncDAL }: TChef
cert: string;
privateKey: string;
certificateChain?: string;
caCertificate?: string;
certificateId: string;
isUpdate: boolean;
targetItemName: string;
@@ -150,15 +151,32 @@ export const chefPkiSyncFactory = ({ certificateDAL, certificateSyncDAL }: TChef
const validationErrors: Array<{ name: string; error: string }> = [];
const syncOptions = pkiSync.syncOptions as
| { canRemoveCertificates?: boolean; preserveItemOnRenewal?: boolean }
| {
canRemoveCertificates?: boolean;
preserveItemOnRenewal?: boolean;
fieldMappings?: {
certificate?: string;
privateKey?: string;
certificateChain?: string;
caCertificate?: string;
metadata?: string;
};
}
| undefined;
const canRemoveCertificates = syncOptions?.canRemoveCertificates ?? true;
const preserveItemOnRenewal = syncOptions?.preserveItemOnRenewal ?? true;
const fieldMappings = {
certificate: syncOptions?.fieldMappings?.certificate ?? "certificate",
privateKey: syncOptions?.fieldMappings?.privateKey ?? "private_key",
certificateChain: syncOptions?.fieldMappings?.certificateChain ?? "certificate_chain",
caCertificate: syncOptions?.fieldMappings?.caCertificate ?? "ca_certificate"
};
const activeExternalIdentifiers = new Set<string>();
for (const [certName, certData] of Object.entries(certificateMap)) {
const { cert, privateKey: certPrivateKey, certificateChain, certificateId } = certData;
const { cert, privateKey: certPrivateKey, certificateChain, caCertificate, certificateId } = certData;
if (!cert || cert.trim().length === 0) {
validationErrors.push({
@@ -218,6 +236,7 @@ export const chefPkiSyncFactory = ({ certificateDAL, certificateSyncDAL }: TChef
cert,
privateKey: certPrivateKey,
certificateChain,
caCertificate,
certificateId,
isUpdate,
targetItemName,
@@ -240,18 +259,17 @@ export const chefPkiSyncFactory = ({ certificateDAL, certificateSyncDAL }: TChef
cert,
privateKey: certPrivateKey,
certificateChain,
certificateId,
isUpdate
caCertificate,
certificateId
} = certificateData;
try {
const chefDataBagItem: ChefCertificateDataBagItem = {
id: targetItemName,
certificate: cert,
private_key: certPrivateKey,
...(certificateChain && { certificate_chain: certificateChain }),
...(isUpdate ? {} : { created_at: new Date().toISOString() }),
updated_at: new Date().toISOString()
[fieldMappings.certificate]: cert,
[fieldMappings.privateKey]: certPrivateKey,
...(certificateChain && { [fieldMappings.certificateChain]: certificateChain }),
...(caCertificate && { [fieldMappings.caCertificate]: caCertificate })
};
const itemExists = chefDataBagItems[targetItemName] === true;
@@ -19,6 +19,13 @@ export const ChefPkiSyncConfigSchema = z.object({
)
});
const ChefFieldMappingsSchema = z.object({
certificate: z.string().min(1, "Certificate field name is required").default("certificate"),
privateKey: z.string().min(1, "Private key field name is required").default("private_key"),
certificateChain: z.string().min(1, "Certificate chain field name is required").default("certificate_chain"),
caCertificate: z.string().min(1, "CA certificate field name is required").default("ca_certificate")
});
const ChefPkiSyncOptionsSchema = z.object({
canImportCertificates: z.boolean().default(false),
canRemoveCertificates: z.boolean().default(true),
@@ -57,7 +64,13 @@ const ChefPkiSyncOptionsSchema = z.object({
message:
"Certificate item name schema must include {{certificateId}} placeholder and result in names that contain only alphanumeric characters, underscores, and hyphens and be 1-255 characters long for Chef data bag items."
}
)
),
fieldMappings: ChefFieldMappingsSchema.optional().default({
certificate: "certificate",
privateKey: "private_key",
certificateChain: "certificate_chain",
caCertificate: "ca_certificate"
})
});
export const ChefPkiSyncSchema = PkiSyncSchema.extend({
@@ -95,3 +108,5 @@ export const ChefPkiSyncListItemSchema = z.object({
canImportCertificates: z.literal(false),
canRemoveCertificates: z.literal(true)
});
export { ChefFieldMappingsSchema };
@@ -3,6 +3,7 @@ import { z } from "zod";
import { TChefConnection } from "@app/ee/services/app-connections/chef/chef-connection-types";
import {
ChefFieldMappingsSchema,
ChefPkiSyncConfigSchema,
ChefPkiSyncSchema,
CreateChefPkiSyncSchema,
@@ -11,6 +12,8 @@ import {
export type TChefPkiSyncConfig = z.infer<typeof ChefPkiSyncConfigSchema>;
export type TChefFieldMappings = z.infer<typeof ChefFieldMappingsSchema>;
export type TChefPkiSync = z.infer<typeof ChefPkiSyncSchema>;
export type TChefPkiSyncInput = z.infer<typeof CreateChefPkiSyncSchema>;
@@ -23,16 +26,7 @@ export type TChefPkiSyncWithCredentials = TChefPkiSync & {
export interface ChefCertificateDataBagItem {
id: string;
certificate: string;
private_key: string;
certificate_chain?: string;
common_name?: string;
alternative_names?: string;
serial_number?: string;
not_before?: string;
not_after?: string;
created_at?: string;
updated_at?: string;
[key: string]: string;
}
export interface SyncCertificatesResult {
@@ -236,13 +236,15 @@ export const pkiSyncQueueFactory = ({
}
let certificateChain: string | undefined;
let caCertificate: string | undefined;
try {
if (certBody.encryptedCertificateChain) {
const decryptedCertChain = await kmsDecryptor({
cipherTextBlob: certBody.encryptedCertificateChain
});
certificateChain = decryptedCertChain.toString();
} else if (certificate.caCertId) {
}
if (certificate.caCertId) {
const { caCert, caCertChain } = await getCaCertChain({
caCertId: certificate.caCertId,
certificateAuthorityDAL,
@@ -250,7 +252,10 @@ export const pkiSyncQueueFactory = ({
projectDAL,
kmsService
});
certificateChain = `${caCert}\n${caCertChain}`.trim();
if (!certBody.encryptedCertificateChain) {
certificateChain = `${caCert}\n${caCertChain}`.trim();
}
caCertificate = caCert;
}
} catch (chainError) {
logger.warn(
@@ -259,6 +264,7 @@ export const pkiSyncQueueFactory = ({
);
// Continue without certificate chain
certificateChain = undefined;
caCertificate = undefined;
}
let certificateName: string;
@@ -298,6 +304,7 @@ export const pkiSyncQueueFactory = ({
cert: certificatePem,
privateKey: certPrivateKey || "",
certificateChain,
caCertificate,
alternativeNames,
certificateId: certificate.id
};
@@ -73,7 +73,14 @@ export type TPkiSyncListItem = TPkiSync & {
export type TCertificateMap = Record<
string,
{ cert: string; privateKey: string; certificateChain?: string; alternativeNames?: string[]; certificateId?: string }
{
cert: string;
privateKey: string;
certificateChain?: string;
caCertificate?: string;
alternativeNames?: string[];
certificateId?: string;
}
>;
export type TCreatePkiSyncDTO = {