Skip to content

Commit

Permalink
Retrofit to use IEsdlScriptContext secrets
Browse files Browse the repository at this point in the history
  • Loading branch information
Tim Klemm committed Nov 16, 2023
1 parent 8378d3c commit 8a23f37
Show file tree
Hide file tree
Showing 2 changed files with 79 additions and 154 deletions.
128 changes: 25 additions & 103 deletions esp/services/esdl_svc_engine/esdl_binding.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1394,7 +1394,7 @@ EsdlServiceImpl::IUpdatableGateway* EsdlServiceImpl::createInlineGateway(const I
return new CLegacyUrlGateway(gw, gwName, gwUrl);
}

void EsdlServiceImpl::applyGatewayUpdates(IPTreeIterator& gwIt, const UpdatableGateways& updatables, GatewayUpdaters& updaters, TransactionSecrets& secrets) const
void EsdlServiceImpl::applyGatewayUpdates(IPTreeIterator& gwIt, const UpdatableGateways& updatables, GatewayUpdaters& updaters, IEsdlScriptContext* scriptContext) const
{
ForEach(gwIt)
{
Expand All @@ -1405,9 +1405,9 @@ void EsdlServiceImpl::applyGatewayUpdates(IPTreeIterator& gwIt, const UpdatableG
UpdatableGateways::const_iterator ugIt = updatables.find(name);
if (updatables.end() == ugIt)
continue;
Owned<IGatewayUpdater> updater(ugIt->second->getUpdater(updaters, secrets));
Owned<IGatewayUpdater> updater(ugIt->second->getUpdater(updaters, scriptContext));
if (updater)
updater->updateGateway(gw, "allowPublishedGatewayUsage");
updater->updateGateway(gw);
}
}

Expand Down Expand Up @@ -1799,7 +1799,7 @@ void EsdlServiceImpl::prepareFinalRequest(IEspContext &context,
if (ctxGateways)
{
gwIt.setown(ctxGateways->getElements("Gateway"));
applyGatewayUpdates(*gwIt, mgcIt->second.targetContext, updaters, secrets);
applyGatewayUpdates(*gwIt, mgcIt->second.targetContext, updaters, scriptContext);
}
}
toXML(tgtctx.get(), reqProcessed);
Expand All @@ -1822,7 +1822,7 @@ void EsdlServiceImpl::prepareFinalRequest(IEspContext &context,
// used in subsequent transactions.
Owned<IPTree> copy(createPTreeFromIPT(cfgGateways));
gwIt.setown(copy->getElements("Gateway"));
applyGatewayUpdates(*gwIt, mgcIt->second.legacyTransform, updaters, secrets);
applyGatewayUpdates(*gwIt, mgcIt->second.legacyTransform, updaters, scriptContext);
}
else
gwIt.setown(cfgGateways->getElements("Gateway"));
Expand Down Expand Up @@ -1905,51 +1905,22 @@ EsdlServiceImpl::~EsdlServiceImpl()
}
}

IPTree* EsdlServiceImpl::TransactionSecrets::getVaultSecret(const char* category, const char* vaultId, const char* name)
{
Owned<IPTree> secret(lookup(category, vaultId, name));
if (!secret)
{
secret.setown(::getVaultSecret(category, vaultId, name));
if (secret)
store(*secret, category, vaultId, name);
}
return secret.getClear();
}

IPTree* EsdlServiceImpl::TransactionSecrets::getSecret(const char* category, const char* name)
{
Owned<IPTree> secret(lookup(category, "", name));
if (!secret)
{
secret.setown(::getVaultSecret(category, "", name));
if (secret)
store(*secret, category, "", name);
}
return secret;
}

IPTree* EsdlServiceImpl::TransactionSecrets::lookup(const char* category, const char* vaultId, const char* name) const
EsdlServiceImpl::IGatewayUpdater* EsdlServiceImpl::CUpdatableGateway::getUpdater(GatewayUpdaters& updaters, TransactionSecrets& secrets) const
{
Key key = std::make_tuple<std::string, std::string, std::string>(category ? category : "", vaultId ? vaultId : "", name ? name : "");
Cache::const_iterator it = cache.find(key);
if (it != cache.end())
GatewayUpdaters::iterator it = updaters.find(updatersKey);
if (it != updaters.end())
return it->second.getLink();
return nullptr;
}

