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"); - }); -});