By Johanna Amann, Senior Engineer at Corelight

If you are familiar with Bro scripts you have probably encountered redefs, which allow you to change a number of Bro settings. One commonly used redef is Site::local_nets, which lists the networks that Bro considers local.

As the name redef implies, redefs allow the re-definition of already defined constants in Bro. This is often done in local.bro (but can be done in any loaded script-file). To modify Site::local_net, you can use code similar to this:

redef Site::local_nets = +={10.1.0.0/16};

A disadvantage of redefs is that these redefinitions can only be performed when Bro first starts. Afterwards, Site::local_nets is just a normal constant and can no longer be modified.

However, it is clearly desirable to be able to change many of the configuration options that Bro offers at runtime. Having to restart Bro causes Bro to lose all connection state and knowledge that it accumulated. To solve this problem, Corelight has created the Bro configuration framework, which allows changing configuration options at runtime. We designed the configuration framework in a way that is easy to use and unobtrusive, while also giving great power and flexibility when needed. Using the configuration framework in your script only requires minimal changes. To declare a configuration option, you just prefix it with the newly introduced option keyword:

module OurModule;

export {
    option known_networks: set[subnet] = {};
    option enable_feature: bool = F;
    option system_name: string = "testsystem";
}

Options lie in between variables and constants. Like constants, options cannot be assigned to at runtime; trying to manipulate an option will result in an error. However, there are special calls that can be used to modify options at runtime; these are also used internally by the scripts that power the configuration framework; we discuss this further below.

Given those three options defined above, we just need to tell Bro where to find the configuration file. Simply add something akin to this to local.bro:

redef Config::config_files += { "/path/to/config.dat" };

config.dat contains a mapping between the option names and their values:

OurModule::known_networks 10.0.12.0/24,192.168.17.0/24
OurModule::enable_feature  T
OurModule::system_name  prod-1

Now the options are updated automatically each time that config.dat is changed. Additionally, a new log file, config.log contains information about the configuration changes that occurred during runtime.

Behind the scenes, the config framework uses the Bro input framework with a new custom reader. Users familiar with the Bro input framework might be aware that the input framework is usually very strict about the syntax that it requires. This is not true for configuration files: the files need no header lines and either tabs or spaces are accepted as separators.

For more advanced use-cases, it is possible to be notified each time an option changes:

function system_change_handler(ID: string, new_value: string): string
    {
    print fmt("Value changed from system_name to %s", new_value);
    return new_value;
    }
    
event bro_init()
    {
    Option::set_change_handler("OurModule::system_name", system_change_handler);
    }

This code registers a change handler for the OurModule::system_name option. Each time that the option value is changed, the system_change_handler function will be called before the change is performed. As you might already have deduced from the function signature, the change handler also can change the value before it is finally assigned to the option. This allows, for example, checking of parameters values to reject invalid input. It is also possible to chain together multiple change handlers: Option::set_change_handler takes an optional third argument that can specify a priority for the handlers.

Note that change handlers are also extensively used internally by the configuration framework. If you look at the script level source code of the config framework, you can see that change handlers are used for logging the option changes to config.log.

If you inspect the scripts further, you will also notice that the script-level config framework simply catches events from the Input framework and then calls Option::set to set an option to the new value. If you want to change an option yourself during runtime, you can call Option::set directly from a script.

The following figure shows the data flow and the different components that make up the entirety of the config framework:

unnamed (4).png

You can try the configuration framework today! It has been merged into Bro and will be part of Bro 2.6. To try it, either install Bro from source or install one of the nightly builds.