Commit 53b4bf55 authored by jan.koester's avatar jan.koester
Browse files

test

parent 62cd1ffb
Loading
Loading
Loading
Loading
+5 −0
Changes for src/config/BootstrapConfig.cpp: 5 added lines, 0 removed lines.
Original line number Diff line number Diff line
@@ -32,6 +32,7 @@ const std::set<std::string> &knownLeafPatterns() {
        "/proxyplus/http/listen/#/address",
        "/proxyplus/http/listen/#/port",
        "/proxyplus/http/listen/#/tls_profile",
        "/proxyplus/http/listen/#/tls_profiles/#",
        "/proxyplus/smtp/listen/#/address",
        "/proxyplus/smtp/listen/#/port",
        "/proxyplus/smtp/listen/#/tls_profile",
@@ -133,6 +134,10 @@ std::vector<ListenerConfig> readListeners(confplus::Config &conf, const std::str
        lc.address = readValue(conf, pfx + "/address", "");
        lc.port = readInt(conf, pfx + "/port", 0);
        lc.tlsProfile = readValue(conf, pfx + "/tls_profile", "");
        lc.tlsProfiles = readStringList(conf, pfx + "/tls_profiles");
        if (!lc.tlsProfile.empty() && !lc.tlsProfiles.empty()) {
            throw ConfigValidationError(pfx + ": set either tls_profile or tls_profiles, not both");
        }
        out.push_back(lc);
    }
    return out;
+6 −0
Changes for src/config/BootstrapConfig.h: 6 added lines, 0 removed lines.
Original line number Diff line number Diff line
@@ -15,6 +15,12 @@ struct ListenerConfig {
    std::string address;
    int port = 0;
    std::string tlsProfile; // "" = plaintext; resolves to <dataPath>/tls/<profile>.{crt,key}

    // SNI virtual hosting: two or more TLS profiles sharing this one listener/port, each
    // presented only for its own TlsProfileRecord::domains rather than one cert for everyone.
    // Mutually exclusive with tlsProfile above -- set at most one of the two (see
    // BootstrapConfig.cpp's readListeners()); empty = single-profile listener as before.
    std::vector<std::string> tlsProfiles;
};

enum class NodeMode { Single, Cluster };
+20 −0
Changes for src/http/HttpProxyServer.cpp: 20 added lines, 0 removed lines.
Original line number Diff line number Diff line
@@ -454,6 +454,26 @@ HttpProxyServer::HttpProxyServer(config::ListenerConfig listener, std::string ce
    , _pluginManager(pluginManager)
    , _loadBalancer(loadBalancer)
    , _acmeChallenges(acmeChallenges) {
    bindAndListen();
}

HttpProxyServer::HttpProxyServer(config::ListenerConfig listener,
                                  const std::map<std::string, netplus::ssl::CertificateBundle> &certsByHostname,
                                  store::ConfigStore &configStore, auth::IAuthService &authService,
                                  plugin::PluginManager &pluginManager, lb::LoadBalancer &loadBalancer,
                                  tls::AcmeChallengeStore *acmeChallenges)
    : _listener(listener)
    , _tlsActive(true)
    , _httpd(std::make_unique<libhttppp::HttpD>(listener.address, listener.port, 512, certsByHostname))
    , _configStore(configStore)
    , _authService(authService)
    , _pluginManager(pluginManager)
    , _loadBalancer(loadBalancer)
    , _acmeChallenges(acmeChallenges) {
    bindAndListen();
}

void HttpProxyServer::bindAndListen() {
    // netplus::tcp/ssl/quic only create the socket at construction; bind()/listen() are
    // separate calls the event loop would otherwise defer until run() -- too late, since
    // run() executes on a worker thread after main.cpp has already dropped root (see this
+19 −0
Changes for src/http/HttpProxyServer.h: 19 added lines, 0 removed lines.
Original line number Diff line number Diff line
@@ -83,6 +83,17 @@ public:
                     lb::LoadBalancer &loadBalancer,
                     tls::AcmeChallengeStore *acmeChallenges = nullptr);

    // SNI virtual hosting variant: certsByHostname carries one CertificateBundle per domain
    // sharing this listener (see ListenerConfig::tlsProfiles) instead of the single cert/key
    // path pair above.
    HttpProxyServer(config::ListenerConfig listener,
                     const std::map<std::string, netplus::ssl::CertificateBundle> &certsByHostname,
                     store::ConfigStore &configStore,
                     auth::IAuthService &authService,
                     plugin::PluginManager &pluginManager,
                     lb::LoadBalancer &loadBalancer,
                     tls::AcmeChallengeStore *acmeChallenges = nullptr);

    void run();

    // Hot cert reload for CertificateManager (see src/tls/CertificateManager) -- safe to
@@ -91,7 +102,15 @@ public:
        return _httpd->reloadCertificates(certPath, keyPath);
    }

    // Per-hostname equivalent, for a listener constructed via the SNI ctor above.
    bool reloadCertificate(const std::string &hostname, const std::string &certPath,
                            const std::string &keyPath) {
        return _httpd->reloadCertificate(hostname, certPath, keyPath);
    }

private:
    void bindAndListen();

    config::ListenerConfig _listener;
    bool _tlsActive;
    std::unique_ptr<libhttppp::HttpD> _httpd;
+53 −0
Changes for src/main.cpp: 53 added lines, 0 removed lines.
Original line number Diff line number Diff line
#include <algorithm>
#include <fstream>
#include <iostream>
#include <map>
#include <memory>
#include <sstream>
#include <thread>
@@ -29,6 +31,36 @@ std::string certPathFor(const proxyplus::config::AppConfig &cfg, const std::stri
std::string keyPathFor(const proxyplus::config::AppConfig &cfg, const std::string &profile) {
    return profile.empty() ? "" : cfg.dataPath + "/tls/" + profile + ".key";
}

// Builds the hostname->CertificateBundle map for an SNI listener (ListenerConfig::tlsProfiles):
// every domain of every named profile gets that profile's bundle, so e.g. profile "shop" with
// domains ["shop.example.com"] and profile "www" with domains ["example.com", "www.example.com"]
// both end up presented correctly off the same :443 listener.
std::map<std::string, netplus::ssl::CertificateBundle> loadSniCertMap(
    const proxyplus::config::AppConfig &cfg, const proxyplus::store::ConfigStore &configStore,
    const std::vector<std::string> &profiles) {
    std::map<std::string, netplus::ssl::CertificateBundle> out;
    auto allProfiles = configStore.listTlsProfiles();
    for (const auto &profileName : profiles) {
        auto it = std::find_if(allProfiles.begin(), allProfiles.end(),
                                [&](const auto &p) { return p.profile == profileName; });
        if (it == allProfiles.end()) {
            throw std::runtime_error("tls_profiles entry '" + profileName +
                                      "' has no matching TLS profile in the config store -- "
                                      "create it via POST /api/v1/tls/profiles first");
        }
        if (it->domains.empty()) {
            throw std::runtime_error("TLS profile '" + profileName +
                                      "' has no domains -- SNI dispatch needs at least one");
        }
        netplus::ssl::CertificateBundle bundle;
        if (!bundle.loadFromFile(certPathFor(cfg, profileName), keyPathFor(cfg, profileName))) {
            throw std::runtime_error("failed to load cert/key for TLS profile '" + profileName + "'");
        }
        for (const auto &domain : it->domains) out[domain] = bundle;
    }
    return out;
}
} // namespace

#ifndef PROXYPLUS_DEFAULT_STATIC_DIR
@@ -148,6 +180,27 @@ int main(int argc, char **argv) {

        if (cfg.maintenanceMode != proxyplus::config::MaintenanceMode::On) {
            for (const auto &listener : cfg.httpListeners) {
                if (!listener.tlsProfiles.empty()) {
                    auto sniCerts = loadSniCertMap(cfg, configStore, listener.tlsProfiles);
                    httpServers.push_back(std::make_unique<proxyplus::httpproxy::HttpProxyServer>(
                        listener, sniCerts, configStore, authService, pluginManager, loadBalancer,
                        &acmeChallenges));
                    auto *server = httpServers.back().get();
                    auto allProfiles = configStore.listTlsProfiles();
                    for (const auto &profileName : listener.tlsProfiles) {
                        auto it = std::find_if(allProfiles.begin(), allProfiles.end(),
                                                [&](const auto &p) { return p.profile == profileName; });
                        std::vector<std::string> domains = it != allProfiles.end() ? it->domains
                                                                                    : std::vector<std::string>{};
                        certManager.registerReloadCallback(profileName, [server, &cfg, profileName, domains] {
                            for (const auto &domain : domains) {
                                server->reloadCertificate(domain, certPathFor(cfg, profileName),
                                                           keyPathFor(cfg, profileName));
                            }
                        });
                    }
                    continue;
                }
                httpServers.push_back(std::make_unique<proxyplus::httpproxy::HttpProxyServer>(
                    listener, certPathFor(cfg, listener.tlsProfile),
                    keyPathFor(cfg, listener.tlsProfile), configStore, authService, pluginManager,