Skip to content

Commit

Permalink
Add TLS and hostname support
Browse files Browse the repository at this point in the history
This adds both TLS and hostname support.

TLS support is activated by specifying paths to a key and a cert.
There are caveats about self-signed certs in the README.

This also adds hostname support. This impacts both the URL sent to
browsers and the IP the server socket listens to.  There are caveats
about valid names, valid IPs, and HSTS preload in the README.
HSTS-type errors in both Chrome and Firefox are detected automatically
and translated into friendlier errors with a short link to the
documentation.

Closes jasmine#42
Replaces PR jasmine#43
One part of solving shaka-project/shaka-player#5547
  • Loading branch information
joeyparrish committed Mar 8, 2024
1 parent 58e60e4 commit c66c5bc
Show file tree
Hide file tree
Showing 11 changed files with 378 additions and 31 deletions.
57 changes: 57 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,63 @@ To use a browser other than Firefox, add a `browser` field to
Its value can be `"firefox"`, `"headlessFirefox"`, `"safari"`,
`"MicrosoftEdge"`, `"chrome"`, or `"headlessChrome"`.

## TLS support

To serve tests over HTTPS instead of HTTP, supply a path to a TLS cert and key
in PEM format in `jasmine-browser.json`:

```javascript
{
// ...
"tlsKey": "/path/to/tlsKey.pem",
"tlsCert": "/path/to/tlsCert.pem",
// ...
}
```

These can also be specified on the command line with `--tlsKey` and `--tlsCert`.

Note that if you are using a self-signed or otherwise invalid certificate, the
browser will not allow the connection by default. Additional browser configs
or command line options may be necessary to use an invalid TLS certificate.

## Hostname support

To serve tests on a specific interface or IP, you can specify a hostname in
`jasmine-browser.json`:

```javascript
{
// ...
"hostname": "mymachine.mynetwork",
// ...
}
```

This can also be specified on the command line with `--hostname`.

There are a few important caveats when doing this:

