Add option to use a secure connection for the daemon

This commit is contained in:
hensm
2022-09-02 09:19:23 +01:00
parent 8ecd3320f7
commit cde15cfd91
7 changed files with 131 additions and 41 deletions

View File

@@ -1,35 +1,31 @@
import http, { IncomingMessage } from "http";
import WebSocket from "ws";
import http from "http";
import https from "https";
import { spawn } from "child_process";
import { Readable } from "stream";
import WebSocket from "ws";
import { DecodeTransform, EncodeTransform } from "./transforms.js";
interface DaemonOpts {
export interface DaemonOpts {
host: string;
port: number;
password?: string;
secure?: boolean;
key?: Buffer;
cert?: Buffer;
}
export function init(opts: DaemonOpts) {
const server = http.createServer();
const server = !opts.secure
? http.createServer()
: https.createServer({
key: opts.key,
cert: opts.cert
});
const wss = new WebSocket.Server({ noServer: true });
process.stdout.write(
`Starting WebSocket server at ws://${
opts.host.includes(":") ? `[${opts.host}]` : opts.host
}:${opts.port}... `
);
server.on("listening", () => {
process.stdout.write("Done!\n");
});
server.on("error", err => {
console.error("Failed!");
console.error(err.message);
});
wss.on("connection", socket => {
// Stream for incoming WebSocket messages
const messageStream = new Readable({ objectMode: true });
@@ -72,7 +68,7 @@ export function init(opts: DaemonOpts) {
* Authenticates requests by checking password URL param against
* server password specified in launch options.
*/
function authenticate(req: IncomingMessage) {
function authenticate(req: http.IncomingMessage) {
if (!opts.password) return true;
const password = new URL(
@@ -121,5 +117,17 @@ export function init(opts: DaemonOpts) {
res.end();
});
server.listen({ port: opts.port, host: opts.host });
process.stdout.write(
`Starting WebSocket server at ${opts.secure ? "wss" : "ws"}://${
opts.host.includes(":") ? `[${opts.host}]` : opts.host
}:${opts.port}... `
);
server.listen({ port: opts.port, host: opts.host }, () => {
process.stdout.write("Done!\n");
});
server.on("error", err => {
console.error("Failed!");
console.error(err.message);
});
}

View File

@@ -1,4 +1,9 @@
import fs from "fs";
import yargs from "yargs";
import type { DaemonOpts } from "./daemon";
import { applicationName, applicationVersion } from "../config.json";
const argv = yargs()
@@ -30,14 +35,46 @@ the port set in the extension options.`,
alias: "P",
describe: `Set an optional password for the daemon WebSocket server. \
This must match the password set in the extension options.
WARNING: This password is intended only as a basic access control measure and \
is transmitted in plain text even over remote connections!`,
Note: If using this option it is highly recommended that you enable secure \
connections to avoid leaking plaintext passwords!`,
type: "string"
})
.option("secure", {
alias: "s",
describe: `Use a secure HTTPS server for WebSocket connections. \
Requires key/cert file options to be specified.`,
type: "boolean",
default: false
})
.option("key-file", {
alias: "k",
describe: `Path to the private key PEM file to use for the \
HTTPS server.`,
type: "string"
})
.option("cert-file", {
alias: "c",
describe: `Path to the certificate PEM file to use for the \
HTTPS server.`,
type: "string"
})
.check(argv => {
// Ensure valid port range
if (argv.port < 1025 || argv.port > 65535) {
throw new Error("Invalid port specified!");
}
// Ensure secure options are valid
if (argv.secure) {
if (!argv["key-file"] || !argv["cert-file"]) {
throw new Error("Missing required key/cert files.");
}
if (
!fs.existsSync(argv["key-file"]) ||
!fs.existsSync(argv["cert-file"])
) {
throw new Error("Specified key/cert files do not exist.");
}
}
return true;
})
@@ -45,11 +82,18 @@ is transmitted in plain text even over remote connections!`,
if (argv.daemon) {
import("./daemon").then(daemon => {
daemon.init({
const daemonOpts: DaemonOpts = {
host: argv.host,
port: argv.port,
password: argv.password
});
};
if (argv.secure) {
daemonOpts.secure = true;
daemonOpts.key = fs.readFileSync(argv.keyFile!);
daemonOpts.cert = fs.readFileSync(argv.certFile!);
}
daemon.init(daemonOpts);
});
} else {
import("./bridge");