Rspamd uses the Universal Configuration Language (UCL) for its configuration. The UCL format is described in detail in this document. Rspamd defines several variables and macros to extend UCL functionality.
UCL implements the JSON data model. However, there are a lot of mentions of sections
in Rspamd documentation. You can treat sections as simple sub-objects of the top object. For example, here are two equivalent definitions in UCL
and in JSON
syntax:
section "foo" {
opt = 10;
}
vs
{
"section": {
"foo": {
"opt": 10
}
}
}
UCL also supports a .include
macro which is used extensively in Rspamd configuration. This allows inclusion of another file (or files), to define an override/merge strategy, to define new element priorities, and to do other useful things. Almost all Rspamd sections are followed by two or three include directives. The following is a summary of how the include macro is used in Rspamd. More details are available in the Macros Support section of the UCL document.
.include(param=value,param=value) "filename"
Param/value arguments are options (sometimes called policies) which are applied to the file being included.
The “priority” policy determines how values are overwritten during include. Higher priority elements overwrite lower priority ones.
The “duplicate” policy defines what happens if there are two objects with the same name in both files. The value “merge” on the “duplicate” policy causes a merge of keys from the included file into the current file. (See the Macros Support notes for other values.)
This is how included files are processed:
Here is an example of how .include
is used to achieve a desired configuration.
# fileA.conf
obj {
key = 1;
subobj {
subkey = 2;
}
}
.include(duplicate=merge,priority=1) "fileB.conf"
# fileB.conf
obj {
key = 2;
nkey = 1;
subobj {
subkey = 3;
nsub = 1;
}
}
will form the following configuration:
obj {
key = 2; # fileB overrides fileA
nkey = 1; # added from fileB
subobj {
subkey = 3; # fileB overrides fileA
nsub = 1; # added from fileB
}
}
Typical section in Rspamd (where section is replaced with an actual section name):
section {
default_key = "value";
subsection {
foo = "bar";
}
.include(try=true,priority=5) "${DBDIR}/dynamic/section.conf"
.include(try=true,priority=1,duplicate=merge) "$LOCAL_CONFDIR/local.d/section.conf"
.include(try=true,priority=10) "$LOCAL_CONFDIR/override.d/section.conf"
}
If you want to add or redefine some of those default values, you can use a local.d/section
file:
# local.d/section.conf
new_key = "new_value";
subsection {
foo = "nobar";
}
Note: When an include directive is inside of a section {}
block, the included local.d/override.d files must not also have the same section {}
wrapper. This would result in nested blocks: section { section{} }
. Files referenced by include directives outside of a section must specify the sections being defined, achieving the following pattern: section{} section{}
where the second section overrides or merges with the first.
Rspamd defines some useful variables to use in configuration files, as ${VAR}
, or simply as $VAR
). These variables are replaced with substitution values at run-time. Nested variables like ${ VAR1_${VAR2} }
are NOT supported.
$PREFIX/etc/rspamd/
Configuration files that are installed or updated with Rspamd are not intended to be changed by the user. Rather, files should be overridden in site-specific configuration files. See the quickstart page for details.
Nevertheless, packaging will generally never overwrite configuration files on upgrade if they have been touched by the user. Please read the migration notes carefully if you upgrade Rspamd to a new version for all incompatible configuration changes.
Rspamd configuration starts in file $CONFDIR/rspamd.conf
. That file has a few settings, and includes other files, which themselves include yet more files. The final configuration is the sum of all files processed in the order found.
For each Rspamd “core” file, there is an include to the local.d
folder for local overrides, and another include to the overide.d
folder for final overrides. For clarity on this architecture, see this FAQ and others related to the topic.
The rspamd.conf
file starts by including common.conf
:
.include "$CONFDIR/common.conf"
In common.conf
, a Lua script containing rules is defined, $RULESDIR/rspamd.lua
. This is the main Lua config file for Rspamd, which loads and executes many other Lua programs as modules.
Tip: $RULESDIR may be something like /usr/share/rspamd/rules.
Other files are then included to define rules, and set rule weights and Rspamd actions.
lua = "$RULESDIR/rspamd.lua"
.include "$CONFDIR/metrics.conf"
.include "$CONFDIR/actions.conf"
.include "$CONFDIR/groups.conf"
.include "$CONFDIR/composites.conf"
.include "$CONFDIR/statistic.conf"
.include "$CONFDIR/modules.conf"
.include "$CONFDIR/settings.conf"
Each of those .conf files defines a specific section, as well as .include
files to override each section with local settings.
actions.conf
and groups.conf
..include
directives to files containing details for symbols related to those groups. (See also)modules.conf
file exists merely to .include
all of those files.After those includes, common.conf
ends with a check for final override settings, and a small modules
section:
.include(try=true) "$LOCAL_CONFDIR/rspamd.conf.local"
.include(try=true,priority=10) "$LOCAL_CONFDIR/rspamd.conf.local.override"
.include(try=true,priority=10) "$LOCAL_CONFDIR/rspamd.conf.override"
modules {
path = "${PLUGINSDIR}";
fallback_path = "${SHAREDIR}/lua"; # Legacy path
try_path = "${LOCAL_CONFDIR}/plugins.d/"; # User plugins
}
The modules section defines paths of directories or specific files. If a directory is specified, all files in that folder with a .lua
suffix are loaded as Lua plugins. (The directory path is treated as a *.lua
shell pattern).
Coming back to rspamd.conf
, a global options section is defined, followed by logging configuration.
options {
pidfile = "$RUNDIR/rspamd.pid";
.include "$CONFDIR/options.inc"
.include(try=true; priority=1,duplicate=merge) "$LOCAL_CONFDIR/local.d/options.inc"
.include(try=true; priority=10) "$LOCAL_CONFDIR/override.d/options.inc"
}
.include(try=true; duplicate=merge) "$CONFDIR/cgp.inc"
.include(try=true; priority=1,duplicate=merge) "$LOCAL_CONFDIR/local.d/cgp.inc"
logging {
type = "file";
filename = "$LOGDIR/rspamd.log";
.include "$CONFDIR/logging.inc"
.include(try=true; priority=1,duplicate=merge) "$LOCAL_CONFDIR/local.d/logging.inc"
.include(try=true; priority=10) "$LOCAL_CONFDIR/override.d/logging.inc"
}
Finally, a workers section (code not provided here) is defined using the section "foo" {}
syntax noted above, for worker types: normal, controller, rspamd_proxy, and fuzzy.
Jinja is a modern and designer-friendly templating language for Python, modelled after Django’s templates.
Lupa is a Jinja2 template engine implementation written in Lua and supports Lua syntax within tags and variables.
Starting in version 1.9.1, Rspamd supports Jinja templates with Lua code within Rspamd UCL configuration files. This can be used to implement logic and data transformations, beyond the static assignments and .include
directives noted above. Rspamd itself uses a specific syntax for variable tags: {=
and =}
instead of the traditional {{
and }}
, as these tags could otherwise mean “a table within a table” in Lua.
Templating can be useful to hide secret values from config files, by placing them into environment variables. Rspamd automatically reads environment variables that start with a RSPAMD_
prefix, and pushes them onto the env
variable. For example, RSPAMD_foo=bar
becomes env.foo="bar"
in templates.
The env
variable also contains the following information:
ver_major
- major version (e.g. 1
)ver_minor
- minor version (e.g. 9
)ver_patch
- patch version (e.g. 1
)version
- full version as a string (e.g. 1.9.1
)ver_num
- numeric version as a hex string (e.g. 0x1090100000000
)ver_id
- git ID or release
if not a git buildhostname
- local hostnameYou can also add values, not merely plain strings but any Lua objects, like tables, by specifying additional environment files with the --lua-env
command line argument. The specified Lua program file will be read by the root
user if the Rspamd main process starts as root and then drops privileges.
The Lua program file (or files) when specified multiple times should return a table as a single possible outcome. For example:
return {
var1 = "value",
var2 = {
subvar1 = "foo",
subvar2 = true,
}
}
You can then use the variables as follows in config files:
{% if env.var2.subvar2 %}
foo = {= env.var1 =};
baz = {= env.var2.subvar2 =};
{% endif %}
You can also use this for secure storage of passwords. This code sample would be used in a config file, and demonstrates many details for using Jinja.
# local.d/controller.inc
{% if env.password %}
password = "{= env.password|pbkdf =}"; # Password is encrypted using `catena` PBKDF
{% endif %}
Note that the pipe symbol |
is used to send the password into a Jinja filter for additional processing. The pbkdf
filter (defined in Rspamd) encrypts the password using the PBKDF standard, specifically using the Catena hashing scheme.)
With this enhancement, as of version 1.9.1, your config files should be Jinja safe. However, this also implies that there should be no special sequences like {%
or {=
anywhere in your configuration, other than for Jinja. If you do require these sequences for any reason, you can escape them using {% raw %} and {% endraw %} tags.
Just remember that the standard Jinja documentation describes the use of Python syntax and features, while Rspamd with Lupa facilitates the use of Lua syntax and features. See the Lupa documentation for details.