-
Notifications
You must be signed in to change notification settings - Fork 304
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
HPCC-30411 Add support for dynamically updating TLS config #17851
Conversation
https://track.hpccsystems.com/browse/HPCC-30411 |
system/jlib/jsecrets.cpp
Outdated
@@ -1349,18 +1402,10 @@ IPropertyTree *createIssuerTlsClientConfig(const char *issuer, bool acceptSelfSi | |||
return info.getClear(); | |||
} | |||
|
|||
IPropertyTree *getIssuerTlsServerConfig(const char *name) | |||
static ISyncedPropertyTree * createIssuerTlsServerConfig(const char *name) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I know it's existing code (but since you are changing these).. I've been slowly changing the "name" parameter on these routines to "issuer" to be more self documenting. Or if you prefer "issuerName".
The issuer which is part of the path to the secret act like categories for the various trust zones: public, local, remote, etc.
if (!isEmptyString(passPhrase)) | ||
info->setProp("passphrase", passPhrase); // encrypted | ||
|
||
Owned<ISyncedPropertyTree> entry = createSyncedPropertyTree(info); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Not really new, although changing, but there may be a timing issue here because the meaning of "local" is being overridden.
In practical terms, looking up "local" means, "get tls configuration based on the secret at '/opt/HPCCsystems/secrets/certificates/local' ".
The various ways of configuring bare metal need consideration.
One way of configuring bare metal could be to put TLS info into the secret locations we use in kubernetes.
The other way of configuring bare-metal would be to fill in the custom TLS configuration values. queryHPCCPKIKeyFiles(&cert, &pubKey, &privKey, &passPhrase) above.
Here, a tree based on the custom bare-metal configuration values are turned into a tree that is cached to override the contents at the secret location.
If we ever (accidently?) use both the tree retrieved from cache would be whichever came first.
So I guess we need to make sure if we are going to override "local" we always override "local"? Maybe init this entry at bare-metal startup if the config parameters are used?
The alternative would be to cache under a different name, a name implying "bare-metal-mtls". But we'd have to track down what was currently depending on it being called "local".
system/jlib/jsecrets.cpp
Outdated
config.setown(createPTree(issuer)); | ||
|
||
//GH-Tony should this condition be removed? | ||
if (strieq(issuer, "remote")||strieq(issuer, "local")) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It looks like you've mixed some "ClientConfig" lines in with "ServerConfig" lines.
When inside ClientConfig these lines imply that we don't currently setup issuer client certificates for issuers other than remote and local.
Using this logic on the ServerConfig won't work, because server side we always want the certs included.
system/jlib/jptree.hpp
Outdated
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 vali and consistent until it is released. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
"valid"
@ghalliday I've added some comments, but the general direction looks good. Next steps, I think we want to:
When those code paths are cleaned up we should start to see any complications that might be there. Be careful with the idea of caching the trusted_peers because it is not tied directly to the issuer so needs another level of cache if cached. For example, roxie can have multiple services defined and theoretically could have: roxie: The trusted config with trusted peers probably doesn't need caching at all, it can just be an object held by the secure context and one level away from the secret that is checked for being stale. But then refreshing needs to preserve the trusted peers. Not sure if that explanation made sense, but it will come out in the wash once you dive in. |
c641632
to
d1682e4
Compare
Almost complete, but one remaining issue in dafsserver.cpp(~150) createSecureSocket overriding an option needs some thought |
eb41e66
to
ee85cf9
Compare
@afishbeck @mckellyln @jakesmith I think this is now ready for review for logical flaws etc. (Jake for the dafilesrv change.) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@afishbeck @mckellyln @jakesmith I think this is now ready for review for logical flaws etc. (Jake for the dafilesrv change.) It has not been tested at all, and I plan to review it myself tomorrow before starting testing.
@ghalliday - I have only looked at the dafilesrv changes, but they look fine.
@ghalliday - however, there's a build failure relating to an unreferenced symbol 'createSecureSocketConfig' building dafsclint
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
One bug a few typos, and some code that needs to used synced trees.
system/jlib/jptree.hpp
Outdated
//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; | ||
//An indication that the property tree may be out of date because it couldn'tt be resynchronized. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
couldn'tt
{ | ||
return secretHash; | ||
} | ||
virtual bool isValid() const override | ||
{ | ||
CriticalBlock block(secretCs); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
is the critical section really needed? Should secretHash be atomic? Revisit when background updating is implemented.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
What is the point of an isValid() function like this? As soon as you release the critsec, the value you are returning may no longer be correct...
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is typically called at startup to check that the configuration has been set up. The calling code is cleaner since it avoids the problem with freeing the returning the linked object.
system/jlib/jsecrets.cpp
Outdated
if (!checkFileExists(filepath)) | ||
return nullptr; | ||
//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 condig tree |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
condig->config
system/jlib/jsecrets.cpp
Outdated
IPropertyTree *verify = ensurePTree(tlsConfig, "verify"); | ||
verify->setProp("trusted_peers", trusted_peers); | ||
return tlsConfig.getClear(); | ||
const IPropertyTree *getIssuerTlsServerConfigWithTrustedPeers(const char *issuer, const char *trusted_peers) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This should return a synced tree and fix the caller - then roxie server certificates will not expire.
throw makeStringException(-100, "TLS secure communication requested but not configured"); | ||
|
||
Owned<const ISyncedPropertyTree> info = getIssuerTlsSyncedConfig(issuer); | ||
if (!!info->isValid()) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Oops double negative!!
{ | ||
if (requireMtlsFlag && !queryMtls()) | ||
throw makeStringException(-100, "TLS secure communication requested but not configured"); | ||
if (ssf == nullptr || !ssf->queryTlsConfig()) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
could remove the second condition, since it will fall through the the same code below.
@@ -112,6 +112,12 @@ static class _securitySettingsClient | |||
} | |||
} | |||
|
|||
const IPropertyTree * getSecureConfig() | |||
{ | |||
//Later: return a synced tree... |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
When? Is that for a different PR? Is there a Jira open to revisit if so?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
HPCC-30753
//--------------------------------------------------------------------------------------------------------------------- | ||
|
||
//More: Should this move inside PTree? It may allow a more efficient hash caclulaton. | ||
unsigned getPropertyTreeHash(const IPropertyTree & source, unsigned hashcode) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Feels like this will be an expensive calculation on a large tree, that would be avoided if you could store hash of each component when it was created. But maybe it's not going to be used on large trees?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Created HPCC-30752 to address optimizing it. Currently it will be used on small trees.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Some minor comments. A bit too large to review properly
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@ghalliday - some minor comments/questions.
: CSyncedCertificateBase(_issuer), trustedPeers(_trustedPeers), isClientConnection(_isClientConnection), acceptSelfSigned(_acceptSelfSigned), addCACert(_addCACert), disableMTLS(_disableMTLS) | ||
{ | ||
secret.setown(resolveSecret("certificates", issuer, nullptr, nullptr)); | ||
createConfig(); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
looks like createConfig (and other methods isStale/isValid) depend on 'secret' being instantiated, but resolveSecret can return null afaics.
Should the ctor error out if it can't resolve the secret, or should isValid() check if secret for null (and other methods)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
resolve secret can never return null - it always returns an object which can be used to find out if the value is defined, and may be updated behind the scenes.
system/jlib/jptree.cpp
Outdated
return hashcode; | ||
} | ||
|
||
class UnchangingPropertyTree : extends CInterfaceOf<ISyncedPropertyTree> |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Not that keen on the class name.
Maybe SyncedIPTWrapper or SyncedPropertyTreeWrapper
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I agree, changed
system/jlib/jptree.hpp
Outdated
virtual bool getProp(MemoryBuffer & result, const char * xpath) const = 0; | ||
virtual bool getProp(StringBuffer & result, const char * xpath) 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. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Not necessarily 'secret/certificate specific.
Change comment to say when the property tree changes etc. ?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
cleaned up the comments to reflect the more general applicability
system/jlib/jptree.hpp
Outdated
virtual unsigned getVersion() const = 0; | ||
//An indication that the property tree may be out of date because it couldn't be resynchronized. | ||
virtual bool isStale() const = 0; | ||
virtual bool isValid() const = 0; // Was an entry found for this secret? |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Not necessarily 'secret/certificate specific.
|
||
IPropertyTree *verify = config->queryPropTree("verify"); | ||
//For now only the "public" issuer implies client certificates are not required | ||
verify->setPropBool("@enable", !disableMTLS && (isClientConnection || !strieq(issuer, "public"))); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
is 'verify' guaranteed to exist by this state? does it need to be null checked.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
yes - I will add an assert for safety
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
yes meaning it is guaranteed to exist
: CSyncedCertificateBase(nullptr), addCACert(_addCACert) | ||
{ | ||
secret.setown(resolveSecret(_category, _secretName, nullptr, nullptr)); | ||
if (!secret->isValid()) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
it looks like 'secret' could be null?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
no it can't - see above.
@@ -1268,9 +1268,10 @@ static bool setVerifyCertsPEMBuffer(SSL_CTX *ctx, const char *caCertBuf, int caC | |||
return true; | |||
} | |||
|
|||
class CSecureSocketContext : public CInterfaceOf<ISecureSocketContext> | |||
class CSecureSocketContext : implements ISecureSocketContext, implements ISecureSocketContextCallback, public CInterface |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
use a CInterfaceOf< to remove 1 vmt?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I could but the IMPLEMENT_USING gets a bit ugly - not enough instances to worry either way.
#else | ||
privateKeyFName = environment->getPrivateKeyPath(keyPairName); | ||
const char *privateKeyFName = privateKeyFName = environment->getPrivateKeyPath(keyPairName); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
const char *privateKeyFName = privateKeyFName ?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Interesting. I wonder how that compiles - because the declaration comes into effect before the value is evaluated.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Looks good, one question posted.
Not 100% clear to me about isValid() meaning a prop tree exists and/or a secret (if not nullptr) is present.
system/jlib/jptree.cpp
Outdated
|
||
//--------------------------------------------------------------------------------------------------------------------- | ||
|
||
//More: Should this move inside PTree? It may allow a more efficient hash caclulaton. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Putting this in PTree could definitely avoid the binary copies. I also always think that source.getElements("*");
looks slow when walking full PTrees. If internally you could get away with something more like this->checkChildren()->getIterator();
would it be much faster?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Created a jira to make it internal. I'm not too concerned about the efficiency at the moment.
system/jlib/jptree.hpp
Outdated
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 sequence number which changes whenever the secret actually changes - so that a caller can determine |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
"sequence number", hash, or just unique identifier?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Clarified.
system/jlib/jsecrets.cpp
Outdated
if (!isClientConnection || !strieq(issuer, "public")) | ||
updateCertificateFromSecret(secretInfo); | ||
|
||
if (!isClientConnection || addCACert) // same condition as before, but is it correct? |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Assuming addCACert gets set the same as before, the reason this should be correct is that addCACert is usually true. A client hitting a public issuer is the case where we don't want the cacert 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.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Moved your comment above into the source.
roxie/ccd/ccdmain.cpp
Outdated
if (serviceTLS) | ||
{ | ||
protocol = "ssl"; | ||
#ifdef _USE_OPENSSL | ||
if (isContainerized()) | ||
{ | ||
//MORE: Why is this containerized only? Should it check for this in both, and fall back to the | ||
//old config if none found and non-containerized? |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes, absolutely agreed, that was the ultimate goal. Bare-metal and fusion should be able to treat certificate config as issuer secrets if they want, and if that isn't present fall back to the old style config.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
created hpcc-30754
8f6d937
to
f017877
Compare
rebased and another commit added to respond to review comments. |
#ifdef _USE_OPENSSL | ||
if (isContainerized()) | ||
protocol = "ssl"; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This roxie config change looks good... but may want to fix line wrapping in commit message?
// 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. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Just a note, to clarify a little further: caCert prevents a secure client from trying to use the issuer secret at all. This is necessary because internal components don't get a public issuer secret, they don't need one. It's ok because the OS level installed CA certs should work for public services.
When setting up a secure client to hit services in the current environment/namespace, config options like issuer, caCert, and selfSigned come from the list of services in our global config, for example:
global:
services:
- name: eclqueries
type: eclqueries
caCert: false
class: esp
issuer: public
port: 28002
public: true
selfSigned: true
tls: true
- name: roxie
type: roxie
caCert: true
class: roxie
issuer: remote
port: 29876
public: true
selfSigned: false
target: roxie
tls: true
@afishbeck I have traced through the execution and fixed a few problems. Also added some options to roxie to allow issuer in bare-metal and disable mtls. Please take a look at the follow on commits (I think you have reviewed one of them) |
NOTE: This code has a problem- that the expiry date is stored as a property in the secret tree - so that even if the contents of the secret is identical the hash will change and the config will be updated. Since this is once every 10 minute I think that is acceptable as a short term solution, but @afishbeck please let me know if you think otherwise. |
As we discussed offline, I don't think its a problem. It will just be a thorough test of your secret renewal code. Later I would optimize this away though so that the renewal only happens when something really has changed. |
633ed1a
to
6f170ef
Compare
HPCC-30754 Allow roxie to use issuer based tls in bare-metal configuration Signed-off-by: Gavin Halliday <[email protected]>
6f170ef
to
551169e
Compare
20afbe8
into
hpcc-systems:candidate-9.4.x
Type of change:
Checklist:
Smoketest:
Testing: