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:
Tom Moor
2022-01-22 17:40:55 -08:00
committed by GitHub
parent 50547ae355
commit e4dbd67ae1
4 changed files with 64 additions and 2 deletions

View File

@@ -102,6 +102,12 @@ OIDC_SCOPES="openid profile email"
# 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.
# 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

2
.gitignore vendored
View File

@@ -9,3 +9,5 @@ stats.json
fakes3/*
.idea
*.pem
*.key
*.cert

View File

@@ -4,6 +4,7 @@ import env from "./env";
import "./tracing"; // must come before importing any instrumented module
import http from "http";
import https from "https";
import Koa from "koa";
import compress from "koa-compress";
import helmet from "koa-helmet";
@@ -18,6 +19,7 @@ import Logger from "./logging/logger";
import { requestErrorHandler } from "./logging/sentry";
import services from "./services";
import { getArg } from "./utils/args";
import { getSSLOptions } from "./utils/ssl";
import { checkEnv, checkMigrations } from "./utils/startup";
import { checkUpdates } from "./utils/updates";
@@ -67,10 +69,18 @@ function master() {
// This function will only be called in each forked process
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
const normalizedPortFlag = getArg("port", "p");
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();
// install basic middleware shared by all services
@@ -108,7 +118,9 @@ async function start(id: number, disconnect: () => void) {
Logger.info(
"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");

42
server/utils/ssl.ts Normal file
View 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,
};
}
}