Loading README.md +2 −0 Changes for README.md: 2 added lines, 0 removed lines. Original line number Diff line number Diff line Loading @@ -14,6 +14,8 @@ HTTP is available without TLS configuration. To enable HTTPS, set both ## Master/slave replication A master can feed any number of read-only slaves. Master and slave can each be a single node (file backend) or a cluster (`FENRIRDS/CLUSTER/ENABLE`). A cluster always has at least three nodes in `CLUSTER/PEERS` (this node included); fenrirds refuses to start, and the web interface to save, with fewer. Slaves poll the master over the normal fenrirds HTTP(S) port. The master publishes a content hash per domain (admin db and every domain); a slave Loading data/serverconfig.js +1 −1 Changes for data/serverconfig.js: 1 added line, 1 removed line. Original line number Diff line number Diff line Loading @@ -49,7 +49,7 @@ var ServerConfig = (function() { { key: "CLUSTER/PARITY_BLOCKS", label: "Parity blocks", type: "number" }, { key: "CLUSTER/SSLVERIFY", label: "Verify TLS", type: "bool" }, { key: "CLUSTER/PEERS", label: "Peers", type: "list", hint: "one ip:port per line, this node included" } hint: "one ip:port per line, this node included; at least 3 nodes" } ]}, { title: "Maintenance", fields: [ { key: "VACUUM_INTERVAL", label: "Vacuum interval (h)", type: "number" } Loading src/CMakeLists.txt +1 −0 Changes for src/CMakeLists.txt: 1 added line, 0 removed lines. Original line number Diff line number Diff line Loading @@ -81,6 +81,7 @@ add_executable(fenrirds fenrirds.cpp mdns.cpp nodejoin.cpp selfcert.cpp api.cpp log.cpp ) Loading src/confhelper.cpp +83 −56 Changes for src/confhelper.cpp: 83 added lines, 56 removed lines. Original line number Diff line number Diff line Loading @@ -39,6 +39,7 @@ #include <confplus/conf.h> #include "confhelper.h" #include "selfcert.h" #ifndef _WIN32 #include <unistd.h> Loading Loading @@ -129,6 +130,7 @@ namespace fenrirds::confhelper { " CLUSTER:\n" " ENABLE: \"false\"\n" " PATH: \"" STATEPATH "/cluster\"\n" " SSLVERIFY: \"false\"\n" " KDC:\n" " BIND: \"0.0.0.0\"\n" " PORT: 88\n" Loading @@ -137,9 +139,11 @@ namespace fenrirds::confhelper { " PORT: 389\n" " MAXCONN: 1024\n" " REPLICATION:\n" " ROLE: \"none\"\n"; " ROLE: \"none\"\n" " SSLVERIFY: \"false\"\n"; const size_t MAXMSG = 65536; const size_t MIN_CLUSTER_NODES = 3; std::string specBackend(const std::string &spec) { size_t d = spec.find(':'); Loading Loading @@ -220,9 +224,10 @@ namespace fenrirds::confhelper { return false; } #ifndef _WIN32 /* a missing file is generated, see generateCertificate() */ struct stat st; if (stat(v.c_str(), &st) != 0 || !S_ISREG(st.st_mode)) { err = v + ": no such file"; if (stat(v.c_str(), &st) == 0 && !S_ISREG(st.st_mode)) { err = v + ": not a file"; return false; } #endif Loading Loading @@ -437,60 +442,13 @@ namespace fenrirds::confhelper { return jres; } #ifndef _WIN32 bool runProgram(const std::vector<std::string> &argv) { pid_t pid = fork(); if (pid < 0) return false; if (pid == 0) { std::vector<char *> args; for (const auto &a : argv) args.push_back(const_cast<char *>(a.c_str())); args.push_back(nullptr); execvp(args[0], args.data()); _exit(127); } int status = 0; while (waitpid(pid, &status, 0) < 0 && errno == EINTR) {} return WIFEXITED(status) && WEXITSTATUS(status) == 0; } #endif /* a self-signed certificate in <config dir>/ssl (DER, as the * Debian package creates it) unless one is there already */ /* the default certificate in <config dir>/ssl, generated if missing */ void ensureCertificate(const std::string &confDir, std::string &cert, std::string &key) { std::string dir = confDir + "/ssl"; cert = dir + "/cert.der"; key = dir + "/key.der"; #ifndef _WIN32 struct stat st; if (stat(cert.c_str(), &st) == 0 && stat(key.c_str(), &st) == 0) return; if (stat(confDir.c_str(), &st) != 0 && mkdir(confDir.c_str(), 0755) != 0) throw std::runtime_error("mkdir " + confDir + ": " + strerror(errno)); if (stat(dir.c_str(), &st) != 0 && mkdir(dir.c_str(), 0700) != 0) throw std::runtime_error("mkdir " + dir + ": " + strerror(errno)); char host[256] = "fenrirds"; gethostname(host, sizeof(host) - 1); std::string kpem = dir + "/key.pem.tmp", cpem = dir + "/cert.pem.tmp"; bool ok = runProgram({ "openssl", "req", "-x509", "-newkey", "rsa:4096", "-nodes", "-keyout", kpem, "-out", cpem, "-days", "3650", "-subj", std::string("/CN=") + host + "/O=fenrirds/OU=auto-generated" }) && runProgram({ "openssl", "x509", "-in", cpem, "-outform", "DER", "-out", cert }) && runProgram({ "openssl", "rsa", "-in", kpem, "-outform", "DER", "-out", key }); ::unlink(kpem.c_str()); ::unlink(cpem.c_str()); if (!ok) { ::unlink(cert.c_str()); ::unlink(key.c_str()); throw std::runtime_error("generating a TLS certificate with openssl failed"); } chmod(key.c_str(), 0600); chmod(cert.c_str(), 0644); std::cerr << "[CONFHELPER] generated self-signed certificate " << cert << std::endl; #else throw std::runtime_error("certificate generation is not supported on this platform"); #endif cert = confDir + "/ssl/cert.der"; key = confDir + "/ssl/key.der"; std::string err; if (!generateCertificate(cert, key, err)) throw std::runtime_error(err); } /* validates one field; on success vals holds what goes into the file Loading Loading @@ -627,6 +585,17 @@ namespace fenrirds::confhelper { else cf.put(f.key, vals); } /* a cluster needs at least three nodes (this one included) */ if (cf.values("CLUSTER/ENABLE") == std::vector<std::string>{ "true" } && cf.values("CLUSTER/PEERS").size() < MIN_CLUSTER_NODES) { err = "CLUSTER/PEERS: a cluster needs at least " + std::to_string(MIN_CLUSTER_NODES) + " nodes, this one included"; return nullptr; } /* a certificate/key that is configured but not there yet */ std::vector<std::string> cert = cf.values("SSL/CERT"), key = cf.values("SSL/KEY"); if (!cert.empty() && !key.empty() && !generateCertificate(cert[0], key[0], err)) return nullptr; cf.save(); json_object *jres = json_object_new_object(); json_object_object_add(jres, "ok", json_object_new_boolean(true)); Loading Loading @@ -828,6 +797,64 @@ namespace fenrirds::confhelper { #endif } bool generateCertificate(const std::string &cert, const std::string &key, std::string &err) { #ifndef _WIN32 struct stat st; if (stat(cert.c_str(), &st) == 0 && stat(key.c_str(), &st) == 0) return true; /* creates the directories of both files (the key's private) */ auto mkdirs = [&err](const std::string &dir, mode_t mode) { for (size_t p = 1; p != std::string::npos; ) { p = dir.find('/', p + 1); std::string d = dir.substr(0, p); struct stat ds; if (stat(d.c_str(), &ds) != 0 && mkdir(d.c_str(), p == std::string::npos ? mode : 0755) != 0) { err = "mkdir " + d + ": " + strerror(errno); return false; } } return true; }; if (!mkdirs(parentDir(cert), 0755) || !mkdirs(parentDir(key), 0700)) return false; char host[256] = "fenrirds"; gethostname(host, sizeof(host) - 1); selfcert::Result sc; try { sc = selfcert::create(host); } catch (std::exception &e) { err = std::string("generating a TLS certificate failed: ") + e.what(); return false; } /* key first and private from the start: write to .tmp, rename */ auto writeFile = [&err](const std::string &path, const std::vector<uint8_t> &data, mode_t mode) { std::string tmp = path + ".tmp"; int fd = ::open(tmp.c_str(), O_WRONLY | O_CREAT | O_TRUNC | O_CLOEXEC, mode); bool ok = fd >= 0 && ::write(fd, data.data(), data.size()) == (ssize_t)data.size(); if (fd >= 0) { ok = ok && ::fchmod(fd, mode) == 0; ::close(fd); } if (!ok || ::rename(tmp.c_str(), path.c_str()) != 0) { err = "writing " + path + ": " + strerror(errno); ::unlink(tmp.c_str()); return false; } return true; }; if (!writeFile(key, sc.key, 0600) || !writeFile(cert, sc.cert, 0644)) { ::unlink(key.c_str()); return false; } std::cerr << "[FENRIRDS] generated self-signed certificate " << cert << " / " << key << std::endl; return true; #else (void)cert; (void)key; err = "certificate generation is not supported on this platform"; return false; #endif } void start(const std::string &configSpec, const std::vector<std::string> &restartArgv) { #ifndef _WIN32 int sv[2]; Loading src/confhelper.h +5 −0 Changes for src/confhelper.h: 5 added lines, 0 removed lines. Original line number Diff line number Diff line Loading @@ -50,6 +50,11 @@ namespace fenrirds::confhelper { * stays false. */ void start(const std::string &configSpec, const std::vector<std::string> &restartArgv); /* Creates a self-signed certificate (DER, RSA, libnetplus) at * cert/key unless both files exist. Needs write access there: the * helper, or the server while it is still root. */ bool generateCertificate(const std::string &cert, const std::string &key, std::string &err); /* true in the server once start() forked the helper */ bool available(); Loading Loading
README.md +2 −0 Changes for README.md: 2 added lines, 0 removed lines. Original line number Diff line number Diff line Loading @@ -14,6 +14,8 @@ HTTP is available without TLS configuration. To enable HTTPS, set both ## Master/slave replication A master can feed any number of read-only slaves. Master and slave can each be a single node (file backend) or a cluster (`FENRIRDS/CLUSTER/ENABLE`). A cluster always has at least three nodes in `CLUSTER/PEERS` (this node included); fenrirds refuses to start, and the web interface to save, with fewer. Slaves poll the master over the normal fenrirds HTTP(S) port. The master publishes a content hash per domain (admin db and every domain); a slave Loading
data/serverconfig.js +1 −1 Changes for data/serverconfig.js: 1 added line, 1 removed line. Original line number Diff line number Diff line Loading @@ -49,7 +49,7 @@ var ServerConfig = (function() { { key: "CLUSTER/PARITY_BLOCKS", label: "Parity blocks", type: "number" }, { key: "CLUSTER/SSLVERIFY", label: "Verify TLS", type: "bool" }, { key: "CLUSTER/PEERS", label: "Peers", type: "list", hint: "one ip:port per line, this node included" } hint: "one ip:port per line, this node included; at least 3 nodes" } ]}, { title: "Maintenance", fields: [ { key: "VACUUM_INTERVAL", label: "Vacuum interval (h)", type: "number" } Loading
src/CMakeLists.txt +1 −0 Changes for src/CMakeLists.txt: 1 added line, 0 removed lines. Original line number Diff line number Diff line Loading @@ -81,6 +81,7 @@ add_executable(fenrirds fenrirds.cpp mdns.cpp nodejoin.cpp selfcert.cpp api.cpp log.cpp ) Loading
src/confhelper.cpp +83 −56 Changes for src/confhelper.cpp: 83 added lines, 56 removed lines. Original line number Diff line number Diff line Loading @@ -39,6 +39,7 @@ #include <confplus/conf.h> #include "confhelper.h" #include "selfcert.h" #ifndef _WIN32 #include <unistd.h> Loading Loading @@ -129,6 +130,7 @@ namespace fenrirds::confhelper { " CLUSTER:\n" " ENABLE: \"false\"\n" " PATH: \"" STATEPATH "/cluster\"\n" " SSLVERIFY: \"false\"\n" " KDC:\n" " BIND: \"0.0.0.0\"\n" " PORT: 88\n" Loading @@ -137,9 +139,11 @@ namespace fenrirds::confhelper { " PORT: 389\n" " MAXCONN: 1024\n" " REPLICATION:\n" " ROLE: \"none\"\n"; " ROLE: \"none\"\n" " SSLVERIFY: \"false\"\n"; const size_t MAXMSG = 65536; const size_t MIN_CLUSTER_NODES = 3; std::string specBackend(const std::string &spec) { size_t d = spec.find(':'); Loading Loading @@ -220,9 +224,10 @@ namespace fenrirds::confhelper { return false; } #ifndef _WIN32 /* a missing file is generated, see generateCertificate() */ struct stat st; if (stat(v.c_str(), &st) != 0 || !S_ISREG(st.st_mode)) { err = v + ": no such file"; if (stat(v.c_str(), &st) == 0 && !S_ISREG(st.st_mode)) { err = v + ": not a file"; return false; } #endif Loading Loading @@ -437,60 +442,13 @@ namespace fenrirds::confhelper { return jres; } #ifndef _WIN32 bool runProgram(const std::vector<std::string> &argv) { pid_t pid = fork(); if (pid < 0) return false; if (pid == 0) { std::vector<char *> args; for (const auto &a : argv) args.push_back(const_cast<char *>(a.c_str())); args.push_back(nullptr); execvp(args[0], args.data()); _exit(127); } int status = 0; while (waitpid(pid, &status, 0) < 0 && errno == EINTR) {} return WIFEXITED(status) && WEXITSTATUS(status) == 0; } #endif /* a self-signed certificate in <config dir>/ssl (DER, as the * Debian package creates it) unless one is there already */ /* the default certificate in <config dir>/ssl, generated if missing */ void ensureCertificate(const std::string &confDir, std::string &cert, std::string &key) { std::string dir = confDir + "/ssl"; cert = dir + "/cert.der"; key = dir + "/key.der"; #ifndef _WIN32 struct stat st; if (stat(cert.c_str(), &st) == 0 && stat(key.c_str(), &st) == 0) return; if (stat(confDir.c_str(), &st) != 0 && mkdir(confDir.c_str(), 0755) != 0) throw std::runtime_error("mkdir " + confDir + ": " + strerror(errno)); if (stat(dir.c_str(), &st) != 0 && mkdir(dir.c_str(), 0700) != 0) throw std::runtime_error("mkdir " + dir + ": " + strerror(errno)); char host[256] = "fenrirds"; gethostname(host, sizeof(host) - 1); std::string kpem = dir + "/key.pem.tmp", cpem = dir + "/cert.pem.tmp"; bool ok = runProgram({ "openssl", "req", "-x509", "-newkey", "rsa:4096", "-nodes", "-keyout", kpem, "-out", cpem, "-days", "3650", "-subj", std::string("/CN=") + host + "/O=fenrirds/OU=auto-generated" }) && runProgram({ "openssl", "x509", "-in", cpem, "-outform", "DER", "-out", cert }) && runProgram({ "openssl", "rsa", "-in", kpem, "-outform", "DER", "-out", key }); ::unlink(kpem.c_str()); ::unlink(cpem.c_str()); if (!ok) { ::unlink(cert.c_str()); ::unlink(key.c_str()); throw std::runtime_error("generating a TLS certificate with openssl failed"); } chmod(key.c_str(), 0600); chmod(cert.c_str(), 0644); std::cerr << "[CONFHELPER] generated self-signed certificate " << cert << std::endl; #else throw std::runtime_error("certificate generation is not supported on this platform"); #endif cert = confDir + "/ssl/cert.der"; key = confDir + "/ssl/key.der"; std::string err; if (!generateCertificate(cert, key, err)) throw std::runtime_error(err); } /* validates one field; on success vals holds what goes into the file Loading Loading @@ -627,6 +585,17 @@ namespace fenrirds::confhelper { else cf.put(f.key, vals); } /* a cluster needs at least three nodes (this one included) */ if (cf.values("CLUSTER/ENABLE") == std::vector<std::string>{ "true" } && cf.values("CLUSTER/PEERS").size() < MIN_CLUSTER_NODES) { err = "CLUSTER/PEERS: a cluster needs at least " + std::to_string(MIN_CLUSTER_NODES) + " nodes, this one included"; return nullptr; } /* a certificate/key that is configured but not there yet */ std::vector<std::string> cert = cf.values("SSL/CERT"), key = cf.values("SSL/KEY"); if (!cert.empty() && !key.empty() && !generateCertificate(cert[0], key[0], err)) return nullptr; cf.save(); json_object *jres = json_object_new_object(); json_object_object_add(jres, "ok", json_object_new_boolean(true)); Loading Loading @@ -828,6 +797,64 @@ namespace fenrirds::confhelper { #endif } bool generateCertificate(const std::string &cert, const std::string &key, std::string &err) { #ifndef _WIN32 struct stat st; if (stat(cert.c_str(), &st) == 0 && stat(key.c_str(), &st) == 0) return true; /* creates the directories of both files (the key's private) */ auto mkdirs = [&err](const std::string &dir, mode_t mode) { for (size_t p = 1; p != std::string::npos; ) { p = dir.find('/', p + 1); std::string d = dir.substr(0, p); struct stat ds; if (stat(d.c_str(), &ds) != 0 && mkdir(d.c_str(), p == std::string::npos ? mode : 0755) != 0) { err = "mkdir " + d + ": " + strerror(errno); return false; } } return true; }; if (!mkdirs(parentDir(cert), 0755) || !mkdirs(parentDir(key), 0700)) return false; char host[256] = "fenrirds"; gethostname(host, sizeof(host) - 1); selfcert::Result sc; try { sc = selfcert::create(host); } catch (std::exception &e) { err = std::string("generating a TLS certificate failed: ") + e.what(); return false; } /* key first and private from the start: write to .tmp, rename */ auto writeFile = [&err](const std::string &path, const std::vector<uint8_t> &data, mode_t mode) { std::string tmp = path + ".tmp"; int fd = ::open(tmp.c_str(), O_WRONLY | O_CREAT | O_TRUNC | O_CLOEXEC, mode); bool ok = fd >= 0 && ::write(fd, data.data(), data.size()) == (ssize_t)data.size(); if (fd >= 0) { ok = ok && ::fchmod(fd, mode) == 0; ::close(fd); } if (!ok || ::rename(tmp.c_str(), path.c_str()) != 0) { err = "writing " + path + ": " + strerror(errno); ::unlink(tmp.c_str()); return false; } return true; }; if (!writeFile(key, sc.key, 0600) || !writeFile(cert, sc.cert, 0644)) { ::unlink(key.c_str()); return false; } std::cerr << "[FENRIRDS] generated self-signed certificate " << cert << " / " << key << std::endl; return true; #else (void)cert; (void)key; err = "certificate generation is not supported on this platform"; return false; #endif } void start(const std::string &configSpec, const std::vector<std::string> &restartArgv) { #ifndef _WIN32 int sv[2]; Loading
src/confhelper.h +5 −0 Changes for src/confhelper.h: 5 added lines, 0 removed lines. Original line number Diff line number Diff line Loading @@ -50,6 +50,11 @@ namespace fenrirds::confhelper { * stays false. */ void start(const std::string &configSpec, const std::vector<std::string> &restartArgv); /* Creates a self-signed certificate (DER, RSA, libnetplus) at * cert/key unless both files exist. Needs write access there: the * helper, or the server while it is still root. */ bool generateCertificate(const std::string &cert, const std::string &key, std::string &err); /* true in the server once start() forked the helper */ bool available(); Loading