Skip to content

Commit

Permalink
Merge pull request #78 from dappnode/diego/fix-empty-domain
Browse files Browse the repository at this point in the history
Fix empty dappnode domain
  • Loading branch information
dsimog01 authored Jun 30, 2023
2 parents 364356c + 6f5f7fd commit 114eb69
Show file tree
Hide file tree
Showing 8 changed files with 140 additions and 101 deletions.
170 changes: 90 additions & 80 deletions src/api/app.ts
Original file line number Diff line number Diff line change
@@ -1,87 +1,97 @@
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";
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 };
9 changes: 5 additions & 4 deletions src/api/index.ts
Original file line number Diff line number Diff line change
@@ -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";
Expand All @@ -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);
});
Expand Down
7 changes: 5 additions & 2 deletions src/api/utils/nginx.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,11 @@ import { updateServerConfigs } from "../../nginx";

const maxRetries = 3;

export async function reconfigureNGINX(force = false): Promise<boolean> {
await updateServerConfigs(entriesDb.read(), force);
export async function reconfigureNGINX(
dappnodeDomain: string,
force = false
): Promise<boolean> {
await updateServerConfigs(entriesDb.read(), force, dappnodeDomain);
for (let i = 0; i < maxRetries; i++) {
try {
await shell("nginx -s reload");
Expand Down
4 changes: 2 additions & 2 deletions src/certificates/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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");
Expand All @@ -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);
Expand Down
5 changes: 2 additions & 3 deletions src/certificates/openssl.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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}`
);
}

Expand Down
36 changes: 30 additions & 6 deletions src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand All @@ -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<string> {
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();
5 changes: 3 additions & 2 deletions src/nginx/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,8 @@ async function deleteOldConfig(mappings: DomainMapping[]): Promise<void> {

export async function updateServerConfigs(
mappings: DomainMapping[],
force: boolean
force: boolean,
dappnodeDomain: string
) {
console.log(" *** Updating mappings *** ");
await deleteOldConfig(mappings);
Expand All @@ -41,7 +42,7 @@ export async function updateServerConfigs(
}
await fs.writeFileSync(
path.join(config.serverConfigDir, filename),
await generateServerConfig(mapping)
await generateServerConfig(mapping, dappnodeDomain)
);
}
}
Expand Down
5 changes: 3 additions & 2 deletions src/nginx/templater.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,13 +5,14 @@ import { config } from "../config";
import { DomainMapping } from "../types";

export async function generateServerConfig(
mapping: DomainMapping
mapping: DomainMapping,
dappnodeDomain: string
): Promise<string> {
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,
};
Expand Down

0 comments on commit 114eb69

Please sign in to comment.