void EsdlServiceImpl::TransactionSecrets::store(IPTree& secret, const char* category, const char* vaultId, const char* name)
{
Key key = std::make_tuple<std::string, std::string, std::string>(category ? category : "", vaultId ? vaultId : "", name ? name : "");
cache[key].set(&secret);
Owned<IGatewayUpdater> spawned(getUpdater(secrets));
updaters[updatersKey].setown(spawned.getLink());
return spawned.getClear();
}

EsdlServiceImpl::IGatewayUpdater* EsdlServiceImpl::CUpdatableGateway::getUpdater(GatewayUpdaters& updaters, TransactionSecrets& secrets) const
EsdlServiceImpl::IGatewayUpdater* EsdlServiceImpl::CUpdatableGateway::getUpdater(GatewayUpdaters& updaters, IEsdlScriptContext* scriptContext) const
{
GatewayUpdaters::iterator it = updaters.find(updatersKey);
if (it != updaters.end())
return it->second.getLink();
Owned<IGatewayUpdater> spawned(getUpdater(secrets));
Owned<IGatewayUpdater> spawned(getUpdater(scriptContext));
updaters[updatersKey].setown(spawned.getLink());
return spawned.getClear();
}
Expand Down Expand Up @@ -1997,7 +1968,12 @@ EsdlServiceImpl::CLegacyUrlGateway* EsdlServiceImpl::CLegacyUrlGateway::getUpdat
return LINK(const_cast<CLegacyUrlGateway*>(this));
}

void EsdlServiceImpl::CLegacyUrlGateway::updateGateway(IPTree& gw, const char*)
EsdlServiceImpl::CLegacyUrlGateway* EsdlServiceImpl::CLegacyUrlGateway::getUpdater(IEsdlScriptContext*) const
{
return LINK(const_cast<CLegacyUrlGateway*>(this));
}

void EsdlServiceImpl::CLegacyUrlGateway::updateGateway(IPTree& gw)
{
gw.setProp("@url", url);
}
Expand All @@ -2019,80 +1995,26 @@ EsdlServiceImpl::CLegacyUrlGateway::CLegacyUrlGateway(const IPTree& gw, const ch
updateURLCredentials(url, username, password);
}

void EsdlServiceImpl::CLocalSecretGateway::CUpdater::updateGateway(IPTree& gw, const char* requiredUsage)
{
if (!secret)
throw makeStringExceptionV(-1, "gateway %s: 'esp' category secret '%s' not found", "?", entry->secretId.str());
entry->doUpdate(gw, *secret, requiredUsage);
}

EsdlServiceImpl::CLocalSecretGateway::CUpdater::CUpdater(const CLocalSecretGateway& _entry, TransactionSecrets& secrets)
{
entry.set(&_entry);
if (entry->vaultId.isEmpty())
secret.setown(secrets.getSecret("esp", entry->secretName));
else
secret.setown(secrets.getVaultSecret("esp", entry->vaultId.str(), entry->secretName));
}

EsdlServiceImpl::IGatewayUpdater* EsdlServiceImpl::CLocalSecretGateway::getUpdater(TransactionSecrets& secrets) const
{
return new CUpdater(*this, secrets);
}

EsdlServiceImpl::CLocalSecretGateway::CLocalSecretGateway(const IPTree& gw, const char* gwName, const char* gwUrl, const char* classPrefix)
: CUpdatableGateway(gwName)
{
secretId.append(gwUrl + gwLocalSecretPrefixLength);
StringArray tokens;
tokens.appendList(secretId, ":", true);
const char* vaultToken = nullptr;
const char* secretToken = nullptr;
switch (tokens.ordinality())
{
case 1: // secret name without vault name
secretToken = tokens.item(0);
break;
case 2: // secret name after vault name
vaultToken = tokens.item(0);
secretToken = tokens.item(1);
break;
default:
throw makeStringExceptionV(-1, "gateway %s: '%s' is not of the form \"[ vault-id ':' ] secret-name\"", gwName, secretId.str());
}

if (!isEmptyString(vaultToken))
vaultId.set(vaultToken);
secretName.append(classPrefix).append(secretToken);
}

