Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Filebeat: Have filesets disabled unless explicitly configured #27526

Merged
merged 7 commits into from
Sep 15, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions CHANGELOG.next.asciidoc
Original file line number Diff line number Diff line change
Expand Up @@ -85,6 +85,7 @@ https://github.com/elastic/beats/compare/v7.0.0-alpha2...master[Check the HEAD d
- Add option for S3 input to work without SQS notification {issue}18205[18205] {pull}27332[27332]
- Fix Crowdstrike ingest pipeline that was creating flattened `process` fields. {issue}27622[27622] {pull}27623[27623]
- Rename `log.path` to `log.file.path` in filestream to be consistent with `log` input and ECS. {pull}27761[27761]
- Only filesets that are explicitly configured will be enabled. {issue}17256[17256] {pull}27526[27526]

*Heartbeat*
- Remove long deprecated `watch_poll` functionality. {pull}27166[27166]
Expand Down
2 changes: 1 addition & 1 deletion filebeat/autodiscover/builder/hints/logs.go
Original file line number Diff line number Diff line change
Expand Up @@ -201,7 +201,7 @@ func (l *logHints) getFilesets(hints common.MapStr, module string) map[string]*f
var configured bool
filesets := make(map[string]*filesetConfig)

moduleFilesets, err := l.registry.ModuleFilesets(module)
moduleFilesets, err := l.registry.ModuleAvailableFilesets(module)
if err != nil {
logp.Err("Error retrieving module filesets: %+v", err)
return nil
Expand Down
13 changes: 13 additions & 0 deletions filebeat/beater/filebeat.go
Original file line number Diff line number Diff line change
Expand Up @@ -113,6 +113,19 @@ func newBeater(b *beat.Beat, plugins PluginFactory, rawConfig *common.Config) (b
}
if !moduleRegistry.Empty() {
logp.Info("Enabled modules/filesets: %s", moduleRegistry.InfoString())
for _, mod := range moduleRegistry.ModuleNames() {
if mod == "" {
continue
}
filesets, err := moduleRegistry.ModuleConfiguredFilesets(mod)
if err != nil {
logp.Err("Failed listing filesets for module %s", mod)
continue
}
if len(filesets) == 0 {
logp.Warn("Module %s is enabled but has no enabled filesets", mod)
}
}
}

moduleInputs, err := moduleRegistry.GetInputConfigs()
Expand Down
4 changes: 4 additions & 0 deletions filebeat/docs/filebeat-modules-options.asciidoc
Original file line number Diff line number Diff line change
Expand Up @@ -75,8 +75,12 @@ The following example shows a configuration that runs the `nginx`,`mysql`, and
----
{beatname_lc}.modules:
- module: nginx
access:
error:
- module: mysql
slowlog:
- module: system
auth:
----

[[advanced-settings]]
Expand Down
75 changes: 51 additions & 24 deletions filebeat/fileset/modules.go
Original file line number Diff line number Diff line change
Expand Up @@ -69,33 +69,13 @@ func newModuleRegistry(modulesPath string,
return nil, fmt.Errorf("error getting filesets for module %s: %v", mcfg.Module, err)
}

for _, filesetName := range moduleFilesets {
fcfg, exists := mcfg.Filesets[filesetName]
if !exists {
fcfg = &FilesetConfig{}
}
for filesetName, fcfg := range mcfg.Filesets {

fcfg, err = applyOverrides(fcfg, mcfg.Module, filesetName, overrides)
if err != nil {
return nil, fmt.Errorf("error applying overrides on fileset %s/%s: %v", mcfg.Module, filesetName, err)
}

if fcfg.Enabled != nil && !(*fcfg.Enabled) {
continue
}

fileset, err := New(modulesPath, filesetName, mcfg, fcfg)
if err != nil {
return nil, err
}
if err = fileset.Read(beatInfo); err != nil {
return nil, fmt.Errorf("error reading fileset %s/%s: %v", mcfg.Module, filesetName, err)
}
reg.registry[mcfg.Module][filesetName] = fileset
}

// check that no extra filesets are configured
for filesetName, fcfg := range mcfg.Filesets {
if fcfg.Enabled != nil && !(*fcfg.Enabled) {
Copy link

@aspacca aspacca Sep 6, 2021

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

due to https://github.com/elastic/beats/pull/27526/files#diff-515d023121b7ec77d64c2a0cfcfe3dc6f9b46e19166e71ac34e5f5cf74fddfe6L74-L76 we could have a case where fcfg.Enabled = nil and the fileset was not skipped.
now fcfc comes from defined filesets: https://github.com/elastic/beats/pull/27526/files#diff-515d023121b7ec77d64c2a0cfcfe3dc6f9b46e19166e71ac34e5f5cf74fddfe6R72, but still it can be the case that enabled: false is not defined.
I'd change this to if fcfg.Enabled == nil || !(*fcfg.Enabled)

what do you think @adriansr ?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This PR maintains the current behavior of the enabled flag: If it's not defined, it defaults to true. So enabled==nil || *enabled is the true check, and enabled!=nil && !(*enabled) is the false check.

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

got it @adriansr, but what do you think about changing this behaviour?

cc @exekias this is related to what we discussed in #27612

We could address in another PR in case, just opening the discussion

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The problem with this change is that it introduces another breaking change, and will potentially break existing configurations for our users. Also it makes configuring filesets from the command line more complicated as it forces users to always include an enabled flag -M mymodule.mydataset.enabled=true.

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I understand your concern, it is actually a breaking change.

The problem I see in enabling by default if not explicitly disabled is that we miss the chance to enforce stricter validation on filesets where there is not a default not failing config (see #27612 ).
In the specific case there's no option to either prevent filebeat from starting if we introduce the validation, or let it start with a misconfigured explicitly enabled fileset that will stop as input.

Also what we have as current behaviour is the possibility of filesets starting and stopping because "implicitly" enabled and not properly configured, producing noisy logs that could be confusing.

Hard choice to take, indeed

continue
}
Expand All @@ -108,6 +88,15 @@ func newModuleRegistry(modulesPath string,
if !found {
return nil, fmt.Errorf("fileset %s/%s is configured but doesn't exist", mcfg.Module, filesetName)
}

fileset, err := New(modulesPath, filesetName, mcfg, fcfg)
if err != nil {
return nil, err
}
if err = fileset.Read(beatInfo); err != nil {
return nil, fmt.Errorf("error reading fileset %s/%s: %v", mcfg.Module, filesetName, err)
}
reg.registry[mcfg.Module][filesetName] = fileset
}
}

Expand Down Expand Up @@ -152,9 +141,30 @@ func NewModuleRegistry(moduleConfigs []*common.Config, beatInfo beat.Info, init
return nil, err
}

enableFilesetsFromOverrides(mcfgs, modulesOverrides)
return newModuleRegistry(modulesPath, mcfgs, modulesOverrides, beatInfo)
}

// enableFilesetsFromOverrides enables in mcfgs the filesets mentioned in overrides,
// so that the overridden configuration can be applied.
func enableFilesetsFromOverrides(mcfgs []*ModuleConfig, overrides *ModuleOverrides) {
if overrides == nil {
return
}
for _, mcfg := range mcfgs {
if modOvr, ok := (*overrides)[mcfg.Module]; ok {
for fset := range modOvr {
if _, ok = mcfg.Filesets[fset]; !ok {
if mcfg.Filesets == nil {
mcfg.Filesets = make(map[string]*FilesetConfig)
}
mcfg.Filesets[fset] = &FilesetConfig{}
}
}
}
}
}

func mcfgFromConfig(cfg *common.Config) (*ModuleConfig, error) {
var mcfg ModuleConfig

Expand All @@ -171,11 +181,18 @@ func mcfgFromConfig(cfg *common.Config) (*ModuleConfig, error) {
}

mcfg.Filesets = map[string]*FilesetConfig{}
for name, filesetConfig := range dict {

// This calls cfg.GetFields() instead of iterating over `dict` keys
// because cfg.Unpack above doesn't return keys that map to a nil value,
// but GetFields() returns all keys. We need to observe filesets that
// don't contain any configuration (all default values).
for _, name := range cfg.GetFields() {
if name == "module" || name == "enabled" || name == "path" {
continue
}

filesetConfig, _ := dict[name] // Nil config if name is not present.

tmpCfg, err := common.NewConfigFrom(filesetConfig)
if err != nil {
return nil, fmt.Errorf("error creating config from fileset %s/%s: %v", mcfg.Module, name, err)
Expand Down Expand Up @@ -400,9 +417,19 @@ func (reg *ModuleRegistry) ModuleNames() []string {
return modules
}

// ModuleFilesets return the list of available filesets for the given module
// ModuleAvailableFilesets return the list of available filesets for the given module
// it returns an empty list if the module doesn't exist
func (reg *ModuleRegistry) ModuleFilesets(module string) ([]string, error) {
func (reg *ModuleRegistry) ModuleAvailableFilesets(module string) ([]string, error) {
modulesPath := paths.Resolve(paths.Home, "module")
return getModuleFilesets(modulesPath, module)
}

// ModuleConfiguredFilesets return the list of configured filesets for the given module
// it returns an empty list if the module doesn't exist
func (reg *ModuleRegistry) ModuleConfiguredFilesets(module string) (list []string, err error) {
filesets, _ := reg.registry[module]
for name := range filesets {
list = append(list, name)
}
return
}
8 changes: 7 additions & 1 deletion filebeat/fileset/modules_integration_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -105,7 +105,13 @@ func TestSetupNginx(t *testing.T) {
require.NoError(t, err)

configs := []*ModuleConfig{
{Module: "nginx"},
{
Module: "nginx",
Filesets: map[string]*FilesetConfig{
"error": {},
"access": {},
},
},
}

reg, err := newModuleRegistry(modulesPath, configs, nil, makeTestInfo("5.2.0"))
Expand Down
180 changes: 175 additions & 5 deletions filebeat/fileset/modules_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -45,11 +45,39 @@ func TestNewModuleRegistry(t *testing.T) {
modulesPath, err := filepath.Abs("../module")
require.NoError(t, err)

falseVar := false

configs := []*ModuleConfig{
{Module: "nginx"},
{Module: "mysql"},
{Module: "system"},
{Module: "auditd"},
{
Module: "nginx",
Filesets: map[string]*FilesetConfig{
"access": {},
"error": {},
"ingress_controller": {
Enabled: &falseVar,
},
},
},
{
Module: "mysql",
Filesets: map[string]*FilesetConfig{
"slowlog": {},
"error": {},
},
},
{
Module: "system",
Filesets: map[string]*FilesetConfig{
"syslog": {},
"auth": {},
},
},
{
Module: "auditd",
Filesets: map[string]*FilesetConfig{
"log": {},
},
},
}

reg, err := newModuleRegistry(modulesPath, configs, nil, beat.Info{Version: "5.2.0"})
Expand All @@ -58,7 +86,7 @@ func TestNewModuleRegistry(t *testing.T) {

expectedModules := map[string][]string{
"auditd": {"log"},
"nginx": {"access", "error", "ingress_controller"},
"nginx": {"access", "error"},
"mysql": {"slowlog", "error"},
"system": {"syslog", "auth"},
}
Expand Down Expand Up @@ -374,6 +402,19 @@ func TestMcfgFromConfig(t *testing.T) {
},
},
},
{
name: "empty fileset (nil)",
config: load(t, map[string]interface{}{
"module": "nginx",
"error": nil,
}),
expected: ModuleConfig{
Module: "nginx",
Filesets: map[string]*FilesetConfig{
"error": {},
},
},
},
}

for _, test := range tests {
Expand Down Expand Up @@ -451,3 +492,132 @@ func TestInterpretError(t *testing.T) {
})
}
}

func TestEnableFilesetsFromOverrides(t *testing.T) {
tests := []struct {
Name string
Cfg []*ModuleConfig
Overrides *ModuleOverrides
Expected []*ModuleConfig
}{
{
Name: "add fileset",
Cfg: []*ModuleConfig{
{
Module: "foo",
Filesets: map[string]*FilesetConfig{
"bar": {},
},
},
},
Overrides: &ModuleOverrides{
"foo": {
"baz": nil,
},
},
Expected: []*ModuleConfig{
{
Module: "foo",
Filesets: map[string]*FilesetConfig{
"bar": {},
"baz": {},
},
},
},
},
{
Name: "defined fileset",
Cfg: []*ModuleConfig{
{
Module: "foo",
Filesets: map[string]*FilesetConfig{
"bar": {
Var: map[string]interface{}{
"a": "b",
},
},
},
},
},
Overrides: &ModuleOverrides{
"foo": {
"bar": nil,
},
},
Expected: []*ModuleConfig{
{
Module: "foo",
Filesets: map[string]*FilesetConfig{
"bar": {
Var: map[string]interface{}{
"a": "b",
},
},
},
},
},
},
{
Name: "disabled module",
Cfg: []*ModuleConfig{
{
Module: "foo",
Filesets: map[string]*FilesetConfig{
"bar": {},
},
},
},
Overrides: &ModuleOverrides{
"other": {
"bar": nil,
},
},
Expected: []*ModuleConfig{
{
Module: "foo",
Filesets: map[string]*FilesetConfig{
"bar": {},
},
},
},
},
{
Name: "nil overrides",
Cfg: []*ModuleConfig{
{
Module: "foo",
Filesets: map[string]*FilesetConfig{
"bar": {},
},
},
},
Overrides: nil,
Expected: []*ModuleConfig{
{
Module: "foo",
Filesets: map[string]*FilesetConfig{
"bar": {},
},
},
},
},
{
Name: "no modules",
Cfg: nil,
Overrides: &ModuleOverrides{
"other": {
"bar": nil,
},
},
Expected: nil,
},
}

for _, test := range tests {
t.Run(test.Name, func(t *testing.T) {
enableFilesetsFromOverrides(test.Cfg, test.Overrides)
assert.Equal(t, test.Expected, test.Cfg)
})
}

}