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 <libsinsp/plugin_manager.h>
33 #include <libscap/scap_engines.h>
35 #define WS_LOG_DOMAIN "falcodump"
37 #include <extcap/extcap-base.h>
39 #include <wsutil/application_flavor.h>
40 #include <wsutil/file_util.h>
41 #include <wsutil/filesystem.h>
42 #include <wsutil/json_dumper.h>
43 #include <wsutil/privileges.h>
44 #include <wsutil/utf8_entities.h>
45 #include <wsutil/wsjson.h>
46 #include <wsutil/wslog.h>
48 #define FALCODUMP_VERSION_MAJOR "1"
49 #define FALCODUMP_VERSION_MINOR "0"
50 #define FALCODUMP_VERSION_RELEASE "0"
52 #define FALCODUMP_PLUGIN_PLACEHOLDER "<plugin name>"
54 #define SINSP_CHECK_VERSION(major, minor, micro) \
55 (((SINSP_VERSION_MAJOR << 16) + (SINSP_VERSION_MINOR << 8) + SINSP_VERSION_MICRO) >= ((major << 16) + (minor << 8) + micro))
57 // We load our plugins and fetch their configs before we set our log level.
58 // #define DEBUG_JSON_PARSING
59 // #define DEBUG_SINSP
62 EXTCAP_BASE_OPTIONS_ENUM
,
65 OPT_PLUGIN_API_VERSION
,
66 #if defined(HAS_ENGINE_KMOD) || defined(HAS_ENGINE_MODERN_BPF)
67 OPT_INCLUDE_CAPTURE_PROCESSES
,
68 OPT_INCLUDE_SWITCH_CALLS
,
71 OPT_SCHEMA_PROPERTIES_START
,
74 // "s3DownloadConcurrency": {
76 // "description": "Controls the number of background goroutines used to download S3 files (Default: 1)"
78 struct config_properties
{
80 std::string display
; // "title" property || name
81 std::string option
; // Command line option including leading dashes (lowercase display name)
82 int option_index
; // Starts from OPT_SCHEMA_PROPERTIES_START
83 std::string type
; // "boolean", "integer", "string", "enum", "BEGIN_CONFIG_PROPERTIES", or "END_CONFIG_PROPERTIES"
84 std::string description
;
85 std::string default_value
;
86 std::vector
<std::string
>enum_values
;
87 std::string current_value
;
90 #if defined(HAS_ENGINE_KMOD) || defined(HAS_ENGINE_MODERN_BPF)
91 struct syscall_configuration
{
92 bool include_capture_processes
;
93 bool include_switch_calls
;
97 struct plugin_configuration
{
98 std::vector
<struct config_properties
> property_list
;
100 std::string
json_config() {
101 json_dumper dumper
= {};
102 dumper
.output_string
= g_string_new(NULL
);
104 json_dumper_begin_object(&dumper
);
106 for (const auto &prop
: property_list
) {
107 if (prop
.type
== "BEGIN_CONFIG_PROPERTIES") {
108 json_dumper_set_member_name(&dumper
, prop
.name
.c_str());
109 json_dumper_begin_object(&dumper
);
111 } else if (prop
.type
== "END_CONFIG_PROPERTIES") {
112 json_dumper_end_object(&dumper
);
116 if (prop
.current_value
== prop
.default_value
) {
120 json_dumper_set_member_name(&dumper
, prop
.name
.c_str());
121 if (prop
.type
== "string" || prop
.type
== "selector") {
122 json_dumper_value_string(&dumper
, prop
.current_value
.c_str());
124 json_dumper_value_anyf(&dumper
, "%s", prop
.current_value
.c_str());
128 json_dumper_end_object(&dumper
);
129 json_dumper_finish(&dumper
);
130 std::string config_blob
= dumper
.output_string
->str
;
131 ws_debug("configuration: %s", dumper
.output_string
->str
);
132 g_string_free(dumper
.output_string
, TRUE
);
137 // Read a line without trailing (CR)LF. Returns -1 on failure. Copied from addr_resolv.c.
138 // XXX Use g_file_get_contents or GMappedFile instead?
140 fgetline(char *buf
, int size
, FILE *fp
)
142 if (fgets(buf
, size
, fp
)) {
143 int len
= (int)strcspn(buf
, "\r\n");
150 static const size_t MAX_AWS_LINELEN
= 2048;
151 void print_cloudtrail_aws_profile_config(int arg_num
, const char *display
, const char *description
) {
152 char buf
[MAX_AWS_LINELEN
];
153 char profile_name
[MAX_AWS_LINELEN
];
155 std::set
<std::string
>profiles
;
157 // Look in files as specified in https://docs.aws.amazon.com/cli/latest/userguide/cli-configure-envvars.html
158 char *cred_path
= g_strdup(g_getenv("AWS_SHARED_CREDENTIALS_FILE"));
159 if (cred_path
== NULL
) {
160 cred_path
= g_build_filename(g_get_home_dir(), ".aws", "credentials", (char *)NULL
);
163 aws_fp
= ws_fopen(cred_path
, "r");
166 if (aws_fp
!= NULL
) {
167 while (fgetline(buf
, sizeof(buf
), aws_fp
) >= 0) {
168 if (sscanf(buf
, "[%2047[^]]s]", profile_name
) == 1) {
169 if (strcmp(profile_name
, "default") == 0) {
172 profiles
.insert(profile_name
);
178 char *conf_path
= g_strdup(g_getenv("AWS_CONFIG_FILE"));
179 if (conf_path
== NULL
) {
180 conf_path
= g_build_filename(g_get_home_dir(), ".aws", "config", (char *)NULL
);
183 aws_fp
= ws_fopen(conf_path
, "r");
186 if (aws_fp
!= NULL
) {
187 while (fgetline(buf
, sizeof(buf
), aws_fp
) >= 0) {
188 if (sscanf(buf
, "[profile %2047[^]]s]", profile_name
) == 1) {
189 if (strcmp(profile_name
, "default") == 0) {
192 profiles
.insert(profile_name
);
198 const char *aws_profile_env
= g_getenv("AWS_PROFILE");
199 for (auto &profile
: profiles
) {
200 if (aws_profile_env
&& profile
== aws_profile_env
) {
201 aws_profile_env
= nullptr;
204 if (aws_profile_env
) {
205 profiles
.insert(aws_profile_env
);
210 "{call=--cloudtrail-aws-profile}"
212 "{type=editselector}"
216 arg_num
, display
, description
);
217 printf ("value {arg=%d}{value=}{display=Default}{default=true}\n", arg_num
);
218 for (auto &profile
: profiles
) {
224 arg_num
, profile
.c_str(), profile
.c_str());
228 void print_cloudtrail_aws_region_config(int arg_num
, const char *display
, const char *description
) {
229 // printf ' "%s",\n' $(aws ec2 describe-regions --all-regions --query "Regions[].{Name:RegionName}" --output text) | sort
230 std::set
<std::string
> regions
= {
262 const char *aws_region_env
= g_getenv("AWS_REGION");
263 for (auto ®ion
: regions
) {
264 if (aws_region_env
&& region
== aws_region_env
) {
265 aws_region_env
= nullptr;
268 if (aws_region_env
) {
269 regions
.insert(aws_region_env
);
274 "{call=--cloudtrail-aws-region}"
276 "{type=editselector}"
280 arg_num
, display
, description
);
281 printf ("value {arg=%d}{value=}{display=From profile}{default=true}\n", arg_num
);
283 for (auto ®ion
: regions
) {
289 arg_num
, region
.c_str(), region
.c_str());
294 // Load our plugins. This should match the behavior of the Falco Bridge dissector.
295 static void load_plugins(sinsp
&inspector
) {
298 char *plugin_paths
[] = {
299 // XXX Falco plugins should probably be installed in a path that reflects
300 // the Falco version or its plugin API version.
301 g_build_filename(get_plugins_dir(), "falco", NULL
),
302 g_build_filename(get_plugins_pers_dir(), "falco", NULL
)
305 for (size_t idx
= 0; idx
< 2; idx
++) {
306 char *plugin_path
= plugin_paths
[idx
];
307 if ((dir
= ws_dir_open(plugin_path
, 0, NULL
)) != NULL
) {
308 while ((file
= ws_dir_read_name(dir
)) != NULL
) {
309 char *libname
= g_build_filename(plugin_path
, ws_dir_get_name(file
), NULL
);
311 auto plugin
= inspector
.register_plugin(libname
);
312 ws_debug("Registered plugin %s via %s", plugin
->name().c_str(), libname
);
313 } catch (sinsp_exception
&e
) {
314 ws_warning("%s", e
.what());
324 // Given a key, try to find its value in a JSON object.
325 // Returns (value, true) on success, or (err_str, false) on failure.
326 const std::pair
<const std::string
,bool> find_json_object_value(const std::string
&object_blob
, const std::string
&key
, int value_type
) {
327 std::vector
<jsmntok_t
> tokens
;
328 int num_tokens
= json_parse(object_blob
.c_str(), NULL
, 0);
330 switch (num_tokens
) {
331 case JSMN_ERROR_INVAL
:
332 return std::pair
<std::string
,bool>("invalid", false);
333 case JSMN_ERROR_PART
:
334 return std::pair
<std::string
,bool>("incomplete", false);
339 tokens
.resize(num_tokens
);
340 json_parse(object_blob
.c_str(), tokens
.data(), num_tokens
);
341 for (int idx
= 0; idx
< num_tokens
- 1; idx
++) {
342 jsmntok_t
&k_tok
= tokens
[idx
];
343 jsmntok_t
&v_tok
= tokens
[idx
+1];
344 std::string cur_key
= object_blob
.substr(k_tok
.start
, k_tok
.end
- k_tok
.start
);
345 if (cur_key
== key
&& k_tok
.type
== JSMN_STRING
&& v_tok
.type
== value_type
) {
346 std::string value
= object_blob
.substr(v_tok
.start
, v_tok
.end
- v_tok
.start
);
347 return std::pair
<std::string
,bool>(value
, true);
349 #ifdef DEBUG_JSON_PARSING
350 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
);
353 return std::pair
<const std::string
,bool>("", false);
356 // Given an RFC 6901-style JSON pointer, try to find its value in a JSON object.
357 // Returns (value, true) on success, or (err_str, false) on failure.
358 const std::pair
<const std::string
,bool> find_json_pointer_value(const std::string
&object_blob
, const std::string
&pointer
, int value_type
) {
359 std::string blob
= object_blob
;
360 std::istringstream
ob_stream(pointer
);
362 while (std::getline(ob_stream
, token
, '/')) {
363 if (token
== "#" || token
.empty()) {
366 std::pair
<std::string
,bool> jv
= find_json_object_value(blob
, token
, value_type
);
368 #ifdef DEBUG_JSON_PARSING
369 ws_warning("JSON pointer %s not found at %s", blob
.c_str(), token
.c_str());
371 return std::pair
<std::string
,bool>("", false);
375 #ifdef DEBUG_JSON_PARSING
376 ws_warning("JSON pointer %s = %s ... %s", pointer
.c_str(), blob
.substr(0, 10).c_str(), blob
.substr(blob
.size() - 10, 10).c_str());
378 return std::pair
<const std::string
,bool>(blob
, true);
381 // Convert a JSON array to a string vector.
382 // Returns (vector, true) on success, or (err_str, false) on failure.
383 const std::pair
<std::vector
<std::string
>,bool> get_json_array(const std::string
&array_blob
) {
384 std::vector
<jsmntok_t
> tokens
;
385 int num_tokens
= json_parse(array_blob
.c_str(), NULL
, 0);
387 switch (num_tokens
) {
388 case JSMN_ERROR_INVAL
:
389 return std::pair
<std::vector
<std::string
>,bool>(std::vector
<std::string
>{"invalid"}, false);
390 case JSMN_ERROR_PART
:
392 return std::pair
<std::vector
<std::string
>,bool>(std::vector
<std::string
>{"incomplete"}, false);
398 tokens
.resize(num_tokens
);
399 json_parse(array_blob
.c_str(), tokens
.data(), num_tokens
);
400 std::vector
<std::string
> elements
;
401 // First token is the full array.
402 for (int idx
= 1; idx
< num_tokens
; idx
++) {
403 jsmntok_t
&el_tok
= tokens
[idx
];
404 elements
.push_back(array_blob
.substr(el_tok
.start
, el_tok
.end
- el_tok
.start
));
406 #ifdef DEBUG_JSON_PARSING
407 ws_warning("%s: %d", array_blob
.c_str(), (int)elements
.size());
409 return std::pair
<std::vector
<std::string
>,bool>(elements
, true);
412 // Given a JSON blob containing a schema properties object, add each property to the
413 // given plugin config.
414 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
) {
415 std::vector
<jsmntok_t
> tokens
;
416 int num_tokens
= json_parse(props_blob
.c_str(), NULL
, 0);
418 switch (num_tokens
) {
419 case JSMN_ERROR_INVAL
:
420 return std::pair
<std::string
,bool>("invalid", false);
421 case JSMN_ERROR_PART
:
422 return std::pair
<std::string
,bool>("incomplete", false);
427 tokens
.resize(num_tokens
);
428 json_parse(props_blob
.c_str(), tokens
.data(), num_tokens
);
430 if (tokens
[0].type
!= JSMN_OBJECT
) {
431 return std::pair
<std::string
,bool>("malformed", false);
434 char *plugin_name_lower
= g_ascii_strdown(plugin_name
.c_str(), -1);
436 // We have an object blob which contains a list of properties and property blobs, e.g.
437 // { "property_1": { "type": ... }, "prob_blob_1": { "properties": { "prop_blob_2": { "type": ... } } }
438 // Skip over the outer { ... } and process the contents as pairs.
439 for (int idx
= 1; idx
< num_tokens
- 2; idx
++) {
440 jsmntok_t
&n_tok
= tokens
[idx
];
441 jsmntok_t
&p_tok
= tokens
[idx
+1];
443 std::string name
= props_blob
.substr(n_tok
.start
, n_tok
.end
- n_tok
.start
);
444 std::string display
= name
;
445 std::string property_blob
= props_blob
.substr(p_tok
.start
, p_tok
.end
- p_tok
.start
);
446 std::vector
<std::string
> enum_values
;
448 // XXX Check for errors?
449 int prop_tokens
= json_parse(property_blob
.c_str(), NULL
, 0);
450 switch (prop_tokens
) {
451 case JSMN_ERROR_INVAL
:
452 return std::pair
<std::string
,bool>("invalid property", false);
453 case JSMN_ERROR_PART
:
454 return std::pair
<std::string
,bool>("incomplete property", false);
458 #ifdef DEBUG_JSON_PARSING
459 ws_warning("property %s [%d]\n", name
.c_str(), prop_tokens
);
462 std::pair
<std::string
,bool> jv
= find_json_object_value(property_blob
, "properties", JSMN_OBJECT
);
464 config_properties properties
= {
469 "BEGIN_CONFIG_PROPERTIES",
475 property_list
.push_back(properties
);
476 get_schema_properties(jv
.first
, opt_idx
, option_prefix
+ "-" + name
, plugin_name
, property_list
);
482 "END_CONFIG_PROPERTIES",
488 property_list
.push_back(properties
);
493 jv
= find_json_object_value(property_blob
, "title", JSMN_STRING
);
497 // else split+capitalize "name"?
499 jv
= find_json_object_value(property_blob
, "type", JSMN_STRING
);
501 return std::pair
<std::string
,bool>("missing type", false);
503 std::string type
= jv
.first
;
504 jv
= find_json_object_value(property_blob
, "description", JSMN_STRING
);
506 return std::pair
<std::string
,bool>("missing description", false);
508 std::string description
= jv
.first
;
509 std::string default_value
;
510 jv
= find_json_object_value(property_blob
, "default", JSMN_STRING
);
512 default_value
= jv
.first
;
514 std::string default_pfx
= "(Default: ";
515 size_t pfx_pos
= description
.rfind(default_pfx
);
516 if (pfx_pos
!= std::string::npos
) {
517 default_value
= description
.substr(pfx_pos
+ default_pfx
.size());
518 pfx_pos
= default_value
.rfind(")");
519 default_value
= default_value
.erase(pfx_pos
);
522 jv
= find_json_object_value(property_blob
, "enum", JSMN_ARRAY
);
524 const std::pair
<std::vector
<std::string
>,bool> ja
= get_json_array(jv
.first
);
526 enum_values
= ja
.first
;
530 #ifdef DEBUG_JSON_PARSING
531 ws_warning("%s: %s, %s, [%d]\n", name
.c_str(), type
.c_str(), description
.c_str(), (int)enum_values
.size());
533 const char *call
= g_ascii_strdown(name
.c_str(), -1);
534 config_properties properties
= {
537 std::string() + plugin_name_lower
+ option_prefix
+ "-" + call
, // Command line option (lowercase plugin + display name)
545 property_list
.push_back(properties
);
546 g_free((void *)call
);
550 g_free(plugin_name_lower
);
551 return std::pair
<std::string
,bool>("",true);
554 // Wherein we try to implement a sufficiently complete JSON Schema parser.
555 // Given a plugin config schema like the following:
557 // "$schema": "http://json-schema.org/draft-04/schema#",
558 // "$ref": "#/definitions/PluginConfig",
562 // "s3DownloadConcurrency": {
563 // "type": "integer",
564 // "title": "S3 download concurrency",
565 // "description": "Controls the number of background goroutines used to download S3 files (Default: 1)",
570 // "$schema": "http://json-schema.org/draft-04/schema#",
571 // "$ref": "#/definitions/PluginConfigAWS"
574 // "additionalProperties": true,
577 // "PluginConfigAWS": {
581 // "title": "Shared AWS Config Profile",
582 // "description": "If non-empty",
587 // "additionalProperties": true,
592 // find the plugin properties and parse them using parse_schema_properties.
593 // https://json-schema.org/draft/2020-12/json-schema-validation.html#name-a-vocabulary-for-basic-meta
595 static bool get_plugin_config_schema(const std::shared_ptr
<sinsp_plugin
> &plugin
, plugin_configuration
&plugin_config
)
597 ss_plugin_schema_type schema_type
= SS_PLUGIN_SCHEMA_JSON
;
598 std::string schema_blob
= plugin
->get_init_schema(schema_type
);
599 std::string config_name
;
600 std::pair
<std::string
,bool> jv
;
602 #ifdef DEBUG_JSON_PARSING
603 ws_warning("raw schema: %s\n", schema_blob
.c_str());
607 std::string::size_type ref_pos
= 0;
608 while ((ref_pos
= schema_blob
.find("\"$ref\"", ref_pos
)) != std::string::npos
) {
613 // Dereference all of our $ref pairs.
614 // This is kind of janky, but makes subsequent parsing more simple.
615 for (int ref_idx
= 0; ref_idx
< ref_cnt
; ref_idx
++) {
616 jv
= find_json_object_value(schema_blob
, "$ref", JSMN_STRING
);
620 const std::string ref_pointer
= jv
.first
;
621 jv
= find_json_pointer_value(schema_blob
, ref_pointer
, JSMN_OBJECT
);
623 ws_warning("Unable to find $ref %s.", ref_pointer
.c_str());
626 const std::string ref_body
= jv
.first
.substr(1, jv
.first
.size() - 2);
628 std::vector
<jsmntok_t
> tokens
;
629 int num_tokens
= json_parse(schema_blob
.c_str(), NULL
, 0);
631 switch (num_tokens
) {
632 case JSMN_ERROR_INVAL
:
633 ws_warning("Invalid schema.");
635 case JSMN_ERROR_PART
:
637 ws_warning("Incomplete schema.");
644 #ifdef DEBUG_JSON_PARSING
645 ws_warning("searching for $ref: %s\n", ref_pointer
.c_str());
647 tokens
.resize(num_tokens
);
648 json_parse(schema_blob
.c_str(), tokens
.data(), num_tokens
);
649 std::vector
<std::string
> elements
;
650 // First token is the full array.
651 for (int idx
= 0; idx
< num_tokens
- 1; idx
++) {
652 if (tokens
[idx
].type
!= JSMN_STRING
&& tokens
[idx
+1].type
!= JSMN_STRING
) {
655 auto key_tok
= &tokens
[idx
];
656 auto val_tok
= &tokens
[idx
+1];
657 std::string key
= schema_blob
.substr(key_tok
->start
, key_tok
->end
- key_tok
->start
);
658 std::string value
= schema_blob
.substr(val_tok
->start
, val_tok
->end
- val_tok
->start
);
659 if (key
!= "$ref" || value
!= ref_pointer
) {
663 #ifdef DEBUG_JSON_PARSING
664 ws_warning("replacing: %s\n", schema_blob
.substr(key_tok
->start
- 1, val_tok
->end
- key_tok
->start
+ 2).c_str());
666 schema_blob
.replace(key_tok
->start
- 1, val_tok
->end
- key_tok
->start
+ 2, ref_body
);
667 } catch (std::out_of_range
const&) {
668 ws_warning("Unknown reference %s.", key
.c_str());
674 #ifdef DEBUG_JSON_PARSING
675 ws_warning("cooked schema: %s\n", schema_blob
.c_str());
678 // XXX Should each sub-schema ref be in its own category?
679 jv
= find_json_object_value(schema_blob
, "properties", JSMN_OBJECT
);
681 ws_warning("ERROR: Interface \"%s\" has an %s configuration schema.", plugin
->name().c_str(), schema_blob
.c_str());
684 int opt_idx
= OPT_SCHEMA_PROPERTIES_START
;
685 jv
= get_schema_properties(jv
.first
, opt_idx
, "", plugin
->name(), plugin_config
.property_list
);
687 ws_warning("ERROR: Interface \"%s\" has an unsupported or invalid configuration schema: %s", plugin
->name().c_str(), jv
.first
.c_str());
694 // For each loaded plugin, get its name and properties.
695 static bool get_source_plugins(sinsp
&inspector
, std::map
<std::string
, struct plugin_configuration
> &plugin_configs
) {
696 const auto plugin_manager
= inspector
.get_plugin_manager();
698 // XXX sinsp_plugin_manager::sources() can return different names, e.g. aws_cloudtrail vs cloudtrail.
700 for (auto &plugin
: plugin_manager
->plugins()) {
701 if (plugin
->caps() & CAP_SOURCING
) {
702 plugin_configuration plugin_config
= {};
703 if (!get_plugin_config_schema(plugin
, plugin_config
)) {
706 plugin_configs
[plugin
->name()] = plugin_config
;
709 } catch (sinsp_exception
&e
) {
710 ws_warning("%s", e
.what());
716 // Build our command line options based on our source plugins.
717 static const std::vector
<ws_option
> get_longopts(const std::map
<std::string
, struct plugin_configuration
> &plugin_configs
) {
718 std::vector
<ws_option
> longopts
;
719 struct ws_option base_longopts
[] = {
721 { "help", ws_no_argument
, NULL
, OPT_HELP
},
722 { "version", ws_no_argument
, NULL
, OPT_VERSION
},
723 { "plugin-api-version", ws_no_argument
, NULL
, OPT_PLUGIN_API_VERSION
},
724 #if defined(HAS_ENGINE_KMOD) || defined(HAS_ENGINE_MODERN_BPF)
725 { "include-capture-processes", ws_required_argument
, NULL
, OPT_INCLUDE_CAPTURE_PROCESSES
},
726 { "include-switch-calls", ws_required_argument
, NULL
, OPT_INCLUDE_SWITCH_CALLS
},
728 { "plugin-source", ws_required_argument
, NULL
, OPT_PLUGIN_SOURCE
},
732 for (idx
= 0; base_longopts
[idx
].name
; idx
++) {
733 longopts
.push_back(base_longopts
[idx
]);
735 for (const auto &it
: plugin_configs
) {
736 const struct plugin_configuration plugin_config
= it
.second
;
737 for (const auto &prop
: plugin_config
.property_list
) {
738 ws_option option
= { g_strdup(prop
.option
.c_str()), ws_required_argument
, NULL
, prop
.option_index
};
739 longopts
.push_back(option
);
742 longopts
.push_back(base_longopts
[idx
]);
746 #if defined(HAS_ENGINE_KMOD) || defined(HAS_ENGINE_MODERN_BPF)
748 get_bool_value(const char *bool_str
)
753 switch (bool_str
[0]) {
763 // Show the configuration for a given plugin/interface.
764 static int show_syscall_config(void)
768 "{call=--include-capture-processes}"
769 "{display=Include capture processes}"
771 "{tooltip=Include system calls made by any capture processes (falcodump, dumpcap, and Stratoshark)}"
776 "{call=--include-switch-calls}"
777 "{display=Include \"switch\" calls}"
779 "{tooltip=Include \"switch\" system calls}"
783 "value {arg=0}{value=1}\n"
791 static const std::string
syscall_capture_filter(const struct syscall_configuration
&syscall_config
, const char *capture_filter
)
793 if (syscall_config
.include_capture_processes
&& syscall_config
.include_switch_calls
) {
794 if (capture_filter
) {
795 return std::string(capture_filter
);
797 return std::string();
803 if (capture_filter
) {
804 filter
= "(" + std::string(capture_filter
) + ") and (";
807 if (!syscall_config
.include_capture_processes
) {
808 // We want to exclude Stratoshark and any of its children, including
809 // this one (falcodump).
811 std::string pid
, comm
, _s
, ppid
;
813 // Exclude this process only at a minimum.
814 std::ifstream
stat_stream("/proc/self/stat");
815 stat_stream
>> pid
>> comm
>> _s
>> ppid
;
816 std::string process_filter
= "proc.pid != " + pid
;
817 if (comm
!= "(falcodump)") {
818 ws_warning("Our process is named %s, not falcodump", comm
.c_str());
822 // If our parent is Stratoshark, exclude it and its direct children.
823 std::ifstream
pstat_stream("/proc/" + ppid
+ "/stat");
824 pstat_stream
>> _s
>> comm
;
825 if (comm
== "(stratoshark)") {
826 // XXX Use proc.apid instead?
827 process_filter
= "proc.pid != " + ppid
+ " and proc.ppid != " + ppid
;
829 pstat_stream
.close();
831 filter
+= process_filter
;
834 if (!syscall_config
.include_switch_calls
) {
835 if (!syscall_config
.include_capture_processes
) {
838 filter
+= "evt.type != switch";
841 if (capture_filter
) {
847 #endif // HAS_ENGINE_KMOD || HAS_ENGINE_MODERN_BPF
849 // Show the configuration for a given plugin/interface.
850 static int show_plugin_config(const std::string
&interface
, const struct plugin_configuration
&plugin_config
)
852 unsigned arg_num
= 0;
853 // char* plugin_filter;
855 if (interface
.empty()) {
856 ws_warning("ERROR: No interface specified.");
862 "{call=--plugin-source}"
863 "{display=Log data URL}"
865 "{tooltip=The plugin data source. This is usually a URL.}"
866 "{placeholder=Enter a source URL" UTF8_HORIZONTAL_ELLIPSIS
"}"
870 // if (plugin_filter)
871 // printf("{default=%s}", plugin_filter);
872 for (const auto &properties
: plugin_config
.property_list
) {
873 if (properties
.option_index
< OPT_SCHEMA_PROPERTIES_START
) {
876 std::string default_value
;
877 if (!properties
.default_value
.empty()) {
878 default_value
= "{default=" + properties
.default_value
+ "}";
880 if (properties
.option
== "cloudtrail-aws-profile") {
881 print_cloudtrail_aws_profile_config(arg_num
, properties
.display
.c_str(), properties
.description
.c_str());
882 } else if (properties
.option
== "cloudtrail-aws-region") {
883 print_cloudtrail_aws_region_config(arg_num
, properties
.display
.c_str(), properties
.description
.c_str());
894 arg_num
, properties
.option
.c_str(), properties
.display
.c_str(), properties
.type
.c_str(), default_value
.c_str(), properties
.description
.c_str());
895 if (properties
.enum_values
.size() > 0) {
896 for (const auto &enum_val
: properties
.enum_values
) {
903 arg_num
, enum_val
.c_str(), enum_val
.c_str(), enum_val
== default_value
? "{default=true}" : "");
909 extcap_config_debug(&arg_num
);
914 int main(int argc
, char **argv
)
916 char* configuration_init_error
;
919 int ret
= EXIT_FAILURE
;
920 extcap_parameters
* extcap_conf
= g_new0(extcap_parameters
, 1);
921 std::map
<std::string
, struct plugin_configuration
> plugin_configs
;
922 #if defined(HAS_ENGINE_KMOD) || defined(HAS_ENGINE_MODERN_BPF)
923 struct syscall_configuration syscall_config
= {};
926 char* help_header
= NULL
;
928 std::string plugin_source
;
930 /* Set the program name. */
931 g_set_prgname("falcodump");
933 /* Initialize log handler early so we can have proper logging during startup. */
937 * Get credential information for later use.
939 init_process_policies();
942 * Attempt to get the pathname of the directory containing the
945 configuration_init_error
= configuration_init(argv
[0]);
946 set_application_flavor(APPLICATION_FLAVOR_STRATOSHARK
);
947 if (configuration_init_error
!= NULL
) {
948 ws_warning("Can't get pathname of directory containing the extcap program: %s.",
949 configuration_init_error
);
950 g_free(configuration_init_error
);
953 // Plain eBPF requires extra configuration, so probe for kmod and modern BPF support only for now.
954 #ifdef HAS_ENGINE_KMOD
956 inspector
.open_kmod();
957 extcap_base_register_interface(extcap_conf
, KMOD_ENGINE
, "System calls via kernel module", 147, "USER0");
958 } catch (sinsp_exception
&e
) {
959 ws_warning("Unable to open kmod: %s", e
.what());
963 #ifdef HAS_ENGINE_MODERN_BPF
965 inspector
.open_modern_bpf();
966 extcap_base_register_interface(extcap_conf
, MODERN_BPF_ENGINE
, "System calls via modern eBPF", 147, "USER0");
967 } catch (sinsp_exception
&e
) {
968 ws_warning("Unable to open kmod: %s", e
.what());
973 load_plugins(inspector
);
975 if (get_source_plugins(inspector
, plugin_configs
)) {
976 for (auto iter
= plugin_configs
.begin(); iter
!= plugin_configs
.end(); ++iter
) {
977 // Where we're going we don't need DLTs, so use USER0 (147).
978 // Additional info available via plugin->description() and plugin->event_source().
979 extcap_base_register_interface(extcap_conf
, iter
->first
.c_str(), "Falco plugin", 147, "USER0");
982 ws_warning("Unable to load plugins.");
985 if (g_list_length(extcap_conf
->interfaces
) < 1) {
986 ws_debug("No source plugins found.");
990 help_url
= data_file_url("falcodump.html");
991 extcap_base_set_util_info(extcap_conf
, argv
[0], FALCODUMP_VERSION_MAJOR
, FALCODUMP_VERSION_MINOR
,
992 FALCODUMP_VERSION_RELEASE
, help_url
);
995 help_header
= ws_strdup_printf(
996 " %s --extcap-interfaces\n"
997 " %s --extcap-interface=%s --extcap-capture-filter=<filter>\n"
998 " %s --extcap-interface=%s --extcap-dlts\n"
999 " %s --extcap-interface=%s --extcap-config\n"
1000 " %s --extcap-interface=%s --fifo=<filename> --capture --plugin-source=<source url> [--extcap-capture-filter=<filter>]\n",
1002 argv
[0], FALCODUMP_PLUGIN_PLACEHOLDER
,
1003 argv
[0], FALCODUMP_PLUGIN_PLACEHOLDER
,
1004 argv
[0], FALCODUMP_PLUGIN_PLACEHOLDER
,
1005 argv
[0], FALCODUMP_PLUGIN_PLACEHOLDER
);
1006 extcap_help_add_header(extcap_conf
, help_header
);
1007 g_free(help_header
);
1008 extcap_help_add_option(extcap_conf
, "--help", "print this help");
1009 extcap_help_add_option(extcap_conf
, "--version", "print the version");
1010 extcap_help_add_option(extcap_conf
, "--plugin-api-version", "print the Falco plugin API version");
1011 extcap_help_add_option(extcap_conf
, "--plugin-source", "plugin source URL");
1012 extcap_help_add_option(extcap_conf
, "--include-capture-processes", "Include capture processes");
1013 extcap_help_add_option(extcap_conf
, "--include-switch-calls", "Include \"switch\" calls");
1015 for (const auto &it
: plugin_configs
) {
1016 const struct plugin_configuration plugin_config
= it
.second
;
1017 for (const auto &prop
: plugin_config
.property_list
) {
1018 if (prop
.option_index
< OPT_SCHEMA_PROPERTIES_START
) {
1021 extcap_help_add_option(extcap_conf
, g_strdup_printf("--%s", prop
.option
.c_str()), g_strdup(prop
.description
.c_str()));
1029 extcap_help_print(extcap_conf
);
1033 static const std::vector
<ws_option
> longopts
= get_longopts(plugin_configs
);
1034 while ((result
= ws_getopt_long(argc
, argv
, ":", longopts
.data(), &option_idx
)) != -1) {
1039 extcap_help_print(extcap_conf
);
1044 extcap_version_print(extcap_conf
);
1048 case OPT_PLUGIN_API_VERSION
:
1049 fprintf(stdout
, "Falco plugin API version %s\n", inspector
.get_plugin_api_version());
1053 #if defined(HAS_ENGINE_KMOD) || defined(HAS_ENGINE_MODERN_BPF)
1054 case OPT_INCLUDE_CAPTURE_PROCESSES
:
1055 syscall_config
.include_capture_processes
= get_bool_value(ws_optarg
);
1058 case OPT_INCLUDE_SWITCH_CALLS
:
1059 syscall_config
.include_switch_calls
= get_bool_value(ws_optarg
);
1063 case OPT_PLUGIN_SOURCE
:
1064 plugin_source
= ws_optarg
;
1068 /* missing option argument */
1069 ws_warning("Option '%s' requires an argument", argv
[ws_optind
- 1]);
1073 if (result
>= OPT_SCHEMA_PROPERTIES_START
) {
1075 for (auto &it
: plugin_configs
) {
1076 struct plugin_configuration
*plugin_config
= &it
.second
;
1077 for (auto &prop
: plugin_config
->property_list
) {
1078 if (prop
.option_index
== result
) {
1079 prop
.current_value
= ws_optarg
;
1089 ws_warning("Invalid option: %s", argv
[ws_optind
- 1]);
1092 } else if (!extcap_base_parse_options(extcap_conf
, result
- EXTCAP_OPT_LIST_INTERFACES
, ws_optarg
)) {
1093 ws_warning("Invalid option: %s", argv
[ws_optind
- 1]);
1099 extcap_cmdline_debug(argv
, argc
);
1101 if (extcap_base_handle_interface(extcap_conf
)) {
1106 if (extcap_conf
->show_config
) {
1107 #ifdef HAS_ENGINE_KMOD
1108 if (strcmp(extcap_conf
->interface
, KMOD_ENGINE
) == 0)
1110 ret
= show_syscall_config();
1114 #ifdef HAS_ENGINE_MODERN_BPF
1115 if (strcmp(extcap_conf
->interface
, MODERN_BPF_ENGINE
) == 0)
1117 ret
= show_syscall_config();
1122 ret
= show_plugin_config(extcap_conf
->interface
, plugin_configs
.at(extcap_conf
->interface
));
1127 if (extcap_conf
->capture
|| extcap_conf
->capture_filter
) {
1128 bool builtin_capture
= false;
1131 inspector
.set_debug_mode(true);
1132 inspector
.set_log_stderr();
1135 #ifdef HAS_ENGINE_KMOD
1136 if (strcmp(extcap_conf
->interface
, KMOD_ENGINE
) == 0)
1139 inspector
.open_kmod();
1140 builtin_capture
= true;
1141 } catch (sinsp_exception
&e
) {
1142 ws_warning("Unable to open " KMOD_ENGINE
": %s", e
.what());
1147 #ifdef HAS_ENGINE_MODERN_BPF
1148 if (strcmp(extcap_conf
->interface
, MODERN_BPF_ENGINE
) == 0)
1151 inspector
.open_modern_bpf();
1152 builtin_capture
= true;
1153 } catch (sinsp_exception
&e
) {
1154 ws_warning("Unable to open " MODERN_BPF_ENGINE
": %s", e
.what());
1160 if (plugin_source
.empty()) {
1161 if (extcap_conf
->capture
) {
1162 ws_warning("Missing or invalid parameter: --plugin-source");
1164 // XXX Can we bypass this somehow?
1165 fprintf(stdout
, "Validating a capture filter requires a plugin source");
1170 std::shared_ptr
<sinsp_plugin
> plugin_interface
;
1171 const auto plugin_manager
= inspector
.get_plugin_manager();
1172 for (auto &plugin
: plugin_manager
->plugins()) {
1173 if (plugin
->name() == extcap_conf
->interface
) {
1174 plugin_interface
= plugin
;
1178 if (plugin_interface
== nullptr) {
1179 ws_warning("Unable to find interface %s", extcap_conf
->interface
);
1184 std::string init_err
;
1185 plugin_interface
->init(plugin_configs
[extcap_conf
->interface
].json_config().c_str(), init_err
);
1186 if (!init_err
.empty()) {
1187 ws_warning("%s", init_err
.c_str());
1190 #if SINSP_CHECK_VERSION(0, 18, 0)
1191 inspector
.open_plugin(extcap_conf
->interface
, plugin_source
, sinsp_plugin_platform::SINSP_PLATFORM_HOSTINFO
);
1193 inspector
.open_plugin(extcap_conf
->interface
, plugin_source
);
1195 // scap_dump_open handles "-"
1196 } catch (sinsp_exception
&e
) {
1197 ws_warning("%s", e
.what());
1202 if (!extcap_conf
->capture
) {
1203 // Check our filter syntax
1205 sinsp_filter_compiler
compiler(&inspector
, extcap_conf
->capture_filter
);
1207 } catch (sinsp_exception
&e
) {
1208 fprintf(stdout
, "%s", e
.what());
1215 sinsp_dumper dumper
;
1217 dumper
.open(&inspector
, extcap_conf
->fifo
, false);
1218 } catch (sinsp_exception
&e
) {
1220 ws_warning("%s", e
.what());
1224 #if defined(HAS_ENGINE_KMOD) || defined(HAS_ENGINE_MODERN_BPF)
1225 std::string capture_filter
= syscall_capture_filter(syscall_config
, extcap_conf
->capture_filter
);
1226 if (!capture_filter
.empty()) {
1227 ws_debug("Setting filter %s\n", capture_filter
.c_str());
1229 inspector
.set_filter(capture_filter
);
1230 } catch (sinsp_exception
&e
) {
1231 fprintf(stdout
, "%s", e
.what());
1237 if (builtin_capture
) {
1238 inspector
.start_capture();
1242 ws_noisy("Starting capture loop.");
1243 while (!extcap_end_application
) {
1245 int32_t res
= inspector
.next(&evt
);
1248 case SCAP_FILTERED_EVENT
:
1255 ws_noisy("Inspector exited with %d", res
);
1256 extcap_end_application
= true;
1259 } catch (sinsp_exception
&e
) {
1260 ws_warning("%s", e
.what());
1264 ws_noisy("Closing dumper and inspector.");
1265 if (builtin_capture
) {
1266 inspector
.stop_capture();
1272 ws_debug("You should not come here... maybe some parameter missing?");
1276 /* clean up stuff */
1277 extcap_base_cleanup(&extcap_conf
);