diff --git a/src/api/app.ts b/src/api/app.ts index ea1dcda..5d879f8 100644 --- a/src/api/app.ts +++ b/src/api/app.ts @@ -1,4 +1,9 @@ -import express, { ErrorRequestHandler, Request, Response } from "express"; +import express, { + ErrorRequestHandler, + Request, + Response, + Express, +} from "express"; import morgan from "morgan"; import { HttpError, BadRequestError, asyncHandler } from "./utils/asyncHandler"; import { entriesDb } from "./db"; @@ -6,82 +11,87 @@ import { reconfigureNGINX } from "./utils/nginx"; import { sanitizeExternal, sanitizeFrom, sanitizeTo } from "./utils/sanitize"; import { config } from "../config"; -const app = express(); - -app.use(morgan("tiny")); - -app.get( - "/add", - asyncHandler(async (req) => { - const from = await sanitizeFrom(req.query.from as string); - const to = sanitizeTo(req.query.to as string); - const external = sanitizeExternal(req.query.external as string); //true if not set, we should swap this, but it left like this for backwards compatibility - - const entries = entriesDb.read(); - if (entries.some((entry) => entry.from === from)) { - throw new BadRequestError("External endpoint already exists"); - } - - // NGINX will crash in loop if a domain is longer than `server_names_hash_bucket_size` - // Force that from has only ASCII characters to make sure the char length = bytes lenght - // fulldomain = from + "." + dappnodeDomain - const dappnodeDomain = process.env._DAPPNODE_GLOBAL_DOMAIN; - const maxLen = config.maximum_domain_length - dappnodeDomain.length - 1; - if (from.length > maxLen) { - throw new BadRequestError(`'from' ${from} exceeds max length of ${from}`); - } - - entries.push({ from, to, external }); - entriesDb.write(entries); - - const reconfigured = await reconfigureNGINX(); - if (!reconfigured) { - entriesDb.write(entries.filter((e) => e.from !== from)); // rollback - await reconfigureNGINX(); - throw new HttpError("Unable to add mapping", 500); - } - }) -); - -app.get( - "/remove", - asyncHandler(async (req) => { - const from = await sanitizeFrom(req.query.from as string); - - const entries = entriesDb.read(); - entriesDb.write(entries.filter((e) => e.from !== from)); - - await reconfigureNGINX(); - }) -); - -app.get( - "/", - asyncHandler(async () => entriesDb.read()) -); - -app.get( - "/reconfig", - asyncHandler(async () => await reconfigureNGINX()) -); - -app.get( - "/clear", - asyncHandler(async () => { - entriesDb.write([]); - await reconfigureNGINX(); - }) -); - -app.use((_req: Request, res: Response) => { - res.status(404).json({ error: "Not Found" }); -}); - -// Default error handler -app.use(function (err, _req, res, next) { - if (res.headersSent) return next(err); - const code = err instanceof HttpError ? err.code : 500; - res.status(code).json({ error: err.message }); -} as ErrorRequestHandler); - -export { app }; +function getHttpsApi(dappnodeDomain: string): Express { + const app = express(); + + app.use(morgan("tiny")); + + app.get( + "/add", + asyncHandler(async (req) => { + const from = await sanitizeFrom(req.query.from as string); + const to = sanitizeTo(req.query.to as string); + const external = sanitizeExternal(req.query.external as string); //true if not set, we should swap this, but it left like this for backwards compatibility + + const entries = entriesDb.read(); + if (entries.some((entry) => entry.from === from)) { + throw new BadRequestError("External endpoint already exists"); + } + + // NGINX will crash in loop if a domain is longer than `server_names_hash_bucket_size` + // Force that from has only ASCII characters to make sure the char length = bytes lenght + // fulldomain = from + "." + dappnodeDomain + const maxLen = config.maximum_domain_length - dappnodeDomain.length - 1; + if (from.length > maxLen) { + throw new BadRequestError( + `'from' ${from} exceeds max length of ${from}` + ); + } + + entries.push({ from, to, external }); + entriesDb.write(entries); + + const reconfigured = await reconfigureNGINX(dappnodeDomain); + if (!reconfigured) { + entriesDb.write(entries.filter((e) => e.from !== from)); // rollback + await reconfigureNGINX(dappnodeDomain); + throw new HttpError("Unable to add mapping", 500); + } + }) + ); + + app.get( + "/remove", + asyncHandler(async (req) => { + const from = await sanitizeFrom(req.query.from as string); + + const entries = entriesDb.read(); + entriesDb.write(entries.filter((e) => e.from !== from)); + + await reconfigureNGINX(dappnodeDomain); + }) + ); + + app.get( + "/", + asyncHandler(async () => entriesDb.read()) + ); + + app.get( + "/reconfig", + asyncHandler(async () => await reconfigureNGINX(dappnodeDomain)) + ); + + app.get( + "/clear", + asyncHandler(async () => { + entriesDb.write([]); + await reconfigureNGINX(dappnodeDomain); + }) + ); + + app.use((_req: Request, res: Response) => { + res.status(404).json({ error: "Not Found" }); + }); + + // Default error handler + app.use(function (err, _req, res, next) { + if (res.headersSent) return next(err); + const code = err instanceof HttpError ? err.code : 500; + res.status(code).json({ error: err.message }); + } as ErrorRequestHandler); + + return app; +} + +export { getHttpsApi }; diff --git a/src/api/index.ts b/src/api/index.ts index 1ed9db0..e170fc7 100644 --- a/src/api/index.ts +++ b/src/api/index.ts @@ -1,4 +1,4 @@ -import { app } from "./app"; +import { getHttpsApi } from "./app"; import { AddressInfo } from "net"; import { reconfigureNGINX } from "./utils/nginx"; import { entriesDb } from "./db"; @@ -15,10 +15,11 @@ function dbMigration() { ); } -export default async function startAPI() { +export default async function startAPI(dappnodeDomain: string) { dbMigration(); - await reconfigureNGINX(); - const server = app.listen(5000, "0.0.0.0", () => { + await reconfigureNGINX(dappnodeDomain); + const httpsApi = getHttpsApi(dappnodeDomain); + const server = httpsApi.listen(5000, "0.0.0.0", () => { const { port, address } = server.address() as AddressInfo; console.log("Server listening on:", "http://" + address + ":" + port); }); diff --git a/src/api/utils/nginx.ts b/src/api/utils/nginx.ts index 483ffd3..f878ebd 100644 --- a/src/api/utils/nginx.ts +++ b/src/api/utils/nginx.ts @@ -4,8 +4,11 @@ import { updateServerConfigs } from "../../nginx"; const maxRetries = 3; -export async function reconfigureNGINX(force = false): Promise { - await updateServerConfigs(entriesDb.read(), force); +export async function reconfigureNGINX( + dappnodeDomain: string, + force = false +): Promise { + await updateServerConfigs(entriesDb.read(), force, dappnodeDomain); for (let i = 0; i < maxRetries; i++) { try { await shell("nginx -s reload"); diff --git a/src/certificates/index.ts b/src/certificates/index.ts index fb00f42..9b2c004 100644 --- a/src/certificates/index.ts +++ b/src/certificates/index.ts @@ -16,7 +16,7 @@ export async function ensureValidCert(createIfNotExists = false) { } } -export default async function initCertificateProvider() { +export default async function initCertificateProvider(dappnodeDomain: string) { console.log("Certificates provider initializing"); try { console.log("- Creating Dummy certificate"); @@ -26,7 +26,7 @@ export default async function initCertificateProvider() { console.log("- Generating DH parameters (this may take a while)"); await generateDHParam(); console.log("- Creating Certificate signing request"); - await createCSR(); + await createCSR(dappnodeDomain); console.log("Certificates provider initialized"); } catch (e) { console.log(e); diff --git a/src/certificates/openssl.ts b/src/certificates/openssl.ts index 2df4ac6..9daed84 100644 --- a/src/certificates/openssl.ts +++ b/src/certificates/openssl.ts @@ -37,14 +37,13 @@ async function generateDomainKey() { await shell(`openssl genrsa ${keyLength} > ${config.keyPath}`); } -async function createCSR() { +async function createCSR(dappnodeDomain: string) { if (fs.existsSync(config.csrPath)) { console.log("Exists, skipping"); return; } - const publicDomain = process.env._DAPPNODE_GLOBAL_DOMAIN; await shell( - `openssl req -new -sha256 -key ${config.keyPath} -subj '/CN=${publicDomain}' -addext 'subjectAltName = DNS:*.${publicDomain}' > ${config.csrPath}` + `openssl req -new -sha256 -key ${config.keyPath} -subj '/CN=${dappnodeDomain}' -addext 'subjectAltName = DNS:*.${dappnodeDomain}' > ${config.csrPath}` ); } diff --git a/src/index.ts b/src/index.ts index d31e01d..a5f058a 100644 --- a/src/index.ts +++ b/src/index.ts @@ -4,11 +4,15 @@ import prepareNGINX from "./nginx"; import shell from "./utils/shell"; import fs from "fs"; import { config } from "./config"; +import axios from "axios"; + async function main() { - if (!process.env._DAPPNODE_GLOBAL_DOMAIN) { - console.error("DAppManager did not inject enviornment. Quitting."); - process.exit(1); - } + const dappnodeDomain = process.env._DAPPNODE_GLOBAL_DOMAIN + ? process.env._DAPPNODE_GLOBAL_DOMAIN + : await retrieveDappnodeDomain(); + + if (process.env._DAPPNODE_GLOBAL_DOMAIN) + console.log("Domain retrieved from environment:", dappnodeDomain); if (!fs.existsSync(config.serverConfigDir)) { fs.mkdirSync(config.serverConfigDir); @@ -19,9 +23,29 @@ async function main() { } await prepareNGINX(); - await initCertificateProvider(); + await initCertificateProvider(dappnodeDomain); await shell("nginx -q"); - startAPI(); + startAPI(dappnodeDomain); +} + +async function retrieveDappnodeDomain(): Promise { + while (true) { + try { + const response = await axios.get( + "http://my.dappnode/global-envs/DOMAIN/" + ); + const domain: string = response.data; + console.log("Domain retrieved from Dappmanager API:", domain); + return domain; // Return the domain once it is available + } catch (error) { + console.error( + "Error: Dappnode domain could not be retrieved from dappmanager API", + error.message + ); + // Retry after 5s delay + await new Promise((resolve) => setTimeout(resolve, 5000)); + } + } } main(); diff --git a/src/nginx/index.ts b/src/nginx/index.ts index dd069cd..98bb41d 100644 --- a/src/nginx/index.ts +++ b/src/nginx/index.ts @@ -23,7 +23,8 @@ async function deleteOldConfig(mappings: DomainMapping[]): Promise { export async function updateServerConfigs( mappings: DomainMapping[], - force: boolean + force: boolean, + dappnodeDomain: string ) { console.log(" *** Updating mappings *** "); await deleteOldConfig(mappings); @@ -41,7 +42,7 @@ export async function updateServerConfigs( } await fs.writeFileSync( path.join(config.serverConfigDir, filename), - await generateServerConfig(mapping) + await generateServerConfig(mapping, dappnodeDomain) ); } } diff --git a/src/nginx/templater.ts b/src/nginx/templater.ts index 25db9c3..e63eb06 100644 --- a/src/nginx/templater.ts +++ b/src/nginx/templater.ts @@ -5,13 +5,14 @@ import { config } from "../config"; import { DomainMapping } from "../types"; export async function generateServerConfig( - mapping: DomainMapping + mapping: DomainMapping, + dappnodeDomain: string ): Promise { const data = { certPath: config.certPath, keyPath: config.keyPath, dhparamPath: config.dhparamPath, - domain: `${mapping.from}.${process.env._DAPPNODE_GLOBAL_DOMAIN}`, + domain: `${mapping.from}.${dappnodeDomain}`, target: mapping.to, external: mapping.external, };