mirror of
https://github.com/awatertrevi/infisical.git
synced 2026-10-20 15:43:12 +00:00
PKI Syncs improvements
This commit is contained in:
+200
-137
@@ -92,43 +92,6 @@ const shouldSkipCertificateExport = (certificate: AWS.ACM.CertificateSummary): b
|
||||
return isAwsIssuedCertificate(certificate);
|
||||
};
|
||||
|
||||
const findTagByKey = (tags: AWS.ACM.TagList | undefined, key: string): AWS.ACM.Tag | undefined => {
|
||||
if (!tags || !Array.isArray(tags)) {
|
||||
return undefined;
|
||||
}
|
||||
return tags.find((tag: AWS.ACM.Tag) => tag.Key === key && tag.Value);
|
||||
};
|
||||
|
||||
const findInfisicalCertificateTag = (tags: AWS.ACM.TagList | undefined): AWS.ACM.Tag | undefined => {
|
||||
return findTagByKey(tags, INFISICAL_CERTIFICATE_TAG);
|
||||
};
|
||||
|
||||
const validateCertificateIdentification = (
|
||||
certName: string,
|
||||
existingCert: { arn?: string; Tags?: AWS.ACM.TagList; cert?: string; privateKey?: string; certificateChain?: string },
|
||||
alternativeCertNames?: string[]
|
||||
): boolean => {
|
||||
if (!existingCert?.arn || !existingCert?.Tags) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const certNameTag = findInfisicalCertificateTag(existingCert.Tags);
|
||||
|
||||
if (!certNameTag || !certNameTag.Value) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (certNameTag.Value === certName) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (alternativeCertNames && alternativeCertNames.includes(certNameTag.Value)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
};
|
||||
|
||||
const validateCertificateNameSchema = (schema: string): void => {
|
||||
if (!schema.includes("{{certificateId}}")) {
|
||||
throw new Error(
|
||||
@@ -185,7 +148,15 @@ const generateCertificateName = (certificateName: string, pkiSync: TPkiSyncWithC
|
||||
type TAwsCertificateManagerPkiSyncFactoryDeps = {
|
||||
appConnectionDAL: Pick<TAppConnectionDALFactory, "findById" | "updateById">;
|
||||
kmsService: Pick<TKmsServiceFactory, "createCipherPairWithDataKey">;
|
||||
certificateSyncDAL: Pick<TCertificateSyncDALFactory, "removeCertificates">;
|
||||
certificateSyncDAL: Pick<
|
||||
TCertificateSyncDALFactory,
|
||||
| "removeCertificates"
|
||||
| "addCertificates"
|
||||
| "findByPkiSyncAndCertificate"
|
||||
| "updateSyncStatus"
|
||||
| "updateById"
|
||||
| "findByPkiSyncId"
|
||||
>;
|
||||
certificateDAL: Pick<TCertificateDALFactory, "findById">;
|
||||
};
|
||||
|
||||
@@ -418,16 +389,38 @@ export const awsCertificateManagerPkiSyncFactory = ({
|
||||
>;
|
||||
} = await $getAwsAcmCertificates(acm, pkiSync.id);
|
||||
|
||||
const acmCertificatesByArn = new Map<string, (typeof acmCertificates)[string]>();
|
||||
Object.values(acmCertificates).forEach((acmCert) => {
|
||||
if (acmCert.arn) {
|
||||
acmCertificatesByArn.set(acmCert.arn, acmCert);
|
||||
}
|
||||
});
|
||||
|
||||
const existingSyncRecords = await certificateSyncDAL.findByPkiSyncId(pkiSync.id);
|
||||
type SyncRecord = (typeof existingSyncRecords)[0];
|
||||
const syncRecordsByCertId = new Map<string, SyncRecord>();
|
||||
const syncRecordsByExternalId = new Map<string, SyncRecord>();
|
||||
|
||||
existingSyncRecords.forEach((record: SyncRecord) => {
|
||||
if (record.certificateId) {
|
||||
syncRecordsByCertId.set(record.certificateId, record);
|
||||
}
|
||||
if (record.externalIdentifier) {
|
||||
syncRecordsByExternalId.set(record.externalIdentifier, record);
|
||||
}
|
||||
});
|
||||
|
||||
const setCertificates: CertificateImportRequest[] = [];
|
||||
const validationErrors: Array<{ name: string; error: string }> = [];
|
||||
|
||||
const activeCertificateNames = Object.keys(certificateMap);
|
||||
const syncOptions = pkiSync.syncOptions as { preserveArn?: boolean; canRemoveCertificates?: boolean } | undefined;
|
||||
const preserveArn = syncOptions?.preserveArn ?? true;
|
||||
const canRemoveCertificates = syncOptions?.canRemoveCertificates ?? true;
|
||||
|
||||
const activeExternalIdentifiers = new Set<string>();
|
||||
|
||||
for (const [certName, certData] of Object.entries(certificateMap)) {
|
||||
const { cert, privateKey, certificateChain, alternativeNames, certificateId } = certData;
|
||||
const { cert, privateKey, certificateChain, certificateId } = certData;
|
||||
|
||||
try {
|
||||
validateCertificateContent(cert, privateKey);
|
||||
@@ -441,7 +434,7 @@ export const awsCertificateManagerPkiSyncFactory = ({
|
||||
continue;
|
||||
}
|
||||
|
||||
if (preserveArn && certificateId) {
|
||||
if (preserveArn && certificateId && typeof certificateId === "string") {
|
||||
const certificate = await certificateDAL.findById(certificateId);
|
||||
if (certificate?.renewedByCertificateId) {
|
||||
// eslint-disable-next-line no-continue
|
||||
@@ -451,60 +444,120 @@ export const awsCertificateManagerPkiSyncFactory = ({
|
||||
|
||||
const certificateName = generateCertificateName(certName, pkiSync);
|
||||
|
||||
let existingArn: string | undefined;
|
||||
let targetArn: string | undefined;
|
||||
let shouldCreateNew = false;
|
||||
|
||||
const existingCert = Object.values(acmCertificates).find((acmCert) => {
|
||||
return validateCertificateIdentification(certName, acmCert, alternativeNames);
|
||||
});
|
||||
if (!certificateId || typeof certificateId !== "string") {
|
||||
shouldCreateNew = true;
|
||||
} else {
|
||||
const currentCertificate = await certificateDAL.findById(certificateId);
|
||||
const isRenewal = !!currentCertificate?.renewedFromCertificateId;
|
||||
|
||||
if (existingCert?.arn && preserveArn) {
|
||||
// When preserveArn is true, reuse the existing ARN
|
||||
existingArn = existingCert.arn;
|
||||
if (isRenewal) {
|
||||
const currentSyncRecord = syncRecordsByCertId.get(certificateId);
|
||||
const oldCertificateId = currentCertificate.renewedFromCertificateId;
|
||||
const oldSyncRecord = oldCertificateId ? syncRecordsByCertId.get(oldCertificateId) : undefined;
|
||||
|
||||
if (currentSyncRecord?.externalIdentifier) {
|
||||
const existingAcmCert = acmCertificatesByArn.get(currentSyncRecord.externalIdentifier);
|
||||
|
||||
if (existingAcmCert) {
|
||||
if (!preserveArn && oldSyncRecord?.externalIdentifier === currentSyncRecord.externalIdentifier) {
|
||||
shouldCreateNew = true;
|
||||
} else if (preserveArn && oldSyncRecord?.externalIdentifier === currentSyncRecord.externalIdentifier) {
|
||||
targetArn = currentSyncRecord.externalIdentifier;
|
||||
shouldCreateNew = true;
|
||||
activeExternalIdentifiers.add(targetArn);
|
||||
|
||||
if (oldCertificateId && oldSyncRecord) {
|
||||
await certificateSyncDAL.removeCertificates(pkiSync.id, [oldCertificateId]);
|
||||
}
|
||||
} else {
|
||||
targetArn = currentSyncRecord.externalIdentifier;
|
||||
activeExternalIdentifiers.add(targetArn);
|
||||
shouldCreateNew = false;
|
||||
}
|
||||
} else {
|
||||
shouldCreateNew = true;
|
||||
}
|
||||
} else if (preserveArn && oldSyncRecord?.externalIdentifier) {
|
||||
const existingAcmCert = acmCertificatesByArn.get(oldSyncRecord.externalIdentifier);
|
||||
|
||||
if (existingAcmCert) {
|
||||
targetArn = oldSyncRecord.externalIdentifier;
|
||||
shouldCreateNew = true;
|
||||
activeExternalIdentifiers.add(targetArn);
|
||||
if (oldCertificateId) {
|
||||
await certificateSyncDAL.removeCertificates(pkiSync.id, [oldCertificateId]);
|
||||
}
|
||||
} else {
|
||||
shouldCreateNew = true;
|
||||
}
|
||||
} else {
|
||||
shouldCreateNew = true;
|
||||
}
|
||||
} else {
|
||||
const existingSyncRecord = syncRecordsByCertId.get(certificateId);
|
||||
if (existingSyncRecord?.externalIdentifier) {
|
||||
const existingAcmCert = acmCertificatesByArn.get(existingSyncRecord.externalIdentifier);
|
||||
if (existingAcmCert) {
|
||||
targetArn = existingSyncRecord.externalIdentifier;
|
||||
activeExternalIdentifiers.add(targetArn);
|
||||
shouldCreateNew = false;
|
||||
} else {
|
||||
shouldCreateNew = true;
|
||||
}
|
||||
} else {
|
||||
shouldCreateNew = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
setCertificates.push({
|
||||
key: certName,
|
||||
name: certificateName,
|
||||
cert,
|
||||
privateKey,
|
||||
certificateChain,
|
||||
existingArn,
|
||||
certificateId
|
||||
});
|
||||
if (shouldCreateNew) {
|
||||
setCertificates.push({
|
||||
key: certName,
|
||||
name: certificateName,
|
||||
cert,
|
||||
privateKey,
|
||||
certificateChain,
|
||||
existingArn: targetArn,
|
||||
certificateId: certificateId as string
|
||||
});
|
||||
}
|
||||
|
||||
if (targetArn) {
|
||||
activeExternalIdentifiers.add(targetArn);
|
||||
}
|
||||
}
|
||||
|
||||
const certificatesToRemove = canRemoveCertificates
|
||||
? Object.values(acmCertificates)
|
||||
.filter((acmCert) => {
|
||||
if (!acmCert.arn || !acmCert.Tags) {
|
||||
return false;
|
||||
const certificatesToRemove: string[] = [];
|
||||
|
||||
if (canRemoveCertificates) {
|
||||
existingSyncRecords.forEach((syncRecord) => {
|
||||
if (syncRecord.externalIdentifier && !activeExternalIdentifiers.has(syncRecord.externalIdentifier)) {
|
||||
const acmCert = acmCertificatesByArn.get(syncRecord.externalIdentifier);
|
||||
if (acmCert?.arn) {
|
||||
certificatesToRemove.push(acmCert.arn);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
Object.values(acmCertificates).forEach((acmCert) => {
|
||||
if (acmCert.arn && acmCert.Tags) {
|
||||
const hasInfisicalTag = acmCert.Tags.some((tag) => tag.Key === INFISICAL_CERTIFICATE_TAG && tag.Value);
|
||||
|
||||
if (hasInfisicalTag) {
|
||||
const isTrackedInSyncRecords = existingSyncRecords.some(
|
||||
(record) => record.externalIdentifier === acmCert.arn
|
||||
);
|
||||
const isInActiveSet = activeExternalIdentifiers.has(acmCert.arn);
|
||||
if (!isTrackedInSyncRecords && !isInActiveSet && !certificatesToRemove.includes(acmCert.arn)) {
|
||||
certificatesToRemove.push(acmCert.arn);
|
||||
}
|
||||
|
||||
const certNameTag = findInfisicalCertificateTag(acmCert.Tags);
|
||||
if (!certNameTag || !certNameTag.Value) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const isActive = activeCertificateNames.some((activeCertName) => {
|
||||
const certData = certificateMap[activeCertName];
|
||||
if (!certData) return false;
|
||||
|
||||
return validateCertificateIdentification(activeCertName, acmCert, certData.alternativeNames);
|
||||
});
|
||||
|
||||
if (!isActive) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (!preserveArn && isActive) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
})
|
||||
.map((acmCert) => acmCert.arn!)
|
||||
.filter((arn) => arn)
|
||||
: [];
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
const uploadResults = await executeWithConcurrencyLimit(
|
||||
setCertificates,
|
||||
@@ -569,10 +622,21 @@ export const awsCertificateManagerPkiSyncFactory = ({
|
||||
}
|
||||
}
|
||||
|
||||
if (existingArn && preserveArn && certificateId) {
|
||||
const currentCertificate = await certificateDAL.findById(certificateId);
|
||||
if (currentCertificate?.renewedFromCertificateId) {
|
||||
await certificateSyncDAL.removeCertificates(pkiSync.id, [currentCertificate.renewedFromCertificateId]);
|
||||
if (response.CertificateArn && certificateId) {
|
||||
const existingCertSync = await certificateSyncDAL.findByPkiSyncAndCertificate(pkiSync.id, certificateId);
|
||||
if (existingCertSync) {
|
||||
await certificateSyncDAL.updateById(existingCertSync.id, {
|
||||
externalIdentifier: response.CertificateArn,
|
||||
syncStatus: CertificateSyncStatus.Succeeded,
|
||||
lastSyncedAt: new Date()
|
||||
});
|
||||
} else {
|
||||
await certificateSyncDAL.addCertificates(pkiSync.id, [
|
||||
{
|
||||
certificateId,
|
||||
externalIdentifier: response.CertificateArn
|
||||
}
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -682,29 +746,33 @@ export const awsCertificateManagerPkiSyncFactory = ({
|
||||
kmsService
|
||||
);
|
||||
|
||||
const {
|
||||
acmCertificates
|
||||
}: {
|
||||
acmCertificates: Record<
|
||||
string,
|
||||
{ cert: string; privateKey: string; certificateChain?: string; arn?: string; Tags?: AWS.ACM.TagList }
|
||||
>;
|
||||
} = await $getAwsAcmCertificates(acm, pkiSync.id);
|
||||
|
||||
const existingSyncRecords = await certificateSyncDAL.findByPkiSyncId(pkiSync.id);
|
||||
const certificateArnsToRemove: string[] = [];
|
||||
|
||||
const certificateIdToArnMap = new Map<string, string>();
|
||||
for (const certName of certificateNames) {
|
||||
const matchingCerts = Object.values(acmCertificates).filter((acmCert) =>
|
||||
validateCertificateIdentification(certName, acmCert)
|
||||
);
|
||||
const certificateData = deps?.certificateMap?.[certName];
|
||||
if (certificateData?.certificateId) {
|
||||
const { certificateId } = certificateData;
|
||||
|
||||
for (const acmCert of matchingCerts) {
|
||||
if (acmCert.arn) {
|
||||
certificateArnsToRemove.push(acmCert.arn);
|
||||
if (typeof certificateId === "string") {
|
||||
const syncRecord = existingSyncRecords.find((record) => record.certificateId === certificateId);
|
||||
|
||||
if (syncRecord?.externalIdentifier) {
|
||||
certificateArnsToRemove.push(syncRecord.externalIdentifier);
|
||||
certificateIdToArnMap.set(certificateId, syncRecord.externalIdentifier);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (certificateArnsToRemove.length === 0) {
|
||||
return {
|
||||
removed: 0,
|
||||
failed: 0,
|
||||
skipped: certificateNames.length
|
||||
};
|
||||
}
|
||||
|
||||
const results = await executeWithConcurrencyLimit(
|
||||
certificateArnsToRemove,
|
||||
async (certificateArn) =>
|
||||
@@ -714,43 +782,38 @@ export const awsCertificateManagerPkiSyncFactory = ({
|
||||
|
||||
const failedRemovals = results.filter((result) => result.status === "rejected");
|
||||
|
||||
if (failedRemovals.length > 0 && deps?.certificateSyncDAL && deps?.certificateMap) {
|
||||
const certificateNameToArnMap = new Map<string, string>();
|
||||
for (const certName of certificateNames) {
|
||||
const matchingCerts = Object.values(acmCertificates).filter((acmCert) =>
|
||||
validateCertificateIdentification(certName, acmCert)
|
||||
);
|
||||
for (const acmCert of matchingCerts) {
|
||||
if (acmCert.arn) {
|
||||
certificateNameToArnMap.set(acmCert.arn, certName);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (failedRemovals.length > 0 && deps?.certificateSyncDAL) {
|
||||
for (const failure of failedRemovals) {
|
||||
if (failure.status === "rejected") {
|
||||
const failedArn = certificateArnsToRemove[results.indexOf(failure)];
|
||||
const certificateName = certificateNameToArnMap.get(failedArn);
|
||||
if (certificateName && deps.certificateMap[certificateName]?.certificateId) {
|
||||
const { certificateId } = deps.certificateMap[certificateName];
|
||||
if (certificateId) {
|
||||
const errorMessage = failure.reason instanceof Error ? failure.reason.message : "Unknown error";
|
||||
try {
|
||||
await deps.certificateSyncDAL.updateSyncStatus(
|
||||
pkiSync.id,
|
||||
certificateId,
|
||||
CertificateSyncStatus.Failed,
|
||||
`Failed to remove from AWS: ${errorMessage}`
|
||||
);
|
||||
} catch (updateError) {
|
||||
logger.warn(`Failed to update sync status for certificate ${certificateId}:`, String(updateError));
|
||||
}
|
||||
}
|
||||
const certificateId = Array.from(certificateIdToArnMap.entries()).find(([, arn]) => arn === failedArn)?.[0];
|
||||
|
||||
if (certificateId) {
|
||||
const errorMessage = failure.reason instanceof Error ? failure.reason.message : "Unknown error";
|
||||
await deps.certificateSyncDAL.updateSyncStatus(
|
||||
pkiSync.id,
|
||||
certificateId,
|
||||
CertificateSyncStatus.Failed,
|
||||
`Failed to remove from AWS: ${errorMessage}`
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const successfulRemovals = results.filter((result) => result.status === "fulfilled");
|
||||
if (successfulRemovals.length > 0) {
|
||||
const successfulArns = new Set(successfulRemovals.map((_, index) => certificateArnsToRemove[index]));
|
||||
|
||||
const certificateIdsToRemove = Array.from(certificateIdToArnMap.entries())
|
||||
.filter(([, arn]) => successfulArns.has(arn))
|
||||
.map(([certificateId]) => certificateId);
|
||||
|
||||
if (certificateIdsToRemove.length > 0) {
|
||||
await certificateSyncDAL.removeCertificates(pkiSync.id, certificateIdsToRemove);
|
||||
}
|
||||
}
|
||||
|
||||
if (failedRemovals.length > 0) {
|
||||
const failedReasons = failedRemovals.map((failure) => {
|
||||
if (failure.status === "rejected") {
|
||||
|
||||
Reference in New Issue
Block a user