Skip to content

Commit

Permalink
feat(dev): built-in tls support (#6483)
Browse files Browse the repository at this point in the history
  • Loading branch information
pcattori authored May 25, 2023
1 parent 2b31855 commit 88d286d
Show file tree
Hide file tree
Showing 6 changed files with 131 additions and 3 deletions.
85 changes: 85 additions & 0 deletions .changeset/ninety-hornets-brake.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
---
"@remix-run/dev": minor
---

built-in tls support

New options:
- `--tls-key` / `tlsKey`: TLS key
- `--tls-cert` / `tlsCert`: TLS Certificate

If both TLS options are set, `scheme` defaults to `https`

## 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.
2 changes: 2 additions & 0 deletions packages/remix-dev/__tests__/cli-test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -122,6 +122,8 @@ describe("remix CLI", () => {
--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:
Expand Down
18 changes: 16 additions & 2 deletions packages/remix-dev/cli/commands.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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") {
Expand Down Expand Up @@ -472,7 +474,10 @@ type DevOrigin = {
};
let resolveDevOrigin = async (
config: RemixConfig,
flags: Partial<DevOrigin> = {}
flags: Partial<DevOrigin> & {
tlsKey?: string;
tlsCert?: string;
} = {}
): Promise<DevOrigin> => {
let dev = config.future.unstable_dev;
if (dev === false) throw Error("This should never happen");
Expand All @@ -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 ??
Expand All @@ -503,6 +508,8 @@ let resolveDevOrigin = async (
type DevServeFlags = DevOrigin & {
command?: string;
restart: boolean;
tlsKey?: string;
tlsCert?: string;
};
let resolveDevServe = async (
config: RemixConfig,
Expand All @@ -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,
};
};
13 changes: 13 additions & 0 deletions packages/remix-dev/cli/run.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand Down Expand Up @@ -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,
Expand All @@ -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;
}
Expand Down
2 changes: 2 additions & 0 deletions packages/remix-dev/config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,8 @@ type Dev = {
host?: string;
port?: number;
restart?: boolean;
tlsKey?: string;
tlsCert?: string;
};

interface FutureConfig {
Expand Down
14 changes: 13 additions & 1 deletion packages/remix-dev/devServer_unstable/index.ts
Original file line number Diff line number Diff line change
@@ -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";
Expand Down Expand Up @@ -47,6 +48,8 @@ export let serve = async (
host: string;
port: number;
restart: boolean;
tlsKey?: string;
tlsCert?: string;
}
) => {
await loadEnv(initialConfig.rootDirectory);
Expand Down Expand Up @@ -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 = {
Expand Down

0 comments on commit 88d286d

Please sign in to comment.