Commit abecc5ce authored by jan.koester's avatar jan.koester
Browse files

test

parent f7a7e9f6
Loading
Loading
Loading
Loading
+6 −0
Changes for CMakeLists.txt: 6 added lines, 0 removed lines.
Original line number Diff line number Diff line
@@ -98,6 +98,12 @@ install(TARGETS proxyplus-bootstrap RUNTIME DESTINATION bin)
set(PROXYPLUS_PLUGIN_INSTALL_DIR "${CMAKE_INSTALL_PREFIX}/lib/proxyplus/plugins" CACHE PATH
    "Directory plugin .so modules install into (must match paths.plugins in config.yaml)")

# proxyplus-bootstrap writes a missing config from the installed example, with paths.plugins
# set to where the plugins really are.
target_compile_definitions(proxyplus-bootstrap PRIVATE
    PROXYPLUS_EXAMPLE_CONFIG="${CMAKE_INSTALL_PREFIX}/share/proxyplus/examples/proxyplus.yaml"
    PROXYPLUS_PLUGIN_DIR="${PROXYPLUS_PLUGIN_INSTALL_DIR}")

add_subdirectory(plugins/example_dnsbl)
add_subdirectory(plugins/acme_common)
add_subdirectory(plugins/letsencrypt)
+111 −3
Changes for tools/proxyplus-bootstrap.cpp: 111 added lines, 3 removed lines.
Original line number Diff line number Diff line
@@ -13,9 +13,12 @@
//   4. Creates one GPO per permission in docs/ROLES.md's list, with those groups
//      resolved to their real ids as allowed members, and records permission->GPO-id into
//      ProxyPlus's own binary ConfigStore at <data-dir> (never into authdb itself).
//   5. Writes the bootstrap config (default /etc/proxyplus/proxyplus.yaml) if there is none
//      yet: the installed example config with paths.authdb, paths.data and paths.plugins
//      set for this node. An existing config is left alone.
//
// Usage:
//   proxyplus-bootstrap [--insecure] [--client-name <name>]
//   proxyplus-bootstrap [--insecure] [--client-name <name>] [--config <path>]
//                       <authdb-url> <admin-username> <admin-password> <data-dir>
//
// The service client is named "proxyplus-service" unless --client-name says otherwise.
@@ -24,9 +27,11 @@

#include <algorithm>
#include <cctype>
#include <filesystem>
#include <fstream>
#include <iostream>
#include <map>
#include <sstream>
#include <string>
#include <vector>

@@ -142,6 +147,102 @@ std::string askOtherClientName(const std::string &taken) {
    return name;
}

#ifndef PROXYPLUS_EXAMPLE_CONFIG
#define PROXYPLUS_EXAMPLE_CONFIG "/usr/local/share/proxyplus/examples/proxyplus.yaml"
#endif
#ifndef PROXYPLUS_PLUGIN_DIR
#define PROXYPLUS_PLUGIN_DIR "/usr/local/lib/proxyplus/plugins"
#endif

const char *const kDefaultConfigPath = "/etc/proxyplus/proxyplus.yaml";

// YAML double-quoted scalar.
std::string yamlQuote(const std::string &s) {
    std::string out = "\"";
    for (char c : s) {
        if (c == '"' || c == '\\') out += '\\';
        out += c;
    }
    return out + "\"";
}

// The example config with the given proxyplus.paths entries set (values are YAML, already
// quoted where needed): an entry the example has is replaced in place -- only lines at
// exactly the paths block's child indentation, so the documentation comments and every
// other setting stay as they are -- and one it lacks is added at the end of that block.
std::string configFromExample(const std::string &example, const std::map<std::string, std::string> &paths) {
    std::istringstream in(example);
    std::ostringstream out;
    std::string line;
    bool inPaths = false;
    std::map<std::string, std::string> missing = paths;
    auto addMissing = [&]() {
        for (const auto &kv : missing) out << "    " << kv.first << ": " << kv.second << '\n';
        missing.clear();
    };
    while (std::getline(in, line)) {
        size_t indent = line.find_first_not_of(' ');
        bool content = indent != std::string::npos && line[indent] != '#';
        if (content && line.compare(indent, 6, "paths:") == 0) {
            inPaths = true;
        } else if (content && inPaths && indent <= 2) {
            inPaths = false;
            addMissing();
        } else if (content && inPaths && indent == 4) {
            size_t colon = line.find(':', indent);
            std::string key = colon == std::string::npos ? "" : line.substr(indent, colon - indent);
            auto it = paths.find(key);
            if (it != paths.end()) {
                line = "    " + key + ": " + it->second;
                missing.erase(key);
            }
        }
        out << line << '\n';
    }
    if (inPaths) addMissing();
    return out.str();
}

