From: Francis Dupont Date: Sun, 12 Jul 2020 12:58:32 +0000 (+0200) Subject: [#1304] Added config/parse X-Git-Tag: Kea-1.9.0~148 X-Git-Url: http://git.ipfire.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=0fc1cd514def1b4e9d90afc1402666f6ab7d441c;p=thirdparty%2Fkea.git [#1304] Added config/parse --- diff --git a/doc/examples/agent/comments.json b/doc/examples/agent/comments.json index 1f90e1ce55..d0c6a00285 100644 --- a/doc/examples/agent/comments.json +++ b/doc/examples/agent/comments.json @@ -10,6 +10,17 @@ "http-host": "127.0.0.1", "http-port": 8000, + "basic-authentication-realm": "kea-control-agent", + + // In basoc HTTP authentication + "basic-authentications": + [ + { + "comment": "admin is authorized", + "user": "admin", + "password": "1234" + } + ], // In control socket "control-sockets": diff --git a/doc/examples/agent/simple.json b/doc/examples/agent/simple.json index 7bde939e8f..db37248568 100644 --- a/doc/examples/agent/simple.json +++ b/doc/examples/agent/simple.json @@ -11,6 +11,28 @@ // Another mandatory parameter is the HTTP port. "http-port": 8000, + // An optional parameter is the basic HTTP authentication realm. + // Its default is "kea-control-agent". + "basic-authentication-realm": "kea-control-agent", + + // This list specifies the user ids and passwords to use for + // basic HTTP authentication. If empty or not present any client + // is authorized. + "basic-authentications": + [ + // This specifies an authorized client. + { + "comment": "admin is authorized", + + // The user id must not be empty or contain the ':' character. + // It is a mandatory parameter. + "user": "admin", + + // If password is not specified an empty password is used. + "password": "1234" + } + ], + // This map specifies where control channel of each server is configured // to listen on. See 'control-socket' object in the respective // servers. At this time the only supported socket type is "unix". diff --git a/src/bin/agent/ca_cfg_mgr.cc b/src/bin/agent/ca_cfg_mgr.cc index 5bf87e5e94..fc013113ad 100644 --- a/src/bin/agent/ca_cfg_mgr.cc +++ b/src/bin/agent/ca_cfg_mgr.cc @@ -1,4 +1,4 @@ -// Copyright (C) 2016-2019 Internet Systems Consortium, Inc. ("ISC") +// Copyright (C) 2016-2020 Internet Systems Consortium, Inc. ("ISC") // // This Source Code Form is subject to the terms of the Mozilla Public // License, v. 2.0. If a copy of the MPL was not distributed with this @@ -21,7 +21,7 @@ namespace isc { namespace agent { CtrlAgentCfgContext::CtrlAgentCfgContext() - :http_host_(""), http_port_(0) { + : http_host_(""), http_port_(0), basic_auth_realm_("") { } CtrlAgentCfgContext::CtrlAgentCfgContext(const CtrlAgentCfgContext& orig) @@ -50,6 +50,8 @@ CtrlAgentCfgMgr::getConfigSummary(const uint32_t /*selection*/) { // Then print the control-sockets s << ctx->getControlSocketInfoSummary(); + // @todo: add something if authentication is required + // Finally, print the hook libraries names const isc::hooks::HookLibsCollection libs = ctx->getHooksConfig().get(); s << ", " << libs.size() << " lib(s):"; @@ -152,6 +154,8 @@ CtrlAgentCfgContext::toElement() const { ca->set("http-host", Element::create(http_host_)); // Set http-port ca->set("http-port", Element::create(static_cast(http_port_))); + // Set basic-authentication-realm + ca->set("basic-authentication-realm", Element::create(basic_auth_realm_)); // Set hooks-libraries ca->set("hooks-libraries", hooks_config_.toElement()); // Set control-sockets @@ -161,6 +165,7 @@ CtrlAgentCfgContext::toElement() const { control_sockets->set(si->first, socket); } ca->set("control-sockets", control_sockets); + // @todo: Set authentication. // Set Control-agent ElementPtr result = Element::createMap(); result->set("Control-agent", ca); diff --git a/src/bin/agent/ca_cfg_mgr.h b/src/bin/agent/ca_cfg_mgr.h index 27c8c02a64..74de00b277 100644 --- a/src/bin/agent/ca_cfg_mgr.h +++ b/src/bin/agent/ca_cfg_mgr.h @@ -1,4 +1,4 @@ -// Copyright (C) 2016-2018 Internet Systems Consortium, Inc. ("ISC") +// Copyright (C) 2016-2020 Internet Systems Consortium, Inc. ("ISC") // // This Source Code Form is subject to the terms of the Mozilla Public // License, v. 2.0. If a copy of the MPL was not distributed with this @@ -98,6 +98,20 @@ public: return (http_port_); } + /// @brief Sets basic-authentication-realm parameter + /// + /// @param real Basic HTTP authentication realm + void setBasicAuthRealm(const std::string& realm) { + basic_auth_realm_ = realm; + } + + /// @brief Returns basic-authentication-realm parameter + /// + /// @return Basic HTTP authentication realm. + std::string getBasicAuthRealm() const { + return (basic_auth_realm_); + } + /// @brief Returns non-const reference to configured hooks libraries. /// /// @return non-const reference to configured hooks libraries. @@ -147,6 +161,9 @@ private: /// TCP port the CA should listen on. uint16_t http_port_; + /// Basic HTTP authentication realm. + std::string basic_auth_realm_; + /// @brief Configured hooks libraries. isc::hooks::HooksConfig hooks_config_; }; diff --git a/src/bin/agent/simple_parser.cc b/src/bin/agent/simple_parser.cc index 7f79439e46..14062dc589 100644 --- a/src/bin/agent/simple_parser.cc +++ b/src/bin/agent/simple_parser.cc @@ -36,8 +36,9 @@ namespace agent { /// /// These are global Control Agent parameters. const SimpleDefaults AgentSimpleParser::AGENT_DEFAULTS = { - { "http-host", Element::string, "127.0.0.1"}, - { "http-port", Element::integer, "8000"} + { "http-host", Element::string, "127.0.0.1" }, + { "http-port", Element::integer, "8000" }, + { "basic-authentication-realm", Element::string, "kea-control-agent" } }; /// @brief This table defines default values for control sockets. @@ -88,6 +89,8 @@ AgentSimpleParser::parse(const CtrlAgentCfgContextPtr& ctx, // Let's get the HTTP parameters first. ctx->setHttpHost(SimpleParser::getString(config, "http-host")); ctx->setHttpPort(SimpleParser::getIntType(config, "http-port")); + ctx->setBasicAuthRealm(SimpleParser::getString(config, + "basic-authentication-realm")); // Control sockets are second. ConstElementPtr ctrl_sockets = config->get("control-sockets"); @@ -98,6 +101,8 @@ AgentSimpleParser::parse(const CtrlAgentCfgContextPtr& ctx, } } + // Basic HTTP authentications are third. + // User context can be done at anytime. ConstElementPtr user_context = config->get("user-context"); if (user_context) { diff --git a/src/lib/http/Makefile.am b/src/lib/http/Makefile.am index 64c07b7775..34e5f500ec 100644 --- a/src/lib/http/Makefile.am +++ b/src/lib/http/Makefile.am @@ -40,6 +40,7 @@ libkea_http_la_SOURCES += response_creator_factory.h libkea_http_la_SOURCES += response_json.cc response_json.h libkea_http_la_SOURCES += url.cc url.h libkea_http_la_SOURCES += basic_auth.cc basic_auth.h +libkea_http_la_SOURCES += basic_auth_config.cc basic_auth_config.h libkea_http_la_CXXFLAGS = $(AM_CXXFLAGS) libkea_http_la_CPPFLAGS = $(AM_CPPFLAGS) @@ -90,6 +91,8 @@ endif # Specify the headers for copying into the installation directory tree. libkea_http_includedir = $(pkgincludedir)/http libkea_http_include_HEADERS = \ + basic_auth.h \ + basic_auth_config.h \ client.h \ connection.h \ connection_pool.h \ @@ -112,6 +115,7 @@ libkea_http_include_HEADERS = \ response.h \ response_context.h \ response_creator.h \ + response_creator_auth.h \ response_creator_factory.h \ response_json.h \ response_parser.h \ diff --git a/src/lib/http/basic_auth_config.cc b/src/lib/http/basic_auth_config.cc new file mode 100644 index 0000000000..d6213f5465 --- /dev/null +++ b/src/lib/http/basic_auth_config.cc @@ -0,0 +1,137 @@ +// Copyright (C) 2020 Internet Systems Consortium, Inc. ("ISC") +// +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + +#include + +#include + +using namespace isc; +using namespace isc::data; +using namespace isc::dhcp; +using namespace std; + +namespace isc { +namespace http { + +BasicHttpAuthClient::BasicHttpAuthClient(const std::string& user, + const std::string& password, + const isc::data::ConstElementPtr& user_context) + : user_(user), password_(password) { + if (user_context) { + setContext(user_context); + } +} + +ElementPtr +BasicHttpAuthClient::toElement() const { + ElementPtr result = Element::createMap(); + + // Set user-context + contextToElement(result); + + // Set user + result->set("user", Element::create(user_)); + + // Set password + result->set("password", Element::create(password_)); + + return (result); +} + +void +BasicHttpAuthConfig::add(const std::string& user, + const std::string& password, + const ConstElementPtr& user_context) { + BasicHttpAuth basic_auth(user, password); + list_.push_back(BasicHttpAuthClient(user, password, user_context)); + map_[basic_auth.getCredential()] = user; +} + +void +BasicHttpAuthConfig::clear() { + list_.clear(); + map_.clear(); +} + +ElementPtr +BasicHttpAuthConfig::toElement() const { + ElementPtr result = Element::createList(); + + for (auto client : list_) { + result->add(client.toElement()); + } + + return (result); +} + +void +BasicHttpAuthConfig::parse(const ConstElementPtr& config) { + if (!config) { + return; + } + if (config->getType() != Element::list) { + isc_throw(DhcpConfigError, "basic-authentications must be a list (" + << config->getPosition() << ")"); + } + for (auto client : config->listValue()) { + if (client->getType() != Element::map) { + isc_throw(DhcpConfigError, "basic-authentications items must be " + << "maps (" << client->getPosition() << ")"); + } + + // user + ConstElementPtr user_cfg = client->get("user"); + if (!user_cfg) { + isc_throw(DhcpConfigError, "user is required in " + << "basic-authentications items (" + << client->getPosition() << ")"); + } + if (user_cfg->getType() != Element::string) { + isc_throw(DhcpConfigError, "user must be a string (" + << user_cfg->getPosition() << ")"); + } + string user = user_cfg->stringValue(); + if (user.empty()) { + isc_throw(DhcpConfigError, "user must be not be empty (" + << user_cfg->getPosition() << ")"); + } + if (user.find(':') != string::npos) { + isc_throw(DhcpConfigError, "user must not contain a ':': '" + << user << "' (" << user_cfg->getPosition() << ")"); + } + + // password + string password; + ConstElementPtr password_cfg = client->get("password"); + if (password_cfg) { + if (password_cfg->getType() != Element::string) { + isc_throw(DhcpConfigError, "password must be a string (" + << password_cfg->getPosition() << ")"); + } + password = password_cfg->stringValue(); + } + + // user context + ConstElementPtr user_context = client->get("user-context"); + if (user_context) { + if (user_context->getType() != Element::map) { + isc_throw(DhcpConfigError, "user-context must be a map (" + << user_context->getPosition() << ")"); + } + } + + // add it. + try { + add(user, password, user_context); + } catch (const std::exception& ex) { + isc_throw(DhcpConfigError, ex.what() << " (" + << client->getPosition() << ")"); + } + } +} + +} // end of namespace isc::http +} // end of namespace isc diff --git a/src/lib/http/basic_auth_config.h b/src/lib/http/basic_auth_config.h new file mode 100644 index 0000000000..a5d3e74a6c --- /dev/null +++ b/src/lib/http/basic_auth_config.h @@ -0,0 +1,120 @@ +// Copyright (C) 2020 Internet Systems Consortium, Inc. ("ISC") +// +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + +#ifndef HTTP_BASIC_AUTH_CONFIG_H +#define HTTP_BASIC_AUTH_CONFIG_H + +#include +#include +#include +#include +#include +#include +#include + +namespace isc { +namespace http { + +/// @brief Type of basic HTTP authentication credential and user id map. +typedef std::unordered_map BasicHttpAuthMap; + +/// @brief Basic HTTP authentication client configuration. +class BasicHttpAuthClient : public isc::data::UserContext, + public isc::data::CfgToElement { +public: + + /// @brief Constructor. + /// + /// @param user User id + /// @param password Password + /// @param user_context Optional user context + BasicHttpAuthClient(const std::string& user, + const std::string& password, + const isc::data::ConstElementPtr& user_context); + + /// @brief Returns the user id. + const std::string& getUser() const { + return (user_); + } + + /// @brief Returns the password. + const std::string& getPassword() const { + return (password_); + } + + /// @brief Unparses basic HTTP authentication client configuration. + /// + /// @return A pointer to unparsed client configuration. + virtual isc::data::ElementPtr toElement() const; + +private: + + /// @brief The user id. + std::string user_; + + /// @brief The password. + std::string password_; +}; + +/// @brief Type of basic HTTP authentication client configuration list. +typedef std::list BasicHttpAuthClientList; + +/// @brief Basic HTTP authentication configuration. +class BasicHttpAuthConfig : public isc::data::CfgToElement { +public: + + /// @brief Add a client configuration. + /// + /// @param user User id + /// @param password Password + /// @param user_context Optional user context + /// @throw BadValue if the user id contains the ':' character. + void add(const std::string& user, + const std::string& password, + const isc::data::ConstElementPtr& user_context = isc::data::ConstElementPtr()); + + /// @brief Clear configuration. + void clear(); + + /// @brief Returns the list of client configuration. + /// + /// @return List of basic HTTP authentication client configuration. + const BasicHttpAuthClientList& getClientList() const { + return (list_); + } + + /// @brief Returns the credential and user id map. + /// + /// @return The basic HTTP authentication credential and user id map. + const BasicHttpAuthMap& getCredentialMap() const { + return (map_); + } + + /// @brief Parses basic HTTP authentication configuration. + /// + /// @param config Element holding the basic HTTP authentication + /// configuration to be parsed. + /// @throw DhcpConfigError when the configuration is invalid. + void parse(const isc::data::ConstElementPtr& config); + + /// @brief Unparses basic HTTP authentication configuration. + /// + /// @return A pointer to unparsed basic HTTP authentication configuration. + virtual isc::data::ElementPtr toElement() const; + +private: + + /// @brief The list of basic HTTP authentication client configuration. + BasicHttpAuthClientList list_; + + /// @brief The basic HTTP authentication credential and user id map. + BasicHttpAuthMap map_; +}; + +} // end of namespace isc::http +} // end of namespace isc + +#endif // endif HTTP_BASIC_AUTH_CONFIG_H diff --git a/src/lib/http/response_creator_auth.h b/src/lib/http/response_creator_auth.h index 5cc085d071..0bcfe2c993 100644 --- a/src/lib/http/response_creator_auth.h +++ b/src/lib/http/response_creator_auth.h @@ -7,6 +7,7 @@ #ifndef HTTP_RESPONSE_CREATOR_AUTH_H #define HTTP_RESPONSE_CREATOR_AUTH_H +#include #include #include #include @@ -14,9 +15,6 @@ namespace isc { namespace http { -/// @brief Type of basic HTTP authentication credential and user id map. -typedef std::unordered_map BasicHttpAuthMap; - /// @brief Validate basic HTTP authentication. /// /// @param creator The HTTP response creator. diff --git a/src/lib/http/tests/Makefile.am b/src/lib/http/tests/Makefile.am index e88d4dcccc..6dd63a1d5e 100644 --- a/src/lib/http/tests/Makefile.am +++ b/src/lib/http/tests/Makefile.am @@ -21,6 +21,7 @@ if HAVE_GTEST TESTS += libhttp_unittests libhttp_unittests_SOURCES = basic_auth_unittests.cc +libhttp_unittests_SOURCES += basic_auth_config_unittests.cc libhttp_unittests_SOURCES += connection_pool_unittests.cc libhttp_unittests_SOURCES += date_time_unittests.cc libhttp_unittests_SOURCES += http_header_unittests.cc diff --git a/src/lib/http/tests/basic_auth_config_unittests.cc b/src/lib/http/tests/basic_auth_config_unittests.cc new file mode 100644 index 0000000000..5b7cd8605f --- /dev/null +++ b/src/lib/http/tests/basic_auth_config_unittests.cc @@ -0,0 +1,218 @@ +// Copyright (C) 2020 Internet Systems Consortium, Inc. ("ISC") +// +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + +#include +#include +#include +#include +#include + +using namespace isc; +using namespace isc::data; +using namespace isc::dhcp; +using namespace isc::http; +using namespace isc::test; +using namespace std; + +namespace { + +// Test that basic auth client works as expected. +TEST(BasicHttpAuthClientTest, basic) { + // Create a client. + ConstElementPtr ctx = Element::fromJSON("{ \"foo\": \"bar\" }"); + BasicHttpAuthClient client("foo", "bar", ctx); + + // Check it. + EXPECT_EQ("foo", client.getUser()); + EXPECT_EQ("bar", client.getPassword()); + EXPECT_TRUE(ctx->equals(*client.getContext())); + + // Check toElement. + ElementPtr expected = Element::createMap(); + expected->set("user", Element::create(string("foo"))); + expected->set("password", Element::create(string("bar"))); + expected->set("user-context", ctx); + runToElementTest(expected, client); +} + +// Test that basic auth configuration works as expected. +TEST(BasicHttpAuthConfigTest, basic) { + // Create a configuration. + BasicHttpAuthConfig config; + + // Initial configuration is empty. + EXPECT_TRUE(config.getClientList().empty()); + EXPECT_TRUE(config.getCredentialMap().empty()); + + // Add rejects user id with embedded ':'. + EXPECT_THROW(config.add("foo:", "bar"), BadValue); + + // Add a client. + ConstElementPtr ctx = Element::fromJSON("{ \"foo\": \"bar\" }"); + EXPECT_NO_THROW(config.add("foo", "bar", ctx)); + + // Check the client. + ASSERT_EQ(1, config.getClientList().size()); + const BasicHttpAuthClient& client = config.getClientList().front(); + EXPECT_EQ("foo", client.getUser()); + EXPECT_EQ("bar", client.getPassword()); + EXPECT_TRUE(ctx->equals(*client.getContext())); + + // Check the credential. + ASSERT_NE(0, config.getCredentialMap().count("Zm9vOmJhcg==")); + string user; + EXPECT_NO_THROW(user = config.getCredentialMap().at("Zm9vOmJhcg==")); + EXPECT_EQ("foo", user); + + // Check toElement. + ElementPtr expected = Element::createList(); + ElementPtr elem = Element::createMap(); + elem->set("user", Element::create(string("foo"))); + elem->set("password", Element::create(string("bar"))); + elem->set("user-context", ctx); + expected->add(elem); + runToElementTest(expected, config); + + // Add a second client and test it. + EXPECT_NO_THROW(config.add("test", "123\xa3")); + ASSERT_EQ(2, config.getClientList().size()); + EXPECT_EQ("foo", config.getClientList().front().getUser()); + EXPECT_EQ("test", config.getClientList().back().getUser()); + ASSERT_NE(0, config.getCredentialMap().count("dGVzdDoxMjPCow==")); + + // Check clear. + config.clear(); + expected = Element::createList(); + runToElementTest(expected, config); + + // Add clients again. + EXPECT_NO_THROW(config.add("test", "123\xa3")); + EXPECT_NO_THROW(config.add("foo", "bar", ctx)); + + // Check that toElement keeps add order. + ElementPtr elem0 = Element::createMap(); + elem0->set("user", Element::create(string("test"))); + elem0->set("password", Element::create(string("123\xa3"))); + expected->add(elem0); + expected->add(elem); + runToElementTest(expected, config); +} + +// Test that basic auth configuration parses. +TEST(BasicHttpAuthConfigTest, parse) { + BasicHttpAuthConfig config; + ElementPtr cfg; + + // No config is accepted. + EXPECT_NO_THROW(config.parse(cfg)); + EXPECT_TRUE(config.getClientList().empty()); + EXPECT_TRUE(config.getCredentialMap().empty()); + runToElementTest(Element::createList(), config); + + // The config must be a list. + cfg = Element::createMap(); + EXPECT_THROW_MSG(config.parse(cfg), DhcpConfigError, + "basic-authentications must be a list (:0:0)"); + + // The client config must be a map. + cfg = Element::createList(); + ElementPtr client_cfg = Element::createList(); + cfg->add(client_cfg); + EXPECT_THROW_MSG(config.parse(cfg), DhcpConfigError, + "basic-authentications items must be maps (:0:0)"); + + // The user parameter is mandatory in client config. + client_cfg = Element::createMap(); + cfg = Element::createList(); + cfg->add(client_cfg); + EXPECT_THROW_MSG(config.parse(cfg), DhcpConfigError, + "user is required in basic-authentications items (:0:0)"); + + // The user parameter must be a string. + ElementPtr user_cfg = Element::create(1); + client_cfg = Element::createMap(); + client_cfg->set("user", user_cfg); + cfg = Element::createList(); + cfg->add(client_cfg); + EXPECT_THROW_MSG(config.parse(cfg), DhcpConfigError, + "user must be a string (:0:0)"); + + // The user parameter must not be empty. + user_cfg = Element::create(string("")); + client_cfg = Element::createMap(); + client_cfg->set("user", user_cfg); + cfg = Element::createList(); + cfg->add(client_cfg); + EXPECT_THROW_MSG(config.parse(cfg), DhcpConfigError, + "user must be not be empty (:0:0)"); + + // The user parameter must not contain ':'. + user_cfg = Element::create(string("foo:bar")); + client_cfg = Element::createMap(); + client_cfg->set("user", user_cfg); + cfg = Element::createList(); + cfg->add(client_cfg); + EXPECT_THROW_MSG(config.parse(cfg), DhcpConfigError, + "user must not contain a ':': 'foo:bar' (:0:0)"); + + // Password is not required. + user_cfg = Element::create(string("foo")); + client_cfg = Element::createMap(); + client_cfg->set("user", user_cfg); + cfg = Element::createList(); + cfg->add(client_cfg); + EXPECT_NO_THROW(config.parse(cfg)); + ASSERT_EQ(1, config.getClientList().size()); + EXPECT_EQ("", config.getClientList().front().getPassword()); + config.clear(); + + // The password parameter must be a string. + ElementPtr password_cfg = Element::create(1); + client_cfg = Element::createMap(); + client_cfg->set("user", user_cfg); + client_cfg->set("password", password_cfg); + cfg = Element::createList(); + cfg->add(client_cfg); + EXPECT_THROW_MSG(config.parse(cfg), DhcpConfigError, + "password must be a string (:0:0)"); + + // Empty password is accepted. + password_cfg = Element::create(string("")); + client_cfg = Element::createMap(); + client_cfg->set("user", user_cfg); + client_cfg->set("password", password_cfg); + cfg = Element::createList(); + cfg->add(client_cfg); + EXPECT_NO_THROW(config.parse(cfg)); + ASSERT_EQ(1, config.getClientList().size()); + EXPECT_EQ("", config.getClientList().front().getPassword()); + config.clear(); + + // User context must be a map. + password_cfg = Element::create(string("bar")); + ElementPtr ctx = Element::createList(); + client_cfg = Element::createMap(); + client_cfg->set("user", user_cfg); + client_cfg->set("password", password_cfg); + client_cfg->set("user-context", ctx); + cfg = Element::createList(); + cfg->add(client_cfg); + EXPECT_THROW_MSG(config.parse(cfg), DhcpConfigError, + "user-context must be a map (:0:0)"); + + // Check a working not empty config. + ctx = Element::fromJSON("{ \"foo\": \"bar\" }"); + client_cfg = Element::createMap(); + client_cfg->set("user", user_cfg); + client_cfg->set("password", password_cfg); + client_cfg->set("user-context", ctx); + cfg = Element::createList(); + cfg->add(client_cfg); + EXPECT_NO_THROW(config.parse(cfg)); + runToElementTest(cfg, config); +} + +} // end of anonymous namespace