diff --git a/CommonServer/Utils/Express.ts b/CommonServer/Utils/Express.ts index 0eef23cbd8..be200c9be8 100644 --- a/CommonServer/Utils/Express.ts +++ b/CommonServer/Utils/Express.ts @@ -11,6 +11,7 @@ import { import UserType from 'Common/Types/UserType'; import Dictionary from 'Common/Types/Dictionary'; import Port from 'Common/Types/Port'; +import https from 'https'; export type RequestHandler = express.RequestHandler; export type NextFunction = express.NextFunction; @@ -64,8 +65,24 @@ class Express { } public static async launchApplication( - appName: string, port?: Port + appName: string, + port?: Port, + httpsOptions?: { + port?: Port, + sniCallBack?: any + } ): Promise { + + const serverOptions = { + SNICallback: httpsOptions?.sniCallBack, + } + + if (httpsOptions && httpsOptions.port) { + https.createServer(serverOptions, this.app).listen(httpsOptions?.port.toNumber(), () => { + logger.info(`${appName} HTTPS server started on port: ${httpsOptions?.port?.toNumber()}`); + }); + } + return new Promise((resolve: Function) => { if (!this.app) { this.setupExpress(); @@ -73,7 +90,7 @@ class Express { this.app.listen(port?.toNumber() || this.app.get('port'), () => { // eslint-disable-next-line - logger.info(`${appName} server started on port: ${this.app.get('port')}`); + logger.info(`${appName} server started on port: ${port?.toNumber() || this.app.get('port')}`); return resolve(this.app); }); }); diff --git a/CommonServer/Utils/StartServer.ts b/CommonServer/Utils/StartServer.ts index d2dd38b2fd..7e386422a3 100644 --- a/CommonServer/Utils/StartServer.ts +++ b/CommonServer/Utils/StartServer.ts @@ -91,8 +91,15 @@ app.use(ExpressUrlEncoded({ limit: '50mb' })); app.use(logRequest); -const init: Function = async (appName: string, port?: Port): Promise => { - await Express.launchApplication(appName, port); +const init: Function = async ( + appName: string, + port?: Port, + httpsOptions?: { + port?: Port, + sniCallBack?: Function + } +): Promise => { + await Express.launchApplication(appName, port, httpsOptions); LocalCache.setString('app', 'name', appName); CommonAPI(appName); @@ -135,19 +142,35 @@ const init: Function = async (appName: string, port?: Port): Promise { - return Response.sendErrorResponse(req, res, new NotFoundException("Not found")) + return Response.sendErrorResponse( + req, + res, + new NotFoundException('Not found') + ); }); app.put('*', (req: ExpressRequest, res: ExpressResponse) => { - return Response.sendErrorResponse(req, res, new NotFoundException("Not found")) + return Response.sendErrorResponse( + req, + res, + new NotFoundException('Not found') + ); }); app.delete('*', (req: ExpressRequest, res: ExpressResponse) => { - return Response.sendErrorResponse(req, res, new NotFoundException("Not found")) + return Response.sendErrorResponse( + req, + res, + new NotFoundException('Not found') + ); }); app.get('*', (req: ExpressRequest, res: ExpressResponse) => { - return Response.sendErrorResponse(req, res, new NotFoundException("Not found")) + return Response.sendErrorResponse( + req, + res, + new NotFoundException('Not found') + ); }); // await OpenTelemetrySDK.start(); diff --git a/Dashboard/src/Components/Header/Logo.tsx b/Dashboard/src/Components/Header/Logo.tsx index 525e1033d8..123db6e3f6 100644 --- a/Dashboard/src/Components/Header/Logo.tsx +++ b/Dashboard/src/Components/Header/Logo.tsx @@ -17,9 +17,7 @@ const Logo: FunctionComponent = ( onClick={() => { props.onClick && props.onClick(); }} - imageUrl={Route.fromString( - `${OneUptimeLogo}` - )} + imageUrl={Route.fromString(`${OneUptimeLogo}`)} /> ); diff --git a/Dashboard/src/Components/Header/UserProfile.tsx b/Dashboard/src/Components/Header/UserProfile.tsx index 86d04853b4..79a32c5f2d 100644 --- a/Dashboard/src/Components/Header/UserProfile.tsx +++ b/Dashboard/src/Components/Header/UserProfile.tsx @@ -22,9 +22,7 @@ const DashboardUserProfile: FunctionComponent = ( <> { - logger.info("HERE!") + logger.info('HERE!'); const host: string | undefined = req.get('host'); if (!host) { @@ -90,7 +94,39 @@ app.get( const init: Function = async (): Promise => { try { // init the app - await App(APP_NAME); + await App(APP_NAME, new Port(3106), { + port: new Port(3107), + sniCallback: (serverName: string, callback: Function) => { + logger.info("SNI CALLBACK " + serverName); + + + GreenlockCertificateService.findOneBy({ + query: { + key: serverName, + }, + select: { + blob: true, + }, + props: { + isRoot: true, + }, + }).then((result: GreenlockCertificate | null) => { + if (!result) { + return callback("Certificate not found"); + } + + const blob = JSON.parse(result.blob as string); + + callback(null, new (tls as any).createSecureContext({ + cert: blob.cert as string, + key: blob.key as string, + })); + }).catch((err: Error) => { + logger.error(err); + return callback("Server Error. Please try again later."); + }); + } + }); // connect to the database. await PostgresAppInstance.connect( diff --git a/StatusPage/Serve.ts b/StatusPage/Serve.ts index 4509a2f147..c481d091ea 100644 --- a/StatusPage/Serve.ts +++ b/StatusPage/Serve.ts @@ -24,7 +24,7 @@ app.get('/*', (_req: ExpressRequest, res: ExpressResponse) => { const init: Function = async (): Promise => { try { // init the app - await App(APP_NAME, new Port(3106)); + await App(APP_NAME, new Port(3105)); } catch (err) { logger.error('App Init Failed:'); logger.error(err); diff --git a/Workers/Jobs/StatusPageCerts/StausPageCerts.ts b/Workers/Jobs/StatusPageCerts/StausPageCerts.ts index 9a8b06fa33..40db6227b4 100644 --- a/Workers/Jobs/StatusPageCerts/StausPageCerts.ts +++ b/Workers/Jobs/StatusPageCerts/StausPageCerts.ts @@ -64,10 +64,10 @@ const greenlock: any = Greenlock.create({ notify: function (event: string, details: any) { if ('error' === event) { - logger.error("Greenlock Notify: " + event); + logger.error('Greenlock Notify: ' + event); logger.error(details); } - logger.info("Greenlock Notify: " + event); + logger.info('Greenlock Notify: ' + event); logger.info(details); }, @@ -92,7 +92,7 @@ router.delete( } await greenlock.remove({ - subject: body['domain'] + subject: body['domain'], }); return Response.sendEmptyResponse(req, res); @@ -116,7 +116,7 @@ router.post( await greenlock.add({ subject: body['domain'], - altnames: [body['domain']] + altnames: [body['domain']], }); return Response.sendEmptyResponse(req, res); @@ -149,15 +149,6 @@ router.get( } ); -RunCron( - 'StatusPageCerts:Renew', - IsDevelopment ? EVERY_MINUTE : EVERY_HOUR, - async () => { - // fetch all domains wiht expired certs. - await greenlock.renew({}); - } -); - RunCron( 'StatusPageCerts:OrderCerts', IsDevelopment ? EVERY_MINUTE : EVERY_HOUR, @@ -168,12 +159,12 @@ RunCron( await StatusPageDomainService.findBy({ query: { isAddedtoGreenlock: true, - isSslProvisioned: false + isSslProvisioned: false, }, select: { _id: true, greenlockConfig: true, - fullDomain: true + fullDomain: true, }, limit: LIMIT_MAX, skip: 0, @@ -187,9 +178,7 @@ RunCron( `StatusPageCerts:OrderCerts - Checking CNAME ${domain.fullDomain}` ); - await greenlock.order(domain.greenlockConfig); - } } ); @@ -243,7 +232,7 @@ RunCron( await greenlock.add({ subject: domain.fullDomain, - altnames: [domain.fullDomain] + altnames: [domain.fullDomain], }); await StatusPageDomainService.updateOneById({ @@ -334,7 +323,6 @@ RunCron( } ); - RunCron( 'StatusPageCerts:CheckSslProvisioningStatus', IsDevelopment ? EVERY_MINUTE : EVERY_HOUR, @@ -394,28 +382,31 @@ RunCron( } ); - const checkCnameValidation: Function = async ( fulldomain: string, token: string ): Promise => { - logger.info("Check CNAMeValidation.") + logger.info('Check CNAMeValidation.'); try { const agent = new https.Agent({ - rejectUnauthorized: false + rejectUnauthorized: false, }); - const result = await axios.get('https://' + fulldomain + '/status-page-api/cname-verification/' + token, { httpsAgent: agent }); - + const result = await axios.get( + 'https://' + + fulldomain + + '/status-page-api/cname-verification/' + + token, + { httpsAgent: agent } + ); + if (result.status === 200) { return true; - } else { - return false; } + return false; } catch (err) { - logger.error(err); - return false; + return false; } }; @@ -423,19 +414,20 @@ const isSslProvisioned: Function = async ( fulldomain: string, token: string ): Promise => { - try { + const result = await axios.get( + 'https://' + + fulldomain + + '/status-page-api/cname-verification/' + + token + ); - const result = await axios.get('https://' + fulldomain + '/status-page-api/cname-verification/' + token); - if (result.status === 200) { return true; - } else { - return false; } + return false; } catch (err) { - logger.error(err); - return false; + return false; } }; diff --git a/Workers/Utils/Greenlock/HttpChallenge.ts b/Workers/Utils/Greenlock/HttpChallenge.ts index 245cdc2f0e..ef9cc20ba8 100644 --- a/Workers/Utils/Greenlock/HttpChallenge.ts +++ b/Workers/Utils/Greenlock/HttpChallenge.ts @@ -4,103 +4,110 @@ import logger from 'CommonServer/Utils/Logger'; // because greenlock package expects module.exports. module.exports = { - init: async (): Promise => { - logger.info("Greenlock HTTP Challenge Init"); - return Promise.resolve(null); - }, - - set: async (data: any): Promise => { - logger.info("Greenlock HTTP Challenge Set"); - logger.info(data); - - const ch: any = data.challenge; - const key: string = ch.identifier.value + '#' + ch.token; - - let challenge: GreenlockChallenge | null = - await GreenlockChallengeService.findOneBy({ - query: { - key: key, - }, - select: { - _id: true, - }, - props: { - isRoot: true, - ignoreHooks: true, - }, - }); - - if (!challenge) { - challenge = new GreenlockChallenge(); - challenge.key = key; - challenge.challenge = ch.keyAuthorization; - - await GreenlockChallengeService.create({ - data: challenge, - props: { - isRoot: true, - }, - }); - } else { - challenge.challenge = ch.keyAuthorization; - await GreenlockChallengeService.updateOneById({ - id: challenge.id!, - data: challenge, - props: { - isRoot: true, - }, - }); - } - - // - return null; - }, - - get: async (data: any): Promise => { - - logger.info("Greenlock HTTP Challenge Get"); - logger.info(data); - - const ch: any = data.challenge; - const key: string = ch.identifier.value + '#' + ch.token; - - const challenge: GreenlockChallenge | null = - await GreenlockChallengeService.findOneBy({ - query: { - key: key, - }, - select: { - _id: true, - }, - props: { - isRoot: true, - ignoreHooks: true, - }, - }); - - if (!challenge) { - return null; - } - - return { keyAuthorization: challenge.challenge }; - }, - - remove: async (data: any): Promise => { - logger.info("Greenlock HTTP Challenge Remove"); - logger.info(data); - - const ch: any = data.challenge; - const key: string = ch.identifier.value + '#' + ch.token; - await GreenlockChallengeService.deleteOneBy({ - query: { - key: key, + create: (_opts: any) => { + return { + init: async (): Promise => { + logger.info('Greenlock HTTP Challenge Init'); + return Promise.resolve(null); }, - props: { - isRoot: true, - ignoreHooks: true, - }, - }); - return null; - }, + set: async (data: any): Promise => { + logger.info('Greenlock HTTP Challenge Set'); + logger.info(data); + + const ch: any = data.challenge; + const key: string = ch.identifier.value + '#' + ch.token; + const token: string = ch.token; + + let challenge: GreenlockChallenge | null = + await GreenlockChallengeService.findOneBy({ + query: { + key: key, + }, + select: { + _id: true, + }, + props: { + isRoot: true, + ignoreHooks: true, + }, + }); + + if (!challenge) { + challenge = new GreenlockChallenge(); + challenge.key = key; + challenge.token = token; + challenge.challenge = ch.keyAuthorization; + + await GreenlockChallengeService.create({ + data: challenge, + props: { + isRoot: true, + }, + }); + } else { + challenge.challenge = ch.keyAuthorization; + challenge.token = token; + await GreenlockChallengeService.updateOneById({ + id: challenge.id!, + data: challenge, + props: { + isRoot: true, + }, + }); + } + + // + return null; + }, + + get: async (data: any): Promise => { + logger.info('Greenlock HTTP Challenge Get'); + logger.info(data); + + const ch: any = data.challenge; + const key: string = ch.identifier.value + '#' + ch.token; + + const challenge: GreenlockChallenge | null = + await GreenlockChallengeService.findOneBy({ + query: { + key: key, + }, + select: { + _id: true, + }, + props: { + isRoot: true, + ignoreHooks: true, + }, + }); + + if (!challenge) { + return null; + } + + return { keyAuthorization: challenge.challenge }; + }, + + remove: async (data: any): Promise => { + logger.info('Greenlock HTTP Challenge Remove'); + logger.info(data); + + const ch: any = data.challenge; + const key: string = ch.identifier.value + '#' + ch.token; + await GreenlockChallengeService.deleteOneBy({ + query: { + key: key, + }, + props: { + isRoot: true, + ignoreHooks: true, + }, + }); + + return null; + }, + } + } + }; diff --git a/Workers/Utils/Greenlock/Manager.ts b/Workers/Utils/Greenlock/Manager.ts index ef11414535..5e83a45409 100644 --- a/Workers/Utils/Greenlock/Manager.ts +++ b/Workers/Utils/Greenlock/Manager.ts @@ -19,7 +19,7 @@ module.exports = { // Optional (wildcard support): find a certificate with `wildname` as an altname // { subject, altnames, renewAt, deletedAt, challenges, ... } - logger.info("Greenlock Manager Get"); + logger.info('Greenlock Manager Get'); logger.info(servername); const domain: StatusPageDomain | null = await StatusPageDomainService.findOneBy({ @@ -37,11 +37,15 @@ module.exports = { }); if (!domain || !domain.greenlockConfig) { - logger.info("Greenlock Manager GET " + servername+" - No domain found."); + logger.info( + 'Greenlock Manager GET ' + + servername + + ' - No domain found.' + ); return undefined; } - logger.info("Greenlock Manager GET " + servername + " RESULT"); + logger.info('Greenlock Manager GET ' + servername + ' RESULT'); logger.info(domain.greenlockConfig); return domain.greenlockConfig; @@ -49,7 +53,7 @@ module.exports = { // Set set: async (opts: any) => { - logger.info("Greenlock Manager Set"); + logger.info('Greenlock Manager Set'); logger.info(opts); // { subject, altnames, renewAt, deletedAt } diff --git a/Workers/Utils/Greenlock/Store.ts b/Workers/Utils/Greenlock/Store.ts index f6739010ea..e3f14bfd51 100644 --- a/Workers/Utils/Greenlock/Store.ts +++ b/Workers/Utils/Greenlock/Store.ts @@ -4,7 +4,7 @@ import GreenlockCertificate from 'Model/Models/GreenlockCertificate'; import GreenlockCertificateService from 'CommonServer/Services/GreenlockCertificateService'; module.exports = { - create: () => { + create: (_opts: any) => { const saveCertificate: Function = async ( id: string, blob: string @@ -116,7 +116,7 @@ module.exports = { }, }, - certificate: { + certificates: { setKeypair: async (opts: any): Promise => { // The ID is a string that doesn't clash between accounts and certificates. // That's all you need to know... unless you're doing something special (in which case you're on your own). @@ -176,7 +176,7 @@ module.exports = { // but it's easiest to implement last since it's not useful until there // are certs that can actually be loaded from storage. check: async (opts: any): Promise => { - const id: string = opts.certificate.id || opts.subject; + const id: string = opts.certificate?.id || opts.subject; const certblob: any = await getCertificate(id); if (!certblob) { diff --git a/docker-compose.tpl.yml b/docker-compose.tpl.yml index befc943245..e84a8c7c89 100644 --- a/docker-compose.tpl.yml +++ b/docker-compose.tpl.yml @@ -139,8 +139,9 @@ services: status-page: ports: - - '3105:3105' - - '3106:3106' + - '3105:3105' # UI Port + - '3106:3106' # HTTP API Port + - '3107:3107' # HTTPS API Port {{ if eq .Env.ENVIRONMENT "development" }} - 9764:9229 # Debugging port. {{ end }}