// Writes `path` from the installed example config unless it already exists. Returns false
// only on an actual error; an existing config is a success (left untouched).
bool writeConfigIfMissing(const std::string &path, const std::string &authdbUrl,
                          const std::string &dataDir, bool authdbTlsVerify) {
    std::error_code ec;
    if (std::filesystem::exists(path, ec)) {
        std::cerr << "kept existing config " << path << std::endl;
        return true;
    }
    std::ifstream ex(PROXYPLUS_EXAMPLE_CONFIG);
    if (!ex) {
        std::cerr << "cannot write " << path << ": example config " << PROXYPLUS_EXAMPLE_CONFIG
                  << " not found" << std::endl;
        return false;
    }
    std::stringstream example;
    example << ex.rdbuf();

    std::map<std::string, std::string> paths = {
        {"data", yamlQuote(dataDir)},
        {"authdb", yamlQuote(authdbUrl)},
        {"plugins", yamlQuote(PROXYPLUS_PLUGIN_DIR)}};
    // bootstrapped with --insecure: ProxyPlus itself reaches authdb the same way
    if (!authdbTlsVerify) paths["authdb_tls_verify"] = "false";
    std::string config = configFromExample(example.str(), paths);

    std::filesystem::path p(path);
    if (p.has_parent_path()) std::filesystem::create_directories(p.parent_path(), ec);
    std::ofstream out(path, std::ios::trunc);
    out << config;
    out.close();
    if (!out) {
        std::cerr << "cannot write " << path << std::endl;
        return false;
    }
    std::cerr << "wrote " << path << " (from " << PROXYPLUS_EXAMPLE_CONFIG
              << "; review node, listen and process settings)" << std::endl;
    return true;
}

} // namespace

int main(int argc, char **argv) {
@@ -150,26 +251,31 @@ int main(int argc, char **argv) {
    std::vector<std::string> args;
    bool tlsVerify = true;
    std::string serviceClientName = "proxyplus-service";
    std::string configPath = kDefaultConfigPath;
    for (int i = 1; i < argc; ++i) {
        std::string arg = argv[i];
        if (arg == "--insecure") {
            tlsVerify = false;
        } else if (arg == "--client-name" && i + 1 < argc) {
            serviceClientName = argv[++i];
        } else if (arg == "--config" && i + 1 < argc) {
            configPath = argv[++i];
        } else {
            args.push_back(arg);
        }
    }

    if (args.size() < 4 || serviceClientName.empty()) {
    if (args.size() < 4 || serviceClientName.empty() || configPath.empty()) {
        std::cerr << "usage: " << argv[0]
                  << " [--insecure] [--client-name <name>]"
                  << " [--insecure] [--client-name <name>] [--config <path>]"
                     " <authdb-url> <admin-username> <admin-password> <data-dir>"
                  << std::endl;
        std::cerr << "  --insecure     skip TLS peer verification (needed when authdb is reached"
                     " by an IP not covered by its certificate's SAN)" << std::endl;
        std::cerr << "  --client-name  name of the service client to create in authdb"
                     " (default: proxyplus-service)" << std::endl;
        std::cerr << "  --config       bootstrap config to write if missing (default: "
                  << kDefaultConfigPath << ")" << std::endl;
        return 1;
    }
    std::string url = args[0], username = args[1], password = args[2], dataDir = args[3];
@@ -285,5 +391,7 @@ int main(int argc, char **argv) {
    std::ofstream credFile(dataDir + "/service-credential", std::ios::trunc);
    credFile << serviceClientName << ":" << serviceClientSecret;
    std::cerr << "wrote " << dataDir << "/service-credential" << std::endl;

    if (!writeConfigIfMissing(configPath, url, dataDir, tlsVerify)) return 1;
    return 0;
}