2 * Falcodump is an extcap tool which dumps logs using Falco source plugins.
3 * https://falco.org/docs/plugins/
5 * Adapted from sdjournal.
6 * Copyright 2022, Gerald Combs and Dario Lombardo
8 * Wireshark - Network traffic analyzer
9 * By Gerald Combs <gerald@wireshark.org>
10 * Copyright 1998 Gerald Combs
12 * SPDX-License-Identifier: GPL-2.0-or-later
17 * - Pull plugin source description from list_open_params?
19 * - Add an option to dump plugin fields.
20 * - Add options for credentials.
21 * - Let the user create preconfigured interfaces.
22 * - Exit more cleanly (see MRs 2063 and 7673).
23 * - Better config schema default value parsing? Would likely require a schema change.
24 * - Make sure all types are handled in parse_schema_properties.
25 * - Handle "required" config schema annotation (Okta).
30 #include <libsinsp/sinsp.h>
31 #include <plugin_manager.h>
33 #include <scap_engines.h>
35 #define WS_LOG_DOMAIN "falcodump"
37 #include <extcap/extcap-base.h>
39 #include <wsutil/file_util.h>
40 #include <wsutil/filesystem.h>
41 #include <wsutil/json_dumper.h>
42 #include <wsutil/privileges.h>
43 #include <wsutil/utf8_entities.h>
44 #include <wsutil/wsjson.h>
45 #include <wsutil/wslog.h>
47 #define FALCODUMP_VERSION_MAJOR "1"
48 #define FALCODUMP_VERSION_MINOR "0"
49 #define FALCODUMP_VERSION_RELEASE "0"
51 #define FALCODUMP_PLUGIN_PLACEHOLDER "<plugin name>"
53 #define SINSP_CHECK_VERSION(major, minor, micro) \
54 (((SINSP_VERSION_MAJOR << 16) + (SINSP_VERSION_MINOR << 8) + SINSP_VERSION_MICRO) >= ((major << 16) + (minor << 8) + micro))
56 // We load our plugins and fetch their configs before we set our log level.
57 // #define DEBUG_JSON_PARSING
58 // #define DEBUG_SINSP
61 EXTCAP_BASE_OPTIONS_ENUM
,
64 OPT_PLUGIN_API_VERSION
,
65 #if defined(HAS_ENGINE_KMOD) || defined(HAS_ENGINE_MODERN_BPF)
66 OPT_INCLUDE_CAPTURE_PROCESSES
,
67 OPT_INCLUDE_SWITCH_CALLS
,
70 OPT_SCHEMA_PROPERTIES_START
,
73 // "s3DownloadConcurrency": {
75 // "description": "Controls the number of background goroutines used to download S3 files (Default: 1)"
77 struct config_properties
{
79 std::string display
; // "title" property || name
80 std::string option
; // Command line option including leading dashes (lowercase display name)
81 int option_index
; // Starts from OPT_SCHEMA_PROPERTIES_START
82 std::string type
; // "boolean", "integer", "string", "enum", "BEGIN_CONFIG_PROPERTIES", or "END_CONFIG_PROPERTIES"
83 std::string description
;
84 std::string default_value
;
85 std::vector
<std::string
>enum_values
;
86 std::string current_value
;
89 #if defined(HAS_ENGINE_KMOD) || defined(HAS_ENGINE_MODERN_BPF)
90 struct syscall_configuration
{
91 bool include_capture_processes
;
92 bool include_switch_calls
;
96 struct plugin_configuration
{
97 std::vector
<struct config_properties
> property_list
;
99 std::string
json_config() {
100 json_dumper dumper
= {};
101 dumper
.output_string
= g_string_new(NULL
);
103 json_dumper_begin_object(&dumper
);
105 for (const auto &prop
: property_list
) {
106 if (prop
.type
== "BEGIN_CONFIG_PROPERTIES") {
107 json_dumper_set_member_name(&dumper
, prop
.name
.c_str());
108 json_dumper_begin_object(&dumper
);
110 } else if (prop
.type
== "END_CONFIG_PROPERTIES") {
111 json_dumper_end_object(&dumper
);
115 if (prop
.current_value
== prop
.default_value
) {
119 json_dumper_set_member_name(&dumper
, prop
.name
.c_str());
120 if (prop
.type
== "string" || prop
.type
== "selector") {
121 json_dumper_value_string(&dumper
, prop
.current_value
.c_str());
123 json_dumper_value_anyf(&dumper
, "%s", prop
.current_value
.c_str());
127 json_dumper_end_object(&dumper
);
128 json_dumper_finish(&dumper
);
129 std::string config_blob
= dumper
.output_string
->str
;
130 ws_debug("configuration: %s", dumper
.output_string
->str
);
131 g_string_free(dumper
.output_string
, TRUE
);
136 // Read a line without trailing (CR)LF. Returns -1 on failure. Copied from addr_resolv.c.
137 // XXX Use g_file_get_contents or GMappedFile instead?
139 fgetline(char *buf
, int size
, FILE *fp
)
141 if (fgets(buf
, size
, fp
)) {
142 int len
= (int)strcspn(buf
, "\r\n");
149 static const size_t MAX_AWS_LINELEN
= 2048;
150 void print_cloudtrail_aws_profile_config(int arg_num
, const char *display
, const char *description
) {
151 char buf
[MAX_AWS_LINELEN
];
152 char profile_name
[MAX_AWS_LINELEN
];
154 std::set
<std::string
>profiles
;
156 // Look in files as specified in https://docs.aws.amazon.com/cli/latest/userguide/cli-configure-envvars.html
157 char *cred_path
= g_strdup(g_getenv("AWS_SHARED_CREDENTIALS_FILE"));
158 if (cred_path
== NULL
) {
159 cred_path
= g_build_filename(g_get_home_dir(), ".aws", "credentials", (char *)NULL
);
162 aws_fp
= ws_fopen(cred_path
, "r");
165 if (aws_fp
!= NULL
) {
166 while (fgetline(buf
, sizeof(buf
), aws_fp
) >= 0) {
167 if (sscanf(buf
, "[%2047[^]]s]", profile_name
) == 1) {
168 if (strcmp(profile_name
, "default") == 0) {
171 profiles
.insert(profile_name
);
177 char *conf_path
= g_strdup(g_getenv("AWS_CONFIG_FILE"));
178 if (conf_path
== NULL
) {
179 conf_path
= g_build_filename(g_get_home_dir(), ".aws", "config", (char *)NULL
);
182 aws_fp
= ws_fopen(conf_path
, "r");
185 if (aws_fp
!= NULL
) {
186 while (fgetline(buf
, sizeof(buf
), aws_fp
) >= 0) {
187 if (sscanf(buf
, "[profile %2047[^]]s]", profile_name
) == 1) {
188 if (strcmp(profile_name
, "default") == 0) {
191 profiles
.insert(profile_name
);
197 const char *aws_profile_env
= g_getenv("AWS_PROFILE");
198 for (auto &profile
: profiles
) {
199 if (aws_profile_env
&& profile
== aws_profile_env
) {
200 aws_profile_env
= nullptr;
203 if (aws_profile_env
) {
204 profiles
.insert(aws_profile_env
);
209 "{call=--cloudtrail-aws-profile}"
211 "{type=editselector}"
215 arg_num
, display
, description
);
216 printf ("value {arg=%d}{value=}{display=Default}{default=true}\n", arg_num
);
217 for (auto &profile
: profiles
) {
223 arg_num
, profile
.c_str(), profile
.c_str());
227 void print_cloudtrail_aws_region_config(int arg_num
, const char *display
, const char *description
) {
228 // printf ' "%s",\n' $(aws ec2 describe-regions --all-regions --query "Regions[].{Name:RegionName}" --output text) | sort
229 std::set
<std::string
> regions
= {
261 const char *aws_region_env
= g_getenv("AWS_REGION");
262 for (auto ®ion
: regions
) {
263 if (aws_region_env
&& region
== aws_region_env
) {
264 aws_region_env
= nullptr;
267 if (aws_region_env
) {
268 regions
.insert(aws_region_env
);
273 "{call=--cloudtrail-aws-region}"
275 "{type=editselector}"
279 arg_num
, display
, description
);
280 printf ("value {arg=%d}{value=}{display=From profile}{default=true}\n", arg_num
);
282 for (auto ®ion
: regions
) {
288 arg_num
, region
.c_str(), region
.c_str());
293 // Load our plugins. This should match the behavior of the Falco Bridge dissector.
294 static void load_plugins(sinsp
&inspector
) {
297 char *plugin_paths
[] = {
298 // XXX Falco plugins should probably be installed in a path that reflects
299 // the Falco version or its plugin API version.
300 g_build_filename(get_plugins_dir(), "falco", NULL
),
301 g_build_filename(get_plugins_pers_dir(), "falco", NULL
)
304 for (size_t idx
= 0; idx
< 2; idx
++) {
305 char *plugin_path
= plugin_paths
[idx
];
306 if ((dir
= ws_dir_open(plugin_path
, 0, NULL
)) != NULL
) {
307 while ((file
= ws_dir_read_name(dir
)) != NULL
) {
308 char *libname
= g_build_filename(plugin_path
, ws_dir_get_name(file
), NULL
);
310 auto plugin
= inspector
.register_plugin(libname
);
311 ws_debug("Registered plugin %s via %s", plugin
->name().c_str(), libname
);
312 } catch (sinsp_exception
&e
) {
313 ws_warning("%s", e
.what());
323 // Given a key, try to find its value in a JSON object.
324 // Returns (value, true) on success, or (err_str, false) on failure.
325 const std::pair
<const std::string
,bool> find_json_object_value(const std::string
&object_blob
, const std::string
&key
, int value_type
) {
326 std::vector
<jsmntok_t
> tokens
;
327 int num_tokens
= json_parse(object_blob
.c_str(), NULL
, 0);
329 switch (num_tokens
) {
330 case JSMN_ERROR_INVAL
:
331 return std::pair
<std::string
,bool>("invalid", false);
332 case JSMN_ERROR_PART
:
333 return std::pair
<std::string
,bool>("incomplete", false);
338 tokens
.resize(num_tokens
);
339 json_parse(object_blob
.c_str(), tokens
.data(), num_tokens
);
340 for (int idx
= 0; idx
< num_tokens
- 1; idx
++) {
341 jsmntok_t
&k_tok
= tokens
[idx
];
342 jsmntok_t
&v_tok
= tokens
[idx
+1];
343 std::string cur_key
= object_blob
.substr(k_tok
.start
, k_tok
.end
- k_tok
.start
);
344 if (cur_key
== key
&& k_tok
.type
== JSMN_STRING
&& v_tok
.type
== value_type
) {
345 std::string value
= object_blob
.substr(v_tok
.start
, v_tok
.end
- v_tok
.start
);
346 return std::pair
<std::string
,bool>(value
, true);
348 #ifdef DEBUG_JSON_PARSING
349 else if (cur_key
== key
) ws_warning("|%s(%d): %s(%d)|\n", cur_key
.c_str(), k_tok
.type
, object_blob
.substr(v_tok
.start
, v_tok
.end
- v_tok
.start
).c_str(), v_tok
.type
);
352 return std::pair
<const std::string
,bool>("", false);
355 // Given an RFC 6901-style JSON pointer, try to find its value in a JSON object.
356 // Returns (value, true) on success, or (err_str, false) on failure.
357 const std::pair
<const std::string
,bool> find_json_pointer_value(const std::string
&object_blob
, const std::string
&pointer
, int value_type
) {
358 std::string blob
= object_blob
;
359 std::istringstream
ob_stream(pointer
);
361 while (std::getline(ob_stream
, token
, '/')) {
362 if (token
== "#" || token
.empty()) {
365 std::pair
<std::string
,bool> jv
= find_json_object_value(blob
, token
, value_type
);
367 #ifdef DEBUG_JSON_PARSING
368 ws_warning("JSON pointer %s not found at %s", blob
.c_str(), token
.c_str());
370 return std::pair
<std::string
,bool>("", false);
374 #ifdef DEBUG_JSON_PARSING
375 ws_warning("JSON pointer %s = %s ... %s", pointer
.c_str(), blob
.substr(0, 10).c_str(), blob
.substr(blob
.size() - 10, 10).c_str());
377 return std::pair
<const std::string
,bool>(blob
, true);
380 // Convert a JSON array to a string vector.
381 // Returns (vector, true) on success, or (err_str, false) on failure.
382 const std::pair
<std::vector
<std::string
>,bool> get_json_array(const std::string
&array_blob
) {
383 std::vector
<jsmntok_t
> tokens
;
384 int num_tokens
= json_parse(array_blob
.c_str(), NULL
, 0);
386 switch (num_tokens
) {
387 case JSMN_ERROR_INVAL
:
388 return std::pair
<std::vector
<std::string
>,bool>(std::vector
<std::string
>{"invalid"}, false);
389 case JSMN_ERROR_PART
:
391 return std::pair
<std::vector
<std::string
>,bool>(std::vector
<std::string
>{"incomplete"}, false);
397 tokens
.resize(num_tokens
);
398 json_parse(array_blob
.c_str(), tokens
.data(), num_tokens
);
399 std::vector
<std::string
> elements
;
400 // First token is the full array.
401 for (int idx
= 1; idx
< num_tokens
; idx
++) {
402 jsmntok_t
&el_tok
= tokens
[idx
];
403 elements
.push_back(array_blob
.substr(el_tok
.start
, el_tok
.end
- el_tok
.start
));
405 #ifdef DEBUG_JSON_PARSING
406 ws_warning("%s: %d", array_blob
.c_str(), (int)elements
.size());
408 return std::pair
<std::vector
<std::string
>,bool>(elements
, true);
411 // Given a JSON blob containing a schema properties object, add each property to the
412 // given plugin config.
413 const std::pair
<const std::string
,bool> get_schema_properties(const std::string props_blob
, int &opt_idx
, const std::string option_prefix
, const std::string plugin_name
, std::vector
<struct config_properties
> &property_list
) {
414 std::vector
<jsmntok_t
> tokens
;
415 int num_tokens
= json_parse(props_blob
.c_str(), NULL
, 0);
417 switch (num_tokens
) {
418 case JSMN_ERROR_INVAL
:
419 return std::pair
<std::string
,bool>("invalid", false);
420 case JSMN_ERROR_PART
:
421 return std::pair
<std::string
,bool>("incomplete", false);
426 tokens
.resize(num_tokens
);
427 json_parse(props_blob
.c_str(), tokens
.data(), num_tokens
);
429 if (tokens
[0].type
!= JSMN_OBJECT
) {
430 return std::pair
<std::string
,bool>("malformed", false);
433 char *plugin_name_lower
= g_ascii_strdown(plugin_name
.c_str(), -1);
435 // We have an object blob which contains a list of properties and property blobs, e.g.
436 // { "property_1": { "type": ... }, "prob_blob_1": { "properties": { "prop_blob_2": { "type": ... } } }
437 // Skip over the outer { ... } and process the contents as pairs.
438 for (int idx
= 1; idx
< num_tokens
- 2; idx
++) {
439 jsmntok_t
&n_tok
= tokens
[idx
];
440 jsmntok_t
&p_tok
= tokens
[idx
+1];
442 std::string name
= props_blob
.substr(n_tok
.start
, n_tok
.end
- n_tok
.start
);
443 std::string display
= name
;
444 std::string property_blob
= props_blob
.substr(p_tok
.start
, p_tok
.end
- p_tok
.start
);
445 std::vector
<std::string
> enum_values
;
447 // XXX Check for errors?
448 int prop_tokens
= json_parse(property_blob
.c_str(), NULL
, 0);
449 switch (prop_tokens
) {
450 case JSMN_ERROR_INVAL
:
451 return std::pair
<std::string
,bool>("invalid property", false);
452 case JSMN_ERROR_PART
:
453 return std::pair
<std::string
,bool>("incomplete property", false);
457 #ifdef DEBUG_JSON_PARSING
458 ws_warning("property %s [%d]\n", name
.c_str(), prop_tokens
);
461 std::pair
<std::string
,bool> jv
= find_json_object_value(property_blob
, "properties", JSMN_OBJECT
);
463 config_properties properties
= {
468 "BEGIN_CONFIG_PROPERTIES",
474 property_list
.push_back(properties
);
475 get_schema_properties(jv
.first
, opt_idx
, option_prefix
+ "-" + name
, plugin_name
, property_list
);
481 "END_CONFIG_PROPERTIES",
487 property_list
.push_back(properties
);
492 jv
= find_json_object_value(property_blob
, "title", JSMN_STRING
);
496 // else split+capitalize "name"?
498 jv
= find_json_object_value(property_blob
, "type", JSMN_STRING
);
500 return std::pair
<std::string
,bool>("missing type", false);
502 std::string type
= jv
.first
;
503 jv
= find_json_object_value(property_blob
, "description", JSMN_STRING
);
505 return std::pair
<std::string
,bool>("missing description", false);
507 std::string description
= jv
.first
;
508 std::string default_value
;
509 jv
= find_json_object_value(property_blob
, "default", JSMN_STRING
);
511 default_value
= jv
.first
;
513 std::string default_pfx
= "(Default: ";
514 size_t pfx_pos
= description
.rfind(default_pfx
);
515 if (pfx_pos
!= std::string::npos
) {
516 default_value
= description
.substr(pfx_pos
+ default_pfx
.size());
517 pfx_pos
= default_value
.rfind(")");
518 default_value
= default_value
.erase(pfx_pos
);
521 jv
= find_json_object_value(property_blob
, "enum", JSMN_ARRAY
);
523 const std::pair
<std::vector
<std::string
>,bool> ja
= get_json_array(jv
.first
);
525 enum_values
= ja
.first
;
529 #ifdef DEBUG_JSON_PARSING
530 ws_warning("%s: %s, %s, [%d]\n", name
.c_str(), type
.c_str(), description
.c_str(), (int)enum_values
.size());
532 const char *call
= g_ascii_strdown(name
.c_str(), -1);
533 config_properties properties
= {
536 std::string() + plugin_name_lower
+ option_prefix
+ "-" + call
, // Command line option (lowercase plugin + display name)
544 property_list
.push_back(properties
);
545 g_free((void *)call
);
549 g_free(plugin_name_lower
);
550 return std::pair
<std::string
,bool>("",true);
553 // Wherein we try to implement a sufficiently complete JSON Schema parser.
554 // Given a plugin config schema like the following:
556 // "$schema": "http://json-schema.org/draft-04/schema#",
557 // "$ref": "#/definitions/PluginConfig",
561 // "s3DownloadConcurrency": {
562 // "type": "integer",
563 // "title": "S3 download concurrency",
564 // "description": "Controls the number of background goroutines used to download S3 files (Default: 1)",
569 // "$schema": "http://json-schema.org/draft-04/schema#",
570 // "$ref": "#/definitions/PluginConfigAWS"
573 // "additionalProperties": true,
576 // "PluginConfigAWS": {
580 // "title": "Shared AWS Config Profile",
581 // "description": "If non-empty",
586 // "additionalProperties": true,
591 // find the plugin properties and parse them using parse_schema_properties.
592 // https://json-schema.org/draft/2020-12/json-schema-validation.html#name-a-vocabulary-for-basic-meta
594 static bool get_plugin_config_schema(const std::shared_ptr
<sinsp_plugin
> &plugin
, plugin_configuration
&plugin_config
)
596 ss_plugin_schema_type schema_type
= SS_PLUGIN_SCHEMA_JSON
;
597 std::string schema_blob
= plugin
->get_init_schema(schema_type
);
598 std::string config_name
;
599 std::pair
<std::string
,bool> jv
;
601 #ifdef DEBUG_JSON_PARSING
602 ws_warning("raw schema: %s\n", schema_blob
.c_str());
606 std::string::size_type ref_pos
= 0;
607 while ((ref_pos
= schema_blob
.find("\"$ref\"", ref_pos
)) != std::string::npos
) {
612 // Dereference all of our $ref pairs.
613 // This is kind of janky, but makes subsequent parsing more simple.
614 for (int ref_idx
= 0; ref_idx
< ref_cnt
; ref_idx
++) {
615 jv
= find_json_object_value(schema_blob
, "$ref", JSMN_STRING
);
619 const std::string ref_pointer
= jv
.first
;
620 jv
= find_json_pointer_value(schema_blob
, ref_pointer
, JSMN_OBJECT
);
622 ws_warning("Unable to find $ref %s.", ref_pointer
.c_str());
625 const std::string ref_body
= jv
.first
.substr(1, jv
.first
.size() - 2);
627 std::vector
<jsmntok_t
> tokens
;
628 int num_tokens
= json_parse(schema_blob
.c_str(), NULL
, 0);
630 switch (num_tokens
) {
631 case JSMN_ERROR_INVAL
:
632 ws_warning("Invalid schema.");
634 case JSMN_ERROR_PART
:
636 ws_warning("Incomplete schema.");
643 #ifdef DEBUG_JSON_PARSING
644 ws_warning("searching for $ref: %s\n", ref_pointer
.c_str());
646 tokens
.resize(num_tokens
);
647 json_parse(schema_blob
.c_str(), tokens
.data(), num_tokens
);
648 std::vector
<std::string
> elements
;
649 // First token is the full array.
650 for (int idx
= 0; idx
< num_tokens
- 1; idx
++) {
651 if (tokens
[idx
].type
!= JSMN_STRING
&& tokens
[idx
+1].type
!= JSMN_STRING
) {
654 auto key_tok
= &tokens
[idx
];
655 auto val_tok
= &tokens
[idx
+1];
656 std::string key
= schema_blob
.substr(key_tok
->start
, key_tok
->end
- key_tok
->start
);
657 std::string value
= schema_blob
.substr(val_tok
->start
, val_tok
->end
- val_tok
->start
);
658 if (key
!= "$ref" || value
!= ref_pointer
) {
662 #ifdef DEBUG_JSON_PARSING
663 ws_warning("replacing: %s\n", schema_blob
.substr(key_tok
->start
- 1, val_tok
->end
- key_tok
->start
+ 2).c_str());
665 schema_blob
.replace(key_tok
->start
- 1, val_tok
->end
- key_tok
->start
+ 2, ref_body
);
666 } catch (std::out_of_range
const&) {
667 ws_warning("Unknown reference %s.", key
.c_str());
673 #ifdef DEBUG_JSON_PARSING
674 ws_warning("cooked schema: %s\n", schema_blob
.c_str());
677 // XXX Should each sub-schema ref be in its own category?
678 jv
= find_json_object_value(schema_blob
, "properties", JSMN_OBJECT
);
680 ws_warning("ERROR: Interface \"%s\" has an %s configuration schema.", plugin
->name().c_str(), schema_blob
.c_str());
683 int opt_idx
= OPT_SCHEMA_PROPERTIES_START
;
684 jv
= get_schema_properties(jv
.first
, opt_idx
, "", plugin
->name(), plugin_config
.property_list
);
686 ws_warning("ERROR: Interface \"%s\" has an unsupported or invalid configuration schema: %s", plugin
->name().c_str(), jv
.first
.c_str());
693 // For each loaded plugin, get its name and properties.
694 static bool get_source_plugins(sinsp
&inspector
, std::map
<std::string
, struct plugin_configuration
> &plugin_configs
) {
695 const auto plugin_manager
= inspector
.get_plugin_manager();
697 // XXX sinsp_plugin_manager::sources() can return different names, e.g. aws_cloudtrail vs cloudtrail.
699 for (auto &plugin
: plugin_manager
->plugins()) {
700 if (plugin
->caps() & CAP_SOURCING
) {
701 plugin_configuration plugin_config
= {};
702 if (!get_plugin_config_schema(plugin
, plugin_config
)) {
705 plugin_configs
[plugin
->name()] = plugin_config
;
708 } catch (sinsp_exception
&e
) {
709 ws_warning("%s", e
.what());
715 // Build our command line options based on our source plugins.
716 static const std::vector
<ws_option
> get_longopts(const std::map
<std::string
, struct plugin_configuration
> &plugin_configs
) {
717 std::vector
<ws_option
> longopts
;
718 struct ws_option base_longopts
[] = {
720 { "help", ws_no_argument
, NULL
, OPT_HELP
},
721 { "version", ws_no_argument
, NULL
, OPT_VERSION
},
722 { "plugin-api-version", ws_no_argument
, NULL
, OPT_PLUGIN_API_VERSION
},
723 #if defined(HAS_ENGINE_KMOD) || defined(HAS_ENGINE_MODERN_BPF)
724 { "include-capture-processes", ws_required_argument
, NULL
, OPT_INCLUDE_CAPTURE_PROCESSES
},
725 { "include-switch-calls", ws_required_argument
, NULL
, OPT_INCLUDE_SWITCH_CALLS
},
727 { "plugin-source", ws_required_argument
, NULL
, OPT_PLUGIN_SOURCE
},
731 for (idx
= 0; base_longopts
[idx
].name
; idx
++) {
732 longopts
.push_back(base_longopts
[idx
]);
734 for (const auto &it
: plugin_configs
) {
735 const struct plugin_configuration plugin_config
= it
.second
;
736 for (const auto &prop
: plugin_config
.property_list
) {
737 ws_option option
= { g_strdup(prop
.option
.c_str()), ws_required_argument
, NULL
, prop
.option_index
};
738 longopts
.push_back(option
);
741 longopts
.push_back(base_longopts
[idx
]);
745 #if defined(HAS_ENGINE_KMOD) || defined(HAS_ENGINE_MODERN_BPF)
747 get_bool_value(const char *bool_str
)
752 switch (bool_str
[0]) {
762 // Show the configuration for a given plugin/interface.
763 static int show_syscall_config(void)
767 "{call=--include-capture-processes}"
768 "{display=Include capture processes}"
770 "{tooltip=Include system calls made by any capture processes (falcodump, dumpcap, and Stratoshark)}"
775 "{call=--include-switch-calls}"
776 "{display=Include \"switch\" calls}"
778 "{tooltip=Include \"switch\" system calls}"
782 "value {arg=0}{value=1}\n"
790 static const std::string
syscall_capture_filter(const struct syscall_configuration
&syscall_config
, const char *capture_filter
)
792 if (syscall_config
.include_capture_processes
&& syscall_config
.include_switch_calls
) {
793 if (capture_filter
) {
794 return std::string(capture_filter
);
796 return std::string();
802 if (capture_filter
) {
803 filter
= "(" + std::string(capture_filter
) + ") and (";
806 if (!syscall_config
.include_capture_processes
) {
807 // We want to exclude Stratoshark and any of its children, including
808 // this one (falcodump).
810 std::string pid
, comm
, _s
, ppid
;
812 // Exclude this process only at a minimum.
813 std::ifstream
stat_stream("/proc/self/stat");
814 stat_stream
>> pid
>> comm
>> _s
>> ppid
;
815 std::string process_filter
= "proc.pid != " + pid
;
816 if (comm
!= "(falcodump)") {
817 ws_warning("Our process is named %s, not falcodump", comm
.c_str());
821 // If our parent is Stratoshark, exclude it and its direct children.
822 std::ifstream
pstat_stream("/proc/" + ppid
+ "/stat");
823 pstat_stream
>> _s
>> comm
;
824 if (comm
== "(stratoshark)") {
825 // XXX Use proc.apid instead?
826 process_filter
= "proc.pid != " + ppid
+ " and proc.ppid != " + ppid
;
828 pstat_stream
.close();
830 filter
+= process_filter
;
833 if (!syscall_config
.include_switch_calls
) {
834 if (!syscall_config
.include_capture_processes
) {
837 filter
+= "evt.type != switch";
840 if (capture_filter
) {
846 #endif // HAS_ENGINE_KMOD || HAS_ENGINE_MODERN_BPF
848 // Show the configuration for a given plugin/interface.
849 static int show_plugin_config(const std::string
&interface
, const struct plugin_configuration
&plugin_config
)
851 unsigned arg_num
= 0;
852 // char* plugin_filter;
854 if (interface
.empty()) {
855 ws_warning("ERROR: No interface specified.");
861 "{call=--plugin-source}"
862 "{display=Log data URL}"
864 "{tooltip=The plugin data source. This is usually a URL.}"
865 "{placeholder=Enter a source URL" UTF8_HORIZONTAL_ELLIPSIS
"}"
869 // if (plugin_filter)
870 // printf("{default=%s}", plugin_filter);
871 for (const auto &properties
: plugin_config
.property_list
) {
872 if (properties
.option_index
< OPT_SCHEMA_PROPERTIES_START
) {
875 std::string default_value
;
876 if (!properties
.default_value
.empty()) {
877 default_value
= "{default=" + properties
.default_value
+ "}";
879 if (properties
.option
== "cloudtrail-aws-profile") {
880 print_cloudtrail_aws_profile_config(arg_num
, properties
.display
.c_str(), properties
.description
.c_str());
881 } else if (properties
.option
== "cloudtrail-aws-region") {
882 print_cloudtrail_aws_region_config(arg_num
, properties
.display
.c_str(), properties
.description
.c_str());
893 arg_num
, properties
.option
.c_str(), properties
.display
.c_str(), properties
.type
.c_str(), default_value
.c_str(), properties
.description
.c_str());
894 if (properties
.enum_values
.size() > 0) {
895 for (const auto &enum_val
: properties
.enum_values
) {
902 arg_num
, enum_val
.c_str(), enum_val
.c_str(), enum_val
== default_value
? "{default=true}" : "");
908 extcap_config_debug(&arg_num
);
913 int main(int argc
, char **argv
)
915 char* configuration_init_error
;
918 int ret
= EXIT_FAILURE
;
919 extcap_parameters
* extcap_conf
= g_new0(extcap_parameters
, 1);
920 std::map
<std::string
, struct plugin_configuration
> plugin_configs
;
921 #if defined(HAS_ENGINE_KMOD) || defined(HAS_ENGINE_MODERN_BPF)
922 struct syscall_configuration syscall_config
= {};
925 char* help_header
= NULL
;
927 std::string plugin_source
;
929 /* Initialize log handler early so we can have proper logging during startup. */
930 extcap_log_init("falcodump");
933 * Get credential information for later use.
935 init_process_policies();
938 * Attempt to get the pathname of the directory containing the
941 configuration_init_error
= configuration_init(argv
[0], "Stratoshark");
942 if (configuration_init_error
!= NULL
) {
943 ws_warning("Can't get pathname of directory containing the extcap program: %s.",
944 configuration_init_error
);
945 g_free(configuration_init_error
);
948 // Plain eBPF requires extra configuration, so probe for kmod and modern BPF support only for now.
949 #ifdef HAS_ENGINE_KMOD
951 inspector
.open_kmod();
952 extcap_base_register_interface(extcap_conf
, KMOD_ENGINE
, "System calls via kernel module", 147, "USER0");
953 } catch (sinsp_exception
&e
) {
954 ws_warning("Unable to open kmod: %s", e
.what());
958 #ifdef HAS_ENGINE_MODERN_BPF
960 inspector
.open_modern_bpf();
961 extcap_base_register_interface(extcap_conf
, MODERN_BPF_ENGINE
, "System calls via modern eBPF", 147, "USER0");
962 } catch (sinsp_exception
&e
) {
963 ws_warning("Unable to open kmod: %s", e
.what());
968 load_plugins(inspector
);
970 if (get_source_plugins(inspector
, plugin_configs
)) {
971 for (auto iter
= plugin_configs
.begin(); iter
!= plugin_configs
.end(); ++iter
) {
972 // Where we're going we don't need DLTs, so use USER0 (147).
973 // Additional info available via plugin->description() and plugin->event_source().
974 extcap_base_register_interface(extcap_conf
, iter
->first
.c_str(), "Falco plugin", 147, "USER0");
977 ws_warning("Unable to load plugins.");
980 if (g_list_length(extcap_conf
->interfaces
) < 1) {
981 ws_debug("No source plugins found.");
985 help_url
= data_file_url("falcodump.html");
986 extcap_base_set_util_info(extcap_conf
, argv
[0], FALCODUMP_VERSION_MAJOR
, FALCODUMP_VERSION_MINOR
,
987 FALCODUMP_VERSION_RELEASE
, help_url
);
990 help_header
= ws_strdup_printf(
991 " %s --extcap-interfaces\n"
992 " %s --extcap-interface=%s --extcap-capture-filter=<filter>\n"
993 " %s --extcap-interface=%s --extcap-dlts\n"
994 " %s --extcap-interface=%s --extcap-config\n"
995 " %s --extcap-interface=%s --fifo=<filename> --capture --plugin-source=<source url> [--extcap-capture-filter=<filter>]\n",
997 argv
[0], FALCODUMP_PLUGIN_PLACEHOLDER
,
998 argv
[0], FALCODUMP_PLUGIN_PLACEHOLDER
,
999 argv
[0], FALCODUMP_PLUGIN_PLACEHOLDER
,
1000 argv
[0], FALCODUMP_PLUGIN_PLACEHOLDER
);
1001 extcap_help_add_header(extcap_conf
, help_header
);
1002 g_free(help_header
);
1003 extcap_help_add_option(extcap_conf
, "--help", "print this help");
1004 extcap_help_add_option(extcap_conf
, "--version", "print the version");
1005 extcap_help_add_option(extcap_conf
, "--plugin-api-version", "print the Falco plugin API version");
1006 extcap_help_add_option(extcap_conf
, "--plugin-source", "plugin source URL");
1007 extcap_help_add_option(extcap_conf
, "--include-capture-processes", "Include capture processes");
1008 extcap_help_add_option(extcap_conf
, "--include-switch-calls", "Include \"switch\" calls");
1010 for (const auto &it
: plugin_configs
) {
1011 const struct plugin_configuration plugin_config
= it
.second
;
1012 for (const auto &prop
: plugin_config
.property_list
) {
1013 if (prop
.option_index
< OPT_SCHEMA_PROPERTIES_START
) {
1016 extcap_help_add_option(extcap_conf
, g_strdup_printf("--%s", prop
.option
.c_str()), g_strdup(prop
.description
.c_str()));
1024 extcap_help_print(extcap_conf
);
1028 static const std::vector
<ws_option
> longopts
= get_longopts(plugin_configs
);
1029 while ((result
= ws_getopt_long(argc
, argv
, ":", longopts
.data(), &option_idx
)) != -1) {
1034 extcap_help_print(extcap_conf
);
1039 extcap_version_print(extcap_conf
);
1043 case OPT_PLUGIN_API_VERSION
:
1044 fprintf(stdout
, "Falco plugin API version %s\n", inspector
.get_plugin_api_version());
1048 #if defined(HAS_ENGINE_KMOD) || defined(HAS_ENGINE_MODERN_BPF)
1049 case OPT_INCLUDE_CAPTURE_PROCESSES
:
1050 syscall_config
.include_capture_processes
= get_bool_value(ws_optarg
);
1053 case OPT_INCLUDE_SWITCH_CALLS
:
1054 syscall_config
.include_switch_calls
= get_bool_value(ws_optarg
);
1058 case OPT_PLUGIN_SOURCE
:
1059 plugin_source
= ws_optarg
;
1063 /* missing option argument */
1064 ws_warning("Option '%s' requires an argument", argv
[ws_optind
- 1]);
1068 if (result
>= OPT_SCHEMA_PROPERTIES_START
) {
1070 for (auto &it
: plugin_configs
) {
1071 struct plugin_configuration
*plugin_config
= &it
.second
;
1072 for (auto &prop
: plugin_config
->property_list
) {
1073 if (prop
.option_index
== result
) {
1074 prop
.current_value
= ws_optarg
;
1084 ws_warning("Invalid option: %s", argv
[ws_optind
- 1]);
1087 } else if (!extcap_base_parse_options(extcap_conf
, result
- EXTCAP_OPT_LIST_INTERFACES
, ws_optarg
)) {
1088 ws_warning("Invalid option: %s", argv
[ws_optind
- 1]);
1094 extcap_cmdline_debug(argv
, argc
);
1096 if (extcap_base_handle_interface(extcap_conf
)) {
1101 if (extcap_conf
->show_config
) {
1102 #ifdef HAS_ENGINE_KMOD
1103 if (strcmp(extcap_conf
->interface
, KMOD_ENGINE
) == 0)
1105 ret
= show_syscall_config();
1109 #ifdef HAS_ENGINE_MODERN_BPF
1110 if (strcmp(extcap_conf
->interface
, MODERN_BPF_ENGINE
) == 0)
1112 ret
= show_syscall_config();
1117 ret
= show_plugin_config(extcap_conf
->interface
, plugin_configs
.at(extcap_conf
->interface
));
1122 if (extcap_conf
->capture
|| extcap_conf
->capture_filter
) {
1123 bool builtin_capture
= false;
1126 inspector
.set_debug_mode(true);
1127 inspector
.set_log_stderr();
1130 #ifdef HAS_ENGINE_KMOD
1131 if (strcmp(extcap_conf
->interface
, KMOD_ENGINE
) == 0)
1134 inspector
.open_kmod();
1135 builtin_capture
= true;
1136 } catch (sinsp_exception
&e
) {
1137 ws_warning("Unable to open " KMOD_ENGINE
": %s", e
.what());
1142 #ifdef HAS_ENGINE_MODERN_BPF
1143 if (strcmp(extcap_conf
->interface
, MODERN_BPF_ENGINE
) == 0)
1146 inspector
.open_modern_bpf();
1147 builtin_capture
= true;
1148 } catch (sinsp_exception
&e
) {
1149 ws_warning("Unable to open " MODERN_BPF_ENGINE
": %s", e
.what());
1155 if (plugin_source
.empty()) {
1156 if (extcap_conf
->capture
) {
1157 ws_warning("Missing or invalid parameter: --plugin-source");
1159 // XXX Can we bypass this somehow?
1160 fprintf(stdout
, "Validating a capture filter requires a plugin source");
1165 std::shared_ptr
<sinsp_plugin
> plugin_interface
;
1166 const auto plugin_manager
= inspector
.get_plugin_manager();
1167 for (auto &plugin
: plugin_manager
->plugins()) {
1168 if (plugin
->name() == extcap_conf
->interface
) {
1169 plugin_interface
= plugin
;
1173 if (plugin_interface
== nullptr) {
1174 ws_warning("Unable to find interface %s", extcap_conf
->interface
);
1179 std::string init_err
;
1180 plugin_interface
->init(plugin_configs
[extcap_conf
->interface
].json_config().c_str(), init_err
);
1181 if (!init_err
.empty()) {
1182 ws_warning("%s", init_err
.c_str());
1185 #if SINSP_CHECK_VERSION(0, 18, 0)
1186 inspector
.open_plugin(extcap_conf
->interface
, plugin_source
, sinsp_plugin_platform::SINSP_PLATFORM_HOSTINFO
);
1188 inspector
.open_plugin(extcap_conf
->interface
, plugin_source
);
1190 // scap_dump_open handles "-"
1191 } catch (sinsp_exception
&e
) {
1192 ws_warning("%s", e
.what());
1197 if (!extcap_conf
->capture
) {
1198 // Check our filter syntax
1200 sinsp_filter_compiler
compiler(&inspector
, extcap_conf
->capture_filter
);
1202 } catch (sinsp_exception
&e
) {
1203 fprintf(stdout
, "%s", e
.what());
1210 sinsp_dumper dumper
;
1212 dumper
.open(&inspector
, extcap_conf
->fifo
, false);
1213 } catch (sinsp_exception
&e
) {
1215 ws_warning("%s", e
.what());
1219 #if defined(HAS_ENGINE_KMOD) || defined(HAS_ENGINE_MODERN_BPF)
1220 std::string capture_filter
= syscall_capture_filter(syscall_config
, extcap_conf
->capture_filter
);
1221 if (!capture_filter
.empty()) {
1222 ws_debug("Setting filter %s\n", capture_filter
.c_str());
1224 inspector
.set_filter(capture_filter
);
1225 } catch (sinsp_exception
&e
) {
1226 fprintf(stdout
, "%s", e
.what());
1232 if (builtin_capture
) {
1233 inspector
.start_capture();
1237 ws_noisy("Starting capture loop.");
1238 while (!extcap_end_application
) {
1240 int32_t res
= inspector
.next(&evt
);
1243 case SCAP_FILTERED_EVENT
:
1250 ws_noisy("Inspector exited with %d", res
);
1251 extcap_end_application
= true;
1254 } catch (sinsp_exception
&e
) {
1255 ws_warning("%s", e
.what());
1259 ws_noisy("Closing dumper and inspector.");
1260 if (builtin_capture
) {
1261 inspector
.stop_capture();
1267 ws_debug("You should not come here... maybe some parameter missing?");
1271 /* clean up stuff */
1272 extcap_base_cleanup(&extcap_conf
);