void EsdlServiceImpl::CLocalSecretGateway::doUpdate(IPTree& gw, const IPTree& secret, const char* requiredUsage) const
{
if (!isEmptyString(requiredUsage) && !secret.getPropBool(requiredUsage))
throw makeStringExceptionV(-1, "gateway %s: secret '%s' does not allow '%s' usage", updatersKey.c_str(), secretId.str(), requiredUsage);
}

void EsdlServiceImpl::CHttpConnectGateway::doUpdate(IPTree& gw, const IPTree& secret, const char* requiredUsage) const
void EsdlServiceImpl::CHttpConnectGateway::doUpdate(IPTree& gw, const IPTree& secret) const
{
CLocalSecretGateway::doUpdate(gw, secret, requiredUsage);
StringBuffer url;
if (!secret.getProp("url", url.clear()) || url.isEmpty())
throw makeStringExceptionV(-1, "gateway %s: secret '%s' missing required 'url' property; credential-only secrets not supported", gw.queryProp("@name"), secretId.str());
throw makeStringExceptionV(-1, "gateway %s: secret '%s' missing required 'url' property; credential-only secrets not supported", gw.queryProp("@name"), identifier.str());
const char* username = secret.queryProp("username");
bool omitCredentials = secret.getPropBool("omitCredentials");
if (isEmptyString(username) && !omitCredentials)
throw makeStringExceptionV(-1, "gateway %s: secret '%s' missing expected 'username' property; set 'omitCredentials` property to 'true' if credentials are not required", gw.queryProp("@name"), secretId.str());
throw makeStringExceptionV(-1, "gateway %s: secret '%s' missing expected 'username' property; set 'omitCredentials` property to 'true' if credentials are not required", gw.queryProp("@name"), identifier.str());
if (!isEmptyString(username) && omitCredentials)
throw makeStringExceptionV(-1, "gateway %s: secret '%s' contains 'username' and sets 'omitCredentials'", gw.queryProp("@name"), secretId.str());
throw makeStringExceptionV(-1, "gateway %s: secret '%s' contains 'username' and sets 'omitCredentials'", gw.queryProp("@name"), identifier.str());
const char* password = secret.queryProp("password");
if (isEmptyString(username) && password)
throw makeStringExceptionV(-1, "gateway %s: '%s' invalid use of password without username", gw.queryProp("@name"), secretId.str());
throw makeStringExceptionV(-1, "gateway %s: '%s' invalid use of password without username", gw.queryProp("@name"), identifier.str());
updateURLCredentials(url, username, password);
gw.setProp("@url", url);
}

EsdlServiceImpl::CHttpConnectGateway::CHttpConnectGateway(const IPTree& gw, const char* gwName, const char* gwUrl)
: CLocalSecretGateway(gw, gwName, gwUrl, "http-connect-")
: TLocalSecretGateway<HttpConnectSecretId>(gw, gwName, gwUrl)
{
}

Expand Down
105 changes: 54 additions & 51 deletions esp/services/esdl_svc_engine/esdl_binding.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -72,34 +72,6 @@ typedef int (*cpp_service_method_t)(const char* CtxXML, const char* ReqXML, Stri

class EsdlServiceImpl : public CInterface, implements IEspService
{
private:
/**
* @brief Cache of secrets accessed as part of transaction processing.
*
* In the context of a single transaction, multiple requests for the same secret should always
* return the same secret data for each request. Cached data does not expire and there is no
* option to reload data.
*
* TODO: Refactor to support additional use cases
* - Provide ESDL script operation support so mysql (now) and http-post-xml (eventually)
* can benefit secret reuse.
* - Encapsulate non-jsecrets access to support non-standard secret sources, unless
* jsecrets is changed to provide this encapsulation.
*/
class TransactionSecrets
{
private:
using Key = std::tuple<std::string, std::string, std::string>;
using Cache = std::map<Key, Owned<IPTree>>;
Cache cache;
public:
IPTree* getVaultSecret(const char* category, const char* vaultId, const char* name);
IPTree* getSecret(const char* category, const char* name);
protected:
IPTree* lookup(const char* category, const char* vaultId, const char* name) const;
void store(IPTree& secret, const char* category, const char* vaultId, const char* name);
};

private:
/**
* @brief Abstraction of a per-transaction gateway updater.
Expand All @@ -115,7 +87,7 @@ class EsdlServiceImpl : public CInterface, implements IEspService
*/
interface IGatewayUpdater : public IInterface
{
virtual void updateGateway(IPTree& gw, const char* requiredUsage) = 0;
virtual void updateGateway(IPTree& gw) = 0;
};
using GatewayUpdaters = std::map<std::string, Owned<IGatewayUpdater>>;

Expand Down Expand Up @@ -143,6 +115,7 @@ class EsdlServiceImpl : public CInterface, implements IEspService
interface IUpdatableGateway : public IInterface
{
virtual IGatewayUpdater* getUpdater(GatewayUpdaters& updaters, TransactionSecrets& secrets) const = 0;
virtual IGatewayUpdater* getUpdater(GatewayUpdaters& updaters, IEsdlScriptContext* scriptContext) const = 0;
};
using UpdatableGateways = std::map<std::string, Owned<IUpdatableGateway>>;

Expand All @@ -164,11 +137,13 @@ class EsdlServiceImpl : public CInterface, implements IEspService
{
public:
virtual IGatewayUpdater* getUpdater(GatewayUpdaters& updaters, TransactionSecrets& secrets) const override;
virtual IGatewayUpdater* getUpdater(GatewayUpdaters& updaters, IEsdlScriptContext* scriptContext) const override;
protected:
std::string updatersKey;
protected:
CUpdatableGateway(const char* gwName);
virtual IGatewayUpdater* getUpdater(TransactionSecrets& secrets) const = 0;
virtual IGatewayUpdater* getUpdater(IEsdlScriptContext* scriptContext) const = 0;
bool updateURLCredentials(StringBuffer& url, const char* username, const char* password) const;
};

Expand All @@ -189,8 +164,9 @@ class EsdlServiceImpl : public CInterface, implements IEspService
{
protected: // CUpdatableGateway
virtual CLegacyUrlGateway* getUpdater(TransactionSecrets&) const override;
virtual CLegacyUrlGateway* getUpdater(IEsdlScriptContext*) const override;
public: // IGatewayUpdater
virtual void updateGateway(IPTree& gw, const char*) override;
virtual void updateGateway(IPTree& gw) override;
protected:
StringBuffer url;
public:
Expand All @@ -202,58 +178,85 @@ class EsdlServiceImpl : public CInterface, implements IEspService
* @brief Concrete extension of `CUpdatableGateway` for handling local secrets that is always
* extended with secret usage-specific logic.
*
* - Ensures `Gateway/@url` matches the pattern `"local-secret:" [ vault-id ":" ] secret-name".`
* - Ensures `Gateway/@url` matches the pattern `"local-secret:" [ vault-id "::" ] secret-name [ "::" version ]`.
* - `secret-name` is required always
* - `vault-id` is required before `version` can be specified
* - Defines a standard updater separating dynamically changing secrets from the configuration.
* - Enforces a requirement for a local secret to explicitly allow its data to be shared with
* a backend roxie service.
*
* Support is provided as an alternative to inline definitions when secret names cannot be
* passed to the target roxie for resolution. Use is discouraged unless it is unavoidable.
*/
class CLocalSecretGateway : public CUpdatableGateway
template <typename secret_identity_t>
class TLocalSecretGateway : public CUpdatableGateway
{
protected:
class CUpdater : public CInterfaceOf<IGatewayUpdater>
template <typename secret_source_t>
class TUpdater : public CInterfaceOf<IGatewayUpdater>
{
friend class CLocalSecretGateway;
friend class TLocalSecretGateway<secret_identity_t>;
static constexpr const char* category = "espUser";
public: // IGatewayUpdater
virtual void updateGateway(IPTree& gw, const char* requiredUsage) override;
virtual void updateGateway(IPTree& gw) override
{
if (!secret)
throw makeStringExceptionV(-1, "gateway %s: '%s' category secret '%s' not found", "?", entry->identifier.str(), category);
entry->doUpdate(gw, *secret);
}
protected:
Linked<const CLocalSecretGateway> entry;
Linked<const TLocalSecretGateway<secret_identity_t>> entry;
Owned<IPTree> secret;
public:
CUpdater(const CLocalSecretGateway& _entry, TransactionSecrets& secrets);
TUpdater(const TLocalSecretGateway<secret_identity_t>& _entry, secret_source_t& secrets)
{
entry.set(&_entry);
secret.setown(secrets.getSecret(category, entry->identity));
}
};
protected: // CUpdatableGateway
virtual IGatewayUpdater* getUpdater(TransactionSecrets& secrets) const override;
virtual IGatewayUpdater* getUpdater(TransactionSecrets& secrets) const override
{
return new TUpdater<TransactionSecrets>(*this, secrets);
}
virtual IGatewayUpdater* getUpdater(IEsdlScriptContext* scriptContext) const override
{
if (!scriptContext)
return nullptr;
return new TUpdater<IEsdlScriptContext>(*this, *scriptContext);
}
protected:
StringBuffer secretId;
StringAttr vaultId;
StringBuffer secretName;
StringBuffer identifier;
secret_identity_t identity;
protected:
CLocalSecretGateway(const IPTree& gw, const char* gwName, const char* gwUrl, const char* classPrefix);
TLocalSecretGateway(const IPTree& gw, const char* gwName, const char* gwUrl)
: CUpdatableGateway(gwName)
, identifier(gwUrl + gwLocalSecretPrefixLength)
, identity(identifier)
{
}
protected:
virtual void doUpdate(IPTree& gw, const IPTree& secret, const char* requiredUsage) const;
virtual void doUpdate(IPTree& gw, const IPTree& secret) const
{
}
};

/**
* @brief Concete extension of `CLocalSecretGateway` to construct a new `Gateway/@url` value
* @brief Concete extension of `TLocalSecretGateway<>` to construct a new `Gateway/@url` value
* from secret-defined values.
*
* - A secret name must begin with "http-connect-". Configurations should omit this prefix,
* but its presence is acceptable.
* - A secret must satisfy the requirements defined in `CLocalSecretGateway`.
* - A secret must satisfy the requirements defined in `TLocalSecretGateway<>`.
* - A secret must define a non-empty `url` property.
* - A secret must define either a non-empty `username` property or set the `omitCredentials`
* property to true.
* - A secret may define a `password` property if `username` is also defined.
* - A secret must not define `username` and set `omitCredentials` to true.
* - A secret must not define a `password` property if `username` is not defined.
*/
class CHttpConnectGateway : public CLocalSecretGateway
class CHttpConnectGateway : public TLocalSecretGateway<HttpConnectSecretId>
{
protected: // CLocalSecretGateway
void doUpdate(IPTree& gw, const IPTree& secret, const char* requiredUsage) const override;
protected: // TLocalSecretGateway
void doUpdate(IPTree& gw, const IPTree& secret) const override;
public:
CHttpConnectGateway(const IPTree& gw, const char* gwName, const char* gwUrl);
};
Expand Down Expand Up @@ -489,7 +492,7 @@ class EsdlServiceImpl : public CInterface, implements IEspService
* @param updaters cache of updaters used in the current transaction
* @param secrets cache of secrets used in the current transaction
*/
void applyGatewayUpdates(IPTreeIterator& gwIt, const UpdatableGateways& updatables, GatewayUpdaters& updaters, TransactionSecrets& secrets) const;
void applyGatewayUpdates(IPTreeIterator& gwIt, const UpdatableGateways& updatables, GatewayUpdaters& updaters, IEsdlScriptContext* scriptContext) const;

/**
* @brief Implementation of legacy gateway transformation invoked only during preparation of
Expand Down

0 comments on commit 8a23f37

Please sign in to comment.