From a417b7b590c30f73240cdca98163d84714e0de08 Mon Sep 17 00:00:00 2001 From: Mario Villaplana Date: Mon, 13 May 2019 17:53:07 -0500 Subject: [PATCH] Add TLS support This adds support for processing HTTPS requests on a separate port from the usual HTTP requests. It does so by adding options for specifying the path to a TLS key and a TLS certificate. This can be useful for testing worker scripts that do things like custom redirects depending on whether the user is making an encrypted request or not. Note: This PR does not yet set special `request.cf` attributes such as `tlsVersion` and `tlsCipher` as described here: https://developers.cloudflare.com/workers/reference/request-attributes/ --- README.md | 3 ++ bin/cloudworker.js | 76 ++++++++++++++++++++++++++++++++++++++++------ lib/cloudworker.js | 21 +++++++++++-- 3 files changed, 88 insertions(+), 12 deletions(-) diff --git a/README.md b/README.md index 3a79da5..f9867db 100644 --- a/README.md +++ b/README.md @@ -42,6 +42,9 @@ Options: -w, --wasm [variable=path] Binds variable to wasm located at path (default: []) -c, --enable-cache Enables cache -r, --watch Watch the worker script and restart the worker when changes are detected + --tls-key Optional. Path to encryption key for serving requests with TLS enabled. Must specify --tls-cert when using this option. + --tls-cert Optional. Path to certificate for serving requests with TLS enabled. Must specify --tls-key when using this option. + --https-port Optional. Port to listen on for HTTPS requests. Must specify --tls-cert and --tls-key when using this option. May not be the same value as --port. -h, --help output usage information ``` diff --git a/bin/cloudworker.js b/bin/cloudworker.js index f396be8..60b0dbf 100755 --- a/bin/cloudworker.js +++ b/bin/cloudworker.js @@ -24,6 +24,9 @@ program .option('-c, --enable-cache', 'Enables cache ', false) .option('-r, --watch', 'Watch the worker script and restart the worker when changes are detected', false) .option('-s, --set [variable.key=value]', '(Deprecated) Binds variable to a local implementation of Workers KV and sets key to value', collect, []) + .option('--tls-key ', 'Optional. Path to encryption key for serving requests with TLS enabled. Must specify --tls-cert when using this option.') + .option('--tls-cert ', 'Optional. Path to certificate for serving requests with TLS enabled. Must specify --tls-key when using this option.') + .option('--https-port ', 'Optional. Port to listen on for HTTPS requests. Must specify --tls-cert and --tls-key when using this option. May not be the same value as --port.', 3001) .action(f => { file = f }) .parse(process.argv) @@ -50,10 +53,44 @@ function run (file, wasmBindings) { // Add a warning log for deprecation if (program.set.length > 0) console.warn('Warning: Flag --set is now deprecated, please use --kv-set instead') - const opts = {debug: program.debug, enableCache: program.enableCache, bindings: bindings} - let server = new Cloudworker(script, opts).listen(program.port) + if ((program.tlsKey && !program.tlsCert) || (!program.tlsKey && program.tlsCert)) { + console.error('Both --tls-key and --tls-cert must be set when using TLS.') + process.exit(1) + } + + let tlsKey = '' + let tlsCert = '' + if (program.tlsKey && program.tlsCert) { + try { + tlsKey = fs.readFileSync(program.tlsKey) + tlsCert = fs.readFileSync(program.tlsCert) + } catch (err) { + console.error('Error reading TLS configuration') + console.error(err) + process.exit(1) + } + if (program.port === program.httpsPort) { + console.error('HTTP port and HTTPS port must be different') + process.exit(1) + } + } - console.log(`Listening on ${program.port}`) + const opts = { + debug: program.debug, + enableCache: program.enableCache, + bindings: bindings, + tlsKey: tlsKey, + tlsCert: tlsCert, + } + let worker = new Cloudworker(script, opts) + let server = worker.listen(program.port) + console.log(`Listening on ${program.port} for HTTP requests`) + + let httpsServer = null + if (tlsKey && tlsCert) { + httpsServer = worker.httpsListen(program.httpsPort) + console.log(`Listening on ${program.httpsPort} for HTTPS requests`) + } let stopping = false let reloading = false @@ -64,12 +101,23 @@ function run (file, wasmBindings) { console.log('Changes to the worker script detected - reloading...') server.close(() => { - if (stopping) return - + if (stopping) { + if (httpsServer) { + httpsServer.close(() => { }) + } + return + } + + worker = new Cloudworker(utils.read(fullpath), opts) + server = worker.listen(program.port) + + if (httpsServer) { + httpsServer.close(() => { + httpsServer = worker.httpsListen(program.httpsPort) + }) + } reloading = false - console.log('Successfully reloaded!') - - server = new Cloudworker(utils.read(fullpath), opts).listen(program.port) + console.log('Successfully reloaded server!') }) }) } @@ -80,8 +128,16 @@ function run (file, wasmBindings) { stopping = true console.log('\nShutting down...') server.close(terminate) - - if (reloading) server.on('close', terminate) + if (httpsServer) { + httpsServer.close(terminate) + } + + if (reloading) { + server.on('close', terminate) + if (httpsServer) { + httpsServer.on('close', terminate) + } + } } function terminate () { diff --git a/lib/cloudworker.js b/lib/cloudworker.js index 09a3e26..b44da27 100644 --- a/lib/cloudworker.js +++ b/lib/cloudworker.js @@ -1,4 +1,5 @@ const http = require('http') +const https = require('https') const vm = require('vm') const runtime = require('./runtime') const EventEmitter = require('events') @@ -7,13 +8,15 @@ const StubCacheFactory = require('./runtime/cache/stub') const CacheFactory = require('./runtime/cache/cache') class Cloudworker { - constructor (workerScript, {debug = false, bindings = {}, enableCache = false} = {}) { + constructor (workerScript, {debug = false, bindings = {}, enableCache = false, tlsKey = null, tlsCert = null} = {}) { if (!workerScript || typeof workerScript !== 'string') { throw new TypeError('worker script must be a string') } this.debug = debug this.dispatcher = new EventEmitter() + this.tlsKey = tlsKey + this.tlsCert = tlsCert const eventListener = (eventType, handler) => { const wrapper = (event) => { Promise.resolve(handler(event)).catch((error) => { event.onError(error) }) @@ -57,10 +60,24 @@ class Cloudworker { return server.listen(...args) } + httpsListen (...args) { + const options = { + key: this.tlsKey, + cert: this.tlsCert, + } + const server = https.createServer(options, this._handle.bind(this)) + return server.listen(...args) + } + async _handle (req, res) { const start = new Date() - var url = 'http://' + req.headers['host'] + req.url + let url = '' + if (!req.connection.encrypted) { + url = 'http://' + req.headers['host'] + req.url + } else { + url = 'https://' + req.headers['host'] + req.url + } let body = null if (req.method !== 'GET' && req.method !== 'HEAD') {