From cb24c4236a7f02efb949a9ae9a44e2fa36cc00bd Mon Sep 17 00:00:00 2001 From: Ian Denhardt Date: Fri, 20 May 2022 00:24:43 -0400 Subject: [PATCH] Allow multiple values for HTTPS_PORT Fixes #3619 --- docs/administering/config-file.md | 16 ++++----- src/sandstorm/config.c++ | 56 ++++++++++--------------------- src/sandstorm/config.h | 2 +- src/sandstorm/run-bundle.c++ | 27 +++++++++------ 4 files changed, 42 insertions(+), 59 deletions(-) diff --git a/docs/administering/config-file.md b/docs/administering/config-file.md index 0e5c4363c..039caee54 100644 --- a/docs/administering/config-file.md +++ b/docs/administering/config-file.md @@ -51,11 +51,14 @@ typically a static publishing website, then serve a normal response. ### HTTPS_PORT -A port number for Sandstorm to bind on and listen for HTTPS. On a default install, if port 443 -was available and the user chose to use Sandcats, this is 443. However, this may be set for any -Sandstorm-managed TLS configuration, including automated renewals of certificates with Let's Encrypt -with a supported DNS provider or a manually-uploaded certificate. If this config option is missing, -Sandstorm's built-in HTTPS server is disabled. +Like `PORT`, but these ports are served using HTTPS. If both `PORT` and `HTTPS_PORT` are specified, +the first `HTTPS_PORT` "First port" as described above, and all values in `PORT` are "alternate +ports." + +On a default install, if port 443 was available and the user chose to use Sandcats, this is 443. +However, this may be set for any Sandstorm-managed TLS configuration, including automated renewals +of certificates with Let's Encrypt with a supported DNS provider or a manually-uploaded certificate. +If this config option is missing, Sandstorm's built-in HTTPS server is disabled. If Sandstorm is started as root, Sandstorm binds to this port as root, allowing it to use low-numbered ports. The socket is passed-through to code that does not run as root. @@ -66,9 +69,6 @@ Example: HTTPS_PORT=443 ``` -A HTTPS_PORT is automatically treated as the first port, in the context of "first port" vs. -"alternate ports." - ### SMTP_LISTEN_PORT A port number on which Sandstorm will bind, listening for inbound email. By default, 30025; if diff --git a/src/sandstorm/config.c++ b/src/sandstorm/config.c++ index d7d037809..8e99005a6 100644 --- a/src/sandstorm/config.c++ +++ b/src/sandstorm/config.c++ @@ -70,31 +70,17 @@ auto parser = p::sequence(delimited<' '>(assignment), p::discardWhitespace, p::e // ======================================================================================= -kj::Array parsePorts(kj::Maybe httpsPort, kj::StringPtr portList) { - auto portsSplitOnComma = split(portList, ','); - size_t numHttpPorts = portsSplitOnComma.size(); - size_t numHttpsPorts; - kj::Array result; - - // If the configuration has a https port, then add it first. - KJ_IF_MAYBE(portNumber, httpsPort) { - numHttpsPorts = 1; - result = kj::heapArray(numHttpsPorts + numHttpPorts); - result[0] = *portNumber; - } else { - numHttpsPorts = 0; - result = kj::heapArray(numHttpsPorts + numHttpPorts); - } - +kj::Array parsePortList(kj::StringPtr key, kj::StringPtr portListStr) { + auto portsSplitOnComma = split(portListStr, ','); + auto result = kj::heapArrayBuilder(portsSplitOnComma.size()); for (size_t i = 0; i < portsSplitOnComma.size(); i++) { KJ_IF_MAYBE(portNumber, parseUInt(trim(portsSplitOnComma[i]), 10)) { - result[i + numHttpsPorts] = *portNumber; + result.add(*portNumber); } else { - KJ_FAIL_REQUIRE("invalid config value PORT", portList); + KJ_FAIL_REQUIRE("invalid config value ", key, portListStr); } } - - return kj::mv(result); + return result.finish(); } kj::Maybe getUserIds(kj::StringPtr name) { @@ -170,10 +156,6 @@ Config readConfig(const char *path, bool parseUids) { config.uids.uid = getuid(); config.uids.gid = getgid(); - // Store the PORT and HTTPS_PORT values in variables here so we can - // process them at the end. - kj::Maybe maybePortValue = nullptr; - auto lines = splitLines(readAll(path)); for (auto& line: lines) { auto equalsPos = KJ_ASSERT_NONNULL(line.findFirst('='), "Invalid config line", line); @@ -190,13 +172,9 @@ Config readConfig(const char *path, bool parseUids) { } } } else if (key == "HTTPS_PORT") { - KJ_IF_MAYBE(p, parseUInt(value, 10)) { - config.httpsPort = *p; - } else { - KJ_FAIL_REQUIRE("invalid config value HTTPS_PORT", value); - } + config.httpsPorts = parsePortList(key, value); } else if (key == "PORT") { - maybePortValue = kj::mv(value); + config.ports = parsePortList(key, value); } else if (key == "MONGO_PORT") { KJ_IF_MAYBE(p, parseUInt(value, 10)) { config.mongoPort = *p; @@ -280,16 +258,16 @@ Config readConfig(const char *path, bool parseUids) { } } - // Now process the PORT setting, since the actual value in config.ports - // depends on if HTTPS_PORT was provided at any point in reading the - // config file. - // - // Outer KJ_IF_MAYBE so we only run this code if the config file contained - // a PORT= declaration. - KJ_IF_MAYBE(portValue, maybePortValue) { - auto ports = parsePorts(config.httpsPort, *portValue); - config.ports = kj::mv(ports); + // config.ports should actually include the HTTPS_PORTs as well + // (and they should come first): + auto allPorts = kj::heapArrayBuilder(config.ports.size() + config.httpsPorts.size()); + for(uint port : config.httpsPorts) { + allPorts.add(port); + } + for(uint port : config.ports) { + allPorts.add(port); } + config.ports = allPorts.finish(); return config; } diff --git a/src/sandstorm/config.h b/src/sandstorm/config.h index 38e64d169..ec1fd5d46 100644 --- a/src/sandstorm/config.h +++ b/src/sandstorm/config.h @@ -12,7 +12,7 @@ struct UserIds { }; struct Config { - kj::Maybe httpsPort; + kj::Array httpsPorts; kj::Array ports; uint mongoPort = 3001; UserIds uids; diff --git a/src/sandstorm/run-bundle.c++ b/src/sandstorm/run-bundle.c++ index 18ad4d920..e14662fbf 100644 --- a/src/sandstorm/run-bundle.c++ +++ b/src/sandstorm/run-bundle.c++ @@ -2509,15 +2509,22 @@ private: KJ_FAIL_REQUIRE("backend died; gateway aborting too"); })); + auto isHttpsPort = [&](uint port) -> bool { + for(uint httpsPort : config.httpsPorts) { + if (httpsPort == port) { + return true; + } + } + return false; + }; + // Listen on main port. if (config.ports.size() > 0) { auto port = config.ports[0]; auto listener = fdBundle.consume(port, *io.lowLevelProvider); - bool isHttps = false; - KJ_IF_MAYBE(p, config.httpsPort) { - isHttps = port == *p; - } - auto promise = isHttps ? tlsManager.listenHttps(*listener) : server.listenHttp(*listener); + auto promise = isHttpsPort(port) + ? tlsManager.listenHttps(*listener) + : server.listenHttp(*listener); promises = promises.exclusiveJoin(promise.attach(kj::mv(listener))); } @@ -2530,7 +2537,9 @@ private: altPortServer = altPortServer.attach(kj::mv(altPortService)); for (auto port: config.ports.slice(1, config.ports.size())) { auto listener = fdBundle.consume(port, *io.lowLevelProvider); - auto promise = altPortServer->listenHttp(*listener); + auto promise = isHttpsPort(port) + ? tlsManager.listenHttps(*listener) + : altPortServer->listenHttp(*listener); promises = promises.exclusiveJoin(promise.attach(kj::mv(listener))); } promises = promises.attach(kj::mv(altPortServer)); @@ -2611,10 +2620,6 @@ private: KJ_SYSCALL(setenv("HTTP_GATEWAY", "local", true)); KJ_SYSCALL(setenv("PORT", kj::str(config.ports[0]).cStr(), true)); - KJ_IF_MAYBE(httpsPort, config.httpsPort) { - // TODO(cleanup): At this point, all this does is tell Sandcats to refresh certs. - KJ_SYSCALL(setenv("HTTPS_PORT", kj::str(*httpsPort).cStr(), true)); - } KJ_SYSCALL(setenv("MONGO_URL", kj::str("mongodb://", authPrefix, "127.0.0.1:", config.mongoPort, @@ -2628,7 +2633,7 @@ private: kj::StringPtr scheme; uint defaultPort; - if (config.httpsPort == nullptr) { + if (config.httpsPorts.size() == 0) { scheme = "http://"; defaultPort = 80; } else {