Daniel Bretón Suárez
11/08/2022, 1:27 PMconfig_parser
in a C++ extension. I'm using REGISTER_EXTERNAL
macro and I see the debug message at registry_factory.cpp
I1108 12:56:08.491199 231789 registry_factory.cpp:107] Extension 499 registered config_parser plugin devo_params
However, the update function is never called.
If I add some debug messages to osquery/config/config.cpp
and print the list of all config_parser modules it handles, I can't see it. However, if I add the same function at the extension code, it exists!
Any ideas why this could be happening?
void printAll()
{
auto plugins = osquery::RegistryFactory::get().plugins("config_parser");
for (auto & p : plugins) {
printf("registered parser %s\n", p.first.c_str());
}
}
Stefano Bonicatti
11/08/2022, 6:24 PMDaniel Bretón Suárez
11/09/2022, 11:06 AMclear; sudo osqueryd --flagfile "/etc/osquery/osquery.flags" --verbose 2>&1 | tee devo-ea-agent-verbose.out
With this flagfile
--enroll_secret_path=/etc/osquery/certs/secret
--tls_server_certs=/etc/osquery/certs/devo-ea-manager.crt
--tls_hostname=devo-ea-manager:8080
--enroll_tls_endpoint=/api/v1/osquery/enroll
--config_plugin=tls
--config_tls_endpoint=/api/v1/osquery/config
--config_refresh=10
--disable_distributed=false
--distributed_plugin=tls
--distributed_interval=3
--distributed_tls_max_attempts=3
--distributed_tls_read_endpoint=/api/v1/osquery/distributed/read
--distributed_tls_write_endpoint=/api/v1/osquery/distributed/write
--logger_plugin=tls
--logger_tls_endpoint=/api/v1/osquery/log
--logger_tls_period=10
--logger_tls_max_lines=8192
--extensions_autoload=/etc/osquery/extensions.load
--watchdog_memory_limit=512
--enable_extensions_watchdog=true
--extensions_require=system-stats,fetchfiles,devo-wevent-logger
--disable_tables=curl
An this extension.load (devo_wevent_logger is the one I'm working on)
/opt/osquery/share/osquery/extensions/fetchfiles.ext
/opt/osquery/share/osquery/extensions/devo_wevent_logger.ext
/opt/osquery/share/osquery/extensions/system-stats.ext
I'm also running a fleet server in a VM. The ultimate goal is to distribute the configuration. Just to be clear, I can access the parameters defined as custom_
flags under options
as in the first image. But I want to implement a schema as the second imageStefano Bonicatti
11/09/2022, 11:30 AMDaniel Bretón Suárez
11/09/2022, 11:38 AMConfig::refresh()
under osquery/config/config.cpp
The one that prints the parser is under the extensionStefano Bonicatti
11/09/2022, 12:12 PMRegisteryFactory::addBroadcast
function called: https://github.com/osquery/osquery/blob/master/osquery/registry/registry_factory.cpp#L68
Then on line 100 we register the plugin provided by the extension using the specific registry addExternal
function; that ends up calling the RegistryInterface relative function https://github.com/osquery/osquery/blob/master/osquery/registry/registry_interface.cpp#L256
which at line 270 registers the route to that plugin in `external_`; before that it calls the addExternalPlugin
, which you can follow in our plugin.h which is basically empty and always returns success, the only thing that implements it is a TablePlugin, which uses SQL to make the table appear in the local sqlite database.
Now a Config plugin works because the code uses the call
function of the Registry, which automatically uses all the registry types; you can see its implementation here:
https://github.com/osquery/osquery/blob/master/osquery/registry/registry_interface.cpp#L133
As you can see that also uses the routes previously registered in the external registryplugins("config_parser")
function only gets local plugins; after having select the config_parsers
registry, it ends up in the plugins()
function of the RegistryInterface
, here: https://github.com/osquery/osquery/blob/master/osquery/registry/registry_interface.cpp#L330
As you can see it’s returning the items_
member, but in RegistryInterface you also have external_
, which is where the external plugins are.--config_plugin
, which will receive the whole config and will have to select and modify only what’s interested in.--config_plugin
. In theory each plugin before should compose a final response which contains everything the core has to use; so the last plugin in the list should get all.Daniel Bretón Suárez
11/09/2022, 2:57 PMosquery::Registry::call("config", {{"action", "genConfig"}}, config);
and then, parsing it into a map the same way it is done at https://github.com/osquery/osquery/blob/f8bd96e1ad499397596e3fb551ac8f0065e43c32/plugins/config/parsers/options.cpp#L76
std::map<std::string, std::string>
Config::parseDistConfig(const osquery::PluginResponse & config,
std::string plugin, std::string cat)
{
std::map<std::string, std::string> ret;
for (auto conf : config) {
for (auto c1 : conf) {
osquery::JSON json;
json.fromString(c1.second);
const auto& options = json.doc()[plugin][cat];
for (const auto& option : options.GetObject()) {
std::string name = option.name.GetString();
std::string value;
if (option.value.IsString()) {
value = option.value.GetString();
} else if (option.value.IsBool()) {
value = (option.value.GetBool()) ? "true" : "false";
} else if (option.value.IsInt()) {
value = std::to_string(option.value.GetInt());
} else if (option.value.IsNumber()) {
value = std::to_string(option.value.GetUint64());
} else if (option.value.IsObject() || option.value.IsArray()) {
auto doc = osquery::JSON::newFromValue(option.value);
doc.toString(value);
} else {
DEVOLOG(WARNING) << "Cannot parse unknown value type for option: " << name;
}
ret.insert({name, value});
}
}
}
return ret;
}