falcodump: Add support for selection options.

If a plugin has an "enum" + array in its configuration, convert it to a
selector option.

Start adding plugin sections to the falcodump man page.
This commit is contained in:
Gerald Combs 2022-09-21 09:33:01 -07:00
parent 91bb68c86a
commit 28a26096fb
2 changed files with 106 additions and 26 deletions

View File

@ -69,6 +69,17 @@ Save captured packet to file or send it through pipe.
--plugin-source=<source path or URL>::
Capture from the specified location.
== PLUGINS
=== cloudtrail (AWS CloudTrail)
CloudTrail sources can be S3 buckets or SQS queue URLs. S3 bucket URLs have the form
s3://__bucket_name__/AWSLogs/__id__/CloudTrail/__region__/__year__/_month_/__day__
The __region__, __year__, _month_, and __day__ components can be omitted in order to fetch more or less data.
For example, the source s3://mybucket/AWSLogs/012345678/CloudTrail/us-west-2/2023 will fetch all CloudWatch logs for the year 2023.
== EXAMPLES
To see program arguments:

View File

@ -51,6 +51,7 @@
#define FALCODUMP_PLUGIN_PLACEHOLDER "<plugin name>"
// We load our plugins and fetch their configs before we set our log level.
// #define DEBUG_JSON_PARSING
// #define DEBUG_SINSP
@ -75,6 +76,7 @@ struct config_schema_properties {
std::string type; // "boolean", "integer", "string"
std::string description;
std::string default_value;
std::vector<std::string>enum_values;
std::string current_value;
};
@ -91,7 +93,7 @@ struct plugin_configuration {
continue;
}
json_dumper_set_member_name(&dumper, prop.name.c_str());
if (prop.type == "string") {
if (prop.type == "string" || prop.type == "selector") {
json_dumper_value_string(&dumper, prop.current_value.c_str());
} else {
json_dumper_value_anyf(&dumper, "%s", prop.current_value.c_str());
@ -149,12 +151,43 @@ const std::pair<std::string,bool> find_json_value(const std::string blob, const
return std::pair<std::string,bool>(value, true);
}
#ifdef DEBUG_JSON_PARSING
else if (cur_key == key) ws_debug("|%s(%d): %s(%d)|\n", cur_key.c_str(), k_tok.type, blob.substr(v_tok.start, v_tok.end - v_tok.start).c_str(), v_tok.type);
else if (cur_key == key) ws_warning("|%s(%d): %s(%d)|\n", cur_key.c_str(), k_tok.type, blob.substr(v_tok.start, v_tok.end - v_tok.start).c_str(), v_tok.type);
#endif
}
return std::pair<std::string,bool>("", false);
}
// Convert a JSON array to a string vector.
// Returns (vector, true) on success, or (err_str, false) on failure.
const std::pair<std::vector<std::string>,bool> get_json_array(const std::string blob) {
std::vector<jsmntok_t> tokens;
int num_tokens = json_parse(blob.c_str(), NULL, 0);
switch (num_tokens) {
case JSMN_ERROR_INVAL:
return std::pair<std::vector<std::string>,bool>(std::vector<std::string>{"invalid"}, false);
case JSMN_ERROR_PART:
{
return std::pair<std::vector<std::string>,bool>(std::vector<std::string>{"incomplete"}, false);
}
default:
break;
}
tokens.resize(num_tokens);
json_parse(blob.c_str(), tokens.data(), num_tokens);
std::vector<std::string> elements;
// First token is the full array.
for (int idx = 1; idx < num_tokens; idx++) {
jsmntok_t &el_tok = tokens[idx];
elements.push_back(blob.substr(el_tok.start, el_tok.end - el_tok.start));
}
#ifdef DEBUG_JSON_PARSING
ws_warning("%s: %d\n", blob.c_str(), (int)elements.size());
#endif
return std::pair<std::vector<std::string>,bool>(elements, true);
}
// Given a JSON blob containing a schema properties map, add each property to the
// given plugin config.
const std::pair<std::string,bool> parse_schema_properties(const std::string props_blob, const std::string plugin_name, plugin_configuration &plugin_config) {
@ -187,25 +220,26 @@ const std::pair<std::string,bool> parse_schema_properties(const std::string prop
std::string display = name;
jsmntok_t &p_tok = tokens[idx+1];
std::string property_blob = props_blob.substr(p_tok.start, p_tok.end - p_tok.start);
std::vector<std::string> enum_values;
std::pair<std::string,bool> jv = find_json_value(property_blob, "title", JSMN_OBJECT|JSMN_ARRAY, JSMN_OBJECT|JSMN_ARRAY);
std::pair<std::string,bool> jv = find_json_value(property_blob, "title", JSMN_STRING, JSMN_STRING);
if (jv.second) {
display = jv.first;
}
// else split+capitalize "name"?
jv = find_json_value(property_blob, "type", JSMN_OBJECT|JSMN_ARRAY, JSMN_OBJECT|JSMN_ARRAY);
jv = find_json_value(property_blob, "type", JSMN_STRING, JSMN_STRING);
if (!jv.second) {
return std::pair<std::string,bool>(jv.first, false);
}
std::string type = jv.first;
jv = find_json_value(property_blob, "description", JSMN_OBJECT|JSMN_ARRAY, JSMN_OBJECT|JSMN_ARRAY);
jv = find_json_value(property_blob, "description", JSMN_STRING, JSMN_STRING);
if (!jv.second) {
return std::pair<std::string,bool>(jv.first, false);
}
std::string description = jv.first;
std::string default_value;
jv = find_json_value(property_blob, "default", JSMN_OBJECT|JSMN_ARRAY, JSMN_OBJECT|JSMN_ARRAY);
jv = find_json_value(property_blob, "default", JSMN_STRING, JSMN_STRING);
if (jv.second) {
default_value = jv.first;
} else {
@ -217,8 +251,16 @@ const std::pair<std::string,bool> parse_schema_properties(const std::string prop
default_value = default_value.erase(pfx_pos);
}
}
jv = find_json_value(property_blob, "enum", JSMN_STRING, JSMN_ARRAY);
if (jv.second) {
const std::pair<std::vector<std::string>,bool> ja = get_json_array(jv.first);
if (ja.second) {
enum_values = ja.first;
type = "selector";
}
}
#ifdef DEBUG_JSON_PARSING
ws_debug("%s: %s, %s\n", name.c_str(), type.c_str(), description.c_str());
ws_warning("%s: %s, %s, [%d]\n", name.c_str(), type.c_str(), description.c_str(), (int)enum_values.size());
#endif
const char *call = g_ascii_strdown(name.c_str(), -1);
config_schema_properties properties = {
@ -229,7 +271,8 @@ const std::pair<std::string,bool> parse_schema_properties(const std::string prop
type,
description,
default_value,
default_value
enum_values,
default_value,
};
plugin_config.properties.push_back(properties);
g_free((gpointer)call);
@ -274,30 +317,46 @@ static bool get_plugin_config_schema(const std::shared_ptr<sinsp_plugin> &plugin
{
ss_plugin_schema_type schema_type = SS_PLUGIN_SCHEMA_JSON;
std::string init_schema = plugin->get_init_schema(schema_type);
std::string config_name;
std::pair<std::string,bool> jv = find_json_value(init_schema, "definitions", JSMN_OBJECT|JSMN_ARRAY, JSMN_OBJECT);
#ifdef DEBUG_JSON_PARSING
ws_warning("schema: %s\n", init_schema.c_str());
#endif
std::pair<std::string,bool> jv = find_json_value(init_schema, "$ref", JSMN_STRING, JSMN_STRING);
if (!jv.second) {
ws_warning("ERROR: Interface \"%s\" has an %s configuration schema.", plugin->name().c_str(), jv.first.c_str());
ws_warning("ERROR: Unable to find schema reference for interface \"%s\".", plugin->name().c_str());
return false;
}
#ifdef DEBUG_JSON_PARSING
ws_debug("%s\n", jv.first.c_str());
#endif
std::string definitions = jv.first;
jv = find_json_value(definitions, "PluginConfig", JSMN_OBJECT|JSMN_ARRAY, JSMN_OBJECT);
std::string schema_ref = jv.first;
std::string ref_pfx = "#/definitions/";
if (schema_ref.substr(0, ref_pfx.size()) == ref_pfx) {
config_name = schema_ref.substr(ref_pfx.size());
}
if (config_name.size() < 1) {
ws_warning("ERROR: Unable to find configuration name for interface \"%s\".", plugin->name().c_str());
return false;
}
jv = find_json_value(init_schema, "definitions", JSMN_STRING, JSMN_OBJECT);
if (!jv.second) {
ws_warning("ERROR: Interface \"%s\" has an %s configuration schema.", plugin->name().c_str(), jv.first.c_str());
ws_warning("ERROR: Definitions not found in interface \"%s\" configuration schema.", plugin->name().c_str());
return false;
}
std::string definitions = jv.first;
jv = find_json_value(definitions, config_name, JSMN_STRING, JSMN_OBJECT);
if (!jv.second) {
ws_warning("ERROR: Configuration \"%s\" not found in interface \"%s\" configuration schema.", config_name.c_str(), plugin->name().c_str());
return false;
}
std::string pluginconfig = jv.first;
jv = find_json_value(pluginconfig, "properties", JSMN_OBJECT|JSMN_ARRAY, JSMN_OBJECT);
jv = find_json_value(pluginconfig, "properties", JSMN_STRING, JSMN_OBJECT);
if (!jv.second) {
ws_warning("ERROR: Interface \"%s\" has an %s configuration schema.", plugin->name().c_str(), jv.first.c_str());
return false;
}
#ifdef DEBUG_JSON_PARSING
ws_debug("properties: %s\n", jv.first.c_str());
#endif
jv = parse_schema_properties(jv.first, plugin->name(), plugin_config);
if (!jv.second) {
ws_warning("ERROR: Interface \"%s\" has an %s configuration schema.", plugin->name().c_str(), jv.first.c_str());
@ -373,23 +432,33 @@ static int show_config(const std::string &interface, const struct plugin_configu
arg_num++);
// if (plugin_filter)
// printf("{default=%s}", plugin_filter);
// printf("{group=Capture}\n");
for (const auto &properties : plugin_config.properties) {
std::string default_value;
if (!properties.default_value.empty()) {
default_value = "{default=" + properties.default_value + "}";
}
const char *cfg_line = g_strdup_printf(
printf(
"arg {number=%d}"
"{call=--%s}"
"{display=%s}"
"{type=%s}"
"%s"
"{tooltip=%s}"
"{group=Capture}",
arg_num++, properties.option.c_str(), properties.display.c_str(), properties.type.c_str(), default_value.c_str(), properties.description.c_str());
puts(cfg_line);
g_free((gpointer)cfg_line);
"{group=Capture}"
"\n",
arg_num, properties.option.c_str(), properties.display.c_str(), properties.type.c_str(), default_value.c_str(), properties.description.c_str());
if (properties.enum_values.size() > 0) {
for (const auto &enum_val : properties.enum_values) {
printf(
"value {arg=%d}"
"{value=%s}"
"{display=%s}"
"%s"
"\n",
arg_num, enum_val.c_str(), enum_val.c_str(), enum_val == default_value ? "{default=true}" : "");
}
}
arg_num++;
}
extcap_config_debug(&arg_num);