mirror of
https://github.com/awatertrevi/infisical.git
synced 2026-09-22 13:39:35 +00:00
PKI Syncs ui and removal improvements
This commit is contained in:
@@ -93,6 +93,8 @@ const PkiSyncCertificateSchema = z.object({
|
|||||||
certificateStatus: z.string().optional(),
|
certificateStatus: z.string().optional(),
|
||||||
certificateNotBefore: z.date().optional(),
|
certificateNotBefore: z.date().optional(),
|
||||||
certificateNotAfter: z.date().optional(),
|
certificateNotAfter: z.date().optional(),
|
||||||
|
certificateRenewBeforeDays: z.number().optional(),
|
||||||
|
certificateRenewalError: z.string().optional(),
|
||||||
pkiSyncName: z.string().optional(),
|
pkiSyncName: z.string().optional(),
|
||||||
pkiSyncDestination: z.string().optional()
|
pkiSyncDestination: z.string().optional()
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -184,6 +184,9 @@ export const certificateSyncDALFactory = (db: TDbClient) => {
|
|||||||
certificateStatus?: string;
|
certificateStatus?: string;
|
||||||
certificateNotBefore?: Date;
|
certificateNotBefore?: Date;
|
||||||
certificateNotAfter?: Date;
|
certificateNotAfter?: Date;
|
||||||
|
certificateRenewBeforeDays?: number;
|
||||||
|
certificateRenewedByCertificateId?: string;
|
||||||
|
certificateRenewalError?: string;
|
||||||
pkiSyncName?: string;
|
pkiSyncName?: string;
|
||||||
pkiSyncDestination?: string;
|
pkiSyncDestination?: string;
|
||||||
})[];
|
})[];
|
||||||
@@ -216,6 +219,9 @@ export const certificateSyncDALFactory = (db: TDbClient) => {
|
|||||||
db.ref("status").withSchema(TableName.Certificate).as("certificateStatus"),
|
db.ref("status").withSchema(TableName.Certificate).as("certificateStatus"),
|
||||||
db.ref("notBefore").withSchema(TableName.Certificate).as("certificateNotBefore"),
|
db.ref("notBefore").withSchema(TableName.Certificate).as("certificateNotBefore"),
|
||||||
db.ref("notAfter").withSchema(TableName.Certificate).as("certificateNotAfter"),
|
db.ref("notAfter").withSchema(TableName.Certificate).as("certificateNotAfter"),
|
||||||
|
db.ref("renewBeforeDays").withSchema(TableName.Certificate).as("certificateRenewBeforeDays"),
|
||||||
|
db.ref("renewedByCertificateId").withSchema(TableName.Certificate).as("certificateRenewedByCertificateId"),
|
||||||
|
db.ref("renewalError").withSchema(TableName.Certificate).as("certificateRenewalError"),
|
||||||
db.ref("name").withSchema(TableName.PkiSync).as("pkiSyncName"),
|
db.ref("name").withSchema(TableName.PkiSync).as("pkiSyncName"),
|
||||||
db.ref("destination").withSchema(TableName.PkiSync).as("pkiSyncDestination")
|
db.ref("destination").withSchema(TableName.PkiSync).as("pkiSyncDestination")
|
||||||
)
|
)
|
||||||
@@ -235,6 +241,9 @@ export const certificateSyncDALFactory = (db: TDbClient) => {
|
|||||||
certificateStatus?: string;
|
certificateStatus?: string;
|
||||||
certificateNotBefore?: Date;
|
certificateNotBefore?: Date;
|
||||||
certificateNotAfter?: Date;
|
certificateNotAfter?: Date;
|
||||||
|
certificateRenewBeforeDays?: number;
|
||||||
|
certificateRenewedByCertificateId?: string;
|
||||||
|
certificateRenewalError?: string;
|
||||||
pkiSyncName?: string;
|
pkiSyncName?: string;
|
||||||
pkiSyncDestination?: string;
|
pkiSyncDestination?: string;
|
||||||
})[];
|
})[];
|
||||||
|
|||||||
@@ -2,5 +2,6 @@ export enum CertificateSyncStatus {
|
|||||||
Pending = "pending",
|
Pending = "pending",
|
||||||
Syncing = "syncing",
|
Syncing = "syncing",
|
||||||
Succeeded = "succeeded",
|
Succeeded = "succeeded",
|
||||||
Failed = "failed"
|
Failed = "failed",
|
||||||
|
Running = "running"
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -51,7 +51,7 @@ import {
|
|||||||
import { TCertificateSyncDALFactory } from "../certificate-sync/certificate-sync-dal";
|
import { TCertificateSyncDALFactory } from "../certificate-sync/certificate-sync-dal";
|
||||||
import { TPkiSyncDALFactory } from "../pki-sync/pki-sync-dal";
|
import { TPkiSyncDALFactory } from "../pki-sync/pki-sync-dal";
|
||||||
import { TPkiSyncQueueFactory } from "../pki-sync/pki-sync-queue";
|
import { TPkiSyncQueueFactory } from "../pki-sync/pki-sync-queue";
|
||||||
import { replaceCertificateInSyncs, triggerAutoSyncForCertificate } from "../pki-sync/pki-sync-utils";
|
import { addRenewedCertificateToSyncs, triggerAutoSyncForCertificate } from "../pki-sync/pki-sync-utils";
|
||||||
import {
|
import {
|
||||||
TCertificateFromProfileResponse,
|
TCertificateFromProfileResponse,
|
||||||
TCertificateOrderResponse,
|
TCertificateOrderResponse,
|
||||||
@@ -885,7 +885,7 @@ export const certificateV3ServiceFactory = ({
|
|||||||
tx
|
tx
|
||||||
);
|
);
|
||||||
|
|
||||||
await replaceCertificateInSyncs(originalCert.id, newCert.id, { certificateSyncDAL }, tx);
|
await addRenewedCertificateToSyncs(originalCert.id, newCert.id, { certificateSyncDAL }, tx);
|
||||||
|
|
||||||
return {
|
return {
|
||||||
certificate,
|
certificate,
|
||||||
|
|||||||
@@ -15,6 +15,9 @@ import {
|
|||||||
AwsConnectionAssumeRoleCredentialsSchema
|
AwsConnectionAssumeRoleCredentialsSchema
|
||||||
} from "@app/services/app-connection/aws/aws-connection-schemas";
|
} from "@app/services/app-connection/aws/aws-connection-schemas";
|
||||||
import { TAwsConnectionConfig } from "@app/services/app-connection/aws/aws-connection-types";
|
import { TAwsConnectionConfig } from "@app/services/app-connection/aws/aws-connection-types";
|
||||||
|
import { TCertificateDALFactory } from "@app/services/certificate/certificate-dal";
|
||||||
|
import { TCertificateSyncDALFactory } from "@app/services/certificate-sync/certificate-sync-dal";
|
||||||
|
import { CertificateSyncStatus } from "@app/services/certificate-sync/certificate-sync-enums";
|
||||||
import { createConnectionQueue, RateLimitConfig } from "@app/services/connection-queue";
|
import { createConnectionQueue, RateLimitConfig } from "@app/services/connection-queue";
|
||||||
import { TKmsServiceFactory } from "@app/services/kms/kms-service";
|
import { TKmsServiceFactory } from "@app/services/kms/kms-service";
|
||||||
import { TCertificateMap } from "@app/services/pki-sync/pki-sync-types";
|
import { TCertificateMap } from "@app/services/pki-sync/pki-sync-types";
|
||||||
@@ -182,6 +185,8 @@ const generateCertificateName = (certificateName: string, pkiSync: TPkiSyncWithC
|
|||||||
type TAwsCertificateManagerPkiSyncFactoryDeps = {
|
type TAwsCertificateManagerPkiSyncFactoryDeps = {
|
||||||
appConnectionDAL: Pick<TAppConnectionDALFactory, "findById" | "updateById">;
|
appConnectionDAL: Pick<TAppConnectionDALFactory, "findById" | "updateById">;
|
||||||
kmsService: Pick<TKmsServiceFactory, "createCipherPairWithDataKey">;
|
kmsService: Pick<TKmsServiceFactory, "createCipherPairWithDataKey">;
|
||||||
|
certificateSyncDAL: Pick<TCertificateSyncDALFactory, "removeCertificates">;
|
||||||
|
certificateDAL: Pick<TCertificateDALFactory, "findById">;
|
||||||
};
|
};
|
||||||
|
|
||||||
const getAwsAcmClient = async (
|
const getAwsAcmClient = async (
|
||||||
@@ -240,7 +245,9 @@ const getAwsAcmClient = async (
|
|||||||
|
|
||||||
export const awsCertificateManagerPkiSyncFactory = ({
|
export const awsCertificateManagerPkiSyncFactory = ({
|
||||||
kmsService,
|
kmsService,
|
||||||
appConnectionDAL
|
appConnectionDAL,
|
||||||
|
certificateSyncDAL,
|
||||||
|
certificateDAL
|
||||||
}: TAwsCertificateManagerPkiSyncFactoryDeps) => {
|
}: TAwsCertificateManagerPkiSyncFactoryDeps) => {
|
||||||
const deleteCertificateFromAcm = async (
|
const deleteCertificateFromAcm = async (
|
||||||
acm: AWS.ACM,
|
acm: AWS.ACM,
|
||||||
@@ -415,11 +422,12 @@ export const awsCertificateManagerPkiSyncFactory = ({
|
|||||||
const validationErrors: Array<{ name: string; error: string }> = [];
|
const validationErrors: Array<{ name: string; error: string }> = [];
|
||||||
|
|
||||||
const activeCertificateNames = Object.keys(certificateMap);
|
const activeCertificateNames = Object.keys(certificateMap);
|
||||||
const syncOptions = pkiSync.syncOptions as { preserveArn?: boolean } | undefined;
|
const syncOptions = pkiSync.syncOptions as { preserveArn?: boolean; canRemoveCertificates?: boolean } | undefined;
|
||||||
const preserveArn = syncOptions?.preserveArn ?? true;
|
const preserveArn = syncOptions?.preserveArn ?? true;
|
||||||
|
const canRemoveCertificates = syncOptions?.canRemoveCertificates ?? true;
|
||||||
|
|
||||||
Object.entries(certificateMap).forEach(([certName, certData]) => {
|
for (const [certName, certData] of Object.entries(certificateMap)) {
|
||||||
const { cert, privateKey, certificateChain, alternativeNames } = certData;
|
const { cert, privateKey, certificateChain, alternativeNames, certificateId } = certData;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
validateCertificateContent(cert, privateKey);
|
validateCertificateContent(cert, privateKey);
|
||||||
@@ -429,7 +437,16 @@ export const awsCertificateManagerPkiSyncFactory = ({
|
|||||||
name: certName,
|
name: certName,
|
||||||
error: `Certificate validation failed: ${errorMessage}`
|
error: `Certificate validation failed: ${errorMessage}`
|
||||||
});
|
});
|
||||||
return;
|
// eslint-disable-next-line no-continue
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (preserveArn && certificateId) {
|
||||||
|
const certificate = await certificateDAL.findById(certificateId);
|
||||||
|
if (certificate?.renewedByCertificateId) {
|
||||||
|
// eslint-disable-next-line no-continue
|
||||||
|
continue;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const certificateName = generateCertificateName(certName, pkiSync);
|
const certificateName = generateCertificateName(certName, pkiSync);
|
||||||
@@ -451,44 +468,47 @@ export const awsCertificateManagerPkiSyncFactory = ({
|
|||||||
cert,
|
cert,
|
||||||
privateKey,
|
privateKey,
|
||||||
certificateChain,
|
certificateChain,
|
||||||
existingArn
|
existingArn,
|
||||||
|
certificateId
|
||||||
});
|
});
|
||||||
});
|
}
|
||||||
|
|
||||||
const certificatesToRemove = Object.values(acmCertificates)
|
const certificatesToRemove = canRemoveCertificates
|
||||||
.filter((acmCert) => {
|
? Object.values(acmCertificates)
|
||||||
if (!acmCert.arn || !acmCert.Tags) {
|
.filter((acmCert) => {
|
||||||
return false;
|
if (!acmCert.arn || !acmCert.Tags) {
|
||||||
}
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
const certNameTag = findInfisicalCertificateTag(acmCert.Tags);
|
const certNameTag = findInfisicalCertificateTag(acmCert.Tags);
|
||||||
if (!certNameTag || !certNameTag.Value) {
|
if (!certNameTag || !certNameTag.Value) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
const isActive = activeCertificateNames.some((activeCertName) => {
|
const isActive = activeCertificateNames.some((activeCertName) => {
|
||||||
const certData = certificateMap[activeCertName];
|
const certData = certificateMap[activeCertName];
|
||||||
if (!certData) return false;
|
if (!certData) return false;
|
||||||
|
|
||||||
return validateCertificateIdentification(activeCertName, acmCert, certData.alternativeNames);
|
return validateCertificateIdentification(activeCertName, acmCert, certData.alternativeNames);
|
||||||
});
|
});
|
||||||
|
|
||||||
if (!isActive) {
|
if (!isActive) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!preserveArn && isActive) {
|
if (!preserveArn && isActive) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
return false;
|
return false;
|
||||||
})
|
})
|
||||||
.map((acmCert) => acmCert.arn!)
|
.map((acmCert) => acmCert.arn!)
|
||||||
.filter((arn) => arn);
|
.filter((arn) => arn)
|
||||||
|
: [];
|
||||||
|
|
||||||
const uploadResults = await executeWithConcurrencyLimit(
|
const uploadResults = await executeWithConcurrencyLimit(
|
||||||
setCertificates,
|
setCertificates,
|
||||||
async ({ key, name, cert, privateKey, certificateChain, existingArn }) => {
|
async ({ key, name, cert, privateKey, certificateChain, existingArn, certificateId }) => {
|
||||||
try {
|
try {
|
||||||
const importParams: AWS.ACM.ImportCertificateRequest = {
|
const importParams: AWS.ACM.ImportCertificateRequest = {
|
||||||
Certificate: cert,
|
Certificate: cert,
|
||||||
@@ -549,6 +569,13 @@ export const awsCertificateManagerPkiSyncFactory = ({
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (existingArn && preserveArn && certificateId) {
|
||||||
|
const currentCertificate = await certificateDAL.findById(certificateId);
|
||||||
|
if (currentCertificate?.renewedFromCertificateId) {
|
||||||
|
await certificateSyncDAL.removeCertificates(pkiSync.id, [currentCertificate.renewedFromCertificateId]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return { key, name, success: true, response };
|
return { key, name, success: true, response };
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
const errorMessage = error instanceof Error ? error.message : "Unknown error";
|
const errorMessage = error instanceof Error ? error.message : "Unknown error";
|
||||||
@@ -644,7 +671,8 @@ export const awsCertificateManagerPkiSyncFactory = ({
|
|||||||
|
|
||||||
const removeCertificates = async (
|
const removeCertificates = async (
|
||||||
pkiSync: TPkiSyncWithCredentials,
|
pkiSync: TPkiSyncWithCredentials,
|
||||||
certificateNames: string[]
|
certificateNames: string[],
|
||||||
|
deps?: { certificateSyncDAL?: TCertificateSyncDALFactory; certificateMap?: TCertificateMap }
|
||||||
): Promise<RemoveCertificatesResult> => {
|
): Promise<RemoveCertificatesResult> => {
|
||||||
const destinationConfig = pkiSync.destinationConfig as TAwsCertificateManagerPkiSyncConfig;
|
const destinationConfig = pkiSync.destinationConfig as TAwsCertificateManagerPkiSyncConfig;
|
||||||
const acm = await getAwsAcmClient(
|
const acm = await getAwsAcmClient(
|
||||||
@@ -686,6 +714,43 @@ export const awsCertificateManagerPkiSyncFactory = ({
|
|||||||
|
|
||||||
const failedRemovals = results.filter((result) => result.status === "rejected");
|
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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if (failedRemovals.length > 0) {
|
if (failedRemovals.length > 0) {
|
||||||
const failedReasons = failedRemovals.map((failure) => {
|
const failedReasons = failedRemovals.map((failure) => {
|
||||||
if (failure.status === "rejected") {
|
if (failure.status === "rejected") {
|
||||||
|
|||||||
@@ -56,4 +56,5 @@ export interface CertificateImportRequest {
|
|||||||
privateKey: string;
|
privateKey: string;
|
||||||
certificateChain?: string;
|
certificateChain?: string;
|
||||||
existingArn?: string;
|
existingArn?: string;
|
||||||
|
certificateId?: string;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -6,6 +6,8 @@ import { request } from "@app/lib/config/request";
|
|||||||
import { logger } from "@app/lib/logger";
|
import { logger } from "@app/lib/logger";
|
||||||
import { TAppConnectionDALFactory } from "@app/services/app-connection/app-connection-dal";
|
import { TAppConnectionDALFactory } from "@app/services/app-connection/app-connection-dal";
|
||||||
import { getAzureConnectionAccessToken } from "@app/services/app-connection/azure-key-vault";
|
import { getAzureConnectionAccessToken } from "@app/services/app-connection/azure-key-vault";
|
||||||
|
import { TCertificateSyncDALFactory } from "@app/services/certificate-sync/certificate-sync-dal";
|
||||||
|
import { CertificateSyncStatus } from "@app/services/certificate-sync/certificate-sync-enums";
|
||||||
import { createConnectionQueue, RateLimitConfig } from "@app/services/connection-queue";
|
import { createConnectionQueue, RateLimitConfig } from "@app/services/connection-queue";
|
||||||
import { TKmsServiceFactory } from "@app/services/kms/kms-service";
|
import { TKmsServiceFactory } from "@app/services/kms/kms-service";
|
||||||
import { matchesCertificateNameSchema } from "@app/services/pki-sync/pki-sync-fns";
|
import { matchesCertificateNameSchema } from "@app/services/pki-sync/pki-sync-fns";
|
||||||
@@ -32,7 +34,9 @@ const extractCertificateNameFromId = (certificateId: string): string => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
const isInfisicalManagedCertificate = (certificateName: string, pkiSync: TPkiSyncWithCredentials): boolean => {
|
const isInfisicalManagedCertificate = (certificateName: string, pkiSync: TPkiSyncWithCredentials): boolean => {
|
||||||
const syncOptions = pkiSync.syncOptions as { certificateNameSchema?: string } | undefined;
|
const syncOptions = pkiSync.syncOptions as
|
||||||
|
| { certificateNameSchema?: string; canRemoveCertificates?: boolean }
|
||||||
|
| undefined;
|
||||||
const certificateNameSchema = syncOptions?.certificateNameSchema;
|
const certificateNameSchema = syncOptions?.certificateNameSchema;
|
||||||
|
|
||||||
if (certificateNameSchema) {
|
if (certificateNameSchema) {
|
||||||
@@ -330,13 +334,19 @@ export const azureKeyVaultPkiSyncFactory = ({ kmsService, appConnectionDAL }: TA
|
|||||||
cert: string;
|
cert: string;
|
||||||
privateKey: string;
|
privateKey: string;
|
||||||
certificateChain?: string;
|
certificateChain?: string;
|
||||||
|
certificateId?: string;
|
||||||
}[] = [];
|
}[] = [];
|
||||||
|
|
||||||
|
const syncOptions = pkiSync.syncOptions as
|
||||||
|
| { certificateNameSchema?: string; canRemoveCertificates?: boolean }
|
||||||
|
| undefined;
|
||||||
|
const canRemoveCertificates = syncOptions?.canRemoveCertificates ?? true;
|
||||||
|
|
||||||
// Track which certificates should exist in Azure Key Vault
|
// Track which certificates should exist in Azure Key Vault
|
||||||
const activeCertificateNames = Object.keys(certificateMap);
|
const activeCertificateNames = Object.keys(certificateMap);
|
||||||
|
|
||||||
// Iterate through certificates to sync to Azure Key Vault
|
// Iterate through certificates to sync to Azure Key Vault
|
||||||
Object.entries(certificateMap).forEach(([certName, { cert, privateKey, certificateChain }]) => {
|
Object.entries(certificateMap).forEach(([certName, { cert, privateKey, certificateChain, certificateId }]) => {
|
||||||
if (disabledAzureKeyVaultCertificateKeys.includes(certName)) {
|
if (disabledAzureKeyVaultCertificateKeys.includes(certName)) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -349,19 +359,22 @@ export const azureKeyVaultPkiSyncFactory = ({ kmsService, appConnectionDAL }: TA
|
|||||||
key: certName,
|
key: certName,
|
||||||
cert,
|
cert,
|
||||||
privateKey,
|
privateKey,
|
||||||
certificateChain
|
certificateChain,
|
||||||
|
certificateId
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
// Identify expired/removed certificates that need to be cleaned up from Azure Key Vault
|
// Identify expired/removed certificates that need to be cleaned up from Azure Key Vault
|
||||||
// Only remove certificates that were managed by Infisical (match naming schema)
|
// Only remove certificates that were managed by Infisical (match naming schema)
|
||||||
const certificatesToRemove = Object.keys(vaultCertificates).filter(
|
const certificatesToRemove = canRemoveCertificates
|
||||||
(vaultCertName) =>
|
? Object.keys(vaultCertificates).filter(
|
||||||
isInfisicalManagedCertificate(vaultCertName, pkiSync) &&
|
(vaultCertName) =>
|
||||||
!activeCertificateNames.includes(vaultCertName) &&
|
isInfisicalManagedCertificate(vaultCertName, pkiSync) &&
|
||||||
!disabledAzureKeyVaultCertificateKeys.includes(vaultCertName)
|
!activeCertificateNames.includes(vaultCertName) &&
|
||||||
);
|
!disabledAzureKeyVaultCertificateKeys.includes(vaultCertName)
|
||||||
|
)
|
||||||
|
: [];
|
||||||
|
|
||||||
// Upload certificates to Azure Key Vault with rate limiting
|
// Upload certificates to Azure Key Vault with rate limiting
|
||||||
const uploadResults = await executeWithConcurrencyLimit(
|
const uploadResults = await executeWithConcurrencyLimit(
|
||||||
@@ -599,7 +612,11 @@ export const azureKeyVaultPkiSyncFactory = ({ kmsService, appConnectionDAL }: TA
|
|||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
const removeCertificates = async (pkiSync: TPkiSyncWithCredentials, certificateNames: string[]) => {
|
const removeCertificates = async (
|
||||||
|
pkiSync: TPkiSyncWithCredentials,
|
||||||
|
certificateNames: string[],
|
||||||
|
deps?: { certificateSyncDAL?: TCertificateSyncDALFactory; certificateMap?: TCertificateMap }
|
||||||
|
) => {
|
||||||
const { accessToken } = await getAzureConnectionAccessToken(pkiSync.connection.id, appConnectionDAL, kmsService);
|
const { accessToken } = await getAzureConnectionAccessToken(pkiSync.connection.id, appConnectionDAL, kmsService);
|
||||||
|
|
||||||
// Cast destination config to Azure Key Vault config
|
// Cast destination config to Azure Key Vault config
|
||||||
@@ -648,6 +665,27 @@ export const azureKeyVaultPkiSyncFactory = ({ kmsService, appConnectionDAL }: TA
|
|||||||
);
|
);
|
||||||
const failedRemovals = results.filter((result) => result.status === "rejected");
|
const failedRemovals = results.filter((result) => result.status === "rejected");
|
||||||
|
|
||||||
|
if (failedRemovals.length > 0 && deps?.certificateSyncDAL && deps?.certificateMap) {
|
||||||
|
for (const failure of failedRemovals) {
|
||||||
|
if (failure.status === "rejected") {
|
||||||
|
const failedIndex = results.indexOf(failure);
|
||||||
|
const failedCertName = infisicalManagedCertNames[failedIndex];
|
||||||
|
if (failedCertName && deps.certificateMap[failedCertName]?.certificateId) {
|
||||||
|
const { certificateId } = deps.certificateMap[failedCertName];
|
||||||
|
if (certificateId) {
|
||||||
|
const errorMessage = (failure.reason as Error)?.message || "Unknown error";
|
||||||
|
await deps.certificateSyncDAL.updateSyncStatus(
|
||||||
|
pkiSync.id,
|
||||||
|
certificateId,
|
||||||
|
CertificateSyncStatus.Failed,
|
||||||
|
`Failed to remove from Azure: ${errorMessage}`
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if (failedRemovals.length > 0) {
|
if (failedRemovals.length > 0) {
|
||||||
const failedReasons = failedRemovals.map((failure) => {
|
const failedReasons = failedRemovals.map((failure) => {
|
||||||
if (failure.status === "rejected") {
|
if (failure.status === "rejected") {
|
||||||
|
|||||||
@@ -50,7 +50,7 @@ export const CreateAzureKeyVaultPkiSyncSchema = z.object({
|
|||||||
isAutoSyncEnabled: z.boolean().default(true),
|
isAutoSyncEnabled: z.boolean().default(true),
|
||||||
destinationConfig: AzureKeyVaultPkiSyncConfigSchema,
|
destinationConfig: AzureKeyVaultPkiSyncConfigSchema,
|
||||||
syncOptions: AzureKeyVaultPkiSyncOptionsSchema.optional().default({}),
|
syncOptions: AzureKeyVaultPkiSyncOptionsSchema.optional().default({}),
|
||||||
subscriberId: z.string().optional(),
|
subscriberId: z.string().nullish(),
|
||||||
connectionId: z.string(),
|
connectionId: z.string(),
|
||||||
projectId: z.string().trim().min(1),
|
projectId: z.string().trim().min(1),
|
||||||
certificateIds: z.array(z.string().uuid()).optional()
|
certificateIds: z.array(z.string().uuid()).optional()
|
||||||
@@ -62,7 +62,7 @@ export const UpdateAzureKeyVaultPkiSyncSchema = z.object({
|
|||||||
isAutoSyncEnabled: z.boolean().optional(),
|
isAutoSyncEnabled: z.boolean().optional(),
|
||||||
destinationConfig: AzureKeyVaultPkiSyncConfigSchema.optional(),
|
destinationConfig: AzureKeyVaultPkiSyncConfigSchema.optional(),
|
||||||
syncOptions: AzureKeyVaultPkiSyncOptionsSchema.optional(),
|
syncOptions: AzureKeyVaultPkiSyncOptionsSchema.optional(),
|
||||||
subscriberId: z.string().optional(),
|
subscriberId: z.string().nullish(),
|
||||||
connectionId: z.string().optional()
|
connectionId: z.string().optional()
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@@ -4,6 +4,8 @@ import { z, ZodSchema } from "zod";
|
|||||||
import { TLicenseServiceFactory } from "@app/ee/services/license/license-service";
|
import { TLicenseServiceFactory } from "@app/ee/services/license/license-service";
|
||||||
import { BadRequestError } from "@app/lib/errors";
|
import { BadRequestError } from "@app/lib/errors";
|
||||||
import { TAppConnectionDALFactory } from "@app/services/app-connection/app-connection-dal";
|
import { TAppConnectionDALFactory } from "@app/services/app-connection/app-connection-dal";
|
||||||
|
import { TCertificateDALFactory } from "@app/services/certificate/certificate-dal";
|
||||||
|
import { TCertificateSyncDALFactory } from "@app/services/certificate-sync/certificate-sync-dal";
|
||||||
import { TKmsServiceFactory } from "@app/services/kms/kms-service";
|
import { TKmsServiceFactory } from "@app/services/kms/kms-service";
|
||||||
|
|
||||||
import { AWS_CERTIFICATE_MANAGER_PKI_SYNC_LIST_OPTION } from "./aws-certificate-manager/aws-certificate-manager-pki-sync-constants";
|
import { AWS_CERTIFICATE_MANAGER_PKI_SYNC_LIST_OPTION } from "./aws-certificate-manager/aws-certificate-manager-pki-sync-constants";
|
||||||
@@ -184,6 +186,8 @@ export const PkiSyncFns = {
|
|||||||
dependencies: {
|
dependencies: {
|
||||||
appConnectionDAL: Pick<TAppConnectionDALFactory, "findById" | "updateById">;
|
appConnectionDAL: Pick<TAppConnectionDALFactory, "findById" | "updateById">;
|
||||||
kmsService: Pick<TKmsServiceFactory, "createCipherPairWithDataKey">;
|
kmsService: Pick<TKmsServiceFactory, "createCipherPairWithDataKey">;
|
||||||
|
certificateDAL: TCertificateDALFactory;
|
||||||
|
certificateSyncDAL: TCertificateSyncDALFactory;
|
||||||
}
|
}
|
||||||
): Promise<{
|
): Promise<{
|
||||||
uploaded: number;
|
uploaded: number;
|
||||||
@@ -205,7 +209,12 @@ export const PkiSyncFns = {
|
|||||||
}
|
}
|
||||||
case PkiSync.AwsCertificateManager: {
|
case PkiSync.AwsCertificateManager: {
|
||||||
checkPkiSyncDestination(pkiSync, PkiSync.AwsCertificateManager);
|
checkPkiSyncDestination(pkiSync, PkiSync.AwsCertificateManager);
|
||||||
const awsCertificateManagerPkiSync = awsCertificateManagerPkiSyncFactory(dependencies);
|
const awsCertificateManagerPkiSync = awsCertificateManagerPkiSyncFactory({
|
||||||
|
appConnectionDAL: dependencies.appConnectionDAL,
|
||||||
|
kmsService: dependencies.kmsService,
|
||||||
|
certificateDAL: dependencies.certificateDAL,
|
||||||
|
certificateSyncDAL: dependencies.certificateSyncDAL
|
||||||
|
});
|
||||||
return awsCertificateManagerPkiSync.syncCertificates(pkiSync, certificateMap);
|
return awsCertificateManagerPkiSync.syncCertificates(pkiSync, certificateMap);
|
||||||
}
|
}
|
||||||
default:
|
default:
|
||||||
@@ -219,19 +228,33 @@ export const PkiSyncFns = {
|
|||||||
dependencies: {
|
dependencies: {
|
||||||
appConnectionDAL: Pick<TAppConnectionDALFactory, "findById" | "updateById">;
|
appConnectionDAL: Pick<TAppConnectionDALFactory, "findById" | "updateById">;
|
||||||
kmsService: Pick<TKmsServiceFactory, "createCipherPairWithDataKey">;
|
kmsService: Pick<TKmsServiceFactory, "createCipherPairWithDataKey">;
|
||||||
|
certificateSyncDAL: TCertificateSyncDALFactory;
|
||||||
|
certificateDAL: TCertificateDALFactory;
|
||||||
|
certificateMap: TCertificateMap;
|
||||||
}
|
}
|
||||||
): Promise<void> => {
|
): Promise<void> => {
|
||||||
switch (pkiSync.destination) {
|
switch (pkiSync.destination) {
|
||||||
case PkiSync.AzureKeyVault: {
|
case PkiSync.AzureKeyVault: {
|
||||||
checkPkiSyncDestination(pkiSync, PkiSync.AzureKeyVault);
|
checkPkiSyncDestination(pkiSync, PkiSync.AzureKeyVault);
|
||||||
const azureKeyVaultPkiSync = azureKeyVaultPkiSyncFactory(dependencies);
|
const azureKeyVaultPkiSync = azureKeyVaultPkiSyncFactory(dependencies);
|
||||||
await azureKeyVaultPkiSync.removeCertificates(pkiSync, certificateNames);
|
await azureKeyVaultPkiSync.removeCertificates(pkiSync, certificateNames, {
|
||||||
|
certificateSyncDAL: dependencies.certificateSyncDAL,
|
||||||
|
certificateMap: dependencies.certificateMap
|
||||||
|
});
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
case PkiSync.AwsCertificateManager: {
|
case PkiSync.AwsCertificateManager: {
|
||||||
checkPkiSyncDestination(pkiSync, PkiSync.AwsCertificateManager);
|
checkPkiSyncDestination(pkiSync, PkiSync.AwsCertificateManager);
|
||||||
const awsCertificateManagerPkiSync = awsCertificateManagerPkiSyncFactory(dependencies);
|
const awsCertificateManagerPkiSync = awsCertificateManagerPkiSyncFactory({
|
||||||
await awsCertificateManagerPkiSync.removeCertificates(pkiSync, certificateNames);
|
appConnectionDAL: dependencies.appConnectionDAL,
|
||||||
|
kmsService: dependencies.kmsService,
|
||||||
|
certificateDAL: dependencies.certificateDAL,
|
||||||
|
certificateSyncDAL: dependencies.certificateSyncDAL
|
||||||
|
});
|
||||||
|
await awsCertificateManagerPkiSync.removeCertificates(pkiSync, certificateNames, {
|
||||||
|
certificateSyncDAL: dependencies.certificateSyncDAL,
|
||||||
|
certificateMap: dependencies.certificateMap
|
||||||
|
});
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
default:
|
default:
|
||||||
|
|||||||
@@ -27,6 +27,7 @@ import { TCertificateAuthorityCertDALFactory } from "../certificate-authority/ce
|
|||||||
import { TCertificateAuthorityDALFactory } from "../certificate-authority/certificate-authority-dal";
|
import { TCertificateAuthorityDALFactory } from "../certificate-authority/certificate-authority-dal";
|
||||||
import { getCaCertChain } from "../certificate-authority/certificate-authority-fns";
|
import { getCaCertChain } from "../certificate-authority/certificate-authority-fns";
|
||||||
import { TCertificateSyncDALFactory } from "../certificate-sync/certificate-sync-dal";
|
import { TCertificateSyncDALFactory } from "../certificate-sync/certificate-sync-dal";
|
||||||
|
import { CertificateSyncStatus } from "../certificate-sync/certificate-sync-enums";
|
||||||
import { TPkiSyncDALFactory } from "./pki-sync-dal";
|
import { TPkiSyncDALFactory } from "./pki-sync-dal";
|
||||||
import { PkiSyncStatus } from "./pki-sync-enums";
|
import { PkiSyncStatus } from "./pki-sync-enums";
|
||||||
import { PkiSyncError } from "./pki-sync-errors";
|
import { PkiSyncError } from "./pki-sync-errors";
|
||||||
@@ -57,23 +58,12 @@ type TPkiSyncQueueFactoryDep = {
|
|||||||
auditLogService: Pick<TAuditLogServiceFactory, "createAuditLog">;
|
auditLogService: Pick<TAuditLogServiceFactory, "createAuditLog">;
|
||||||
projectDAL: TProjectDALFactory;
|
projectDAL: TProjectDALFactory;
|
||||||
licenseService: Pick<TLicenseServiceFactory, "getPlan">;
|
licenseService: Pick<TLicenseServiceFactory, "getPlan">;
|
||||||
certificateDAL: Pick<
|
certificateDAL: TCertificateDALFactory;
|
||||||
TCertificateDALFactory,
|
|
||||||
| "findLatestActiveCertForSubscriber"
|
|
||||||
| "findAllActiveCertsForSubscriber"
|
|
||||||
| "findActiveCertificatesByIds"
|
|
||||||
| "create"
|
|
||||||
| "findById"
|
|
||||||
| "find"
|
|
||||||
>;
|
|
||||||
certificateBodyDAL: Pick<TCertificateBodyDALFactory, "findOne" | "create">;
|
certificateBodyDAL: Pick<TCertificateBodyDALFactory, "findOne" | "create">;
|
||||||
certificateSecretDAL: Pick<TCertificateSecretDALFactory, "findOne" | "create">;
|
certificateSecretDAL: Pick<TCertificateSecretDALFactory, "findOne" | "create">;
|
||||||
certificateAuthorityDAL: Pick<TCertificateAuthorityDALFactory, "findById">;
|
certificateAuthorityDAL: Pick<TCertificateAuthorityDALFactory, "findById">;
|
||||||
certificateAuthorityCertDAL: Pick<TCertificateAuthorityCertDALFactory, "findById">;
|
certificateAuthorityCertDAL: Pick<TCertificateAuthorityCertDALFactory, "findById">;
|
||||||
certificateSyncDAL: Pick<
|
certificateSyncDAL: TCertificateSyncDALFactory;
|
||||||
TCertificateSyncDALFactory,
|
|
||||||
"findCertificateIdsByPkiSyncId" | "updateSyncStatus" | "bulkUpdateSyncStatus"
|
|
||||||
>;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
type PkiSyncActionJob = Job<
|
type PkiSyncActionJob = Job<
|
||||||
@@ -303,7 +293,8 @@ export const pkiSyncQueueFactory = ({
|
|||||||
cert: certificatePem,
|
cert: certificatePem,
|
||||||
privateKey: certPrivateKey || "",
|
privateKey: certPrivateKey || "",
|
||||||
certificateChain,
|
certificateChain,
|
||||||
alternativeNames
|
alternativeNames,
|
||||||
|
certificateId: certificate.id
|
||||||
};
|
};
|
||||||
|
|
||||||
certificateMetadata.set(certificateName, {
|
certificateMetadata.set(certificateName, {
|
||||||
@@ -427,7 +418,7 @@ export const pkiSyncQueueFactory = ({
|
|||||||
const statusUpdates = Array.from(certificateMetadata.entries()).map(([, metadata]) => ({
|
const statusUpdates = Array.from(certificateMetadata.entries()).map(([, metadata]) => ({
|
||||||
pkiSyncId: pkiSync.id,
|
pkiSyncId: pkiSync.id,
|
||||||
certificateId: metadata.id,
|
certificateId: metadata.id,
|
||||||
status: "running",
|
status: CertificateSyncStatus.Running,
|
||||||
message: "Syncing certificate to destination"
|
message: "Syncing certificate to destination"
|
||||||
}));
|
}));
|
||||||
|
|
||||||
@@ -437,7 +428,9 @@ export const pkiSyncQueueFactory = ({
|
|||||||
|
|
||||||
const syncResult = await PkiSyncFns.syncCertificates(pkiSyncWithCredentials, certificateMap, {
|
const syncResult = await PkiSyncFns.syncCertificates(pkiSyncWithCredentials, certificateMap, {
|
||||||
appConnectionDAL,
|
appConnectionDAL,
|
||||||
kmsService
|
kmsService,
|
||||||
|
certificateDAL,
|
||||||
|
certificateSyncDAL
|
||||||
});
|
});
|
||||||
|
|
||||||
logger.info(
|
logger.info(
|
||||||
@@ -462,7 +455,7 @@ export const pkiSyncQueueFactory = ({
|
|||||||
postSyncUpdates.push({
|
postSyncUpdates.push({
|
||||||
pkiSyncId: pkiSync.id,
|
pkiSyncId: pkiSync.id,
|
||||||
certificateId: metadata.id,
|
certificateId: metadata.id,
|
||||||
status: "succeeded",
|
status: CertificateSyncStatus.Succeeded,
|
||||||
message: "Certificate successfully synced to destination"
|
message: "Certificate successfully synced to destination"
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@@ -476,7 +469,7 @@ export const pkiSyncQueueFactory = ({
|
|||||||
postSyncUpdates[updateIndex] = {
|
postSyncUpdates[updateIndex] = {
|
||||||
pkiSyncId: pkiSync.id,
|
pkiSyncId: pkiSync.id,
|
||||||
certificateId: metadata.id,
|
certificateId: metadata.id,
|
||||||
status: "failed",
|
status: CertificateSyncStatus.Failed,
|
||||||
message: `${validationError.error}`
|
message: `${validationError.error}`
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
@@ -493,7 +486,7 @@ export const pkiSyncQueueFactory = ({
|
|||||||
postSyncUpdates[updateIndex] = {
|
postSyncUpdates[updateIndex] = {
|
||||||
pkiSyncId: pkiSync.id,
|
pkiSyncId: pkiSync.id,
|
||||||
certificateId: metadata.id,
|
certificateId: metadata.id,
|
||||||
status: "failed",
|
status: CertificateSyncStatus.Failed,
|
||||||
message: `Failed to sync certificate: ${failure.error}`
|
message: `Failed to sync certificate: ${failure.error}`
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
@@ -699,7 +692,10 @@ export const pkiSyncQueueFactory = ({
|
|||||||
Object.keys(certificateMap),
|
Object.keys(certificateMap),
|
||||||
{
|
{
|
||||||
appConnectionDAL,
|
appConnectionDAL,
|
||||||
kmsService
|
kmsService,
|
||||||
|
certificateSyncDAL,
|
||||||
|
certificateDAL,
|
||||||
|
certificateMap
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|||||||
@@ -610,6 +610,10 @@ export const pkiSyncServiceFactory = ({
|
|||||||
certificateStatus: detail.certificateStatus || undefined,
|
certificateStatus: detail.certificateStatus || undefined,
|
||||||
certificateNotBefore: detail.certificateNotBefore || undefined,
|
certificateNotBefore: detail.certificateNotBefore || undefined,
|
||||||
certificateNotAfter: detail.certificateNotAfter || undefined,
|
certificateNotAfter: detail.certificateNotAfter || undefined,
|
||||||
|
certificateRenewBeforeDays: !detail.certificateRenewedByCertificateId
|
||||||
|
? detail.certificateRenewBeforeDays
|
||||||
|
: undefined,
|
||||||
|
certificateRenewalError: detail.certificateRenewalError || undefined,
|
||||||
pkiSyncName: detail.pkiSyncName || undefined,
|
pkiSyncName: detail.pkiSyncName || undefined,
|
||||||
pkiSyncDestination: detail.pkiSyncDestination || undefined
|
pkiSyncDestination: detail.pkiSyncDestination || undefined
|
||||||
}));
|
}));
|
||||||
|
|||||||
@@ -73,7 +73,7 @@ export type TPkiSyncListItem = TPkiSync & {
|
|||||||
|
|
||||||
export type TCertificateMap = Record<
|
export type TCertificateMap = Record<
|
||||||
string,
|
string,
|
||||||
{ cert: string; privateKey: string; certificateChain?: string; alternativeNames?: string[] }
|
{ cert: string; privateKey: string; certificateChain?: string; alternativeNames?: string[]; certificateId?: string }
|
||||||
>;
|
>;
|
||||||
|
|
||||||
export type TCreatePkiSyncDTO = {
|
export type TCreatePkiSyncDTO = {
|
||||||
@@ -176,6 +176,8 @@ export type TPkiSyncCertificate = {
|
|||||||
certificateStatus?: string;
|
certificateStatus?: string;
|
||||||
certificateNotBefore?: Date;
|
certificateNotBefore?: Date;
|
||||||
certificateNotAfter?: Date;
|
certificateNotAfter?: Date;
|
||||||
|
certificateRenewBeforeDays?: number;
|
||||||
|
certificateRenewalError?: string;
|
||||||
pkiSyncName?: string;
|
pkiSyncName?: string;
|
||||||
pkiSyncDestination?: string;
|
pkiSyncDestination?: string;
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -59,7 +59,7 @@ export const triggerAutoSyncForCertificate = async (
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
export const replaceCertificateInSyncs = async (
|
export const addRenewedCertificateToSyncs = async (
|
||||||
oldCertificateId: string,
|
oldCertificateId: string,
|
||||||
newCertificateId: string,
|
newCertificateId: string,
|
||||||
dependencies: {
|
dependencies: {
|
||||||
@@ -77,18 +77,15 @@ export const replaceCertificateInSyncs = async (
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const replacementPromises = pkiSyncIds.map(async (pkiSyncId) => {
|
const addPromises = pkiSyncIds.map(async (pkiSyncId) => {
|
||||||
await dependencies.certificateSyncDAL.removeCertificates(pkiSyncId, [oldCertificateId], tx);
|
|
||||||
await dependencies.certificateSyncDAL.addCertificates(pkiSyncId, [newCertificateId], tx);
|
await dependencies.certificateSyncDAL.addCertificates(pkiSyncId, [newCertificateId], tx);
|
||||||
});
|
});
|
||||||
|
|
||||||
await Promise.all(replacementPromises);
|
await Promise.all(addPromises);
|
||||||
|
|
||||||
logger.info(
|
logger.info(`Successfully added renewed certificate ${newCertificateId} to ${pkiSyncIds.length} PKI sync(s)`);
|
||||||
`Successfully replaced certificate ${oldCertificateId} with ${newCertificateId} in ${pkiSyncIds.length} PKI sync(s)`
|
|
||||||
);
|
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
logger.error(error, `Failed to replace certificate ${oldCertificateId} with ${newCertificateId} in syncs:`);
|
logger.error(error, `Failed to add renewed certificate ${newCertificateId} to syncs:`);
|
||||||
throw error;
|
throw error;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -71,14 +71,14 @@ export const PkiSyncOptionsFields = ({ destination }: Props) => {
|
|||||||
isChecked={value}
|
isChecked={value}
|
||||||
>
|
>
|
||||||
<p>
|
<p>
|
||||||
Enable Certificate Removal{" "}
|
Enable Inactive Certificate Removal{" "}
|
||||||
<Tooltip
|
<Tooltip
|
||||||
className="max-w-md"
|
className="max-w-md"
|
||||||
content={
|
content={
|
||||||
<>
|
<>
|
||||||
<p>
|
<p>
|
||||||
When enabled, Infisical will remove certificates from the destination during
|
When enabled, Infisical will remove certificates from the destination during
|
||||||
a sync if they are no longer managed by Infisical.
|
a sync if they are no longer active in Infisical.
|
||||||
</p>
|
</p>
|
||||||
<p className="mt-4">
|
<p className="mt-4">
|
||||||
Disable this option if you intend to manage some certificates manually
|
Disable this option if you intend to manage some certificates manually
|
||||||
|
|||||||
@@ -61,6 +61,8 @@ export type TPkiSyncCertificate = {
|
|||||||
certificateStatus?: string;
|
certificateStatus?: string;
|
||||||
certificateNotBefore?: Date;
|
certificateNotBefore?: Date;
|
||||||
certificateNotAfter?: Date;
|
certificateNotAfter?: Date;
|
||||||
|
certificateRenewBeforeDays?: number;
|
||||||
|
certificateRenewalError?: string;
|
||||||
pkiSyncName?: string;
|
pkiSyncName?: string;
|
||||||
pkiSyncDestination?: string;
|
pkiSyncDestination?: string;
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -1,6 +1,11 @@
|
|||||||
import { useState } from "react";
|
import { useState } from "react";
|
||||||
import { subject } from "@casl/ability";
|
import { subject } from "@casl/ability";
|
||||||
import { faCertificate, faEdit, faTrash } from "@fortawesome/free-solid-svg-icons";
|
import {
|
||||||
|
faCertificate,
|
||||||
|
faClockRotateLeft,
|
||||||
|
faEdit,
|
||||||
|
faTrash
|
||||||
|
} from "@fortawesome/free-solid-svg-icons";
|
||||||
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
|
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
|
||||||
|
|
||||||
import { createNotification } from "@app/components/notifications";
|
import { createNotification } from "@app/components/notifications";
|
||||||
@@ -49,6 +54,18 @@ const getSyncStatusText = (status?: CertificateSyncStatus | null) => {
|
|||||||
return "Unknown";
|
return "Unknown";
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const getCertificateStatusVariant = (isExpired: boolean, isRevoked: boolean) => {
|
||||||
|
if (isRevoked) return "danger";
|
||||||
|
if (isExpired) return "danger";
|
||||||
|
return "success";
|
||||||
|
};
|
||||||
|
|
||||||
|
const getCertificateStatusText = (isExpired: boolean, isRevoked: boolean) => {
|
||||||
|
if (isRevoked) return "Revoked";
|
||||||
|
if (isExpired) return "Expired";
|
||||||
|
return "Active";
|
||||||
|
};
|
||||||
|
|
||||||
export const PkiSyncCertificatesSection = ({ pkiSync }: Props) => {
|
export const PkiSyncCertificatesSection = ({ pkiSync }: Props) => {
|
||||||
const [isManageModalOpen, setIsManageModalOpen] = useState(false);
|
const [isManageModalOpen, setIsManageModalOpen] = useState(false);
|
||||||
const [isDeleteModalOpen, setIsDeleteModalOpen] = useState(false);
|
const [isDeleteModalOpen, setIsDeleteModalOpen] = useState(false);
|
||||||
@@ -128,11 +145,12 @@ export const PkiSyncCertificatesSection = ({ pkiSync }: Props) => {
|
|||||||
<Table>
|
<Table>
|
||||||
<THead>
|
<THead>
|
||||||
<Tr>
|
<Tr>
|
||||||
<Th className="w-1/3">SAN / CN</Th>
|
<Th className="w-3/8">SAN / CN</Th>
|
||||||
<Th className="w-1/3">Serial Number</Th>
|
<Th className="w-1/8">Status</Th>
|
||||||
<Th className="w-1/9">Status</Th>
|
<Th className="w-1/8">Serial Number</Th>
|
||||||
<Th className="w-1/9">Expires</Th>
|
<Th className="w-1/8">Sync Status</Th>
|
||||||
<Th className="w-1/9">Actions</Th>
|
<Th className="w-1/8">Expires At</Th>
|
||||||
|
<Th className="w-1/8" />
|
||||||
</Tr>
|
</Tr>
|
||||||
</THead>
|
</THead>
|
||||||
<TBody>
|
<TBody>
|
||||||
@@ -140,6 +158,29 @@ export const PkiSyncCertificatesSection = ({ pkiSync }: Props) => {
|
|||||||
const isExpired = syncCert.certificateNotAfter
|
const isExpired = syncCert.certificateNotAfter
|
||||||
? new Date(syncCert.certificateNotAfter) < new Date()
|
? new Date(syncCert.certificateNotAfter) < new Date()
|
||||||
: false;
|
: false;
|
||||||
|
const isRevoked = syncCert.certificateStatus === "revoked";
|
||||||
|
|
||||||
|
// Calculate auto-renewal timeline
|
||||||
|
const hasAutoRenewal = Boolean(
|
||||||
|
syncCert.certificateRenewBeforeDays &&
|
||||||
|
syncCert.certificateRenewBeforeDays > 0 &&
|
||||||
|
!syncCert.certificateRenewalError &&
|
||||||
|
syncCert.certificateNotAfter
|
||||||
|
);
|
||||||
|
|
||||||
|
const daysUntilRenewal =
|
||||||
|
hasAutoRenewal && syncCert.certificateNotAfter
|
||||||
|
? (() => {
|
||||||
|
const expiryDate = new Date(syncCert.certificateNotAfter);
|
||||||
|
const renewalDate = new Date(
|
||||||
|
expiryDate.getTime() -
|
||||||
|
syncCert.certificateRenewBeforeDays! * 24 * 60 * 60 * 1000
|
||||||
|
);
|
||||||
|
const now = new Date();
|
||||||
|
const diffInMs = renewalDate.getTime() - now.getTime();
|
||||||
|
return Math.max(0, Math.ceil(diffInMs / (24 * 60 * 60 * 1000)));
|
||||||
|
})()
|
||||||
|
: null;
|
||||||
|
|
||||||
const { originalDisplayName } = getCertificateDisplayName(
|
const { originalDisplayName } = getCertificateDisplayName(
|
||||||
{
|
{
|
||||||
@@ -162,12 +203,22 @@ export const PkiSyncCertificatesSection = ({ pkiSync }: Props) => {
|
|||||||
fallback="Unknown"
|
fallback="Unknown"
|
||||||
/>
|
/>
|
||||||
</Td>
|
</Td>
|
||||||
|
<Td>
|
||||||
|
<Badge variant={getCertificateStatusVariant(isExpired, isRevoked)}>
|
||||||
|
{getCertificateStatusText(isExpired, isRevoked)}
|
||||||
|
</Badge>
|
||||||
|
</Td>
|
||||||
<Td className="max-w-0">
|
<Td className="max-w-0">
|
||||||
<div
|
<div
|
||||||
className="truncate text-xs"
|
className="truncate text-xs"
|
||||||
title={syncCert.certificateSerialNumber || "Unknown"}
|
title={syncCert.certificateSerialNumber || "Unknown"}
|
||||||
>
|
>
|
||||||
{syncCert.certificateSerialNumber || "Unknown"}
|
{(() => {
|
||||||
|
const serial = syncCert.certificateSerialNumber;
|
||||||
|
if (!serial || serial === "Unknown") return "Unknown";
|
||||||
|
if (serial.length <= 8) return serial;
|
||||||
|
return `${serial.substring(0, 4)}...${serial.substring(serial.length - 4)}`;
|
||||||
|
})()}
|
||||||
</div>
|
</div>
|
||||||
</Td>
|
</Td>
|
||||||
<Td>
|
<Td>
|
||||||
@@ -191,7 +242,14 @@ export const PkiSyncCertificatesSection = ({ pkiSync }: Props) => {
|
|||||||
: "Unknown"}
|
: "Unknown"}
|
||||||
</span>
|
</span>
|
||||||
</Td>
|
</Td>
|
||||||
<Td className="flex items-center">
|
<Td className="flex items-center justify-end gap-2 pr-4">
|
||||||
|
{hasAutoRenewal && daysUntilRenewal !== null && (
|
||||||
|
<Tooltip content={`Auto-renews in ${daysUntilRenewal}d`}>
|
||||||
|
<div className="text-primary-500">
|
||||||
|
<FontAwesomeIcon icon={faClockRotateLeft} size="sm" />
|
||||||
|
</div>
|
||||||
|
</Tooltip>
|
||||||
|
)}
|
||||||
<ProjectPermissionCan
|
<ProjectPermissionCan
|
||||||
I={ProjectPermissionPkiSyncActions.Edit}
|
I={ProjectPermissionPkiSyncActions.Edit}
|
||||||
a={permissionSubject}
|
a={permissionSubject}
|
||||||
|
|||||||
@@ -65,7 +65,7 @@ export const PkiSyncOptionsSection = ({ pkiSync, onEditOptions }: Props) => {
|
|||||||
</Badge>
|
</Badge>
|
||||||
</GenericFieldLabel>
|
</GenericFieldLabel>
|
||||||
*/}
|
*/}
|
||||||
<GenericFieldLabel label="Certificate Removal" labelClassName="mb-1">
|
<GenericFieldLabel label="Inactive Certificate Removal" labelClassName="mb-1">
|
||||||
<Badge variant={canRemoveCertificates ? "success" : "danger"}>
|
<Badge variant={canRemoveCertificates ? "success" : "danger"}>
|
||||||
{canRemoveCertificates ? "Enabled" : "Disabled"}
|
{canRemoveCertificates ? "Enabled" : "Disabled"}
|
||||||
</Badge>
|
</Badge>
|
||||||
|
|||||||
Reference in New Issue
Block a user