mirror of
https://github.com/awatertrevi/infisical.git
synced 2026-10-12 04:29:09 +00:00
Address greptile comments
This commit is contained in:
@@ -5,8 +5,7 @@ import { TableName } from "../schemas";
|
||||
export async function up(knex: Knex): Promise<void> {
|
||||
if (await knex.schema.hasColumn(TableName.PkiApiEnrollmentConfig, "autoRenewDays")) {
|
||||
await knex.schema.alterTable(TableName.PkiApiEnrollmentConfig, (t) => {
|
||||
t.dropColumn("autoRenewDays");
|
||||
t.integer("renewBeforeDays");
|
||||
t.renameColumn("autoRenewDays", "renewBeforeDays");
|
||||
});
|
||||
}
|
||||
|
||||
@@ -46,8 +45,7 @@ export async function down(knex: Knex): Promise<void> {
|
||||
|
||||
if (await knex.schema.hasColumn(TableName.PkiApiEnrollmentConfig, "renewBeforeDays")) {
|
||||
await knex.schema.alterTable(TableName.PkiApiEnrollmentConfig, (t) => {
|
||||
t.dropColumn("renewBeforeDays");
|
||||
t.integer("autoRenewDays");
|
||||
t.renameColumn("renewBeforeDays", "autoRenewDays");
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2147,9 +2147,6 @@ export const registerRoutes = async (
|
||||
const certificateV3Queue = certificateV3QueueServiceFactory({
|
||||
queueService,
|
||||
certificateDAL,
|
||||
certificateAuthorityDAL,
|
||||
certificateProfileDAL,
|
||||
projectDAL,
|
||||
certificateV3Service,
|
||||
auditLogService
|
||||
});
|
||||
|
||||
@@ -406,10 +406,14 @@ export const registerCertificatesRouter = async (server: FastifyZodProvider) =>
|
||||
params: z.object({
|
||||
certificateId: z.string().uuid()
|
||||
}),
|
||||
body: z.object({
|
||||
renewBeforeDays: z.number().int().min(1).max(30).optional(),
|
||||
disableAutoRenewal: z.boolean().optional()
|
||||
}),
|
||||
body: z
|
||||
.object({
|
||||
renewBeforeDays: z.number().int().min(1).max(30).optional(),
|
||||
disableAutoRenewal: z.boolean().optional()
|
||||
})
|
||||
.refine((data) => !(data.renewBeforeDays !== undefined && data.disableAutoRenewal === true), {
|
||||
message: "Cannot specify both renewBeforeDays and disableAutoRenewal"
|
||||
}),
|
||||
response: {
|
||||
200: z.object({
|
||||
message: z.string(),
|
||||
|
||||
@@ -175,6 +175,36 @@ export enum CertSignatureAlgorithm {
|
||||
ECDSA_SHA512 = "ECDSA-SHA512"
|
||||
}
|
||||
|
||||
export enum CertificateRenewalErrorType {
|
||||
TEMPLATE_VALIDATION_FAILED = "TEMPLATE_VALIDATION_FAILED",
|
||||
CA_NOT_FOUND = "CA_NOT_FOUND",
|
||||
CA_INACTIVE = "CA_INACTIVE",
|
||||
CERTIFICATE_OUTLIVES_CA = "CERTIFICATE_OUTLIVES_CA",
|
||||
TTL_TOO_SHORT = "TTL_TOO_SHORT",
|
||||
NOT_ELIGIBLE = "NOT_ELIGIBLE",
|
||||
VALIDITY_EXCEEDS_MAXIMUM = "VALIDITY_EXCEEDS_MAXIMUM",
|
||||
NOT_ALLOWED_BY_TEMPLATE = "NOT_ALLOWED_BY_TEMPLATE",
|
||||
UNKNOWN_ERROR = "UNKNOWN_ERROR"
|
||||
}
|
||||
|
||||
export const CERTIFICATE_RENEWAL_ERROR_MESSAGES = {
|
||||
[CertificateRenewalErrorType.TEMPLATE_VALIDATION_FAILED]:
|
||||
"Auto-renewal failed: certificate template policy has changed and this certificate no longer meets the requirements",
|
||||
[CertificateRenewalErrorType.CA_NOT_FOUND]:
|
||||
"Auto-renewal failed: Certificate Authority for this certificate is no longer available",
|
||||
[CertificateRenewalErrorType.CA_INACTIVE]: "Auto-renewal failed: Certificate Authority is currently inactive",
|
||||
[CertificateRenewalErrorType.CERTIFICATE_OUTLIVES_CA]:
|
||||
"Auto-renewal failed: certificate would outlive the Certificate Authority",
|
||||
[CertificateRenewalErrorType.TTL_TOO_SHORT]:
|
||||
"Auto-renewal failed: certificate validity period is too short for the renewal threshold",
|
||||
[CertificateRenewalErrorType.NOT_ELIGIBLE]: "Auto-renewal failed: certificate is not eligible for automatic renewal",
|
||||
[CertificateRenewalErrorType.VALIDITY_EXCEEDS_MAXIMUM]:
|
||||
"Auto-renewal failed: certificate validity period exceeds the maximum allowed by the profile template",
|
||||
[CertificateRenewalErrorType.NOT_ALLOWED_BY_TEMPLATE]:
|
||||
"Auto-renewal failed: certificate settings are no longer allowed by the profile template",
|
||||
[CertificateRenewalErrorType.UNKNOWN_ERROR]: "Auto-renewal failed: an unexpected error occurred"
|
||||
} as const;
|
||||
|
||||
export const CERTIFICATE_RENEWAL_CONFIG = {
|
||||
MIN_RENEW_BEFORE_DAYS: 1,
|
||||
MAX_RENEW_BEFORE_DAYS: 30,
|
||||
|
||||
@@ -1,8 +1,12 @@
|
||||
import RE2 from "re2";
|
||||
|
||||
import { BadRequestError, ForbiddenRequestError, NotFoundError } from "@app/lib/errors";
|
||||
|
||||
import { CertExtendedKeyUsage, CertKeyUsage } from "../certificate/certificate-types";
|
||||
import {
|
||||
CertExtendedKeyUsageType,
|
||||
CERTIFICATE_RENEWAL_ERROR_MESSAGES,
|
||||
CertificateRenewalErrorType,
|
||||
CertKeyUsageType,
|
||||
mapExtendedKeyUsageToLegacy,
|
||||
mapKeyUsageToLegacy,
|
||||
@@ -196,3 +200,74 @@ export const convertExtendedKeyUsageArrayToLegacy = (
|
||||
): CertExtendedKeyUsage[] | undefined => {
|
||||
return usages?.map(convertToLegacyExtendedKeyUsage);
|
||||
};
|
||||
|
||||
export const categorizeCertificateRenewalError = (error: unknown): string => {
|
||||
if (!error) {
|
||||
return CERTIFICATE_RENEWAL_ERROR_MESSAGES[CertificateRenewalErrorType.UNKNOWN_ERROR];
|
||||
}
|
||||
|
||||
const errorMessage = error instanceof Error ? error.message : String(error);
|
||||
|
||||
if (error instanceof NotFoundError) {
|
||||
if (errorMessage.includes("Certificate Authority")) {
|
||||
return CERTIFICATE_RENEWAL_ERROR_MESSAGES[CertificateRenewalErrorType.CA_NOT_FOUND];
|
||||
}
|
||||
if (errorMessage.includes("Certificate template")) {
|
||||
return CERTIFICATE_RENEWAL_ERROR_MESSAGES[CertificateRenewalErrorType.TEMPLATE_VALIDATION_FAILED];
|
||||
}
|
||||
}
|
||||
|
||||
if (error instanceof BadRequestError) {
|
||||
if (errorMessage.includes("Certificate Authority is") && errorMessage.includes("must be ACTIVE")) {
|
||||
return CERTIFICATE_RENEWAL_ERROR_MESSAGES[CertificateRenewalErrorType.CA_INACTIVE];
|
||||
}
|
||||
if (errorMessage.includes("would expire") && errorMessage.includes("after its issuing CA")) {
|
||||
return CERTIFICATE_RENEWAL_ERROR_MESSAGES[CertificateRenewalErrorType.CERTIFICATE_OUTLIVES_CA];
|
||||
}
|
||||
if (errorMessage.includes("TTL") && errorMessage.includes("must be greater than renewal threshold")) {
|
||||
return CERTIFICATE_RENEWAL_ERROR_MESSAGES[CertificateRenewalErrorType.TTL_TOO_SHORT];
|
||||
}
|
||||
if (errorMessage.includes("not eligible for renewal")) {
|
||||
return CERTIFICATE_RENEWAL_ERROR_MESSAGES[CertificateRenewalErrorType.NOT_ELIGIBLE];
|
||||
}
|
||||
if (errorMessage.includes("Requested validity period exceeds maximum allowed duration")) {
|
||||
return CERTIFICATE_RENEWAL_ERROR_MESSAGES[CertificateRenewalErrorType.VALIDITY_EXCEEDS_MAXIMUM];
|
||||
}
|
||||
if (errorMessage.includes("not allowed by template policy")) {
|
||||
return CERTIFICATE_RENEWAL_ERROR_MESSAGES[CertificateRenewalErrorType.NOT_ALLOWED_BY_TEMPLATE];
|
||||
}
|
||||
}
|
||||
|
||||
if (error instanceof ForbiddenRequestError) {
|
||||
if (errorMessage.includes("Template validation failed")) {
|
||||
return CERTIFICATE_RENEWAL_ERROR_MESSAGES[CertificateRenewalErrorType.TEMPLATE_VALIDATION_FAILED];
|
||||
}
|
||||
}
|
||||
|
||||
if (errorMessage.includes("Template validation failed")) {
|
||||
return CERTIFICATE_RENEWAL_ERROR_MESSAGES[CertificateRenewalErrorType.TEMPLATE_VALIDATION_FAILED];
|
||||
}
|
||||
if (errorMessage.includes("Certificate Authority") && errorMessage.includes("not found")) {
|
||||
return CERTIFICATE_RENEWAL_ERROR_MESSAGES[CertificateRenewalErrorType.CA_NOT_FOUND];
|
||||
}
|
||||
if (errorMessage.includes("Certificate Authority is") && errorMessage.includes("must be ACTIVE")) {
|
||||
return CERTIFICATE_RENEWAL_ERROR_MESSAGES[CertificateRenewalErrorType.CA_INACTIVE];
|
||||
}
|
||||
if (errorMessage.includes("would expire") && errorMessage.includes("after its issuing CA")) {
|
||||
return CERTIFICATE_RENEWAL_ERROR_MESSAGES[CertificateRenewalErrorType.CERTIFICATE_OUTLIVES_CA];
|
||||
}
|
||||
if (errorMessage.includes("TTL") && errorMessage.includes("must be greater than renewal threshold")) {
|
||||
return CERTIFICATE_RENEWAL_ERROR_MESSAGES[CertificateRenewalErrorType.TTL_TOO_SHORT];
|
||||
}
|
||||
if (errorMessage.includes("not eligible for renewal")) {
|
||||
return CERTIFICATE_RENEWAL_ERROR_MESSAGES[CertificateRenewalErrorType.NOT_ELIGIBLE];
|
||||
}
|
||||
if (errorMessage.includes("Requested validity period exceeds maximum allowed duration")) {
|
||||
return CERTIFICATE_RENEWAL_ERROR_MESSAGES[CertificateRenewalErrorType.VALIDITY_EXCEEDS_MAXIMUM];
|
||||
}
|
||||
if (errorMessage.includes("not allowed by template policy")) {
|
||||
return CERTIFICATE_RENEWAL_ERROR_MESSAGES[CertificateRenewalErrorType.NOT_ALLOWED_BY_TEMPLATE];
|
||||
}
|
||||
|
||||
return `${CERTIFICATE_RENEWAL_ERROR_MESSAGES[CertificateRenewalErrorType.UNKNOWN_ERROR]}: ${errorMessage}`;
|
||||
};
|
||||
|
||||
@@ -5,19 +5,13 @@ import { QueueJobs, QueueName, TQueueServiceFactory } from "@app/queue";
|
||||
|
||||
import { ActorType } from "../auth/auth-type";
|
||||
import { TCertificateDALFactory } from "../certificate/certificate-dal";
|
||||
import { CertStatus } from "../certificate/certificate-types";
|
||||
import { TCertificateAuthorityDALFactory } from "../certificate-authority/certificate-authority-dal";
|
||||
import { CERTIFICATE_RENEWAL_CONFIG } from "../certificate-common/certificate-constants";
|
||||
import { TCertificateProfileDALFactory } from "../certificate-profile/certificate-profile-dal";
|
||||
import { TProjectDALFactory } from "../project/project-dal";
|
||||
import { categorizeCertificateRenewalError } from "../certificate-common/certificate-utils";
|
||||
import { TCertificateV3ServiceFactory } from "./certificate-v3-service";
|
||||
|
||||
type TCertificateV3QueueServiceFactoryDep = {
|
||||
queueService: TQueueServiceFactory;
|
||||
certificateDAL: TCertificateDALFactory;
|
||||
certificateAuthorityDAL: Pick<TCertificateAuthorityDALFactory, "findByIdWithAssociatedCa">;
|
||||
certificateProfileDAL: Pick<TCertificateProfileDALFactory, "findByIdWithConfigs">;
|
||||
projectDAL: Pick<TProjectDALFactory, "findById">;
|
||||
certificateDAL: Pick<TCertificateDALFactory, "findCertificatesEligibleForRenewal" | "updateById">;
|
||||
certificateV3Service: TCertificateV3ServiceFactory;
|
||||
auditLogService: Pick<TAuditLogServiceFactory, "createAuditLog">;
|
||||
};
|
||||
@@ -25,9 +19,6 @@ type TCertificateV3QueueServiceFactoryDep = {
|
||||
export const certificateV3QueueServiceFactory = ({
|
||||
queueService,
|
||||
certificateDAL,
|
||||
certificateAuthorityDAL,
|
||||
certificateProfileDAL,
|
||||
projectDAL,
|
||||
certificateV3Service,
|
||||
auditLogService
|
||||
}: TCertificateV3QueueServiceFactoryDep) => {
|
||||
@@ -38,190 +29,109 @@ export const certificateV3QueueServiceFactory = ({
|
||||
const { QUEUE_BATCH_SIZE } = CERTIFICATE_RENEWAL_CONFIG;
|
||||
let offset = 0;
|
||||
let hasMore = true;
|
||||
let totalCertificatesFound = 0;
|
||||
let totalCertificatesRenewed = 0;
|
||||
|
||||
while (hasMore) {
|
||||
const certificates = await certificateDAL.find(
|
||||
{
|
||||
$notNull: ["profileId"],
|
||||
status: CertStatus.ACTIVE,
|
||||
renewedById: null,
|
||||
renewalError: null,
|
||||
revokedAt: null
|
||||
},
|
||||
{
|
||||
limit: QUEUE_BATCH_SIZE,
|
||||
offset
|
||||
}
|
||||
);
|
||||
const certificates = await certificateDAL.findCertificatesEligibleForRenewal({
|
||||
limit: QUEUE_BATCH_SIZE,
|
||||
offset
|
||||
});
|
||||
|
||||
if (certificates.length === 0) {
|
||||
hasMore = false;
|
||||
break;
|
||||
}
|
||||
|
||||
await Promise.all(
|
||||
certificates.map(async (certificate) => {
|
||||
try {
|
||||
if (!certificate.profileId || !certificate.notAfter) {
|
||||
return;
|
||||
}
|
||||
|
||||
const profile = await certificateProfileDAL.findByIdWithConfigs(certificate.profileId);
|
||||
if (!profile) {
|
||||
logger.warn(`Profile not found for certificate ${certificate.id}`);
|
||||
return;
|
||||
}
|
||||
|
||||
const ca = await certificateAuthorityDAL.findByIdWithAssociatedCa(profile.caId);
|
||||
if (!ca) {
|
||||
logger.warn(`CA not found for certificate ${certificate.id}`);
|
||||
return;
|
||||
}
|
||||
|
||||
const profileAutoRenewEnabled = profile.apiConfig?.autoRenew === true;
|
||||
const certificateHasRenewalConfig =
|
||||
certificate.renewBeforeDays != null && certificate.renewBeforeDays > 0;
|
||||
|
||||
if (!profileAutoRenewEnabled && !certificateHasRenewalConfig) {
|
||||
return;
|
||||
}
|
||||
|
||||
const now = new Date();
|
||||
if (certificate.notAfter <= now) {
|
||||
return;
|
||||
}
|
||||
|
||||
const renewBeforeDays = certificate.renewBeforeDays || profile.apiConfig?.renewBeforeDays;
|
||||
if (!renewBeforeDays) {
|
||||
return;
|
||||
}
|
||||
totalCertificatesFound += certificates.length;
|
||||
logger.info(
|
||||
`${QueueJobs.CertificateV3DailyAutoRenewal}: found ${certificates.length} certificates eligible for renewal (batch ${Math.floor(offset / QUEUE_BATCH_SIZE) + 1}, total found so far: ${totalCertificatesFound})`
|
||||
);
|
||||
|
||||
for (const certificate of certificates) {
|
||||
try {
|
||||
if (certificate.renewBeforeDays) {
|
||||
const { MIN_RENEW_BEFORE_DAYS, MAX_RENEW_BEFORE_DAYS } = CERTIFICATE_RENEWAL_CONFIG;
|
||||
if (renewBeforeDays < MIN_RENEW_BEFORE_DAYS || renewBeforeDays > MAX_RENEW_BEFORE_DAYS) {
|
||||
logger.warn(`Invalid renewal threshold ${renewBeforeDays} for certificate ${certificate.id}`);
|
||||
return;
|
||||
}
|
||||
|
||||
const expiryDate = new Date(certificate.notAfter);
|
||||
const renewalDate = new Date(expiryDate.getTime() - renewBeforeDays * 24 * 60 * 60 * 1000);
|
||||
|
||||
const shouldRenew = renewalDate <= now;
|
||||
|
||||
if (shouldRenew) {
|
||||
logger.info(`Auto-renewing certificate ${certificate.id} (common name: ${certificate.commonName})`);
|
||||
|
||||
const project = await projectDAL.findById(certificate.projectId);
|
||||
if (!project) {
|
||||
logger.error(`Project not found for certificate ${certificate.id}`);
|
||||
return;
|
||||
}
|
||||
|
||||
await certificateV3Service.renewCertificate({
|
||||
actor: ActorType.PLATFORM,
|
||||
actorId: "",
|
||||
actorAuthMethod: null,
|
||||
actorOrgId: project.orgId,
|
||||
certificateId: certificate.id,
|
||||
internal: true
|
||||
});
|
||||
|
||||
await certificateDAL.updateById(certificate.id, {
|
||||
renewalError: null
|
||||
});
|
||||
|
||||
await auditLogService.createAuditLog({
|
||||
projectId: certificate.projectId,
|
||||
actor: {
|
||||
type: ActorType.PLATFORM,
|
||||
metadata: {}
|
||||
},
|
||||
event: {
|
||||
type: EventType.AUTOMATED_RENEW_CERTIFICATE,
|
||||
metadata: {
|
||||
certificateId: certificate.id,
|
||||
commonName: certificate.commonName || "",
|
||||
profileId: certificate.profileId,
|
||||
renewBeforeDays: certificate.renewBeforeDays?.toString() || ""
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
logger.info(`Successfully auto-renewed certificate ${certificate.id}`);
|
||||
}
|
||||
} catch (error) {
|
||||
logger.error(
|
||||
error,
|
||||
`Failed to auto-renew certificate ${certificate.id} (common name: ${certificate.commonName})`
|
||||
);
|
||||
|
||||
const errorMessage = error instanceof Error ? error.message : "Unknown error";
|
||||
let categorizedError = errorMessage;
|
||||
|
||||
if (errorMessage.includes("Template validation failed")) {
|
||||
categorizedError =
|
||||
"Auto-renewal failed: certificate template policy has changed and this certificate no longer meets the requirements";
|
||||
} else if (errorMessage.includes("Certificate Authority") && errorMessage.includes("not found")) {
|
||||
categorizedError =
|
||||
"Auto-renewal failed: Certificate Authority for this certificate is no longer available";
|
||||
} else if (errorMessage.includes("Certificate Authority is") && errorMessage.includes("must be ACTIVE")) {
|
||||
categorizedError = "Auto-renewal failed: Certificate Authority is currently inactive";
|
||||
} else if (errorMessage.includes("would expire") && errorMessage.includes("after its issuing CA")) {
|
||||
categorizedError = "Auto-renewal failed: certificate would outlive the Certificate Authority";
|
||||
} else if (
|
||||
errorMessage.includes("TTL") &&
|
||||
errorMessage.includes("must be greater than renewal threshold")
|
||||
if (
|
||||
certificate.renewBeforeDays < MIN_RENEW_BEFORE_DAYS ||
|
||||
certificate.renewBeforeDays > MAX_RENEW_BEFORE_DAYS
|
||||
) {
|
||||
categorizedError =
|
||||
"Auto-renewal failed: certificate validity period is too short for the renewal threshold";
|
||||
} else if (errorMessage.includes("not eligible for renewal")) {
|
||||
categorizedError = "Auto-renewal failed: certificate is not eligible for automatic renewal";
|
||||
} else if (errorMessage.includes("Requested validity period exceeds maximum allowed duration")) {
|
||||
categorizedError =
|
||||
"Auto-renewal failed: certificate validity period exceeds the maximum allowed by the profile template";
|
||||
} else if (errorMessage.includes("not allowed by template policy")) {
|
||||
categorizedError =
|
||||
"Auto-renewal failed: certificate settings are no longer allowed by the profile template";
|
||||
} else {
|
||||
categorizedError = `Auto-renewal failed: ${errorMessage}`;
|
||||
}
|
||||
|
||||
try {
|
||||
await certificateDAL.updateById(certificate.id, {
|
||||
renewalError: categorizedError
|
||||
});
|
||||
} catch (updateError) {
|
||||
logger.error(updateError, `Failed to update renewal error for certificate ${certificate.id}`);
|
||||
}
|
||||
|
||||
try {
|
||||
await auditLogService.createAuditLog({
|
||||
projectId: certificate.projectId,
|
||||
actor: {
|
||||
type: ActorType.PLATFORM,
|
||||
metadata: {}
|
||||
},
|
||||
event: {
|
||||
type: EventType.AUTOMATED_RENEW_CERTIFICATE_FAILED,
|
||||
metadata: {
|
||||
certificateId: certificate.id,
|
||||
commonName: certificate.commonName || "",
|
||||
profileId: certificate.profileId || "",
|
||||
renewBeforeDays: certificate.renewBeforeDays?.toString() || "",
|
||||
error: categorizedError
|
||||
}
|
||||
}
|
||||
});
|
||||
} catch (auditError) {
|
||||
logger.error(auditError, `Failed to create audit log for failed certificate renewal ${certificate.id}`);
|
||||
// eslint-disable-next-line no-continue
|
||||
continue;
|
||||
}
|
||||
}
|
||||
})
|
||||
);
|
||||
|
||||
await certificateV3Service.renewCertificate({
|
||||
actor: ActorType.PLATFORM,
|
||||
actorId: "",
|
||||
actorAuthMethod: null,
|
||||
actorOrgId: "",
|
||||
certificateId: certificate.id,
|
||||
internal: true
|
||||
});
|
||||
|
||||
await certificateDAL.updateById(certificate.id, {
|
||||
renewalError: null
|
||||
});
|
||||
totalCertificatesRenewed += 1;
|
||||
|
||||
await auditLogService.createAuditLog({
|
||||
projectId: certificate.projectId,
|
||||
actor: {
|
||||
type: ActorType.PLATFORM,
|
||||
metadata: {}
|
||||
},
|
||||
event: {
|
||||
type: EventType.AUTOMATED_RENEW_CERTIFICATE,
|
||||
metadata: {
|
||||
certificateId: certificate.id,
|
||||
commonName: certificate.commonName || "",
|
||||
profileId: certificate.profileId!,
|
||||
renewBeforeDays: certificate.renewBeforeDays?.toString() || ""
|
||||
}
|
||||
}
|
||||
});
|
||||
} catch (error) {
|
||||
const categorizedError: string = categorizeCertificateRenewalError(error);
|
||||
|
||||
try {
|
||||
await certificateDAL.updateById(certificate.id, {
|
||||
renewalError: categorizedError
|
||||
});
|
||||
} catch (updateError) {
|
||||
logger.error(updateError, `Failed to update renewal error for certificate ${certificate.id}`);
|
||||
}
|
||||
|
||||
try {
|
||||
await auditLogService.createAuditLog({
|
||||
projectId: certificate.projectId,
|
||||
actor: {
|
||||
type: ActorType.PLATFORM,
|
||||
metadata: {}
|
||||
},
|
||||
event: {
|
||||
type: EventType.AUTOMATED_RENEW_CERTIFICATE_FAILED,
|
||||
metadata: {
|
||||
certificateId: certificate.id,
|
||||
commonName: certificate.commonName || "",
|
||||
profileId: certificate.profileId || "",
|
||||
renewBeforeDays: certificate.renewBeforeDays?.toString() || "",
|
||||
error: categorizedError
|
||||
}
|
||||
}
|
||||
});
|
||||
} catch (auditError) {
|
||||
logger.error(auditError, `Failed to create audit log for failed certificate renewal ${certificate.id}`);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
offset += QUEUE_BATCH_SIZE;
|
||||
}
|
||||
|
||||
logger.info(`${QueueJobs.CertificateV3DailyAutoRenewal}: queue task completed`);
|
||||
logger.info(
|
||||
`${QueueJobs.CertificateV3DailyAutoRenewal}: queue task completed. Renewed ${totalCertificatesRenewed} certificates out of ${totalCertificatesFound}`
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
@@ -1646,7 +1646,7 @@ describe("CertificateV3Service", () => {
|
||||
...mockActor
|
||||
})
|
||||
).rejects.toThrow(
|
||||
"Certificate renewal failed because requested validity period exceeds maximum allowed duration by the profile template"
|
||||
"Certificate renewal failed because requested validity period exceeds maximum allowed duration by the profile template: Subject alternative name not allowed"
|
||||
);
|
||||
|
||||
// Should store template validation error
|
||||
@@ -1788,7 +1788,7 @@ describe("CertificateV3Service", () => {
|
||||
certificateId: "cert-123",
|
||||
...mockActor
|
||||
})
|
||||
).rejects.toThrow("New certificate would expire");
|
||||
).rejects.toThrow(/New certificate would expire \(.+\) after its issuing CA \(.+\)/);
|
||||
});
|
||||
|
||||
it("should allow manual renewal outside window (manual renewal always bypasses window)", async () => {
|
||||
|
||||
@@ -733,8 +733,9 @@ export const certificateV3ServiceFactory = ({
|
||||
? originalCert.altNames.split(",").map((san) => {
|
||||
const trimmed = san.trim();
|
||||
const isIp =
|
||||
new RE2("^\\d{1,3}\\.\\d{1,3}\\.\\d{1,3}\\.\\d{1,3}$").test(trimmed) ||
|
||||
new RE2("^([0-9a-fA-F]{1,4}:){7}[0-9a-fA-F]{1,4}$").test(trimmed);
|
||||
trimmed.length <= 45 &&
|
||||
(new RE2("^\\d{1,3}\\.\\d{1,3}\\.\\d{1,3}\\.\\d{1,3}$").test(trimmed) ||
|
||||
new RE2("^([0-9a-fA-F]{1,4}:){7}[0-9a-fA-F]{1,4}$").test(trimmed));
|
||||
return {
|
||||
type: isIp ? CertSubjectAlternativeNameType.IP_ADDRESS : CertSubjectAlternativeNameType.DNS_NAME,
|
||||
value: trimmed
|
||||
|
||||
@@ -114,12 +114,55 @@ export const certificateDALFactory = (db: TDbClient) => {
|
||||
}
|
||||
};
|
||||
|
||||
const findCertificatesEligibleForRenewal = async ({
|
||||
limit,
|
||||
offset
|
||||
}: {
|
||||
limit: number;
|
||||
offset: number;
|
||||
}): Promise<TCertificates[]> => {
|
||||
try {
|
||||
const now = new Date();
|
||||
const endOfDay = new Date(now.getFullYear(), now.getMonth(), now.getDate(), 23, 59, 59, 999);
|
||||
|
||||
const certs = (await db
|
||||
.replicaNode()(TableName.Certificate)
|
||||
.select(`${TableName.Certificate}.*`)
|
||||
.where(`${TableName.Certificate}.status`, CertStatus.ACTIVE)
|
||||
.whereNull(`${TableName.Certificate}.renewedById`)
|
||||
.whereNull(`${TableName.Certificate}.renewalError`)
|
||||
.whereNull(`${TableName.Certificate}.revokedAt`)
|
||||
.whereNotNull(`${TableName.Certificate}.profileId`)
|
||||
.whereNotNull(`${TableName.Certificate}.notAfter`)
|
||||
.where(`${TableName.Certificate}.notAfter`, ">", now)
|
||||
.where((queryBuilder) => {
|
||||
void queryBuilder.where((subQuery) => {
|
||||
void subQuery
|
||||
.whereNotNull(`${TableName.Certificate}.renewBeforeDays`)
|
||||
.where(`${TableName.Certificate}.renewBeforeDays`, ">", 0)
|
||||
.whereRaw(
|
||||
`"${TableName.Certificate}"."notAfter" - INTERVAL '1 day' * "${TableName.Certificate}"."renewBeforeDays" <= ?`,
|
||||
[endOfDay]
|
||||
);
|
||||
});
|
||||
})
|
||||
.limit(limit)
|
||||
.offset(offset)
|
||||
.orderBy(`${TableName.Certificate}.notAfter`, "asc")) as TCertificates[];
|
||||
|
||||
return certs;
|
||||
} catch (error) {
|
||||
throw new DatabaseError({ error, name: "Find certificates eligible for renewal" });
|
||||
}
|
||||
};
|
||||
|
||||
return {
|
||||
...certificateOrm,
|
||||
countCertificatesInProject,
|
||||
countCertificatesForPkiSubscriber,
|
||||
findLatestActiveCertForSubscriber,
|
||||
findAllActiveCertsForSubscriber,
|
||||
findExpiredSyncedCertificates
|
||||
findExpiredSyncedCertificates,
|
||||
findCertificatesEligibleForRenewal
|
||||
};
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user