diff --git a/x-pack/elastic-agent/CHANGELOG.asciidoc b/x-pack/elastic-agent/CHANGELOG.asciidoc index 170cbd6fa91..b60423ac6c5 100644 --- a/x-pack/elastic-agent/CHANGELOG.asciidoc +++ b/x-pack/elastic-agent/CHANGELOG.asciidoc @@ -68,4 +68,5 @@ - Pick up version from libbeat {pull}18350[18350] - Use shorter hash for application differentiator {pull}18770[18770] - When not port are specified and the https is used fallback to 443 {pull}18844[18844] +- Change monitoring defaults for agent {pull}18927[18927] - Agent verifies packages before using them {pull}18876[18876] diff --git a/x-pack/elastic-agent/_meta/config/common.p2.yml.tmpl b/x-pack/elastic-agent/_meta/config/common.p2.yml.tmpl index 0e7c950cdd5..58a82bbd3c2 100644 --- a/x-pack/elastic-agent/_meta/config/common.p2.yml.tmpl +++ b/x-pack/elastic-agent/_meta/config/common.p2.yml.tmpl @@ -23,13 +23,13 @@ datasources: - metricset: filesystem dataset: system.filesystem -settings.monitoring: - # enabled turns on monitoring of running processes - enabled: true - # enables log monitoring - logs: true - # enables metrics monitoring - metrics: true +# settings.monitoring: +# # enabled turns on monitoring of running processes +# enabled: true +# # enables log monitoring +# logs: true +# # enables metrics monitoring +# metrics: true # management: # # Mode of management, the Elastic Agent support two modes of operation: diff --git a/x-pack/elastic-agent/docs/elastic-agent_configuration_example.yml b/x-pack/elastic-agent/docs/elastic-agent_configuration_example.yml index e91363998fb..774306f23ba 100644 --- a/x-pack/elastic-agent/docs/elastic-agent_configuration_example.yml +++ b/x-pack/elastic-agent/docs/elastic-agent_configuration_example.yml @@ -23,7 +23,12 @@ outputs: ca_sha256: "7lHLiyp4J8m9kw38SJ7SURJP4bXRZv/BNxyyXkCcE/M=" settings.monitoring: - use_output: monitoring + # enabled turns on monitoring of running processes + enabled: true + # enables log monitoring + logs: true + # enables metrics monitoring + metrics: true # Sets log level. The default log level is info. # Available log levels are: error, warning, info, debug diff --git a/x-pack/elastic-agent/elastic-agent.yml b/x-pack/elastic-agent/elastic-agent.yml index d28cce65ab5..72ed3abc1a9 100644 --- a/x-pack/elastic-agent/elastic-agent.yml +++ b/x-pack/elastic-agent/elastic-agent.yml @@ -29,13 +29,13 @@ datasources: - metricset: filesystem dataset: system.filesystem -settings.monitoring: - # enabled turns on monitoring of running processes - enabled: true - # enables log monitoring - logs: true - # enables metrics monitoring - metrics: true +# settings.monitoring: +# # enabled turns on monitoring of running processes +# enabled: true +# # enables log monitoring +# logs: true +# # enables metrics monitoring +# metrics: true # management: # # Mode of management, the Elastic Agent support two modes of operation: diff --git a/x-pack/elastic-agent/pkg/agent/application/monitoring_decorator.go b/x-pack/elastic-agent/pkg/agent/application/monitoring_decorator.go index 89328dd05b2..5bd02888aee 100644 --- a/x-pack/elastic-agent/pkg/agent/application/monitoring_decorator.go +++ b/x-pack/elastic-agent/pkg/agent/application/monitoring_decorator.go @@ -20,6 +20,8 @@ const ( outputKey = "output" enabledKey = "settings.monitoring.enabled" + logsKey = "settings.monitoring.logs" + metricsKey = "settings.monitoring.metrics" outputsKey = "outputs" elasticsearchKey = "elasticsearch" typeKey = "type" @@ -35,42 +37,48 @@ func injectMonitoring(outputGroup string, rootAst *transpiler.AST, programsToRun }, } - var config map[string]interface{} + config := make(map[string]interface{}) + // if monitoring is not specified use default one where everything is enabled if _, found := transpiler.Lookup(rootAst, monitoringKey); !found { - config = make(map[string]interface{}) - config[enabledKey] = false - } else { - // get monitoring output name to be used - monitoringOutputName := defaultOutputName - useOutputNode, found := transpiler.Lookup(rootAst, monitoringUseOutputKey) - if found { - - monitoringOutputNameKey, ok := useOutputNode.Value().(*transpiler.StrVal) - if !ok { - return programsToRun, nil - } - - monitoringOutputName = monitoringOutputNameKey.String() - } + monitoringNode := transpiler.NewDict([]transpiler.Node{ + transpiler.NewKey("enabled", transpiler.NewBoolVal(true)), + transpiler.NewKey("logs", transpiler.NewBoolVal(true)), + transpiler.NewKey("metrics", transpiler.NewBoolVal(true)), + transpiler.NewKey("use_output", transpiler.NewStrVal("default")), + }) - ast := rootAst.Clone() - if err := getMonitoringRule(monitoringOutputName).Apply(ast); err != nil { - return programsToRun, err - } + transpiler.Insert(rootAst, transpiler.NewKey("monitoring", monitoringNode), "settings") + } - config, err = ast.Map() - if err != nil { - return programsToRun, err + // get monitoring output name to be used + monitoringOutputName := defaultOutputName + useOutputNode, found := transpiler.Lookup(rootAst, monitoringUseOutputKey) + if found { + monitoringOutputNameKey, ok := useOutputNode.Value().(*transpiler.StrVal) + if !ok { + return programsToRun, nil } - programList := make([]string, 0, len(programsToRun)) - for _, p := range programsToRun { - programList = append(programList, p.Spec.Cmd) - } - // making program list part of the config - // so it will get regenerated with every change - config[programsKey] = programList + monitoringOutputName = monitoringOutputNameKey.String() + } + + ast := rootAst.Clone() + if err := getMonitoringRule(monitoringOutputName).Apply(ast); err != nil { + return programsToRun, err + } + + config, err = ast.Map() + if err != nil { + return programsToRun, err + } + + programList := make([]string, 0, len(programsToRun)) + for _, p := range programsToRun { + programList = append(programList, p.Spec.Cmd) } + // making program list part of the config + // so it will get regenerated with every change + config[programsKey] = programList monitoringProgram.Config, err = transpiler.NewAST(config) if err != nil { diff --git a/x-pack/elastic-agent/pkg/agent/application/monitoring_decorator_test.go b/x-pack/elastic-agent/pkg/agent/application/monitoring_decorator_test.go index e26a6d57e3f..fb6db79d0a8 100644 --- a/x-pack/elastic-agent/pkg/agent/application/monitoring_decorator_test.go +++ b/x-pack/elastic-agent/pkg/agent/application/monitoring_decorator_test.go @@ -82,6 +82,158 @@ GROUPLOOP: } } +func TestMonitoringInjectionDefaults(t *testing.T) { + ast, err := transpiler.NewAST(inputConfigMapDefaults) + if err != nil { + t.Fatal(err) + } + + programsToRun, err := program.Programs(ast) + if err != nil { + t.Fatal(err) + } + +GROUPLOOP: + for group, ptr := range programsToRun { + programsCount := len(ptr) + newPtr, err := injectMonitoring(group, ast, ptr) + if err != nil { + t.Error(err) + continue GROUPLOOP + } + + if programsCount+1 != len(newPtr) { + t.Errorf("incorrect programs to run count, expected: %d, got %d", programsCount+1, len(newPtr)) + continue GROUPLOOP + } + + for _, p := range newPtr { + if p.Spec.Name != monitoringName { + continue + } + + cm, err := p.Config.Map() + if err != nil { + t.Error(err) + continue GROUPLOOP + } + + outputCfg, found := cm[outputKey] + if !found { + t.Errorf("output not found for '%s'", group) + continue GROUPLOOP + } + + outputMap, ok := outputCfg.(map[string]interface{}) + if !ok { + t.Errorf("output is not a map for '%s'", group) + continue GROUPLOOP + } + + esCfg, found := outputMap["elasticsearch"] + if !found { + t.Errorf("elasticsearch output not found for '%s'", group) + continue GROUPLOOP + } + + esMap, ok := esCfg.(map[string]interface{}) + if !ok { + t.Errorf("output.elasticsearch is not a map for '%s'", group) + continue GROUPLOOP + } + + if uname, found := esMap["username"]; !found { + t.Errorf("output.elasticsearch.username output not found for '%s'", group) + continue GROUPLOOP + } else if uname != "xxx" { + t.Errorf("output.elasticsearch.username has incorrect value expected '%s', got '%s for %s", "monitoring-uname", uname, group) + continue GROUPLOOP + } + } + } +} + +func TestMonitoringInjectionDisabled(t *testing.T) { + ast, err := transpiler.NewAST(inputConfigMapDisabled) + if err != nil { + t.Fatal(err) + } + + programsToRun, err := program.Programs(ast) + if err != nil { + t.Fatal(err) + } + +GROUPLOOP: + for group, ptr := range programsToRun { + programsCount := len(ptr) + newPtr, err := injectMonitoring(group, ast, ptr) + if err != nil { + t.Error(err) + continue GROUPLOOP + } + + if programsCount+1 != len(newPtr) { + t.Errorf("incorrect programs to run count, expected: %d, got %d", programsCount+1, len(newPtr)) + continue GROUPLOOP + } + + for _, p := range newPtr { + if p.Spec.Name != monitoringName { + continue + } + + cm, err := p.Config.Map() + if err != nil { + t.Error(err) + continue GROUPLOOP + } + + // is enabled set + settingsObj, found := cm["settings"] + if !found { + t.Errorf("settings not found for '%s(%s)': %v", group, p.Spec.Name, cm) + continue GROUPLOOP + } + + settingsMap, ok := settingsObj.(map[string]interface{}) + if !ok { + t.Errorf("settings not a map for '%s(%s)': %v", group, p.Spec.Name, cm) + continue GROUPLOOP + } + + monitoringObj, found := settingsMap["monitoring"] + if !found { + t.Errorf("settings.monitoring not found for '%s(%s)': %v", group, p.Spec.Name, cm) + continue GROUPLOOP + } + + monitoringMap, ok := monitoringObj.(map[string]interface{}) + if !ok { + t.Errorf("settings.monitoring not a map for '%s(%s)': %v", group, p.Spec.Name, cm) + continue GROUPLOOP + } + + enabledVal, found := monitoringMap["enabled"] + if !found { + t.Errorf("monitoring.enabled not found for '%s(%s)': %v", group, p.Spec.Name, cm) + continue GROUPLOOP + } + + monitoringEnabled, ok := enabledVal.(bool) + if !ok { + t.Errorf("settings.monitoring.enabled is not a bool for '%s'", group) + continue GROUPLOOP + } + + if monitoringEnabled { + t.Errorf("settings.monitoring.enabled is enabled, should be disabled for '%s'", group) + continue GROUPLOOP + } + } + } +} + var inputConfigMap = map[string]interface{}{ "settings.monitoring": map[string]interface{}{ "enabled": true, @@ -152,6 +304,137 @@ var inputConfigMap = map[string]interface{}{ }, } +var inputConfigMapDefaults = map[string]interface{}{ + "outputs": map[string]interface{}{ + "default": map[string]interface{}{ + "index_name": "general", + "pass": "xxx", + "type": "elasticsearch", + "url": "xxxxx", + "username": "xxx", + }, + "infosec1": map[string]interface{}{ + "pass": "xxx", + "spool": map[string]interface{}{ + "file": "${path.data}/spool.dat", + }, + "type": "elasticsearch", + "url": "xxxxx", + "username": "xxx", + }, + "monitoring": map[string]interface{}{ + "type": "elasticsearch", + "index_name": "general", + "pass": "xxx", + "url": "xxxxx", + "username": "monitoring-uname", + }, + }, + "datasources": []map[string]interface{}{ + map[string]interface{}{ + "inputs": []map[string]interface{}{ + map[string]interface{}{ + "type": "log", + "streams": []map[string]interface{}{ + map[string]interface{}{"paths": "/xxxx"}, + }, + "processors": []interface{}{ + map[string]interface{}{ + "dissect": map[string]interface{}{ + "tokenizer": "---", + }, + }, + }, + }, + }, + }, + map[string]interface{}{ + "inputs": []map[string]interface{}{ + map[string]interface{}{ + "type": "system/metrics", + "streams": []map[string]interface{}{ + map[string]interface{}{ + "id": "system/metrics-system.core", + "enabled": true, + "dataset": "system.core", + "period": "10s", + "metrics": []string{"percentages"}, + }, + }, + }, + }, + "use_output": "infosec1", + }, + }, +} + +var inputConfigMapDisabled = map[string]interface{}{ + "settings.monitoring": map[string]interface{}{ + "enabled": false, + }, + "outputs": map[string]interface{}{ + "default": map[string]interface{}{ + "index_name": "general", + "pass": "xxx", + "type": "elasticsearch", + "url": "xxxxx", + "username": "xxx", + }, + "infosec1": map[string]interface{}{ + "pass": "xxx", + "spool": map[string]interface{}{ + "file": "${path.data}/spool.dat", + }, + "type": "elasticsearch", + "url": "xxxxx", + "username": "xxx", + }, + "monitoring": map[string]interface{}{ + "type": "elasticsearch", + "index_name": "general", + "pass": "xxx", + "url": "xxxxx", + "username": "monitoring-uname", + }, + }, + "datasources": []map[string]interface{}{ + map[string]interface{}{ + "inputs": []map[string]interface{}{ + map[string]interface{}{ + "type": "log", + "streams": []map[string]interface{}{ + map[string]interface{}{"paths": "/xxxx"}, + }, + "processors": []interface{}{ + map[string]interface{}{ + "dissect": map[string]interface{}{ + "tokenizer": "---", + }, + }, + }, + }, + }, + }, + map[string]interface{}{ + "inputs": []map[string]interface{}{ + map[string]interface{}{ + "type": "system/metrics", + "streams": []map[string]interface{}{ + map[string]interface{}{ + "id": "system/metrics-system.core", + "enabled": true, + "dataset": "system.core", + "period": "10s", + "metrics": []string{"percentages"}, + }, + }, + }, + }, + "use_output": "infosec1", + }, + }, +} + // const inputConfig = `outputs: // default: // index_name: general diff --git a/x-pack/elastic-agent/pkg/agent/transpiler/ast.go b/x-pack/elastic-agent/pkg/agent/transpiler/ast.go index db717241c3c..96fb09f2bb0 100644 --- a/x-pack/elastic-agent/pkg/agent/transpiler/ast.go +++ b/x-pack/elastic-agent/pkg/agent/transpiler/ast.go @@ -64,6 +64,11 @@ type Dict struct { value []Node } +// NewDict creates a new dict with provided nodes. +func NewDict(nodes []Node) *Dict { + return &Dict{nodes} +} + // Find takes a string which is a key and try to find the elements in the associated K/V. func (d *Dict) Find(key string) (Node, bool) { for _, i := range d.value { @@ -116,6 +121,11 @@ type Key struct { value Node } +// NewKey creates a new key with provided name node pair. +func NewKey(name string, val Node) *Key { + return &Key{name, val} +} + func (k *Key) String() string { var sb strings.Builder sb.WriteString(k.name) @@ -229,6 +239,11 @@ type StrVal struct { value string } +// NewStrVal creates a new string value node with provided value. +func NewStrVal(val string) *StrVal { + return &StrVal{val} +} + // Find receive a key and return false since the node is not a List or Dict. func (s *StrVal) Find(key string) (Node, bool) { return nil, false @@ -259,6 +274,11 @@ type IntVal struct { value int } +// NewIntVal creates a new int value node with provided value. +func NewIntVal(val int) *IntVal { + return &IntVal{val} +} + // Find receive a key and return false since the node is not a List or Dict. func (s *IntVal) Find(key string) (Node, bool) { return nil, false @@ -289,6 +309,11 @@ type UIntVal struct { value uint64 } +// NewUIntVal creates a new uint value node with provided value. +func NewUIntVal(val uint64) *UIntVal { + return &UIntVal{val} +} + // Find receive a key and return false since the node is not a List or Dict. func (s *UIntVal) Find(key string) (Node, bool) { return nil, false @@ -320,6 +345,11 @@ type FloatVal struct { value float64 } +// NewFloatVal creates a new float value node with provided value. +func NewFloatVal(val float64) *FloatVal { + return &FloatVal{val} +} + // Find receive a key and return false since the node is not a List or Dict. func (s *FloatVal) Find(key string) (Node, bool) { return nil, false @@ -350,6 +380,11 @@ type BoolVal struct { value bool } +// NewBoolVal creates a new bool value node with provided value. +func NewBoolVal(val bool) *BoolVal { + return &BoolVal{val} +} + // Find receive a key and return false since the node is not a List or Dict. func (s *BoolVal) Find(key string) (Node, bool) { return nil, false diff --git a/x-pack/elastic-agent/pkg/core/plugin/app/monitoring/beats/beats_monitor.go b/x-pack/elastic-agent/pkg/core/plugin/app/monitoring/beats/beats_monitor.go index ae951172e8f..a372c2e571e 100644 --- a/x-pack/elastic-agent/pkg/core/plugin/app/monitoring/beats/beats_monitor.go +++ b/x-pack/elastic-agent/pkg/core/plugin/app/monitoring/beats/beats_monitor.go @@ -23,6 +23,12 @@ type wrappedConfig struct { MonitoringConfig *monitoringConfig.MonitoringConfig `config:"settings.monitoring" yaml:"settings.monitoring"` } +func defaultWrappedConfig() *wrappedConfig { + return &wrappedConfig{ + MonitoringConfig: monitoringConfig.DefaultConfig(), + } +} + // Monitor is a monitoring interface providing information about the way // how beat is monitored type Monitor struct { @@ -32,23 +38,27 @@ type Monitor struct { } // NewMonitor creates a beats monitor. -func NewMonitor(downloadConfig *artifact.Config) *Monitor { +func NewMonitor(downloadConfig *artifact.Config, monitoringCfg *monitoringConfig.MonitoringConfig) *Monitor { + if monitoringCfg == nil { + monitoringCfg = monitoringConfig.DefaultConfig() + } + return &Monitor{ operatingSystem: downloadConfig.OS(), installPath: downloadConfig.InstallPath, - config: &monitoringConfig.MonitoringConfig{}, + config: monitoringCfg, } } // Reload reloads state of the monitoring based on config. func (b *Monitor) Reload(rawConfig *config.Config) error { - cfg := &wrappedConfig{} + cfg := defaultWrappedConfig() if err := rawConfig.Unpack(&cfg); err != nil { return err } if cfg == nil || cfg.MonitoringConfig == nil { - b.config = &monitoringConfig.MonitoringConfig{} + b.config = monitoringConfig.DefaultConfig() } else { b.config = cfg.MonitoringConfig } diff --git a/x-pack/elastic-agent/pkg/core/plugin/app/monitoring/config/config.go b/x-pack/elastic-agent/pkg/core/plugin/app/monitoring/config/config.go index 7f5a5b4bffe..ceb4b3b6a56 100644 --- a/x-pack/elastic-agent/pkg/core/plugin/app/monitoring/config/config.go +++ b/x-pack/elastic-agent/pkg/core/plugin/app/monitoring/config/config.go @@ -10,3 +10,12 @@ type MonitoringConfig struct { MonitorLogs bool `yaml:"logs" config:"logs"` MonitorMetrics bool `yaml:"metrics" config:"metrics"` } + +// DefaultConfig creates a config with pre-set default values. +func DefaultConfig() *MonitoringConfig { + return &MonitoringConfig{ + Enabled: true, + MonitorLogs: true, + MonitorMetrics: true, + } +} diff --git a/x-pack/elastic-agent/pkg/core/plugin/app/monitoring/monitor.go b/x-pack/elastic-agent/pkg/core/plugin/app/monitoring/monitor.go index 4fbffc2d526..954872cd68f 100644 --- a/x-pack/elastic-agent/pkg/core/plugin/app/monitoring/monitor.go +++ b/x-pack/elastic-agent/pkg/core/plugin/app/monitoring/monitor.go @@ -8,6 +8,7 @@ import ( "github.com/elastic/beats/v7/x-pack/elastic-agent/pkg/artifact" "github.com/elastic/beats/v7/x-pack/elastic-agent/pkg/config" "github.com/elastic/beats/v7/x-pack/elastic-agent/pkg/core/plugin/app/monitoring/beats" + monitoringConfig "github.com/elastic/beats/v7/x-pack/elastic-agent/pkg/core/plugin/app/monitoring/config" ) // Monitor is a monitoring interface providing information about the way @@ -27,17 +28,19 @@ type Monitor interface { } type wrappedConfig struct { - DownloadConfig *artifact.Config `yaml:"download" config:"download"` + DownloadConfig *artifact.Config `yaml:"download" config:"download"` + MonitoringConfig *monitoringConfig.MonitoringConfig `config:"settings.monitoring" yaml:"settings.monitoring"` } // NewMonitor creates a monitor based on a process configuration. func NewMonitor(config *config.Config) (Monitor, error) { cfg := &wrappedConfig{ - DownloadConfig: artifact.DefaultConfig(), + DownloadConfig: artifact.DefaultConfig(), + MonitoringConfig: monitoringConfig.DefaultConfig(), } if err := config.Unpack(&cfg); err != nil { return nil, err } - return beats.NewMonitor(cfg.DownloadConfig), nil + return beats.NewMonitor(cfg.DownloadConfig, cfg.MonitoringConfig), nil }