application_config.cpp 17 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383
  1. /*##############################################################################
  2. HPCC SYSTEMS software Copyright (C) 2020 HPCC Systems®.
  3. Licensed under the Apache License, Version 2.0 (the "License");
  4. you may not use this file except in compliance with the License.
  5. You may obtain a copy of the License at
  6. http://www.apache.org/licenses/LICENSE-2.0
  7. Unless required by applicable law or agreed to in writing, software
  8. distributed under the License is distributed on an "AS IS" BASIS,
  9. WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  10. See the License for the specific language governing permissions and
  11. limitations under the License.
  12. ############################################################################## */
  13. #pragma warning (disable : 4786)
  14. #include "jliball.hpp"
  15. // We can use "work" as the first parameter when debugging.
  16. #define ESP_SINGLE_PROCESS
  17. //ESP Core
  18. #include "espp.hpp"
  19. #include "espcfg.ipp"
  20. #include "esplog.hpp"
  21. #include "espcontext.hpp"
  22. enum class LdapType { LegacyAD, AzureAD };
  23. static void appendPTreeFromYamlFile(IPropertyTree *tree, const char *file, bool overwriteAttr)
  24. {
  25. Owned<IPropertyTree> appendTree = createPTreeFromYAMLFile(file);
  26. if (appendTree->hasProp("esp"))
  27. mergeConfiguration(*tree, *appendTree->queryPropTree("esp"), nullptr, overwriteAttr);
  28. else
  29. mergeConfiguration(*tree, *appendTree, nullptr, overwriteAttr);
  30. }
  31. IPropertyTree *loadApplicationConfig(const char *application, const char* argv[])
  32. {
  33. Owned<IPropertyTree> applicationConfig = createPTree(application);
  34. IPropertyTree *defaultConfig = applicationConfig->addPropTree("esp");
  35. char sepchar = getPathSepChar(hpccBuildInfo.componentDir);
  36. StringBuffer path(hpccBuildInfo.componentDir);
  37. addPathSepChar(path, sepchar).append("applications").append(sepchar).append("common").append(sepchar);
  38. if (checkDirExists(path))
  39. {
  40. Owned<IDirectoryIterator> common_dir = createDirectoryIterator(path, "*.yaml", false, false);
  41. ForEach(*common_dir)
  42. appendPTreeFromYamlFile(defaultConfig, common_dir->query().queryFilename(), true);
  43. }
  44. path.set(hpccBuildInfo.componentDir);
  45. addPathSepChar(path, sepchar).append("applications").append(sepchar).append(application).append(sepchar);
  46. if (!checkDirExists(path))
  47. throw MakeStringException(-1, "Can't find esp application %s (dir %s)", application, path.str());
  48. Owned<IDirectoryIterator> application_dir = createDirectoryIterator(path, "*.yaml", false, false);
  49. ForEach(*application_dir)
  50. appendPTreeFromYamlFile(defaultConfig, application_dir->query().queryFilename(), true);
  51. //apply provided config to the application
  52. Owned<IPropertyTree> config = loadConfiguration(defaultConfig, argv, "esp", "ESP", nullptr, nullptr);
  53. return config.getClear();
  54. }
  55. static void copyAttributes(IPropertyTree *target, IPropertyTree *src)
  56. {
  57. Owned<IAttributeIterator> aiter = src->getAttributes();
  58. ForEach (*aiter)
  59. target->addProp(aiter->queryName(), aiter->queryValue());
  60. }
  61. static void copyDirectories(IPropertyTree *target, IPropertyTree *src)
  62. {
  63. if (!src || !target)
  64. return;
  65. Owned<IAttributeIterator> aiter = src->getAttributes();
  66. ForEach (*aiter)
  67. {
  68. IPropertyTree *entry = target->addPropTree("Category");
  69. entry->setProp("@name", aiter->queryName()+1);
  70. entry->setProp("@dir", aiter->queryValue());
  71. }
  72. }
  73. bool addLdapSecurity(IPropertyTree *legacyEsp, IPropertyTree *appEsp, StringBuffer &bindAuth, LdapType ldapType)
  74. {
  75. const char *ldapAddress = appEsp->queryProp("@ldapAddress");
  76. if (isEmptyString(ldapAddress))
  77. throw MakeStringException(-1, "LDAP not configured. To run without security set auth=none");
  78. StringBuffer path(hpccBuildInfo.componentDir);
  79. char sepchar = getPathSepChar(hpccBuildInfo.componentDir);
  80. addPathSepChar(path, sepchar).append("applications").append(sepchar).append("common").append(sepchar).append("ldap").append(sepchar).append("ldap.yaml");
  81. if (checkFileExists(path))
  82. appendPTreeFromYamlFile(appEsp, path.str(), false);
  83. IPropertyTree *appLdap = appEsp->queryPropTree("ldap");
  84. if (!appLdap)
  85. throw MakeStringException(-1, "Can't find application LDAP settings. To run without security set auth=none");
  86. IPropertyTree *legacyLdap = legacyEsp->addPropTree("ldapSecurity");
  87. copyAttributes(legacyLdap, appLdap);
  88. legacyLdap->setProp("@ldapAddress", ldapAddress);
  89. StringAttr configname(appLdap->queryProp("@objname"));
  90. if (!legacyLdap->hasProp("@name"))
  91. legacyLdap->setProp("@name", configname.str());
  92. StringAttr resourcesBasedn(appLdap->queryProp("@resourcesBasedn"));
  93. StringAttr workunitsBasedn(appLdap->queryProp("@workunitsBasedn"));
  94. bindAuth.setf("<Authenticate method='LdapSecurity' config='%s' resourcesBasedn='%s' workunitsBasedn='%s'/>", configname.str(), resourcesBasedn.str(), workunitsBasedn.str());
  95. VStringBuffer authenticationXml("<Authentication htpasswdFile='/etc/HPCCSystems/.htpasswd' ldapConnections='10' ldapServer='%s' method='ldaps' passwordExpirationWarningDays='10'/>", configname.str());
  96. legacyEsp->addPropTree("Authentication", createPTreeFromXMLString(authenticationXml));
  97. return true;
  98. }
  99. bool addAuthNZSecurity(const char *name, IPropertyTree *legacyEsp, IPropertyTree *appEsp, StringBuffer &bindAuth)
  100. {
  101. IPropertyTree *authNZ = appEsp->queryPropTree("authNZ");
  102. if (!authNZ)
  103. throw MakeStringException(-1, "Can't find application AuthNZ section. To run without security set auth=none");
  104. authNZ = authNZ->queryPropTree(name);
  105. if (!authNZ)
  106. throw MakeStringException(-1, "Can't find application %s AuthNZ settings. To run without security set auth=none", name);
  107. IPropertyTree *appSecMgr = authNZ->queryPropTree("SecurityManager");
  108. if (!appSecMgr)
  109. {
  110. const char *application = appEsp->queryProp("@application");
  111. throw MakeStringException(-1, "Can't find SecurityManager settings configuring application '%s'. To run without security set auth=none", application ? application : "");
  112. }
  113. const char *method = appSecMgr->queryProp("@name");
  114. const char *tag = appSecMgr->queryProp("@type");
  115. if (isEmptyString(tag))
  116. throw MakeStringException(-1, "SecurityManager type attribute required. To run without security set auth=none");
  117. legacyEsp->addPropTree("AuthDomains", createPTreeFromXMLString("<AuthDomains><AuthDomain authType='AuthPerRequestOnly' clientSessionTimeoutMinutes='120' domainName='default' invalidURLsAfterAuth='/esp/login' loginLogoURL='/esp/files/eclwatch/img/Loginlogo.png' logonURL='/esp/files/Login.html' logoutURL='' serverSessionTimeoutMinutes='240' unrestrictedResources='/favicon.ico,/esp/files/*,/esp/xslt/*'/></AuthDomains>"));
  118. IPropertyTree *legacy = legacyEsp->addPropTree("SecurityManagers");
  119. legacy = legacy->addPropTree("SecurityManager");
  120. copyAttributes(legacy, appSecMgr);
  121. legacy = legacy->addPropTree(tag);
  122. mergePTree(legacy, authNZ); //extra info clean up later
  123. legacy->removeProp("SecurityManager"); //already copied these attributes above, don't need this as a child
  124. bindAuth.setf("<Authenticate method='%s'/>", method ? method : "unknown");
  125. return true;
  126. }
  127. // auth "none" must be explicit, default to secure mode, don't want to accidentally turn off security
  128. bool addSecurity(IPropertyTree *legacyEsp, IPropertyTree *appEsp, StringBuffer &bindAuth)
  129. {
  130. const char *auth = appEsp->queryProp("@auth");
  131. if (isEmptyString(auth))
  132. throw MakeStringException(-1, "'auth' attribute required. To run without security set 'auth=none'");
  133. if (streq(auth, "none"))
  134. return false;
  135. if (streq(auth, "ldap"))
  136. return addLdapSecurity(legacyEsp, appEsp, bindAuth, LdapType::LegacyAD);
  137. if (streq(auth, "azure_ldap"))
  138. return addLdapSecurity(legacyEsp, appEsp, bindAuth, LdapType::AzureAD);
  139. return addAuthNZSecurity(auth, legacyEsp, appEsp, bindAuth);
  140. }
  141. void bindAuthResources(IPropertyTree *legacyAuthenticate, IPropertyTree *app, const char *service, const char *auth)
  142. {
  143. IPropertyTree *appAuth = nullptr;
  144. if (isEmptyString(auth) || streq(auth, "ldap") || streq(auth, "azure_ldap"))
  145. appAuth = app->queryPropTree("ldap");
  146. else if (streq(auth, "none"))
  147. return;
  148. else
  149. {
  150. appAuth = app->queryPropTree("authNZ");
  151. if (!appAuth)
  152. return;
  153. appAuth = appAuth->queryPropTree(auth);
  154. if (!appAuth)
  155. return;
  156. }
  157. if (!appAuth)
  158. throw MakeStringException(-1, "Can't find application Auth settings. To run without security set auth=none");
  159. IPropertyTree *root_access = appAuth->queryPropTree("root_access");
  160. StringAttr required(root_access->queryProp("@required"));
  161. StringAttr description(root_access->queryProp("@description"));
  162. StringAttr resource(root_access->queryProp("@resource"));
  163. VStringBuffer locationXml("<Location path='/' resource='%s' required='%s' description='%s'/>", resource.str(), required.str(), description.str());
  164. legacyAuthenticate->addPropTree("Location", createPTreeFromXMLString(locationXml));
  165. VStringBuffer featuresPath("resource_map/%s/Feature", service);
  166. Owned<IPropertyTreeIterator> features = appAuth->getElements(featuresPath);
  167. ForEach(*features)
  168. legacyAuthenticate->addPropTree("Feature", LINK(&features->query()));
  169. }
  170. void bindService(IPropertyTree *legacyEsp, IPropertyTree *app, const char *service, const char *protocol, const char *netAddress, unsigned port, const char *bindAuth, int seq)
  171. {
  172. VStringBuffer xpath("binding_plugins/@%s", service);
  173. const char *binding_plugin = app->queryProp(xpath);
  174. VStringBuffer bindingXml("<EspBinding name='%s_binding' service='%s_service' protocol='%s' type='%s_http' plugin='%s' netAddress='%s' port='%d'/>", service, service, protocol, service, binding_plugin, netAddress, port);
  175. IPropertyTree *bindingEntry = legacyEsp->addPropTree("EspBinding", createPTreeFromXMLString(bindingXml));
  176. if (seq==0)
  177. bindingEntry->setProp("@defaultBinding", "true");
  178. if (!isEmptyString(bindAuth))
  179. {
  180. const char *auth = app->queryProp("@auth");
  181. IPropertyTree *authenticate = bindingEntry->addPropTree("Authenticate", createPTreeFromXMLString(bindAuth));
  182. if (authenticate)
  183. bindAuthResources(authenticate, app, service, auth);
  184. }
  185. }
  186. static void mergeServicePTree(IPropertyTree *target, IPropertyTree *toMerge)
  187. {
  188. Owned<IAttributeIterator> aiter = toMerge->getAttributes();
  189. ForEach (*aiter)
  190. {
  191. //make uppercase attributes into elements to match legacy ESP config
  192. const char *xpath = aiter->queryName();
  193. if (xpath && xpath[0]=='@' && isupper(xpath[1]))
  194. xpath++;
  195. target->addProp(xpath, aiter->queryValue());
  196. }
  197. Owned<IPropertyTreeIterator> iter = toMerge->getElements("*");
  198. ForEach (*iter)
  199. {
  200. IPropertyTree &e = iter->query();
  201. target->addPropTree(e.queryName(), LINK(&e));
  202. }
  203. }
  204. void addService(IPropertyTree *legacyEsp, IPropertyTree *app, const char *application, const char *service, const char *protocol, const char *netAddress, unsigned port, const char *bindAuth, int seq)
  205. {
  206. VStringBuffer plugin_xpath("service_plugins/@%s", service);
  207. const char *service_plugin = app->queryProp(plugin_xpath);
  208. VStringBuffer serviceXml("<EspService name='%s_service' type='%s' plugin='%s'/>", service, service, service_plugin);
  209. VStringBuffer config_xpath("%s/%s", application, service);
  210. IPropertyTree *serviceConfig = app->queryPropTree(config_xpath);
  211. IPropertyTree *serviceEntry = legacyEsp->addPropTree("EspService", createPTreeFromXMLString(serviceXml));
  212. if (serviceConfig && serviceEntry)
  213. mergeServicePTree(serviceEntry, serviceConfig);
  214. bindService(legacyEsp, app, service, protocol, netAddress, port, bindAuth, seq);
  215. }
  216. bool addProtocol(IPropertyTree *legacyEsp, IPropertyTree *app)
  217. {
  218. bool useTls = app->getPropBool("@tls", true);
  219. if (useTls)
  220. {
  221. StringBuffer protocolXml("<EspProtocol name='https' type='secure_http_protocol' plugin='esphttp' maxRequestEntityLength='60000000'/>");
  222. IPropertyTree *protocol = legacyEsp->addPropTree("EspProtocol", createPTreeFromXMLString(protocolXml));
  223. IPropertyTree *tls = app->queryPropTree("tls_config");
  224. if (protocol && tls)
  225. {
  226. Owned<IAttributeIterator> aiter = tls->getAttributes();
  227. ForEach (*aiter)
  228. tls->addProp(aiter->queryName()+1, aiter->queryValue()); //attributes to elements to match legacy config
  229. Owned<IPropertyTree> temp;
  230. const char *instance = app->queryProp("@instance");
  231. if (instance)
  232. {
  233. StringBuffer xml;
  234. toXML(tls, xml);
  235. xml.replaceString("{$instance}", instance);
  236. temp.setown(createPTreeFromXMLString(xml));
  237. tls = temp.get();
  238. }
  239. mergePTree(protocol, tls);
  240. }
  241. }
  242. else
  243. {
  244. StringBuffer protocolXml("<EspProtocol name='http' type='http_protocol' plugin='esphttp' maxRequestEntityLength='60000000'/>");
  245. legacyEsp->addPropTree("EspProtocol", createPTreeFromXMLString(protocolXml));
  246. }
  247. return useTls;
  248. }
  249. void addServices(IPropertyTree *legacyEsp, IPropertyTree *appEsp, const char *application, const char *auth, bool tls)
  250. {
  251. Owned<IPropertyTreeIterator> services = appEsp->getElements("application/services");
  252. int port = appEsp->getPropInt("service/@port", appEsp->getPropInt("@port", 8880));
  253. const char *netAddress = appEsp->queryProp("@netAddress");
  254. if (!netAddress)
  255. netAddress = ".";
  256. int seq=0;
  257. ForEach(*services)
  258. addService(legacyEsp, appEsp, application, services->query().queryProp("."), tls ? "https" : "http", netAddress, port, auth, seq++);
  259. }
  260. void addBindingToServiceResource(IPropertyTree *service, const char *name, const char *serviceType, unsigned port,
  261. const char *baseDN, const char *workunitsBaseDN)
  262. {
  263. IPropertyTree *resourcesTree = service->queryPropTree("Resources");
  264. if (!resourcesTree)
  265. resourcesTree = service->addPropTree("Resources", createPTree("Resources"));
  266. IPropertyTree *bindingTree = resourcesTree->addPropTree("Binding", createPTree("Binding"));
  267. bindingTree->setProp("@name", name);
  268. bindingTree->setProp("@service", serviceType);
  269. bindingTree->setPropInt("@port", port);
  270. bindingTree->setProp("@basedn", baseDN);
  271. bindingTree->setProp("@workunitsBasedn", workunitsBaseDN);
  272. }
  273. void setLDAPSecurityInWSAccess(IPropertyTree *legacyEsp, IPropertyTree *legacyLdap)
  274. {
  275. IPropertyTree *wsAccessService = legacyEsp->queryPropTree("EspService[@type='ws_access']");
  276. if (!wsAccessService)
  277. throw makeStringException(-1, "Missing configuration for EspService 'ws_access'");
  278. IPropertyTree *wsSMCService = legacyEsp->queryPropTree("EspService[@type='WsSMC']");
  279. if (!wsSMCService)
  280. throw makeStringException(-1, "Missing configuration for EspService 'WsSMC'");
  281. const char *fileBaseDN = legacyLdap->queryProp("@filesBasedn");
  282. if (!isEmptyString(fileBaseDN))
  283. {
  284. IPropertyTree *filesTree = wsAccessService->queryPropTree("Files");
  285. if (!filesTree)
  286. filesTree = wsAccessService->addPropTree("Files", createPTree("Files"));
  287. filesTree->setProp("@basedn", fileBaseDN);
  288. }
  289. VStringBuffer xpath("EspBinding[@service='%s']", wsSMCService->queryProp("@name"));
  290. Owned<IPropertyTreeIterator> bindings = legacyEsp->getElements(xpath);
  291. ForEach(*bindings)
  292. {
  293. IPropertyTree &binding = bindings->query();
  294. IPropertyTree *authTree = binding.queryPropTree("Authenticate");
  295. const char *baseDN = authTree->queryProp("@resourcesBasedn");
  296. const char *workunitsBaseDN = authTree->queryProp("@workunitsBasedn");
  297. addBindingToServiceResource(wsAccessService, binding.queryProp("@name"), "WsSMC",
  298. binding.getPropInt("@port"), baseDN, workunitsBaseDN);
  299. }
  300. }
  301. IPropertyTree *buildApplicationLegacyConfig(const char *application, const char* argv[])
  302. {
  303. Owned<IPropertyTree> appEspConfig = loadApplicationConfig(application, argv);
  304. Owned<IPropertyTree> legacy = createPTreeFromXMLString("<Environment><Software><EspProcess/><Directories name='HPCCSystems'/></Software></Environment>");
  305. IPropertyTree *legacyEsp = legacy->queryPropTree("Software/EspProcess");
  306. copyAttributes(legacyEsp, appEspConfig);
  307. if (!legacyEsp->hasProp("@name") && legacyEsp->hasProp("@instance"))
  308. legacyEsp->setProp("@name", legacyEsp->queryProp("@instance"));
  309. if (!legacyEsp->hasProp("@directory"))
  310. {
  311. const char *componentName = legacyEsp->queryProp("@name");
  312. VStringBuffer s("%s/%s", hpccBuildInfo.runtimeDir, isEmptyString(componentName) ? application : componentName);
  313. legacyEsp->setProp("@directory", s.str());
  314. }
  315. if (!legacyEsp->hasProp("@componentfilesDir"))
  316. legacyEsp->setProp("@componentfilesDir", hpccBuildInfo.componentDir);
  317. bool tls = addProtocol(legacyEsp, appEspConfig);
  318. StringBuffer bindAuth;
  319. addSecurity(legacyEsp, appEspConfig, bindAuth);
  320. addServices(legacyEsp, appEspConfig, application, bindAuth, tls);
  321. IPropertyTree *legacyLdap = legacyEsp->queryPropTree("ldapSecurity");
  322. if (legacyLdap && strieq(application, "eclwatch"))
  323. setLDAPSecurityInWSAccess(legacyEsp, legacyLdap);
  324. IPropertyTree *legacyDirectories = legacy->queryPropTree("Software/Directories");
  325. IPropertyTree *appDirectories = appEspConfig->queryPropTree("directories");
  326. copyDirectories(legacyDirectories, appDirectories);
  327. return legacy.getClear();
  328. }