Falco Plugins Go SDK Walkthrough
Introduction
The Go SDK provides prebuilt constructs and definitions that help developing plugins by abstracting all the complexities related to the bridging between the C and the Go runtimes. The Go SDK takes care of satisfying all the plugin framework requirements without having to deal with the low-level details, by also optimizing the most critical code paths.
The SDK allows developers to choose either from a low-level set of abstractions, or from a more high-level set of packages designed for simplicity and ease of use. The best way to approach the Go SDK is to start by importing a few high-level packages, which is enough to satisfy the majority of use cases.
This section documents the Go SDK at a high-level, please refer to the official Go SDK documentation for deeper details.
Architecture of the Go SDK
Since Falcosecurity plugins run in a C runtime, the Go SDK has been designed to abstract most of the complexity related to writing C-compliant code acceptable by the plugin framework, so that developers can focus on writing Go code only.
At a high level, the SDK is on top of three fundamental packages with different levels of abstractions:
Package
sdk
is a container for all the basic types, definitions, and helpers that are reused across all the SDK parts.Package
sdk/symbols
contains prebuilt implementations for all the C symbols that plugins must export to be accepted by the framework. The prebuilt C symbols are divided in many subpackages, so that each of them can be imported individually to opt-in/opt-out each symbol.Package
sdk/plugins
provide high-level definition and base types for implementing plugin capabilities. This usessdk/symbols
internally and takes care of importing all the prebuilt C symbols required each plugin capability respectively. This is the main entrypoint for developers to write plugins in Go.
Two additional packages ptr
and cgo
are used internally to simplify and optimize the state management and the usage of C-allocated memory pointers.
For some use cases, developers can consider using the SDK layers selectively. This is meaningful only if developers wish to manually write part of the low-level C details of the framework in their plugins, but still want to use some parts of the SDK. However, this is discouraged if not for advanced use cases only. Developers are encouraged to use the sdk/plugins
to build Falcosecurity plugins, which is easier to use and will have less frequent breaking changes.
Further details can be found in the documentation of each package: sdk
, sdk/symbols
, and sdk/plugins
.
Getting Started
The SDK is built on top of a set of minimal composable interfaces describing the behavior of plugins and plugin instances. As such, developing plugins is as easy as defining a struct type representing the plugin itself, ensuring that the mandatory interface methods are defined on it, and then registering it to the SDK.
To use the Go SDK, all you need to import are the sdk
and sdk/plugins
packages. The first contains all the core types and definitions used across the rest of the SDK packages, whereas the latter contains built-in constructs to develop plugins. The subpackages sdk/plugins/source
and sdk/plugins/extractor
contain specialized definitions for the event sourcing and the field extraction capabilities respectively.
The dummy
plugin, documented in the next sections, is a simple example that helps understand how to start writing Go plugins with this SDK. The SDK also provides a set of base examples to get you started with plugin development.
Defining a Plugin with Field Extraction Capability
In the Go SDK, a plugin with field extraction capability is a type implementing the following interface:
// sdk/plugins/extractor
type Plugin interface {
...
Info() *sdk.Info
Init(config string) error
Fields() []sdk.FieldEntry
Extract(req sdk.ExtractRequest, evt sdk.EventReader) error
}
Info()
returns all the info about the plugin. The returned plugins.Info
struct should be filled in by the plugin author and contains fields such as the plugin ID, name, description, etc.
Init()
method is called to initialize a plugin when the framework allocates it. A user-defined configuration string is passed by the framework. This is where the plugin can initialize its internal state and acquire all the resources it needs.
Fields()
returns an array of sdk.FieldEntry
representing all the fields supported by a plugin for extraction. The order of the fields is relevant, as their index is used as an identifier during extraction.
Extract()
extracts the value of one of the supported fields from a given event passed in by the framework. The sdk.ExtractRequest
argument should be used to set the extracted value.
Optional Interfaces
type Destroyer interface {
Destroy()
}
type InitSchema interface {
InitSchema() *SchemaInfo
}
Plugins with field extraction capability can optionally implement the sdk.Destroyer
interface. In that case, Destroy()
will be called when the plugin gets destroyed and can be used to release any allocated resource. they can also also optionally implement the sdk.InitSchema
interface. In that case, InitSchema()
will be used to to return a schema describing the data expected to be passed as a configuration during the plugin initialization. This follows the semantics documented for get_init_schema
. Currently, the schema must follow the JSON Schema specific, which in Go can also be easily auto-generated with external packages (e.g. alecthomas/jsonschema).
Defining a Plugin with Event Sourcing Capability
In the Go SDK, a plugin with event sourcing capability must specify two types, one of the plugin itself and one for the plugin instances, implementing the following interfaces respectively:
// sdk/plugins/source
type Plugin interface {
...
Info() *sdk.Info
Init(config string) error
Open(params string) (Instance, error)
}
// sdk/plugins/source
type Instance interface {
...
NextBatch(pState PluginState, evts EventWriters) (int, error)
}
The source.Plugin
interface has many functions in common with extractor.Plugin
.
Open()
creates a new plugin instance to open a new stream of events. The framework provides the user-defined open parameters to customize the event source. The return value must implement the source.Instance
interface, and its lifecycle ends when the event stream is closed.
The source.Instance
interface represents plugin instances for an opened event stream, and has one mandatory method and a few optional ones.
NextBatch
creates a new batch of events to be pushed in the event stream. The SDK provides a pre-allocated batch to write events into, in order to manage the used memory optimally.
Optional Interfaces
type Destroyer interface {
Destroy()
}
type Closer interface {
Close()
}
type InitSchema interface {
InitSchema() *SchemaInfo
}
type OpenParams interface {
OpenParams() ([]OpenParam, error)
}
type Progresser interface {
Progress(pState sdk.PluginState) (float64, string)
}
type Stringer interface {
String(evt sdk.EventReader) (string, error)
}
Plugins with event sourcing capabilities can optionally implement the sdk.Destroyer
and sdk.InitSchema
interfaces, just like mentioned in the section above.
Additionally, they can also implement the sdk.OpenParams
interface. If requested by the application, the framework may call OpenParams()
before opening the event stream to obtains some suggested values that would valid parameters for Open()
. For more details, see the documentation of list_open_params
.
Plugin instances can optionally implement the sdk.Closer
, sdk.Progresser
, and sdk.Stringer
interfaces. If sdk.Closer
is implemented, the Close()
method is called while closing the event stream and can be used to release the resources used by the plugin instance. If sdk.Progresser
is implemented, the Progress()
method is called by the SDK when the framework requests progress data about the event stream of the plugin instance. Progress()
must return a float64
with a value between 0 and 1 representing the current progress percentage, and a string representation of the same progress value. If sdk.Stringer
is implemented, the String()
method must return a string representation of an event created by the plugin, which is used by the framework as an extraction value of the evt.plugininfo
field. The string representation should be on a single line and contain important information about the event.
Best Practices and Go SDK Prebuilts for Source Instances
Although the Go SDK gives developers high control and flexibility, in the general case implementing the sdk.NextBatcher
interface is not trivial. Custom definitions of source.Instance
require developers to be mindful of the following while implementing the NextBatch()
function:
- It should return as fast as possible and should try to fill-up event batch up to its maximum capacity
- Listen for a timeout of few milliseconds and return the batch in its current state once the timeout is expired
- Conceive the case in which
Close()
is called beforeNextBatch()
has returned. This can potentially happen if the plugin framework receives signals such as SIGINT or SIGTERM - Minimize the number of memory allocations
- Keep returning
sdk.ErrEOF
after returning it the first time
Considering the above, the SDK provides prebuilt implementations of source.Instance
that satisfy a broad range of use cases, so that developers need to define their own type only if they have advanced or custom requirements.
// sdk/plugins/source
func NewPullInstance(pull source.PullFunc, options ...func(*<unexported-type>)) (source.Instance, error)
func NewPushInstance(evtC <-chan source.PushEvent, options ...func(*<unexported-type>)) (source.Instance, error)
source.NewPullInstance
and source.NewPushInstance
are two constructors for SDK-provided source.Instance
implementations that cover the following use cases:
- Pull Model: for when the event source can be implemented sequentially and the time required to generate a sequence of event is deterministic. This is implemented with a functional design, where the passed-in callback is expected to be non-suspensive and to return quickly
- Push Model: for when the event source can be suspensive and there is no time guarantee reguarding when an event gets produced. For instance, this applies for all event sources that generate events from webhook events. Given the event-driven nature of this use case, this is implemented by passing event data in the form of byte slices through a channel
The prebuilt source.Instance
s can be configured in the function constructors by using the Go options pattern. The SDK provides options for configuring and overriding all the default values:
// sdk/plugins/source
func WithInstanceContext(ctx context.Context) func(*<unexported-type>)
func WithInstanceTimeout(timeout time.Duration) func(*<unexported-type>)
func WithInstanceClose(close func()) func(*<unexported-type>)
func WithInstanceBatchSize(size uint32) func(*<unexported-type>)
func WithInstanceEventSize(size uint32) func(*<unexported-type>)
func WithInstanceProgress(progress func() (float64, string)) func(*<unexported-type>)
Here's an example of how the Pull Model prebuilt can be used to implement an event source:
func (m *MyPlugin) Open(params string) (source.Instance, error) {
counter := uint64(0)
pull := func(ctx context.Context, evt sdk.EventWriter) error {
counter++
if err := gob.NewEncoder(evt.Writer()).Encode(counter); err != nil {
return err
}
evt.SetTimestamp(uint64(time.Now().UnixNano()))
return nil
}
return source.NewPullInstance(pull)
}
Registering a Plugin in the SDK
After defining proper types for the plugin, the only thing remaining is to register it in the SDK so that it can be used in the plugin framework.
// sdk/plugins
type FactoryFunc func() plugins.Plugin
// sdk/plugins
func SetFactory(plugins.FactoryFunc)
// sdk/plugins/extractor
func Register(extractor.Plugin)
// sdk/plugins/source
func Register(source.Plugin)
The newly created plugin type need to be registered to the SDK in a Go init
function and through the plugins.SetFactory()
function. plugins.FactoryFunc
is a function type that is used by the SDK to create plugins when requested by the plugin framework. Then, the source.Register()
and extractor.Register()
functions should be invoked inside the body of plugins.FactoryFunc
functions to implement the event sourcing and the field extraction capabilities respectively.
The defined plugin types are expected to implement a given set of methods. Compilation will fail at the Register()
functions if any of the required methods is not defined. Developers are encouraged to compose their structs with plugins.BasePlugin
, and source.BaseInstance
, which provide prebuilt boilerplate for many of those methods. In this way, developers just need to focus on implementing the few plugin-specific methods remaining.
Besides the interface requirements, the defined types can contain arbitrary fields and methods. State variable that must be maintained during the plugin lifecycle (or in the lifecycle of an opened event stream) must be contained in the defined types. In this way, the SDK can guarantee that the state variables are not disposed by the garbage collector.
Interacting with Events
Generating new events, and extracting field values from them, are the hottest path in the plugin framework and can happen at a very high rate. For this reason, the Go SDK optimizes the memory usage as much as possible, avoiding reallocations and copies wherever possible. Internally, this sometimes means reading and writing on C-allocated memory from Go code directly, which is efficient but also very unsafe and can lead to unstable code.
As such, the SDK provides the two sdk.EventReader
and sdk.EventWriter
interfaces, which enable developers to safely read and write from events while still fully leveraging the underlying memory optimizations. sdk.EventReader
gives a read-only view of an event, with accessor methods for all the internal fields, and sdk.EventWriter
does the same in read-only mode.
type EventReader interface {
EventNum() uint64
Timestamp() uint64
Reader() io.ReadSeeker
}
type EventWriter interface {
SetTimestamp(value uint64)
Writer() io.Writer
}
Event data can either be read or written through the standard io.SeekReader
and io.Writer
interfaces, returned by the Reader()
and Writer()
methods respectively. The SDK hides behind these interfaces all the safety and optimization mechanisms.
For plugins with event sourcing capability, a reusable batch of sdk.EventWriter
s is automatically allocated in each plugin source instance after the Open()
method returns. This slab-allocator creates reusable event data by using the default sdk.DefaultBatchSize
and sdk.DefaultEvtSize
constants. Developers can override the automatic allocation to define batches of arbitrary sizes in the Open()
method, by calling the SetEvents()
method on the newly opened plugin instance before returning it. The reusable event batch can be created with the sdk.NewEventWriters
function, that takes the event data size and batch size as arguments.
func NewEventWriters(size, dataSize int64) (EventWriters, error)
Note that the size of the reusable event batch defines the maximum size of each event batch created by the plugin in NextBatch
.
Compiling Plugins
After successfully writing a plugin, all you need is to compile it. Go allows compiling binaries as a C-compliant shared library with the -buildmode=c-shared
flag. The build command will be something looking like:
go build -buildmode=c-shared -o <outname>.so *.go
The SDK takes care of generating all the required C exported functions that the plugin framework needs to load the plugin. Once built, your plugin is ready to be used in the Falcosecurity plugin system.
Example Go Plugin: dummy
This section walks through the implementation of the dummy
. This plugin returns events that are just a number value that increases with each event generated. Each increase is 1 plus a random "jitter" value that ranges from [0:jitter]. The jitter value is provided as configuration to the plugin in plugin_init
. The starting value and the maximum number of events are provided as open parameters to the plugin in plugin_open
.
This will show how the above API functions are actually used in a functional plugin. The source code for this plugin can be found at dummy.go.
Initial Imports
package main
import (
...
"github.com/falcosecurity/plugin-sdk-go/pkg/sdk"
"github.com/falcosecurity/plugin-sdk-go/pkg/sdk/plugins"
"github.com/falcosecurity/plugin-sdk-go/pkg/sdk/plugins/extractor"
"github.com/falcosecurity/plugin-sdk-go/pkg/sdk/plugins/source"
)
Importing the sdk
and sdk/plugins
packages is the first step for developing a Falcosecurity plugin in Go. The sdk
package contains all the core types and definitions used across the other packages of the SDK. The sdk/plugins
package contains prebuilt constructs for defining new plugins.
The sdk/plugins/source
and sdk/plugins/extractor
packages are required to register the event sourcing and field extraction capabilities. dummy
implements both of them.
The Go module falcosecurity/plugin-sdk-go
has its own documentation, which gives deeper insights about the internal architecture of the SDK.
Defining the Plugin
In the Go SDK, plugins are defined by a set of composable tiny interfaces. To define a new plugin, the first step is to define a new struct
type representing the plugin itself, and then register it to the SDK. Plugins with event sourcing capability, like dummy
, must define an additional type representing the opened instance of the plugin event stream.
type PluginConfig struct {
// This reflects potential internal state for the plugin. In
// this case, the plugin is configured with a jitter.
Jitter uint64 `json:"jitter" jsonschema:"description=A random amount added to the sample of each event (Default: 10)"`
}
type Plugin struct {
plugins.BasePlugin
// Will be used to randomize samples
rand *rand.Rand
// Contains the init configuration values
config PluginConfig
}
type PluginInstance struct {
source.BaseInstance
// Copy of the init params from plugin_open()
initParams string
// The number of events to return before EOF
maxEvents uint64
// A count of events returned. Used to count against maxEvents.
counter uint64
// A semi-random numeric value, derived from this value and
// jitter. This is put in every event as the data property.
sample uint64
}
func init() {
plugins.SetFactory(func() plugins.Plugin {
p := &Plugin{}
source.Register(p)
extractor.Register(p)
return p
})
}
Plugin Info
An Info()
method is needed to return a data struct containing all the plugin info. Info()
is a required method for the defined plugin struct type. This plugin defined its info as a set of constants for simplicity, but it's not a requirement.
const (
PluginID uint32 = 3
PluginName = "dummy"
PluginDescription = "Reference plugin for educational purposes"
PluginContact = "github.com/falcosecurity/plugins"
PluginVersion = "0.4.0"
PluginEventSource = "dummy"
)
...
func (m *Plugin) Info() *plugins.Info {
return &plugins.Info{
ID: PluginID,
Name: PluginName,
Description: PluginDescription,
Contact: PluginContact,
Version: PluginVersion,
RequiredAPIVersion: PluginRequiredApiVersion,
EventSource: PluginEventSource,
}
}
...
Initializing/Destroying the Plugin
The mandatory Init()
method serves as an initialization entrypoint for plugins. This is where the user-defined configuration string is passed in by the framework. The internal state of the plugin should be initialized at this level. An error can be returned to abort the plugin initialization.
Defining the Destroy()
method is optional but can be useful if some resource needs to be released before the plugin gets destroyed. The InitSchema()
method is optional too, but it allows the framework to parse the init config automatically, thus avoiding the need of doing it manually inside Init()
.
// Set the config default values.
func (p *PluginConfig) setDefault() {
p.Jitter = 10
}
// This returns a schema representing the configuration expected by the
// plugin to be passed to the Init() method. Defining InitSchema() allows
// the framework to automatically validate the configuration, so that the
// plugin can assume that it to be always be well-formed when passed to Init().
func (p *Plugin) InitSchema() *sdk.SchemaInfo {
// We leverage the jsonschema package to autogenerate the
// JSON Schema definition using reflection from our config struct.
reflector := jsonschema.Reflector{
// all properties are optional by default
RequiredFromJSONSchemaTags: true,
// unrecognized properties don't cause a parsing failures
AllowAdditionalProperties: true,
}
if schema, err := reflector.Reflect(&PluginConfig{}).MarshalJSON(); err == nil {
return &sdk.SchemaInfo{
Schema: string(schema),
}
}
return nil
}
// Since this plugin defines the InitSchema() method, we can assume
// that the configuration is pre-validated by the framework and
// always well-formed according to the provided schema.
func (m *Plugin) Init(cfg string) error {
// initialize state
m.rand = rand.New(rand.NewSource(time.Now().UnixNano()))
// The format of cfg is a json object with a single param
// "jitter", e.g. {"jitter": 10}
// Empty configs are allowed, in which case the default is used.
// Since we provide a schema through InitSchema(), the framework
// guarantees that the config is always well-formed json.
m.config.setDefault()
json.Unmarshal([]byte(cfg), &m.config)
return nil
}
func (m *Plugin) Destroy() {
// nothing to do here
}
Opening/Closing a Stream of Events
A plugin instance is created when the plugin event stream is opened, which can happen more than once during the plugin lifecycle. Plugins with event sourcing capability are required to define an Open()
method that creates a returns a new plugin instance. This is where the framework passes in the user-defined open parameters string.
The plugin instance type returned by Open()
can define an optional Close()
method bundling any additional deinitialization logic to run while closing the event stream.
func (m *Plugin) Open(prms string) (source.Instance, error) {
// The format of params is a json object with two params:
// - "start", which denotes the initial value of sample
// - "maxEvents": which denotes the number of events to return before EOF.
// Example:
// {"start": 1, "maxEvents": 1000}
var obj map[string]uint64
err := json.Unmarshal([]byte(prms), &obj)
if err != nil {
return nil, fmt.Errorf("params %s could not be parsed: %v", prms, err)
}
if _, ok := obj["start"]; !ok {
return nil, fmt.Errorf("params %s did not contain start property", prms)
}
if _, ok := obj["maxEvents"]; !ok {
return nil, fmt.Errorf("params %s did not contain maxEvents property", prms)
}
return &PluginInstance{
initParams: prms,
maxEvents: obj["maxEvents"],
counter: 0,
sample: obj["start"],
}, nil
}
func (m *PluginInstance) Close() {
// nothing to do here
}
Returning new Events
New events are generated in batch by the NextBatch
function. The function is mandatory for plugins with event sourcing capability and must be defined as a method of the plugin instance struct type. The pState
argument is the plugin struct type initialized in Init()
, passed in by the framework for ease of access. The plugin state is passed as an instance of the sdk.PluginState
interface, so a manual cast is required to access the internal state variables defined in the struct type.
The evts
parameter is a sdk-managed batch of events to be used for creating new events. For that, the SDK uses a slab allocator and reuses the same event batch at every iteration to improve performance. The length of the evts
list represents the maximum size of each event batch.
Each element of the batch is an instance of sdk.EventWriter
that provides handy methods to write the event info and data. Event data can be written with the Go io.Writer
interface.
If an error is returned, the SDK returns a failure to the framework and invalidates the current batch. The special errors sdk.ErrTimeout
and sdk.ErrEOF
have a special meaning, and are used to either advise the framework that no new events are currently available, or that the event stream is terminated.
func (m *PluginInstance) NextBatch(pState sdk.PluginState, evts sdk.EventWriters) (int, error) {
// Return EOF if reached maxEvents
if m.counter >= m.maxEvents {
return 0, sdk.ErrEOF
}
// access the plugin state
plugin := pState.(*Plugin)
var n int
var evt sdk.EventWriter
for n = 0; m.counter < m.maxEvents && n < evts.Len(); n++ {
evt = evts.Get(n)
m.counter++
// Increment sample by 1, also add a jitter of [0:jitter]
m.sample += 1 + uint64(plugin.rand.Int63n(int64(plugin.config.Jitter+1)))
// The representation of a dummy event is the sample as a string.
str := strconv.Itoa(int(m.sample))
// It is not mandatory to set the Timestamp of the event (it
// would be filled in by the framework if set to uint_max),
// but it's a good practice.
evt.SetTimestamp(uint64(time.Now().UnixNano()))
_, err := evt.Writer().Write([]byte(str))
if err != nil {
return 0, err
}
}
return n, nil
}
Printing Events As Strings
Plugins with event sourcing capability can optionally have a String()
method to format the contents of events created with a previous call to NextBatch()
. The event data is readable through an instance of sdk.EventReader
provided by the SDK. Internally, this allows safe memory access and an optimal reusage of the same buffer to maximize the performance of hot framework paths.
func (m *Plugin) String(evt sdk.EventReader) (string, error) {
evtBytes, err := ioutil.ReadAll(evt.Reader())
if err != nil {
return "", err
}
evtStr := string(evtBytes)
// The string representation of an event is a json object with the sample
return fmt.Sprintf("{\"sample\": \"%s\"}", evtStr), nil
}
Defining Fields
This dummy plugin has field extraction capability and exports 3 fields:
dummy.value
: the value in the event, as a uint64dummy.strvalue
: the value in the event, as a stringdummy.divisible
: this field takes an argument and returns 1 if the value in the event is divisible by the argument (a numeric divisor). For example, if the value was 12,dummy.divisible[3]
would return 1 for that event.
The Fields()
method returns a slice of sdk.FieldEntry
representing all the supported fields.
func (m *Plugin) Fields() []sdk.FieldEntry {
return []sdk.FieldEntry{
{
Type: "uint64",
Name: "dummy.divisible",
Desc: "Return 1 if the value is divisible by the provided divisor, 0 otherwise",
Arg: sdk.FieldEntryArg{IsRequired: true, IsKey: true},
},
{
Type: "uint64",
Name: "dummy.value",
Desc: "The sample value in the event",
},
{
Type: "string",
Name: "dummy.strvalue",
Desc: "The sample value in the event, as a string",
},
}
}
Extracting Fields
The Extract
method extracts all of the supported fields. The req
parameter allows accessing all the info regarding the field request, such as the field id or name, and the optional user-passed argument. The evt
parameter is an interface that helps reading the event info and data.
The extracted field value must be set through the SetValue
method of sdk.ExtractRequest
. Returning from Extract
without calling SetValue
will signal the SDK that the requested field is not present in the given event.
func (m *Plugin) Extract(req sdk.ExtractRequest, evt sdk.EventReader) error {
evtBytes, err := ioutil.ReadAll(evt.Reader())
if err != nil {
return err
}
evtStr := string(evtBytes)
evtVal, err := strconv.Atoi(evtStr)
if err != nil {
return err
}
switch req.FieldID() {
case 0: // dummy.divisible
divisor, err := strconv.Atoi(req.ArgKey())
if err != nil {
return fmt.Errorf("argument to dummy.divisible %s could not be converted to number", req.ArgKey())
}
if evtVal%divisor == 0 {
req.SetValue(uint64(1))
} else {
req.SetValue(uint64(0))
}
case 1: // dummy.value
req.SetValue(uint64(evtVal))
case 2: // dummy.strvalue
req.SetValue(evtStr)
default:
return fmt.Errorf("no known field: %s", req.Field())
}
return nil
}
Running the Plugin
This plugin can be configured in Falco by adding the following to falco.yaml
file:
plugins:
- name: dummy
library_path: /tmp/my-plugins/dummy/libdummy.so
init_config:
jitter: 10
open_params: '{"start": 1, "maxEvents": 20}'
## Optional
load_plugins: [dummy]
This simple rule prints a Falco alert any time the event number is between 0 and 10, and the sample value is divisible by 3:
- rule: My Dummy Rule
desc: My Desc
condition: evt.num > 0 and evt.num < 10 and dummy.divisible[3] = 1
output: A dummy event (event=%evt.plugininfo sample=%dummy.value sample_str=%dummy.strvalue num=%evt.num)
priority: INFO
source: dummy
Here's what it looks like when run:
$ ./falco -r ../falco-files/dummy_rules.yaml -c ../falco-files/falco.yaml
Wed Feb 2 16:26:35 2022: Falco version 0.31.0 (driver version 319368f1ad778691164d33d59945e00c5752cd27)
Wed Feb 2 16:26:35 2022: Falco initialized with configuration file ../falco-files/falco.yaml
Wed Feb 2 16:26:35 2022: Loading plugin (dummy) from file /tmp/my-plugins/dummy/libdummy.so
Wed Feb 2 16:26:35 2022: Loading rules from file ../rules/dummy_rules.yaml:
Wed Feb 2 16:26:35 2022: Starting internal webserver, listening on port 8765
16:26:35.527827816: Notice A dummy event (event={"sample": "6"} sample=6 sample_str=6 num=1)
16:26:35.527829658: Notice A dummy event (event={"sample": "18"} sample=18 sample_str=18 num=3)
16:26:35.527831048: Notice A dummy event (event={"sample": "33"} sample=33 sample_str=33 num=8)
Events detected: 3
Rule counts by severity:
INFO: 3
Triggered rules by rule name:
My Dummy Rule: 3
Syscall event drop monitoring:
- event drop detected: 0 occurrences
- num times actions taken: 0
Feedback
Was this page helpful?
Glad to hear it! Please tell us how we can improve.
Sorry to hear that. Please tell us how we can improve.