feat: Support SSL without reverse proxy (#2959)
* Enable (optional) reading SSL certificates on startup * Update gitignore * fix: Expect ssl environment variables to be Base64 encoded * docs: Add env variables to .env.sample
This commit is contained in:
@@ -102,6 +102,12 @@ OIDC_SCOPES="openid profile email"
|
|||||||
|
|
||||||
# –––––––––––––––– OPTIONAL ––––––––––––––––
|
# –––––––––––––––– OPTIONAL ––––––––––––––––
|
||||||
|
|
||||||
|
# Base64 encoded private key and certificate for HTTPS termination. This is only
|
||||||
|
# required if you do not use an external reverse proxy. See documentation:
|
||||||
|
# https://wiki.generaloutline.com/share/1c922644-40d8-41fe-98f9-df2b67239d45
|
||||||
|
SSL_KEY=
|
||||||
|
SSL_CERT=
|
||||||
|
|
||||||
# If using a Cloudfront/Cloudflare distribution or similar it can be set below.
|
# If using a Cloudfront/Cloudflare distribution or similar it can be set below.
|
||||||
# This will cause paths to javascript, stylesheets, and images to be updated to
|
# This will cause paths to javascript, stylesheets, and images to be updated to
|
||||||
# the hostname defined in CDN_URL. In your CDN configuration the origin server
|
# the hostname defined in CDN_URL. In your CDN configuration the origin server
|
||||||
|
|||||||
2
.gitignore
vendored
2
.gitignore
vendored
@@ -9,3 +9,5 @@ stats.json
|
|||||||
fakes3/*
|
fakes3/*
|
||||||
.idea
|
.idea
|
||||||
*.pem
|
*.pem
|
||||||
|
*.key
|
||||||
|
*.cert
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ import env from "./env";
|
|||||||
import "./tracing"; // must come before importing any instrumented module
|
import "./tracing"; // must come before importing any instrumented module
|
||||||
|
|
||||||
import http from "http";
|
import http from "http";
|
||||||
|
import https from "https";
|
||||||
import Koa from "koa";
|
import Koa from "koa";
|
||||||
import compress from "koa-compress";
|
import compress from "koa-compress";
|
||||||
import helmet from "koa-helmet";
|
import helmet from "koa-helmet";
|
||||||
@@ -18,6 +19,7 @@ import Logger from "./logging/logger";
|
|||||||
import { requestErrorHandler } from "./logging/sentry";
|
import { requestErrorHandler } from "./logging/sentry";
|
||||||
import services from "./services";
|
import services from "./services";
|
||||||
import { getArg } from "./utils/args";
|
import { getArg } from "./utils/args";
|
||||||
|
import { getSSLOptions } from "./utils/ssl";
|
||||||
import { checkEnv, checkMigrations } from "./utils/startup";
|
import { checkEnv, checkMigrations } from "./utils/startup";
|
||||||
import { checkUpdates } from "./utils/updates";
|
import { checkUpdates } from "./utils/updates";
|
||||||
|
|
||||||
@@ -67,10 +69,18 @@ function master() {
|
|||||||
|
|
||||||
// This function will only be called in each forked process
|
// This function will only be called in each forked process
|
||||||
async function start(id: number, disconnect: () => void) {
|
async function start(id: number, disconnect: () => void) {
|
||||||
|
// Find if SSL certs are available
|
||||||
|
const ssl = getSSLOptions();
|
||||||
|
const useHTTPS = !!ssl.key && !!ssl.cert;
|
||||||
|
|
||||||
// If a --port flag is passed then it takes priority over the env variable
|
// If a --port flag is passed then it takes priority over the env variable
|
||||||
const normalizedPortFlag = getArg("port", "p");
|
const normalizedPortFlag = getArg("port", "p");
|
||||||
const app = new Koa();
|
const app = new Koa();
|
||||||
const server = stoppable(http.createServer(app.callback()));
|
const server = stoppable(
|
||||||
|
useHTTPS
|
||||||
|
? https.createServer(ssl, app.callback())
|
||||||
|
: http.createServer(app.callback())
|
||||||
|
);
|
||||||
const router = new Router();
|
const router = new Router();
|
||||||
|
|
||||||
// install basic middleware shared by all services
|
// install basic middleware shared by all services
|
||||||
@@ -108,7 +118,9 @@ async function start(id: number, disconnect: () => void) {
|
|||||||
|
|
||||||
Logger.info(
|
Logger.info(
|
||||||
"lifecycle",
|
"lifecycle",
|
||||||
`Listening on http://localhost:${(address as AddressInfo).port}`
|
`Listening on ${useHTTPS ? "https" : "http"}://localhost:${
|
||||||
|
(address as AddressInfo).port
|
||||||
|
}`
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
server.listen(normalizedPortFlag || env.PORT || "3000");
|
server.listen(normalizedPortFlag || env.PORT || "3000");
|
||||||
|
|||||||
42
server/utils/ssl.ts
Normal file
42
server/utils/ssl.ts
Normal file
@@ -0,0 +1,42 @@
|
|||||||
|
import fs from "fs";
|
||||||
|
import path from "path";
|
||||||
|
import env from "../env";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Find if SSL certs are available in the environment or filesystem and return
|
||||||
|
* as a valid ServerOptions object
|
||||||
|
*/
|
||||||
|
export function getSSLOptions() {
|
||||||
|
function safeReadFile(name: string) {
|
||||||
|
try {
|
||||||
|
return fs.readFileSync(
|
||||||
|
path.normalize(`${__dirname}/../../../${name}`),
|
||||||
|
"utf8"
|
||||||
|
);
|
||||||
|
} catch (err) {
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
return {
|
||||||
|
key:
|
||||||
|
(env.SSL_KEY
|
||||||
|
? Buffer.from(env.SSL_KEY, "base64").toString("ascii")
|
||||||
|
: undefined) ||
|
||||||
|
safeReadFile("private.key") ||
|
||||||
|
safeReadFile("private.pem"),
|
||||||
|
cert:
|
||||||
|
(env.SSL_CERT
|
||||||
|
? Buffer.from(env.SSL_CERT, "base64").toString("ascii")
|
||||||
|
: undefined) ||
|
||||||
|
safeReadFile("public.cert") ||
|
||||||
|
safeReadFile("public.pem"),
|
||||||
|
};
|
||||||
|
} catch (err) {
|
||||||
|
return {
|
||||||
|
key: undefined,
|
||||||
|
cert: undefined,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user