diff --git a/doc/falcodump.adoc b/doc/falcodump.adoc index a16629c6e0..d2a4ebf654 100644 --- a/doc/falcodump.adoc +++ b/doc/falcodump.adoc @@ -69,6 +69,17 @@ Save captured packet to file or send it through pipe. --plugin-source=:: 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: diff --git a/extcap/falcodump.cpp b/extcap/falcodump.cpp index 0f3c295467..84308ddfe7 100644 --- a/extcap/falcodump.cpp +++ b/extcap/falcodump.cpp @@ -51,6 +51,7 @@ #define FALCODUMP_PLUGIN_PLACEHOLDER "" +// 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::vectorenum_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 find_json_value(const std::string blob, const return std::pair(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("", false); } +// Convert a JSON array to a string vector. +// Returns (vector, true) on success, or (err_str, false) on failure. +const std::pair,bool> get_json_array(const std::string blob) { + std::vector tokens; + int num_tokens = json_parse(blob.c_str(), NULL, 0); + + switch (num_tokens) { + case JSMN_ERROR_INVAL: + return std::pair,bool>(std::vector{"invalid"}, false); + case JSMN_ERROR_PART: + { + return std::pair,bool>(std::vector{"incomplete"}, false); + } + default: + break; + } + + tokens.resize(num_tokens); + json_parse(blob.c_str(), tokens.data(), num_tokens); + std::vector 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,bool>(elements, true); +} + // Given a JSON blob containing a schema properties map, add each property to the // given plugin config. const std::pair parse_schema_properties(const std::string props_blob, const std::string plugin_name, plugin_configuration &plugin_config) { @@ -187,25 +220,26 @@ const std::pair 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 enum_values; - std::pair jv = find_json_value(property_blob, "title", JSMN_OBJECT|JSMN_ARRAY, JSMN_OBJECT|JSMN_ARRAY); + std::pair 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(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(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 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,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 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 &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 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 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);