refactor: Improve SSL ordering for domains in StatusPageCerts job

This commit is contained in:
Simon Larsen 2024-08-28 12:25:04 +01:00
parent 108940678f
commit 5c3dcf7bc9
No known key found for this signature in database
GPG Key ID: 96C5DCA24769DBCA
6 changed files with 166 additions and 61 deletions

View File

@ -50,7 +50,7 @@ RunCron(
},
fn: async (span: Span): Promise<void> => {
try {
logger.info("Ordering SSL for domains which are not ordered yet");
logger.debug("Ordering SSL for domains which are not ordered yet");
await StatusPageDomainService.orderSSLForDomainsWhichAreNotOrderedYet();
Telemetry.endSpan(span);

View File

@ -125,6 +125,10 @@ export class Service extends DatabaseService<StatusPageDomain> {
);
}
logger.debug(
"Ordering SSL for domain: " + statusPageDomain.fullDomain,
);
await GreenlockUtil.orderCert({
domain: statusPageDomain.fullDomain as string,
validateCname: async (fullDomain: string) => {
@ -132,6 +136,10 @@ export class Service extends DatabaseService<StatusPageDomain> {
},
});
logger.debug(
"SSL ordered for domain: " + statusPageDomain.fullDomain,
);
// update the order.
await this.updateOneById({
id: statusPageDomain.id!,
@ -238,6 +246,8 @@ export class Service extends DatabaseService<StatusPageDomain> {
try {
// get the token from the domain.
logger.debug("Checking for CNAME " + fullDomain);
const statusPageDomain: StatusPageDomain | null = await this.findOneBy({
query: {
fullDomain: fullDomain,
@ -257,6 +267,8 @@ export class Service extends DatabaseService<StatusPageDomain> {
const token: string = statusPageDomain.cnameVerificationToken!;
logger.debug("Checking for CNAME " + fullDomain + " with token " + token);
const result: HTTPErrorResponse | HTTPResponse<JSONObject> =
await API.get(
URL.fromString(
@ -267,6 +279,9 @@ export class Service extends DatabaseService<StatusPageDomain> {
),
);
logger.debug("CNAME verification result");
logger.debug(result);
if (result.isSuccess()) {
await this.updateCnameStatusForStatusPageDomain({
domain: fullDomain,
@ -275,8 +290,6 @@ export class Service extends DatabaseService<StatusPageDomain> {
return true;
}
logger.debug("CNAME verification failed for http endpoint");
logger.debug(result);
// try with https
@ -290,6 +303,9 @@ export class Service extends DatabaseService<StatusPageDomain> {
),
);
logger.debug("CNAME verification result for https");
logger.debug(resultHttps);
if (resultHttps.isSuccess()) {
await this.updateCnameStatusForStatusPageDomain({
domain: fullDomain,
@ -298,8 +314,6 @@ export class Service extends DatabaseService<StatusPageDomain> {
return true;
}
logger.debug("CNAME verification fails for https endpoint");
logger.debug(resultHttps);
await this.updateCnameStatusForStatusPageDomain({
domain: fullDomain,
@ -419,6 +433,7 @@ export class Service extends DatabaseService<StatusPageDomain> {
for (const domain of domains) {
try {
logger.debug("Ordering SSL for domain: " + domain.fullDomain);
await this.orderCert(domain);
} catch (e) {
logger.error(e);

View File

@ -24,64 +24,107 @@ export default class GreenlockUtil {
validateCname: (domain: string) => Promise<boolean>;
notifyDomainRemoved: (domain: string) => Promise<void>;
}): Promise<void> {
logger.debug("Renewing all certificates");
return await Telemetry.startActiveSpan<Promise<void>>({
name: "GreenlockUtil.renewAllCertsWhichAreExpiringSoon",
fn: async (span: Span): Promise<void> => {
try {
logger.debug("Renewing all certificates");
// get all certificates which are expiring soon
// get all certificates which are expiring soon
const certificates: AcmeCertificate[] = await AcmeCertificateService.findBy(
{
query: {
expiresAt: QueryHelper.lessThanEqualTo(
OneUptimeDate.addRemoveDays(
OneUptimeDate.getCurrentDate(),
40, // 40 days before expiry
),
),
},
limit: LIMIT_MAX,
skip: 0,
select: {
domain: true,
},
sort: {
expiresAt: SortOrder.Ascending,
},
props: {
isRoot: true,
},
},
);
const certificates: AcmeCertificate[] =
await AcmeCertificateService.findBy({
query: {
expiresAt: QueryHelper.lessThanEqualTo(
OneUptimeDate.addRemoveDays(
OneUptimeDate.getCurrentDate(),
40, // 40 days before expiry
),
),
},
limit: LIMIT_MAX,
skip: 0,
select: {
domain: true,
},
sort: {
expiresAt: SortOrder.Ascending,
},
props: {
isRoot: true,
},
});
// order certificate for each domain
logger.debug(
`Found ${certificates.length} certificates which are expiring soon`,
);
for (const certificate of certificates) {
if (!certificate.domain) {
continue;
}
// order certificate for each domain
try {
//validate cname
const isValidCname: boolean = await data.validateCname(
certificate.domain,
);
for (const certificate of certificates) {
if (!certificate.domain) {
continue;
}
if (!isValidCname) {
// if cname is not valid then remove the domain
await GreenlockUtil.removeDomain(certificate.domain);
await data.notifyDomainRemoved(certificate.domain);
} else {
await GreenlockUtil.orderCert({
domain: certificate.domain,
validateCname: data.validateCname,
logger.debug(
`Renewing certificate for domain: ${certificate.domain}`,
);
try {
//validate cname
const isValidCname: boolean = await data.validateCname(
certificate.domain,
);
if (!isValidCname) {
logger.debug(
`CNAME is not valid for domain: ${certificate.domain}`,
);
// if cname is not valid then remove the domain
await GreenlockUtil.removeDomain(certificate.domain);
await data.notifyDomainRemoved(certificate.domain);
logger.error(
`Cname is not valid for domain: ${certificate.domain}`,
);
} else {
logger.debug(
`CNAME is valid for domain: ${certificate.domain}`,
);
await GreenlockUtil.orderCert({
domain: certificate.domain,
validateCname: data.validateCname,
});
logger.debug(
`Certificate renewed for domain: ${certificate.domain}`,
);
}
} catch (e) {
logger.error(
`Error renewing certificate for domain: ${certificate.domain}`,
);
logger.error(e);
}
}
Telemetry.endSpan(span);
} catch (e) {
logger.error("Error renewing all certificates");
logger.error(e);
// record exception
Telemetry.recordExceptionMarkSpanAsErrorAndEndSpan({
span,
exception: e,
});
throw e;
}
} catch (e) {
logger.error(
`Error renewing certificate for domain: ${certificate.domain}`,
);
logger.error(e);
}
}
},
});
}
public static async removeDomain(domain: string): Promise<void> {
@ -134,6 +177,10 @@ export default class GreenlockUtil {
},
fn: async (span: Span): Promise<void> => {
try {
logger.debug(
`GreenlockUtil - Ordering certificate for domain: ${data.domain}`,
);
let { domain } = data;
domain = domain.trim().toLowerCase();
@ -155,9 +202,14 @@ export default class GreenlockUtil {
//validate cname
logger.debug(`Validating cname for domain: ${domain}`);
const isValidCname: boolean = await data.validateCname(domain);
if (!isValidCname) {
logger.debug(`CNAME is not valid for domain: ${domain}`);
logger.debug(`Removing domain: ${domain}`);
await GreenlockUtil.removeDomain(domain);
logger.error(`Cname is not valid for domain: ${domain}`);
throw new BadDataException(
@ -165,6 +217,8 @@ export default class GreenlockUtil {
);
}
logger.debug(`Cname is valid for domain: ${domain}`);
const client: acme.Client = new acme.Client({
directoryUrl: acme.directory.letsencrypt.production,
accountKey: acmeAccountKey,
@ -175,6 +229,8 @@ export default class GreenlockUtil {
commonName: domain,
});
logger.debug(`Ordering certificate for domain: ${domain}`);
const certificate: string = await client.auto({
csr: certificateRequest,
email: LetsEncryptNotificationEmail.toString(),
@ -188,6 +244,10 @@ export default class GreenlockUtil {
// Satisfy challenge here
/* http-01 */
if (challenge.type === "http-01") {
logger.debug(
`Creating challenge for domain: ${authz.identifier.value}`,
);
const acmeChallenge: AcmeChallenge = new AcmeChallenge();
acmeChallenge.challenge = keyAuthorization;
acmeChallenge.token = challenge.token;
@ -199,6 +259,10 @@ export default class GreenlockUtil {
isRoot: true,
},
});
logger.debug(
`Challenge created for domain: ${authz.identifier.value}`,
);
}
},
challengeRemoveFn: async (
@ -207,6 +271,10 @@ export default class GreenlockUtil {
) => {
// Clean up challenge here
logger.debug(
`Removing challenge for domain: ${authz.identifier.value}`,
);
if (challenge.type === "http-01") {
await AcmeChallengeService.deleteBy({
query: {
@ -219,15 +287,24 @@ export default class GreenlockUtil {
},
});
}
logger.debug(
`Challenge removed for domain: ${authz.identifier.value}`,
);
},
});
logger.debug(`Certificate ordered for domain: ${domain}`);
// get expires at date from certificate
const cert: acme.CertificateInfo =
acme.crypto.readCertificateInfo(certificate);
const issuedAt: Date = cert.notBefore;
const expiresAt: Date = cert.notAfter;
logger.debug(`Certificate expires at: ${expiresAt}`);
logger.debug(`Certificate issued at: ${issuedAt}`);
// check if the certificate is already in the database.
const existingCertificate: AcmeCertificate | null =
await AcmeCertificateService.findOneBy({
@ -243,6 +320,8 @@ export default class GreenlockUtil {
});
if (existingCertificate) {
logger.debug(`Updating certificate for domain: ${domain}`);
// update the certificate
await AcmeCertificateService.updateBy({
query: {
@ -260,7 +339,10 @@ export default class GreenlockUtil {
isRoot: true,
},
});
logger.debug(`Certificate updated for domain: ${domain}`);
} else {
logger.debug(`Creating certificate for domain: ${domain}`);
// create the certificate
const acmeCertificate: AcmeCertificate = new AcmeCertificate();
@ -276,6 +358,8 @@ export default class GreenlockUtil {
isRoot: true,
},
});
logger.debug(`Certificate created for domain: ${domain}`);
}
Telemetry.endSpan(span);

View File

@ -50,8 +50,10 @@ const Row: FunctionComponent<ComponentProps> = (
<div
className={`flex w-full border-b-2 border-gray-200 border-l-2 border-l-gray-400 border-r-2 border-r-gray-400`}
>
<div className="flex w-1/4 border-r-2 border-gray-300">
<div className={`pl-${paddingCount} pt-2 pb-2 pr-2 flex`}>
<div className="flex w-1/4 border-r-2 border-gray-300 overflow-hidden">
<div
className={`pl-${paddingCount} pt-2 pb-2 pr-2 flex overflow-hidden`}
>
<div className="w-5 h-5 ml-3 mt-1">
{hasChildRows && (
<Icon

View File

@ -11,9 +11,13 @@ const RowLabel: FunctionComponent<ComponentProps> = (
return (
// rectangle div with curved corners and text inside in tailwindcss
<div>
<div className="text-sm text-gray-600 truncate">{props.title}</div>
<div className="text-xs text-gray-500 truncate">{props.description}</div>
<div className="overflow-hidden">
<div className="text-sm text-gray-600 truncate overflow-hidden">
{props.title}
</div>
<div className="text-xs text-gray-500 truncate overflow-hidden">
{props.description}
</div>
</div>
);
};

View File

@ -319,7 +319,7 @@ const TraceExplorer: FunctionComponent<ComponentProps> = (
const rootRow: GanttChartRow = {
rowInfo: {
title: <div>{rootSpan.name!}</div>,
title: <div className="truncate">{rootSpan.name!}</div>,
description: telemetryService ? (
getRowDescription({
telemetryService,