Loading src/api/JsonApiServer.cpp +31 −7 Changes for src/api/JsonApiServer.cpp: 31 added lines, 7 removed lines. Original line number Diff line number Diff line Loading @@ -272,13 +272,37 @@ json_object *tlsProfileToJson(const store::TlsProfileRecord &r) { // -- never the unsealed private key. Restoring these on the same deployment (same // cluster.key) round-trips exactly; restoring onto a different deployment's cluster won't // decrypt (see src/tls/CertSeal.h) and needs cert_pem/key_pem supplied fresh instead. json_object *tlsProfileToExportJson(const store::TlsProfileRecord &r) { // // r.certPem is only populated in ConfigStore for the manual case, or for self-signed once // CertificateManager has published it there (cluster mode only, see generateSelfSigned()) -- // single-node self-signed/manual certs live purely on local disk and would otherwise be // silently missing from every export. Fall back to disk (sealing on the fly with the same // cluster-wide key CertSeal always uses, cluster mode or not) for exactly that case; skip it // for ACME profiles, which don't need backing up at all -- they just reissue automatically. json_object *tlsProfileToExportJson(const config::AppConfig &cfg, const store::TlsProfileRecord &r) { json_object *item = tlsProfileToJson(r); json_object_object_add(item, "cert_pem", json_object_new_string(r.certPem.c_str())); json_object_object_add(item, "encrypted_key_pem_base64", json_object_new_string(toBase64(r.encryptedKeyPem).c_str())); json_object_object_add(item, "key_nonce_base64", json_object_new_string(toBase64(r.keyNonce).c_str())); json_object_object_add(item, "key_tag_base64", json_object_new_string(toBase64(r.keyTag).c_str())); std::string certPem = r.certPem; std::vector<uint8_t> encKeyPem = r.encryptedKeyPem, keyNonce = r.keyNonce, keyTag = r.keyTag; if (certPem.empty() && !r.acmeEnabled) { std::ifstream certFile(cfg.dataPath + "/tls/" + r.profile + ".crt", std::ios::binary); std::ifstream keyFile(cfg.dataPath + "/tls/" + r.profile + ".key", std::ios::binary); if (certFile && keyFile) { std::string diskCert((std::istreambuf_iterator<char>(certFile)), std::istreambuf_iterator<char>()); std::string diskKey((std::istreambuf_iterator<char>(keyFile)), std::istreambuf_iterator<char>()); auto clusterKey = cluster::ClusterManager::loadOrCreateSharedKey(cfg.dataPath); auto sealed = tls::sealPrivateKey(clusterKey, diskKey); certPem = diskCert; encKeyPem = sealed.ciphertext; keyNonce = sealed.nonce; keyTag = sealed.tag; } } json_object_object_add(item, "cert_pem", json_object_new_string(certPem.c_str())); json_object_object_add(item, "encrypted_key_pem_base64", json_object_new_string(toBase64(encKeyPem).c_str())); json_object_object_add(item, "key_nonce_base64", json_object_new_string(toBase64(keyNonce).c_str())); json_object_object_add(item, "key_tag_base64", json_object_new_string(toBase64(keyTag).c_str())); return item; } Loading Loading @@ -660,7 +684,7 @@ void JsonApiController::registerRoutes() { // is a far more privileged permission than tls.profiles.read. json_object *profiles = json_object_new_array(); for (const auto &r : _configStore.listTlsProfiles()) { json_object_array_add(profiles, tlsProfileToExportJson(r)); json_object_array_add(profiles, tlsProfileToExportJson(_cfg, r)); } json_object_object_add(root, "tls_profiles", profiles); Loading src/cluster/ClusterManager.cpp +10 −1 Changes for src/cluster/ClusterManager.cpp: 10 added lines, 1 removed line. Original line number Diff line number Diff line Loading @@ -185,16 +185,25 @@ void ClusterManager::pushLocalSnapshot() { } void ClusterManager::pullPeerSnapshots(const std::vector<std::string> &roster) { bool anyApplied = false; for (const auto &peerId : roster) { if (peerId == _cfg.nodeId) continue; try { auto blob = _client->retrieve(groupIdForNode(peerId)); _configStore.applySnapshotIfNewer(blob); if (_configStore.applySnapshotIfNewer(blob)) anyApplied = true; } catch (const std::exception &e) { std::lock_guard lk(_statusMutex); _status.lastSyncError = std::string("pull from ") + peerId + " failed: " + e.what(); } } if (!anyApplied) return; std::lock_guard lk(_configChangeMutex); for (const auto &cb : _configChangeCallbacks) cb(); } void ClusterManager::registerConfigChangeCallback(std::function<void()> cb) { std::lock_guard lk(_configChangeMutex); _configChangeCallbacks.push_back(std::move(cb)); } void ClusterManager::pollLoop() { Loading src/cluster/ClusterManager.h +11 −0 Changes for src/cluster/ClusterManager.h: 11 added lines, 0 removed lines. Original line number Diff line number Diff line #pragma once #include <atomic> #include <functional> #include <memory> #include <mutex> #include <string> #include <thread> #include <vector> Loading Loading @@ -51,6 +53,12 @@ public: ClusterStatus status() const; // Invoked whenever pullPeerSnapshots() actually applies a newer snapshot from some peer // (i.e. applySnapshotIfNewer() returned true) -- so e.g. CertificateManager can reconcile // TLS profiles right after a peer's edit lands here, instead of only noticing on this // node's own next (up to several-hours-away) background check. Register before start(). void registerConfigChangeCallback(std::function<void()> cb); private: void pollLoop(); void announceSelfInRoster(); Loading @@ -71,6 +79,9 @@ private: mutable std::mutex _statusMutex; ClusterStatus _status; std::mutex _configChangeMutex; std::vector<std::function<void()>> _configChangeCallbacks; }; } // namespace proxyplus::cluster src/main.cpp +6 −0 Changes for src/main.cpp: 6 added lines, 0 removed lines. Original line number Diff line number Diff line Loading @@ -194,6 +194,12 @@ int main(int argc, char **argv) { proxyplus::tls::AcmeChallengeStore acmeChallenges; proxyplus::tls::CertificateManager certManager(cfg, configStore, pluginManager, acmeChallenges); if (clusterManager) { // Otherwise a peer's tls/profiles edit only reaches this node's ConfigStore (via // the periodic pull above) without this node's CertificateManager noticing until // its own next background check, up to kCheckIntervalSeconds (6h) later. clusterManager->registerConfigChangeCallback([&certManager] { certManager.checkAllOnce(); }); } registerAutogenSelfSignedProfiles(configStore, cfg.httpListeners); // Self-signed profiles must have a cert on disk before any listener binds below -- Loading src/tls/CertificateManager.cpp +6 −2 Changes for src/tls/CertificateManager.cpp: 6 added lines, 2 removed lines. Original line number Diff line number Diff line Loading @@ -247,8 +247,7 @@ void CertificateManager::removeCert(const std::string &profile) { ::remove((dir + "/" + profile + ".key").c_str()); } void CertificateManager::checkLoop() { while (_running.load()) { void CertificateManager::checkAllOnce() { for (const auto &rec : _configStore.listTlsProfiles()) { if (rec.acmeEnabled) { if (needsRenewal(rec.profile)) renew(rec); Loading @@ -258,6 +257,11 @@ void CertificateManager::checkLoop() { materializeManualCert(rec); } } } void CertificateManager::checkLoop() { while (_running.load()) { checkAllOnce(); for (int i = 0; i < kCheckIntervalSeconds && _running.load(); ++i) { std::this_thread::sleep_for(std::chrono::seconds(1)); } Loading Loading
src/api/JsonApiServer.cpp +31 −7 Changes for src/api/JsonApiServer.cpp: 31 added lines, 7 removed lines. Original line number Diff line number Diff line Loading @@ -272,13 +272,37 @@ json_object *tlsProfileToJson(const store::TlsProfileRecord &r) { // -- never the unsealed private key. Restoring these on the same deployment (same // cluster.key) round-trips exactly; restoring onto a different deployment's cluster won't // decrypt (see src/tls/CertSeal.h) and needs cert_pem/key_pem supplied fresh instead. json_object *tlsProfileToExportJson(const store::TlsProfileRecord &r) { // // r.certPem is only populated in ConfigStore for the manual case, or for self-signed once // CertificateManager has published it there (cluster mode only, see generateSelfSigned()) -- // single-node self-signed/manual certs live purely on local disk and would otherwise be // silently missing from every export. Fall back to disk (sealing on the fly with the same // cluster-wide key CertSeal always uses, cluster mode or not) for exactly that case; skip it // for ACME profiles, which don't need backing up at all -- they just reissue automatically. json_object *tlsProfileToExportJson(const config::AppConfig &cfg, const store::TlsProfileRecord &r) { json_object *item = tlsProfileToJson(r); json_object_object_add(item, "cert_pem", json_object_new_string(r.certPem.c_str())); json_object_object_add(item, "encrypted_key_pem_base64", json_object_new_string(toBase64(r.encryptedKeyPem).c_str())); json_object_object_add(item, "key_nonce_base64", json_object_new_string(toBase64(r.keyNonce).c_str())); json_object_object_add(item, "key_tag_base64", json_object_new_string(toBase64(r.keyTag).c_str())); std::string certPem = r.certPem; std::vector<uint8_t> encKeyPem = r.encryptedKeyPem, keyNonce = r.keyNonce, keyTag = r.keyTag; if (certPem.empty() && !r.acmeEnabled) { std::ifstream certFile(cfg.dataPath + "/tls/" + r.profile + ".crt", std::ios::binary); std::ifstream keyFile(cfg.dataPath + "/tls/" + r.profile + ".key", std::ios::binary); if (certFile && keyFile) { std::string diskCert((std::istreambuf_iterator<char>(certFile)), std::istreambuf_iterator<char>()); std::string diskKey((std::istreambuf_iterator<char>(keyFile)), std::istreambuf_iterator<char>()); auto clusterKey = cluster::ClusterManager::loadOrCreateSharedKey(cfg.dataPath); auto sealed = tls::sealPrivateKey(clusterKey, diskKey); certPem = diskCert; encKeyPem = sealed.ciphertext; keyNonce = sealed.nonce; keyTag = sealed.tag; } } json_object_object_add(item, "cert_pem", json_object_new_string(certPem.c_str())); json_object_object_add(item, "encrypted_key_pem_base64", json_object_new_string(toBase64(encKeyPem).c_str())); json_object_object_add(item, "key_nonce_base64", json_object_new_string(toBase64(keyNonce).c_str())); json_object_object_add(item, "key_tag_base64", json_object_new_string(toBase64(keyTag).c_str())); return item; } Loading Loading @@ -660,7 +684,7 @@ void JsonApiController::registerRoutes() { // is a far more privileged permission than tls.profiles.read. json_object *profiles = json_object_new_array(); for (const auto &r : _configStore.listTlsProfiles()) { json_object_array_add(profiles, tlsProfileToExportJson(r)); json_object_array_add(profiles, tlsProfileToExportJson(_cfg, r)); } json_object_object_add(root, "tls_profiles", profiles); Loading
src/cluster/ClusterManager.cpp +10 −1 Changes for src/cluster/ClusterManager.cpp: 10 added lines, 1 removed line. Original line number Diff line number Diff line Loading @@ -185,16 +185,25 @@ void ClusterManager::pushLocalSnapshot() { } void ClusterManager::pullPeerSnapshots(const std::vector<std::string> &roster) { bool anyApplied = false; for (const auto &peerId : roster) { if (peerId == _cfg.nodeId) continue; try { auto blob = _client->retrieve(groupIdForNode(peerId)); _configStore.applySnapshotIfNewer(blob); if (_configStore.applySnapshotIfNewer(blob)) anyApplied = true; } catch (const std::exception &e) { std::lock_guard lk(_statusMutex); _status.lastSyncError = std::string("pull from ") + peerId + " failed: " + e.what(); } } if (!anyApplied) return; std::lock_guard lk(_configChangeMutex); for (const auto &cb : _configChangeCallbacks) cb(); } void ClusterManager::registerConfigChangeCallback(std::function<void()> cb) { std::lock_guard lk(_configChangeMutex); _configChangeCallbacks.push_back(std::move(cb)); } void ClusterManager::pollLoop() { Loading
src/cluster/ClusterManager.h +11 −0 Changes for src/cluster/ClusterManager.h: 11 added lines, 0 removed lines. Original line number Diff line number Diff line #pragma once #include <atomic> #include <functional> #include <memory> #include <mutex> #include <string> #include <thread> #include <vector> Loading Loading @@ -51,6 +53,12 @@ public: ClusterStatus status() const; // Invoked whenever pullPeerSnapshots() actually applies a newer snapshot from some peer // (i.e. applySnapshotIfNewer() returned true) -- so e.g. CertificateManager can reconcile // TLS profiles right after a peer's edit lands here, instead of only noticing on this // node's own next (up to several-hours-away) background check. Register before start(). void registerConfigChangeCallback(std::function<void()> cb); private: void pollLoop(); void announceSelfInRoster(); Loading @@ -71,6 +79,9 @@ private: mutable std::mutex _statusMutex; ClusterStatus _status; std::mutex _configChangeMutex; std::vector<std::function<void()>> _configChangeCallbacks; }; } // namespace proxyplus::cluster
src/main.cpp +6 −0 Changes for src/main.cpp: 6 added lines, 0 removed lines. Original line number Diff line number Diff line Loading @@ -194,6 +194,12 @@ int main(int argc, char **argv) { proxyplus::tls::AcmeChallengeStore acmeChallenges; proxyplus::tls::CertificateManager certManager(cfg, configStore, pluginManager, acmeChallenges); if (clusterManager) { // Otherwise a peer's tls/profiles edit only reaches this node's ConfigStore (via // the periodic pull above) without this node's CertificateManager noticing until // its own next background check, up to kCheckIntervalSeconds (6h) later. clusterManager->registerConfigChangeCallback([&certManager] { certManager.checkAllOnce(); }); } registerAutogenSelfSignedProfiles(configStore, cfg.httpListeners); // Self-signed profiles must have a cert on disk before any listener binds below -- Loading
src/tls/CertificateManager.cpp +6 −2 Changes for src/tls/CertificateManager.cpp: 6 added lines, 2 removed lines. Original line number Diff line number Diff line Loading @@ -247,8 +247,7 @@ void CertificateManager::removeCert(const std::string &profile) { ::remove((dir + "/" + profile + ".key").c_str()); } void CertificateManager::checkLoop() { while (_running.load()) { void CertificateManager::checkAllOnce() { for (const auto &rec : _configStore.listTlsProfiles()) { if (rec.acmeEnabled) { if (needsRenewal(rec.profile)) renew(rec); Loading @@ -258,6 +257,11 @@ void CertificateManager::checkLoop() { materializeManualCert(rec); } } } void CertificateManager::checkLoop() { while (_running.load()) { checkAllOnce(); for (int i = 0; i < kCheckIntervalSeconds && _running.load(); ++i) { std::this_thread::sleep_for(std::chrono::seconds(1)); } Loading