From 6f170ef60eb0aeada6ecd284109057e57b6a006a Mon Sep 17 00:00:00 2001 From: Gavin Halliday Date: Fri, 29 Sep 2023 17:38:14 +0100 Subject: [PATCH] HPCC-30411 Add support for dynamically updating TLS config HPCC-30754 Allow roxie to use issuer based tls in bare-metal configuration Signed-off-by: Gavin Halliday --- common/thorhelper/thorsoapcall.cpp | 5 +- esp/bindings/http/client/httpclient.cpp | 2 +- esp/clients/wsdfuaccess/wsdfuaccess.cpp | 25 +- esp/services/ws_dfu/ws_dfuService.cpp | 6 +- esp/test/httptest/httptest.cpp | 15 +- esp/tools/soapplus/http.cpp | 5 +- esp/tools/soapplus/httpproxy.cpp | 5 +- fs/dafilesrv/dafilesrv.cpp | 3 +- fs/dafsclient/rmtclient.cpp | 30 +- fs/dafsserver/dafsserver.cpp | 26 +- roxie/ccd/ccd.hpp | 2 +- roxie/ccd/ccdlistener.cpp | 4 +- roxie/ccd/ccdmain.cpp | 39 +- roxie/ccd/ccdprotocol.cpp | 31 +- roxie/ccd/hpccprotocol.hpp | 2 +- system/jlib/jptree.cpp | 86 ++++ system/jlib/jptree.hpp | 20 + system/jlib/jsecrets.cpp | 401 +++++++++++------- system/jlib/jsecrets.hpp | 28 +- system/jlib/jsmartsock.cpp | 2 +- system/jlib/jsmartsock.hpp | 4 +- system/jlib/jsmartsock.ipp | 4 +- system/security/securesocket/securesocket.cpp | 349 ++++++++------- system/security/securesocket/securesocket.hpp | 14 +- 24 files changed, 664 insertions(+), 444 deletions(-) diff --git a/common/thorhelper/thorsoapcall.cpp b/common/thorhelper/thorsoapcall.cpp index be9abd2b4b2..98df33c7a32 100644 --- a/common/thorhelper/thorsoapcall.cpp +++ b/common/thorhelper/thorsoapcall.cpp @@ -1242,7 +1242,10 @@ class CWSCHelper : implements IWSCHelper, public CInterface if (!ownedSC) { if (clientCert != NULL) - ownedSC.setown(createSecureSocketContextEx(clientCert->certificate, clientCert->privateKey, clientCert->passphrase, ClientSocket)); + { + Owned config = createSecureSocketConfig(clientCert->certificate, clientCert->privateKey, clientCert->passphrase); + ownedSC.setown(createSecureSocketContextEx2(config, ClientSocket)); + } else if (clientCertIssuer.length()) ownedSC.setown(createSecureSocketContextSecret(clientCertIssuer.str(), ClientSocket)); else diff --git a/esp/bindings/http/client/httpclient.cpp b/esp/bindings/http/client/httpclient.cpp index 3c0f1dd99c9..07829510c71 100644 --- a/esp/bindings/http/client/httpclient.cpp +++ b/esp/bindings/http/client/httpclient.cpp @@ -123,7 +123,7 @@ IHttpClient* CHttpClientContext::createHttpClient(const char* proxy, const char* if (xproc) m_ssctx.setown(xproc(m_config.get(),ClientSocket)); else - throw MakeStringException(-1, "procedure createSecureSocketContext can't be loaded"); + throw MakeStringException(-1, "procedure createSecureSocketContextEx2 can't be loaded"); } if(m_ssctx.get() == NULL) diff --git a/esp/clients/wsdfuaccess/wsdfuaccess.cpp b/esp/clients/wsdfuaccess/wsdfuaccess.cpp index 7850464c80f..b8d59e7189e 100644 --- a/esp/clients/wsdfuaccess/wsdfuaccess.cpp +++ b/esp/clients/wsdfuaccess/wsdfuaccess.cpp @@ -534,7 +534,6 @@ StringBuffer &encodeDFUFileMeta(StringBuffer &metaInfoBlob, IPropertyTree *metaI metaInfo->serialize(metaInfoBlob); const char *keyPairName = metaInfo->queryProp("keyPairName"); // NB: in container mode, this is the name of the secret containing the cert. - const char *privateKeyFName = nullptr; Owned metaInfoEnvelope = createPTree(); #ifdef _CONTAINERIZED /* Encode the public certificate in the request. NB: this is an approach used for JWT token delegation. @@ -543,24 +542,24 @@ StringBuffer &encodeDFUFileMeta(StringBuffer &metaInfoBlob, IPropertyTree *metaI * If the size of this initial request was ever a concern, we could consider other ways to ensure a one-off * delivery of this esp public signing cert. to dafilesrv, e.g. by dafilesrv reaching out to esp to request it. */ - Owned info = getIssuerTlsServerConfig(keyPairName); - if (!info) + Owned config = getIssuerTlsSyncedConfig(keyPairName); + if (!config || !config->isValid()) throw makeStringExceptionV(-1, "encodeDFUFileMeta: No '%s' MTLS certificate detected.", keyPairName); - privateKeyFName = info->queryProp("privatekey"); - if (isEmptyString(privateKeyFName)) - throw makeStringException(-1, "encodeDFUFileMeta: MTLS - private path missing"); - const char *certPath = info->queryProp("certificate"); - verifyex(certPath); - StringBuffer certificate; - certificate.loadFile(certPath); - verifyex(certificate.length()); + + Owned info = config->getTree(); + const char *privateKeyText = info->queryProp("privatekey"); + if (isEmptyString(privateKeyText)) + throw makeStringException(-1, "encodeDFUFileMeta: MTLS - private key missing"); + const char *certificate = info->queryProp("certificate"); + verifyex(certificate); metaInfoEnvelope->setProp("certificate", certificate); + Owned privateKey = loadPrivateKeyFromMemory(privateKeyText, nullptr); #else - privateKeyFName = environment->getPrivateKeyPath(keyPairName); + const char *privateKeyFName = environment->getPrivateKeyPath(keyPairName); if (isEmptyString(privateKeyFName)) throw makeStringExceptionV(-1, "Key name '%s' is not found in environment settings: /EnvSettings/Keys/KeyPair.", keyPairName); -#endif Owned privateKey = loadPrivateKeyFromFile(privateKeyFName, nullptr); +#endif StringBuffer metaInfoSignature; digiSign(metaInfoSignature, metaInfoBlob.length(), metaInfoBlob.bytes(), *privateKey); diff --git a/esp/services/ws_dfu/ws_dfuService.cpp b/esp/services/ws_dfu/ws_dfuService.cpp index 343d9cf26e2..97412c42cbb 100644 --- a/esp/services/ws_dfu/ws_dfuService.cpp +++ b/esp/services/ws_dfu/ws_dfuService.cpp @@ -6113,8 +6113,7 @@ void CWsDfuEx::dFUFileAccessCommon(IEspContext &context, const CDfsLogicalFileNa StringBuffer dafilesrvHost; #ifdef _CONTAINERIZED keyPairName.set("signing"); - Owned info = getIssuerTlsServerConfig(keyPairName); - if (!info) + if (!hasIssuerTlsConfig(keyPairName)) throw makeStringExceptionV(-1, "dFUFileAccessCommon: file signing certificate ('%s') not defined in configuration.", keyPairName.str()); auto externalService = k8s::getDafileServiceFromConfig("stream"); @@ -6490,8 +6489,7 @@ bool CWsDfuEx::onDFUFileCreateV2(IEspContext &context, IEspDFUFileCreateV2Reques #ifdef _CONTAINERIZED keyPairName.set("signing"); - Owned info = getIssuerTlsServerConfig(keyPairName); - if (!info) + if (!hasIssuerTlsConfig(keyPairName)) throw makeStringExceptionV(-1, "onDFUFileCreateV2: file signing certificate ('%s' ) not defined in configuration.", keyPairName.str()); const char *planeName = clusterName; diff --git a/esp/test/httptest/httptest.cpp b/esp/test/httptest/httptest.cpp index 0888478912e..b72bbd55311 100644 --- a/esp/test/httptest/httptest.cpp +++ b/esp/test/httptest/httptest.cpp @@ -152,10 +152,7 @@ HttpClient::HttpClient(int threads, int times, const char* host, int port, FILE* if(use_ssl) { #ifdef _USE_OPENSSL - if(sslconfig != NULL) - m_ssctx.setown(createSecureSocketContextEx2(sslconfig, ClientSocket)); - else - m_ssctx.setown(createSecureSocketContext(ClientSocket)); + m_ssctx.setown(createSecureSocketContextEx2(sslconfig, ClientSocket)); #else throw MakeStringException(-1, "HttpClient: failure to create SSL connection to host '%s': OpenSSL not enabled in build", host); #endif @@ -614,10 +611,7 @@ HttpServer::HttpServer(int port, const char* in, FILE* ofile, bool use_ssl, IPro if(use_ssl) { #ifdef _USE_OPENSSL - if(sslconfig != NULL) - m_ssctx.setown(createSecureSocketContextEx2(sslconfig, ServerSocket)); - else - m_ssctx.setown(createSecureSocketContext(ServerSocket)); + m_ssctx.setown(createSecureSocketContextEx2(sslconfig, ServerSocket)); #else throw MakeStringException(-1, "HttpServer: failure to create SSL socket - OpenSSL not enabled in build"); #endif @@ -1180,10 +1174,7 @@ HttpProxy::HttpProxy(int localport, const char* host, int port, FILE* ofile, boo if(use_ssl) { #if _USE_OPENSSL - if(sslconfig != NULL) - m_ssctx.setown(createSecureSocketContextEx2(sslconfig, ClientSocket)); - else - m_ssctx.setown(createSecureSocketContext(ClientSocket)); + m_ssctx.setown(createSecureSocketContextEx2(sslconfig, ClientSocket)); #else throw MakeStringException(-1, "HttpProxy: failure to create SSL connection to host '%s': OpenSSL not enabled in build", host); #endif diff --git a/esp/tools/soapplus/http.cpp b/esp/tools/soapplus/http.cpp index fd288668ef4..ceb70c6fba8 100644 --- a/esp/tools/soapplus/http.cpp +++ b/esp/tools/soapplus/http.cpp @@ -505,10 +505,7 @@ HttpClient::HttpClient(IProperties* globals, const char* url, const char* inname if (cfg && *cfg) cfgtree.setown(createPTreeFromXMLFile(cfg)); } - if (cfgtree) - m_ssctx.setown(createSecureSocketContextEx2(cfgtree, ClientSocket)); - else - m_ssctx.setown(createSecureSocketContext(ClientSocket)); + m_ssctx.setown(createSecureSocketContextEx2(cfgtree, ClientSocket)); } #else throw MakeStringException(-1, "HttpClient: failure to create SSL socket - OpenSSL not enabled in build"); diff --git a/esp/tools/soapplus/httpproxy.cpp b/esp/tools/soapplus/httpproxy.cpp index b07874d8c7f..4d9a8d303b2 100644 --- a/esp/tools/soapplus/httpproxy.cpp +++ b/esp/tools/soapplus/httpproxy.cpp @@ -582,10 +582,7 @@ HttpProxy::HttpProxy(int localport, const char* host, int port, FILE* ofile, boo if(use_ssl) { #ifdef _USE_OPENSSL - if(sslconfig != NULL) - m_ssctx.setown(createSecureSocketContextEx2(sslconfig, ClientSocket)); - else - m_ssctx.setown(createSecureSocketContext(ClientSocket)); + m_ssctx.setown(createSecureSocketContextEx2(sslconfig, ClientSocket)); #else throw MakeStringException(-1, "HttpProxy: failure to create SSL socket - OpenSSL not enabled in build"); #endif diff --git a/fs/dafilesrv/dafilesrv.cpp b/fs/dafilesrv/dafilesrv.cpp index 182aaa5fbed..0c5a52ee301 100644 --- a/fs/dafilesrv/dafilesrv.cpp +++ b/fs/dafilesrv/dafilesrv.cpp @@ -397,8 +397,7 @@ int main(int argc, const char* argv[]) // Use the "public" certificate issuer, unless it's visibility is "cluster" (meaning internal only) const char *visibility = getComponentConfigSP()->queryProp("service/@visibility"); const char *certScope = strsame("cluster", visibility) ? "local" : "public"; - Owned info = getIssuerTlsServerConfig(certScope); - connectMethod = info ? SSLOnly : SSLNone; + connectMethod = hasIssuerTlsConfig(certScope) ? SSLOnly : SSLNone; // NB: connectMethod will direct the CRemoteFileServer on accept to create a secure socket based on the same issuer certificates dedicatedRowServicePort = 0; // row service always runs on same secure ssl port in containerized mode diff --git a/fs/dafsclient/rmtclient.cpp b/fs/dafsclient/rmtclient.cpp index 48fcb1e9c58..4204a1185b5 100644 --- a/fs/dafsclient/rmtclient.cpp +++ b/fs/dafsclient/rmtclient.cpp @@ -112,6 +112,12 @@ static class _securitySettingsClient } } + const IPropertyTree * getSecureConfig() + { + //Later: return a synced tree... + return createSecureSocketConfig(queryCertificate(), queryPrivateKey(), queryPassPhrase()); + } + protected: DAFSConnectCfg connectMethod; unsigned short daFileSrvPort; @@ -156,10 +162,10 @@ static ISecureSocket *createSecureSocket(ISocket *sock, const char *issuer) auto it = secureCtxClientIssuerMap.find(issuer); if (it == secureCtxClientIssuerMap.end()) { - Owned info = getIssuerTlsServerConfig(issuer); - if (!info) + Owned info = getIssuerTlsSyncedConfig(issuer); + if (!info || !info->isValid()) throw makeStringExceptionV(-1, "createSecureSocket() : missing MTLS configuration for issuer: %s", issuer); - secureContext.setown(createSecureSocketContextEx2(info, ClientSocket)); + secureContext.setown(createSecureSocketContextSynced(info, ClientSocket)); secureCtxClientIssuerMap.emplace(issuer, secureContext.getLink()); } else @@ -168,7 +174,10 @@ static ISecureSocket *createSecureSocket(ISocket *sock, const char *issuer) else { if (!secureContextClient) - secureContextClient.setown(createSecureSocketContextEx(securitySettings.queryCertificate(), securitySettings.queryPrivateKey(), securitySettings.queryPassPhrase(), ClientSocket)); + { + Owned config = securitySettings.getSecureConfig(); + secureContextClient.setown(createSecureSocketContextEx2(config, ClientSocket)); + } secureContext.set(secureContextClient); } } @@ -751,17 +760,8 @@ void CRemoteBase::connectSocket(SocketEndpoint &ep, unsigned connectTimeoutMs, u } else { - Owned secretPTree = getSecret("storage", storageSecret); - if (!secretPTree) - throw makeStringExceptionV(-1, "secret %s.%s not found", "storage", storageSecret.str()); - - StringBuffer certSecretBuf; - getSecretKeyValue(certSecretBuf, secretPTree, "tls.crt"); - - StringBuffer privKeySecretBuf; - getSecretKeyValue(privKeySecretBuf, secretPTree, "tls.key"); - - Owned secureContext = createSecureSocketContextEx(certSecretBuf, privKeySecretBuf, nullptr, ClientSocket); + Owned config = createStorageTlsConfig(storageSecret, false); + Owned secureContext = createSecureSocketContextSynced(config, ClientSocket); ssock.setown(secureContext->createSecureSocket(socket.getClear(), loglevel)); } } diff --git a/fs/dafsserver/dafsserver.cpp b/fs/dafsserver/dafsserver.cpp index cd0fc21ce62..5cb9a26b118 100644 --- a/fs/dafsserver/dafsserver.cpp +++ b/fs/dafsserver/dafsserver.cpp @@ -112,6 +112,13 @@ static class _securitySettingsServer { queryDafsSecSettings(&connectMethod, &daFileSrvPort, &daFileSrvSSLPort, &certificate, &privateKey, &passPhrase); } + + const IPropertyTree * getSecureConfig() + { + //Later: return a synced tree... + return createSecureSocketConfig(certificate, privateKey, passPhrase); + } + } securitySettings; #endif @@ -133,23 +140,12 @@ static ISecureSocket *createSecureSocket(ISocket *sock, bool disableClientCertVe */ const char *certScope = strsame("cluster", getComponentConfigSP()->queryProp("service/@visibility")) ? "local" : "public"; - Owned info = getIssuerTlsServerConfig(certScope); - if (!info) + Owned info = getIssuerTlsSyncedConfig(certScope, nullptr, disableClientCertVerification); + if (!info || !info->isValid()) throw makeStringException(-1, "createSecureSocket() : missing MTLS configuration"); - Owned cloneInfo; - if (disableClientCertVerification) - { - // do not insist clients provide a cerificate for verification. - // This is used when the connection is TLS, but the authentication is done via other means - // e.g. in the case of the streaming service a opaque signed blob is transmitted and must - // be verified before proceeding. - cloneInfo.setown(createPTreeFromIPT(info)); - cloneInfo->setPropBool("verify/@enable", false); - info = cloneInfo; - } - secureContextServer.setown(createSecureSocketContextEx2(info, ServerSocket)); + secureContextServer.setown(createSecureSocketContextSynced(info, ServerSocket)); #else - secureContextServer.setown(createSecureSocketContextEx(securitySettings.certificate, securitySettings.privateKey, securitySettings.passPhrase, ServerSocket)); + secureContextServer.setown(createSecureSocketContextEx2(securitySettings.getSecureConfig(), ServerSocket)); #endif } } diff --git a/roxie/ccd/ccd.hpp b/roxie/ccd/ccd.hpp index ff954e4e619..0537b0f0b06 100644 --- a/roxie/ccd/ccd.hpp +++ b/roxie/ccd/ccd.hpp @@ -410,7 +410,7 @@ extern int backgroundCopyClass; extern int backgroundCopyPrio; extern unsigned roxiePort; // If listening on multiple, this is the first. Used for lock cascading -extern IPropertyTree *roxiePortTlsClientConfig; +extern ISyncedPropertyTree *roxiePortTlsClientConfig; extern unsigned udpMulticastBufferSize; diff --git a/roxie/ccd/ccdlistener.cpp b/roxie/ccd/ccdlistener.cpp index e0830b7edc9..99a48198fe8 100644 --- a/roxie/ccd/ccdlistener.cpp +++ b/roxie/ccd/ccdlistener.cpp @@ -79,7 +79,7 @@ class CascadeManager : public CInterface CriticalSection revisionCrit; int myEndpoint; const IRoxieContextLogger &logctx; - IPropertyTree *tlsConfig = nullptr; + ISyncedPropertyTree *tlsConfig = nullptr; void unlockChildren() { @@ -135,7 +135,7 @@ class CascadeManager : public CInterface assertex(sock); if (tlsConfig) { - Owned secureCtx = createSecureSocketContextEx2(tlsConfig, ClientSocket); + Owned secureCtx = createSecureSocketContextSynced(tlsConfig, ClientSocket); if (!secureCtx) throw makeStringException(ROXIE_TLS_ERROR, "Roxie CascadeManager failed creating secure context for roxie control message"); Owned ssock = secureCtx->createSecureSocket(sock.getClear()); diff --git a/roxie/ccd/ccdmain.cpp b/roxie/ccd/ccdmain.cpp index 9f20d67fc63..159d6f99642 100644 --- a/roxie/ccd/ccdmain.cpp +++ b/roxie/ccd/ccdmain.cpp @@ -46,6 +46,10 @@ #include "hpccconfig.hpp" #include "udpsha.hpp" +#ifdef _USE_OPENSSL +#include "securesocket.hpp" +#endif + #if defined (__linux__) #include #include "ioprio.h" @@ -230,7 +234,7 @@ unsigned leafCacheMB = 50; unsigned blobCacheMB = 0; unsigned roxiePort = 0; -IPropertyTree *roxiePortTlsClientConfig = nullptr; +ISyncedPropertyTree *roxiePortTlsClientConfig = nullptr; #ifndef _CONTAINERIZED Owned perfMonHook; @@ -1450,7 +1454,7 @@ int CCD_API roxie_main(int argc, const char *argv[], const char * defaultYaml) else { Owned protocolPlugin = loadHpccProtocolPlugin(protocolCtx, NULL); - Owned roxieServer = protocolPlugin->createListener("runOnce", createRoxieProtocolMsgSink(myNode.getIpAddress(), 0, 1, false), 0, 0, nullptr, nullptr, nullptr, nullptr, nullptr); + Owned roxieServer = protocolPlugin->createListener("runOnce", createRoxieProtocolMsgSink(myNode.getIpAddress(), 0, 1, false), 0, 0, nullptr, nullptr); try { const char *format = topology->queryProp("@format"); @@ -1501,7 +1505,7 @@ int CCD_API roxie_main(int argc, const char *argv[], const char * defaultYaml) { roxiePort = port; if (roxieFarm.getPropBool("@tls")) - roxiePortTlsClientConfig = createIssuerTlsClientConfig(roxieFarm.queryProp("@issuer"), roxieFarm.getPropBool("@selfSigned")); + roxiePortTlsClientConfig = createIssuerTlsConfig(roxieFarm.queryProp("@issuer"), nullptr, true, roxieFarm.getPropBool("@selfSigned"), true, false); debugEndpoint.set(roxiePort, ip); } bool suspended = roxieFarm.getPropBool("@suspended", false); @@ -1513,23 +1517,21 @@ int CCD_API roxie_main(int argc, const char *argv[], const char * defaultYaml) StringBuffer certFileName; StringBuffer keyFileName; StringBuffer passPhraseStr; - Owned tlsConfig; + Owned tlsConfig; if (serviceTLS) { - protocol = "ssl"; #ifdef _USE_OPENSSL - if (isContainerized()) + protocol = "ssl"; + const char *certIssuer = roxieFarm.queryProp("@issuer"); + if (isEmptyString(certIssuer)) + certIssuer = roxieFarm.getPropBool("@public", true) ? "public" : "local"; + bool disableMtls = roxieFarm.getPropBool("@disableMtls", false); + tlsConfig.setown(getIssuerTlsSyncedConfig(certIssuer, roxieFarm.queryProp("trusted_peers"), disableMtls)); + if (!tlsConfig || !tlsConfig->isValid()) { - const char *certIssuer = roxieFarm.queryProp("@issuer"); - if (isEmptyString(certIssuer)) - certIssuer = roxieFarm.getPropBool("@public", true) ? "public" : "local"; - tlsConfig.setown(getIssuerTlsServerConfigWithTrustedPeers(certIssuer, roxieFarm.queryProp("trusted_peers"))); - if (!tlsConfig) + if (isContainerized()) throw MakeStringException(ROXIE_FILE_ERROR, "TLS secret for issuer %s not found", certIssuer); - DBGLOG("Roxie service, port(%d) TLS issuer (%s)", port, certIssuer); - } - else - { + const char *passPhrase = roxieFarm.queryProp("@passphrase"); if (!isEmptyString(passPhrase)) decrypt(passPhraseStr, passPhrase); @@ -1555,7 +1557,12 @@ int CCD_API roxie_main(int argc, const char *argv[], const char * defaultYaml) if (!checkFileExists(keyFileName.str())) throw MakeStringException(ROXIE_FILE_ERROR, "Roxie SSL Farm Listener on port %d missing privateKeyFile (%s)", port, keyFileName.str()); + + Owned staticConfig = createSecureSocketConfig(certFileName, keyFileName, passPhraseStr); + tlsConfig.setown(createSyncedPropertyTree(staticConfig)); } + else + DBGLOG("Roxie service, port(%d) TLS issuer (%s)", port, certIssuer); #else OWARNLOG("Skipping Roxie SSL Farm Listener on port %d : OpenSSL disabled in build", port); @@ -1566,7 +1573,7 @@ int CCD_API roxie_main(int argc, const char *argv[], const char * defaultYaml) const char *config = roxieFarm.queryProp("@config"); // NB: leaks - until we fix bug in ensureProtocolPlugin() whereby some paths return a linked object and others do not IHpccProtocolPlugin *protocolPlugin = ensureProtocolPlugin(*protocolCtx, soname); - roxieServer.setown(protocolPlugin->createListener(protocol ? protocol : "native", createRoxieProtocolMsgSink(ip, port, numThreads, suspended), port, listenQueue, config, tlsConfig, certFileName, keyFileName, passPhraseStr)); + roxieServer.setown(protocolPlugin->createListener(protocol ? protocol : "native", createRoxieProtocolMsgSink(ip, port, numThreads, suspended), port, listenQueue, config, tlsConfig)); } else roxieServer.setown(createRoxieWorkUnitListener(numThreads, suspended)); diff --git a/roxie/ccd/ccdprotocol.cpp b/roxie/ccd/ccdprotocol.cpp index fe0e1be6567..e5546259936 100644 --- a/roxie/ccd/ccdprotocol.cpp +++ b/roxie/ccd/ccdprotocol.cpp @@ -29,7 +29,7 @@ //================================================================================================================================ -IHpccProtocolListener *createProtocolListener(const char *protocol, IHpccProtocolMsgSink *sink, unsigned port, unsigned listenQueue, const IPropertyTree *tlsConfig, const char *certFile, const char *keyFile, const char *passPhrase); +IHpccProtocolListener *createProtocolListener(const char *protocol, IHpccProtocolMsgSink *sink, unsigned port, unsigned listenQueue, const ISyncedPropertyTree *tlsConfig); class CHpccProtocolPlugin : implements IHpccProtocolPlugin, public CInterface { @@ -60,9 +60,9 @@ class CHpccProtocolPlugin : implements IHpccProtocolPlugin, public CInterface maxHttpConnectionRequests = ctx.ctxGetPropInt("@maxHttpConnectionRequests", 0); maxHttpKeepAliveWait = ctx.ctxGetPropInt("@maxHttpKeepAliveWait", 5000); // In milliseconds } - IHpccProtocolListener *createListener(const char *protocol, IHpccProtocolMsgSink *sink, unsigned port, unsigned listenQueue, const char *config, const IPropertyTree *tlsConfig, const char *certFile, const char *keyFile, const char *passPhrase) + IHpccProtocolListener *createListener(const char *protocol, IHpccProtocolMsgSink *sink, unsigned port, unsigned listenQueue, const char *config, const ISyncedPropertyTree *tlsConfig) { - return createProtocolListener(protocol, sink, port, listenQueue, tlsConfig, certFile, keyFile, passPhrase); + return createProtocolListener(protocol, sink, port, listenQueue, tlsConfig); } public: StringArray targetNames; @@ -224,32 +224,23 @@ class ProtocolSocketListener : public ProtocolListener Owned socket; SocketEndpoint ep; StringAttr protocol; - StringAttr certFile; - StringAttr keyFile; - StringAttr passPhrase; Owned secureContext; bool isSSL = false; public: - ProtocolSocketListener(IHpccProtocolMsgSink *_sink, unsigned _port, unsigned _listenQueue, const char *_protocol, const IPropertyTree *_tlsConfig, const char *_certFile, const char *_keyFile, const char *_passPhrase) + ProtocolSocketListener(IHpccProtocolMsgSink *_sink, unsigned _port, unsigned _listenQueue, const char *_protocol, const ISyncedPropertyTree *_tlsConfig) : ProtocolListener(_sink) { port = _port; listenQueue = _listenQueue; ep.set(port, queryHostIP()); protocol.set(_protocol); - certFile.set(_certFile); - keyFile.set(_keyFile); - passPhrase.set(_passPhrase); isSSL = streq(protocol.str(), "ssl"); #ifdef _USE_OPENSSL if (isSSL) { - if (_tlsConfig) - secureContext.setown(createSecureSocketContextEx2(_tlsConfig, ServerSocket)); - else - secureContext.setown(createSecureSocketContextEx(certFile.get(), keyFile.get(), passPhrase.get(), ServerSocket)); + secureContext.setown(createSecureSocketContextSynced(_tlsConfig, ServerSocket)); } #endif } @@ -2212,16 +2203,20 @@ void ProtocolSocketListener::runOnce(const char *query) p->runOnce(query); } -IHpccProtocolListener *createProtocolListener(const char *protocol, IHpccProtocolMsgSink *sink, unsigned port, unsigned listenQueue, const IPropertyTree *tlsConfig, const char *certFile, const char *keyFile, const char *passPhrase) +IHpccProtocolListener *createProtocolListener(const char *protocol, IHpccProtocolMsgSink *sink, unsigned port, unsigned listenQueue, const ISyncedPropertyTree *tlsConfig) { if (traceLevel) { const char *certIssuer = "none"; - if (tlsConfig && tlsConfig->hasProp("@issuer")) - certIssuer = tlsConfig->queryProp("@issuer"); + if (tlsConfig) + { + Owned tlsInfo = tlsConfig->getTree(); + if (tlsInfo && tlsInfo->hasProp("@issuer")) + certIssuer = tlsInfo->queryProp("@issuer"); + } DBGLOG("Creating Roxie socket listener, protocol %s, issuer=%s, pool size %d, listen queue %d%s", protocol, certIssuer, sink->getPoolSize(), listenQueue, sink->getIsSuspended() ? " SUSPENDED":""); } - return new ProtocolSocketListener(sink, port, listenQueue, protocol, tlsConfig, certFile, keyFile, passPhrase); + return new ProtocolSocketListener(sink, port, listenQueue, protocol, tlsConfig); } extern IHpccProtocolPlugin *loadHpccProtocolPlugin(IHpccProtocolPluginContext *ctx, IActiveQueryLimiterFactory *_limiterFactory) diff --git a/roxie/ccd/hpccprotocol.hpp b/roxie/ccd/hpccprotocol.hpp index 21380c0a54d..0fbb19c2e0d 100644 --- a/roxie/ccd/hpccprotocol.hpp +++ b/roxie/ccd/hpccprotocol.hpp @@ -134,7 +134,7 @@ interface IActiveQueryLimiterFactory : extends IInterface interface IHpccProtocolPlugin : extends IInterface { - virtual IHpccProtocolListener *createListener(const char *protocol, IHpccProtocolMsgSink *sink, unsigned port, unsigned listenQueue, const char *config, const IPropertyTree *tlsConfig, const char *certFile, const char *keyFile, const char *passPhrase)=0; + virtual IHpccProtocolListener *createListener(const char *protocol, IHpccProtocolMsgSink *sink, unsigned port, unsigned listenQueue, const char *config, const ISyncedPropertyTree *tlsConfig)=0; }; extern IHpccProtocolPlugin *loadHpccProtocolPlugin(IHpccProtocolPluginContext *ctx, IActiveQueryLimiterFactory *limiterFactory); diff --git a/system/jlib/jptree.cpp b/system/jlib/jptree.cpp index 4a3df7ce08f..994d916f422 100644 --- a/system/jlib/jptree.cpp +++ b/system/jlib/jptree.cpp @@ -10138,3 +10138,89 @@ void setExpertOpt(const char *opt, const char *value) getExpertOptPath(opt, xpath.clear()); config->setProp(xpath, value); } + +//--------------------------------------------------------------------------------------------------------------------- + +//HPCC-30752 This should move inside PTree to allow a more efficient hash calculation and possible caching. +//Currently the values are not persisted so the implementation could change. That may change in the future. +unsigned getPropertyTreeHash(const IPropertyTree & source, unsigned hashcode) +{ + if (source.isBinary()) + { + MemoryBuffer mb; + source.getPropBin(nullptr, mb); + hashcode = hashc((const byte *)mb.bufferBase(), mb.length(), hashcode); + } + else + { + const char * value = source.queryProp(nullptr); + if (value) + hashcode = hashcz((const byte *)value, hashcode); + } + + Owned aiter = source.getAttributes(); + ForEach(*aiter) + { + hashcode = hashcz((const byte *)aiter->queryName(), hashcode); + hashcode = hashcz((const byte *)aiter->queryValue(), hashcode); + } + + Owned iter = source.getElements("*"); + ForEach(*iter) + { + IPropertyTree & child = iter->query(); + hashcode = hashcz((const byte *)child.queryName(), hashcode); + hashcode = getPropertyTreeHash(child, hashcode); + } + return hashcode; +} + +class SyncedPropertyTreeWrapper : extends CInterfaceOf +{ +public: + SyncedPropertyTreeWrapper(IPropertyTree * _tree) : tree(_tree) + { + } + + virtual const IPropertyTree * getTree() const override + { + return LINK(tree); + } + + virtual bool getProp(MemoryBuffer & result, const char * xpath) const override + { + if (!tree) + return false; + return tree->getPropBin(xpath, result); + } + + virtual bool getProp(StringBuffer & result, const char * xpath) const override + { + if (!tree) + return false; + return tree->getProp(xpath, result); + } + + virtual unsigned getVersion() const override + { + return 0; + } + + virtual bool isStale() const override + { + return false; + } + + virtual bool isValid() const override + { + return tree != nullptr; + } + +protected: + Linked tree; +}; + +ISyncedPropertyTree * createSyncedPropertyTree(IPropertyTree * tree) +{ + return new SyncedPropertyTreeWrapper(tree); +} diff --git a/system/jlib/jptree.hpp b/system/jlib/jptree.hpp index b3dc69c6fdd..80888c8218d 100644 --- a/system/jlib/jptree.hpp +++ b/system/jlib/jptree.hpp @@ -430,4 +430,24 @@ extern jlib_decl StringBuffer &getExpertOptString(const char *opt, StringBuffer extern jlib_decl void setExpertOpt(const char *opt, const char *value); +//--------------------------------------------------------------------------------------------------------------------- + +extern jlib_decl unsigned getPropertyTreeHash(const IPropertyTree & source, unsigned hashcode); + +//Interface for encapsulating an IPropertyTree that can be atomically updated. The result of getTree() is guaranteed +//to not be modified and to remain valid and consistent until it is released. +interface ISyncedPropertyTree : extends IInterface +{ + virtual const IPropertyTree * getTree() const = 0; + virtual bool getProp(MemoryBuffer & result, const char * xpath) const = 0; + virtual bool getProp(StringBuffer & result, const char * xpath) const = 0; + //Return a version-hash which changes whenever the property tree changes - so that a caller can determine whether it needs to update + virtual unsigned getVersion() const = 0; + virtual bool isStale() const = 0; // An indication that the property tree may be out of date because it couldn't be resynchronized. + virtual bool isValid() const = 0; // Is the property tree non-null? Typically called at startup to check configuration is provided. +}; + +extern jlib_decl ISyncedPropertyTree * createSyncedPropertyTree(IPropertyTree * tree); + + #endif diff --git a/system/jlib/jsecrets.cpp b/system/jlib/jsecrets.cpp index 7fa24b4b794..5951ad6fbb7 100644 --- a/system/jlib/jsecrets.cpp +++ b/system/jlib/jsecrets.cpp @@ -49,6 +49,7 @@ #include #endif +//#define TRACE_SECRETS #include enum class CVaultKind { kv_v1, kv_v2 }; @@ -72,7 +73,7 @@ interface IVaultManager : extends IInterface static CriticalSection secretCacheCS; static Owned secretCache; static CriticalSection mtlsInfoCacheCS; -static Owned mtlsInfoCache; +static std::unordered_map> mtlsInfoCache; static Owned vaultManager; static MemoryAttr udpKey; static bool udpKeyInitialized = false; @@ -80,7 +81,6 @@ static bool udpKeyInitialized = false; MODULE_INIT(INIT_PRIORITY_SYSTEM) { secretCache.setown(createPTree()); - mtlsInfoCache.setown(createPTree()); return true; } @@ -1157,41 +1157,17 @@ IPropertyTree *getSecret(const char *category, const char * name, const char * o bool getSecretKeyValue(MemoryBuffer & result, const IPropertyTree *secret, const char * key) { validateKeyName(key); - if (!secret) return false; - - IPropertyTree *tree = secret->queryPropTree(key); - if (tree) - return tree->getPropBin(nullptr, result); - return false; + return secret->getPropBin(key, result); } bool getSecretKeyValue(StringBuffer & result, const IPropertyTree *secret, const char * key) { validateKeyName(key); - if (!secret) return false; - - IPropertyTree *tree = secret->queryPropTree(key); - if (!tree) - return false; - if (tree->isBinary(nullptr)) - { - MemoryBuffer mb; - tree->getPropBin(nullptr, mb); - //caller implies it's a string - result.append(mb.length(), mb.toByteArray()); - return true; - } - const char *value = tree->queryProp(nullptr); - if (value) - { - result.append(value); - return true; - } - return false; + return secret->getProp(key, result); } extern jlib_decl bool getSecretValue(StringBuffer & result, const char *category, const char * name, const char * key, bool required) @@ -1207,7 +1183,7 @@ extern jlib_decl bool getSecretValue(StringBuffer & result, const char *category //--------------------------------------------------------------------------------------------------------------------- -class CSecret final : public CInterfaceOf +class CSecret final : public CInterfaceOf { public: CSecret(const char *_category, const char * _name, const char * _vaultId, const char * _version, const IPropertyTree * _secret) @@ -1216,28 +1192,35 @@ class CSecret final : public CInterfaceOf updateHash(); } - virtual const IPropertyTree * getTree() const; + virtual const IPropertyTree * getTree() const override; - virtual bool getKeyValue(MemoryBuffer & result, const char * key) const + virtual bool getProp(MemoryBuffer & result, const char * key) const override { CriticalBlock block(secretCs); checkStale(); return getSecretKeyValue(result, secret, key); } - virtual bool getKeyValue(StringBuffer & result, const char * key) const + virtual bool getProp(StringBuffer & result, const char * key) const override { CriticalBlock block(secretCs); checkStale(); return getSecretKeyValue(result, secret, key); } - virtual bool isStale() const + virtual bool isStale() const override { return secret && hasCacheExpired(secret); } - virtual unsigned getVersion() const + virtual unsigned getVersion() const override { + CriticalBlock block(secretCs); + checkStale(); return secretHash; } + virtual bool isValid() const override + { + CriticalBlock block(secretCs); + return secret != nullptr; + } protected: void checkStale() const; @@ -1265,6 +1248,9 @@ void CSecret::checkStale() const { if (isStale()) { +#ifdef TRACE_SECRETS + DBGLOG("Secret %s/%s is stale updating from %u...", category.str(), name.str(), secretHash); +#endif //MORE: This could block or fail - in roxie especially it would be better to return the old value try { @@ -1280,48 +1266,15 @@ void CSecret::checkStale() const } } -//This should probably move to jptree.?pp as a generally useful function -static unsigned calculateTreeHash(const IPropertyTree & source, unsigned hashcode) -{ - if (source.isBinary()) - { - MemoryBuffer mb; - source.getPropBin(nullptr, mb); - hashcode = hashc((const byte *)mb.bufferBase(), mb.length(), hashcode); - } - else - { - const char * value = source.queryProp(nullptr); - if (value) - hashcode = hashcz((const byte *)value, hashcode); - } - - Owned aiter = source.getAttributes(); - ForEach(*aiter) - { - hashcode = hashcz((const byte *)aiter->queryName(), hashcode); - hashcode = hashcz((const byte *)aiter->queryValue(), hashcode); - } - - Owned iter = source.getElements("*"); - ForEach(*iter) - { - IPropertyTree & child = iter->query(); - hashcode = hashcz((const byte *)child.queryName(), hashcode); - hashcode = calculateTreeHash(child, hashcode); - } - return hashcode; -} - void CSecret::updateHash() const { if (secret) - secretHash = calculateTreeHash(*secret.get(), 0x811C9DC5); + secretHash = getPropertyTreeHash(*secret.get(), 0x811C9DC5); else secretHash = 0; } -ISecret * resolveSecret(const char *category, const char * name, const char * optVaultId, const char * optVersion) +ISyncedPropertyTree * resolveSecret(const char *category, const char * name, const char * optVaultId, const char * optVersion) { Owned resolved = getSecret(category, name, optVaultId, optVersion); return new CSecret(category, name, optVaultId, optVersion, resolved); @@ -1385,106 +1338,241 @@ jlib_decl bool containsEmbeddedKey(const char *certificate) return false; } -IPropertyTree *createIssuerTlsClientConfig(const char *issuer, bool acceptSelfSigned, bool addCACert) -{ - if (isEmptyString(issuer)) - return nullptr; - StringBuffer filepath; - StringBuffer secretpath; - buildSecretPath(secretpath, "certificates", issuer); +//--------------------------------------------------------------------------------------------------------------------- + +class CSyncedCertificateBase : public CInterfaceOf +{ +public: + CSyncedCertificateBase(const char *_issuer) + : issuer(_issuer) + { + } - Owned info = createPTree(); + virtual const IPropertyTree * getTree() const override final; - if (strieq(issuer, "remote")||strieq(issuer, "local")) + virtual bool getProp(MemoryBuffer & result, const char * key) const override final { - filepath.set(secretpath).append("tls.crt"); - if (!checkFileExists(filepath)) - return nullptr; + CriticalBlock block(secretCs); + checkStale(); + return getSecretKeyValue(result, config, key); + } + virtual bool getProp(StringBuffer & result, const char * key) const override final + { + CriticalBlock block(secretCs); + checkStale(); + return getSecretKeyValue(result, config, key); + } + virtual bool isStale() const override final + { + return secret->isStale(); + } + virtual bool isValid() const override + { + return secret->isValid(); - info->setProp("certificate", filepath.str()); - filepath.set(secretpath).append("tls.key"); - if (checkFileExists(filepath)) - info->setProp("privatekey", filepath.str()); + } + virtual unsigned getVersion() const override final + { + CriticalBlock block(secretCs); + checkStale(); + //If information that is combined with the secret (e.g. trusted peers) can also change dynamically this would + //need to be a separate hash calculated from the config tree + return secretHash; } - IPropertyTree *verify = ensurePTree(info, "verify"); - if (addCACert) +protected: + virtual void updateConfigFromSecret(const IPropertyTree * secretInfo) const = 0; + +protected: + void checkStale() const; + void createConfig() const; + void createDefaultConfigFromSecret(const IPropertyTree * secretInfo, bool addCertificates, bool addCertificateAuthority) const; + void updateCertificateFromSecret(const IPropertyTree * secretInfo) const; + void updateCertificateAuthorityFromSecret(const IPropertyTree * secretInfo) const; + +protected: + StringAttr issuer; + Owned secret; + mutable CriticalSection secretCs; + mutable Linked config; + mutable std::atomic secretHash{0}; +}; + + +const IPropertyTree * CSyncedCertificateBase::getTree() const +{ + CriticalBlock block(secretCs); + checkStale(); + return LINK(config); +} + +void CSyncedCertificateBase::checkStale() const +{ + if (secretHash != secret->getVersion()) + createConfig(); +} + +void CSyncedCertificateBase::createConfig() const +{ + //Update before getting the tree to avoid potential race condition updating the tree at the same time. + //Could alternatively return the version number from the getTree() call. + secretHash = secret->getVersion(); + + Owned secretInfo = secret->getTree(); + if (secretInfo) { - filepath.set(secretpath).append("ca.crt"); - if (checkFileExists(filepath)) - { - IPropertyTree *ca = ensurePTree(verify, "ca_certificates"); - ca->setProp("@path", filepath.str()); - } + config.setown(createPTree(issuer)); + ensurePTree(config, "verify"); + updateConfigFromSecret(secretInfo); } - verify->setPropBool("@enable", true); - verify->setPropBool("@address_match", false); - verify->setPropBool("@accept_selfsigned", acceptSelfSigned); - verify->setProp("trusted_peers", "anyone"); + else + config.clear(); +} + - return info.getClear(); +void CSyncedCertificateBase::updateCertificateFromSecret(const IPropertyTree * secretInfo) const +{ + StringBuffer value; + config->setProp("@issuer", issuer); // server only? + if (secretInfo->getProp("tls.crt", value.clear())) + config->setProp("certificate", value.str()); + if (secretInfo->getProp("tls.key", value.clear())) + config->setProp("privatekey", value.str()); } -IPropertyTree *getIssuerTlsServerConfig(const char *name) +void CSyncedCertificateBase::updateCertificateAuthorityFromSecret(const IPropertyTree * secretInfo) const { - if (isEmptyString(name)) - return nullptr; + StringBuffer value; + if (secretInfo->getProp("ca.crt", value.clear())) + { + IPropertyTree *verify = config->queryPropTree("verify"); + IPropertyTree *ca = ensurePTree(verify, "ca_certificates"); + ca->setProp("pem", value.str()); + } +} - validateSecretName(name); - CriticalBlock block(mtlsInfoCacheCS); - Owned info = mtlsInfoCache->getPropTree(name); - if (info) - return info.getClear(); +//--------------------------------------------------------------------------------------------------------------------- - StringBuffer filepath; - StringBuffer secretpath; +class CIssuerConfig final : public CSyncedCertificateBase +{ +public: + CIssuerConfig(const char *_issuer, const char * _trustedPeers, bool _isClientConnection, bool _acceptSelfSigned, bool _addCACert, bool _disableMTLS) + : CSyncedCertificateBase(_issuer), trustedPeers(_trustedPeers), isClientConnection(_isClientConnection), acceptSelfSigned(_acceptSelfSigned), addCACert(_addCACert), disableMTLS(_disableMTLS) + { + secret.setown(resolveSecret("certificates", issuer, nullptr, nullptr)); + createConfig(); + } - buildSecretPath(secretpath, "certificates", name); + virtual void updateConfigFromSecret(const IPropertyTree * secretInfo) const override; - filepath.set(secretpath).append("tls.crt"); - if (!checkFileExists(filepath)) - return nullptr; +protected: + StringAttr trustedPeers; + bool isClientConnection; // required in constructor + bool acceptSelfSigned; // required in constructor + bool addCACert; // required in constructor + bool disableMTLS; +}; - info.set(mtlsInfoCache->setPropTree(name)); - info->setProp("@issuer", name); - info->setProp("certificate", filepath.str()); - filepath.set(secretpath).append("tls.key"); - if (checkFileExists(filepath)) - info->setProp("privatekey", filepath.str()); - IPropertyTree *verify = ensurePTree(info, "verify"); - if (verify) - { - filepath.set(secretpath).append("ca.crt"); - if (checkFileExists(filepath)) - { - IPropertyTree *ca = ensurePTree(verify, "ca_certificates"); - if (ca) - ca->setProp("@path", filepath.str()); - } - //For now only the "public" issuer implies client certificates are not required - verify->setPropBool("@enable", !strieq(name, "public")); - verify->setPropBool("@address_match", false); - verify->setPropBool("@accept_selfsigned", false); + +void CIssuerConfig::updateConfigFromSecret(const IPropertyTree * secretInfo) const +{ + if (!isClientConnection || !strieq(issuer, "public")) + updateCertificateFromSecret(secretInfo); + + + // addCACert is usually true. A client hitting a public issuer is the case where we don't want the ca cert + // defined. Otherwise, for MTLS we want control over our CACert using addCACert. When hitting public services + // using public certificate authorities we want the well known (browser compatible) CA list installed on the + // system instead. + if (!isClientConnection || addCACert) + updateCertificateAuthorityFromSecret(secretInfo); + + IPropertyTree *verify = config->queryPropTree("verify"); + assertex(verify); // Should always be defined by this point. + + //For now only the "public" issuer implies client certificates are not required + verify->setPropBool("@enable", !disableMTLS && (isClientConnection || !strieq(issuer, "public"))); + verify->setPropBool("@address_match", false); + verify->setPropBool("@accept_selfsigned", isClientConnection && acceptSelfSigned); + if (trustedPeers) // Allow blank string to mean none, null means anyone + verify->setProp("trusted_peers", trustedPeers); + else verify->setProp("trusted_peers", "anyone"); +} + + +ISyncedPropertyTree * createIssuerTlsConfig(const char * issuer, const char * optTrustedPeers, bool isClientConnection, bool acceptSelfSigned, bool addCACert, bool disableMTLS) +{ + return new CIssuerConfig(issuer, optTrustedPeers, isClientConnection, acceptSelfSigned, addCACert, disableMTLS); + +} +//--------------------------------------------------------------------------------------------------------------------- + +class CCertificateConfig final : public CSyncedCertificateBase +{ +public: + CCertificateConfig(const char * _category, const char * _secretName, bool _addCACert) + : CSyncedCertificateBase(nullptr), addCACert(_addCACert) + { + secret.setown(resolveSecret(_category, _secretName, nullptr, nullptr)); + if (!secret->isValid()) + throw makeStringExceptionV(-1, "secret %s.%s not found", _category, _secretName); + createConfig(); } - return info.getClear(); + + virtual void updateConfigFromSecret(const IPropertyTree * secretInfo) const override; + +protected: + bool addCACert; // required in constructor +}; + +void CCertificateConfig::updateConfigFromSecret(const IPropertyTree * secretInfo) const +{ + updateCertificateFromSecret(secretInfo); + + if (addCACert) + updateCertificateAuthorityFromSecret(secretInfo); } -IPropertyTree *getIssuerTlsServerConfigWithTrustedPeers(const char *issuer, const char *trusted_peers) + +ISyncedPropertyTree * createStorageTlsConfig(const char * secretName, bool addCACert) { - Owned issuerConfig = getIssuerTlsServerConfig(issuer); - if (!issuerConfig || isEmptyString(trusted_peers)) - return issuerConfig.getClear(); - //TBD: might cache in the future, but needs thought, lookup must include trusted_peers, but will there be cases where trusted_peers can change dynamically? - Owned tlsConfig = createPTreeFromIPT(issuerConfig); - if (!tlsConfig) + return new CCertificateConfig("storage", secretName, addCACert); + +} + + +const ISyncedPropertyTree * getIssuerTlsSyncedConfig(const char * issuer, const char * optTrustedPeers, bool disableMTLS) +{ + if (isEmptyString(issuer)) return nullptr; - IPropertyTree *verify = ensurePTree(tlsConfig, "verify"); - verify->setProp("trusted_peers", trusted_peers); - return tlsConfig.getClear(); + const char * key; + StringBuffer temp; + if (!isEmptyString(optTrustedPeers) || disableMTLS) + { + temp.append(issuer).append("/").append(optTrustedPeers).append('/').append(disableMTLS); + key = temp.str(); + } + else + key = issuer; + + CriticalBlock block(mtlsInfoCacheCS); + auto match = mtlsInfoCache.find(key); + if (match != mtlsInfoCache.cend()) + return LINK(match->second); + + Owned config = createIssuerTlsConfig(issuer, optTrustedPeers, false, false, true, disableMTLS); + mtlsInfoCache.emplace(key, config); + return config.getClear(); +} + +bool hasIssuerTlsConfig(const char *issuer) +{ + Owned match = getIssuerTlsSyncedConfig(issuer, nullptr, false); + return match && match->isValid(); } enum UseMTLS { UNINIT, DISABLED, ENABLED }; @@ -1518,21 +1606,18 @@ jlib_decl bool queryMtls() if (checkFileExists(cert) && checkFileExists(privKey)) { CriticalBlock block(mtlsInfoCacheCS); - if (mtlsInfoCache) - { - IPropertyTree *info = mtlsInfoCache->queryPropTree("local"); - if (!info) - info = mtlsInfoCache->setPropTree("local"); - if (info) - { // always update - info->setProp("certificate", cert); - info->setProp("privatekey", privKey); - if ( (!isEmptyString(pubKey)) && (checkFileExists(pubKey)) ) - info->setProp("publickey", pubKey); - if (!isEmptyString(passPhrase)) - info->setProp("passphrase", passPhrase); // encrypted - } - } + assertex(mtlsInfoCache.find("local") == mtlsInfoCache.cend()); + + Owned info = createPTree("local"); + info->setProp("certificate", cert); + info->setProp("privatekey", privKey); + if ( (!isEmptyString(pubKey)) && (checkFileExists(pubKey)) ) + info->setProp("publickey", pubKey); + if (!isEmptyString(passPhrase)) + info->setProp("passphrase", passPhrase); // encrypted + + Owned entry = createSyncedPropertyTree(info); + mtlsInfoCache.emplace("local", entry); } } } diff --git a/system/jlib/jsecrets.hpp b/system/jlib/jsecrets.hpp index 39dddeeb3d2..9601deb4d7d 100644 --- a/system/jlib/jsecrets.hpp +++ b/system/jlib/jsecrets.hpp @@ -22,22 +22,17 @@ #include "jlib.hpp" #include "jstring.hpp" -interface ISecret : extends IInterface -{ - virtual const IPropertyTree * getTree() const = 0; - virtual bool getKeyValue(MemoryBuffer & result, const char * key) const = 0; - virtual bool getKeyValue(StringBuffer & result, const char * key) const = 0; - virtual bool isStale() const = 0; - //Return a sequence number which changes whenever the secret actually changes - so that a caller can determine - //whether it needs to reload the certificates. - virtual unsigned getVersion() const = 0; -}; +interface ISyncedPropertyTree; extern jlib_decl void setSecretMount(const char * path); extern jlib_decl void setSecretTimeout(unsigned timeoutMs); +//Return the current (cached) value of a secret. If the secret is not defined, return nullptr. extern jlib_decl IPropertyTree *getSecret(const char *category, const char * name, const char * optVaultId = nullptr, const char * optVersion = nullptr); -extern jlib_decl ISecret * resolveSecret(const char *category, const char * name, const char * optRequiredVault, const char* optVersion); +// resolveSecret() always returns an object, which will potentially be updated behind the scenes. If no secret is originally +// defined, but it then configured in a vault or Kubernetes secret, it will be bicked up when the cache entry is +// refreshed - allowing missing configuration to be updated for a live system. +extern jlib_decl ISyncedPropertyTree * resolveSecret(const char *category, const char * name, const char * optRequiredVault, const char* optVersion); extern jlib_decl bool getSecretKeyValue(MemoryBuffer & result, const IPropertyTree *secret, const char * key); extern jlib_decl bool getSecretKeyValue(StringBuffer & result, const IPropertyTree *secret, const char * key); @@ -48,11 +43,14 @@ extern jlib_decl const MemoryAttr &getSecretUdpKey(bool required); extern jlib_decl bool containsEmbeddedKey(const char *certificate); -//getIssuerTlsServerConfig must return owned because the internal cache could be updated internally and the return will become invalid, so must be linked -extern jlib_decl IPropertyTree *getIssuerTlsServerConfig(const char *issuer); -extern jlib_decl IPropertyTree *getIssuerTlsServerConfigWithTrustedPeers(const char *issuer, const char *trusted_peers); +//getIssuerTlsConfig must return owned because the internal cache could be updated internally and the return will become invalid, so must be linked +extern jlib_decl const ISyncedPropertyTree * getIssuerTlsSyncedConfig(const char * issuer, const char * optTrustedPeers, bool disableMTLS); +inline const ISyncedPropertyTree * getIssuerTlsSyncedConfig(const char * issuer) { return getIssuerTlsSyncedConfig(issuer, nullptr, false); } -extern jlib_decl IPropertyTree *createIssuerTlsClientConfig(const char *issuer, bool acceptSelfSigned, bool addCACert=true); +extern jlib_decl bool hasIssuerTlsConfig(const char *issuer); + +extern jlib_decl ISyncedPropertyTree * createIssuerTlsConfig(const char * issuer, const char * optTrustedPeers, bool isClientConnection, bool acceptSelfSigned, bool addCACert, bool disableMTLS); +extern jlib_decl ISyncedPropertyTree * createStorageTlsConfig(const char * secretName, bool addCACert); extern jlib_decl void splitFullUrl(const char *url, bool &https, StringBuffer &user, StringBuffer &password, StringBuffer &host, StringBuffer &port, StringBuffer &fullpath); extern jlib_decl void splitUrlSchemeHostPort(const char *url, StringBuffer &user, StringBuffer &password, StringBuffer &schemeHostPort, StringBuffer &path); diff --git a/system/jlib/jsmartsock.cpp b/system/jlib/jsmartsock.cpp index f7f0b4190dd..e8644827297 100644 --- a/system/jlib/jsmartsock.cpp +++ b/system/jlib/jsmartsock.cpp @@ -218,7 +218,7 @@ CSmartSocketFactory::CSmartSocketFactory(IPropertyTree &service, bool _retry, un tlsService = service.getPropBool("@tls"); issuer.set(service.queryProp("@issuer")); if (tlsService) - tlsConfig.setown(createIssuerTlsClientConfig(issuer, service.getPropBool("@selfSigned"), service.getPropBool("@caCert"))); + tlsConfig.setown(createIssuerTlsConfig(issuer, nullptr, true, service.getPropBool("@selfSigned"), service.getPropBool("@caCert"), false)); StringBuffer s; s.append(name).append(':').append(port); diff --git a/system/jlib/jsmartsock.hpp b/system/jlib/jsmartsock.hpp index 7e5e21e4c3a..e5139364ef2 100644 --- a/system/jlib/jsmartsock.hpp +++ b/system/jlib/jsmartsock.hpp @@ -37,7 +37,7 @@ interface jlib_decl ISmartSocket : extends IInterface virtual void close() = 0; }; - +interface ISyncedPropertyTree; interface jlib_decl ISmartSocketFactory : extends IInterface { virtual int run()=0; @@ -59,7 +59,7 @@ interface jlib_decl ISmartSocketFactory : extends IInterface virtual StringBuffer & getUrlStr(StringBuffer &str, bool useHostName) = 0; virtual bool isTlsService() const = 0; - virtual const IPropertyTree *queryTlsConfig() const = 0; + virtual const ISyncedPropertyTree *queryTlsConfig() const = 0; }; diff --git a/system/jlib/jsmartsock.ipp b/system/jlib/jsmartsock.ipp index e89c7850d1c..0aa065f95f1 100644 --- a/system/jlib/jsmartsock.ipp +++ b/system/jlib/jsmartsock.ipp @@ -68,7 +68,7 @@ protected: unsigned nextEndpointIndex; bool retry; bool tlsService = false; - Owned tlsConfig; + Owned tlsConfig; StringAttr issuer; unsigned retryInterval; @@ -104,7 +104,7 @@ public: virtual StringBuffer & getUrlStr(StringBuffer &str, bool useHostName); virtual bool isTlsService() const override { return tlsService; } - virtual const IPropertyTree *queryTlsConfig() const { return tlsConfig; }; + virtual const ISyncedPropertyTree *queryTlsConfig() const { return tlsConfig; }; const char *queryTlsIssuer() const { return issuer.str(); } }; diff --git a/system/security/securesocket/securesocket.cpp b/system/security/securesocket/securesocket.cpp index 2b416916d7d..b40d9905077 100644 --- a/system/security/securesocket/securesocket.cpp +++ b/system/security/securesocket/securesocket.cpp @@ -97,6 +97,13 @@ static void readBio(BIO* bio, StringBuffer& buf) } } + +interface ISecureSocketContextCallback : implements IInterface +{ + virtual unsigned getVersion() = 0; // Check the version of the context to see if the SSL context needs to be recreated + virtual SSL * createActiveSSL() = 0; // Must be called after getVersion() +}; + //Use a namespace to prevent clashes with a class of the same name in jhtree namespace securesocket { @@ -137,6 +144,7 @@ class CSecureSocket : implements ISecureSocket, public CInterface { private: SSL* m_ssl; + Linked contextCallback; Owned m_socket; bool m_verify; bool m_address_match; @@ -147,6 +155,7 @@ class CSecureSocket : implements ISecureSocket, public CInterface size32_t nextblocksize = 0; unsigned blockflags = BF_ASYNC_TRANSFER; unsigned blocktimeoutms = WAIT_FOREVER; + unsigned contextVersion; #ifdef USERECVSEM static Semaphore receiveblocksem; bool receiveblocksemowned; // owned by this socket @@ -158,8 +167,7 @@ class CSecureSocket : implements ISecureSocket, public CInterface public: IMPLEMENT_IINTERFACE; - CSecureSocket(ISocket* sock, SSL_CTX* ctx, bool verify = false, bool addres_match = false, CStringSet* m_peers = NULL, int loglevel=SSLogNormal, const char *fqdn = nullptr); - CSecureSocket(int sockfd, SSL_CTX* ctx, bool verify = false, bool addres_match = false, CStringSet* m_peers = NULL, int loglevel=SSLogNormal, const char *fqdn = nullptr); + CSecureSocket(ISocket* sock, int sockfd, ISecureSocketContextCallback * callback, bool verify = false, bool addres_match = false, CStringSet* m_peers = NULL, int loglevel=SSLogNormal, const char *fqdn = nullptr); ~CSecureSocket(); virtual int secure_accept(int logLevel); @@ -173,6 +181,22 @@ class CSecureSocket : implements ISecureSocket, public CInterface virtual size32_t writetms(void const* buf, size32_t size, unsigned timeoutms=WAIT_FOREVER); void readTimeout(void* buf, size32_t min_size, size32_t max_size, size32_t &size_read, unsigned timeout, bool useSeconds); + void checkForUpdatedContext() + { + //Check if a new ssl context should be created. + //No need for a critical section because the socket functions are never accessed by multiple threads at the same time + //It is possible that createActiveSSL() may be for a later version - but that will only mean that the same context + //is recreated when the version number is seen to have changed. + unsigned activeVersion = contextCallback->getVersion(); + if (activeVersion != contextVersion) + { + DBGLOG("CSecureSocket: Updating secure socket context from version %u to %u", contextVersion, activeVersion); + contextVersion = activeVersion; + SSL_free(m_ssl); + m_ssl = contextCallback->createActiveSSL(); + } + } + virtual StringBuffer& get_ssl_version(StringBuffer& ver) { @@ -467,38 +491,14 @@ Semaphore CSecureSocket::receiveblocksem(2); /************************************************************************** * CSecureSocket -- secure socket layer implementation using openssl * **************************************************************************/ -CSecureSocket::CSecureSocket(ISocket* sock, SSL_CTX* ctx, bool verify, bool address_match, CStringSet* peers, int loglevel, const char *fqdn) +CSecureSocket::CSecureSocket(ISocket* sock, int sockfd, ISecureSocketContextCallback * callback, bool verify, bool address_match, CStringSet* peers, int loglevel, const char *fqdn) + : contextCallback(callback) { + if (sock) + sockfd = sock->OShandle(); m_socket.setown(sock); - m_ssl = SSL_new(ctx); - - m_verify = verify; - m_address_match = address_match; - m_peers = peers;; - m_loglevel = loglevel; - m_isSecure = false; - - if(m_ssl == NULL) - { - throw MakeStringException(-1, "Can't create ssl"); - } - - // there is no MSG_NOSIGNAL or SO_NOSIGPIPE for SSL_write() ... -#ifndef _WIN32 - signal(SIGPIPE, SIG_IGN); -#endif - - SSL_set_fd(m_ssl, sock->OShandle()); - - if (fqdn) - m_fqdn.set(fqdn); -} - -CSecureSocket::CSecureSocket(int sockfd, SSL_CTX* ctx, bool verify, bool address_match, CStringSet* peers, int loglevel, const char *fqdn) -{ - //m_socket.setown(sock); - //m_socket.setown(ISocket::attach(sockfd)); - m_ssl = SSL_new(ctx); + contextVersion = callback->getVersion(); + m_ssl = callback->createActiveSSL(); m_verify = verify; m_address_match = address_match; @@ -652,6 +652,7 @@ bool CSecureSocket::verify_cert(X509* cert) int CSecureSocket::secure_accept(int logLevel) { + checkForUpdatedContext(); int err; err = SSL_accept(m_ssl); if(err == 0) @@ -1268,9 +1269,10 @@ static bool setVerifyCertsPEMBuffer(SSL_CTX *ctx, const char *caCertBuf, int caC return true; } -class CSecureSocketContext : public CInterfaceOf +class CSecureSocketContext : implements ISecureSocketContext, implements ISecureSocketContextCallback, public CInterface { private: + SecureSocketType sockettype; OwnedSSLCTX m_ctx; #if (OPENSSL_VERSION_NUMBER > 0x00909000L) const SSL_METHOD* m_meth = nullptr; @@ -1282,6 +1284,9 @@ class CSecureSocketContext : public CInterfaceOf bool m_address_match = false; Owned m_peers; StringAttr password; + CriticalSection cs; + Owned syncedConfig; + unsigned configVersion = 0; void setSessionIdContext() { @@ -1352,129 +1357,157 @@ class CSecureSocketContext : public CInterfaceOf throw makeStringExceptionV(-1, "Error loading CA certificates from %s", caCertsPathOrBuf); } -public: - CSecureSocketContext(SecureSocketType sockettype) - { - initContext(sockettype); - - SSL_CTX_set_mode(m_ctx, SSL_CTX_get_mode(m_ctx) | SSL_MODE_AUTO_RETRY); - } - - CSecureSocketContext(const char* certFileOrBuf, const char* privKeyFileOrBuf, const char* passphrase, SecureSocketType sockettype) - { - initContext(sockettype); - - // MCK TODO: should we set a default cipherList, as is done in other ctor (below) ? - - password.set(passphrase); - SSL_CTX_set_default_passwd_cb_userdata(m_ctx, (void*)password.str()); - SSL_CTX_set_default_passwd_cb(m_ctx, pem_passwd_cb); - - setCertificate(certFileOrBuf); - setPrivateKey(privKeyFileOrBuf); - - SSL_CTX_set_mode(m_ctx, SSL_CTX_get_mode(m_ctx) | SSL_MODE_AUTO_RETRY); - } - - CSecureSocketContext(const IPropertyTree* config, SecureSocketType sockettype) + void createNewContext(const IPropertyTree* config) { - assertex(config); - initContext(sockettype); - const char *cipherList = config->queryProp("cipherList"); - if (!cipherList || !*cipherList) - cipherList = "ECDH+AESGCM:DH+AESGCM:ECDH+AES256:DH+AES256:ECDH+AES128:DH+AES:ECDH+3DES:DH+3DES:RSA+AESGCM:RSA+AES:RSA+3DES:!aNULL:!MD5"; - SSL_CTX_set_cipher_list(m_ctx, cipherList); - - const char* passphrase = config->queryProp("passphrase"); - if (passphrase && *passphrase) + if (config) { - StringBuffer pwd; - decrypt(pwd, passphrase); - password.set(pwd); - SSL_CTX_set_default_passwd_cb_userdata(m_ctx, (void*)password.str()); - SSL_CTX_set_default_passwd_cb(m_ctx, pem_passwd_cb); - } + const char *cipherList = config->queryProp("cipherList"); + if (!cipherList || !*cipherList) + cipherList = "ECDH+AESGCM:DH+AESGCM:ECDH+AES256:DH+AES256:ECDH+AES128:DH+AES:ECDH+3DES:DH+3DES:RSA+AESGCM:RSA+AES:RSA+3DES:!aNULL:!MD5"; + SSL_CTX_set_cipher_list(m_ctx, cipherList); - const char *certFileOrBuf = config->queryProp("certificate_pem"); - if (!certFileOrBuf) - certFileOrBuf = config->queryProp("certificate"); - if (certFileOrBuf && *certFileOrBuf) - setCertificate(certFileOrBuf); + const char* passphrase = config->queryProp("passphrase"); + if (passphrase && *passphrase) + { + StringBuffer pwd; + decrypt(pwd, passphrase); + password.set(pwd); + SSL_CTX_set_default_passwd_cb_userdata(m_ctx, (void*)password.str()); + SSL_CTX_set_default_passwd_cb(m_ctx, pem_passwd_cb); + } - const char *privKeyFileOrBuf = config->queryProp("privatekey_pem"); - if (!privKeyFileOrBuf) - privKeyFileOrBuf = config->queryProp("privatekey"); - if (privKeyFileOrBuf && *privKeyFileOrBuf) - setPrivateKey(privKeyFileOrBuf); + const char *certFileOrBuf = config->queryProp("certificate_pem"); + if (!certFileOrBuf) + certFileOrBuf = config->queryProp("certificate"); + if (certFileOrBuf && *certFileOrBuf) + setCertificate(certFileOrBuf); - SSL_CTX_set_mode(m_ctx, SSL_CTX_get_mode(m_ctx) | SSL_MODE_AUTO_RETRY); + const char *privKeyFileOrBuf = config->queryProp("privatekey_pem"); + if (!privKeyFileOrBuf) + privKeyFileOrBuf = config->queryProp("privatekey"); + if (privKeyFileOrBuf && *privKeyFileOrBuf) + setPrivateKey(privKeyFileOrBuf); - m_verify = config->getPropBool("verify/@enable"); - m_address_match = config->getPropBool("verify/@address_match"); + m_verify = config->getPropBool("verify/@enable"); + m_address_match = config->getPropBool("verify/@address_match"); - if(m_verify) - { - const char *caCertPathOrBuf = config->queryProp("verify/ca_certificates/pem"); - if (!caCertPathOrBuf) - caCertPathOrBuf = config->queryProp("verify/ca_certificates/@path"); - if (caCertPathOrBuf && *caCertPathOrBuf) - setVerifyCerts(caCertPathOrBuf); - - bool acceptSelfSigned = config->getPropBool("verify/@accept_selfsigned"); - SSL_CTX_set_verify(m_ctx, SSL_VERIFY_PEER | SSL_VERIFY_FAIL_IF_NO_PEER_CERT | SSL_VERIFY_CLIENT_ONCE, (acceptSelfSigned) ? verify_callback_allow_selfSigned : verify_callback_reject_selfSigned); - - m_peers.setown(new CStringSet()); - const char* peersstr = config->queryProp("verify/trusted_peers"); - while(peersstr && *peersstr) + if(m_verify) { - StringBuffer onepeerbuf; - peersstr = strtok__(peersstr, "|", onepeerbuf); - if(onepeerbuf.length() == 0) - break; - - char* onepeer = onepeerbuf.detach(); - if (isdigit(*onepeer)) + const char *caCertPathOrBuf = config->queryProp("verify/ca_certificates/pem"); + if (!caCertPathOrBuf) + caCertPathOrBuf = config->queryProp("verify/ca_certificates/@path"); + if (caCertPathOrBuf && *caCertPathOrBuf) + setVerifyCerts(caCertPathOrBuf); + + bool acceptSelfSigned = config->getPropBool("verify/@accept_selfsigned"); + SSL_CTX_set_verify(m_ctx, SSL_VERIFY_PEER | SSL_VERIFY_FAIL_IF_NO_PEER_CERT | SSL_VERIFY_CLIENT_ONCE, (acceptSelfSigned) ? verify_callback_allow_selfSigned : verify_callback_reject_selfSigned); + + m_peers.setown(new CStringSet()); + const char* peersstr = config->queryProp("verify/trusted_peers"); + while(peersstr && *peersstr) { - char *dash = strrchr(onepeer, '-'); - if (dash) + StringBuffer onepeerbuf; + peersstr = strtok__(peersstr, "|", onepeerbuf); + if(onepeerbuf.length() == 0) + break; + + char* onepeer = onepeerbuf.detach(); + if (isdigit(*onepeer)) { - *dash = 0; - int last = atoi(dash+1); - char *dot = strrchr(onepeer, '.'); - *dot = 0; - int first = atoi(dot+1); - for (int i = first; i <= last; i++) + char *dash = strrchr(onepeer, '-'); + if (dash) + { + *dash = 0; + int last = atoi(dash+1); + char *dot = strrchr(onepeer, '.'); + *dot = 0; + int first = atoi(dot+1); + for (int i = first; i <= last; i++) + { + StringBuffer t; + t.append(onepeer).append('.').append(i); + m_peers->add(t.str()); + } + } + else { - StringBuffer t; - t.append(onepeer).append('.').append(i); - m_peers->add(t.str()); + m_peers->add(onepeer); } } else { m_peers->add(onepeer); } + free(onepeer); } - else - { - m_peers->add(onepeer); - } - free(onepeer); } } + + SSL_CTX_set_mode(m_ctx, SSL_CTX_get_mode(m_ctx) | SSL_MODE_AUTO_RETRY); + } + + void checkForUpdatedContext() + { + //Check if a new context should be created - it must be called within a critical section + if (syncedConfig) + { + unsigned activeVersion = syncedConfig->getVersion(); + if (activeVersion != configVersion) + { + DBGLOG("CSecureSocketContext: Updating secure socket context from version %u to %u", configVersion, activeVersion); + configVersion = activeVersion; + Owned config = syncedConfig->getTree(); + createNewContext(config); + } + } + } + +public: + IMPLEMENT_IINTERFACE + + CSecureSocketContext(const IPropertyTree* config, SecureSocketType _sockettype) : sockettype(_sockettype) + { + createNewContext(config); + } + + CSecureSocketContext(const ISyncedPropertyTree* _syncedConfig, SecureSocketType _sockettype) : syncedConfig(_syncedConfig), sockettype(_sockettype) + { + Owned config; + if (syncedConfig) + { + configVersion = syncedConfig->getVersion(); + config.setown(syncedConfig->getTree()); + } + createNewContext(config); } +//interface ISecureSocketContext ISecureSocket* createSecureSocket(ISocket* sock, int loglevel, const char *fqdn) { - return new CSecureSocket(sock, m_ctx, m_verify, m_address_match, m_peers, loglevel, fqdn); + return new CSecureSocket(sock, 0, this, m_verify, m_address_match, m_peers, loglevel, fqdn); } ISecureSocket* createSecureSocket(int sockfd, int loglevel, const char *fqdn) { - return new CSecureSocket(sockfd, m_ctx, m_verify, m_address_match, m_peers, loglevel, fqdn); + return new CSecureSocket(nullptr, sockfd, this, m_verify, m_address_match, m_peers, loglevel, fqdn); + } + +//interface ISecureSocketContextCallback + virtual unsigned getVersion() + { + CriticalBlock block(cs); + checkForUpdatedContext(); + return configVersion; } + virtual SSL * createActiveSSL() + { + //If this function is called it is either a new socket or getVersion() has been called to check it is up to date + CriticalBlock block(cs); + return SSL_new(m_ctx); + } + }; class CRsaCertificate : implements ICertificate, public CInterface @@ -1961,51 +1994,61 @@ extern "C" { SECURESOCKET_API ISecureSocketContext* createSecureSocketContext(SecureSocketType sockettype) { - return new securesocket::CSecureSocketContext(sockettype); + return new securesocket::CSecureSocketContext((ISyncedPropertyTree *)nullptr, sockettype); } -SECURESOCKET_API ISecureSocketContext* createSecureSocketContextEx(const char* certFileOrBuf, const char* privKeyFileOrBuf, const char* passphrase, SecureSocketType sockettype) +SECURESOCKET_API ISecureSocketContext* createSecureSocketContextSynced(const ISyncedPropertyTree * config, SecureSocketType sockettype) { - return new securesocket::CSecureSocketContext(certFileOrBuf, privKeyFileOrBuf, passphrase, sockettype); + return new securesocket::CSecureSocketContext(config, sockettype); } -SECURESOCKET_API ISecureSocketContext* createSecureSocketContextEx2(const IPropertyTree* config, SecureSocketType sockettype) + +SECURESOCKET_API ISecureSocketContext* createSecureSocketContextSecret(const char *issuer, SecureSocketType sockettype) { - if (config == NULL) - return createSecureSocketContext(sockettype); + Owned info = getIssuerTlsSyncedConfig(issuer); + return createSecureSocketContextSynced(info, sockettype); +} - return new securesocket::CSecureSocketContext(config, sockettype); -} -SECURESOCKET_API ISecureSocketContext* createSecureSocketContextSSF(ISmartSocketFactory* ssf) +SECURESOCKET_API ISecureSocketContext* createSecureSocketContextSecretSrv(const char *issuer, bool requireMtlsFlag) { - if (ssf == nullptr || !ssf->queryTlsConfig()) - return createSecureSocketContext(ClientSocket); + if (requireMtlsFlag && !queryMtls()) + throw makeStringException(-100, "TLS secure communication requested but not configured"); - return new securesocket::CSecureSocketContext(ssf->queryTlsConfig(), ClientSocket); + Owned info = getIssuerTlsSyncedConfig(issuer); + if (!info->isValid()) + throw makeStringException(-101, "TLS secure communication requested but not configured (2)"); + + return createSecureSocketContextSynced(info, ServerSocket); } -SECURESOCKET_API ISecureSocketContext* createSecureSocketContextSecret(const char *issuer, SecureSocketType sockettype) +IPropertyTree * createSecureSocketConfig(const char* certFileOrBuf, const char* privKeyFileOrBuf, const char* passphrase) { - Owned info = getIssuerTlsServerConfig(issuer); - //if the secret doesn't exist doesn't exist just go on without it. IF it is required the tls connection will fail. - //This is primarily for client side... server side would probably use the explict ptree config or explict cert param at least for now. - if (info) - return createSecureSocketContextEx2(info, sockettype); - else - return createSecureSocketContext(sockettype); + if (!certFileOrBuf && !privKeyFileOrBuf && !passphrase) + return nullptr; + + Owned config = createPTree("ssl"); + if (certFileOrBuf) + config->setProp("certificate", certFileOrBuf); + if (privKeyFileOrBuf) + config->setProp("privatekey", privKeyFileOrBuf); + if (passphrase) + config->setProp("passphrase", passphrase); + return config.getClear(); } -SECURESOCKET_API ISecureSocketContext* createSecureSocketContextSecretSrv(const char *issuer, bool requireMtlsFlag) +SECURESOCKET_API ISecureSocketContext* createSecureSocketContextSSF(ISmartSocketFactory* ssf) { - if (requireMtlsFlag && !queryMtls()) - throw makeStringException(-100, "TLS secure communication requested but not configured"); + if (ssf == nullptr) + return createSecureSocketContext(ClientSocket); - Owned info = getIssuerTlsServerConfig(issuer); - if (!info) - throw makeStringException(-101, "TLS secure communication requested but not configured (2)"); + return createSecureSocketContextSynced(ssf->queryTlsConfig(), ClientSocket); +} - return createSecureSocketContextEx2(info, ServerSocket); +//Legacy factory interfaces +SECURESOCKET_API ISecureSocketContext* createSecureSocketContextEx2(const IPropertyTree* config, SecureSocketType sockettype) +{ + return new securesocket::CSecureSocketContext(config, sockettype); } SECURESOCKET_API ICertificate *createCertificate() @@ -2123,7 +2166,7 @@ class CSecureSmartSocketFactory : public CSmartSocketFactory CSecureSmartSocketFactory(IPropertyTree &service, bool _retry, unsigned _retryInterval, unsigned _dnsInterval) : CSmartSocketFactory(service, _retry, _retryInterval, _dnsInterval) { - secureContext.setown(createSecureSocketContextEx2(queryTlsConfig(), ClientSocket)); + secureContext.setown(createSecureSocketContextSynced(queryTlsConfig(), ClientSocket)); } virtual ISmartSocket *connect_timeout(unsigned timeoutms) override diff --git a/system/security/securesocket/securesocket.hpp b/system/security/securesocket/securesocket.hpp index 2bf1758d92e..cc790676f7d 100644 --- a/system/security/securesocket/securesocket.hpp +++ b/system/security/securesocket/securesocket.hpp @@ -87,12 +87,18 @@ typedef ISecureSocketContext* (*createSecureSocketContextSecret_t)(const char *m extern "C" { -SECURESOCKET_API ISecureSocketContext* createSecureSocketContext(SecureSocketType); -SECURESOCKET_API ISecureSocketContext* createSecureSocketContextEx(const char* certFileOrBuf, const char* privKeyFileOrBuf, const char* passphrase, SecureSocketType); -SECURESOCKET_API ISecureSocketContext* createSecureSocketContextEx2(const IPropertyTree* config, SecureSocketType); -SECURESOCKET_API ISecureSocketContext* createSecureSocketContextSSF(ISmartSocketFactory* ssf); +//The following allow the creation of a secure socket context where the certificates will automatically be updated when they expire. +SECURESOCKET_API ISecureSocketContext* createSecureSocketContextSynced(const ISyncedPropertyTree * config, SecureSocketType sockettype); // Will become the primary (only) factory method SECURESOCKET_API ISecureSocketContext* createSecureSocketContextSecret(const char *mtlsSecretName, SecureSocketType); SECURESOCKET_API ISecureSocketContext* createSecureSocketContextSecretSrv(const char *mtlsSecretName, bool requireMtlsConfig); +SECURESOCKET_API ISecureSocketContext* createSecureSocketContextSSF(ISmartSocketFactory* ssf); + +//Helper function to aid migration to the functions above. This should eventually be removed. +SECURESOCKET_API IPropertyTree * createSecureSocketConfig(const char* certFileOrBuf, const char* privKeyFileOrBuf, const char* passphrase); + +//Legacy factory methods - should be phased out. +SECURESOCKET_API ISecureSocketContext* createSecureSocketContext(SecureSocketType); +SECURESOCKET_API ISecureSocketContext* createSecureSocketContextEx2(const IPropertyTree* config, SecureSocketType); SECURESOCKET_API ICertificate *createCertificate(); SECURESOCKET_API int signCertificate(const char* csr, const char* ca_certificate, const char* ca_privkey, const char* ca_passphrase, int days, StringBuffer& certificate); };