1. This name must either be an IP or a name that can really be resolved on your
system. Otherwise, you will get `ENOTFOUND` errors.
2. This name must correspond to an IP assigned to one of the network interfaces
on your system. Otherwise, you will get `EADDRNOTAVAIL` errors.
3. If this name matches the [HSTS preload list](https://hstspreload.org/),
browsers will force the connection to HTTPS. If you are not using TLS, you
will get `WebDriverError: unknown error: net::ERR_SSL_PROTOCOL_ERROR`. You
may be surprised by the names on that list, which include such favorite
local network hostnames as:
- dev
- foo
- app
- nexus
- windows
- office
- dad
You can see a full list in [Chromium source](https://raw.githubusercontent.com/chromium/chromium/main/net/http/transport_security_state_static.json)
or query your hostname at the [HSTS preload site](https://hstspreload.org/).


## ES module support

If a source, spec, or helper file's name ends in `.mjs`, it will be loaded as
Expand Down
10 changes: 5 additions & 5 deletions bin/jasmine-browser-runner
Original file line number Diff line number Diff line change
@@ -1,12 +1,12 @@
#!/usr/bin/env node

const path = require('path'),
jasmineCore = require('../lib/jasmineCore'),
Command = require('../lib/command'),
jasmineBrowser = require('../index.js');
const path = require('path');
const jasmineCore = require('../lib/jasmineCore');
const Command = require('../lib/command');
const jasmineBrowser = require('../index.js');
const UsageError = require('../lib/usage_error');

const command = new Command({
const command = new Command({
baseDir: path.resolve(),
jasmineCore,
jasmineBrowser,
Expand Down
12 changes: 6 additions & 6 deletions index.js
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
const ConsoleReporter = require('./lib/console_reporter'),
webdriverModule = require('./lib/webdriver'),
Server = require('./lib/server'),
Runner = require('./lib/runner'),
ModuleLoader = require('./lib/moduleLoader');
const ConsoleReporter = require('./lib/console_reporter');
const webdriverModule = require('./lib/webdriver');
const Server = require('./lib/server');
const Runner = require('./lib/runner');
const ModuleLoader = require('./lib/moduleLoader');

async function createReporters(options, deps) {
const result = [];
Expand Down Expand Up @@ -98,7 +98,7 @@ module.exports = {
const webdriver = buildWebdriver(options.browser);

try {
const host = `http://localhost:${server.port()}`;
const host = `${server.scheme()}://${server.hostname()}:${server.port()}`;
const runner = new RunnerClass({ webdriver, reporters, host });

console.log('Running tests in the browser...');
Expand Down
3 changes: 3 additions & 0 deletions lib/command.js
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,9 @@ const UsageError = require('./usage_error');
const commonOptions = [
{ name: 'config', type: 'string', description: 'path to the config file' },
{ name: 'port', type: 'number', description: 'port to run the server on' },
{ name: 'tlsCert', type: 'string', description: 'TLS cert for https' },
{ name: 'tlsKey', type: 'string', description: 'TLS key for https' },
{ name: 'hostname', type: 'string', description: 'hostname to listen on' },
];

const subCommands = [
Expand Down
22 changes: 19 additions & 3 deletions lib/runner.js
Original file line number Diff line number Diff line change
Expand Up @@ -100,9 +100,25 @@ class Runner {

async run(runOptions) {
runOptions = runOptions || {};
await this._options.webdriver.get(
this._options.host + urlParams(runOptions)
);

try {
await this._options.webdriver.get(
this._options.host + urlParams(runOptions)
);
} catch (error) {
// Looking for Chrome's "WebDriverError: ... net::ERR_SSL_PROTOCOL_ERROR"
// or Firefox's "WebDriverError: ... about:neterror?e=nssFailure2"
if (error.name == 'WebDriverError') {
if (error.message.includes('ERR_SSL_PROTOCOL_ERROR') ||
error.message.includes('about:neterror?e=nssFailure2')) {
// Show a friendlier error.
throw new Error('The browser tried to speak HTTPS to an HTTP server. Misconfiguration is likely. See https://tinyurl.com/y46m83cc for details.');
}
}

// Rethrow the original error.
throw error;
}

return await runTillEnd(this._options.webdriver, this._options.reporters);
}
Expand Down
69 changes: 60 additions & 9 deletions lib/server.js
Original file line number Diff line number Diff line change
@@ -1,8 +1,9 @@
const defaultExpress = require('express'),
glob = require('glob'),
ejs = require('ejs'),
path = require('path'),
fs = require('fs');
const defaultExpress = require('express');
const ejs = require('ejs');
const fs = require('fs');
const glob = require('glob');
const https = require('https');
const path = require('path');

/**
* @class Server
Expand Down Expand Up @@ -210,12 +211,26 @@ class Server {
});

const port = findPort(serverOptions.port, this.options.port);
const tlsCert = serverOptions.tlsCert || this.options.tlsCert;
const tlsKey = serverOptions.tlsKey || this.options.tlsKey;
const hostname = serverOptions.hostname || this.options.hostname;

// NOTE: Before hostname support, jasmine-browser-runner would listen on
// all IPs (0.0.0.0) and point browsers to "localhost". We preserve
// backward compatibility here by using different defaults for these two
// things.
const listenOptions = {
port,
host: hostname || '0.0.0.0',
};
this._httpHostname = hostname || 'localhost';

return new Promise(resolve => {
this._httpServer = app.listen(port, () => {
const callback = () => {
const runningPort = this._httpServer.address().port;
const url = this._httpServerScheme + '://' + this._httpHostname + ':' + runningPort;
console.log(
`Jasmine server is running here: http://localhost:${runningPort}`
);
`Jasmine server is running here: ${url}`);
console.log(
`Jasmine tests are here: ${path.resolve(
self.options.specDir
Expand All @@ -225,7 +240,21 @@ class Server {
`Source files are here: ${path.resolve(self.options.srcDir)}`
);
resolve();
});
};

if (tlsKey && tlsCert) {
const httpsOptions = {
key: fs.readFileSync(tlsKey),
cert: fs.readFileSync(tlsCert),
};
this._httpServer = https
.createServer(httpsOptions, app)
.listen(listenOptions, callback);
this._httpServerScheme = 'https';
} else {
this._httpServer = app.listen(listenOptions, callback);
this._httpServerScheme = 'http';
}
});
}

Expand Down Expand Up @@ -257,6 +286,28 @@ class Server {

return this._httpServer.address().port;
}

/**
* Gets the URL scheme that the server is listening on. The server must be
* started before this method is called.
* @function
* @name Server#scheme
* @return {string} The URL scheme ('http' or 'https')
*/
scheme() {
return this._httpServerScheme;
}

/**
* Gets the hostname that the server is listening on. The server must be
* started before this method is called.
* @function
* @name Server#hostname
* @return {string} The hostname (localhost if not specified)
*/
hostname() {
return this._httpHostname;
}
}

function findPort(serverPort, optionsPort) {
Expand Down
40 changes: 40 additions & 0 deletions lib/types.js
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,26 @@
* @name ServerCtorOptions#port
* @type number | undefined
*/
/**
* The path to a TLS key. Activates HTTPS mode. If specified, tlsCert must also
* be specified.
* @name ServerCtorOptions#tlsKey
* @type string
*/
/**
* The path to a TLS cert. Activates HTTPS mode. If specified, tlsKey must also
* be specified.
* @name ServerCtorOptions#tlsCert
* @type string
*/
/**
* The hostname to use. This influences both the URL given to browsers and the
* addresses on which the socket listens. If blank, for backward
* compatibility, the browsers will be pointed to localhost, but the listening
* socket will listen on all IPs.
* @name ServerCtorOptions#hostname
* @type string
*/
/**
* The root directory of the project.
* @name ServerCtorOptions#projectBaseDir
Expand Down Expand Up @@ -271,6 +291,26 @@
* @name ServerStartOptions#port
* @type number | undefined
*/
/**
* The path to a TLS key. Activates HTTPS mode. If specified, tlsCert must also
* be specified.
* @name ServerStartOptions#tlsKey
* @type string
*/
/**
* The path to a TLS cert. Activates HTTPS mode. If specified, tlsKey must also
* be specified.
* @name ServerStartOptions#tlsCert
* @type string
*/
/**
* The hostname to use. This influences both the URL given to browsers and the
* addresses on which the socket listens. If blank, for backward
* compatibility, the browsers will be pointed to localhost, but the listening
* socket will listen on all IPs.
* @name ServerStartOptions#hostname
* @type string
*/

/**
* Describes an import map.
Expand Down
38 changes: 38 additions & 0 deletions spec/fixtures/tls-cert.pem
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
-----BEGIN CERTIFICATE-----
MIIF7zCCA9egAwIBAgIUMfxE4gzcNJOO5mAy63yoZjNJj+0wDQYJKoZIhvcNAQEL
BQAwgYYxCzAJBgNVBAYTAlhYMRIwEAYDVQQIDAlTdGF0ZU5hbWUxETAPBgNVBAcM
CENpdHlOYW1lMRQwEgYDVQQKDAtDb21wYW55TmFtZTEbMBkGA1UECwwSQ29tcGFu
eVNlY3Rpb25OYW1lMR0wGwYDVQQDDBRDb21tb25OYW1lT3JIb3N0bmFtZTAeFw0y
NDAzMDcyMzEyMjZaFw0zNDAzMDUyMzEyMjZaMIGGMQswCQYDVQQGEwJYWDESMBAG
A1UECAwJU3RhdGVOYW1lMREwDwYDVQQHDAhDaXR5TmFtZTEUMBIGA1UECgwLQ29t
cGFueU5hbWUxGzAZBgNVBAsMEkNvbXBhbnlTZWN0aW9uTmFtZTEdMBsGA1UEAwwU
Q29tbW9uTmFtZU9ySG9zdG5hbWUwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIK
AoICAQDjeoBBH28RexVG5OSjotUU/0A6ji5gcExefDDfQaxizlp6FzQ2UYsciwwd
Kz6q8peKLP6HlITJ73Z9XQIjb1hudiZzFFuXQza9sJsWSmaEMAs30U42PNhptbhS
hfLFnHS9sV7EJEXiJM73mlkhXjA+iy0t55BiphZxEBVicvgEp82RXiBkQBhipKSL
AWIcrXXPy7G7PyRTgTFmQv7lgwAc0lTV/WhOVv98AJceiDgX/CxYNj41NMoMIOrj
KUEdByl0jRmomZGRfE09UCb577FBupMjk5exbNlV8GPBhgXJb2P9hbcfPNZ/h/uO
IcwL6gTv9Ty2G66ASovwKBn2grl29+95MgSlpmMupA31q7WGnh5A34qfMMdmoSh/
abOLpVi6QCgADAyZAbUwihz+5r8B7lKiSFWvFV36TsGzk/FSznh7m/ZvdUz2v/78
YAMmo1dPtEItMAIYZqVSGakGK+tiLeZbhkT2+cgzdfybdlRucpg3NfcMRX6i8ADd
fQQmlx02LXatsgNZfLRKMjwJ/NfcZ+C04Po/F4Gkr0SOw/kQ2fF8VDsXifT4jRwj
rpOQTqumVDAITYd9lVLy6riBAO4km0k3rIgW6cphZ72BzoRa4NZEufzdAAj9UomN
XgwZZJGT1r/cep4w5qWYKjH/9f0ozPstnWdGtpm7bWqBdcMWuwIDAQABo1MwUTAd
BgNVHQ4EFgQU0oLv+iSCas5EScSdkA+y9qmHotswHwYDVR0jBBgwFoAU0oLv+iSC
as5EScSdkA+y9qmHotswDwYDVR0TAQH/BAUwAwEB/zANBgkqhkiG9w0BAQsFAAOC
AgEALQxvDAOCO9MvwCedx9d5KhtYgqFvB16X6mnfkMjnbp1Iqm4vf8mPIpo8Lw3f
2ZTMG2x5MFoji94ZtIEJENqxfT9p14ftZs6ICX9/obsGHNRRELBokMFtwXxLTVIf
9wSo1JkMqBYZzxZKg2gAgKJCiqPMDo5retCUNG/iJ/6n2g8TiVWPbc2xieCbR4AH
WSwvmy0320ELngifk2rZAnqhzZEXyDBvPYcZgtRG9ZTvAOrE+RL8F+r8Tg3nOt7c
gAWn2YKX40H0qNV5PXuSahScQFtK12slbsDixgZt2WIuXrkLe8jOFSuEbmPqt0bJ
5nmrFWpl3aTcUDJnzycDeLpBXL1hQ8e1iYxcsYL7Wqicd0long0fY+d0mfC0rw2p
CTSN+A5niR/illavxY41I+FT0VeupdkONpo7dNvlWiD/tXaY5XxXnmBrxSOUNObt
fGBh+nqKPlH8H4ne1h+uIKV5no7frxkmUoIY6dbu3K97aKvzLm+5f00S/mETO/HN
fkzk8DHC78v/1yIxQXcnkWMusSUmyuIt2MLPWdmVcD+HMLEXYqG97E/zOvd/7+af
7YoIjANcYRDKCovA0/dfOLF6Wz8L2h9fsfpQIwYJ2rFPV9LeT2c/0CCIt5qf2u69
tjhKQ2uQWyy7SpbcPitkrYiEqs4emQDhHWldjgs+eMk6Jjc=
-----END CERTIFICATE-----

Generated with:

openssl req -x509 -newkey rsa:4096 -keyout tls-key.pem -out tls-cert.pem -sha256 -days 3650 -nodes -subj "/C=XX/ST=StateName/L=CityName/O=CompanyName/OU=CompanySectionName/CN=CommonNameOrHostname"
56 changes: 56 additions & 0 deletions spec/fixtures/tls-key.pem
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
-----BEGIN PRIVATE KEY-----
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
YfDYafbxI5ssyb7f2I+fhL0isKAMuw==
-----END PRIVATE KEY-----

Generated with:

openssl req -x509 -newkey rsa:4096 -keyout tls-key.pem -out tls-cert.pem -sha256 -days 3650 -nodes -subj "/C=XX/ST=StateName/L=CityName/O=CompanyName/OU=CompanySectionName/CN=CommonNameOrHostname"
Loading

0 comments on commit c66c5bc

Please sign in to comment.