diff --git a/CHANGELOG.md b/CHANGELOG.md
index 57d2cf45..af6f237e 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -54,11 +54,10 @@ Version 2.0.0
- Added metric mapping of slave and instance ids to their names, allowing
them to be displayed by name in queries that join with the mapping.
- Added option to configure timeout for exported Prometheus metrics.
+- Added option to set address to bind HTTP(S) port to.
### Changes
-- Fixed sslCert and sslPrivKey entries being ignored in the config.
-- Changed ssl key creation to be done at startup instead of on npm install.
- Added error handling during master startup.
- Factorio game and rcon port now defaults to a random port above 49151.
- Removed unimplemented mods update command.
@@ -180,6 +179,7 @@ Version 2.0.0
- Removed usage of socket.io entirely in favor of a plain WebSocket connection.
- Renamed clusterioMod plugin to subspace_storage.
- Master no longer creates secret-api-token.txt.
+- Removed automatic creation of self-signed TLS certificate.
Version 1.2.4
diff --git a/README.md b/README.md
index 4516c218..ae113c07 100644
--- a/README.md
+++ b/README.md
@@ -206,21 +206,21 @@ with either . or .. (which will then be resolved to an absolute path).
## Configure Master Server
-By default the master server will create a self signed TLS certificate
-and listen on HTTPS on port 8443. You can change the port used with the
-command
+By default the master server will listen for HTTP on port 8080. You can
+change the port used with the command
- npx clusteriomaster config set master.https_port 1234
+ npx clusteriomaster config set master.http_port 1234
If you plan to make your cluster available externally set the address
that it will be accessible under with, for example
- npx clusteriomaster config set master.external_address https://192.0.2.4:8443/
+ npx clusteriomaster config set master.external_address http://203.0.113.4:1234/
Change the url to reflect the IP, protocol, and port the master server
-is accessible under, dns names are also supported. If proxyed under a
-sub-path include the sub path as well and make sure the URL ends with a
-slash.
+is accessible under, dns names are also supported. If you're planning
+on making the master server accessible on the internet it's recommended
+to set up TLS, see the [Setting Up TLS](/docs/setting-up-tls.md)
+document for more details.
You can list the config of the master server with the `npx
clusteriomaster config list` command. See the [readme for
@@ -228,43 +228,6 @@ clusteriomaster config list` command. See the [readme for
information.
-### Hosting Behind Proxy
-
-If you want to host the master server behind a reverse proxy the
-recommended setup is to disable HTTPS and host it on a high port
-over HTTP to which you point your proxy to. For example to configure
-port 8080 as the HTTP port for the master server.
-
- npx clusteriomaster config set master.https_port # unsets https_port
- npx clusteriomaster config set master.http_port 8080
-
-The proxy can then forward requests to a sub-path like `/cluster/` to
-`http://localhost:8080/`.
-
-Additianlly WebSocket connections to api/socket relative to the sub-path
-the interface is hosted under also needs to be forwarded. See below for
-the specifics of setting this up on Apache.
-
-
-#### Apache Config
-
-To proxy with Apache make sure that proxy_module, proxy_http_module and
-proxy_wstunnel_module is loaded in the config, then add the following
-directives to wherever is the approriate place for your flavour of
-Apache config organization.
-
-```apache
-
- ProxyPass "http://localhost:8080/"
-
-
- ProxyPass "ws://localhost:8080/api/socket"
-
-```
-
-Pay attention to the use of trailing slashes, they are important.
-
-
### Setting up an admin account
Before you can manage the cluster you need to bootstrap an admin account
diff --git a/docs/configuration.md b/docs/configuration.md
index 74409f5c..185213a8 100644
--- a/docs/configuration.md
+++ b/docs/configuration.md
@@ -19,7 +19,7 @@ Port to host HTTP server on. If set to null no HTTP server will be
exposed. At least one of this and master.https_port needs to be set to
a port.
-Defaults to null.
+Defaults to 8080.
### master.https_port
@@ -29,7 +29,10 @@ master.tls_private_key as the certificate and private key for the HTTPS
server. If set to null no HTTPS server will be exposed. At least one
of this and master.http_port needs to be set to a port.
-Defaults to 8443.
+See the [Setting up TLS](/docs/setting-up-tls.md) document for a guide
+to setting up HTTPS with Clusterio.
+
+Defaults to null.
### master.bind_address
@@ -54,29 +57,20 @@ Defaults to null meaning assume localhost.
### master.tls_certificate
-TLS certificate to use for the HTTPS server when master.https_port is
-configured. If neither this nor the configured master.tls_private_key
-exists a self signed certificate is automatically created.
+Path to TLS certificate to use for the HTTPS server when
+master.https_port is configured.
Defaults to "database/certificates/cert.crt".
### master.tls_private_key
-TLS private key to use for the HTTPS server when master.https_port is
-configured. If neither this nor the configured master.tls_certificate
-exists a self signed certificate is automatically created.
+Path to TLS private key to use for the HTTPS server when
+master.https_port is configured.
Defaults to "database/certificates/cert.key".
-### master.tls_bits
-
-Number of bits to use when creating a self signed certificate.
-
-Defaults to 2048.
-
-
### master.auth_secret
Base64 encoded authentication secret used to verify tokens issued to
@@ -178,7 +172,7 @@ Defaults to "instances".
URL to connect to the master server to.
-Defaults to "https://localhost:8443/".
+Defaults to "http://localhost:8080/".
### slave.master_token
@@ -191,6 +185,17 @@ create a new slave config with the correct url and token in it.
Defaults to "enter token here".
+### slave.tls_ca
+
+Path to certificate in PEM format to use for validating a TLS connection
+to the master server. If you have a self signed certificate on the
+master server you will need to copy the certificate to your slaves and
+set it as the certificate authority with this option.
+
+Defaults to null meaning use Node.js's default set of trusted
+certificate authorities.
+
+
### slave.public_address
External address instances hosted on this slave can be accessed on.
@@ -211,7 +216,7 @@ Defaults to 5.
Instance Configuration
-======================
+----------------------
### instance.name
@@ -419,3 +424,44 @@ need to be enabled on the master server in order for them to be loaded
on instances.
Defaults to true
+
+
+Control Configuration
+---------------------
+
+### control.master_url
+
+URL to connect to the master server to.
+
+Defaults to null meaning complain about it not being set and exit.
+
+
+### control.master_token
+
+Access token used for authenticating with the master server. You can
+generate an access token with `clusteriomaster bootstrap
+generate-user-token `, or use the `clusteriomaster bootstrap
+create-ctl-config ` to create a new ctl config with the
+correct url and token in it.
+
+Defaults to null meaning complain about it not being set and exit.
+
+
+### control.tls_ca
+
+Path to certificate in PEM format to use for validating a TLS connection
+to the master server. If you have a self signed certificate on the
+master server you will need to copy the certificate to the computer you
+run clusterctl from and set it as the certificate authority with this
+option.
+
+Defaults to null meaning use Node.js's default set of trusted
+certificate authorities.
+
+
+### control.reconnect_delay
+
+Duration in seconds to wait before attempting to reconnect with the
+master server after the connection is dropped.
+
+Defaults to 2.
diff --git a/docs/setting-up-tls.md b/docs/setting-up-tls.md
new file mode 100644
index 00000000..41d07289
--- /dev/null
+++ b/docs/setting-up-tls.md
@@ -0,0 +1,112 @@
+Setting up TLS
+==============
+
+By default Clusterio will listen for connections over HTTP which is fine
+for local networks but over the Internet it should idealy be over HTTPS
+as there are some credentials that could be exposed to eavesdroppers.
+The recommended setup is to have an ordinary HTTPS server software like
+Apache or Nginx to act as a reverse proxy in front of the Clusterio
+master server. The reverse proxy would then be set up with a TLS
+certificate from a Certificate Authoritiy like [Let's
+Encrypt](https://letsencrypt.org). Alternatively you can have Clusterio
+listen for connections over HTTPS directly by providing it with a TLS
+certificate and private key.
+
+Note that a TLS certificate signed by a recognized Certificate Authority
+in general requires that you own a domain. See the last section for how
+to create and use a self-signed certificate for an IP address if you
+don't have a domain.
+
+
+Using a reverse proxy
+---------------------
+
+Configure the proxy to forward requests over HTTP to the port the master
+server is listening to so that for example a request to
+`https://example.com/instances/` is forwarded to
+`http://localhost:8080/instances/`. It is possible to host the
+interface under a sub-path, in that case a request under the sub-path
+should be mapped to the master server with the sub-path stripped out.
+For example `https://example.com/sub/path/instances/` would be mapped to
+`http://localhost:8080/instances`.
+
+Additianlly WebSocket connections to `/api/socket` also needs to be
+forwarded. Typically this requires explicit configuration and support
+for forwarding WebSocket connections. If the interface hosted under a
+sub-path the `/api/socket` address is relative to the sub-path and the
+sub-path also needs to be stripped out when forwarded.
+
+You can also optionally set the master server to listen only on the
+loopback interface to prevent it being directly connected to from the
+outside.
+
+ npx clusteriomaster config set master.bind_address 127.0.0.1
+
+
+### Apache Config
+
+To proxy with Apache make sure that `proxy_module`, `proxy_http_module`
+and `proxy_wstunnel_module` is loaded in the config, then add the
+following directives to wherever is the approriate place for your
+flavour of Apache config organization.
+
+```apache
+
+ ProxyPass "http://localhost:8080/"
+
+
+ ProxyPass "ws://localhost:8080/api/socket"
+
+```
+
+Replace `/sub/path/` with the location on the proxy you want to host the
+Clusterio interface under. If the whole domain is to be dedicated to
+the Clusterio interface then `/sub/path/` should be just `/`. Pay
+attention to the use of trailing slashes, as they are important.
+
+
+Hosting HTTPS directly from Node.js
+-----------------------------------
+
+Listening on HTTPS is supported by the Clusterio master server via the
+`master.https_port` option provided paths to a certificate file and a
+private key file is configured for the `master.tls_certificate` and
+`master.tls_private_key` options. Both of these are expected to be
+unencrypted PEM files.
+
+
+Creating and using a self-signed certificate
+--------------------------------------------
+
+Create a self-signed certificate with OpenSSL by running
+
+ openssl req -x509 -newkey rsa:2048 -nodes -keyout key.pem -out cert.pem -days 3650 -subj /CN=localhost -addext "subjectAltName=IP:"
+
+substituting `` with the IP address the server will be accessed
+under, for example `IP:203.0.113.4`. Multiple addresses can be given by
+supplying a comma sepparated list of `IP:address` pairs and DNS names
+can also be specified using the `DNS:example.com` format. You need to
+supply every name you want to connect to the server under the
+subjectAltName field, for example to connect to the HTTPS port locally
+via an address like `https://localhost:4443/` you need to add
+`DNS:localhost`.
+
+This creates a private key file named `key.pem` and a public certificate
+file named `cert.pem` and these are used for the
+`master.tls_certificate` and `master.tls_private_key` config options
+respectively on the master server:
+
+ npx clusteriomaster config set master.tls_certificate cert.pem
+ npx clusteriomaster config set master.tls_private_key key.pem
+
+Additionally you will need copy the `cert.pem` file to all of the
+computers you want to set up as slaves and configure it as the
+`slave.tls_ca` option:
+
+ npx clusterioslave config set slave.tls_ca cert.pem
+
+You will also need to copy the `cert.pem` file to all of the computers
+you want to remotely manage the cluster via clusterctl and configure it
+as `control.tls_ca` option:
+
+ npx clusterioctl control-config set control.tls_ca cert.pem
diff --git a/packages/ctl/ctl.js b/packages/ctl/ctl.js
index 0658725a..0f71dcd4 100755
--- a/packages/ctl/ctl.js
+++ b/packages/ctl/ctl.js
@@ -549,8 +549,8 @@ debugCommands.add(new libCommand.Command({
* @private
*/
class ControlConnector extends libLink.WebSocketClientConnector {
- constructor(url, reconnectDelay, token) {
- super(url, reconnectDelay);
+ constructor(url, reconnectDelay, tlsCa, token) {
+ super(url, reconnectDelay, tlsCa);
this._token = token;
}
@@ -765,9 +765,16 @@ async function startControl() {
return;
}
+ let tlsCa = null;
+ let tlsCaPath = controlConfig.get("control.tls_ca");
+ if (tlsCaPath) {
+ tlsCa = await fs.readFile(tlsCaPath);
+ }
+
let controlConnector = new ControlConnector(
controlConfig.get("control.master_url"),
controlConfig.get("control.reconnect_delay"),
+ tlsCa,
controlConfig.get("control.master_token")
);
let control = new Control(controlConnector, controlPlugins);
diff --git a/packages/lib/config/definitions.js b/packages/lib/config/definitions.js
index 223f53f1..3c453eb9 100644
--- a/packages/lib/config/definitions.js
+++ b/packages/lib/config/definitions.js
@@ -24,6 +24,7 @@ MasterGroup.define({
description: "Port to listen for HTTP connections on, set to null to not listen for HTTP connections.",
type: "number",
optional: true,
+ initial_value: 8080,
});
MasterGroup.define({
name: "https_port",
@@ -31,7 +32,6 @@ MasterGroup.define({
description: "Port to listen for HTTPS connection on, set to null to not listen for HTTPS connections.",
type: "number",
optional: true,
- initial_value: 8443,
});
MasterGroup.define({
name: "bind_address",
@@ -53,7 +53,6 @@ MasterGroup.define({
description: "Path to the certificate to use for HTTPS.",
type: "string",
optional: true,
- initial_value: "database/certificates/cert.crt",
});
MasterGroup.define({
name: "tls_private_key",
@@ -61,14 +60,6 @@ MasterGroup.define({
description: "Path to the private key to use for HTTPS.",
type: "string",
optional: true,
- initial_value: "database/certificates/cert.key",
-});
-MasterGroup.define({
- name: "tls_bits",
- title: "TLS Bits",
- description: "Number of bits to use for auto generated TLS certificate.",
- type: "number",
- initial_value: 2048,
});
MasterGroup.define({
name: "auth_secret",
@@ -155,7 +146,7 @@ SlaveGroup.define({
name: "master_url",
description: "URL to connect to the master server at",
type: "string",
- initial_value: "https://localhost:8443/",
+ initial_value: "http://localhost:8080/",
});
SlaveGroup.define({
name: "master_token",
@@ -163,6 +154,12 @@ SlaveGroup.define({
type: "string",
initial_value: "enter token here",
});
+SlaveGroup.define({
+ name: "tls_ca",
+ description: "Path to Certificate Authority to validate TLS connection to master against.",
+ type: "string",
+ optional: true,
+});
SlaveGroup.define({
name: "public_address",
description: "Public facing address players should connect to in order to join instances on this slave",
@@ -337,6 +334,12 @@ ControlGroup.define({
type: "string",
optional: true,
});
+ControlGroup.define({
+ name: "tls_ca",
+ description: "Path to Certificate Authority to validate TLS connection to master against.",
+ type: "string",
+ optional: true,
+});
ControlGroup.define({
name: "reconnect_delay",
title: "Reconnect Delay",
diff --git a/packages/lib/link/connectors.js b/packages/lib/link/connectors.js
index 675d7579..ccf24904 100644
--- a/packages/lib/link/connectors.js
+++ b/packages/lib/link/connectors.js
@@ -144,10 +144,11 @@ class WebSocketBaseConnector extends events.EventEmitter {
* @memberof module:lib/link
*/
class WebSocketClientConnector extends WebSocketBaseConnector {
- constructor(url, reconnectDelay) {
+ constructor(url, reconnectDelay, tlsCa = null) {
super();
this._url = url;
+ this._tlsCa = tlsCa;
// The following states are used in the client connector
// new: Not connected
@@ -243,11 +244,9 @@ class WebSocketClientConnector extends WebSocketBaseConnector {
this._socket = new WebSocket(url);
} else {
- this._socket = new WebSocket(url, {
- // For now we do not verify TLS certificates since the default setup is
- // to create a self-signed certificate.
- rejectUnauthorized: false,
- });
+ let options = {};
+ if (this._tlsCa) { options.ca = this._tlsCa; }
+ this._socket = new WebSocket(url, options);
}
this._attachSocketHandlers();
@@ -317,7 +316,19 @@ class WebSocketClientConnector extends WebSocketBaseConnector {
this._socket.onerror = event => {
// It's assumed that close is always called by ws
- logger.error(`SOCKET | Error: ${event.message || "unknown error"}`);
+ let code = !event.error ? "" : `, code: ${event.error.code}`;
+ logger.error(`SOCKET | Error: ${event.message || "unknown error"}${code}`);
+ if (event.error) {
+ // Abort connection if certificate validation failed
+ if ([
+ "CERT_HAS_EXPIRED",
+ "DEPTH_ZERO_SELF_SIGNED_CERT",
+ "ERR_TLS_CERT_ALTNAME_INVALID",
+ ].includes(event.error.code)) {
+ this.emit("error", new libErrors.AuthenticationFailed(event.error.message));
+ this._state = "closing";
+ }
+ }
};
this._socket.onopen = () => {
diff --git a/packages/master/master.js b/packages/master/master.js
index a0b8af05..611a6231 100755
--- a/packages/master/master.js
+++ b/packages/master/master.js
@@ -48,7 +48,6 @@ let db = {
};
// homebrew modules
-const generateSslCert = require("./src/generate_ssl_cert");
const HttpCloser = require("./src/HttpCloser");
const routes = require("./src/routes");
const libDatabase = require("@clusterio/lib/database");
@@ -1955,14 +1954,11 @@ async function startServer() {
let tls_cert = masterConfig.get("master.tls_certificate");
let tls_key = masterConfig.get("master.tls_private_key");
- // Create a self signed certificate if the certificate files doesn't exist
- if (httpsPort && !await fs.exists(tls_cert) && !await fs.exists(tls_key)) {
- await generateSslCert({
- bits: masterConfig.get("master.tls_bits"),
- sslCertPath: tls_cert,
- sslPrivKeyPath: tls_key,
- doLogging: true,
- });
+
+ if (httpsPort && (!tls_cert || !tls_key)) {
+ throw new libErrors.StartupError(
+ "tls_certificate and tls_private_key must be configure in order to use https_port"
+ );
}
// Add routes for the web interface
diff --git a/packages/master/src/generate_ssl_cert.js b/packages/master/src/generate_ssl_cert.js
deleted file mode 100644
index 0c63ea03..00000000
--- a/packages/master/src/generate_ssl_cert.js
+++ /dev/null
@@ -1,43 +0,0 @@
-"use strict";
-const forge = require("node-forge");
-const fs = require("fs-extra");
-const util = require("util");
-
-const { logger } = require("@clusterio/lib/logging");
-
-const hrtime = process.hrtime.bigint;
-
-
-module.exports = async function({
- bits = 2048,
- sslCertPath,
- sslPrivKeyPath,
-} = {}){
- logger.info(`Generating SSL certificate with ${bits} bits at ${sslCertPath}`);
-
- let startNs = hrtime();
- let keypair = await util.promisify(forge.pki.rsa.generateKeyPair)({ bits: bits, e: 0x10001 });
- logger.info(`Generated ${bits}bit keypair in: ${Number(hrtime()-startNs) / 1e6} ms`);
-
- startNs = hrtime();
- let cert = forge.pki.createCertificate();
- cert.publicKey = keypair.publicKey;
- cert.serialNumber = "01";
-
- cert.validity.notBefore = new Date();
- cert.validity.notAfter = new Date(Date.now()+(1000*60*60*24*365)); // valid for one year
-
- // XXX This should probably be limited to the actual hostname
- let attrs = [{ name: "commonName", value: "*" }];
- cert.setSubject(attrs);
- cert.setIssuer(attrs);
-
- // self-sign certificate
- cert.sign(keypair.privateKey);
-
- // Write certificate and private key to pem files
- await fs.outputFile(sslPrivKeyPath, forge.pki.privateKeyToPem(keypair.privateKey));
- await fs.outputFile(sslCertPath, forge.pki.certificateToPem(cert));
-
- logger.info(`Generated certificate in: ${Number(hrtime()-startNs) / 1e6} ms`);
-};
diff --git a/packages/slave/slave.js b/packages/slave/slave.js
index 350ba6a7..bc4738a2 100755
--- a/packages/slave/slave.js
+++ b/packages/slave/slave.js
@@ -890,8 +890,12 @@ class InstanceConnection extends libLink.Link {
}
class SlaveConnector extends libLink.WebSocketClientConnector {
- constructor(slaveConfig, pluginInfos) {
- super(slaveConfig.get("slave.master_url"), slaveConfig.get("slave.reconnect_delay"));
+ constructor(slaveConfig, tlsCa, pluginInfos) {
+ super(
+ slaveConfig.get("slave.master_url"),
+ slaveConfig.get("slave.reconnect_delay"),
+ tlsCa
+ );
this.slaveConfig = slaveConfig;
this.pluginInfos = pluginInfos;
}
@@ -1593,7 +1597,13 @@ async function startSlave() {
return;
}
- let slaveConnector = new SlaveConnector(slaveConfig, pluginInfos);
+ let tlsCa = null;
+ let tlsCaPath = slaveConfig.get("slave.tls_ca");
+ if (tlsCaPath) {
+ tlsCa = await fs.readFile(tlsCaPath);
+ }
+
+ let slaveConnector = new SlaveConnector(slaveConfig, tlsCa, pluginInfos);
let slave = new Slave(slaveConnector, slaveConfig, pluginInfos);
// Handle interrupts
diff --git a/test/file/tls/cert.pem b/test/file/tls/cert.pem
new file mode 100644
index 00000000..7029b684
--- /dev/null
+++ b/test/file/tls/cert.pem
@@ -0,0 +1,30 @@
+-----BEGIN CERTIFICATE-----
+MIIFJTCCAw2gAwIBAgIUIGyavTA8+spUJ/Tb3gHmTnwXOI0wDQYJKoZIhvcNAQEL
+BQAwFDESMBAGA1UEAwwJbG9jYWxob3N0MB4XDTIxMDEwMzIwNTU1MFoXDTMxMDEw
+MTIwNTU1MFowFDESMBAGA1UEAwwJbG9jYWxob3N0MIICIjANBgkqhkiG9w0BAQEF
+AAOCAg8AMIICCgKCAgEApHfLDSqc2+kdAW8LF8Z1z68VriWEJ+wNkS7rCm8AICe/
+vWYfywWDv4Kw0vzPgoGe7wmURYSS7r5i7MhFzqH4bf0B6wCIFWw2VwFqhgB94hWA
+ellyIYC5ntV8RBT1RYbqSsBEJR5HKA/hFdiAf5gSIiZNnjtNT5JJLROlufD30IUQ
+7teHyrbCF7/JYeVFrdisRBZg6XgMvHX6sXR4BaLkLTl6YBEe+vWkkOd9lN4pnTzG
+PXSxdg4sG4QbCWsANv4bJnWuPC4rd3a/AKqonXumtBErFqluv3ylM6VY8R5E6DrQ
+Wk7wjLphUXPsi8gkMAO4SY4/+CI26jFbk/5HI2baQ90jm8AOoUG6erEu5OU3a7dR
+9uxIqebd9+S9FCxU9dstYiMbRcmG699Wi/mF/taWna1CdOqw7l8WRXp9q0CTqaVe
+a9HdhYiSsimhOttNBD02seyKUQtUw8bw3TUeZofglqcjiP06DGH959z25NjMkiC6
+b745QZ3LfzbPpSP3Ax9heHlti0Pl5LnCOIIdyk3DPDz1/xshDTXS2d18k+wpW0O0
+rqOydCwzMTJjsHYYpOcqC93qfHuX/NEqNLbnOfM8SEZWqaM6ueW7tAoQc+BaHA8i
+AWtgtMhFuaDrHifeIIm8FLXdyXQChL8WhpeyZa3GeSnUdCVzl+2gjSQZpaGO/fEC
+AwEAAaNvMG0wHQYDVR0OBBYEFDi1Q63Txpc4NAbFf1HXlWgm+vlJMB8GA1UdIwQY
+MBaAFDi1Q63Txpc4NAbFf1HXlWgm+vlJMA8GA1UdEwEB/wQFMAMBAf8wGgYDVR0R
+BBMwEYIJbG9jYWxob3N0hwR/AAABMA0GCSqGSIb3DQEBCwUAA4ICAQAuOwG6NFMh
+VI7ipUVsXSOBX+qTd5FnOCCTyV5UixgcDAnEZ87poH0ehI7IOia4xnhUXQsJTTaq
+ho/S2hXeRuyq5Sn0RP3Zqa5IzULJWr9hQ5ajc4VuYxzhsh72gzWTJRKZRZBIL5jt
+VrkS6qFG4QY4i8IAzfwDiJLhl21iEv23idbBE+yUwJNFnnuMxyllOo7fMFwB3Wij
+maEaAamLd2Ocv38bstimCqtKVwg5sWMdaeELzyPtY0HnCNr1ILTW4+wMjgKwUE5z
+rnwgF2d03eV2xaNY8asl5mMzJbKpz5CfD6r8KAZ2ZQM8QN+demmexZU8tMp0X3Hz
+nVlBu48Ecuv5XTU1F8S2a0UoRoadIw2AtydFW6V31XtAivwrhIsz8xVkE9XiJ8c0
+QpMw7bPPeQieFiSAUnRThd/5qdzeep7Vu/CtTht85rT7aqTPdPHiWUdL6x7M1Mi8
+vatHGI0cvhAMXFNKAuvCFknEhUCwAKwIHfoWGsa08Kn4LhACLOd67tA45+Rdz1m5
+w6Ea1zO8bfhUr/3qxod+nr9kAuN5pN3JELzIwIYGuEsp7OGWr0Wc2QRKMlDcTTIU
+Xmd29Vcl5F3OwbBUmoJIqUKsENKJ0jG0DuW1svBnpRcs5wVQzPCDV/fPm3pe0c2c
+KFYDk2Qa1jY9BZVyVEFJdwfVlh5iZwu0Iw==
+-----END CERTIFICATE-----
diff --git a/test/file/tls/gen_cert.sh b/test/file/tls/gen_cert.sh
new file mode 100755
index 00000000..7126bfe9
--- /dev/null
+++ b/test/file/tls/gen_cert.sh
@@ -0,0 +1,2 @@
+#!/usr/bin/bash
+openssl req -x509 -newkey rsa:4096 -keyout key.pem -nodes -out cert.pem -days 3650 -subj /CN=localhost -addext "subjectAltName=DNS:localhost,IP:127.0.0.1"
diff --git a/test/file/tls/key.pem b/test/file/tls/key.pem
new file mode 100644
index 00000000..6f9a9570
--- /dev/null
+++ b/test/file/tls/key.pem
@@ -0,0 +1,52 @@
+-----BEGIN PRIVATE KEY-----
+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
+lEgQcwoqtWDAeenVEKdo2GiojTqk9g==
+-----END PRIVATE KEY-----
diff --git a/test/integration/index.js b/test/integration/index.js
index 78add106..28bb8c00 100644
--- a/test/integration/index.js
+++ b/test/integration/index.js
@@ -134,9 +134,10 @@ before(async function() {
await exec("node ../../packages/master config set master.auth_secret TestSecretDoNotUse");
await exec("node ../../packages/master config set master.http_port 8880");
await exec("node ../../packages/master config set master.https_port 4443");
- await exec("node ../../packages/master config set master.tls_bits 1024");
await exec("node ../../packages/master config set master.heartbeat_interval 0.25");
await exec("node ../../packages/master config set master.connector_shutdown_timeout 2");
+ await exec("node ../../packages/master config set master.tls_certificate ../../test/file/tls/cert.pem");
+ await exec("node ../../packages/master config set master.tls_private_key ../../test/file/tls/key.pem");
await exec("node ../../packages/ctl plugin add ../../plugins/global_chat");
await exec("node ../../packages/ctl plugin add ../../plugins/research_sync");
@@ -145,16 +146,18 @@ before(async function() {
await exec("node ../../packages/master bootstrap create-admin test");
await exec("node ../../packages/master bootstrap create-ctl-config test");
+ await exec("node ../../packages/ctl control-config set control.tls_ca ../../test/file/tls/cert.pem");
masterProcess = await spawn("master:", "node ../../packages/master run", /All plugins loaded/);
- let createArgs = "--id 4 --name slave --generate-token";
- await execCtl(`slave create-config ${createArgs}`);
-
+ await execCtl("slave create-config --id 4 --name slave --generate-token");
await exec(`node ../../packages/slave config set slave.factorio_directory ${path.join("..", "..", "factorio")}`);
+ await exec("node ../../packages/slave config set slave.tls_ca ../../test/file/tls/cert.pem");
+
slaveProcess = await spawn("slave:", "node ../../packages/slave run", /SOCKET \| registering slave/);
- let controlConnector = new TestControlConnector(url, 2);
+ let tlsCa = await fs.readFile("test/file/tls/cert.pem");
+ let controlConnector = new TestControlConnector(url, 2, tlsCa);
controlConnector.token = controlToken;
control = new TestControl(controlConnector);
await controlConnector.connect();
diff --git a/test/integration/link.js b/test/integration/link.js
index dec7133f..731fef97 100644
--- a/test/integration/link.js
+++ b/test/integration/link.js
@@ -1,6 +1,7 @@
"use strict";
const assert = require("assert").strict;
const events = require("events");
+const fs = require("fs-extra");
const jwt = require("jsonwebtoken");
const { TestControlConnector, TestControl, get, exec, url } = require("./index");
@@ -8,10 +9,14 @@ const { TestControlConnector, TestControl, get, exec, url } = require("./index")
let token = jwt.sign({ aud: "user", user: "test" }, "TestSecretDoNotUse");
describe("Integration of lib/link", function() {
+ let tlsCa;
let control;
let controlConnector;
+ before(async function() {
+ tlsCa = await fs.readFile("test/file/tls/cert.pem");
+ });
it("should emitt an error if authentication failed", async function() {
- controlConnector = new TestControlConnector(url, 0.2);
+ controlConnector = new TestControlConnector(url, 0.2, tlsCa);
controlConnector.token = "gibberish";
control = new TestControl(controlConnector);
await assert.rejects(
@@ -21,7 +26,7 @@ describe("Integration of lib/link", function() {
});
it("should connect with proper credentials", async function() {
- controlConnector = new TestControlConnector(url, 0.2);
+ controlConnector = new TestControlConnector(url, 0.2, tlsCa);
controlConnector.token = token;
control = new TestControl(controlConnector);
await controlConnector.connect();
diff --git a/test/master/generate_ssl_cert.js b/test/master/generate_ssl_cert.js
deleted file mode 100644
index 67edf23e..00000000
--- a/test/master/generate_ssl_cert.js
+++ /dev/null
@@ -1,33 +0,0 @@
-"use strict";
-const assert = require("assert");
-const fs = require("fs-extra");
-const path = require("path");
-
-const generateSslCert = require("@clusterio/master/src/generate_ssl_cert");
-
-describe("generate_ssl_cert", function() {
- let testDir = path.join("temp", "test", "certTest");
- before(async function() {
- await fs.remove(testDir);
- });
-
- it("Creates a folder with a .crt and .key file in it", async function() {
- this.timeout(1000);
- // eslint-disable-next-line no-process-env
- if (process.env.FAST_TEST) {
- this.skip();
- }
-
- let certPath = path.join(testDir, "cert.crt");
- let privKeyPath = path.join(testDir, "cert.key");
-
- await generateSslCert({
- bits: 512, // This is too small for real world usage, but faster to test
- sslCertPath: certPath,
- sslPrivKeyPath: privKeyPath,
- });
-
- assert(await fs.exists(certPath), "cert file was not created");
- assert(await fs.exists(privKeyPath), "key file was not created");
- });
-});