Use the certs/ fallback in every environment
TLS no longer depends on NODE_ENV: the TLS_* variables win, else certs/ is used whenever it exists (the container's /app/certs mount lands there too), and TLS_DISABLED=1 is the only way to serve plain HTTP with a certificate present. The plain-HTTP redirect listener now starts whenever TLS is on, not only in development, and HTTP_REDIRECT_PORT replaces DEV_HTTP_PORT. The installer writes TLS_DISABLED=1 for --no-https, and the health check follows the same rule.
This commit is contained in:
@@ -0,0 +1,69 @@
|
||||
import { describe, expect, test } from "bun:test";
|
||||
import pickTlsFiles from "./pick-tls-files";
|
||||
|
||||
const fallback = {
|
||||
fallback_cert_file: "certs/server.crt",
|
||||
fallback_key_file: "certs/server.key",
|
||||
fallback_ca_file: "certs/ca.crt",
|
||||
};
|
||||
|
||||
const exists = (paths: string[]) => (path: string) => paths.includes(path);
|
||||
|
||||
const pick = (
|
||||
options: Partial<Parameters<typeof pickTlsFiles>[0]> = {},
|
||||
paths: string[] = ["certs/server.crt", "certs/server.key", "certs/ca.crt"],
|
||||
) =>
|
||||
pickTlsFiles({
|
||||
...fallback,
|
||||
file_exists: exists(paths),
|
||||
...options,
|
||||
});
|
||||
|
||||
describe("pickTlsFiles", () => {
|
||||
test("prefers the TLS variables", () => {
|
||||
const picked = pick({
|
||||
cert_file: "/etc/cert.pem",
|
||||
key_file: "/etc/key.pem",
|
||||
ca_file: "/etc/ca.pem",
|
||||
});
|
||||
|
||||
expect(picked).toEqual({
|
||||
cert_file: "/etc/cert.pem",
|
||||
key_file: "/etc/key.pem",
|
||||
ca_file: "/etc/ca.pem",
|
||||
source: "explicit",
|
||||
});
|
||||
});
|
||||
|
||||
test("uses certs/ when the variables are unset, in any environment", () => {
|
||||
const picked = pick({ cert_file: "", key_file: "" });
|
||||
|
||||
expect(picked).toEqual({
|
||||
cert_file: "certs/server.crt",
|
||||
key_file: "certs/server.key",
|
||||
ca_file: "certs/ca.crt",
|
||||
source: "fallback",
|
||||
});
|
||||
});
|
||||
|
||||
test("serves HTTP when the certificate is explicitly disabled", () => {
|
||||
expect(
|
||||
pick({
|
||||
cert_file: "/etc/cert.pem",
|
||||
key_file: "/etc/key.pem",
|
||||
disabled: "1",
|
||||
}),
|
||||
).toEqual({ source: "disabled" });
|
||||
expect(pick({ disabled: "true" })).toEqual({ source: "disabled" });
|
||||
expect(pick({ disabled: "no" }).source).toBe("fallback");
|
||||
});
|
||||
|
||||
test("serves HTTP when there is nothing to serve with", () => {
|
||||
expect(pick({}, []).source).toBe("none");
|
||||
expect(pick({}, ["certs/server.crt"]).source).toBe("none");
|
||||
});
|
||||
|
||||
test("keeps half a configuration for the resolver to reject", () => {
|
||||
expect(pick({ cert_file: "/etc/cert.pem" }, []).source).toBe("explicit");
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,83 @@
|
||||
type Params = {
|
||||
/**
|
||||
* `TLS_CERT_FILE`, when the environment names one.
|
||||
*/
|
||||
cert_file?: string;
|
||||
/**
|
||||
* `TLS_KEY_FILE`, when the environment names one.
|
||||
*/
|
||||
key_file?: string;
|
||||
/**
|
||||
* `TLS_CA_FILE`, served for devices to trust.
|
||||
*/
|
||||
ca_file?: string;
|
||||
/**
|
||||
* `TLS_DISABLED`, the explicit opt-out.
|
||||
*/
|
||||
disabled?: string;
|
||||
/**
|
||||
* Where a certificate is looked for when the environment names none:
|
||||
* `certs/` at the project root, which is also where the container's
|
||||
* `/app/certs` mount lands.
|
||||
*/
|
||||
fallback_cert_file: string;
|
||||
fallback_key_file: string;
|
||||
fallback_ca_file: string;
|
||||
file_exists: (path: string) => boolean;
|
||||
};
|
||||
|
||||
export type TlsFiles = {
|
||||
cert_file?: string;
|
||||
key_file?: string;
|
||||
ca_file?: string;
|
||||
/**
|
||||
* Where the choice came from, for the startup log: the environment,
|
||||
* the `certs/` fallback, an explicit disable, or nothing at all.
|
||||
*/
|
||||
source: "explicit" | "fallback" | "disabled" | "none";
|
||||
};
|
||||
|
||||
const DisabledValues = new Set(["1", "true", "yes", "on"]);
|
||||
|
||||
/**
|
||||
* Which certificate files to serve with, in every environment: the
|
||||
* `TLS_*` variables when they are set, else `certs/` when it exists, and
|
||||
* plain HTTP only when `TLS_DISABLED` says so or there is nothing to serve
|
||||
* with.
|
||||
*
|
||||
* Nothing here is tied to `NODE_ENV`: a production process that has a
|
||||
* certificate serves HTTPS, and the only way to turn it off is to say so.
|
||||
*/
|
||||
export default function pickTlsFiles({
|
||||
cert_file,
|
||||
key_file,
|
||||
ca_file,
|
||||
disabled,
|
||||
fallback_cert_file,
|
||||
fallback_key_file,
|
||||
fallback_ca_file,
|
||||
file_exists,
|
||||
}: Params): TlsFiles {
|
||||
if (disabled && DisabledValues.has(disabled.trim().toLowerCase())) {
|
||||
return { source: "disabled" };
|
||||
}
|
||||
|
||||
const cert = cert_file?.trim();
|
||||
const key = key_file?.trim();
|
||||
|
||||
if (cert || key) {
|
||||
return { cert_file: cert, key_file: key, ca_file: ca_file?.trim(), source: "explicit" };
|
||||
}
|
||||
|
||||
if (file_exists(fallback_cert_file) && file_exists(fallback_key_file)) {
|
||||
return {
|
||||
cert_file: fallback_cert_file,
|
||||
key_file: fallback_key_file,
|
||||
ca_file: ca_file?.trim() ||
|
||||
(file_exists(fallback_ca_file) ? fallback_ca_file : undefined),
|
||||
source: "fallback",
|
||||
};
|
||||
}
|
||||
|
||||
return { source: "none" };
|
||||
}
|
||||
Reference in New Issue
Block a user