feat(coressl): add automated Let's Encrypt provisioning for primary host

- add ProvisionPrimaryDomain worker job to order/renew ACME certificates for the HOST
- register job import in Worker Routes
- add ProvisionSsl env flag in Common/Server/EnvironmentConfig
- expose PROVISION_SSL in Helm chart (values.yaml, values.schema.json, _helpers.tpl)
This commit is contained in:
Nawaz Dhandala
2025-11-03 20:48:07 +00:00
parent 123d9b07bc
commit 5851286548
6 changed files with 105 additions and 0 deletions

View File

@@ -276,6 +276,8 @@ export const HttpProtocol: Protocol =
export const Host: string = process.env["HOST"] || "";
export const ProvisionSsl: boolean = process.env["PROVISION_SSL"] === "true";
export const WorkflowScriptTimeoutInMS: number = process.env[
"WORKFLOW_SCRIPT_TIMEOUT_IN_MS"
]

View File

@@ -49,6 +49,8 @@ Usage:
value: {{ $.Values.slackApp.clientId | quote }}
- name: HOST
value: {{ $.Values.host }}
- name: PROVISION_SSL
value: {{ ternary "true" "false" $.Values.provisionSSL | quote }}
- name: STATUS_PAGE_CNAME_RECORD
value: {{ $.Values.statusPage.cnameRecord }}
- name: ALLOWED_ACTIVE_MONITOR_COUNT_IN_FREE_PLAN

View File

@@ -22,6 +22,10 @@
"type": "string",
"enum": ["http", "https"]
},
"provisionSSL": {
"type": "boolean",
"description": "Automatically provision a Let's Encrypt certificate for the primary host"
},
"oneuptimeSecret": {
"type": ["string", "null"]
},

View File

@@ -5,6 +5,8 @@ global:
# Please change this to the domain name / IP where OneUptime server is hosted on.
host: localhost
httpProtocol: http
# Automatically provision a Let's Encrypt certificate for the primary host when set to true.
provisionSSL: false
# Important: You do need to set this to a long random values if you're using OneUptime in production.
# Please set this to string.

View File

@@ -0,0 +1,94 @@
import RunCron from "../../Utils/Cron";
import { EVERY_DAY, EVERY_FIFTEEN_MINUTE } from "Common/Utils/CronTime";
import {
Host,
ProvisionSsl,
IsDevelopment,
} from "Common/Server/EnvironmentConfig";
import logger from "Common/Server/Utils/Logger";
import Domain from "Common/Types/Domain";
import AcmeCertificateService from "Common/Server/Services/AcmeCertificateService";
import GreenlockUtil from "Common/Server/Utils/Greenlock/Greenlock";
import OneUptimeDate from "Common/Types/Date";
import AcmeCertificate from "Common/Models/DatabaseModels/AcmeCertificate";
const JOB_NAME: string = "CoreSSL:EnsurePrimaryHostCertificate";
RunCron(
JOB_NAME,
{
schedule: IsDevelopment ? EVERY_FIFTEEN_MINUTE : EVERY_DAY,
runOnStartup: true,
timeoutInMS: OneUptimeDate.convertMinutesToMilliseconds(30),
},
async () => {
if (!ProvisionSsl) {
logger.debug(`${JOB_NAME}: provisioning disabled. Skipping execution.`);
return;
}
const normalizedHost: string = Host.trim().toLowerCase();
const hostnameOnly: string = normalizedHost.split(":")[0] || "";
if (!hostnameOnly) {
logger.warn(`${JOB_NAME}: HOST environment variable is empty. Unable to provision SSL.`);
return;
}
if (!Domain.isValidDomain(hostnameOnly)) {
logger.warn(
`${JOB_NAME}: HOST "${hostnameOnly}" is not a valid domain. Skipping SSL provisioning.`,
);
return;
}
try {
const existingCertificate: AcmeCertificate | null =
await AcmeCertificateService.findOneBy({
query: {
domain: hostnameOnly,
},
select: {
_id: true,
expiresAt: true,
},
props: {
isRoot: true,
},
});
if (existingCertificate?.expiresAt) {
const renewalCheckDate: Date = OneUptimeDate.addRemoveDays(
OneUptimeDate.getCurrentDate(),
30,
);
if (existingCertificate.expiresAt > renewalCheckDate) {
logger.debug(
`${JOB_NAME}: existing certificate for ${hostnameOnly} is valid until ${existingCertificate.expiresAt.toISOString()}.`,
);
return;
}
}
logger.debug(
`${JOB_NAME}: ordering or renewing certificate for ${hostnameOnly}.`,
);
await GreenlockUtil.orderCert({
domain: hostnameOnly,
validateCname: async () => {
return true;
},
});
logger.info(
`${JOB_NAME}: certificate successfully issued or renewed for ${hostnameOnly}.`,
);
} catch (err) {
logger.error(`${JOB_NAME}: failed to provision SSL for ${hostnameOnly}.`);
logger.error(err);
throw err;
}
},
);

View File

@@ -62,6 +62,7 @@ import "./Jobs/ServerMonitor/CheckOnlineStatus";
// // Certs Routers
import "./Jobs/StatusPageCerts/StatusPageCerts";
import "./Jobs/CoreSsl/ProvisionPrimaryDomain";
// Status Page Announcements
import "./Jobs/StatusPageOwners/SendAnnouncementCreatedNotification";