From 1bdc696a74cb427d1c2ea3d70dee7a80e7bfbbe6 Mon Sep 17 00:00:00 2001 From: Pedro Cattori Date: Thu, 25 May 2023 14:03:17 -0400 Subject: [PATCH] feat(dev): built-in tls support --- .changeset/ninety-hornets-brake.md | 83 +++++++++++++++++++ packages/remix-dev/cli/commands.ts | 18 +++- packages/remix-dev/cli/run.ts | 13 +++ packages/remix-dev/config.ts | 2 + .../remix-dev/devServer_unstable/index.ts | 14 +++- 5 files changed, 127 insertions(+), 3 deletions(-) create mode 100644 .changeset/ninety-hornets-brake.md diff --git a/.changeset/ninety-hornets-brake.md b/.changeset/ninety-hornets-brake.md new file mode 100644 index 00000000000..a64068cf886 --- /dev/null +++ b/.changeset/ninety-hornets-brake.md @@ -0,0 +1,83 @@ +--- +"@remix-run/dev": minor +--- + +built-in tls support + +New options: +- `--tls-key` / `tlsKey`: TLS key +- `--tls-cert` / `tlsCert`: TLS Certificate + +## Example + +Install [mkcert](https://github.com/FiloSottile/mkcert) and create a local CA: + +```sh +brew install mkcert +mkcert -install +``` + +Then make sure you inform `node` about your CA certs: + +```sh +export NODE_EXTRA_CA_CERTS="$(mkcert -CAROOT)/rootCA.pem" +``` + +👆 You'll probably want to put that env var in your scripts or `.bashrc`/`.zshrc` + +Now create `key.pem` and `cert.pem`: + +```sh +mkcert -key-file key.pem -cert-file cert.pem localhost +``` + +See `mkcert` docs for more details. + +Finally, pass in the paths to the key and cert via flags: + +```sh +remix dev --tls-key=key.pem --tls-cert=cert.pem +``` + +or via config: + +```js +module.exports = { + future: { + unstable_dev: { + tlsKey: "key.pem", + tlsCert: "cert.pem", + } + } +} +``` + +That's all that's needed to set up the Remix Dev Server with TLS. + +🚨 Make sure to update your app server for TLS as well. + +For example, with `express`: + +```ts +import express from 'express' +import https from 'node:https' +import fs from 'node:fs' + +let app = express() + +// ...code setting up your express app... + +let appServer = https.createServer({ + key: fs.readFileSync("key.pem"), + cert: fs.readFileSync("cert.pem"), +}, app) + +appServer.listen(3000, () => { + console.log('Ready on https://localhost:3000') +}) +``` + +## Known limitations + +`remix-serve` does not yet support TLS. +That means this only works for custom app server using the `-c` flag for now. diff --git a/packages/remix-dev/cli/commands.ts b/packages/remix-dev/cli/commands.ts index 8df2b505024..0a9074923bc 100644 --- a/packages/remix-dev/cli/commands.ts +++ b/packages/remix-dev/cli/commands.ts @@ -217,6 +217,8 @@ export async function dev( host?: string; port?: number; restart?: boolean; + tlsKey?: string; + tlsCert?: string; } = {} ) { if (process.env.NODE_ENV && process.env.NODE_ENV !== "development") { @@ -472,7 +474,10 @@ type DevOrigin = { }; let resolveDevOrigin = async ( config: RemixConfig, - flags: Partial = {} + flags: Partial & { + tlsKey?: string; + tlsCert?: string; + } = {} ): Promise => { let dev = config.future.unstable_dev; if (dev === false) throw Error("This should never happen"); @@ -481,7 +486,7 @@ let resolveDevOrigin = async ( let scheme = flags.scheme ?? (dev === true ? undefined : dev.scheme) ?? - "http"; + (flags.tlsKey && flags.tlsCert) ? "https": "http"; // prettier-ignore let host = flags.host ?? @@ -503,6 +508,8 @@ let resolveDevOrigin = async ( type DevServeFlags = DevOrigin & { command?: string; restart: boolean; + tlsKey?: string; + tlsCert?: string; }; let resolveDevServe = async ( config: RemixConfig, @@ -521,9 +528,16 @@ let resolveDevServe = async ( let restart = flags.restart ?? (dev === true ? undefined : dev.restart) ?? true; + let tlsKey = flags.tlsKey ?? (dev === true ? undefined : dev.tlsKey); + if (tlsKey) tlsKey = path.resolve(tlsKey); + let tlsCert = flags.tlsCert ?? (dev === true ? undefined : dev.tlsCert); + if (tlsCert) tlsCert = path.resolve(tlsCert); + return { command, ...origin, restart, + tlsKey, + tlsCert, }; }; diff --git a/packages/remix-dev/cli/run.ts b/packages/remix-dev/cli/run.ts index f1d8bcde78f..abba2fdc95e 100644 --- a/packages/remix-dev/cli/run.ts +++ b/packages/remix-dev/cli/run.ts @@ -48,6 +48,8 @@ ${colors.logoBlue("R")} ${colors.logoGreen("E")} ${colors.logoYellow( --host Host for the dev server. Default: localhost --port Port for the dev server. Default: any open port --no-restart Do not restart the app server when rebuilds occur. + --tls-key Path to TLS key (key.pem) + --tls-cert Path to TLS certificate (cert.pem) \`init\` Options: --no-delete Skip deleting the \`remix.init\` script \`routes\` Options: @@ -186,6 +188,8 @@ export async function run(argv: string[] = process.argv.slice(2)) { "--port": Number, "-p": "--port", "--no-restart": Boolean, + "--tls-key": String, + "--tls-cert": String, }, { argv, @@ -210,6 +214,15 @@ export async function run(argv: string[] = process.argv.slice(2)) { return; } + if (flags["tls-key"]) { + flags.tlsKey = flags["tls-key"]; + delete flags["tls-key"]; + } + if (flags["tls-cert"]) { + flags.tlsCert = flags["tls-cert"]; + delete flags["tls-cert"]; + } + if (args["--no-delete"]) { flags.delete = false; } diff --git a/packages/remix-dev/config.ts b/packages/remix-dev/config.ts index e431e14d919..68e91d48641 100644 --- a/packages/remix-dev/config.ts +++ b/packages/remix-dev/config.ts @@ -42,6 +42,8 @@ type Dev = { host?: string; port?: number; restart?: boolean; + tlsKey?: string; + tlsCert?: string; }; interface FutureConfig { diff --git a/packages/remix-dev/devServer_unstable/index.ts b/packages/remix-dev/devServer_unstable/index.ts index 009a24e7439..f9b47c0f114 100644 --- a/packages/remix-dev/devServer_unstable/index.ts +++ b/packages/remix-dev/devServer_unstable/index.ts @@ -1,6 +1,7 @@ import * as path from "node:path"; import * as stream from "node:stream"; import * as http from "node:http"; +import * as https from "node:https"; import fs from "fs-extra"; import prettyMs from "pretty-ms"; import execa from "execa"; @@ -47,6 +48,8 @@ export let serve = async ( host: string; port: number; restart: boolean; + tlsKey?: string; + tlsCert?: string; } ) => { await loadEnv(initialConfig.rootDirectory); @@ -74,7 +77,16 @@ export let serve = async ( res.sendStatus(200); }); - let server = http.createServer(app); + let server = + options.tlsKey && options.tlsCert + ? https.createServer( + { + key: fs.readFileSync(options.tlsKey), + cert: fs.readFileSync(options.tlsCert), + }, + app + ) + : http.createServer(app); let websocket = Socket.serve(server); let origin: Origin = {