conf-yaml: support glob patterns in include directive

SCConfYamlHandleInclude only accepted explicit filenames. The
rule-files directive has supported shell-style glob expansion via
glob(3) for years, and asymmetry with include has been blocking
clean drop-in conf.d/ style configuration directories.

Refactor SCConfYamlHandleInclude into a thin wrapper that resolves
the path against conf_dirname and, if the input contains glob
metacharacters (*, ?, [), expands it with glob(3) and includes
each match in lexicographic order. A pattern that matches no
files is logged as a warning and not treated as an error, so a
drop-in directory can be empty without breaking startup. Literal
paths skip glob() and call the existing per-file inclusion logic
directly, preserving current behaviour.

Glob expansion needs glob(3), which is not available everywhere.
Windows builds have no glob.h, so an include pattern used to fall
through and be opened as a literal filename, failing with a
confusing errno. On such platforms the pattern is now logged as a
warning and skipped instead. Literal includes are unaffected.

The behaviour is verified by the config-includes-glob-order
suricata-verify test, which checks that every file matching a
pattern is loaded and that matches are included in deterministic
lexicographic order.

Document the new behaviour, including the platform limitation, in
doc/userguide/configuration/includes.rst.

Feature: #8427.
pull/16031/head
Samaresh Kumar Singh 3 months ago
parent bc4c06481c
commit 84a90873e9

@ -49,6 +49,29 @@ is the equivalent of::
address-groups:
HOME_NET: "[192.168.0.0/16,10.0.0.0/8,172.16.0.0/12]"
Glob Patterns
-------------
Filenames in ``include`` may contain shell-style glob metacharacters
(``*``, ``?``, ``[...]``). Patterns are expanded at startup via
``glob(3)`` and each matching file is loaded in lexicographic order. A
pattern that matches no files is logged as a warning and is not treated
as an error, which allows drop-in ``conf.d/``-style directories to be
empty.
::
include:
- /etc/suricata/conf.d/*.yaml
Relative patterns are resolved against the directory of the top-level
configuration file, the same as literal includes.
.. note:: Glob expansion requires ``glob(3)``, which is not available on
all platforms. On platforms without it, notably Windows, an
``include`` containing glob metacharacters is skipped with a warning
and no files are loaded from it. Literal filenames are unaffected.
.. note:: Suricata versions less than 7 required multiple ``include``
statements to be specified to include more than one file. While
Suricata 7.0 still supports this it will issue a deprecation

@ -27,6 +27,9 @@
#include "conf.h"
#include "conf-yaml-loader.h"
#include <yaml.h>
#ifdef HAVE_GLOB_H
#include <glob.h>
#endif
#include "util-path.h"
#include "util-debug.h"
#include "util-unittest.h"
@ -104,18 +107,17 @@ ConfYamlSetConfDirname(const char *filename)
}
/**
* \brief Include a file in the configuration.
* \brief Include a single resolved file in the configuration.
*
* \param parent The configuration node the included configuration will be
* placed at.
* \param filename The filename to include.
* \param filename The fully resolved filename to include.
*
* \retval 0 on success, -1 on failure.
*/
int SCConfYamlHandleInclude(SCConfNode *parent, const char *filename)
static int ConfYamlHandleIncludeOne(SCConfNode *parent, const char *filename)
{
yaml_parser_t parser;
char include_filename[PATH_MAX];
FILE *file = NULL;
int ret = -1;
@ -124,18 +126,9 @@ int SCConfYamlHandleInclude(SCConfNode *parent, const char *filename)
return -1;
}
if (PathIsAbsolute(filename)) {
strlcpy(include_filename, filename, sizeof(include_filename));
}
else {
snprintf(include_filename, sizeof(include_filename), "%s/%s",
conf_dirname, filename);
}
file = fopen(include_filename, "r");
file = fopen(filename, "r");
if (file == NULL) {
SCLogError("Failed to open configuration include file %s: %s", include_filename,
strerror(errno));
SCLogError("Failed to open configuration include file %s: %s", filename, strerror(errno));
goto done;
}
@ -157,6 +150,75 @@ done:
return ret;
}
/**
* \brief Include a file or glob pattern in the configuration.
*
* Relative paths are resolved against the directory of the top-level config
* file. If the input contains glob metacharacters (\c *, \c ?, \c [) the
* pattern is expanded via glob(3) and each match is included in lexicographic
* order. A pattern that matches no files is logged as a warning and not
* treated as an error, to support drop-in `conf.d/` directories.
*
* Glob expansion needs glob(3). On platforms without it, notably Windows, a
* pattern is logged as a warning and skipped instead of being opened as a
* literal filename.
*
* \param parent The configuration node the included configuration will be
* placed at.
* \param filename The filename or glob pattern to include.
*
* \retval 0 on success, -1 on failure.
*/
int SCConfYamlHandleInclude(SCConfNode *parent, const char *filename)
{
char include_filename[PATH_MAX];
if (PathIsAbsolute(filename)) {
strlcpy(include_filename, filename, sizeof(include_filename));
} else {
snprintf(include_filename, sizeof(include_filename), "%s/%s", conf_dirname, filename);
}
const bool is_pattern = strpbrk(filename, "*?[") != NULL;
#ifdef HAVE_GLOB_H
if (is_pattern) {
glob_t globbuf;
int gret = glob(include_filename, 0, NULL, &globbuf);
if (gret == GLOB_NOMATCH) {
SCLogWarning("No files match include pattern %s", include_filename);
return 0;
} else if (gret != 0) {
SCLogError(
"Failed to expand include pattern %s: %s", include_filename, strerror(errno));
return -1;
}
int ret = 0;
for (size_t i = 0; i < (size_t)globbuf.gl_pathc; i++) {
const char *path = globbuf.gl_pathv[i];
SCLogInfo("Including configuration file %s (matched %s).", path, filename);
if (ConfYamlHandleIncludeOne(parent, path) != 0) {
ret = -1;
break;
}
}
globfree(&globbuf);
return ret;
}
#else
if (is_pattern) {
SCLogWarning("Glob patterns in include are not supported on this platform, "
"skipping include %s",
include_filename);
return 0;
}
#endif
return ConfYamlHandleIncludeOne(parent, include_filename);
}
/**
* \brief Parse a YAML layer.
*

Loading…
Cancel
Save